하제소프트 주식회사하제소프트 (www.hajesoft.co.kr) 강사이봉석
하제소프트 과정소개 윈도우응용프로그램, 윈도우서비스프로그램, 윈도우디바이스드라이버를개발하는개발자들로하여금고급디버깅기술을제공하는 윈도우디버거 (WinDBG) 사용방법을익히게하여, 고급시스템프로그래머를양성하는데있습니다 윈도우디버거 (WinDBG) 를사용하는개발자는실무에서고급시스템프로그래머가갖추어야할중요한디버깅지식을습득함과동시에시간과비용을최대한아끼는프로그래밍습관과우수한결과물을만들어낼수있습니다
하제소프트 강사약력 본강사 ( 이봉석 ) 는기업교육 ( 삼성첨단기술센터, LG러닝센터, MDS테크놀러지 ) 과사설교육등에서 13년강사이력을가지고있으며, 현재주식회사하제소프트대표이사를역임하고있습니다. 저서로는, 윈도우디바이스드라이버, 실전윈도우디바이스드라이버, 고급개발자들만이알고있던디바이스드라이버구조와원리, 그리고제작노하우, 실전윈도우 CE 가이드, USB 너뭐니 가있습니다. 주식회사하제소프트는 17년간업체들이필요로하는윈도우디바이스드라이버, 리눅스디바이스드라이버그리고훰웨어를개발, 제공하는업체입니다.
하제소프트 과정진행목차 WinDBG 환경준비 Visual Studio 2015, WDK(Windows Driver Kit), WinDBG WinDBG 심볼설정 마이크로프로세서 Calling 규약 x86 CPU 에서 C 함수호출과어셈블리어연관성분석 x64 CPU 에서 C 함수호출과어셈블리어연관성분석 WinDBG 로칼디버깅 응용프로그램, 서비스프로그램디버깅 BSOD(BlueScreen Of Death) 커널덤프파일 커널덤프파일생성설정및덤프분석 WinDBG 원격디버깅 서비스프로그램, 커널디바이스드라이버디버깅 하드웨어와관련된 WinDBG 주요명령어 PCI, USB 버스를사용하는하드웨어를다룰때, 유용한 WinDBG 확장명령어를소개합니다 WinDBG 확장모듈 확장기능을제공하는모듈구현
5 강 WinDBG 원격디버깅 하제소프트 WinDBG 가설치된 PC 입니다 1394, USB, 시리얼, 이더넷을사용해서연결됩니다 디버깅할대상의 PC 입니다 Serial Debugger PC Ethernet Target PC(Debuggee PC) Ethernet 연결은윈도우 8 부터지원됩니다 Debugger PC 는윈도우드라이버를통해서통신합니다 Target PC 는윈도우드라이버를사용하지않고커널이직접하드웨어를접근합니다. OnBoard 하드웨어
2.6.2 샘플커널드라이버 하제소프트 소스코드를이해하기어려울수있습니다. 이때는핵심적인내용만파악하고, 제공하는샘플을사용하세요 간단한필터드라이버를작성해서디바이스스택의운용방식을이해하고, WinDBG 원격디버깅의기술을배웁니다 WinDBG 원격디버깅을재대로하기위해서는반드시샘플커널드라이버를디버깅할줄알아야합니다
하제소프트 2.6.2.1 프로젝트생성 비쥬얼스튜디오 2015 를통해서 Legacy WDM 드 라이버프로젝트를 생성합니다
하제소프트 2.6.2.2 프로젝트세팅 최소지원운영체제를 Win 7 으로지정합니다 본예재는설치파일이 필요하지않습니다 툴 (Tool) 의버그로인해, 로칼시간을사용하도록설정합니다
하제소프트 2.6.2.3 소스추가 새로운소스파일을프 로젝트에추가합니다 (FilterExam.cpp)
드라이버가메모리에적재될때호출됩니다 하제소프트 2.6.2.4 소스작성 1(DriverEntry) extern "C" NTSTATUS DriverEntry( PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath ) { NTSTATUS status = STATUS_SUCCESS; ULONG ulindex; PDRIVER_DISPATCH * dispatch; UNREFERENCED_PARAMETER(RegistryPath); for (ulindex = 0, dispatch = DriverObject->MajorFunction; ulindex <= IRP_MJ_MAXIMUM_FUNCTION; ulindex++, dispatch++) { *dispatch = FilterPass; DriverObject->MajorFunction[IRP_MJ_PNP] = FilterDispatchPnp; DriverObject->MajorFunction[IRP_MJ_POWER] = FilterDispatchPower; DriverObject->DriverExtension->AddDevice = FilterAddDevice; DriverObject->DriverUnload = FilterUnload; return status; IRP_MJ_PNP, IRP_MJ_POWER 명령어를제외한나머지명령을처리하는처리기를 FilterPass 함수로등록합니다
하제소프트 2.6.2.4 소스작성 2(DriverEntry) extern "C" NTSTATUS DriverEntry( PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath ) { NTSTATUS status = STATUS_SUCCESS; ULONG ulindex; PDRIVER_DISPATCH * dispatch; UNREFERENCED_PARAMETER(RegistryPath); for (ulindex = 0, dispatch = DriverObject->MajorFunction; ulindex <= IRP_MJ_MAXIMUM_FUNCTION; ulindex++, dispatch++) { *dispatch = FilterPass; DriverObject->MajorFunction[IRP_MJ_PNP] = FilterDispatchPnp; DriverObject->MajorFunction[IRP_MJ_POWER] = FilterDispatchPower; DriverObject->DriverExtension->AddDevice = FilterAddDevice; DriverObject->DriverUnload = FilterUnload; return status; 필터드라이버가처리할부수적인콜백함수들을등록합니다
대부분의명령어를처리하는명령처리기입니다 하제소프트 2.6.2.4 소스작성 3(FilterPass) NTSTATUS FilterPass( PDEVICE_OBJECT DeviceObject, PIRP Irp ) { PDEVICE_EXTENSION deviceextension; NTSTATUS status; PIO_STACK_LOCATION irpstack; 대부분의명령어를 FilterPass 함수에서처리합니다. 여기서는명령을구체적으로구분해봅니다 deviceextension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension; irpstack = IoGetCurrentIrpStackLocation(Irp); switch (irpstack->minorfunction) { case IRP_MJ_CREATE: DbgPrint("[FilterExam] IRP_MJ_CREATE\n"); case IRP_MJ_CLOSE: DbgPrint("[FilterExam] IRP_MJ_CLOSE\n"); case IRP_MJ_READ: DbgPrint("[FilterExam] IRP_MJ_READ\n");. IoSkipCurrentIrpStackLocation(Irp); status = IoCallDriver(deviceExtension->NextLowerDriver, Irp); return status; break; break; break;
하제소프트 2.6.2.4 소스작성 4(FilterPass) NTSTATUS FilterPass( PDEVICE_OBJECT DeviceObject, PIRP Irp ) { PDEVICE_EXTENSION deviceextension; NTSTATUS status; PIO_STACK_LOCATION irpstack; 필터드라이버는대부분의명령을다음레벨의드라이버에게넘김니다 deviceextension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension; irpstack = IoGetCurrentIrpStackLocation(Irp); switch (irpstack->minorfunction) { case IRP_MJ_CREATE: DbgPrint("[FilterExam] IRP_MJ_CREATE\n"); break; case IRP_MJ_CLOSE: DbgPrint("[FilterExam] IRP_MJ_CLOSE\n"); case IRP_MJ_READ: DbgPrint("[FilterExam] IRP_MJ_READ\n");. IoSkipCurrentIrpStackLocation(Irp); status = IoCallDriver(deviceExtension->NextLowerDriver, Irp); return status; break; break;
드라이버가메모리에서해제될때호출되는콜백함수입니다 하제소프트 2.6.2.4 소스작성 5(FilterUnload) VOID FilterUnload( PDRIVER_OBJECT DriverObject ) { UNREFERENCED_PARAMETER(DriverObject); return; IRP_MJ_POWER 전원명령을처리하는명령처리기입니다 2.6.2.4 소스작성 6(FilterDispatchPower) NTSTATUS FilterDispatchPower( PDEVICE_OBJECT DeviceObject, PIRP Irp ) { PDEVICE_EXTENSION deviceextension; NTSTATUS status; 필터드라이버는 IRP_MJ_POWER 명령을다음레벨의드라이버에게넘김니다 deviceextension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension; PoStartNextPowerIrp(Irp); IoSkipCurrentIrpStackLocation(Irp); status = PoCallDriver(deviceExtension->NextLowerDriver, Irp); return status;
디바이스스택을형성할때호출되는콜백함수입니다 2.6.2.4 소스작성 7(FilterAddDevice) 하제소프트 NTSTATUS FilterAddDevice( PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT PhysicalDeviceObject ) { NTSTATUS status = STATUS_SUCCESS; PDEVICE_OBJECT deviceobject = NULL; PDEVICE_EXTENSION deviceextension; status = IoCreateDevice(DriverObject, sizeof(device_extension), NULL, // No Name FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &deviceobject); if (!NT_SUCCESS(status)) { return status; 필터드라이버가관여할자신의 _DEVICE_OBJECT 를생성합니다
하제소프트 NTSTATUS FilterAddDevice( PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT PhysicalDeviceObject ) {.. deviceextension = (PDEVICE_EXTENSION)deviceObject->DeviceExtension; deviceextension->nextlowerdriver = IoAttachDeviceToDeviceStack( deviceobject, PhysicalDeviceObject); 2.6.2.4 소스작성 8(FilterAddDevice) if (NULL == deviceextension->nextlowerdriver) { IoDeleteDevice(deviceObject); return STATUS_UNSUCCESSFUL; deviceobject->flags = deviceextension->nextlowerdriver->flags & (DO_BUFFERED_IO DO_DIRECT_IO DO_POWER_PAGABLE); deviceobject->devicetype = deviceextension->nextlowerdriver->devicetype; deviceobject->characteristics = deviceextension->nextlowerdriver->characteristics; deviceobject->flags &= ~DO_DEVICE_INITIALIZING; return STATUS_SUCCESS; 디바이스스택의다음레벨의 _DEVICE_OBJECT 와연결하고주소를기억합니다
하제소프트 NTSTATUS FilterAddDevice( PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT PhysicalDeviceObject ) {.. deviceextension = (PDEVICE_EXTENSION)deviceObject->DeviceExtension; deviceextension->nextlowerdriver = IoAttachDeviceToDeviceStack( deviceobject, PhysicalDeviceObject); 2.6.2.4 소스작성 9(FilterAddDevice) if (NULL == deviceextension->nextlowerdriver) { IoDeleteDevice(deviceObject); return STATUS_UNSUCCESSFUL; deviceobject->flags = deviceextension->nextlowerdriver->flags & (DO_BUFFERED_IO DO_DIRECT_IO DO_POWER_PAGABLE); deviceobject->devicetype = deviceextension->nextlowerdriver->devicetype; deviceobject->characteristics = deviceextension->nextlowerdriver->characteristics; deviceobject->flags &= ~DO_DEVICE_INITIALIZING; return STATUS_SUCCESS; 디바이스스택의다음레벨의 _DEVICE_OBJECT 의속성을참고해서현재디바이스의속성을변경합니다
하제소프트 2.6.2.4 소스작성 10(FilterDispatchPnp) NTSTATUS FilterDispatchPnp( PDEVICE_OBJECT DeviceObject, PIRP Irp ) { PDEVICE_EXTENSION deviceextension; PIO_STACK_LOCATION irpstack; NTSTATUS status; deviceextension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension; irpstack = IoGetCurrentIrpStackLocation(Irp); switch (irpstack->minorfunction) { case IRP_MN_REMOVE_DEVICE: IoSkipCurrentIrpStackLocation(Irp); status = IoCallDriver(deviceExtension->NextLowerDriver, Irp); IoDetachDevice(deviceExtension->NextLowerDriver); IoDeleteDevice(DeviceObject); return status; IRP_MJ_PNP 플러그엔플레이명령을처리하는명령처리기입니다 return status; default: IoSkipCurrentIrpStackLocation(Irp); 디바이스스택의다음계층으로현재명령을전달합니다 status = IoCallDriver(deviceExtension->NextLowerDriver, Irp); break;
하제소프트 2.6.2.4 소스작성 11(FilterDispatchPnp) NTSTATUS FilterDispatchPnp( PDEVICE_OBJECT DeviceObject, PIRP Irp ) { PDEVICE_EXTENSION deviceextension; PIO_STACK_LOCATION irpstack; NTSTATUS status; deviceextension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension; irpstack = IoGetCurrentIrpStackLocation(Irp); switch (irpstack->minorfunction) { case IRP_MN_REMOVE_DEVICE: IoSkipCurrentIrpStackLocation(Irp); status = IoCallDriver(deviceExtension->NextLowerDriver, Irp); IoDetachDevice(deviceExtension->NextLowerDriver); IoDeleteDevice(DeviceObject); return status; default: IoSkipCurrentIrpStackLocation(Irp); 디바이스스택으로부터 Filter 자신의 status = IoCallDriver(deviceExtension->NextLowerDriver, Irp); break; return status; _DEVICE_OBJECT 를제거하고있습니다
하제소프트 2.6.2.4 소스작성 12(FilterDispatchPnp) NTSTATUS FilterDispatchPnp( PDEVICE_OBJECT DeviceObject, PIRP Irp ) { PDEVICE_EXTENSION deviceextension; PIO_STACK_LOCATION irpstack; NTSTATUS status; deviceextension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension; irpstack = IoGetCurrentIrpStackLocation(Irp); switch (irpstack->minorfunction) { case IRP_MN_REMOVE_DEVICE: IoSkipCurrentIrpStackLocation(Irp); status = IoCallDriver(deviceExtension->NextLowerDriver, Irp); IoDetachDevice(deviceExtension->NextLowerDriver); IoDeleteDevice(DeviceObject); return status; default: IoSkipCurrentIrpStackLocation(Irp); status = IoCallDriver(deviceExtension->NextLowerDriver, Irp); break; return status; 나머지플러그엔플레이명령들은전부디바이스스택의다음계층으로전달합니다
하제소프트 2.6.2.5 드라이버빌드 가상머쉰 (x86) 환경에맞도록드라이버를빌드해야합니다 빌드된드라이버바이너리파일 (SYS) 과심볼파일 (PDB) 의위치를확인합니다 생성된드라이버바이너리 (SYS) 생성된드라이버바이너리심볼 (PDB)
하제소프트 2.6.2.6 드라이버설치 설치파일을사용하지않는방식으로필터드라이버를설치 하려합니다 다음의순서대로드라이버를설치합니다 1 서비스를생성합니다 ( 드라이버파일위치를지정합니다 ) 2 필터드라이버로동작할디바이스스택을결정합니다 3 시스템레지스트리를수정해서, 디바이스스택상의상위또는하위필터로설치합니다
하제소프트 2.6.2.6.1 서비스생성 서비스를생성하는방법은몇가지가있습니다 Win32 API CreateService() 함수를사용하는방법 System Registry 를수정하고필요하다면재부팅하는방법 SC Utility (Service Control) 를사용하는방법 관리자권한으로실행합니다 서비스생성 SC create. 서비스삭제 SC delete [ 서비스이름 ] 서비스실행 SC start [ 서비스이름 ] 서비스종료 SC stop [ 서비스이름 ]
SC 유틸리티옵션을지정할때, 반드시옵션과 = 를붙여야합니다. 옵션의값은띄어야합니다 하제소프트 2.6.2.6.1 서비스생성 서비스로등록하려는드라이버의위치를확인합니다 ( 가상머쉰을사용하는경우, 가상머쉰내의적당한위치로복사해야합니다 ) SC 유틸리티를사용해서서비스를생성합니다. 서비스의이름은 FilterExam 으로정하겠 습니다
반드시서비스가지정된위치에드라이버파일이있어야합니다. 유의하세요 ^^ 하제소프트 2.6.2.6.2 서비스확인 서비스가성공적으로만들어져있는지확인합니다 (SC query [ 서비스이름 ]) 서비스바이너리를위한심볼 (PDB) 파일을호스트 PC 의심볼폴더로복사합니다 (WinDBG 디버깅을하기위한준비입니다 ) 아직드라이버가동작하지는않습니다. 언제든지동작할준비만되어있는상태입니다
마우스디바이스 ( 스택 ) 로부터데이터가응용프로그램으로전달되는과정을필터링하겠습니다 하제소프트 2.6.2.6.3 디바이스스택선정 필터드라이버 (FilterExam) 가동작할디바이스스택을설정해야합니다 잘못된오동작으로인해서시스템이부팅되지않는상황을피하기위해서마우스드라이버가동작하는디바이스스택을정해서장치를위한상위필터로필터드라이버가동작하도록하겠습니다 ( 혹시라도잘못된다해도키보드는동작하기때문입니다 ) 상위필터로동작하기위해서는클래스상위필터방식과장치상위필터방식이있습니다. 쉽게접근할수있는방식은클래스상위필터방식이기때문에, 여기서는클래스상위필터방식으로필터드라이버 (FilterExam) 를설치해보겠습니다 USB 마우스 PS/2 마우스 USB 마우스 PS/2 마우스 Mouclass Upper Filter Mouclass Upper Filter 필터링 FilterExam Upper Filter FilterExam Upper Filter Mouhid Function i8042prt Function Mouclass Upper Filter Mouclass Upper Filter Mouhid Function i8042prt Function Hidusb Physical Acpi Physical Hidusb Physical Acpi Physical
클래스상위필터는하드웨어버스에상관없이 마우스 로동작하는모든디바이스스택에설치됩니다 하제소프트 2.6.2.6.4 필터설치 클래스상위필터를설치하기위해서시스템레지스트리편집기를통해서해당하는키를찾 아갑니다 (HKLM\SYSTEM\CurrentControlSet\Control\Class\[Class GUID]) Mouse 클래스 GUID 키 윈도우가제공하는클래스상위필터드라이버 (Mouclass) 가설치된모습을확인할수있습니다 모든클래스관련된부모키입니다 이곳에 FilterExam 필터를설치해야합니다
하드웨어트리에포함되기위해서는디바이스스택이해제되었다가다시결합되어야합니다. 이를위해서컴퓨터를재부팅하거나, 해당하는디바이스를 PC 에서제거했다가다시꼽아야합니다 하제소프트 2.6.2.6.4 필터설치 해당하는위치에이미설치된클래스상위필터드라이버 (Mouclass) 와함께예재필터드라이버 (FilterExam) 를설치합니다하드웨어트리는동적으로형성되므로, 이미형성된하드웨어트리의마우스를위한디바이스노드가다시형성될수있도록마우스장치를 PC에서제거했다가다시꼽거나, 아니면시스템을재부팅해야필터예재가동작합니다. 주의!!! 철자가틀리면절대로안됩니다 [MultiString Type] mouclass FilterExam
장치중지 이후, 사용기능 을이용하면, FilterExam 드라이버가동작을시작합니다 하제소프트 2.6.2.6.5 필터동작 컴퓨터를재부팅해도되지만, 편이상제어판을사용해서해당하는마우스의디바이스스택을해제 하고다시형성되도록하겠습니다 ( 장치사용기능과장치중지기능을사용합니다 ) 장치사용중지버튼을누르면, 마우스가동작하지않기때문에, 키보드를사용해서장치사용버튼을눌러야합니다 장치사용중지버튼을누른뒤, 키보드를사용해서다시사용가능버튼을누르세요 FilterExam 드라이버가포함된디바이스스택이동작하는것이확인됩니다
2.6.2.7 WinDBG 를통해서필터의 WinDBG 원격디버깅기능을사용해서필터드라이버 (FilterExam) 드라이버의동작을확인해 보도록합니다 동작을확인하기 하제소프트 심볼파일 (PDB) 을호스트 PC 의심볼경로에복사합니다 호스트 PC 에서 WinDBG 를실행시킨뒤, Ethernet 원격디버깅기능을시작합니다
2.6.2.7 WinDBG 를통해서필터의 동작을확인하기 Break 기능을사용해서제어를 WinDBG 프로그램으로가져옵니다 하제소프트 심볼재로딩명령을실행합니다 (.reload /a) Break Point 를설정하기위해필터드라이버의소스코드를엽니다 Break 기능 심볼재로딩명령
2.6.2.7 WinDBG 를통해서필터의 윈도우 ( 응용프로그램 ) 가마우스데이터 ( 좌표및버튼상태 ) 를요구하는위치에 Break Point 를 설정합니다 동작을확인하기 하제소프트 흐름을재개 (F5 키 ) 시키고마우스를움직이면 Break Point 설정에의해, 제어가다시 WinDBG 프로그램으로돌아옵니다 ( 이때가윈도우가마우스의데이터를읽기위해서드라이버를호출 하는시기입니다 ) 이곳으로제어가들어옵니다
2.6.2.8 필터드라이버동작흐름 하제소프트 윈도우 ( 응용프로그램 ) 가사용하는 Input System 은마우스, 터치패드, 키보드등의입력장치 로부터데이터를읽어와서윈도우가이것을적절한메시지로변환해서사용하게합니다 IRP_MJ_READ IRP_MJ_READ FilterPass() Input System FilterExam 수정된좌표, 버튼상태 그림과같은흐름상에서 FilterExam 드라이버가존재하기때문에, FilterExam 드라이버는 Mouclass 로부터올라오는실제마우스관련좌표정보, 버튼상태등을가장먼저알수있습니다. 따라 IRP_MJ_INTERNAL_ DEVICE_CONTROL Mouclass 좌표, 버튼상태 서필요하다면, 이정보를외부로몰래빼돌리거나수정할수도있지요^^ 다양한종류의마우스 Function(USB, PS/2)
2.6.2.9 마무리 ( 정상동작으로 ) 하제소프트 현재 FilterExam 드라이버가 Mouse를위한클래스의상위필터로설치되어있는상태입니다. 따라 서, 더이상사용하지않을때는반드시시스템레지스트리에해당하는부분을원래대로수정해야합니다. 물론, 수정이끝나면재부팅혹은 FilterExam 이포함된디바이스스택의디바이스노드의사용상태를 사용정지 시켰다가다시 사용 상태로전환해야합니다. 그렇지않으면항상 FilterExam 드라이버가사용됩니다. (HKLM\SYSTEM\CurrentControlSet\Control\Class\[Class GUID])
쉬어가는페이지 하제소프트 어느새우리는 WinDBG 를사용해서, 로칼디버깅과원격디버깅을하는기본적인사용방법을익혀버렸네요. 하지만, 무엇인가부족한것같다는생각이들죠. 지금시기에부족한것은실전적인경험이라고생각합니다. 특별한연습방법을하나소개하려고합니다. 제가 WinDBG 를처음만났을때, 가장재미있게느꼈던점은디바이스드라이버나서비스프로그램을개발하려는목적이아니라, 다만윈도우커널이제공하는 Public 심볼을사용해서커널을공부하는것이었습니다. 시중에서쉽게구할수있는커널과관련된책에서보여주는정보는이제더이상머리속에서만상상하고암기하는내용이아니라, 눈으로직접확인해보는정보가되는것입니다. 윈도우커널이제공하는여러가지형태의커널 API 함수들을오늘부터한가지씩정해서, Public 심볼, Break Point 설정그리고약간의 Disassembly 지식을가지고분석해보는것이어떨까요? 분명하게말씀드릴수있습니다. 이렇게분석하시다보면, 어떤장애가발생했을때이장애가발생한근본원인을누구보다빨리찾을수있습니다. 정말이에요 ^^
하제소프트 주식회사하제소프트 (www.hajesoft.co.kr) 강사이봉석