관리 메뉴

맨땅에 코딩

메모리 구조 핵심 요약(QEMU·플러그인 관점) 본문

낙서장

메모리 구조 핵심 요약(QEMU·플러그인 관점)

나는 푸딩 2025. 10. 26. 20:01

1. 메모리란?

프로그램이 실행 중일 때 사용하는 저장 공간으로,
CPU는 명령어와 데이터를 "주소(Address)"를 통해 메모리에서 읽고 쓴다.

 

CPU  ←→  [주소로 구분된 칸들로 이루어진 메모리]
  • CPU는 항상 주소 단위로 접근한다.
  • 명령어(mov, add 등)는전부 "주소 기반 데이터 이동"으로 이루어져 있다. 

2. 가상 메모리(Virtual Memory)

운영체제가 프로세스마다 독립적인 '가짜 주소 공간'을 만들어주는 기술
  • 실제 RAM은 한정되어있다.
  • 모든 프로세스가 같은 주소(예: 0x400000)를 쓰면 충돌하기 때문에 OS가 각 프로세스마다 "자기만의 주소공간"을 가짜로 만들어 준다.
프로세스 0x400000의 의미
A.exe A의 코드 영역
B.exe B의 코드 영역

 

이 주소는 실제 RAM 주소가 아니고, “가상 주소(Virtual Address)”다.

 

3. 물리 메모리(Physical Memory)

진짜 RAM 칩의 주소

 

운영체제는 "가상 주소 → 물리 주소"로 변환해 줘야 CPU가 데이터를 읽을 수 있다.

이것을 담당하는 하드웨어가 바로 MMU(Memory Management Unit)이다.

 

4. MMU와 페이지 테이블(Page Table)

MMU는 CPU 안의 주소 변환 담당 유닛이고,
변환 정보를 담은 게 페이지 테이블이다.

 

구조

  • 메모리를 페이지 단위로 쪼갠다.
  • 각 페이지는 "가상 주소의 일부"가 "물리 주소의 일부"로 바뀐다.
  • 변환 정보는 페이지 테이블 엔트리(PTE)에 저장된다.
VA 0x7fff0000 → PA 0x12345000

 

→ 페이지 테이블에 이런 매핑이 있다.

 

5. 페이지 테이블의 계층 구조 (x86_64 기준)

단계 이름 크기 역할
1 PML4 512 entries 최상위
2 PDPT 512 entries 중간
3 PD 512 entries 페이지 디렉터리
4 PT 512 entries 실제 페이지 매핑

 

4계층 구조로 가상주소 → 물리주소 변환이 이루어진다.

이 계층의 루트 주소가 바로 CR3 레지스터에 저장된다.

CR3 → PML4 → PDPT → PD → PT → PTE → 물리주소

 

6. 페이지 프레임 번호(PFN, Page Frame Number)

물리 메모리 페이지의 고유 ID
  • 물리주소(Physical Address)는 “PFN × Page Size + Offset” 으로 계산된다.
  • 즉, PFN은 “페이지 단위 물리주소 인덱스”이다.
Page Size = 4KB
PFN = 0x12345
→ Physical Address = 0x12345 * 0x1000 = 0x12345000

 

플러그인에서는 이 PFN을 기반으로 물리 메모리 추적을 할 수 있다.

 

7. 페이지 폴트(Page Fault)

가상 주소를 물리 주소로 변환할 수 없을 때 발생하는 예외
  • 해당 페이지가 아직 로드되지 않았을 때
  • 접근 권한이 없을 때

QEMU는 이 상황도 정확히 시뮬레이션한다.

플러그인은 이런 이벤트를 MMU trace로 감시할 수 있다.

[PAGE_FAULT] VA=0x7ffdf000 | Reason: Write Access Violation

 

8. 주소 변환 전체 흐름

프로그램: mov eax, [0x7ffdf000]
     ↓
CPU: VA=0x7ffdf000
     ↓
MMU: 페이지 테이블 참조
     ↓
PA=0x12345000 변환
     ↓
메모리에서 값 읽기

 

QEMU 내부에서는 이 전체 과정을 소프트웨어적으로 계산하고,

플러그인은 "이 주소 변환 과정"을 hook 할 수 있다.

 

9. QEMU의 메모리 시뮬레이션 방식

  • QEMU는 "가상 MMU"를 가지고 있다.
  • 게스트 OS가 CR3, 페이지 테이블을 변경하면 QEMU도 내부 상태를 업데이트 한다.
  • 플러그인은 qemu_plugin_register_vcpu_mem_cb()를 통해 메모리 접근 이벤트를 받을 수 있다.
void mem_cb(qemu_plugin_id_t id, unsigned int vcpu_index, 
            qemu_plugin_meminfo_t info, uint64_t vaddr)
{
    printf("[MEM] VA=0x%lx, size=%d, type=%s\n",
           vaddr,
           qemu_plugin_mem_size_shift(info),
           qemu_plugin_mem_is_store(info) ? "WRITE" : "READ");
}

 

10. 스냅샷과 메모리

스냅샷은 "현재 메모리 상태 + CPU 상태 + 디스크 상태"를 통째로 저장한 것이다.
Snapshot = { Memory Dump, Registers, CR3, Page Tables }

 

복원 시

Memory Restore → Registers Restore → Resume Execution

 

이 구조를 이해해야 스냅샷/롤백 기능을 효율적으로 설계할 수 있다.