分享
 
 
 

Win32调试API 第一部分

王朝c/c++·作者佚名  2006-01-06
窄屏简体版  字體: |||超大  

在本教程中,我们将学习Win32提供给开发者的用于调试的原语. 在教程的结尾,我们将学习如何调试一个进程.

下载 例子程序.

理论:

Win32有一些供程序员使用的API,它们提供相当于调试器的功能. 他们被称作Win32调试API(或原语).利用这些API,我们可以:

加载一个程序或捆绑到一个正在运行的程序上以供调试

获得被调试的程序的低层信息,例如进程ID,进入地址,映像基址等.

当发生与调试有关的事件时被通知,例如进程/线程的开始/结束, DLL的加载/释放等.

修改被调试的进程或线程

简而言之,我们可以用这些API写一个简单的调试器.由于这个题目有些过大,我把它分为几部分,而本教程就是它的第一部分.在本教程中,我将讲解一些基本概念及Win32调试API的大致框架.

使用Win32调试API的步骤如下:

创建一个进程或捆绑到一个运行中的进程上. 这是使用Win32调试API的第一步.由于我们的程序要扮演调试器的角色,我们要找一个供调试的程序.一个被调试的程序被称为debuggee.可以通过以下两种方式获得debuggee:

通过CreateProcess创建debuggee进程.为了创建被调试的进程,必须指定DEBUG_PROCESS标志.这一标志告诉Windows我们要调试该进程. 当debuggee中发生重要的与调试有关的事件(调试事件)时,Windows 会向我们的程序发送通知.debuggee会立即挂起以等待我们的程序准备好.如果debuggee还创建了子进程,Windows还会为每个子进程中的调试事件向我们的程序发送通知.这一特性通常是不必要的.我们可以通过指定DEBUG_ONLY_THIS_PROCESS与 DEBUG_PROCESS的组合标志来禁止它.

我们也可以用 DebugActiveProcess标志捆绑到一个运行中的进程上.

等待调试事件. 在获得了一个debuggee进程后,debuggee的主线程被挂起,这种状况将持续到我们的程序调用WaitForDebugEvent为止.这个函数和其他的WaitForXXX函数相似,比如说,它阻塞调用线程直到等待的事件发生.对这个函数来说, 它等待由Windows发送的调试事件.下面是它的定义:

WaitForDebugEvent proto lpDebugEvent:DWORD, dwMilliseconds:DWORD

lpDebugEvent is the address of a DEBUG_EVENT这个结构将被填入关于debuggee中发生的调试事件的信息.

dwMilliseconds 该函数等待调试事件的时间,以毫秒为单位.如果这段时间没有调试事件发生, WaitForDebugEvent返回调用者.另一方面,如果将该参数指定为 INFINITE 常数,函数将一直等待直到调试事件发生.

现在我们看一下DEBUG_EVENT 结构.

DEBUG_EVENT STRUCT

dwDebugEventCode dd ?

dwProcessId dd ?

dwThreadId dd ?

u DEBUGSTRUCT <>

DEBUG_EVENT ENDS

dwDebugEventCode 该值指定了等待发生的调试事件的类型.因为有很多种类型的事件发生,我们的程序要检查该值,知道要发生事件的类型并做出响应. 该值可能的取值如下:

取值 含义

CREATE_PROCESS_DEBUG_EVENT 进程被创建.当debuggee进程刚被创建(还未运行) 或我们的程序刚以DebugActiveProcess被捆绑到一个运行中的进程时事件发生. 这是我们的程序应该获得的第一个事件.

EXIT_PROCESS_DEBUG_EVENT 进程退出.

CREATE_THEAD_DEBUG_EVENT 当一个新线程在deuggee进程中创建或我们的程序首次捆绑到运行中的进程时事件发生.要注意的是当debugge的主线程被创建时不会收到该通知.

EXIT_THREAD_DEBUG_EVENT debuggee中的线程退出时事件发生.debugee的主线程退出时不会收到该通知.我们可以认为debuggee的主线程与debugge进程是同义词. 因此, 当我们的程序看到CREATE_PROCESS_DEBUG_EVENT标志时,对主线程来说,就是CREATE_THREAD_DEBUG_EVENT标志.

