(Win32 : WindowsProgramming-22) Virtual Address Space

Posted by : at

Category : win32   WindowsProgramming


가상 메모리

  • 물리 메모리 : RAM
  • 물리 메모리만으로 프로그램을 돌리기엔 부족하다
  • 잠깐 보관할 공간(paging file)을 HDD/SDD에 할당하자
  • 이런 기법을 이용할 시 훨씬더 많은 메모리 공간을 사용가능 (가상 메모리)

페이지(PAGE)

  • 운영체제가 메모리를 관리 할 때 사용하는 최소 단위
    • x64, x84 : 4K(4096)
    • IA-64 : 8K(8192)
  • 예를들어 10바이트를 할당해 달라고 요청하면 OS는 10바이트를 할당하는게 아니라 4k를 할당후 거기서 10바이트를 쓰게 해주는 식이다

  • Physical Storage
    • 물리 메모리 + Paging file 이라 생각하자

프로세스의 가상 주소공간


   Physical Storage
   |               |            Process A
   |   PAGE 10     |    <----- |  0x1234 |
   |               |           |---------|            Process B
   |   PAGE 20     |    <--------------------------- |  0x1234 |
   |---------------|                                 |---------|

  • 모든 프로세스는 가상주소를 사용
    • 다른 프로세스가 사용하는 메모리에 접근 불가
    • 같은 주소라도 프로세스가 다른 경우 다른 물리 공간을 사용
#include <stdio.h>
#include <Windows.h>

int main()
{
    //# PAGE 크기 구하기
    SYSTEM_INFO si = {0};
    GetSystemInfo(&si);
    printf("PAGE SIZE : %d\n", si.dwPageSize);      // 4K단위로 할당될 것

    //# 물리메모리 크기, Page File 크기 얻기
    MEMORYSTATUSEX mse = {0};
    mse.dwLength = sizeof(mse);

    GlobalMemoryStatusEx(&mse);

    printf("Total Phy Mem : %lld\n", mse.ullTotalPhys);     // 설치된 물리메모리 크기
    printf("Available Phy Mem : %lld\n", mse.ullAvailPhys); // 할당 가능한 물리 메모리 크기
    printf("Total Page File : %lld\n", mse.ullTotalPageFile);   // Paging File의 최대 크기
}

가상 주소 공간 파티션


/* 프로세스 주소공간 */

   0xFFFF FFFF
|               |
|    OS 코드    |   << Kernel Mode Partition >>
| Device Driver |   디바이스 드라이버 코드에서만 접근가능
| Kernel Object |   일반 어플리케이션에서 접근 안됨.
|               |
|  0x8000 0000  |
|               |
|               |   << User Mode Partition >>
|               |   실행파일, DLL, Stack, Heap 등이 매핑되는 공간
|               |
|               |
   0x0000 0000

  • User Mode Partition은 모든 프로세스가 독립적으로 사용한다.
  • Kernel Mode Partition 은 모든 프로세스에서 같은 피지컬 메모리공간을 사용한다.
    • OS코드나 Device Driver등 공통된 부분이 있기때문에 이렇게 하는게 효율적이다
#include <stdio.h>
#include <Windows.h>

int main()
{
    SYSTEM_INFO si = {0};
    GetSystemInfo(&si);
    printf("PAGE SIZE : %d\n", si.dwPageSize);
    printf("Max Addr : %p\n", si.lpMaximumApplicationAddress);
    printf("Max Addr : %p\n", si.lpMinimumApplicationAddress);


    MEMORYSTATUSEX mse = {0};
    mse.dwLength = sizeof(mse);

    GlobalMemoryStatusEx(&mse);

    printf("Total Phy Mem : %lld\n", mse.ullTotalPhys);
    printf("Available Phy Mem : %lld\n", mse.ullAvailPhys);
    printf("Total Page File : %lld\n", mse.ullTotalPageFile);
}

가상주소와 모듈


/* 프로세스 주소공간 */

       0xFFFF FFFF
