Windows用户态调试器原理.docx

上传人:b****4 文档编号:5500011 上传时间:2023-05-08 格式:DOCX 页数:16 大小:134.23KB
下载 相关 举报
Windows用户态调试器原理.docx_第1页
第1页 / 共16页
Windows用户态调试器原理.docx_第2页
第2页 / 共16页
Windows用户态调试器原理.docx_第3页
第3页 / 共16页
Windows用户态调试器原理.docx_第4页
第4页 / 共16页
Windows用户态调试器原理.docx_第5页
第5页 / 共16页
Windows用户态调试器原理.docx_第6页
第6页 / 共16页
Windows用户态调试器原理.docx_第7页
第7页 / 共16页
Windows用户态调试器原理.docx_第8页
第8页 / 共16页
Windows用户态调试器原理.docx_第9页
第9页 / 共16页
Windows用户态调试器原理.docx_第10页
第10页 / 共16页
Windows用户态调试器原理.docx_第11页
第11页 / 共16页
Windows用户态调试器原理.docx_第12页
第12页 / 共16页
Windows用户态调试器原理.docx_第13页
第13页 / 共16页
Windows用户态调试器原理.docx_第14页
第14页 / 共16页
Windows用户态调试器原理.docx_第15页
第15页 / 共16页
Windows用户态调试器原理.docx_第16页
第16页 / 共16页
亲,该文档总共16页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

Windows用户态调试器原理.docx

《Windows用户态调试器原理.docx》由会员分享,可在线阅读,更多相关《Windows用户态调试器原理.docx(16页珍藏版)》请在冰点文库上搜索。

Windows用户态调试器原理.docx

Windows用户态调试器原理

Windows用户态调试器原理

Windows操作系统提供了一组API来支持调试器。

这些API可以分为三类:

创建调试目标的API;

在调试循环中处理调试事件的API。

查看和修改调试目标的API。

接下来将会分别对这三种API进行介绍。

创建调试目标

在调试器工作之前,需要创建调试目标。

用户态调试器有两种创建调试目标的方法:

一是创建新进程,二是附加到一个运行的进程。

采用这两种方法中的任一种后,该进程就成为了调试目标。

操作系统将调试器与调试目标关联起来。

调试器创建调试目标是通过调用CreateProcess并传入DEBUG_PROCESS标志。

如:

[cpp]viewplaincopy

1STARTUPINFOsi={0};

2

3si.cb=sizeof(si);

4

5PROCESS_INFORMATIONpi={0};

6

7boolret=CreateProcesss(NULL,argv[1],NULL,NULL,false,

8

9DEBUG_PROCESS,NULL,NULL,&si,&pi);

调试器附加到一个运行的进程是通过调用DebugActiveProcess来实现的。

DebugActiveProcess

此函数允许将调试器捆绑到一个正在运行的进程上。

[cpp]viewplaincopy

10BOOLDebugActiveProcess(DWORDdwProcessId)

dwProcessId:

欲捆绑进程的进程标识符

如果函数成功,则返回非零值;如果失败,则返回零

无论采用哪一种方法,调试器与操作系统的交互都是相同的。

这种调试器被称为活动调试器(livingdebuger)。

每个调试器只能有一个调试目标。

调试循环

在初学Windows时我们一定接触过消息循环。

调试循环与此类似。

while(当调试不结束时)

{

//等待操作系统发送调试事件。

//处理调试事件。

//通知调试目标执行相应操作。

}

在调试目标被调试时,进程执行的一些操作会以事件的方式通知调试器。

例如动态库的加载与卸载、新线程的创建和销毁以及代码或处理器抛出的异常都会通知调试器。

当有事件需要通知调试器时,操作系统会首先挂起调试目标的所有线程,然后把事件通知调试器。

并且等待调试器通知其继续执行。

调试器会调用WaitForDebugEvent来等待事件通知的到来。

当有事件通知到来时此函数返回,返回的事件信息被封装在DEBUG_EVENT结构中。

这个结构包含事件的类型等其他信息。

事件类型有以下几种:

WaitForDebugEvent

此函数用来等待被调试进程发生调试事件。

[cpp]viewplaincopy

11BOOLWaitForDebugEvent(LPDEBUG_ENENTlpDebugEvent,DWORDdwMilliseconds)

lpDebugEvent:

指向接收调试事件信息的DEBUG_ENENT结构的指针

dwMilliseconds:

指定用来等待调试事件发生的毫秒数,如果这段时间内没有调试事件发生,函数将返回调用者;如果将该参数指定为INFINITE,函数将一直等待直到调试事件发生

如果函数成功,则返回非零值;如果失败,则返回零

在调试器调用WaitForDebugEvent返回后,得到事件通知,然后解析DEBUG_EVENT结构,并对事件进行响应,处理完成后调试器将会调用ContinueDebugEvent,并根据参数来通知调试目标执行相应操作。

