출처 : http://blog.naver.com/cowhdgk555/130004533113

 

글이 너무 좋아서, 한 번 번역해 보았습니다.
혹시 향후에 64비트 윈도우에서 프로그래밍 하실 분은 그냥 한 번 참고해 보시기 바랍니다.

 

원문은 아래에서 찾아보실 수 있습니다.
http://msdn.microsoft.com/msdnmag/issues/06/05/x64/#contents


추가사항 (2006-05-18)
아래의 두 웹사이트를 프로그래밍 하기 전에 참고해 보시기 바랍니다.

참고: 64비트 관련 컴파일러 설정 및 프로젝트 구성(한글)

http://msdn2.microsoft.com/ko-kr/library/h2k70f3s(VS.80).aspx
마이크로 소프트웨어에 있는 64비트 프로그램에 대한 글도 한 번 참고해 보시기 바랍니다.
http://www.zdnet.co.kr/builder/dev/dotnet/0,39031607,39134610,00.htm
 

x64 프라이머
 

64비트 윈도우를 프로그래밍 하기 위해서 알아야 할 모든 것들


 

 

이 글은 아래의 내용을 다루고 있습니다:

    64비트 윈도우의 기초적인 내용 

    x64의 간략한 내부 구조

    Visual C++ 2005로 x64용 소프트웨어 개발

    x64용 소프트웨어를 위한 몇 가지 디버깅 기술들

이 글은 아래의 기술들을 사용합니다:
Windows, Win64, Visual Studio 2005


##########0*목차



 

새로운 64비트 윈도우에서 일했던 경험의 좋았던 점 중의 하나는, 새로운 기술이 어떻게 동작하는지 눈으로 확인할 수 있다는 것이었습니다. 저 자신은 특히 어떤 운영체제 밑바닥에 대해서 조금이라도 알기 전까지는, 그 운영체제에 대해서 그렇게 편안함을 느끼지 못하는 편입니다. 그래서, 64비트 Windows XP와 Windows Server™ 2003이 나타났을 때, 저는 아주 열심히 그 운영체제에 대해서 연구하였습니다.
 

Win64와 x64 CPU의 좋은 점은, 그 전의 CPU와 조금 다른 구조를 가지고 있지만, 그 차이를 배우는데 그렇게 많은 시간이 요구되지 않는 다는 점입니다. 저희 같은 개발자들에게는, x64로의 이동이 단지 컴파일만 다시 하면 끝나는 그런 작업이었으면 좋겠지만, 그렇게 생각하고 작업을 하신다면, 앞으로 디버거에서 너무 많은 시간을 보내셔야 할 것 같습니다.
 

이 글에서, 저는 Win64와 x64의 내부구조에 대한 저의 지식을 종합해서 유능한 Win32® 프로그래머가 x64로 이동하기 위해서 꼭 필요한 지식들 제공하겠습니다. 저는 여러분이 이미 Win32의 개념과 기본 x86개념, 그리고 왜 여러분의 코드가 Win64에서 동작 해야 하는지에 대해서 이미 알고 계신다고 가정하겠습니다. 그렇게 해야지 제가 좀 더 핵심적인 것들에 집중할 수 있거든요. 여기서의 이 요약이 x86 내부구조와 Win32에 대한 여러분의 지식과 비교해서 상대적으로 중요한 차이점에 대한 고찰이라고 생각하시기 바랍니다.
 

x64 시스템에서 한 가지 좋은 점은, 아이템니윰(Itanium)기반 시스템과는 틀리게 여러 분이 심각한 효율 저하에 대한 고민을 하지 않고, Win32 혹은 Win64를 동일한 기계에서 이용하실 수 있다는 점입니다. 그리고 인텔과 AMD의 x64 구현에 조금 불명확한 몇 가지의 차이가 존재함에도 불구하고, x64용 윈도우는 둘 중 어느 곳에서나 동작합니다. 여러분이 AMD x64와 Intel x64 시스템을 위해서 각각 다른 버젼의 윈도우가 필요하지 않습니다.
 

