Download ppt - 64bit 프로그래밍

Transcript

64bit 프로그래밍

정성태 ([email protected])

*** 이 문서는 PPT Template 만을 .NETXPERT 를 썼을 뿐 , 회사의 공식 문서가 아닙니다 .

MSDN Magazine 2006-05x64 Primer – Everything You Need to Know To Start Programming 64-bit Windows Systems

2006.06.10

Win64

IA64 와는 달리 x64 는 기존 x86 시스템과의 호환성을 유지 .

Win64 시스템은 x64 - AMD x64, Intel x64 모두 에서 동작하며 , 이로 인해 응용 프로그램은 단일한 Win64 시스템을 목표로 제작하면 됨 .

이 PPT 에서는 기존 Win32 응용 프로그램을 x64 로 변환하기 위해 필요한 Win64 기반 지식을 다룹니다 .

목차

•OS 세부 구현•x64 CPU 구조•VC++ 로 x64 프로그램 개발•.NET 으로 x64 프로그램 개발

OS 세부 구현

1-1. 주소 공간 – 물리 메모리 한계이론상 가능한 주소 공간 : 2 의 64 승 – 16 exabytes

Windows x64 운영 체제 : 2 의 44 승 – 16 terabytes

차이가 나는 이유 ?

•현재의 x64 CPU 자체의 한계 : 2 의 40 승 까지만 지원 – 1 terabytes

•아키텍쳐 차원에서 확보된 한계 : 2 의 52 승 까지 가능 – 4 petabytes

•늘어난 메모리 만큼 관리되어져야 할 페이지 테이블도 따라서 증가표 [1] 참조

OS 세부 구현

물리 메모리 및 CPU 한계 32-Bit 모델 64-Bit 모델

Windows XP Professional

4GB (1-2 CPUs) 128GB (1-2 CPUs)

Windows Server 2003, Standard Edition

4GB (1-4 CPUs) 32GB (1-4 CPUs)

Windows Server 2003, Enterprise Edition

64GB (1-8 CPUs) 1TB (1-8 CPUs)

Windows Server 2003, Datacenter Edition

64GB (8-32 CPUs) 1TB (8-64 CPUs)

•표 1 Physical Memory and CPU Limits

OS 세부 구현

32-Bit Models 64-Bit Models

가상 주소 공간 4GB 16TB

32-bit 프로세스에 대한 가상 주소 공간

2GB (3GB if system is booted with /3GB switch)

4GB if compiled with /LARGEADDRESSAWARE (2GB otherwise)

64-bit 프로세스에 대한 가상 주소 공간

Not applicable 8TB

Paged pool 470MB 128GB

Non-paged pool 256MB 128GB

System Page Table Entry (PTE)

660MB to 900MB 128GB

1-2. 주소 공간 – Windows 시스템

OS 세부 구현

1-3. 주소 공간 – Win64 프로세스 공간Win32 와 마찬가지로 , 가상 주소 공간은 커널과 유저 영역으로 분리•0 ~ 8 TB : User mode

•8 ~ 16 TB : Kernel mode

- Page size : 4KB

- 최초 64KB 영역은 매핑이 안됨 . 따라서 실제 유효한 주소 는 0x10000 이상- System DLL 들은 4GB 이상의 영역에 위치 . ( 보통 0x7FF00000000)

OS 세부 구현

1-4. 주소 공간 – Win32 주소공간 예시x86 – Windows 2003 기준 : Internet Explorer 실행

OS 세부 구현

1-5. 주소 공간 – Win64 주소공간 예시x64 – Windows 2003 기준 : Internet Explorer 실행

OS 세부 구현

2. DEP ( Data Execution Protection )

최근의 x64 프로세서들은 CPU No Execute bit 가 지원되어 , Win64 윈도우즈는 하드웨어적으로 구현된 DEP 기능을 사용 .