LOAD_DLL_DEBUG_EVENT debuggee装入一个DLL.当PE装载器第一次分解指向DLL的链接时,我们将收到这一事件. (当调用CreateProcess装入 debuggee时)并且当debuggee调用LoadLibrary时也会发生.

UNLOAD_DLL_DEBUG_EVENT 一个DLL从debuggee中卸载时事件发生.

EXCEPTION_DEBUG_EVENT 在debuggee中发生异常时事件发生. 注意: 该事件仅在debuggee开始它的第一条指令之前发生一次.异常实际上是一个调试中断(int 3h).如果想恢复debuggee事,以 DBG_CONTINUE 标志调用ContinueDebugEvent 函数. 不要使用DBG_EXCEPTION_NOT_HANDLED 标志否则debuggee会在NT下拒绝运行(Win98下运行得很好).

OUTPUT_DEBUG_STRING_EVENT 当debuggee调用DebugOutputString函数向我们的程序发送消息字符串时该事件发生.

RIP_EVENT 系统调试发生错误

dwProcessId 和dwThreadId发生调试事件的进程和线程Id.我们可以用这些值作为我们感兴趣的进程或线程的标志符.记住如果我们使用CreateProcess来装载debuggee,我们仍可在PROCESS_INFO结构中获得debuggee的进程和线程.我们可以用这些值来区别调试事件是发生在debuggee中还是它的子进程中(当没有指定 DEBUG_ONLY_THIS_PROCESS 标志时).

u 是一个联合,包含了调试事件的更多信息.根据上面dwDebugEventCode的不同,它可以是以下结构:

dwDebugEventCode u的解释

CREATE_PROCESS_DEBUG_EVENT 名为CreateProcessInfo的CREATE_PROCESS_DEBUG_INFO结构

EXIT_PROCESS_DEBUG_EVENT 名为ExitProcess的EXIT_PROCESS_DEBUG_INFO结构

CREATE_THREAD_DEBUG_EVENT 名为CreateThread的CREATE_THREAD_DEBUG_INFO结构

EXIT_THREAD_DEBUG_EVENT 名为ExitThread的EXIT_THREAD_DEBUG_EVENT 结构

LOAD_DLL_DEBUG_EVENT 名为LoadDll的LOAD_DLL_DEBUG_INFO 结构

UNLOAD_DLL_DEBUG_EVENT 名为UnloadDll的UNLOAD_DLL_DEBUG_INFO结构

EXCEPTION_DEBUG_EVENT 名为Exception的EXCEPTION_DEBUG_INFO结构

OUTPUT_DEBUG_STRING_EVENT 名为DebugString的OUTPUT_DEBUG_STRING_INFO 结构

RIP_EVENT 名为RipInfo的RIP_INFO 结构

我不会在这一个教程里讲所有这些结构的细节,这里只详细讲一下CREATE_PROCESS_DEBUG_INFO 结构.

假设我们的程序调用了WaitForDebugEvent函数并返回,我们要做的第一件事就是检查dwDebugEventCode中的值来看debuggee进程中发生了那种类型的调试事件.比如说,如果dwDebugEventCode的值为 CREATE_PROCESS_DEBUG_EVENT,就可认为u的成员为CreateProcessInfo 并用u.CreateProcessInfo来访问.

在我们的程序中做对调试事件的响应. 当WaitForDebugEvent 返回时,这意味着在debuggee进程中发生了调试事件或者发生了超时.所以我们的程序要检查dwDebugEventCode 来作出适当的反应.这里有些象处理Windows消息:由用户来选择和忽略消息.

继续运行debuggee. 当调试事件发生时, Windows挂起了debuggee,所以当我们处理完调试事件,还要让debuggee继续运行.调用ContinueDebugEvent 函数来完成这一过程.

ContinueDebugEvent proto dwProcessId:DWORD, dwThreadId:DWORD, dwContinueStatus:DWORD

该函数恢复由于调试事件而挂起的线程.

dwProcessId和dwThreadId是要恢复的线程的进程ID和线程ID,通常这两个值从 DEBUG_EVENT结构的dwProcessId 和dwThreadId成员获得.