저는 여기서 크게 세 가지 영역으로 이 글을 나누어서 진행하도록 하겠습니다: 운영체제의 구현의 몇 가지 자세한 내용들, x64 CPU 내부 구조에 대한 개괄적인 설명, 그리고 Visual C++로 x64용 프로그램 개발.


x64 운영체제

 

저는 어떤 윈도우 내부구조를 설명하더라도, 처음에는 메모리와 주소 공간에서 부터 시작합니다. 비록 64비트 프로세서가 이론적으로는 16 엑사 바이트(exabytes) 의 메모리에 접근할 수 있다고 하더라고, Win64는 현재 16 테라바이트(terabytes), 44비트 만을 지원하고 있습니다. 왜 64비트 전부를 사용해서, 16 엑사바이트 전부를 쓸 수 없었을 까요? 거기에는 몇 가지 이유가 존재합니다.
 

맨 처음 이유로는, 현재 x64 CPU들이 물리적인 메모리 공간에 접근할 때, 오직 40비트(1테라바이트)만을 허용합니다. 그 제한이 없어진다고 하더라고, 지금 현재의 하드웨어가 아닌, 향후에 나올 수 있는 CPU들의 내부구조들이 오직 52비트 (4페타바이트)만큼만 확장될 수 있습니다.그 정도만 해도, 그 많은 메모리를 매핑하기 위한 페이지 테이블의 사이즈는 어마 어마 할 것입니다.
 

Win32에서와 같이, 접근할 수 있는 주소 공간은, 사용자와 커널의 영역으로 나누어 집니다. 커널 모드의 코드가 8 테라바이트 상위에서 모든 프로세스에 의해 영역되고, 각각의 프로세서는 8 테라바이트 이하에 자신의 고유 영역을 가집니다. 64비트 윈도우의 각 버젼들은 그림 1그림 2 에서 보여지는 것 처럼, 서로 다른 물리적인 메모리 한계를 가지고 있습니다.

Win32에서와 같이, x64의 페이지 사이즈는 4KB 입니다. 처음의 64KB 공간은 절대로 매핑 되지 않기 때문에, 여러분이 볼 수 있는 맵핑 주소중 가장 낮은 번지는 0x10000입니다. Win32에서는 다르게, 시스템 DLL들은 사용자 모드 주소 공간의 제일 위 부분과 근접해 있는, 기본 로드 어드레스라는 것이 없습니다. 그 대신, 시스템 DLL들은 4GB 위의 공간에 적재 됩니다. 통상적으로, 그 주소는 0x7FF00000000 (8 테라바이트) 근처입니다.
 

새로운 x64 프로세서의 좋은 기능 중에 하나는, 윈도우가 하드웨어적으로 데이터 실행 보호(Data Execution Protection-DEP)를 할 수 있게 지원해 준다는 것입니다. x86 플랫폼에서는, 많은 버그와 바이러스가 CPU가 데이터를 올바른 코드 바이트로 인식하고 실행했기 때문에 존재할 수 있었습니다. 실수이든 혹은 고의적인 버퍼 오버런(buffer overrun)이 원래는 데이터 저장을 목적으로 했던 메모리 블락을 CPU에서 명령어로 인식하고 실행해 버리는 결과가 발생하곤 했었습니다. 이 DEP의 도움으로, 운영체제는, 의도한 코드 영역의 경계를 명료하게 설정할 수 있고, 이 의도된 경계를 벗어나는 코드 실행에 대해서는 CPU가 일종의 덫을 놓을 수 있게 되었습니다. 이 기능은 윈도우를 악의적인 공격에 덜 취약하게 만드는데 큰 도움을 줄 수 있습니다.
 

