실 전! 강 의 실 개발자를위한실전 64 비트! 64 비트윈도우커널탐구 연 + 재 + 순 + 서 1회 2005. 9 윈도우커널모드드라이버 64비트포팅 2회 64비트윈도우커널분석 AMD64 3회 64비트윈도우커널분석 IA64 연 + 재 + 가 + 이 + 드운영체제 64비트윈도우 XP, 64비트윈도우 2003 개발도구 윈도우 DDK 최신판기반지식 윈도우커널모드드라이버개발응용분야 응용프로그램및드라이버 64비트포팅 김성현 shkim@ahnlab.com 안철수연구소에서 V3, SpyZero 개발에참여하고있는커널모드드라이버개발자이다. 우리나라에서도평생소트프웨어엔지니어로살아가면서식구들을먹여살릴수있는날이올수있다는꿈을안고그길을닦아나가고있다. 올해의목표로 Code Complete 2nd Edition Windows Internals 4th Edition 읽기, 몸무게 10kg 증량하기, 당구 250 만들기, 외국식당에서영어로원하는거주문하기등을가지고있다. 개발자를위한실전 64비트! 64비트윈도우커널탐구 64비트시대를향한첫걸음, 커널모드드라이버포팅 독자들은올해초부터마소에여러번게재된 64비트기사들을한번쯤읽어봤을것이다. 불행히도아직읽어보지못했다면한번쯤찾아서읽어보는것도 64비트포팅을이해하는데좋은방법이다. 이미앞선기사에서포팅에신경써야할많은내용들이언급되었는데이것들을잘이해하고있다면응용프로그램이나드라이버를 64비트로포팅하는데든든한밑천이될것이다. 이번기사에서는예제를설명하는데반드시필요한내용에대해꼼꼼히살펴볼것이다. 올해들어마소에많은 64비트관련기사들이나왔다. 그만큼 64비트환경이우리에게가까이다가왔다는것을의미하는것이아닌가생각해본다. 이번연재에서는 64비트윈도우에서유저모드모듈들과는달리반드시 64비트포팅을해야만하는커널모드드라이버를실제로포팅해보면서 64비트포팅에대한감각을익혀보도록하겠다. 또한 64비트윈도우커널을살짝들여다보고 64비트커널에대해서조금더심도있는이해를하는시간을가져본다. 64비트드라이버를포팅하는이유 64비트윈도우는 32비트호환성을제공하기때문에 32비트응용프로그램들을수정없이그대로사용할수있다. 이것이가능한이유는 64비트윈도우가 WOW64라는 32비트에뮬레이션레이어를운영하고있기때문이다. 이 WOW64를이용해서대부분의 32비트응용프로그램은 64비트윈도우에서수정없이그대로동작할수있다. 그러나 32비트커널모드드라이버는유저모드레이어인 WOW64가해결해주지못한다. 그렇다고커널모드에 WOW64 같은레이어가존재하는것도아니다. 따라서 32비트커널모드드라이버는 64비트윈도우에발붙이지도못하는비운의주인공이되어버리고말았다. 무협지를보면대부분처음에는주인공이가지각색의시련을겪다가나중에는환골탈태 (?) 하여강력한무공의소유자로변신하는장면이나온다. 32비트로남아있어서는아무런의미가없는우리의드라이버도 64비트로환골탈태해야한다. 이제부터비운의주인공인 32비트드라이버를 64비트윈도우라는거대한중원의주인공으로만들어보자. 제품포팅계획현재자신의회사가 64비트버전출시를계획하고있다면현재 32비트인제품을어떻게수정해야할까? 물론드라이버를포함하고있는제품에대해서말이다. 드라이버는 32비트호환모드라는것 238 2 0 0 5. 9
64 비트시대를향한첫걸음, 커널모드드라이버포팅 이없기때문에무조건 64비트로포팅해야한다고이미말했다. 그렇다면드라이버를사용하는제품에서유저모드모듈들도모두 64비트로포팅해야할까? 물론모두 64비트로포팅하면아주좋겠지만, 일반적인제품들은드라이버모듈의수보다유저모드모듈의수가월등히많을것이다. 이런경우많은수의유저모드모듈들을모두포팅하는데에는시간이너무많이걸릴수있으므로드라이버만 64비트로포팅하는방법을선택할수있다. < 그림 1> 에서첫번째와같은 32비트애플리케이션 + 64비트드라이버 구성의경우가드라이버만 64비트로포팅한경우이다. 어떤제품을빨리 64비트를지원하도록만들려면이와같은방법을취하는것이가장빠른방법일것이다. 가급적이면 32비트애플리케이션은수정을가하지않고드라이버만 64비트로포팅한다. 이방법에는다음과같은두가지주의사항이있다. 32 비트애플리케이션은 64비트환경을완전히이용하지못하는제약사항을가질수있다. 64 비트드라이버는자신을제어하는프로세스가 32비트프로세스인것을정확히인지하도록포팅돼야한다. < 그림 1> 제품포팅계획 32비트애플리케이션 64비트애플리케이션유저모드커널모드 64비트드라이버 64비트드라이버 로그램이고 C:\Program Files(x86) 에들어가는것들은 32비트응용프로그램이라는것을알수있다. 시스템폴더도마찬가지로 C:\Windows\system32는 64 비트응용프로그램들이사용하고 C:\Windows\syswow64는 32 비트응용프로그램들이사용하게된다. 이것은 WOW64에의해자동으로이뤄진다. 자신은 C:\Windows\system32에접근한다고생각하지만실제로는 C:\Windows\syswow64로접근된다. 그렇다면 32비트프로세스는 C:\Windows\system32에접근하지못하는것일까? 이것을가능하게해주는 API를 64비트윈도우에서제공한다. 즉 32비트애플리케이션 + 64비트드라이버 는완전한 64비트로가는중간단계라고볼수있다. 결국완전하게 64비트제품을구성하는것은두번째그림인 64비트애플리케이션 + 64비트드라이버 라는구성이다. 시간과비용이허락한다면당장애플리케이션, 드라이버모두 64비트로포팅하는것이바람직할것이다. 하지만현실적인이유로대부분의업체들은첫번째단계를선택하게된다. 뒤에다루게될포팅예제에서는두가지상황에대해모두살펴본다. 설치패스 32비트애플리케이션 + 64비트드라이버 구성을선택하여제품을만들경우제품이설치되는위치에대해정확히이해하고있어야한다. 설치프로세스도 32비트프로세스일것이기때문에 WOW64가제공하는 File System Redirection과 Registry Redirection의영향을받는다. File System Redirection은 32 비트프로세스가파일시스템에접근할때특정폴더에대해자동으로다른위치를참조하게하는기능이다. 32비트설치프로그램을아무런수정없이 64비트윈도우에서실행했을경우에 C:\Program Files로파일들을설치하게되어있었다면실제로는 C:\Program Files(x86) 에파일들이들어가게된다. C:\Program Files에들어가서아무리찾아봐야아무것도나오지않는다. 따라서 C:\Program Files에들어가는것들은 64비트응용프 BOOL Wow64DisableWow64FsRedirection( PVOID* OldValue ); Wow64DisableWow64FsRedirection(&OldValue); 와같이호출하면 redirection이꺼지면서원하는대로접근이가능해진다. 기본적으로 32비트프로세스는 redirection이동작하고있는게정상이므로특별한이유가있을경우에만이 API를사용해서처리를해주면된다. 예를들어 32비트설치프로그램이드라이버를설치하는위치가 C:\Windows\system32\drivers였다면 64비트윈도우상에서실제로드라이버파일이복사되는위치는 File System Redirection에의해 C:\Windows\syswow64\drivers가된다. 드라이버가이위치에있다고특별히실행에문제가있는것은아니다. 하지만어차피 64비트로포팅한드라이버이고나중에 64비트응용프로그램이사용할수도있으니 64비트원래시스템폴더인 C:\Windows\system32\ drivers로드라이버가복사되게하고싶다면앞에서설명한방법으로 < 표 1> File System Redirection 32비트프로세스접근위치 64비트윈도우 Redirection 위치 C:\Program Files C:\Program Files(x86) C:\Windows\system32 C:\Windows\syswow64 마이크로소프트웨어 239
실 전! 강 의 실 개발자를위한실전 64 비트! 64 비트윈도우커널탐구 처리할수있다. Registry Redirection은 32 비트프로세스가레지스트리에접근할때특정키에대해자동으로다른위치를참조하게하는기능이다. 역시 32비트설치프로그램에서 HKLM\Software에무엇을기록하려고했다면그것은 WOW64에의해 HKLM\Software\Wow 6432Node에기록된다. 32비트프로세스들이접근하는레지스트리는 WoW6432Node로별도관리된다. 특별한이유가없다면그냥이정도사실만알고그대로사용하면된다. 하지만특별한상황에는이것을무시할수있는방법이있다. RegOpenKeyEx(), RegCreateKey Ex() 에다음과같은플래그를주면된다. KEY_WoW64_64KEY - 64비트레지스트리에접근할때 KEY_WoW64_32KEY - 32비트레지스트리에접근할때이 API들은프로세스가 32비트건 64비트건상관없이플래그에따라동작한다. 주로설치시파일복사나레지스트리기록이많으므로 File System Redirection과 Registry Redirection은정확하게이해하고있어야한다. 물론설치시가아니라응용프로그램동작중에도마찬가지로적용되는내용들이다. 32비트프로세스에서파일이나레지스트리를다루는작업이어디에어떻게반영되어야하는지생각해보고특별한상황에서는앞에서설명한특별한방법들을사용해야한다. 64비트 CPU 종류 64비트포팅을하게되는제품이지원하는 CPU의범위는어떻게결정할것인지생각해보자. 현재 64비트윈도우가지원하는 CPU의종류는다음과같다. AMD 옵테론, AMD 애슬론64, 인텔제온 EM64T, 인텔펜티엄 4 with EM64T, 인텔아이태니엄프로세서 ( 아이태니엄2 포함 ) < 표 2> Registry Redirection 32비트프로세스접근위치 64비트윈도우 Redirection 위치 HKLM\Software HKLM\Software\WoW6432Node HKCR HKCR\Wow6432Node < 표 3> CPU 별생성해야할바이너리종류 지원대상 CPU 생성할바이너리종류 AMD 옵테론 AMD64 바이너리 AMD 애슬론64 AMD64 바이너리 인텔제온 EM64T AMD64 바이너리 인텔펜티엄 4 with EM64T AMD64 바이너리 인텔아이태니엄프로세서 ( 아이태니엄2 포함 ) IA64 바이너리 AMD 옵테론과 AMD 애슬론64는통틀어서 AMD64라고도하는데옵테론은서버용, 애슬론64는클라이언트용이다. 포팅시에는두가지를구분하지않고그냥 AMD64로작업하면된다. AMD64는과거 x86 계열이 8비트에서 16비트로갈때나, 16 비트에서 32비트로갈때나하위호환을중시하면서발전해온것처럼 32비트하위호환을제공한다. 32비트모드로사용할수도있으며 64비트모드로사용할경우에도기존의 32비트코드들을자연스럽게동작시킬수있는방법도제공한다. 인텔은진정한 64비트컴퓨팅을보여주겠다는일념으로과감하게하위호환성을버리고진보적인아키텍처의 CPU를설계하고자했다. 그렇게탄생한것이아이태니엄이다. 아이태니엄은 CISC 구조를따랐던 x86 호환시스템과달리 RISC 구조를채택하면서인스트럭션구조, 레지스터셋을전혀새롭게구성하여성능향상을꾀하였다. 통상아이태니엄1, 2를통틀어 IA64라고하는데최근에는대체로아이태니엄2가주류라고보면될것이다. 그러면인텔의 EM64T(Extended Memory 64 Technology) 라는것은무엇일까? 앞에서설명했듯이 AMD와인텔은 64비트시대를준비하면서전혀다른노선을걷게된다. AMD는인텔호환칩을만들던회사답게 (?) 64비트에서도 x86 호환이라는노선을그대로가져가면서 64비트확장과약간의기능추가로 64비트 CPU를만들어냈다. 실제로 AMD64는레지스터셋이모두과거와똑같고레지스터가 64 비트로확장되면서몇개만추가됐다. 레지스터가 64비트로확장됐을뿐인스트럭션도거의비슷하다. 반면에인텔은계속해서발목을잡아오던하위호환을버리고기본구조를새로설계하는노선을선택한다. 레지스터셋, 인스트럭션셋, 내부구조가모두새롭게구성됐다. IA64의디스어셈블리코드를본적이있는독자들은느꼈겠지만처음그것을접하면 생뚱맞다 라는표현이절로나올만큼대단히새롭다. 하지만인텔이너무앞서간것인지 AMD가시장을잘파악한것인지는모르겠지만 AMD64 쪽이오히려주목받는상황이되었다. 아이태니엄은서버급에서점진적으로공급되고있는반면에애슬론64는클라이언트 PC에채용되어많은일반대중들사이를순식간에파고드는상황이되어버린것이다. 여기에서재미있는일이벌어지는데인텔이 EM64T라는하위호환 64비트기술을들고나온것이다. 이것을재미있다고표현하는이유는사실 EM64T는 AMD64 호환 CPU 라고봐야하기때문이다. AMD가인텔호환 CPU를만드는회사였는데이번에는인텔에서 AMD 호환 CPU를만드는상황이벌어진것이다. 역사는돌고돈다고했던가? 64비트커널모드드라이버개발시 EM64T는 AMD64 계열로분류되며 EM64T 플랫폼을지원하려고한다면 AMD64 바이너리를생성해내야한다. 게다가요즘 AMD, 인텔의최신 CPU들에서악성코드실행을방지하는기능이라고말하는 240 2 0 0 5. 9
64 비트시대를향한첫걸음, 커널모드드라이버포팅 NX(No Execution) 역시 AMD64가먼저선보인기능이라는점까지감안하면 64비트 CPU 전쟁에서는 AMD가기선제압을한셈이다. 64비트드라이버빌드는무엇으로? 64비트드라이버를빌드하려면 64비트빌드가지원되는 DDK를사용하면된다. 몇년전부터나오는 DDK들은이미 64비트빌드환경을포함하고있다. 이것을통해 64비트드라이버를빌드할수도있으나아무래도최신버전의 DDK를이용하는것이좋다. 현재최신 DDK 는윈도우서버 2003 SP1 DDK이다. 뉴스그룹이나개발자포럼의게시판을보면최신 DDK를어떻게구하는지묻는질문이심심치않게올라오는데, 이것은당연히마이크로소프트 ( 이하 MS) 사이트에서구할수있다. search.microsoft.com에 DDK 또는 Driver Development Kit 등을입력하여잘찾아보면나오지만결국우리가찾고자하는 URL은다음과같다. http://www.microsoft.com/whdc/devtools/ddk/default.mspx 이페이지에서 Order the DDK Suite or the current DDK 를선택해진행하면된다. 다음단계에서 DDK Suite가아니라윈도우서버 2003 SP1 DDK를선택해야한다. DDK Suite이란 DDK + OS 심볼 + 테스트툴 + 기타를모아놓은패키지인데이것은유료이다. 다행히윈도우서버 2003 SP1 DDK는무료로제공하고있다. 하지만예전같이웹에서바로다운받을수있는방법은없어졌고 CD를신청하여배송받는형태만제공된다. 64비트드라이버빌드하기이렇게구한최신 DDK를설치하고나면시작메뉴에 Development Kits 항목이생기는데이를따라들어가보면빌드가능한환경은윈도우 2000, XP, 서버 2003이다 (< 화면 1>). 자주보는질문중하나가어떤빌드를사용해서드라이버를만들어야하는지에대한질문인데, 간단히답변하면그것은지원할 OS에따른다. 윈도우 2000에서동작하는드라이버는윈도우 2000 빌드환경을사용하고 XP에서동작하는드라이버는 XP 빌드환경을사용하면좋다는뜻이다. 하지만대부분하나의드라이버를빌드해서모든 < 표 4> 64비트빌드환경의종류와용도 DDK 빌드종류 용도 윈도우서버 2003 Checked IA-64비트빌드환경 IA64용디버그버전빌드 윈도우서버 2003 Free IA-64비트빌드환경 IA64용릴리즈버전빌드 윈도우서버 2003 Checked x64비트빌드환경 AMD64, EM64T 디버그버전빌드 윈도우서버 2003 Free x64비트빌드환경 AMD64, EM64T 릴리즈버전빌드 < 화면 1> DDK 설치후시작메뉴화면 OS를다커버하고싶어하니이런경우라면윈도우 2000 빌드환경을사용해서드라이버를빌드하고이드라이버를 XP, 서버 2003 등에사용하는방법을취해야한다. OS가하위호환성을보장하므로셋중에버전이가장낮은윈도우 2000 빌드환경으로만들어진드라이버는상위버전 OS 들에서동작이가능하다. 반면에 XP 빌드환경으로빌드한드라이버를윈도우 2000에서사용할수있긴하지만 100% 보장되지않기때문에주의해야한다. 대표적인예로 XP 빌드환경을이용하면서 XP 에서추가된 API를사용했다면이렇게만들어진드라이버는윈도우 2000에서는동작하지않기때문이다. 64비트빌드를위해사용할수있는빌드환경을 < 표 4> 에서살펴보자. 64 비트드라이버빌드를위해서는 < 표 4> 에서설명하는것처럼용도에맞는빌드환경을실행하고소스코드가존재하는폴더로이동한후에 build 명령으로빌드하면된다. 여기서등장한 X64라는용어는 x86 호환성을유지하면서 64비트확장을한 CPU를지칭하는것이다. 원래는 AMD64비트빌드라는용어였으나 EM64T라는것이나오면서 x64라고정리하게된것이다. 주의할것은 DDK 내부에는 x64라는용어가없다는것이다. 현재출시되고있는 64비트윈도우중 x64 에디션이라는용어가붙은것들은처음부터 AMD64를타겟으로개발된것들이었다. AMD64용커널을 [ 64비트포팅과인라인어셈블리 ] 64비트컴파일러는인라인어셈블리를지원하지않는다. 기존의소스코드에 x86 인라인어셈블리가있었다면이것은완전히제거하고 C 언어로다시작성해야하는포팅대상이된다. 이에대한이유는본문에서설명한 CPU 종류들로설명할수있다. X86, AMD64, IA64는 CPU 자체가모두다르고각각의어셈블리언어도다르다. 띠라서컴파일러가인라인어셈블리를지원하더라도어차피 CPU마다인라인어셈블리는모두다르게작성되어야한다. 인라인어셈블리을지원하지않는 64비트컴파일러는결과적으로프로그래머에게 C 언어만이용해서 CPU 독립적인소스코드를작성하라고권장하는것이다. 어셈블리언어를꼭써야만하는경우가있다면어셈블리소스파일을별도로만드는방법을사용할수있다. 이것을컴파일하여만들어진오브젝트파일을 C 언어로만들어진나머지오브젝트파일들과링크하여바이너리를생성하면된다. 마이크로소프트웨어 241
실 전! 강 의 실 개발자를위한실전 64 비트! 64 비트윈도우커널탐구 개발하고있을당시에는 EM64T도없었고 x64라는용어도없었다. DDK 내부에서사용하는 64비트 CPU에대한디파인을찾아보면여전히 _M_IA64, _M_AMD64만으로구분되고있다. MS에서는향후에도 _M_AMD64 디파인을 _M_X64 같은것으로바꿀계획이없다고한다. 이미만들어진소스코드들을용어때문에고치기도어렵고어차피 EM64T처럼 x64로분류되는것들은이미 AMD64의클론일테니말이다. 드라이버포팅예제이제실제로 DDK 샘플중하나를골라포팅하면서 64비트포팅에대한감각을본격적으로익혀보자. 최신 DDK에는 64비트빌드환경이기본적으로포함되어있는것에서알수있듯이최신 DDK의샘플들은대부분 64비트빌드를고려해만들어졌다. 즉, 이미 64 비트포팅을마친상태라고볼수있다. 따라서 64비트환경에대한고려가되기전에만들어진샘플을가지고작업해야의미가있을것이다. 그래서 NT4 DDK의샘플중에하나를골랐다. 이제부터 NT4 DDK에포함 되어있는샘플소스중에서 DDK\src\general\portio를직접포팅해보자. 다음과같은절차로진행하면된다. 1단계 - 빌드환경중윈도우서버 2003 Checked IA-64( 또는 x64) 비트빌드환경을선택하여커맨드창을띄운다. 2단계 - 샘플소스중에서 DDK\src\general\portio 폴더로이동한다. 3단계 - portio\sys, portio\gpdread, portio\gpdwrite 폴더에서각각 sources 파일을열어서 targetpath를다음과같이편집한다. TARGETPATH=. 현재작업폴더밑에바이너리가생성되게하려는것이다. 4단계 - 다음과같이명령하여빌드한다. Build -cef < 리스트 1> Sys 드라이버모듈에서발생하는오류내용 1>errors in directory e:\homework\test\general\portio\sys 1>sys\genport.c(223) : error C4312: type cast : conversion from ULONG to PVOID of greater size 1>sys\genport.c(513) : error C4311: type cast : pointer truncation from PVOID 1>sys\genport.c(526) : error C4311: type cast : pointer truncation from PVOID 1>sys\genport.c(526) : error C4312: type cast : conversion from unsigned long to PUCHAR of greater size 1>sys\genport.c(530) : error C4311: type cast : pointer truncation from PVOID 1>sys\genport.c(530) : error C4312: type cast : conversion from unsigned long to PUSHORT of greater size 1>sys\genport.c(534) : error C4311: type cast : pointer truncation from PVOID 1>sys\genport.c(534) : error C4312: type cast : conversion from unsigned long to PULONG of greater size 1>sys\genport.c(544) : error C4311: type cast : pointer truncation from PVOID 1>sys\genport.c(544) : error C4312: type cast : conversion from unsigned long to PUCHAR of greater size 1>sys\genport.c(548) : error C4311: type cast : pointer truncation from PVOID 1>sys\genport.c(548) : error C4312: type cast : conversion from unsigned long to PUSHORT of greater size 1>sys\genport.c(552) : error C4311: type cast : pointer truncation from PVOID 1>sys\genport.c(552) : error C4312: type cast : conversion from unsigned long to PULONG of greater size 1>sys\genport.c(652) : error C4311: type cast : pointer truncation from PVOID 1>sys\genport.c(665) : error C4311: type cast : pointer truncation from PVOID 1>sys\genport.c(665) : error C4312: type cast : conversion from unsigned long to PUCHAR of greater size 1>sys\genport.c(670) : error C4311: type cast : pointer truncation from PVOID 1>sys\genport.c(670) : error C4312: type cast : conversion from unsigned long to PUSHORT of greater size 1>sys\genport.c(675) : error C4311: type cast : pointer truncation from PVOID 1>sys\genport.c(675) : error C4312: type cast : conversion from unsigned long to PULONG of greater size 1>sys\genport.c(687) : error C4311: type cast : pointer truncation from PVOID 1>sys\genport.c(687) : error C4312: type cast : conversion from unsigned long to PUCHAR of greater size 1>sys\genport.c(692) : error C4311: type cast : pointer truncation from PVOID 1>sys\genport.c(692) : error C4312: type cast : conversion from unsigned long to PUSHORT of greater size 1>sys\genport.c(697) : error C4311: type cast : pointer truncation from PVOID 1>sys\genport.c(697) : error C4312: type cast : conversion from unsigned long to PULONG of greater size 242 2 0 0 5. 9
64 비트시대를향한첫걸음, 커널모드드라이버포팅 5단계 - 빌드오류를확인한다. 빌드후에생성된 buildchk_wnet_ia64( 또는 AMD64).err 파일을열어서확인해본다. 6단계 - 오류가발생한소스코드를수정 ( 포팅 ) 하고 4단계부터다시반복한다. 포인터의타입캐스팅빌드를해보면 APP 모듈들은모두빌드가잘되고, Sys 드라이버모듈에서오류가발생하는데그내용들은 < 리스트 1> 과같다. 얼핏보면복잡해보이지만자세히보면같은오류들이반복되고있다. 모두 error C4312 또는 error C4311 오류들인데이것들이바로 64 비트포팅작업에서가장많이나오는오류중에하나이다. 오류의내용은 32비트정수를 64비트포인터로대입하는경우와그반대의경우에대한것이다. 오류와이에해당하는소스코드를보면상황을조금더쉽게이해할수있다. 1>sys\genport.c(513) : error C4311: type cast : pointer truncation from PVOID 511 : if (nport >= pldi->portcount 512 : (nport + DataBufferSize) > pldi->portcount 513 : (((ULONG)pLDI->PortBase + nport) & (DataBufferSize - 1))!= 0) 헤더파일에서 PVOID로선언되어있는 pldi->portbase을 ULONG으로캐스팅하다가발생한오류이다. 포인터를항상 32비트로가정하고코드를작성했던 32비트시대의잔재이다. 64비트환경에서포인터는 64비트이므로이런것들은빨리사라져줘야한다. 그런데코드를자세히보면좀이상한생각이든다. PVOID를 ULONG으로캐스팅했는데왜오류가발생할까? 상위 4바이트가잘리면서타입캐스팅이되어야할것만같은데말이다. 하지만 64비트컴파일러는 32비트처럼친절하게캐스팅해주기보다는깐깐하게오류를발생시킨다. 64비트환경에서가장크게바뀐것이포인터크기이므로포팅시가장주의해야할부분이바로포인터를다루는부분이기때문이다. 포인터를크기가다른타입으로캐스팅하면포인터가손상될우려가있으므로포인터를캐스팅하는작업에서는컴파일러가오류를표시하여코드작성자에게확인을요구하는것이다. 코드작성자의의도가포인터를손상시키면서캐스팅하는것이맞다면새롭게제공된캐스팅하는매크로를통해서명시적으로알려줘야컴파일러가오류를발생시키지않는다. 하지만포인터가손상되는것을원하지않았다면컴파일러에게고마워하면서적절하게코드를수정해줘야한다. 포팅을어떤식으로하느냐는그때그때다르다. 상황에맞춰서신중하게잘판단해야한다. 이번경우는어떻게수정해야할까? 513 라인의소스코드를천천히 분석해보면 PortBase는 64비트포인터이지만 32비트정수인 nport 와정수연산이되어야한다. 이런경우 64비트포인터를 64비트정수인것처럼처리해주는 ULONG_PTR 타입캐스팅을사용하면된다. ULONG_PTR은정수이지만그크기는현재빌드되는환경의포인터크기에따라달라진다. 32비트빌드환경에서 ULONG_PTR은 4바이트의크기를가지지만 64비트빌드환경에서는 8바이트의크기를가진다. 포인터를정수로취급하여사용하고싶은경우 ULONG_PTR 로선언된변수에저장하면 32비트에서나 64비트에서나안전하게동작하는코드를작성할수있다. 결과적으로다음과같이수정된다. 513 : (((ULONG_PTR)pLDI->PortBase + nport) & (DataBufferSize - 1))!= 0) 첫번째수정예는이와같다. 포팅을어떤식으로하느냐는그때그때다르다고했으므로다른어떤방법도있을수있는지두번째방법을보여줘야할것같다. 문법적인측면만따지는것에서벗어나서코드의내용을보고어떻게수정할지결정할수도있다. 앞의예에서코드를잘살펴보면 pldi->portbase는 I/O 포트의주소로여겨진다. 대부분의 I/O 포트의주소는낮은주소공간에맵핑된다. 높은주소공간에맵핑될수도있겠지만여기서는낮은주소를사용하는포트라는것을여러분이알고있다고가정해보자. 32비트만으로충분하고굳이 64비트일필요가없는상황말이다. 이런경우는원래코드에서 ULONG 캐스팅을했던것처럼명시적으로 32비트만캐스팅하게해도전혀문제가되지않는다. 그런데 ULONG 캐스팅을하면컴파일오류가발생한다. 어떻게해야할까? 이런것을가능하게하기위해서는이런의미를명확히나타내는매크로를사용하도록되어있다. 그것이바로 PtrToUlong() 이다. PtrToUlong() 매크로를사용해서다음과같이수정할수도있다. 513 : ((PtrToUlong(pLDI->PortBase) + nport) & (DataBufferSize - 1))!= 0) [ ULONG_PTR 의의미 ] ULONG_PTR은이름만보면 ULONG형포인터를나타내는것으로오해할수있다. 하지만 ULONG형포인터는이미알고있듯이 PULONG이다. ULONG_PTR 은이런의미가아니다. ULONG_PTR에서 _PTR은변수의크기를의미한다고이해하기바란다. ULONG 타입인데그크기는포인터크기를따라간다는뜻이다. 64비트환경에서는 64비트 ULONG이고 32 비트환경에서는 32비트 ULONG이된다는말이다. 그렇다면 PULONG_PTR은무엇일까? 그렇다. 그냥 ULONG_PTR 타입을가리키는포인터일뿐이다. 마이크로소프트웨어 243
실 전! 강 의 실 개발자를위한실전 64 비트! 64 비트윈도우커널탐구 PtrToUlong() 매크로를사용하면 pldi->portbase은 PVOID이지만 ULONG 캐스팅이된다. PtrToUlong() 매크로의선언을찾아보면그이유를알게될것이다. 여하튼이코드도첫번째코드와마찬가지로아무런문제없이컴파일되지만의미는확연히다르다. 첫번째코드는결과가 64비트로나오는데비해이두번째코드는결과가 32비트로나온다는점이다. 둘다문법적으로는문제없이수정된것이맞는데동작시에도문제가없는지는신중히생각해봐야할사안이다. 1>sys\genport.c(526) : error C4311: type cast : pointer truncation from PVOID to ULONG 1>sys\genport.c(526) : error C4312: type cast : conversion from unsigned long to PUCHAR of greater size 1>sys\genport.c(530) : error C4311: type cast : pointer truncation from PVOID to ULONG 1>sys\genport.c(530) : error C4312: type cast : conversion from unsigned long to PUSHORT of greater size 1>sys\genport.c(534) : error C4311: type cast : pointer truncation from PVOID to ULONG 1>sys\genport.c(534) : error C4312: type cast : conversion from unsigned long to PULONG of greater size 1>sys\genport.c(544) : error C4311: type cast : pointer truncation from PVOID to ULONG 1>sys\genport.c(544) : error C4312: type cast : conversion from unsigned long to PUCHAR of greater size 1>sys\genport.c(548) : error C4311: type cast : pointer truncation from PVOID to ULONG 1>sys\genport.c(548) : error C4312: type cast : conversion from unsigned long to PUSHORT of greater size 1>sys\genport.c(552) : error C4311: type cast : pointer truncation from PVOID to ULONG 1>sys\genport.c(552) : error C4312: type cast : conversion from unsigned long to PULONG of greater size 다음에나오는오류메시지들의경우들도코드를보면모두같은상황으로볼수있다. 524 : case IOCTL_GPD_READ_PORT_UCHAR: 525: *(PUCHAR)pIOBuffer = READ_PORT_UCHAR( 526: (PUCHAR)((ULONG)pLDI->PortBase + nport) ); 527: break; 528: case IOCTL_GPD_READ_PORT_USHORT: 529: *(PUSHORT)pIOBuffer = READ_PORT_USHORT( 530: (PUSHORT)((ULONG)pLDI->PortBase + nport) ); 531: break; 532: case IOCTL_GPD_READ_PORT_ULONG: 533: *(PULONG)pIOBuffer = READ_PORT_ULONG( 534: (PULONG)((ULONG)pLDI->PortBase + nport) ); 535: break; 모두 nport라는 ULONG 값을 PVOID와연산하기위해캐스팅하는것이므로 ULONG_PTR로캐스팅하여해결할수있다. 또다른방법으로 PtrToUlong() 매크로도사용할수있다. 방법1 526: (PUCHAR)((ULONG_PTR)pLDI->PortBase + nport) ); 방법2 526: (PUCHAR)(PtrToUlong(pLDI->PortBase) + nport) ); 어떤것을선택할지신중히생각해보기바란다. 나머지많은오류메시지들도모두똑같이처리하면된다. 단, 반복적인오류들에대해정신없이포팅을진행하다보면앞서설명한선택적인부분들을제대로인지하지못하고넘어가는경우도있는데그런것이재앙의원인이될수도있으므로주의해서살펴봐야한다. 타입캐스팅을넘어서아직첫번째오류를처리하지않았다. 다시첫번째오류메시지로돌아가보자. 1>sys\genport.c(223) : error C4312: type cast : conversion from ULONG to PVOID of greater size 223 : plocalinfo->portbase = (PVOID)MappedAddress.LowPart; 오류메시지로부터 MappedAddress.LowPart는 ULONG 타입임을알수있다. ULONG을 PVOID로캐스팅하다가오류가발생한것이다. 이미설명한바와비슷하게 32비트 ULONG을 64비트인 PVOID로캐스팅하지못하게되어있다. 32비트에저장되어있던값을그대로 64비트포인터로인정하지못하겠다는의미이다. 이번케이스도앞에서처리했던것과비슷한방법으로처리하면 UlongToPtr 매크로를사용해서수정할수있다. PortBase는낮은주소값만사용된다는것을코드작성자가보장한다면말이다. 수정된코드는다음과같다. 223: plocalinfo->portbase = UlongToPtr( MappedAddress.LowPart ); 다른방법을하나더고려해보자. 이번에는 PortBase에낮은주소값만사용된다는것을코드작성자가보장하지못하는상황이라서 MappedAddress는 64 비트환경에서 64비트주소값을가지고있을수있다고가정해보자. 이번경우는 MappedAddress의형태까지고 244 2 0 0 5. 9
64 비트시대를향한첫걸음, 커널모드드라이버포팅 려해야하는고급상황이라고할수있다. 눈치빠른독자들은이미 LowPart라는게나온것에서눈치챘겠지만이구조체는 HighPart 라는필드도있을것이고두개를합치면 64비트이므로역시 QuadPart라는필드도존재할것이다. 그렇다면어떻게수정해야할까? 정확하게확인해보기위해 MappedAddress 데이터의선언을살펴보자. 199: 0, 200: PortAddress, 201: &MemType, 202: &MappedAddress ); 응용프로그램과드라이버가공유하는구조체 Gdpwrite.c를보면다음과같은코드가있다. PHYSICAL_ADDRESS MappedAddress; sscanf(argv[2], %x, &InputBuffer.PortNumber); // Get the port number sscanf(argv[3], %x, &DataValue); // Get the data to be written. PHYSICAL_ADDRESS 는 Ntdef.h 에다음과같이선언되어있다. switch (argv[1][1]) { typedef LARGE_INTEGER PHYSICAL_ADDRESS, *PPHYSICAL_ADDRESS; 또 LARGE_INTEGER 는다음과같이선언되어있다. #if defined(midl_pass) typedef struct _LARGE_INTEGER { #else // MIDL_PASS typedef union _LARGE_INTEGER { struct { ULONG LowPart; LONG HighPart; }; struct { ULONG LowPart; LONG HighPart; } u; #endif //MIDL_PASS LONGLONG QuadPart; } LARGE_INTEGER; case d : IoctlCode = IOCTL_GPD_WRITE_PORT_ULONG; InputBuffer.LongData = (ULONG)DataValue; DataLength = offsetof(, LongData) + sizeof(inputbuffer.longdata); break; } IoctlResult = DeviceIoControl( hndfile, // Handle to device IoctlCode, // IO Control code for Write &InputBuffer, // Buffer to driver. Holds port & data. DataLength, // Length of buffer in bytes. NULL, // Buffer from driver. Not used. 0, // Length of buffer in bytes. &ReturnedLength, // Bytes placed in outbuf. Should be 0. NULL // NULL means wait till I/O completes. ); 64비트크기를가지는 LONGLONG으로선언된 QuadPart를확인할수있다. 그렇다면수정은아주간단하다. 이번에는캐스팅으로코드를수정하는것이아니라제대로된데이터를 PVOID에대입하는방법으로코드를수정하는것이다. 수정된코드는다음과같다. 223: plocalinfo->portbase = (PVOID)MappedAddress.QuadPart; InputBuffer라는것을드라이버로넘기는데이것은다음과같이선언된변수이다. InputBuffer; // Input buffer for DeviceIoControl 구조체는 GPIOCTL.h에다음과같이선언되어있다. 64비트머신에서 198 라인에있는다음의코드가 MappedAddress 변수에 64비트주소를얻어줄가능성을인정한다면 QuadPart를사용하도록수정하는게확실한 64비트포팅이라고할수있다. 198: HalTranslateBusAddress( Isa, typedef struct _ { ULONG PortNumber; // Port # to write to union { // Data to be output to port ULONG LongData; USHORT ShortData; UCHAR CharData; 마이크로소프트웨어 245
실 전! 강 의 실 개발자를위한실전 64 비트! 64 비트윈도우커널탐구 < 그림 2> 32 비트와 64 비트에서의 구조체 32 비트 gpdwrite.exe < 그림 3> 64 비트에서크기가변하는수정된 구조체 32 비트 gpdwrite.exe (ULONG 4 바이트, union 4 바이트 ) (PVOID 4 바이트, union 4 바이트 ) 64 비트 gpdwrite.sys 64 비트 genport.sys (ULONG 4 바이트, union 4 바이트 ) (PVOID 8 바이트, union 4 바이트 ) }; } ; 이 portio 예제에서응용프로그램은앞의구조체를사용해서데이터를전달하지만드라이버에서는구조체를사용하지않고응용프로그램에서전달한메모리주소를옵셋으로계산하면서값을읽거나쓴다. 왜이렇게작성되어있는지알수없지만, 드라이버에서도이구조체를사용했으면훨씬코드가깔끔해지지않았을까싶다. 어쨌튼응용프로그램과드라이버가구조체를공유하는상황을설명하기위해서드라이버에서도이구조체를다음과같이사용한다고가정해보자. *pinputbuffer pinputbuffer = (*) pirp->associatedirp.systembuffer; switch (IoctlCode) { case IOCTL_GPD_WRITE_PORT_ULONG: nportnum = pinputbuffer->portnumber; nlongdata = pinputbuffer->longdata; break; } 응용프로그램과드라이버가공유하는구조체가있을때문제가생길수있는경우는드라이버만 64비트포팅하고응용프로그램은 32 비트그대로사용하는경우이다. Gpdwrite.exe는 32비트이고 gen < 리스트 2> PortNumber 타입을 PVOID로수정한 GEN_WRITE_INPUT 구조체 typedef struct _ { PVOID PortNumber; // Port # to write to union { // Data to be output to port ULONG LongData; USHORT ShortData; UCHAR CharData; }; } ; port.sys는 64비트라면어떤상황이될까? 32비트 gpdwrite.exe가 DeviceIoControl로전달한 InputBuffer가 64비트 genport.sys에서제대로처리가될것인지생각해보자. < 그림 2> 와같이 는아무런문제를가지고있지않다. 32비트빌드를하거나 64비트빌드를하거나모두같은크기의필드만가지고있기때문이다. 이제본격적으로문제의상황을설명하기위해서 < 리스트 2> 와같이구조체를약간바꾸어보자. 첫번째필드인 PortNumber의자료형을 32비트와 64비트에서다른크기를가지는 PVOID로바꿨다. 이와같은구조체가있었다면어떤문제가발생한다는것일까? < 그림 3> 에서볼수있듯이 32비트 gpdwrite.exe는 8바이트의데이터를내려보냈는데이를받아서해석하는 64비트 genport.sys는 12 바이트데이터인것처럼액세스하려고한다. 64비트 Genport.sys에서 PortNumber를읽으면 32비트 gpdwrite.exe가전달한 8바이트가다읽힐것이고 LongData, ShortData, CharData 등을읽으면 32비트 gpdwrite.exe가전달한 8바이트뒤에있는영역에서쓰레기값이읽힐것이다. 이는곧드라이버가오동작을할상황이라는것을의미한다. PVOID가 32비트로빌드한 gpdwrite.exe와 64 비트로빌드한 genport.sys에서다른크기를가지기때문에구조체의크기가달라지고각필드의옵셋도달라지는것을보여주고있다. 이런상황을해결하기위해서는어쩔수없이구조체의공유를포기해야한다. 64비트 genport.sys는 32 비트 gpdwrite.exe가자신을사용하고있다는것을알고있으므로 DeviceIoControl이호출되었을때다음과같이명시적인 32비트구조체를정의해서데이터를액세스해야한다. typedef struct 32 { ULONG PortNumber; // Port # to write to union { // Data to be output to port ULONG LongData; USHORT ShortData; UCHAR CharData; }; } _32; 246 2 0 0 5. 9
64 비트시대를향한첫걸음, 커널모드드라이버포팅 < 그림 4> 32 비트프로세스를위한 _32 구조체 32 비트 gpdwrite.exe < 그림 5> 64 비트에서의 구조체 32 비트 gpdwrite.exe (PVOID 4 바이트, union 4 바이트 ) (PVOID 8 바이트, union 4 바이트 ) 64 비트 genport.sys 64 비트 genport.sys _32 (ULONG 4 바이트, union 4 바이트 ) (PVOID 8 바이트, union 4 바이트 ) < 그림 4> 는 64비트 genport.sys 드라이버가 GENPORT_WRITE _INPUT_32를사용해서 32비트 gpdwrite.exe로부터전달되는 구조체를정확히인식하는것을보여주고있다. IoIs32bitProcess() 를사용하자앞의예에서는응용프로그램은 32비트그대로사용하고드라이버만 64비트로포팅하는작업을진행했다. 이번에는이상태에서응용프로그램도 64비트로포팅하는상황이발생했다고가정해보자. 응용프로그램의 64비트포팅자체는앞에서드라이버를포팅했던것과마찬가지로몇가지만신경써주면어렵지않게진행할수있다. 정작문제는드라이버가갑자기 32비트응용프로그램과 64비트응용프로그램두가지를지원해야하는운명을맞이했다는것이다. 다른것들은크게문제가없으니이미 < 리스트 2> 에서예로들었던구조체를 DeviceIoControl로전달할때에대한문제만살펴보고넘어가도록하자. 64비트 gpdwrite.exe와 64 비트 genport.sys만동작하게한다면다음과같이아무런문제가없는상태가된다. 문제는하나의 64비트드라이버를만들어놓고이것을 32비트응용프로그램과 64비트응용프로그램에서모두사용할경우에발생한다. 32비트 gpdwrite.exe와 64 비트 gdpwrite.exe가모두하나의소스코드로빌드되어만들어진경우 64비트 genport.sys는 IoctlCode IOCTL_GPD_WRITE_PORT_ULONG을받았을때이것이 32비트로부터온것인지 64비트로부터온것인지구분해서처리해야한다. 64비트로부터온것이라면 < 그림 5> 와같이 GENPORT_WRITE_ INPUT을사용해야하고 32비트로부터온것이라면 GENPORT_ WRITE_INPUT_32를사용해야한다. 이것을어떻게구분할수있을까? 여기에대한답이바로 IoIs32bitProcess() 이다. 다음의코드에서사용예를보인다. *pinputbuffer _32 *pinputbuffer32 If (IoIs32bitProcess( Irp ) == TRUE) pinputbuffer32 = (_32*) pirp->associatedirp.systembuffer; else pinputbuffer = (*) pirp->associatedirp.systembuffer; switch (IoctlCode) { case IOCTL_GPD_WRITE_PORT_ULONG: If (IoIs32bitProcess( Irp ) == TRUE) { nportnum = pinputbuffer32->portnumber; nlongdata = pinputbuffer32->longdata; } Else // 64비트 { nportnum = pinputbuffer->portnumber; nlongdata = pinputbuffer->longdata; } break; } 이와같이 IoIs32bitProcess() 를사용하면 32비트프로세스에서발생한 Irp인지 64비트프로세스에서발생한 Irp인지판단할수있다. 여러분의 64비트드라이버가어떤모드의프로세스로부터 Irp를받고있는지확인해야한다면 IoIs32bitProcess() 를사용하자. 데이터미정렬문제 IA64에서포팅하면서특히신경써야할부분이있는데이것이바로데이터미정렬 (data misalignment) 문제이다. AMD64는 x86과마찬가지로별다른문제가없지만 IA64는이부분에서명백한제약을가지고있기때문이다. IA64는메모리주소경계에맞지않는주소를액세스할경우데이터미정렬예외 (data misalignment exception) 를발생시킨다. 이것은응용프로그램이나커널모두드라이버모두마찬가지여서데이터미정렬문제를잘고려하지않는다면응용프로그램은비정상종료가될수있고드라이버는블루스크린을발생시킬수있다. < 리스트 2> 를변형하여이문제를설명해보자. #pragma pack(1) 은 1바이트패킹, 즉구조체의필드들을모두바이트단위로붙여달라는뜻이다. 컴파일옵션중 /Zp1을사용했다면 pack(1) 과마찬가지의미이고소스내의모든구조체에적용될것이 마이크로소프트웨어 247
실 전! 강 의 실 개발자를위한실전 64 비트! 64 비트윈도우커널탐구 < 그림 6> 1 바이트정렬된 구조체의메모리구조 < 그림 7> 8 바이트정렬된 구조체의메모리구조 0 1 2 3 4 5 6 7 8 9 10 11 12 0 1 2 3 4 5 6 6 7 8 9 10 11 12 13 14 15 16 17 18 btest PortNumber LongData btest Padding PortNumber LongData 다. 이구조체는메모리상에어떻게존재하게되고무엇이문제가되는것일까? 1바이트패킹으로컴파일된구조체는메모리상에 < 그림 6> 과같이 13바이트길이로존재하게된다. 여기서두번째필드인 PVOID PortNumber를액세스할때 IA64에서는데이터미정렬예외가발생한다. 예외가발생하는이유는 PortNumer에해당하는 8바이트를참조하려고하는데이필드가메모리상에서첫번째 8바이트와두번째 8바이트경계에걸쳐있기때문이다. 좀더자세히설명하면 IA64는 64 비트 CPU이기때문에 8바이트단위로메모리어드레싱을하는것이기본구조로되어있다. 어떤메모리를참조하든지 8바이트씩참조하게되어있다. 8의배수인메모리주소에서 8바이트를참조하는것은참조단위에맞기때문에허용되지만다른주소에서 8바이트를참조하는것은 8바이트씩두번읽어야하는상황이발생하므로허용되지않는다. 이와같은구조체에서 PortNumber를제대로참조하려면 0부터 7 까지 8바이트를읽어서 PortNumber의 7바이트를취하고 8부터 15 까지 8바이트를읽어서 PortNumber의나머지 1바이트를취해서다시 8바이트로조합하는과정이이루어져야한다. AMD64는문제가없다고했는데 AMD64는실제로 CPU 내부에서이런작업을해주기때문에문제가없는것이다. 이런방식은사실 x86 계열의동작방식이다. AMD64도 x86 확장 CPU이기때문에이런특성을그대로계승 < 리스트 3> 1바이트패킹되고 btest가추가된 구조체 #pragma pack(1) typedef struct _ { BOOLEAN btest; PVOID PortNumber; // Port # to write to union { // Data to be output to port ULONG LongData; USHORT ShortData; UCHAR CharData; }; } ; #pragma pack() 하고있는것이다. 반면에 IA64는새로운설계를하면서 CPU에서이런기능이제거됐다. 따라서메모리주소경계에맞춰메모리를참조하는모든책임은 IA64를사용하는소프트웨어의몫으로넘어가게되었다. 이와같은상황에서우리가해줄수있는일은다음 3가지정도로볼수있다. 예외핸들링처리 정렬되지않은 (misaligned) 형태의구조체를정렬된 (aligned) 형태의구조체로수정 컴파일러에게도움요청데이터미정렬예외가발생하면예외핸들러에서이것을잡아서문제의상황을분석하여바로잡아주고정상적으로진행하게하는것도하나의방법일수있으나좀더쉬운다른방법들이있으므로이방법은거의사용할일이없을것같다. 두번째방법을주로사용하면되는데구조체의형태를문제가없도록수정하는방법이다. 사실 < 리스트 3> 에서 #pragma pack(1) 만제거하면문제는바로해결된다. 어떻게해결이되는지는메모리구성을보면쉽게이해할수있다. 1바이트패킹으로지정하지않으면컴파일러는기본값은 8바이트패킹이기때문에 < 그림 7> 과같이 8바이트정렬된형태의 20바이트짜리구조체를만들어낸다. 구조체의길이가좀길어지기는했지만 PortNumber를참조할때 8~15를정확히참조하기때문에데이터미정렬문제가발생하지않는다. Padding에의해늘어난길이가비효율적으로보인다면구조체의모양을조금바꾸는것도괜찮은방법이다. < 리스트 3> 에서 btest의위치만바꾸어다음과같이수정하면구조체의모양은 < 그림 8> 과같이바뀐다. typedef struct _ { PVOID PortNumber; // Port # to write to union { // Data to be output to port ULONG LongData; USHORT ShortData; UCHAR CharData; }; BOOLEAN btest; 248 2 0 0 5. 9
64 비트시대를향한첫걸음, 커널모드드라이버포팅 } ; < 그림 8> 8 바이트정렬되고 btest 를맨뒤로배치한메모리구조 처음과같이 13바이트를차지하는구조체이면서 PortNumber를참조할때데이터미정렬문제가발생하지않는구조체가되었다. 사실구조체의필드를잘배열해서메모리주소경계에걸친데이터가존재하지않게하고구조체가차지하는메모리를줄이는것은 64비트포팅에서만존재하는문제는아니다. 일반적으로구조체를만들때늘염두에둬야하는사항이라고할수있다. 다시정리하면 64비트포팅에서데이터미정렬문제를해결하려면 #pragma pack(1) 같은것을제거하여구조체의필드들이 8바이트정렬 (alignment) 을준수하도록해야한다는것이다. 세번째컴파일러에게도움을요청해야하는상황은죽어도 #prag ma pack(1) 을사용해야만하는상황일것이다. 경우에따라하위버전과의데이터호환성과같은기타구구절절한사연에의해 1바이트정렬 (alignment) 을유지해야만하는상황도있을것이다. 이런상황이라면어쩔수없이컴파일러에게도움을요청해야한다. < 리스트 3> 과같은구조체를어쩔수없이사용해야할경우 UNALIGNED 매크로를이용할수있다. 다음코드에서 UNALIGNED 매크로의사용예를보인다. 데이터미정렬예외 (data misalignment exception) 가발생하는코드 pinputbuffer->portnumber = p; 데이터미정렬예외 (data misalignment exception) 가발생하지않는코드 *(UNALIGNED PVOID)&pInputBuffer->PortNumber = p; 0 1 2 3 4 5 6 PortNumber 절반의성공필자는이포팅예제에서컴파일은성공했지만동작테스트는수행하지않았다. 컴파일을성공적으로완료한것만으로 64비트포팅이완전히끝났다고할수있을까? 당연히그렇지않다. 이것은절반정도의작업을마친것이라고보면될것이다. 프로그래머라면모두다느끼겠지만테스트되지않은코드는그냥코드일뿐이다. 어떤동작도보장할수없는코드일뿐이다. 테스트를통해코드를검증해야만가치있는코드로인정받을수있다. 앞에서 64비트포팅된드라이버를실제로동작시키려고하다보면어떤문제가발생할수도있다. 이런것들은컴파일러레벨에서잡을수없는논리적인오류들이거나 IA64의데이터미정렬문제이거나기타의어떤오류들일것이다. 하지만이제는이런문제를두려워하지않아도된다. 앞서설명한포팅의원칙들과과거 64비트기사들에서다뤘던내용들을숙지하고있다면수정할수있는방법을어렵지않게찾아낼수있을것이다. 따라서나머지부분은여러분의숙제로남겨놓을것이다. 이제부터커널모드드라이버 64비트포팅을향한발걸음을힘껏내디뎌보자. m a s o 7 8 9 10 11 12 LongData btest 정리 김세미 semsem@imaso.co.kr 과같은형태의코드를 와같은형태의코드로수정하면데이터미정렬예외가발생하지않는다. UNALIGNED 매크로는무슨마법을부리고있는것일까? 우리가 UNALIGNED 매크로를사용하는것은컴파일러에게 이것은메모리주소경계에정렬되지않은메모리포인터입니다. 잘처리해주세요 라고요청하는것이다. 이럴경우컴파일러는한꺼번에 8바이트액세스를하는코드를생성하지않고친절하게도 1바이트씩메모리를읽어서 8바이트로조합해주는코드를생성해준다. 이렇게되면오류는발생하지않지만코드양이늘어나게된다. 코드의양이늘어나게된다는것은하는일이많아지는것이므로성능에영향을미친다는것을의미한다. DDK를보더라도 UNALINED 매크로를사용하는것은성능에많은영향을주는작업이므로가급적사용하지말라고권고한다. 이런사항들을모두알고있어도어쩔수없는경우라면성능을희생해서라도 UNALINGED를써야할수밖에없다. 마이크로소프트웨어 249