•버퍼 오버런으로 인한 데이터 영역에서의 코드 실행을 금지•버퍼 오버런을 야기 시켰던 바이러스 및 프로그램 자체의 오류에 대한 보호 기능 향상•시스템 안정화

OS 세부 구현

3. 타입 (Types)

int, long, DWORD : Win64 에서도 여전히 32bit

포인터 , HANDLE, size_t : Win64 – 64bit 로 변경

OS 세부 구현

4. 파일 포맷 : PE32+

기존 Win32 PE 파일과 비슷한 구조 유지 . 필요 없는 필드의 삭제 및 32bit 크기의 필드들에 대한 64bit 의 확장 변경

•IMAGE_LOAD_CONFIG, IMAGE_THUNK_DATA 같은 경우 역시 32bit 필드들이 64bit 로 확장•새롭게 PDATA 섹션이 추가 – Exception Handling 을 위한 함수 테이블

표 [3] 참조

Header Field Change

Magic Set to 0x20b instead of 0x10b

BaseOfData Deleted

ImageBase Widened to 64 bits

SizeOfStackReserve Widened

SizeOfStackCommit Widened

SizeOfHeapReserve Widened

SizeOfHeapCommit Widened

•표 3 Changes to PE File FIelds

OS 세부 구현

5-1. 예외 처리 : Win32 – 스택 기반예외가 발생하면 OS 는 FS:[0] 을 시작으로 스택 상에 Linked List 로 연결되어 있는 예외 처리기를 실행 .

EXCEPTION_DISPOSITION __cdecl _except_handler( struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame, struct _CONTEXT *ContextRecord, void * DispatcherContext ){ return ExceptionContinueExecution; }

