VC++ 6.0 툴을사용한디바이스드라이버제작방법 1. win32 App 타입으로새로운프로젝트를생성한다. 2. 메뉴에서프로젝트셋팅을선택한후다음과같이설정을해준다. A. C/C++ Tab의 general Category에서 Debug Info를 Program Database로설정한다. B. C/C++ Tab의 C++ language Category에서 Enable exception handling에체크를없앤다. C. C/C++ Tab의 code generation Category에서 calling 방식을 cdecl 에서 stdcall로변경해준다.( 그림 1_1참조 ) D. C/C++ Tab의 Preprocessor Category에서기존에있는 definition을모두지우고 DBG=1,_X86_,_WIN32_WINNT=0x500로재정의해준다.( 그림 1_2 참조 ) E. 2-B와같은부분에서 Additional include directory부분에 DDK include file이있는디렉토리를지정해준다.( 예를들어필자의경우는 D: WINDDK 2600 inc ddk wxp와 D: WINDDK 2600 inc wxp으로지정하였다. 그림 1_2 참조 ) F. C/C++ Tab의 project option에서 /GZ 옵션을제거한다.( 주의 : -Gz와 /GZ는다른옵션이다!) G. Link Tab의 Customize Category에서 Link incrementally의체크를없앤다. H. Output file name의확장자를.exe에서.sys로바꿔준다.( 그림 1_3 참조 ) I. Link Tab의 Input Category에서 object/library module부분에있는라이브러리파일리스트를모두지우고 int64.lib ntoskrnl.lib hal.lib ntdll.lib로바꿔준다. 그리고 ignore all default libraries 의체크박스를체크한다.( 그림 1_4 참조 ) J. Additional library path에 DDK 라이브러리파일들이위치한디렉토리를지정한다.( 그림 1_5 참조 ) K. Link Tab의 output Category에서 Base address, Entry-point symbol값을각각 0x10000, DriverEntry로지정한다.( 그림 1_5 참조 ) L. Link Tab의 project option에서 /subsystem:windows 옵션을제거하고 driver subsystem: NATIVE,5.00 옵션을추가한다. 3. 2번과같이셋팅을하면일단디바이스드라이버의소스를빌드할수있는기본적인셋팅은끝납니다. 여기에추가적으로역시프로젝트셋팅메뉴의 Custom Build Tab에서 Commands부분에 copy $(TargetPath) $(SystemRoot) System32 Drivers *.* 라고적어주고 outputs부분에 $(SystemRoot) System32 Drivers $(TargetName) 라고적어줍니다. 이것은컴파일및빌드가성공적으로이루어지면자동으로윈도우디렉토리에있는 System32 drivers디렉토리에해당.sys 파일을카피해주는역활을합니다. 나중에다시설명하겠지만디바이스드라이버파일을동작시키기위해서는어차피위와같은작업을해줘야하므로이렇게해놓는것이편합니다.( 그림 1_6 참조 )
< 그림 1_1> < 그림 1_2>
< 그림 1_3> < 그림 1_4>
< 그림 1_5> < 그림 1_6>
예제 1:Hello Driver World 이제 VC++6.0 에서의셋팅이제대로되었는지확인하기위해아래의소스를적고 Build 를시켜봅니다 < 소스 1.> // Exercise Project #1 : Hello Driver World // This program is very simple NT driver source It can only create virtual device and delete it. #include <ntddk.h> PDEVICE_OBJECT DeviceObject = NULL; NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath); VOID Hello_DriverUnload(IN PDRIVER_OBJECT DriverObject); NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) NTSTATUS status = STATUS_SUCCESS; UNICODE_STRING DeviceName; DriverObject->DriverUnload = Hello_DriverUnload; RtlInitUnicodeString ( &DeviceName, L" Device HELLODRV" ); status = IoCreateDevice ( DriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject ); if (!NT_SUCCESS (status)) return status; }
KdPrint(("Hello Driver World!!!")); return status; } VOID Hello_DriverUnload(IN PDRIVER_OBJECT DriverObject) if(deviceobject) IoDeleteDevice(DeviceObject); DeviceObject = NULL; } KdPrint(("Hello Driver Unload!!!")); } 위의소스를작성하고빌드했을때아래와같은문장이나오면일단성공한것이라고생각해도무방합니다. --------------------Configuration: HelloDrv - Win32 Debug-------------------- Copying Driver to System32 Drivers 1개파일이복사되었습니다. HelloDrv.sys - 0 error(s), 0 warning(s) 이드라이버가제대로컴퓨터에로딩이되고동작을하는지확인하기위해서는다음과같은작업이필요합니다. 1. 레지스트리편집기를실행시킨다.( 윈도우의시작-> 실행메뉴를선택하고열기에 regedt32을쓰고엔터를친다.) 2.. 레지스트리편집기실행되면왼쪽의트리뷰에서 HKEY_LOCAL_MACHINE SYSTEM Current ControlSet Services에서마우스오른쪽버튼을클릭, 새로만들기-> 키를선택해새로운키를생성한다. 4. 생성한키의이름은위에서만든드라이버파일의이름으로한다.( 예를들어드라이버파일이 Hello_drv.sys라면키이름은 Hello_drv로 ) 5. 생성한키를선택한후오른쪽버튼을클릭, 새로만들기->DWORD값을선택하여새 Value를생성한다. Value의이름은 Start로하고해당값에 1을집어넣는다.( 그림의값 (3) 은잘못된값임 ) 6. 5와같은방법으로 ErrorControl, Type값을만들고둘모두 1값을셋팅한다.( 그림 1_7 참조 ) 7. 위의작업이모두끝나면이제모든작업을종료하고컴퓨터를재부팅한다.
< 그림 1_7> 위의작업을제대로수행했다면컴퓨터재부팅시윈도우가드라이버를성공적으로인식했을것입니다. 이드라이버가제대로동작하는지확인하기위해서는다시아래와같은작업이필요합니다. 1. 시작-> 프로그램-> 관리도구-> 컴퓨터관리를선택, 실행시킨다.( 혹은바탕화면에있는내컴퓨터아이콘에서오른쪽버튼클릭, 관리메뉴를실행시켜도된다.) 2. 컴퓨터관리의왼쪽뷰에서장치관리자를클릭한다. 그러면현재내컴퓨터에등록되어있는드라이버들의리스트를볼수있다. 3. 다시왼쪽뷰의장치관리자에서오른쪽버튼클릭, 혹은보기메뉴를클릭한후숨김장치표시를체크한다. 그러면오른쪽드라이버리스트에몇가지항목이추가되는것을볼수있다.( 그림 1_8참조 ) 4. 메뉴에서동작-> 하드웨어변경사항검색을선택하여장치관리자에새로만든드라이버를등록한다. 5. 오른쪽리스트에서 비플러그앤플레이드라이버 를선택확장하면리스트들중에서우리가만든드라이버를확인할수있다.( 그림 1_8에서는 Hello_drv) 6. 해당드라이버를더블클릭 ( 혹은오른쪽버튼클릭후속성메뉴를선택 ) 하면드라이버의등록정보를볼수있는데여기서드라이버탭에서드라이버를수동으로작동 / 중지를시킬수있다.( 그림 1_9, 1_10 참조 ) 7. DebugView 유틸리티를먼저실행시킨후해당드라이버를시작 / 중지시켜보면각각 Hello Driver World!!! 와 Hello Driver Unload 메시지가 DebugView화면에나타나는것을확인할수있다.( 그림 1_11참조 )
< 그림 1_8> < 그림 1_9>
< 그림 1_10> < 그림 1_11>
Hello Driver World 소스분석 이제소스를차근차근살펴보도록하겠습니다. #include <ntddk.h> ntddk.h파일은 DDK에있는헤더파일로써 - stdio.h나 windows.h처럼 - 드라이버작성에필요한가장기본적인정의와함수원형을포함하고있습니다. WDM드라이버를작성시에는 ntddk.h대신에 wdm.h를사용하는데이건두개드라이버의동작방식차이때문입니다. 이에대한설명은다음으로미루도록하겠습니다. PDEVICE_OBJECT DeviceObject = NULL; DeviceObject라는전역변수를선언하고그값으로 null을주었습니다. 이변수의데이터형은 PDEVICE_OBJECT인데이것은 DEVICE_OBJECT라고하는 ntddk.h에정의된구조체의포인터형입니다. 이것에대한자세한설명은잠시뒤에하기로하고일단디바이스오브젝트포인터변수를하나선언했으며그값을 null로초기화했다고만알아두도록합니다. 참고로여기서는소스를간단하게하기위해전역변수를사용했지만사실디바이스드라이버를만들때는가급적전역변수의사용을피해야합니다. 그이유는드라이버는여러가지동시적인작업들이많이이루어지는데전역변수를사용하게되면그변수값을사용할때잘못된값을참조할위험이있기때문입니다. NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath); VOID Hello_DriverUnload(IN PDRIVER_OBJECT DriverObject); 이소스에서는두개의함수가정의되어있는데바로그함수들의원형을선언하고있습니다. 우선 DriverEntry() 함수에대해서먼저알아보도록하겠습니다. 이함수는간단히말하자면 C에서의 main() 혹은윈도우프로그래밍에서의 WinMain() 함수와비슷한역할을하는함수입니다. 즉, 프로그램이로딩되었을때가장먼저시작되는함수입니다. 이러한것을진입점 (Entry Point) 라고합니다. 이함수가그러한역할을담당할수있는것은바로 VC++ 프로젝트셋팅에서 Link Tab의 Entry-point symbol을그렇게지정해주었기때문입니다. 이것은다시말하면 Entry-point symbol을다른것으로정의해주면진입점함수를다른이름으로할수있다는뜻입니다. ( 즉, Entry-point symbol을 Hello_DriverEntry라고하면위의함수이름도 Hello_DriverEntry라고바뀌어야합니다.) 하지만다른프로그래밍시진입함수를표준처럼 ( 혹은표준으로 ) 정의해놓고사용하듯이드라이버프로그래밍시에도가급적이함수의이름은바꾸지않는것이좋습니다. ( 참고로 DDK를이용한콘솔모드빌딩시에도 DriverEntry() 가디폴트로정의되어있습니다.) 이함수에는두개의파라미터가넘어오는데각각 PDRIVER_OBJECT와 PUNICODE_STRING형의변수입니다. 이파라미터들을설명하기전에우선어떻게해서드라이버가로드되는지그과정을간략하게살펴보면,
다음과같습니다. 1. 컴퓨터가부팅을하게되면윈도우는컴퓨터가동에필요한가장기본적인요소들을메모리에상주시킨다. (hal.dll, ntoskrnl.dll, kernel32.dll 등등 ) 2. 기본적인요소가가동하고나면현재등록되어있는드라이버들을차례로로딩시키거나서비스컨트롤매니저라고부르는관리프로그램에등록시킨다. 이러한드라이버들에대한정보는레지스트리에서가져오게된다. 즉, 윈도우는레지스트리의 HKLM SYSTEM CurrentControlSet Services에있는목록들을차례로살펴보고해당키에정의된값들 ( 우리드라이버의경우 Start, Type, ErrorControl값 ) 을토대로이드라이버를당장로딩할지혹은나중으로미룰지, 어떻게관리할지등등을정하게된다. 참고로드라이버의로딩시기를결정하는것은 Start값이며 0~4의값을가지고있다. 0은커널로더에의해, 1은 I/O system에의해부팅시로딩되며 2는 SCM(Service Control Manager) 에의해, 그리고우리가정한 3은사용자에의해수동으로로딩시킨다는것을의미한다. 4 값은작동시키지않는다는것을의미한다.(disable) 3. 우리드라이버의경우위에서언급했듯이 3값을가지고있으므로윈도우가드라이버를로딩시키지는않지만일단관리프로그램에해당드라이버정보 - Start값, 드라이버경로명등 - 을기억해놓고있게된다. 따라서우리가컴퓨터관리도구를통해드라이버정보를보았을때우리의드라이버를볼수있는것이다. 4. 우리가관리도구를통해드라이버를수동으로동작시키게되면윈도우는드라이버오브젝트를생성한다. 이것은그리심오한의미가있는것은아니고단지미리정의된 DRIVER_OBJECT라고하는구조체의크기만큼을메모리에할당하고기본적인값을채워넣는것을말한다. 어쨌든윈도우는드라이버오브젝트를생성한후해당드라이버파일 ( 여기서는 Hello_drv.sys) 의진입함수인 DriverEntry() 함수를호출하면서파라미터로그오브젝트의포인터값과해당드라이버의레지스트리경로 (REGISTRY MACHINE SYSTEM ControlSet001 Services 서비스이름 ) 명을넘겨주게된다. 이제드라이버는로딩을시작하게된다. DRIVER_OBJECT 는 ntddk.h 와 wdm.h 에서다음과같이정의되어있습니다. typedef struct _DRIVER_OBJECT CSHORT Type; CSHORT Size; PDEVICE_OBJECT DeviceObject; ULONG Flags; PVOID DriverStart; ULONG DriverSize; PVOID DriverSection; PDRIVER_EXTENSION DriverExtension; UNICODE_STRING DriverName; PUNICODE_STRING HardwareDatabase;
PFAST_IO_DISPATCH FastIoDispatch; PDRIVER_INITIALIZE DriverInit; PDRIVER_STARTIO DriverStartIo; PDRIVER_UNLOAD DriverUnload; PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]; } DRIVER_OBJECT; typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT; 우선알아두어야할사항은드라이버프로그래밍시에사용되는많은구조체에속한멤버들이모두사용자에의한의도적변경이가능한것은아니라는사실입니다. 이러한제약은디바이스드라이버가커널모드메모리에서동작하기때문에생긴것으로써잘못된데이터수정은시스템다운혹은심한경우커널의심각한손실을입힐수있습니다. DRIVER_OBJECT역시마찬가지이며이구조체에서는 Type, Size, Flags, DriverStart, DriverSize, DriverSection, DriverName이직접적인참조 / 변경을금하는멤버들입니다. 이러한변수들은윈도우에의해서생성될당시이미초기화가되거나혹은다른함수나매크로에의해서참조 / 변경이가능한것들입니다.( 물론그렇다하더라도그행위자체가불가능한것은아닙니다.) 어쨋거나 DRIVER_OBJECT는앞서언급한바와같이드라이버가동작하는데필요한기본적인정보들을저장하는데사용되는구조체입니다. 여기에는드라이버가특정상황에서동작하는데필요한함수의포인터나드라이버가컨트롤하게되는디바이스의오브젝트포인터등이포함되어있습니다. 자세한사항은앞으로차근차근설명해나가도록하겠습니다. UNICODE_STRING의구조는다음과같으며 ntdef.h에정의되어있습니다. typedef struct _UNICODE_STRING USHORT Length; // Buffer에저장된문자열의길이 USHORT MaximumLength; // Buffer에저장될수있는최대문자열의길이 PWSTR Buffer; // 문자열값이저장되는버퍼의포인터 } UNICODE_STRING; typedef UNICODE_STRING *PUNICODE_STRING; 드라이버는기본적으로스트링을유니코드방식으로처리합니다. 즉, 일반적인윈도우 Application프로그램이 1 caracter = 1 byte로처리하는것에비해디바이스드라이버에서는 1 character = 2byte로처리한다. 그이유는커널모드에서문자값을 ASCII나 ANSI가아닌 Unicode로처리하기때문입니다. 그리고디바이스드라이버에서는그러한유니코드데이터를다시 UNICODE_STRING이라고하는구조체를통해처리합니다. 즉, 대부분의문자열처리관련함수들은이구조체를파라미터로사용하게됩니다. UNICODE_STRING은정의에서볼수있듯이데이터버퍼외에데이터의사이즈와버퍼에들어갈수있는최대사이즈를정의해놓기때문에문자열처리에있어서버퍼오버플로우등의에러발생을사전에방지해줄수있습니다. 앞에서언급했듯이윈도우는드라이버오브젝트를생성한후해당드라이버를로딩할때드라이버오브젝트
의포인터와함께레지스트리경로명을파라미터로넘기게됩니다. 그리고이때이경로명은유니코드형식으로 UNICODE_STRING구조체에저장되어넘겨지는것입니다. 예를들어드라이버의이름이 Hello_drv라고한다면레지스트리경로명은 REGISTRY MACHINE SYSTEM ControlSet001 Services Hello_drv 이므로 DriverEntry() 함수의두번째파라미터인 PUNICODE_STRING RegistryPath 구조체변수에는다음과같은값들이들어갈것입니다.( 이값은필자의컴퓨터에서디버깅을통해살펴본실제데이터값입니다.) RegistryPath->Length = 0x72(114) RegistryPath->MaximumLength = 0x72(114) RegistryPath->Buffer = 0x858d4008 ( REGISTRY MACHINE SYSTEM ControlSet001 Services Hello_drv) DriverEntry() 함수의리턴형은 NTSTATUS 이며이것은 Ntdef.h 를보면아래와같이정의되어있습니다. typedef LONG NTSTATUS; 즉, 단지 LONG형을사용자의가독성을고려해재정의해놓은것을알수있습니다. 다만 NTSTATUS의특이한점은그리턴값으로미리정의된상태코드를사용한다는점에있으며 DDK에정의된모든 - NTSTATUS를리턴형으로사용하는 - 함수들은그정의에따라특정상태를알려주게되어있습니다. 이상태코드의형식은 Walter Oney의책 Programming the Microsoft Windows Driver Model 에자세히나와있습니다. 하지만이러한상태코드의값을모두알필요는없으며 DDK에는상태코드의특정값들을확인할수있는몇가지매크로를정의해놓고있습니다. 이것에관해서는뒷부분에서설명하겠습니다. 이제 DriverEntry() 함수에서사용하는파라미터와리턴값의설명을마치고윈도우에의해 DriverEntry() 가호출된후이루어지는작업들에대해설명하도록하겠습니다. 앞서언급했듯이윈도우는 DriverEntry() 를호출하기전에드라이버의오브젝트를생성합니다. 여기에는몇가지값들은미리채워져있지만상당수가개발자의몫으로남겨진채비워져있습니다. 그리고바로 DriverEntry() 함수에서하는역할이바로이러한기본정보를설정하는것입니다. 우선필자의드라이버가처음로딩될때넘어온드라이버오브젝트의내용을살펴보면아래와같습니다. 1. Type = 0x4 2. Size = 0xA8 3. DeviceObject = 0x00000000 4. Flags = 0x2 5. DriverStart = 0xF7B7B0000 6. DriverSize = 0x6000 7. DriverSection = 0x85BB7C68 8. DriverExtension = 0x858FF680
A. DriverObject = 0x858FF5D8 B. Addevice = 0x00000000 C. count = 0x0 D. ServiceKeyName i. Length = 0x12 ii. MaximumLength = 0x14 iii. Buffer = 0x85C5C118( Hello_drv ) 9. DriverName A. Length = 0x22 B. MaximumLength = 0x22 C. Buffer = 0xE14D8B08( Driver Hello_drv ) 10. HardwareDatabase(0x806488B4) A. Length = 0x5A B. MaximumLength = 0x5C C. Buffer = 0x806163B8( REGISTRY MACHINE HARDWARE DESCRIPTION SYSTEM ) 11. FastIoDispatch = 0x00000000 12. DriverInit = 0xF7B7C000(DriverEntry) 13. DriverStartIo = 0x00000000 14. DriverUnload = 0x00000000 15. MajorFunction[28] 위에서볼수있듯이드라이버오브젝트는우리의드라이버를인식하고동작하는데필요한기본정보들을포함하고있습니다. 여기서 8-B와 11~15번까지는드라이버가동작하는데필요한함수를정의하는포인터변수들이며나머지는드라이버동작을보조하는정보들을저장하는구조체및데이터변수들입니다. 현재우리드라이버는가장기본적인동작인드라이버로딩 / 언로딩동작만을하도록설계되어있으며따라서이드라이버의동작에필요한함수정의는 12번 DriverInit과 14번 DriverUnload 멤버에해당동작을정의한함수포인터가들어가게됩니다. DriverInit은윈도우에의해이미설정되었으며우리는 DriverUnload만을설정해주면됩니다. DriverObject->DriverUnload = Hello_DriverUnload; 바로위구문이이것을수행합니다. 이구문을수행하고나면위의 14. DriverUnload변수는우리가미리정의한 Hello_DriverUnload() 함수의포인터값으로채워지게될것입니다. 사실이정도까지만수행해도드라이버는이상없이로딩이되고언로딩을수행할수있습니다. 그러나디바이스드라이버의개발목적은특정디바이스에결합되어해당디바이스를컨트롤하는데있는만큼우리드라이버역시디바이스의생성 / 삭제의기능을수행토록만들었습니다. 물론이디바이스는실제물리적인형태를지닌디바이스가아니며단지소프
트웨어적으로생성된가상디바이스입니다. 디바이스의생성은아래의구문을통해이루어집니다. RtlInitUnicodeString ( &DeviceName, L" Device HELLODRV" ); status = IoCreateDevice ( DriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject ); IoCreateDevice() 함수에대한자세한설명은 DDK에잘나와있으므로피하도록하고, 몇가지만언급하자면, 1. 이함수는 이름에서볼수있듯이 - 특정드라이버에결합될디바이스를생성하는역할을합니다. 그리고이디바이스와드라이버를연관지어주는역할을하는것은 I/O manager이다. I/O manager 가무엇이고어떤역할을하는지는다음에다시설명하겠습니다. 2. 세번째파라미터로넘어가는것이생성될디바이스의이름인데이것은옵션값이므로 NULL로줘도상관없습니다. 만약이값을주게되면사용자모드에서의컨트롤이쉬워지는반면에보안에취약한점이있습니다. 3. 마지막파라미터가바로생성된디바이스오브젝트의포인터를받게되는변수입니다. 이파라미터의데이터형은 PDEVICE_OBJECT이며바로밑에서이것에대해언급하도록하겠습니다. 4. 나머지파라미터들은해당디바이스의특성및타입에관련된정보들을제공하는것들입니다. 이것들역시다음에관련된사항이발생할시에자세히설명하도록하겠습니다. DEVICE_OBJECT 는 ntddk.h 에다음과같이정의되어있습니다. typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _DEVICE_OBJECT CSHORT Type; USHORT Size; LONG ReferenceCount; struct _DRIVER_OBJECT *DriverObject; struct _DEVICE_OBJECT *NextDevice; struct _DEVICE_OBJECT *AttachedDevice;
struct _IRP *CurrentIrp; PIO_TIMER Timer; ULONG Flags; ULONG Characteristics; PVOID DoNotUse1; PVOID DeviceExtension; DEVICE_TYPE DeviceType; CCHAR StackSize; union LIST_ENTRY ListEntry; WAIT_CONTEXT_BLOCK Wcb; } Queue; ULONG AlignmentRequirement; KDEVICE_QUEUE DeviceQueue; KDPC Dpc; ULONG ActiveThreadCount; PSECURITY_DESCRIPTOR SecurityDescriptor; KEVENT DeviceLock; USHORT SectorSize; USHORT Spare1; struct _DEVOBJ_EXTENSION *DeviceObjectExtension; PVOID Reserved; } DEVICE_OBJECT; typedef struct _DEVICE_OBJECT *PDEVICE_OBJECT; 모든내용을다설명하기에는무리가있고현재알아야할필요가있는멤버들은아래와같습니다. 1. DriverObject : 해당디바이스와연결된드라이버에대한참조포인터입니다. 참고로드라이버오브젝트역시디바이스오브젝트를참조하는포인터변수가멤버에포함되어있으며따라서상호참조가가능합니다. 2. NextDevice : 하나의드라이버는여러개의디바이스와생성 / 결합이가능합니다. 이는하나의시리얼드라이버가여러개의 COM포트를가질수있다는사실을생각해보면쉽게이해가될것입니다. 이
멤버는드라이버에연결된각각의디바이스들을차례로참조할수있는매개체역할을합니다. (Linked list에서의 next포인터와유사하다.) 3. Flags : 디바이스의특성및상태를나타내는변수입니다. 플래그비트의집합이며현재의미있는값은 DO_DEVICE_INITIALIZING(0x00000080) 입니다. 이것은 DriverEntry() 에서디바이스를생성할때윈도우가자동으로셋팅하는값이며정의된이름에서알수있듯이해당디바이스의초기화가아직완료되지않았음을의미합니다. 이값이셋팅되어있으면 I/O manager는해당디바이스로들어오는여러가지요청이나이벤트들을모두무시하며초기화동작의안전한완료를보장합니다. 이비트는 DriverEntry() 함수가완료되는순간역시윈도우에의해다시 0으로셋팅됩니다. 따라서 DriverEntry() 함수에서이비트값을사용자가건드릴필요는없습니다. 하지만이플래그비트를개발자가직접셋팅해줘야할상황이 WDM드라이버의경우발생하게되는데이것은다음기회에다시언급하도록하겠습니다. 그외에나머지멤버들도다른예제에서필요에따라설명하기로하겠습니다.( 한꺼번에너무많은지식이머리속에들어오면만성두통의야기합니다.) 만약더자세한사항을알고싶으면 Walter Oney의 Programming the Microsoft Windows Driver Model 의 Chapter 2를보기바랍니다. 우선은이디바이스오브젝트가드라이버에서실질적인작업을하는데있어중요한매개체역할을하며하나의드라이버오브젝트는여러개의디바이스오브젝트를가질수있다는정도만기억해두기바랍니다. 이것은마치윈도우프로그램에서하나의클래스오브젝트가여러개의윈도우를가질수있으며각각의윈도우를제어하기위해윈도우핸들변수를사용하는것과유사합니다. 어쨌든 IoCreateDevice() 함수가실행되면디바이스가생성되고그디바이스의정보는드라이버오브젝트의 DeviceObject멤버에저장됩니다. if (!NT_SUCCESS (status)) return status; } 위의구문은 IoCreateDevice() 함수가제대로동작했는지를확인하는구문입니다. 여기서앞에서잠깐언급한 NTSTATUS상태값을확인하는몇가지매크로중하나가사용되었습니다. NT_SUCCESS(status), 는 ntdef.h 에다음과같이정의되어있습니다. #define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0) 즉, 이매크로는해당 status 값이 0 이거나 0 보다크면 TRUE 를, 0 보다작은값이면 FALSE 값을리턴합니다.
따라서위의구문의의미는 status가 0보다작은값이면해당상태값을가지고리턴하고종료하라는뜻입니다. 여기서한가지짐작할수있는것은 NTSTATUS값이양수일경우는해당함수동작이정상적으로이루어졌다는뜻이며, 음수일경우는무엇인가문제가발생했다는것을의미한다는사실입니다. 우선은이런정도만을알고있어도충분하며자세한사항은 Walter Oney의책 Chapter3, 혹은 ntdef.h에서 NTSTATUS를정의한부분을보기바랍니다. KdPrint(("Hello Driver World!!!")); 위의구문은다음과같이정의된매크로입니다. #if DBG #define KdPrint(_x_) DbgPrint _x_ #else #define KdPrint(_x_) 즉, 해당드라이버가디버그모드 ( 혹은 checked build) 로컴파일되었을경우에해당문장을디버그화면에출력하는기능을합니다.(DbgPrint() 가그역할을하는 API이다.) 우리가앞에서 DebugView라는프로그램을통해서확인해본문장은바로이구문을통해발생한것입니다. 이문장은꼭 DebugView를통해서만볼수있는것은아니며 Numega의 softice나마이크로소프트의 WinDbg와같은디버깅툴을통해서도볼수있습니다. return status; 이제모든동작이정상적으로이루어졌다면 status 값은초기에정의한 STATUS_SUCCESS를그대로유지하고있을것이며 DriverEntry() 는이값을리턴하면서종료하게됩니다. 이리턴값은윈도우에게넘겨지게되는데윈도우에서는이값을살펴보고 STATUS_SUCCESS이면성공적으로로딩되었다고판단하고메모리에해당드라이버오브젝트를계속상주시키며특정한이벤트 ( 여기서는드라이버종료 ) 가발생할때까지대기상태로두게됩니다. 만약이값이 STATUS_SUCCESS가아닌에러상태를나타내면, 윈도우는상태값을분석해서적절한에러메시지를출력하고 혹은아무런경고없이 드라이버오브젝트를메모리에서삭제합니다.( 우리드라이버에서는만약 IoCreateDevice() 함수가실패했을때이러한일이발생하게됩니다.) 여기서주의할점은위의상황과같은경우에드라이버가언로드될때는우리가정의한 Hello_ DriverUnload() 함수에의해언로드가수행되지않는다는사실입니다. Hello_DriverUnload() 함수는 DriverEntry() 가 STATUS_SUCCESS를리턴하여윈도우에서드라이버를정상적으로로드시킨상황에서언로드이벤트가발생하였을때수행되며드라이버의로딩이정상적으로완료되지않았을때는드라이버오브젝트에언로드함수를등록하였다하더라도윈도우는해당함수를호출하지않고단지할당된드라이버오브젝트를삭제하고드라이버의동작을멈추게합니다.
예를들어위의 DriverEntry() 함수를아래와같이고쳐보도록합니다. NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) NTSTATUS status = STATUS_SUCCESS; UNICODE_STRING DeviceName; DriverObject->DriverUnload = Hello_DriverUnload; Return STATUS_DLL_NOT_FOUND; // - 이구문을삽입한다. RtlInitUnicodeString ( &DeviceName, L" Device HELLODRV" ); status = IoCreateDevice ( DriverObject, 0, &DeviceName, 위의고친소스를빌드한후컴퓨터관리자에서다시수동으로드라이버를로딩하면아래그림과같은에러메시지와함께드라이버시작이되지않음을알수있습니다.( 그림 1_12) 또한드라이버언로드함수에서출력되는디버그메시지 (Hello Driver Unload bye~) 도볼수없습니다. 한편위와같은상황이발생하게되면레지스트리에있는우리드라이버키에는다음과같이드라이버초기화가실패했다는정보가포함됩니다.( 그림 1_13 참조 ) 이로써드라이버로딩에관련된가장기초적인부분에대한설명을마쳤습니다.
< 그림 1_12> < 그림 1_13>
드라이버언로드과정은대개의컴퓨터작업이그러하듯로딩과정의역순으로진행된다. 우리드라이버에서는로딩과정에서디바이스의생성외에해준작업이없으므로언로드시생성된디바이스를삭제해주기만하면됩니다. VOID Hello_DriverUnload(IN PDRIVER_OBJECT DriverObject) if(deviceobject) IoDeleteDevice(DeviceObject); DeviceObject = NULL; } KdPrint(("Hello Driver Unload!!!")); } Hello_DriverUnload() 함수는파라미터로드라이버오브젝트를받으며 IoDeleteDevice() 함수를통해디바이스오브젝트를삭제하게됩니다. 여기서디바이스오브젝트가전역변수로선언되었기때문에바로참조해서 IoDeleteDevice() 함수의파라미터로사용했지만앞부분에서언급했듯이드라이버에서는전역변수의사용을최대한금 ( 禁 ) 해야합니다. 그리고드라이버오브젝트안에는디바이스오브젝트에대한포인터가멤버로들어있으므로위의소스는아래와같이수정이가능합니다. VOID Hello_DriverUnload(IN PDRIVER_OBJECT DriverObject) if(driverobject->deviceobject) IoDeleteDevice(DriverObject->DeviceObject); DriverObject->DeviceObject = NULL; } KdPrint(("Hello Driver Unload!!!")); } 이렇게하면디바이스오브젝트포인터변수를따로전역변수로사용하지않고 DriverEntry() 함수의지역변수로사용해도상관없으며이방법이더좋은방법입니다. 지금까지 Hello_drv 라고하는매우간단한디바이스드라이버를만들어보았습니다. 비록이것은디바이스드라이버라고부르기에는너무나미흡한토이프로그램 (toy program) 이지만드라이버의기본적인동작을살
펴보는데에는적절하지않았나생각이듭니다. 그러면앞으로우리의드라이버에기능을조금씩덧붙혀나가며드라이버에대한보다자세한이야기들을해나가기로하겠습니다. 글쓴이 : 이은조 (gimmesilver@gmail.com)