ContinueDebugEvent函数

此函数允许调试器恢复先前由于调试事件而挂起的线程。

[cpp]viewplaincopy

12BOOLContinueDebugEvent(DWORDdwProcessId,DWORDdwThreadId,DWORDdwContinueStatus)

dwProcessId为被调试进程的进程标识符

dwThreadId为欲恢复线程的线程标识符

dwContinueStatus指定了该线程将以何种方式继续,包含两个定义值DBG_CONTINUE和DBG_EXCEPTION_NOT_HANDLED

如果函数成功,则返回非零值;如果失败,则返回零。

具体实现为:

[cpp]viewplaincopy

13DWORDCondition=DBG_CONTINUE;

14

15while(Condition)

16

17{

18

19DEBUG_EVENTDebugEvent={0};

20

21WaitForDebugEvent(&DebugEvent,INFINITE);//等待调试事件

22

23ProcessEvenet(DebugEvent)//处理调试事件。

24

25ContinueDebugEvent(DebugEvent.dwProcessId,DebugEvent.dwThreadId,Condition);//通知调试目标继续执行。

26

27}

ProcessEvent用于对调试事件进行处理。

它是用户自定义函数。

在该函数内会对DEBUG_EVENT结构进行解析。

DEBUG_EVENT结构为:

[cpp]viewplaincopy

28typedefstruct_DEBUG_EVENT{

29

30DWORDdwDebugEventCode;

31

32DWORDdwProcessId;

33

34DWORDdwThreadId;

35

36union{

37

38EXCEPTION_DEBUG_INFOException;

39

40CREATE_THREAD_DEBUG_INFOCreateThread;

41

42CREATE_PROCESS_DEBUG_INFOCreateProcessInfo;

43

44EXIT_THREAD_DEBUG_INFOExitThread;

45

46EXIT_PROCESS_DEBUG_INFOExitProcess;

47

48LOAD_DLL_DEBUG_INFOLoadDll;

49

50UNLOAD_DLL_DEBUG_INFOUnloadDll;

51

52OUTPUT_DEBUG_STRING_INFODebugString;

53

54RIP_INFORipInfo;

55

56}u;

57

58}DEBUG_EVENT,*LPDEBUG_EVENT;

处理通知代码如下:

[cpp]viewplaincopy

59DWORDProcessEvent(DEBUG_EVENTde)

60

61{

62

63switch(de.dwDebugEvent.Code)

64

65{

66

67caseEXCEPTION_DEBUG_EVENT:

68

69{

70

71}

72

73break;

74

75caseCREATE_THREAD_DEBUG_EVENT:

76

77{

78

79}

80

81break;

82

83caseCREATE_PROCESS_DEBUG_EVENT:

84

85{

86

87}

88

89break;

90

91caseEXIT_THREAD_DEBUG_EVENT:

92

93{

94

95}

96

97break;

98

99caseEXIT_PROCESS_DEBUG_EVENT:

100

101{

102

103}

104

105break;

106

107caseLOAD_DLL_DEBUG_EVENT:

108

109{

110

111}

112

113break;

114

115caseOUTPUT_DEBUG_STRING_EVENT:

116

117{

118

119}

120

121break;

122

123......

124

125}

126

127returnDBG_CONTINUE;

128

129}

调试事件介绍

OUTPUT_DEBUG_STRING_EVENT事件

很多程序员在调试程序时喜欢将执行的结果或中间步骤输出,用以检查程序执行的正确与否。

在很多系统中这是很不方便的。

但我们可以使用调试输出命令,将某些需要显示的结果输出到输出窗口中。

如vc的TRACE宏。

其实在TRACE宏内部是调用OutputDebugString来实现的。

调试器会把调试目标输出的字符串通过事件处理代码显示出来。

在DEBUG_EVENT结构中有一个DebugString成员。

该结构定义为:

[cpp]viewplaincopy

130typedefstruct_OUTPUT_DEBUG_STRING_INFO{

131

132LPSTRlpDebugStringData;

133

134WORDfUnicode;

135

136WORDnDebugStringLength;

137

138}OUTPUT_DEBUG_STRING_INFO,*LPOUTPUT_DEBUG_STRING_INFO;

在此结构中有一个lpDebugStringData成员,它保存被输出字符串的地址。

nDebugStringLength为字符串长度。

fUnicode表示是ANSI还是UNICODE字符。

下面为处理OUTPUT_DEBUG_STRING_EVENT事件的代码:

[cpp]viewplaincopy

139caseOUTPUT_DEBUG_STRING_EVENT:

140

141{

142

143OUTPUT_DEBUG_STRING_INFOoi=de.u.DebugString;

144

145WCHAR*msg=ReadRemoteString(调试目标句柄,

146

147oi.lpDebugStringData,oi.nDebugStringLength,oi.fUnicode);

148

149std:

:

wcout<

150

151break;

152

153}

ReadRemoteString是用户自定义函数。