에러들을 잡기 위해서 고안된 장치들 중에 하나가, x64 링커가 실행파일에 대한 기본 적재 주소를 32비트 (4GB) 이상으로 설정한 것입니다. 이것은 코드가 Win64로 포팅된 후, 기존에 있던 코드에서 위에서 이야기한 보안에 문제를 일으키는 부분을 찾기 쉽게 만들어 줍니다. 특히, 만약 포인터가 32비트 값(예를 들어, DWORD)값으로 저장되어 있으면, 그러면, 그 값은 Win64 빌드를 동작시킬 때, 값이 일부분이 짤림 으로서, 포인터를 무효화 시키고, 접근위반(access violation)을 일으킵니다. 이러한 잔기술은 지저분한 포인터 버그를 찾기 쉽게 만들어 줍니다.
 

포인터와 DWORD에 대한 주제들도 Win64의 타입을 이야기하는데 빠질 수 없습니다. Win64에서 포인터의 크기가 얼마나 될까요? LONG형의 길이는요? 그리고, 핸들과 HWND의 크기는 얼마나 될까요? 다행스럽게도, 마이크로 소프트가 Win16에서 Win32로의 좀 지저분한 변환을 하면서, 새로운 자료형의 모델에 대해서는 64비트 까지의 확장이 쉽게 될 수 있도록 만들었습니다. 일반적으로 이야기 해서, 몇 가지 예외를 제외하고, 새로운 64비트 세계에서도, 포인터와 size_t를 제외한 모든 나머지 자료형 들은 Win32에서와 동일한 길이를 가지고 있습니다. 즉, 64비트 포인터는 8바이트인 반면에, int,long, DWORD는 아직도 4바이트입니다. 후반부에 Win64 개발에 대해서 이야기 하면서, 이 자료형에 대해서 좀 더 이야기 하도록 하겠습니다.[편집자 주 - 5/2/2006: 핸들은 포인터 값으로 선언되었습니다. 그래서, Win64에서는 4바이트가 아닌 8바이트 값입니다.]
 

Win64의 포맷은 PE32+라고 불립니다. 거의 모든 관점에서 이 포맷은 Win32PE 파일과 거의 구조적으로 동일합니다. ImageBase 같은 몇 몇의 포맷은 더 크기가 커졌고, 한 필드는 없어졌고, 그리고 다른 하나의 필드는 다른 CPU 타입을 반영할 수 있도록 변경되었습니다. 그림 3 은 바뀐 필드들을 보여 줍니다.
 

PE 헤더를 말고는, 그렇게 많이 바뀐 부분이 없습니다. IMAGE_LOAD_CONFIG, IMAGE_THUNK_DATA같은 몇 몇 구조체들은 단순히 필드들을 64비트로의 확장을 했을 뿐입니다. PDATA 섹션의 추가는 Win32와 Win64 구현의 주된 차이점중의 하나를 두드러지게 한다는 점에서 아주 흥미롭습니다: 그 차이는 바로 예외 처리(exception handling) 입니다.
 

x86 세계에서는, 예외 처리는 스택에 기반했었습니다. Win32 함수가 try/catch 혹은 try/finally 코드를 가지고 있을 때, 컴파일러는 스택에 작은 데이타 블락을 만들어 놓는 코드를 생성했었습니다. 추가로, 각각의 데이타 블락은 이전 try 데이타 구조체를 가리켰었습니다. 그래서, 최근에 추가된 구조체가 리스트의 헤드가 되는 링크드 리스트를 생성했었습니다. 함수가 불려지고, 종료될 때, 링크드 리스트의 헤더는 계속 갱신이 되었었습니다. 그러다, 예외가 발생하는 경우, 운영체제가 스택에 있는 링크드 리스트의 블락을 살펴서, 적절한 핸들러를 찾는 방식으로 이루어져 있었습니다. 저의 1997년 MSJ article 에서 좀 더 자세한 내용을 찾아 보실 수 있습니다.
 