{

DWORD handler = (DWORD)_except_handler; __asm {

push handler // Address of handler function push FS:[0] // Address of previous handler mov FS:[0],ESP // Install new EXECEPTION_REGISTRATION} __asm { mov eax,0 // Zero out EAX mov [eax], 1 // Write to EAX to deliberately cause a fault}

__asm {

mov eax,[ESP] // Get pointer to previous record mov FS:[0], EAX // Install previous record add esp, 8 // Clean our EXECEPTION_REGISTRATION off stack}

OS 세부 구현

5-2. 예외 처리 : Win64 – 테이블 기반 테이블 기반의 예외 처리 . Win64 실행 모듈 자체에 runtime function table 을 포함 . 함수 테이블에 있는 각각의 entry 는 해당 함수의 시작 / 끝 주소와 함께 부가 정보에 대한 위치를 포함 .

•Win64 SDK WinNT.h 파일에 정의된 IMAGE_RUNTIME_FUNCTION_ENTRY 를 참조

•동적 생성된 코드를 위해서 런타임 시에 추가 entry 를 넣고 싶다면 , RtlAddFunctionTable API 를 사용

•단점 : 스택 기반의 예외 처리에 비해서 , 함수 테이블을 모두 검색해야 하기 때문에 상대적으로 느리다 .

•장점 : 함수가 실행될 때마다 try 데이터 블록을 설정해야 하는 오버헤드가 없다 .

OS 세부 구현

5-3. 예외 처리 : Win64 – 예외 처리 없는 코드

int boundary = 0;

0000000000402E16 mov dword ptr [rsp],0

boundary ++;

0000000000402E1D mov eax,dword ptr [rsp]

0000000000402E20 add eax,1

0000000000402E23 mov dword ptr [rsp],eax

{ int check = 0;

0000000000402E26 mov dword ptr [check],0

} return 0;

0000000000402E2E xor eax,eax

}

0000000000402E30 add rsp,10h

0000000000402E34 pop rdi

0000000000402E35 ret

int _tmain(int argc, _TCHAR* argv[])

{

0000000000402DF0 mov qword ptr [rsp+10h],rdx

0000000000402DF5 mov dword ptr [rsp+8],ecx

0000000000402DF9 push rdi

0000000000402DFA sub rsp,10h

0000000000402DFE mov rdi,rsp

0000000000402E01 mov rcx,4

0000000000402E0B mov eax,0CCCCCCCCh

0000000000402E10 rep stos dword ptr [rdi]

0000000000402E12 mov ecx,dword ptr [rsp+20h]

OS 세부 구현

5-4. 예외 처리 : Win64 – 예외 처리 있는 코드

int boundary = 0;

0000000000402E1F mov dword ptr [rsp],0

boundary ++;

0000000000402E26 mov eax,dword ptr [rsp]

0000000000402E29 add eax,1

0000000000402E2C mov dword ptr [rsp],eax

try

{

int check = 0;

0000000000402E2F mov dword ptr [check],0

} catch ( ... )

{

}

return 0;

0000000000402E37 xor eax,eax

}

0000000000402E39 add rsp,10h

0000000000402E3D pop rdi

0000000000402E3E ret

int _tmain(int argc, _TCHAR* argv[])

{

0000000000402DF0 mov qword ptr [rsp+10h],rdx

0000000000402DF5 mov dword ptr [rsp+8],ecx

0000000000402DF9 push rdi

0000000000402DFA sub rsp,10h

0000000000402DFE mov rdi,rsp

0000000000402E01 mov rcx,4

0000000000402E0B mov eax,0CCCCCCCCh

0000000000402E10 rep stos dword ptr [rdi]

0000000000402E12 mov ecx,dword ptr [rsp+20h]

0000000000402E16 mov qword ptr [rsp+8],0FFFFFFFFFFFFFFFEh

OS 세부 구현

5-5. 예외 처리 : 참고 자료x86 예외 처리 A Crash Course on the Depths of Win32™ Structured Exception Handlinghttp://www.microsoft.com/msj/0197/Exception/Exception.aspx

x64 예외 처리

X64 Unwind Information

http://blogs.msdn.com/509372.aspx

OS 세부 구현

6. 새로 추가된 API

IA64 버전과는 달리 x64 버전의 Windows 에서는 그다지 특기할 만한 새로운 API 가 없음 .

•IsWow64Process – 현재 Win64 시스템에서 구동되고 있는지 검사 .

•GetNativeSystemInfo – Win64 시스템의 환경 정보

•그 외의 API 들은 [ 표 4] 에 나열

Functionality API

Exception Handling RtlAddFunctionTable RtlDeleteFunctionTable RtlRestoreContext RtlLookupFunctionEntry RtlInstallFunctionTableCallback

Registry RegDeleteKeyEx RegGetValue RegQueryReflectionKey

NUMA (Non-Uniform Memory Access)

GetNumaAvailableMemoryNode GetNumaHighestNodeNumber GetNumaNodeProcessorMask GetNumaProcessorNode

WOW64 Redirection Wow64DisableWow64FsRedirection Wow64RevertWow64FsRedirection RegDisableReflectionKey RegEnableReflectionKey

Miscellaneous GetLogicalProcessorInformation QueryWorkingSetEx SetThreadStackGuarantee GetSystemFileCacheSize SetSystemFileCacheSize EnumSystemFirmwareTables GetSystemFirmwareTable

•표 4 New 64-Bit APIs

OS 세부 구현

7-1. WOW64 Subsystem

Win32 코드를 Win64 환경에서 실행시켜 주는 서브시스템 환경 . WOW64 에서 실행되고 있는 프로세스는 작업관리자에서 “ *32” 로 확인 가능 .

- 16 bit 코드는 지원 종료

OS 세부 구현

7-2. WOW64 Subsystem - 제한32bit 프로세스는 커널 모드로 진입시 , 중간의 WOW64 코드가 해당 호출을 가로채서 처리 .

•32bit 프로세스 – 32bit DLL 만 로딩 가능•64bit 프로세스 – 64bit DLL 만 로딩 가능•프로세스 간 통신은 여전히 유효하며 , 기존의 공유 메모리 (shared memory), 명명 파이프 (named pipe), 동기화 개체 (named synchronization object) 들은 그대로 사용 가능

* 이로 인해 , Internet Explorer 의 경우 32bit 용이 제공됨 . 또한 IIS 웹 애플리케이션의 경우 , 64bit 로 변환될 수 없는 32bit COM DLL 로 인해 WOW64 모드로 동작하는 상황도 발생됨 .

OS 세부 구현

7-2. WOW64 Subsystem – 시스템 디렉토리 관리Kernel32.dll 과 같은 DLL 들이 동일한 디렉토리에서 동일한 이름으로 32bit 모듈과 64 모듈을 가지고 있을 수 없으므로 , 이를 위해 WOW64 는 32bit 프로세스에 대해 시스템 디렉토리를 “ SysWow64” 폴더로 우회

만약 , 64bit 프로세스에서 명시적으로 “ SysWow64” 폴더 경로를 구하고 싶다면 , GetSystemWow64Directory API 를 사용 .

•32bit DLL : “C:\Windows\SysWow64”

•64bit DLL : “C:\Windows\System32”

OS 세부 구현

7-3. WOW64 Subsystem – 레지스트리 관리32bit 와 64bit 로 제공되는 COM 개체가 등록될 때 , 또는 32/64bit 프로세스가 해당 COM 개체를 생성할 때의 레지스트리 참조 문제를 해결하기 위해 별도 관리

명시적으로 , 다른 환경의 레지스트를 구하기 위해서는 RegOpenKey API 등의 flag 값으로 다음과 같은 값들이 추가됨

KEY_WOW64_64KEY – 명시적으로 64 bit 레지스트리 경로를 반환

KEY_WOW64_32KEY – 명시적으로 32 bit 레지스트리 경로를 반환

•64bit 프로세스 : HKEY_CLASSES_ROOT\CLSID

•32bit 프로세스 : HKEY_CLASSES_ROOT\Wow6432Node\CLSID

OS 세부 구현

8. 기타- 쓰레드 환경 블록을 나타내는 FS 레지스터는 Win64 에서는 GS 레지스터가 담당 .

- PatchGuard 적용 : syscall 테이블이나 IDT (interrupt dispatch table) 같은 중요한 커널 데이터를 사용자 모드의 프로그램이나 드라이버가 명시적으로 지원되지 않는 방법으로 변경하는 것을 봉쇄 . 중요한 커널 메모리 영역에 대한 변화를 별도의 커널 모드 쓰레드를 이용하여 감시 .

x64 CPU 구조

1. 레지스터RAX

RBX

RCX

RDX

RSI

RDI

RSP

RBP

R8

R9

R10

R11

R12

R13

R14

R15

•IA64 와는 달리 , 기존 x86 과의 호환성을 위해서 유사한 구조로 확장됨 .

•범용 레지스터의 이름이 64bit 로 확장되면서 새롭게 “ R” 로 시작 . 이전의 EAX, AX, AL, AH 등의 접근 방식도 그대로 지원

•R8 ~ R15 까지의 새로운 64bit 범용 레지스터 추가

•16 개의 128-bit 의 SSE2 레지스터 – XMM0 ~ XMM15

자세한 x64 레지스터 셋은 WinNT.h 파일의 #if defined(_AMD64_)안에 있는 _CONTEXT 구조체를 참고 .

x64 CPU 구조

2. 64bit 주소 지정

아래와 같은 기존 32bit 명령어의 경우 5-byte 명령어 크기를 가짐CALL DWORD PTR [XXXXXXXX]

64bit 에서도 64bit 주소 공간을 사용함에도 불구하고 위의 명령어는 그대로 5-byte 를 유지 . 64-bit 모드에서는 32-bit 크기의 오퍼랜드 값은 현재 명령어를 기준으로 상대적인 위치를 나타내는 offset 값으로 동작 . 즉 , 실제로 다음과 같은 명령어는 ,

00401000: CALL DWORD PTR [00020000h]

00421000h 영역에 저장된 64-bit 포인터 값이 호출 주소가 됨 .

이로 인해 , 64-bit 포인터 값을 담고 있는 영역이 코드 실행 주소로부터 2GB 내에 위치하고 있어야 하는 제한이 생김 . 따라서 개발자가 동적으로 코드를 생성하거나 , 기존 코드를 변경하는 경우에는 이것에 주의해야 함 .

* 2006-08-12 추가 : Call Dword ptr 명령어 다음 주소부터 계산해서 변위값을 더하기 때문에 엄밀히 421000h 값은 아님 .

x64 CPU 구조

3-1. 파라미터 전달 방식 (calling convention)

- 단 하나의 calling convention 만을 지원 . __cdecl 같은 지시자들은 컴파일러에 의해 무시됨 . x64 호출 방식은 굳이 비교하자면 , x86 fastcall 방식과 유사 .

- 처음 4 개의 정수 값들은 (우측에서 좌측 순으로 ) 64bit 레지스터들에 전달 .RCX: 첫번재 인자RDX: 두번재 인자R8: 세번째 인자R9: 네번째 인자

- 처음 4 개의 부동 소수 값들은 XMM0 ~ XMM3 레지스터에 전달 .

- 각각 4 개의 값들을 초과하는 인수에 대해서는 스택을 통해서 전달 .

x64 CPU 구조

3-2. 파라미터 전달 방식 (calling convention) - 예제

int test( int k, int j, int t, int o, int p, double dd )

{

return 0;

}

{

test( 0, 1, 2, 3, 4, 0.06 );

}

0000000000402E52 movsd xmm0,mmword ptr [__real@3faeb851eb851eb8 (405B88h)]

0000000000402E5A movsd mmword ptr [rsp+28h],xmm0 // XMM0 == 0.06

0000000000402E60 mov dword ptr [rsp+20h],4 // 스택 == 4

0000000000402E68 mov r9d,3 // R9 == 3

0000000000402E6E mov r8d,2 // R8 == 2

0000000000402E74 mov edx,1 // RDX == 1

0000000000402E79 xor ecx,ecx // RCX == 0

0000000000402E7B call test

x64 CPU 구조

3-3. 파라미터 전달 방식 (calling convention) – 스택 관리 (1)

-인자를 레지스터에 전달하는 것과 상관없이 스택 영역도 확보 . 레지스터를 사용해야 할 경우가 생기면 , 그 값을 해당하는 스택에 보관 .

-인자가 없더라도 최소 4 개 영역 만큼은 확보 . 이로 인해 , 스택 위치로 복사하는 offset 값이 동일하게 유지됨 . 인자가 5 개 이상일 때부터 추가 스택 영역 확보 .

- 스택 정리 작업도 “호출자 (caller)” 가 담당 .

x64 CPU 구조

3-3. 파라미터 전달 방식 (calling convention) – 스택 관리 (2)

-함수의 진입 (prologue)/ 탈출 (epilogue) 코드 부분을 제외하고 RSP 값이 바뀌는 경우가 거의 발생하지 않음 .

-x64 컴파일러는 함수 내에서 가장 큰 파라미터 수를 가진 호출 함수를 기준으로 충분한 스택 공간을 미리 예약 . 그렇게 잡아둔 공간으로 이후의 함수 호출들에 대해서 스택 공간을 재사용 .

-함수 호출 시마다 발생했던 ESP 레지스터에 대한 작업이 사라짐

자세한 x64 호출 방식에 대해서는 다음의 토픽을 참조 .The history of calling conventions, part 5: amd64

; http://blogs.msdn.com/oldnewthing/archive/2004/01/14/58579.aspx

x64 CPU 구조

3-4. 파라미터 전달 방식 (calling convention) – 반환 값

-정수인 경우 , RAX 로 전달 . 부동 소수 값인 경우 XMM0 으로 전달- 함수 호출 간에 보존되어야 할 레지스터 ; RBX, RBP, RDI, RSI, R12, R13, R14, R15

-휘발성이고 값이 변경될 수 있는 레지스터 ; RAX, RCX, RDX, R8, R9, R10, R11

x64 CPU 구조

4. 기타

1. Integer parameters that are less than 64-bits are sign extended, then still passed via the appropriate register, if among the first four integer parameters.

2. At no point should any parameter be in a stack location that's not a multiple of 8 bytes, thus preserving 64-bit alignment. Any argument that's not 1, 2, 4, or 8 bytes (including structs) is passed by reference.

3. Structs and unions of 8, 16, 32, or 64-bits are passed as if they were integers of the same size.

VC++ 로 x64 프로그램 개발

1. 컴파일 환경 구성 – VS.NET 2005

“Build” / “Configuration Manager” 에서 “ Active solution platform” / “<New...>” 를 선택 . “x64” 환경을 위한 빌드 유형을 생성 .

VC++ 로 x64 프로그램 개발

2. 권고 사항 – 소스 변환 (1)

-포인터 변환 연산을 int, long, DWORD 값 등에 저장해서 한 것이 가장 큰 문제 . 포인터는 x64 에서 8byte 로 바뀌어 4GB 이상의 영역을 지정할 수 있는 데 연산을 수행했던 int/long/DWORD 값은 여전히 4byte 로 심각한 문제 발생

-포인터 연산을 수행한 int/long/DWORD 의 경우 , DWORD_PTR, INT_PTR 등으로 변환하고 , 크기를 명시해야 할 필요가 있는 경우에는 basetsd.h 에 정의된 INT32, INT64, INT16, UINT32, DWORD64 등을 사용

- printf/sprintf 등의 포맷팅에서 문제 발생 . 보통 포인터 값에 대한 출력으로 %X, %08X 를 사용했던 것들을 %p 로 수정 . 크기 종속적인 출력인 경우 “ I” 접두사를 사용 . 예를 들어 , UINT_PTR 변수에는 “ %lu”, 플랫폼에 무관한 64bit 부호 있는 정수에 대해서는 “ %l64d”를 사용

VC++ 로 x64 프로그램 개발

2. 권고 사항 – 소스 변환 (2)

-포인터 문제를 명시적으로 끌어내기 위해 DLL/EXE 의 base address 를 4GB 이상의 영역으로 지정

-Win32 와 Win64 에서의 컴파일 시 소스 코드를 지정하기 위해 다음의 매크로 상수를 사용 .

_M_IX86 : x86 프로세서_M_AMD64 : AMD 64 프로세서_WIN64 : IX86 과 AMD64 를 포함한 64bit 환경

#ifdef _M_AMD64// My x64 code here#else// My x86 code here#endif

#ifdef _M_AMD64// My x64 code here#elif defined (_M_IX86)// My x86 code here#else#error !!! Need to write code for this architecture#endif

VC++ 로 x64 프로그램 개발

2. 권고 사항 – 소스 변환 (3)

- 인라인 어셈블리 : x64 빌드에서는 인라인 어셈블리 코드가 지원되지 않음 . __asm 예약어 자체를 사용할 수 없음 . 64bit MASM (ML64exe) 를 사용해서 별도 컴파일 .

.NET 으로 x64 프로그램 개발

1. 컴파일 환경 구성 – VS.NET 2005

- .NET 은 기본적으로 “ Any CPU” 로 설정되어 있음 . 원한다면 , 특정 CPU 에서만 동작하도록 “ x86”, “x64”, “Itanium” 으로 설정 가능 .

.NET 으로 x64 프로그램 개발

2. 권고 사항 – 소스 변환- P/Invoke 관련 코드가 있는 경우 , 반드시 검증 .