메모리 맵과 파일 쓰기

메모리 맵과 파일 쓰기

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 #

e6e5fd61-2e3e-4bf5-a30c-b8d3492ba2be

 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}

실행결과 #

물리메모리와 가상메모리 매핑 부터 메모리에 대한 여러 정보를 얻을 수 있다. 762592e4-ea98-41be-b22e-fe0ebd14d79a

파일은 qemu를 실행할때 사용한 disk.img 파일(사실상 부트로더 + 램디스크)을 마운트해보면 위에서 봤던 내용이 파일에 저장된 것을 볼 수 있다.

9e6de493-25f7-45e6-8c0d-35abe5d8b62d

comments powered by Disqus