Win32 예외처리와는 반대로, Win64 (x64와 아이템니움(Itanium) 버젼 둘 다 해당됩니다.)에서는 테이블 기반의 예외 처리를 사영합니다. 더 이상 스택 위에 있는 try 데이타 블락의 링크드 리스트는 없습니다. 대신, 각각의 Win64 실행파일은 런타임 함수 테이블을 가지고 있습니다. 각각의 함수 테이블은 함수의 시작 주소와 끝 주소를 가지고 있을 뿐만 아니라, 함수의 스택 프레임 레이아웃과 예외 처리 코드에 관련된 데이타들의 위치 역시 가지고 있습니다. 이 구조체들의 핵심을 보기 위해서 WINNT.H안에 들어 있는 x64 SDK안에 WINNT.H 안에 있는 IMAGE_RUNTIME_FUNCTION_ENTRY 구조를 살펴 보시기 바랍니다.
 

예외가 발생했을 때, 운영체제는 쓰레드 스택을 하나씩 탐색합니다. 스택을 탐색하면서 각각의 프레임을 탐색하고, 저장된 인스트럭션 포인터를 찾아서, 운영체제가 어떤 실행 모듈안에 인스트럭션 포인터가 있는지를 결정합니다. 그리고 나서, 운영체제는 런타임 함수 테이블을 그 모듈에서 찾아서, 적절한 런타임 함수를 찾아서, 데이타로 부터 적절한 예외 처리 결정을 내려 줍니다.
 

만약, 여러분이 로켓 과학자이고, PE32+ 모듈 없이 직접적으로 메모리에서 코드를 생성했으면 어떻게 될까요?  RtlAddFunctionTable API를 이용하여, 운영체제에게 여러분이 동적으로 생성한 코드에 대해서 알려 줄 수 있습니다.
 

테이블 기반의 예외 핸들링의 단점은 (x86 스택 기반 모델에 비해 상대적으로) 함수 테이블을 찾아 보는 것이, 링크드 리스트에서 값을 찾는 것 보다 훨씬 시간이 많이 걸린다는 점입니다. 장점은, 함수를 실행시킬 때 마다 매 번 try 데이타 블락을 생성시키는 오버헤드가 없다는 점입니다.
 

꼭 기억하세요! 이 글이 아무리 재미있고, 흥미로워도, 이 글은 x64 예외 핸들링에 대한 자세한 설명이라기 보다, 간단한 소개에 불과합니다. x64의 예외 핸들러에 대한 좀 더 깊은 지식을 알고 싶으시다면, Kevin Frei의 블로그을  꼭 한 번 읽어 보시기 바랍니다.
 

x64에 호환되는 윈도우에 새로운 API가 그렇게 많지는 않습니다; 거의 모든 새로운 Win64 API들은 아이템니움(Itanium) 프로세서를 위한 윈도우 출시 때 이미 추가되었던 것들 입니다. 간단하게, 그 API들 중 가장 중요한 두 개의 API는 IsWow64Process와 GetNativeSystemInfo 입니다. 이 함수들은 Win32 어플리케이션이 자기 자신들이 Win64에서 돌고 있는지의 여부를 알려 줍니다. 그래서, 만약 64비트 환경에서 동작하고 있다면, 시스템의 진짜 사양(capability)를 올바르게 결정할 수 있게 해 줍니다. 반면에, 32비트 프로세스는 GetSystemInfo 함수를 호출하고, 오직 32비트 시스템인 것 처럼, 시스템의 사양(capability)를 볼 수 있습니다. 예를 들어, GetSystemInfo는 32비트 프로세스 주소 영역만을 보고 합니다. 그림 4 는 x86에서는 사용할 수 없고, 오직 x64에서 쓸 수 있는 API들을 보여 주고 있습니다.
 