|  Kernel Mode Partition  |
|-------------------------|
|       0x8000 0000       |
|         XXX.dll         |
|         .exe            | <- 0x0040 0000(실팽파일이 매핑 될 가상주소) 
|                         |     [PE파일에서 주소값을 확인가능]
|                         |
|                         |
       0x0000 0000

  • 특별한 설정을 하지 않는다면 실행파일은 0x0040 0000에 매핑되어 있음.
    • 단, 현재는 보안상의 문제로 무조건 0x0040 0000에 올라가진 않는다
  • .dll, .exe를 모듈이라하며 GetModuleHandle() 함수를 호출시 모듈의 시작주소를 받을 수 있다.
#include <Windows.h>
#include <stdio.h>

void print_module_address(const char* name)
{
    HANDLE hMod = GetModuleHandleA(name);

    if(name == 0) name = ".exe";
    printf("%15s : %p\n", name, hMod);
}

int main()
{
    HANDLE h = GetModuleHandleA("user32.dll");

    print_module_address(0);
    print_module_address("kernel32.dll");
    print_module_address("user32.dll");
    print_module_address("gdi32.dll");
    print_module_address("ntdll.dll");
}

메모리 보호 속성

  • 속성목록

  • 보호 속성 조사 : VirtualQuery()
  • 보호 속성 변경 : VirtualProtect()
int main()
{
    int y = 0;

    MEMORY_BASIC_INFORMATION mbi = {0};
    VirtualQuery(&y, &mbi, sizeof(mbi));    
    // 지역변수(Stack)의 보호 속성을 보자.

    printf("%p : %d\n", &y, mbi.Protect);   
    // PAGE_READWRITE(말 그대로 읽고 쓰기 가능)
}

모든 변수로 확장해 보자.

  • 함수주소(.Text) : PAGE_EXECUTE_READ
  • 전역변수 주소(.data) : PAGE_READWRITE
  • 지역변수 주소(Stack) : PAGE_READWRITE

  • 보호속성의 적용은 PAGE단위로 적용이 된다.
  • Section마다 보호속성이 다르다.

   /* 실행파일 PE 포맷 */               /* 프로세스 가상주소 */

  |---------------------|              |---------------------|
  |                     |              |                     |
  |     PE Header       |              |     PE Header       |
  |                     |              |                     |
  |---------------------|              |---------------------|
  |                     |              |                     |
  |    Section .Text    |              |      .text          |
  |       (함수)        |              |   PAGE_EXECUTE_READ |
  |                     |              |---------------------|
  |---------------------|              |                     |
  |                     |              |      .data          |
  |    Section .data    |              |   PAGE_READWRITE    |
  |  (초기화된 전역변수) |              |---------------------|
  |                     |              |                     |
  |---------------------|              |       ...           |
  |                     |              |                     |
  |                     |              |---------------------|
  |                     |              |     Stack           |
                                            PAGE_READWRITE
            ...

 지역변수는 별도의 Stack에 포함된다.

보호 속성 변경해 보기

#include <stdio.h>
#include <Windows.h>

int main()
{
    char s1[] = "hello";
    char* s2 = "hello";

    *s1 = 'A';  // okay
    *s2 = 'A';  // runtime error
}

왜 에러가 발생?

char s1[] = "hello";        // Stack에 메모리 할당 (PAGE_READWRITE)
char* s2 = "hello";         // Stack에 포인터 변수만 할당, 그럼 실제 문자열은? (Section .rdata(Readonly Data(PE Header))에 할당됨)

   /* 실행파일 PE 포맷 */
  |---------------------|
  |                     |
  |     PE Header       |
  |                     |
  |---------------------|
  |                     |
  |    Section .Text    |
  |       (함수)        |
  |                     |
  |---------------------|
  |                     |
  |    Section .data    |
  |  (초기화된 전역변수) |
  |                     |
  |---------------------|
  |                     |
  |    Section .rdata   |
  |    PAGE_READONLY    |
  |                     |
            ...

#include <stdio.h>
#include <Windows.h>

int main()
{
    char s1[] = "hello";
    char* s2 = "hello";

    DWORD old = 0;
    VirtualProtect(s2, strlen(s2)+1, PAGE_READWRITE, &old);
    // Section .data를 PAGE_READWRITE로 바꿔달라

    *s1 = 'A';
    *s2 = 'A';
}

About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links