dwContinueStatus显示了如何继续报告调试事件的线程.可能的取值有两个: DBG_CONTINUE 和DBG_EXCEPTION_NOT_HANDLED. 对大多数调试事件,这两个值都一样:恢复线程.唯一的例外是EXCEPTION_DEBUG_EVENT,如果线程报告发生了一个异常调试事件,这意味着在debuggee的线程中发生了一个异常.如果指定了DBG_CONTINUE,线程将忽略它自己的异常处理部分并继续执行.在这种情况下,我们的程序必须在以DBG_CONTINUE恢复线程之前检查并处理异常,否则异常将生生不息地不断发生....如果我们指定了 DBG_EXCEPTION_NOT_HANDLED值,就是告诉Windows我们的程序并不处理异常:Windows将使用debuggee的默认异常处理函数来处理异常.

总而言之,如果我们的程序没有考虑异常,而调试事件又指向debuggee进程中的一个异常的话,就应调用含DBG_CONTINUE标志的ContinueDebugEvent函数.否则,我们的程序就必须以DBG_EXCEPTION_NOT_HANDLED调用 ContinueDebugEvent.但在下面这种情况下必须使用DBG_CONTINUE标志:第一个在ExceptionCode成员中有值EXCEPTION_BREAKPOINT的 EXCEPTION_DEBUG_EVENT事件.当debuggee开始执行它的第一条指令时,我们的函数将接受到异常调试事件.它事实上是一个调试中断(int 3h).如果我们以DBG_EXCEPTION_NOT_HANDLED调用ContinueDebugEvent 来响应调试事件, Windows NT会拒绝执行debuggee(因为它没有异常处理).所以在这种情况下,要用DBG_CONTINUE标志告诉Windows我们希望该线程继续执行.

继续上面的步骤循环直到debuggee进程退出. 我们的程序必须在一个很象消息循环的无限循环中直到debuggee结束.该循环大体如下:

.while TRUE

invoke WaitForDebugEvent, addr DebugEvent, INFINITE

.break .if DebugEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT

<调试事件处理>

invoke ContinueDebugEvent, DebugEvent.dwProcessId, DebugEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED

.endw

就是说,当开始调试程序时,我们的程序不能和debuggee分开直到它结束.

我们再来总结一下这些步骤:

创建一个进程或捆绑我们的程序到运行中的进程上.

等待调试事件

响应调试事件.

继续执行debuggee.

继续这一无尽循环直到debuggee进程结束

例子:

这个例子调试一个win32程序并显示诸如进程句柄,进程Id,映象基址等.

.386

.model flat,stdcall

option casemap:none

include \masm32\include\windows.inc

include \masm32\include\kernel32.inc

include \masm32\include\comdlg32.inc

include \masm32\include\user32.inc

includelib \masm32\lib\kernel32.lib

includelib \masm32\lib\comdlg32.lib

includelib \masm32\lib\user32.lib

.data

AppName db "Win32 Debug Example no.1",0

ofn OPENFILENAME <>

FilterString db "Executable Files",0,"*.exe",0

db "All Files",0,"*.*",0,0

ExitProc db "The debuggee exits",0

NewThread db "A new thread is created",0

EndThread db "A thread is destroyed",0

ProcessInfo db "File Handle: %lx ",0dh,0Ah

db "Process Handle: %lx",0Dh,0Ah

db "Thread Handle: %lx",0Dh,0Ah

db "Image Base: %lx",0Dh,0Ah

db "Start Address: %lx",0

.data?

buffer db 512 dup(?)

startinfo STARTUPINFO <>

pi PROCESS_INFORMATION <>

DBEvent DEBUG_EVENT <>

.code

start:

mov ofn.lStructSize,sizeof ofn

mov ofn.lpstrFilter, offset FilterString

mov ofn.lpstrFile, offset buffer

mov ofn.nMaxFile,512

mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY

invoke GetOpenFileName, ADDR ofn

.if eax==TRUE

invoke GetStartupInfo,addr startinfo

invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi

.while TRUE

invoke WaitForDebugEvent, addr DBEvent, INFINITE

.if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT

invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION

.break

.elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT

invoke wsprintf, addr buffer, addr ProcessInfo, DBEvent.u.CreateProcessInfo.hFile, DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.lpBaseOfImage, DBEvent.u.CreateProcessInfo.lpStartAddress