在此函数内部是调用ReadProcessMemory从调试目标进程内读取字符串。

具体不再介绍。

ReadProcessMemory

读取指定进程的某区域内的数据。

[cpp]viewplaincopy

154BOOLReadProcessMemory(HANDLEhProcess,LPCVOIDlpBassAddress,LPVOIDlpBuffer,SIZE_TnSize,SIZE_T*lpNumberOfBytesRead)

hProcess:

进程的句柄

lpBassAddress:

欲读取区域的基地址

lpBuffer:

保存读取数据的缓冲的指针

nSize:

欲读取的字节数

lpNumberOfBytesRead:

存储已读取字节数的地址指针

如果函数成功,则返回非零值;如果失败,则返回零

处理EXCEPTION_DEBUG_EVENT事件

当调试目标在调试时发生异常时,操作系统将会向调试器发送EXCEPTION_DEBUG_EVENT事件通知

当发生此事件时,DEBUG_EVENT结构包含的是一个EXCEPTION_DEBUG_INFO结构。

[cpp]viewplaincopy

155typedefstruct_EXCEPTION_DEBUG_INFO{

156

157EXCEPTION_RECORDExceptionRecord;

158

159DWORDdwFirstChance;

160

161}EXCEPTION_DEBUG_INFO,*LPEXCEPTION_DEBUG_INFO;

ExceptionRecord成员包含了异常信息的一个副本。

如异常码,异常引发地址以及异常参数等。

定义如下:

[cpp]viewplaincopy

162typedefstruct_EXCEPTION_RECORD{

163

164DWORDExceptionCode;

165

166DWORDExceptionFlags;

167

168struct_EXCEPTION_RECORD*ExceptionRecord;

169

170PVOIDExceptionAddress;

171

172DWORDNumberParameters;

173

174DWORDExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];

175

176}EXCEPTION_RECORD;

dwFirstChance告诉调试器是否是第一轮通知这个异常。

从操作系统的角度来看,调试器必须对异常进行解析,并且将DBG_CONTINUE或者是DBG_EXECPTION_NOT_HANDLED作为参数传递给ContinueDebugEvent。

如果执行DBG_CONTINUE,则操作系统认为该异常已经被妥善处理了。

因此从产生异常的地址开始回复程序的执行。

如果传入DBG_EXCEPTION_NOT_HANDLED,则告诉操作系统该异常并未被处理,操作系统将继续分发异常。

[cpp]viewplaincopy

177caseEXCEPTION_DBUG_EVENT:

178

179{

180

181std:

:

cout<<”异常码为”<

:

hex<

:

endl;

182

183//在switch判断异常类型,并执行相应操作。

184

185switch(debugEvent.u.Exception.ExceptionRecord.ExceptionCode)

186

187{

188

189caseEXCEPTION_BREAKPOINT:

190

191break;

192

193caseEXCEPTION_SINGLE_STEP:

194

195beak;

196

197returnDBG_CONTINUE;

198

199}

200

201break;

202

203}

在调试循环中,从WaitForDebugEvent中返回以及调用ContinueDebugEvent之间的这段时间内,调试目标不会执行,因此它的状态也将保持不变。

当调试目标被挂起时,调试器就进入了交互模式,接收用户的各种指令,并按照不同指令执行不同操作。

调试事件到来的顺序

当我们启动调试目标时,调试器接收到的第一个事件是CREATE_THREAD_DEBUG_EVENT。

接下来是加载dll的事件。

每加载一个,都会产生一个这样的事件。

当所有模块都被加载到进程地址空间后,调试目标就准备好运行了,调试器此时也做好了接收通知的准备。

此时是设置断点的最佳时机。

在调试目标退出之前调试器会收到EXIT_DEBUG_PROCESS_EVENT通知。

此后调试器不能收到加载到进程地址空间的dll从进程卸载的UNLOAD_DLL_DEBUG_EVENT通知。

前面介绍的调试事件都是由Windows操作系统发出的,来通知调试器。

但是调试目标也会发出自己的异常。

调试器在处理这些异常时可以选择与其他调试事件一样的处理方式。

Windows操作系统使用结构化异常处理(SEH)机制将处理器引发的异常传递给内核及用户态程序。

每个SEH异常都有一个无符号整形的异常码来唯一标识。

这个异常码是由系统在异常发生时指定的。

这些异常码使用了操作系统开发人员定义的公开异常码。

例如访问违规异常异常码为0xC0000005,断点异常为0xC80000003。

为了方便记忆,这些异常码被定义为常量。

其名字形如STATUS_XXX。

#defineSTATUS_BREAKPOINT((NTSTATUS)0x80000003L)

由于异常码很难记忆,因此Windows调试器中包含了一些更容易记住的别名来控制调试器的行为。

例如断点异常0x80000003的别名是bpe。

C++异常码0xE06D7363别名为eh。

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 小学教育 > 语文

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2