전부 다 64비트로 동작하는 윈도우 시스템이 아주 멋지게 들리겠지만, 현실적으로 여러분은 잠시 동안 Win32 코드를 필요로 하게 될 것 같습니다. 그러한 작업을 위해서, x64 버젼의 윈도우는 Win32와 Win64 프로세스를 동시에 동일한 시스템에서 동작시킬 수 있는 WOW64 서브시스템이 포함되어 있습니다. 그러나, 여러분의 32비트 DLL을 64비트 프로세스로 올리거나 혹은 반대의 일들은 지원되지 않습니다. (저를 믿으세요, 아주 좋은 일입니다.) 그리고 마침내 여러분은 구닥다리 16비트 코드에게 잘 가라고 인사를 할 수 있게 되었습니다!
 

x64 버젼의 윈도우에서는, 프로세서는 오직 Win64 DLL들만 로딩할 수 있는, Explorer.exe 같은 64비트 실행 파일에서 부터 시작될 수 있습니다. 반면에, 32비트 실행 파일에서 시작한 프로세스는 오직 Win32 DLL 들만 로딩할 수 있습니다. Win32 프로세스가 커널 모드의 함수를 호출할 때-예를 들어서 파일을 읽는다든지-WOW64는 그 함수를 조용히 가로채서, 올바른 x64 코드의 주소를 주어서 호출하게 합니다.

물론, 서로 다른 종족(32비트와 64비트) 프로세서들 끼리 통신할 일도 생길 수 있습니다. 운좋게도, Win32에서 여러분이 사랑하고 좋아했던 모든 프로세스간 통신 방법은 Win64에서도 동작합니다. 쉐어드 메모리(shared memory), 네임드 파이프(named pipe), 그리고, 기타 이름이 있는 동기화 객체들을 포함해서 말입니다.
 

여러분이 혹시 "그럼 시스템 디렉터리도 Win32와 Win64가 동일한가?"라고 생각하실 지도 모르겠습니다. 동일한 디렉터리가 32 비트와 64 비트 KERNEL32 나 USER32 등과 같은 동일한 이름의 시스템 DLL들을 동시에 가질 수 없습니다, 그렇지요? WOW64는 요술같이 파일 시스템의 리다이렉션(redirection)을 통해서 이 문제를 해결합니다. Win32 프로세스에서의 파일에 대한 쓰기 혹은 읽기 등이 발생하면, SysWow64라는 디렉토리에 있는 커널의 함수를 호출하는 것이 아닌, System32에 있는 커널의 함수를 호출하게 합니다. WOW64가 안보이게 SysWow64 디레토리로 요청한 것을 조용히 바꾸어 주는 것입니다. 그래서, Win64 시스템이 효과적으로 두 개의 시스템 디렉터리, 하나는 x64 용 바이너리들과 또 하나는 Win32용의 바이너리를 가지는 것 입니다.
 

약간 혼란스러울 수 있지만, 이러한 내부적인 처리는 상당히 부드러운 것 처럼 보입니다. 제가 System32 디렉터리의 Kernel32.dll에서 Dir을 실행했을 때, SysWow64 디렉터리에서 했던 것과 정확히 똑같은 결과를 볼 수 있었습니다. 파일 시스템의 리다이렉션이 이런 방식으로 동작하는 것을 정확히 이해하기 까지, 제 자신은 머리가 좀 많이 아팠었습니다. 여러분이 x64 어플리케이션에서 32비트 Windows\System32 폴더를 알기를 정말로 원하신다면, GetSystemWow64Directory 라는 API가 여러분께 정확한 경로를 전달해 줄 것 입니다. 그래도, 전체 내용을 알기 위해서 MSDN 문서를 꼭 읽어 보시기 바랍니다.
 

