메모리 맵과 파일 쓰기
2025년 2월 25일
ref #
https://blog.csdn.net/xiaopangzi313/article/details/109928878
https://blog.csdn.net/qq_44807736/article/details/142060638
메모리맵 #
메모리 주소에 어떤것이 매핑되어 있는지를 알 수 있는 테이블? 이다.
예를들어 안드로이드 프로세스 메모리맵은 가상메모리주소+파일+권한 이 매핑되어 있다.
UEFI에서 말하는 메모리맵은 실제 물리 메모리의 어떤 주소들이 어떤 역할을 하고있는지 정해져 있으며, 시작 오프셋(PhysicalStart), 메모리의 타입(Type), 페이지 수(NumberOfPages), 메모리 속성(Attribute) 이 포함된다.
GetMemoryMap API #
1typedef struct {
2 UINT32 Type;
3 EFI_PHYSICAL_ADDRESS PhysicalStart;
4 EFI_VIRTUAL_ADDRESS VirtualStart;
5 UINT64 NumberOfPages;
6 UINT64 Attribute;
7} EFI_MEMORY_DESCRIPTOR;
8
9EFI_STATUS GetMemoryMap(
10 IN OUT UINTN *MemoryMapSize,
11 IN OUT EFI_MEMORY_DESCRIPTOR *MemoryMap,
12 OUT UINTN *MapKey,
13 OUT UINTN *DescriptorSize,
14 OUT UINT32 *DescriptorVersion);
- Return
- EFI_SUCCESS: 메모리맵을 정상적으로 획득한 경우
- EFI_BUFFER_TOO_SMALL: 메모리 영역이 너무 작은 경우
- MemoryMapSize: 메모리맵을 저장할 메모리 영역의 크기를 전달하며, 함수가 정상반환되면 실제 리턴된 메모리맵의 사이즈가 저장된다.
- MemoryMap: 메모리맵을 저장할 메모리의 시작주소를 전달하면, 반환될 때 실제 데이터가 쓰여진다.
- MapKey: 메모리맵의 id 값을 말하며 메모리맵은 UEFI 실행도중 계속 메모리 레이아웃이 변하는데 레이아웃이 변하지 않았음을 식별하기 위한 id 값이 반환된다. 나중에
gBS->ExitBootServices를 호출할때도 사용된다. - DescriptorSize: 실제 물리메모리에 로드된 메모리 디스크립터 구조체의 크기를 말하는데, UEFI 펌웨어가 정렬 등 설정이나 버전에 따라 패딩을 포함시킬 수도 있어서 UEFI 컴파일러가 계산하는 소스코드 상 구조체 크기인
sizeof(EFI_MEMORY_DESCRIPTOR)와 다를 수 있다. - DescriptorVersion: 메모리 디스크립터 구조체의 버전을 리턴해준다.
예제코드 #
UEFI에는 부트서비스와 런타임서비스로 구성되는데 메모리 관련 서비스는 부트서비스이기 때문에 gBS 글로벌 변수를 사용해서 함수를 호출해야한다.
1struct MemoryMap {
2 UINTN buffer_size; // 메모리 맵을 저장할 버퍼 크기
3 VOID* buffer; // 메모리 맵이 저장될 버퍼 포인터
4 UINTN map_size; // 실제 메모리 맵 크기
5 UINTN map_key; // 메모리 맵의 고유 키 (ExitBootServices에서 필요)
6 UINTN descriptor_size; // 각 메모리 디스크립터의 크기
7 UINT32 descriptor_version; // 디스크립터 버전
8};
9
10EFI_STATUS GetMemoryMap(struct MemoryMap* map) {
11 if (map->buffer == NULL) {
12 return EFI_BUFFER_TOO_SMALL;
13 }
14
15 map->map_size = map->buffer_size;
16 return gBS->GetMemoryMap(
17 &map->map_size, // IN OUT UINTN
18 (EFI_MEMORY_DESCRIPTOR*)map->buffer, // IN OUT EFI_MEMORY_DESCRIPTOR
19 &map->map_key, // OUT UINTN
20 &map->descriptor_size, // OUT UINTN
21 &map->descriptor_version); // OUT UINT32
22}
23
24EFI_STATUS EFIAPI UefiMain(
25 EFI_HANDLE image_handle,
26 EFI_SYSTEM_TABLE* system_table) {
27 Print(L"Hello, Mikan World!\n");
28
29 CHAR8 memmap_buf[4096 * 4];
30 struct MemoryMap memmap = {sizeof(memmap_buf), memmap_buf, 0, 0, 0, 0};
31 GetMemoryMap(&memmap);
32
33 // ...
34}
가져온 메모리맵 출력 및 저장 #
1EFI_STATUS EFIAPI UefiMain(
2 EFI_HANDLE image_handle,
3 EFI_SYSTEM_TABLE* system_table) {
4 // ...
5 EFI_FILE_PROTOCOL* root_dir;
6 OpenRootDir(image_handle, &root_dir);
7
8 EFI_FILE_PROTOCOL* memmap_file;
9 root_dir->Open(
10 root_dir, // 루트 디렉터리 핸들
11 &memmap_file, // 열 파일의 핸들을 받을 포인터
12 L"\\memmap", // 열 파일 경로
13 EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, // 파일 모드
14 0);
15
16 SaveMemoryMap(&memmap, memmap_file);
17 memmap_file->Close(memmap_file);
18 while (1);
19 return EFI_SUCCESS;
20}
부트서비스의 OpenProtocol 함수를 사용해서 핸들에 대한 프로토콜(드라이버 인터페이스 등)을 얻을 수 있고 이 프로토콜을 이용해서 어플리케이션이 저장된 디바이스 위치에 새로운 파일을 생성 후 파일핸들을 얻을 수 있다.
1EFI_STATUS OpenRootDir(EFI_HANDLE image_handle, EFI_FILE_PROTOCOL** root) {
2 EFI_LOADED_IMAGE_PROTOCOL* loaded_image;
3 EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* fs;
4
5 // 이미지 핸들은 현재 실행중인 UEFI 어플리케이션을 식별하는 핸들이며 EntryPoint 에서부터 받아온다.
6 // 현재 로드된 어플리케이션 관련 인터페이스를 가져올 수 있다.
7 // ex) DeviceHandle: 현재 어플리케이션이 저장된 장치 핸들 (파일시스템 접근시 사용)
8 // ex2) ImageBase, ImageSize, FilePath, LoadOptions 등
9 gBS->OpenProtocol(
10 image_handle, // 프로토콜이 존재하는 핸들 (디바이스, 드라이버 등)
11 &gEfiLoadedImageProtocolGuid, // EFI_LOADED_IMAGE_PROTOCOL 프로토콜의 GUID
12 (VOID**)&loaded_image, // 인터페이스를 전달 받을 포인터
13 image_handle, // 프로토콜을 여는 주체
14 NULL, // 컨트롤러에 바인딩할때만 사용
15 EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL); // 프로토콜을 여는 방식
16
17 // 어플리케이션이 저장된 장치에서 파일시스템 프로토콜을 가져온다.
18 gBS->OpenProtocol(
19 loaded_image->DeviceHandle,
20 &gEfiSimpleFileSystemProtocolGuid, // EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
21 (VOID**)&fs,
22 image_handle,
23 NULL,
24 EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
25
26 fs->OpenVolume(fs, root); // 루트 디렉터리를 가리키는 핸들
27
28 return EFI_SUCCESS;
29}
획득한 메모리 맵 배열에서 하나씩 꺼내 각 데이터를 파일에 저장한다.
1EFI_STATUS SaveMemoryMap(struct MemoryMap* map, EFI_FILE_PROTOCOL* file) {
2 CHAR8 buf[256];
3 CHAR16 unicode_buf[256];
4 UINTN len;
5
6 CHAR8* header =
7 "Index, Type, Type(name), PhysicalStart, NumberOfPages, Attribute\n";
8 len = AsciiStrLen(header);
9 file->Write(file, &len, header);
10
11 Print(L"map->buffer = %08lx, map->map_size = %08lx\n",
12 map->buffer, map->map_size);
13
14 EFI_PHYSICAL_ADDRESS iter;
15 int i;
16 for (iter = (EFI_PHYSICAL_ADDRESS)map->buffer, i = 0;
17 iter < (EFI_PHYSICAL_ADDRESS)map->buffer + map->map_size;
18 iter += map->descriptor_size, i++) {
19 EFI_MEMORY_DESCRIPTOR* desc = (EFI_MEMORY_DESCRIPTOR*)iter;
20 len = AsciiSPrint(
21 buf, sizeof(buf),
22 "%4u | %08lx -> %08lx | %x, %-25ls, %lx, %lx\n",
23 i, desc->PhysicalStart, desc->VirtualStart, // 메모리타입은 값 -> 문자열로 치환
24 desc->Type, GetMemoryTypeUnicode(desc->Type), desc->NumberOfPages,
25 desc->Attribute & 0xffffflu);
26 DEBUG((DEBUG_INFO, buf));
27 // Ascii -> Unicode 로 변경 후 그대로 Print
28 AsciiStrToUnicodeStrS(buf, unicode_buf, sizeof(unicode_buf) / sizeof(CHAR16));
29 Print(L"%s", unicode_buf);
30
31 file->Write(file, &len, buf);
32 }
33
34 return EFI_SUCCESS;
35}
실행결과 #
물리메모리와 가상메모리 매핑 부터 메모리에 대한 여러 정보를 얻을 수 있다.
파일은 qemu를 실행할때 사용한 disk.img 파일(사실상 부트로더 + 램디스크)을 마운트해보면 위에서 봤던 내용이 파일에 저장된 것을 볼 수 있다.