invoke MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION

.elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT

.if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT

invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE

.continue

.endif

.elseif DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT

invoke MessageBox,0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION

.elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT

invoke MessageBox,0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION

.endif

invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED

.endw

invoke CloseHandle,pi.hProcess

invoke CloseHandle,pi.hThread

.endif

invoke ExitProcess, 0

end start

分析:

程序首先填充OPENFILENAME结构,调用GetOpenFileName让用户选择要调试的程序.

invoke GetStartupInfo,addr startinfo

invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, DEBUG_PROCESS+ DEBUG_ONLY_THIS_PROCESS, NULL, NULL, addr startinfo, addr pi

当接收用户选择后,调用CreateProcess装载程序.并调用GetStartupInfo以默认值填充STARTUPINFO结构.注意我们将DEBUG_PROCESS标志与DEBUG_ONLY_THIS_PROCESS标志组合来仅调试这个程序,不包括子进程.

.while TRUE

invoke WaitForDebugEvent, addr DBEvent, INFINITE

在debuggee被装入后,我们调用WaitForDebugEvent进入无尽的调试循环,WaitForDebugEvent在debuggee中发生调试事件时返回,因为我们指定了INFINITE作为第二个参数.当调试事件发生时, WaitForDebugEvent 返回并填充DBEvent结构.

.if DBEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT

invoke MessageBox, 0, addr ExitProc, addr AppName, MB_OK+MB_ICONINFORMATION

.break

我们要先检查dwDebugEventCode的值, 如果是EXIT_PROCESS_DEBUG_EVENT,用一个消息框显示"The debuggee exits" 并退出调试循环.

.elseif DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT

invoke wsprintf, addr buffer, addr ProcessInfo, DBEvent.u.CreateProcessInfo.hFile, DBEvent.u.CreateProcessInfo.hProcess, DBEvent.u.CreateProcessInfo.hThread, DBEvent.u.CreateProcessInfo.lpBaseOfImage, DBEvent.u.CreateProcessInfo.lpStartAddress

invoke MessageBox,0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION

如果dwDebugEventCode 的值为CREATE_PROCESS_DEBUG_EVENT,我们就在消息框中显示一些感兴趣的底层信息.这些信息从u.CreateProcessInfo获得. CreateProcessInfo是一个CREATE_PROCESS_DEBUG_INFO类型的结构体.你可以查阅Win32 API获得它的更多信息e.

.elseif DBEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT

.if DBEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT

invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE

.continue

.endif

如果dwDebugEventCode 的值为EXCEPTION_DEBUG_EVENT,我们就要更进一步检查异常类型.它是一大堆的结构嵌套,但我们可以从ExceptionCode成员获得异常类型.如果ExceptionCode的值为 EXCEPTION_BREAKPOINT并且是第一次发生(或者我们已知道deuggee中没有int 3h指令),我们可以安全地假定在debuggee要执行第一条指令时发生这一异常.在我们完成这些处理后,就可以用 DBG_CONTINUE调用ContinueDebugEvent来继续执行debuggee.接着我们继续等待下一个调试事件的发生.

.elseif DBEvent.dwDebugEventCode==CREATE_THREAD_DEBUG_EVENT

invoke MessageBox,0, addr NewThread, addr AppName, MB_OK+MB_ICONINFORMATION

.elseif DBEvent.dwDebugEventCode==EXIT_THREAD_DEBUG_EVENT

invoke MessageBox,0, addr EndThread, addr AppName, MB_OK+MB_ICONINFORMATION

.endif

如果dwDebugEventCode 的值为CREATE_THREAD_DEBUG_EVENT或EXIT_THREAD_DEBUG_EVENT, 我们的程序显示一个消息框.

invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED

.endw

除了上面讨论过的 EXCEPTION_DEBUG_EVENT,用DBG_EXCEPTION_NOT_HANDLED标志调用ContinueDebugEvent函数恢复debuggee的执行.

invoke CloseHandle,pi.hProcess

invoke CloseHandle,pi.hThread

当debuggee结束时,我们就跳出了调试循环,这时要关闭 debuggee的线程和进程句柄.关闭这些句柄并不意味着要关闭这些进程和线程.只是说不再用这些句柄罢了.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有