파일 시스템의 리다이렉션이외에도, WOW64가 해주는 또 다른 마법 중의 하나가 레지스트리 리다이렉션입니다. 제가 아까 Win64 프로세스에서는 Win32 DLL들을 불러오지 않는다고 했던 말을 생각해 보시고, 그리고 COM 과 in-process 서버 DLL을 불러올 때, 레지스트리를 이용하는 것을 생각해 보시기 바랍니다. 만약, Win32 DLL에 구현되어 있는 COM 오브젝트를 64비트 어플리케이션이 CoCreateInstance를 이용해서, 생성하려고 하면 어떻게 될까요?  DLL이 올라올 수 없습니다, 맞지요? WOW64는 Win32 어플리케이션으로 부터의 접근을 \Software\Classes 레지스트리 노드로 리다이렉션 해 줍니다. 결과적으로 Win32 어플리케이션에서 보는 레지스트리 구조는 x64 어플리케이션에서 보는 것과 서로 다르게 됩니다. 그리고, 여러분이 기대하시는 대로, 운영체제는 32비트 어플리케이션이 RegOpenKey와 그 계열 함수군을 이용하여, 실제로는 64비트인 레지스트리에 접근하려고 할 때,내부적으로 새로운 플래그 값을 주어서, 그 값들에 접근할 수 있게 합니다.
 

약간만 더 깊숙이 들어가서, 쓰레드 로컬 데이타 영역도 살펴 보아야 합니다. x86 버젼의 윈도우에서는, FS 레지스터가 각 쓰레드의 메모리 영역과 가장 마지막 에러(GetLastError로 확인할 수 있는 에러 값), 그리고 쓰레드의 지역 저장 영역(TLS:Thread Local Storage, TlsGetValue로 값을 얻을 수 있는) 에 사용되었습니다. x64 버젼의 윈도우에서는, FS 레지스터는, GS 레지스터로 교체되었습니다. 그 외에는 거의 동일한 방식으로 x32와 x64의 운영체제가 동작합니다.
 

비록, 이 글이 x64의 사용자 입장에 초점을 두고 있기는 하지만, 커널 모드의 내부 구조에서 한 가지 추가된 중요한 점이 있습니다. PatchGuard라고 불리는 새로운 기술이 x64 윈도우에 추가되었습니다. 이 기술은 보안과 견고함을 위한 목적으로 추가되었습니다. 작게는 syscall 테이블이나 인터럽트 디스패치 테이블(interrupt dispatch table-IDT)를 변경하는 사용자 프로그램이나 드라이버들은 보안상의 문제와 잠재적인 안정성의 문제를 일으켜 왔었습니다. x64의 내부에서는, 그러한 방식으로 커널의 메모리를 지원되지 않는 방식으로 바꾸는 방식이 허용되지 않습니다. 이러한 것을 강화시키는 기술이 PatchGuard 입니다. 이 기술은 중요한 커널 메모리의 위치가 바뀌는 것은 커널 모드의 쓰레드에서 항상 감시합니다. 그리고 메모리가 바뀌면, 시스템은 버그체크를 통하여 멈춰 버립니다.
 

모든 것을 고려해 보아도, 만약 여러분이 Win32의 내부 구조에 어느 정도 알고 있고, 어떻게 코드를 쓸 줄 알고, 동작하는지를 알고 있으면, Win64로의 이동에 있어서 크게 놀라지 않으실 겁니다. 거의 대부분은 좀 더 넓은 환경으로의 이동이라고 간주하셔 됩니다.


x64의 간략한 내부 구조

 

자 이제, CPU의 구조 자체에 대해서 조금 살펴 보기로 하겠습니다. 왜냐하면, 기본적인 CPU의 명령어(instructions)에 대해서 알고 있는 것이, 개발(특히 디버깅!)을 훨씬 쉽게 만들기 때문입니다. 처음에 여러분이 알아 차릴 수 있는 것은, 컴파일러가 생성한 x64 코드가 여러분이 알고 있고, 사랑하는 x86 코드와 거의 흡사하다는 점입니다. IA64 코딩의 경우는 그렇게 유사하지 않았었습니다.
 

그리고, 두번째로 여러분이 알아차릴 수 있는 것은, 레지스터 이름이 여러분이 사용하던 것들과 조금씩 다르고, 레지스터 자체도 조금 많다는 점 입니다. 일반적인 용도의 x64 레지스

안정적인 DNS서비스 DNSEver DNS server, DNS service
Posted by 키르히아이스

댓글을 달아 주세요