为了使层次明确,结构清晰和便于索引,《软件调试》使用三级结构来组织章节正文,章是一级,节是二级,节之下再根据需要分为小节。
例如在第1章绪论中,1.3节的标题是简要历史,下面又分3个小节,分别讲单步执行(1.3.1)、断点(1.3.2)和分支监视(1.3.3)功能的历史。
因为三级目录的总页数有31页,为了控制总页数,在《软件调试》的印刷版本中,使用的是二级目录。
在这里,我们将完整的三级目录提供给大家。
如果您需要WORD版本,那么可以点击这里下载
如果你想查看更简略的二级目录,那么请点击这里。
第1篇 绪论 1
第1章 软件调试基础 3
1.1 简介 3
1.1.1 定义 3
1.1.2 基本过程 5
1.2 基本特征 6
1.2.1 难度大 6
1.2.2 难以估计完成时间 7
1.2.3 广泛的关联性 7
1.3 简要历史 8
1.3.1 单步执行 8
1.3.2 断点指令 10
1.3.3 分支监视 11
1.4 分类 12
1.4.1 按调试目标的系统环境分 12
1.4.2 按目标代码的执行方式分 12
1.4.3 按目标代码的执行模式分 13
1.4.4 按软件所处的阶段分 13
1.4.5 按调试器与调试目标的相对位置分 14
1.4.6 按调试目标的活动性分 14
1.4.7 按调试工具分 15
1.5 调试技术概览 15
1.5.1 断点 15
1.5.2 单步执行 16
1.5.3 输出调试信息 17
1.5.4 日志 17
1.5.5 事件追踪 18
1.5.6 转储文件 18
1.5.7 栈回溯 18
1.5.8 反汇编 19
1.5.9 观察和修改数据 19
1.5.10 控制被调试进程和线程 19
1.5.11 学习调试技术的意义 19
1.6 错误与缺欠 20
1.6.1 内因与表象 20
1.6.2 谁的Bug 21
1.6.3 Bug的生命周期 22
1.6.4 软件错误的开支曲线 22
1.7 与软件工程的关系 24
1.7.1 调试与编码的关系 24
1.7.2 调试与测试的关系 25
1.7.3 调试与逆向工程的关系 25
1.7.4 调试尚未得到应有的重视 26
1.8 本章总结 26
参考文献 26
第2篇 CPU的调试支持 27
第2章 CPU基础 29
2.1 指令和指令集 29
2.1.1 基本特征 30
2.1.2 寻址方式 31
2.1.3 指令的执行过程 31
2.2 IA-32处理器 32
2.2.1 80386处理器 33
2.2.2 80486处理器 33
2.2.3 奔腾处理器 34
2.2.4 P6系列处理器 35
2.2.5 奔腾4处理器 37
2.2.6 Core 2系列处理器 37
2.3 CPU的操作模式 38
2.4 寄存器 40
2.4.1 通用数据寄存器 40
2.4.2 标志寄存器EFLAGS 41
2.4.3 MSR寄存器 42
2.4.4 控制寄存器 42
2.4.5 其他寄存器 45
2.4.6 64位模式时的寄存器 46
2.5 理解保护模式 46
2.5.1 任务间的保护机制 47
2.5.2 任务内的保护 47
2.5.3 特权级 48
2.5.4 特权指令 49
2.6 段机制 50
2.6.1 段描述符 50
2.6.2 描述符表 52
2.6.3 段选择子 52
2.6.4 观察段寄存器 53
2.7 分页机制(Paging) 55
2.7.1 页目录(Page Directory) 56
2.7.2 页表(Page Table) 57
2.7.3 页目录指针表(Page-Directory-Pointer Table) 57
2.7.4 地址翻译 57
2.7.5 使用WinDBG观察分页机制 58
2.7.6 4MB内存页的情况 60
2.7.7 WinDBG的有关命令 61
2.8 系统概貌 62
2.9 本章总结 64
参考文献 64
第3章 中断和异常 65
3.1 概念和差异 65
3.1.1 中断 65
3.1.2 异常 66
3.1.3 比较 67
3.2 异常的分类 67
3.2.1 错误类异常 67
3.2.2 陷阱类异常 68
3.2.3 中止类异常 68
3.3 异常例析 69
3.3.1 列表 69
3.3.2 错误代码 70
3.3.3 示例 70
3.4 中断/异常优先级 72
3.5 中断/异常处理 73
3.6 本章总结 74
参考文献 74
第4章 断点和单步执行 75
4.1 软件断点 75
4.1.1 感受INT 3 75
4.1.2 在调试器中设置断点 76
4.1.3 断点命中 77
4.1.4 恢复执行 79
4.1.5 特别用途 79
4.1.6 断点API 80
4.1.7 系统对INT 3的优待 80
4.1.8 观察调试器写入的INT 3指令 82
4.1.9 归纳 83
4.2 硬件断点 83
4.2.1 调试寄存器概览 84
4.2.2 调试地址寄存器 84
4.2.3 调试控制寄存器 85
4.2.4 指令断点 87
4.2.5 调试异常 88
4.2.6 调试状态寄存器 88
4.2.7 示例 89
4.2.8 硬件断点的设置方法 92
4.2.9 归纳 94
4.3 陷阱标志 95
4.3.1 单步执行标志(TF) 95
4.3.2 高级语言的单步执行 97
4.3.3 任务状态段陷阱标志 98
4.3.4 分支到分支单步执行标志(BTF) 98
4.4 实模式调试器例析 100
4.4.1 Debug.exe 101
4.4.2 8086 Monitor 102
4.4.3 关键实现 103
4.5 本章总结 105
参考文献 105
第5章 分支记录和性能监视 107
5.1 分支监视概览 107
5.2 使用寄存器的分支记录 108
5.2.1 LBR 108
5.2.2 LBR栈 110
5.2.3 应用示例 110
5.3 使用内存的分支记录 113
5.3.1 DS存储区 114
5.3.2 启用DS机制 115
5.3.3 调试控制寄存器 116
5.4 DS示例:CpuWhere 117
5.4.1 驱动程序 117
5.4.2 应用界面 120
5.4.3 局限性和扩展建议 122
5.5 性能监视 123
5.5.1 奔腾处理器的性能监视机制 123
5.5.2 P6处理器的性能监视机制 124
5.5.3 P4处理器的性能监视 126
5.5.4 架构性的性能监视机制 130
5.5.5 Core微架构处理器的性能监视机制 130
5.5.6 资源 131
5.6 本章总结 132
参考文献 132
第6章 机器检查架构(MCA) 133
6.1 奔腾处理器的机器检查机制 134
6.2 MCA 135
6.2.1 概览 135
6.2.2 MCA全局寄存器 136
6.2.3 MCA错误报告寄存器 137
6.2.4 扩展的机器检查状态寄存器 139
6.2.5 MCA错误编码 140
6.3 编写MCA软件 141
6.3.1 基本算法 141
6.3.2 简单示例 143
6.3.3 在Windows系统中的应用 144
6.4 本章总结 145
参考文献 145
第7章 JTAG调试 147
7.1 简介 147
7.1.1 ICE 148
7.1.2 JTAG 148
7.2 JTAG原理 149
7.2.1 边界扫描链路 149
7.2.2 TAP信号 151
7.2.3 TAP寄存器 151
7.2.4 TAP控制器 152
7.2.5 TAP指令 153
7.3 JTAG应用 154
7.3.1 JTAG调试 155
7.3.2 调试端口(Debug Port) 156
7.4 IA-32处理器的JTAG支持 156
7.4.1 P6处理器的JTAG实现 156
7.4.2 探测模式 158
7.4.3 ITP和XDP 158
7.4.4 XDP 160
7.4.5 典型应用 161
7.5 本章总结 161
参考文献 162
第3篇 操作系统的调试支持 163
第8章 Windows概要 165
8.1 简介 165
8.2 进程和进程空间 167
8.2.1 进程和线程 167
8.2.2 进程空间 167
8.2.3 进程资源 168
8.2.4 EPROCESS结构 169
8.2.5 访问令牌 172
8.2.6 PEB 173
8.2.7 其他字段 174
8.2.8 SessionId 174
8.2.9 进程ID 175
8.2.10 父进程ID 175
8.2.11 页目录基地址 175
8.2.12 对象表格 175
8.2.13 句柄数量 176
8.2.14 观察进程 176
8.3 内核模式和用户模式 176
8.3.1 访问模式 177
8.3.2 使用INT 2E切换到内核模式 177
8.3.3 快速系统调用 179
8.3.4 逆向调用 182
8.3.5 实例分析 183
8.4 架构和系统部件 184
8.4.1 概览 184
8.4.2 内核空间 185
8.4.3 内核文件和HAL文件 185
8.4.4 系统和IDLE进程 187
8.4.5 用户空间 189
8.4.6 NTDLL.DLL 190
8.4.7 环境子系统 190
8.5 本章总结 192
参考文献 192
第9章 用户态调试模型 193
9.1 概览 193
9.1.1 参与者 194
9.1.2 调试子系统 195
9.1.3 调试事件驱动 195
9.2 采集调试消息 196
9.2.1 消息常量 196
9.2.2 进程和线程创建消息 197
9.2.3 进程和线程退出消息 197
9.2.4 模块映射和反映射消息 198
9.2.5 异常消息 200
9.3 发送调试消息 200
9.3.1 调试消息结构 200
9.3.2 DbgkpSendApiMessage函数 201
9.3.3 控制被调试进程 202
9.4 调试子系统服务器(XP之后) 203
9.4.1 DebugObject 203
9.4.2 创建调试对象 204
9.4.3 设置调试对象 204
9.4.4 传递调试消息 204
9.4.5 杜撰的调试消息 206
9.4.6 清除调试对象 207
9.4.7 内核服务 207
9.4.8 全景 208
9.5 调试子系统服务器(XP之前) 210
9.5.1 概览 210
9.5.2 Windows会话管理器 211
9.5.3 Windows环境子系统服务器进程 213
9.5.4 调用CSRSS的服务 214
9.5.5 CsrCreateProcess服务 216
9.5.6 CsrDebugProcess服务 217
9.6 比较两种模型 219
9.6.1 Windows 2000调试子系统的优点 219
9.6.2 Windows 2000调试子系统的安全问题 219
9.6.3 Windows XP模型的优点 220
9.6.4 Windows XP引入的新调试功能 221
9.7 NTDLL中的调试支持例程 221
9.7.1 DbgUi函数 222
9.7.2 DbgSs函数 223
9.7.3 Dbg函数 223
9.8 调试API 224
9.9 本章总结 226
参考文献 226
第10章 用户态调试过程 227
10.1 调试器进程 227
10.1.1 线程模型 227
10.1.2 调试器的工作线程 228
10.1.3 DbgSsReserved字段 229
10.2 被调试进程 231
10.2.1 特征 231
10.2.2 DebugPort字段 231
10.2.3 BeingDebugged字段 232
10.2.4 观察DebugPort字段和BeingDebugged字段 232
10.2.5 调试会话 234
10.3 从调试器中启动被调试程序 234
10.3.1 CreateProcess API 234
10.3.2 第一批调试事件 236
10.3.3 初始断点 237
10.3.4 自动启动调试器 238
10.4 附加到已经启动的进程 240
10.4.1 DebugActiveProcess API 240
10.4.2 示例:TinyDbgr程序 241
10.5 处理调试事件 243
10.5.1 DEBUG_EVENT 结构 244
10.5.2 WaitForDebugEvent API 245
10.5.3 调试事件循环 246
10.5.4 回复调试事件 248
10.5.5 定制调试器的事件处理方式 249
10.6 中断到调试器 251
10.6.1 初始断点 251
10.6.2 编程时加入断点 252
10.6.3 通过调试器设置断点 252
10.6.4 通过远程线程触发断点异常 252
10.6.5 在线程当前执行位置设置断点 254
10.6.6 动态调用远程函数 255
10.6.7 挂起中断 256
10.6.8 调试热键(F12) 257
10.6.9 窗口更新 257
10.7 输出调试字符串 259
10.7.1 发送调试信息 259
10.7.2 使用调试器接收调试信息 260
10.7.3 使用工具接收调试信息 261
10.8 终止调试会话 266
10.8.1 被调试进程退出 266
10.8.2 调试器进程退出 267
10.8.3 分离被调试进程 269
10.8.4 退出时分离 271
10.9 本章总结 271
参考文献 271
第11章 中断和异常管理 273
11.1 中断描述符表 273
11.1.1 概况 273
11.1.2 门描述符 275
11.1.3 执行中断和异常处理函数 276
11.1.4 IDT表一览 279
11.2 异常的描述和登记 280
11.2.1 EXCEPTION_RECORD结构 281
11.2.2 登记CPU异常 283
11.2.3 登记软件异常 283
11.3 异常分发过程 284
11.3.1 KiDispatchException函数 284
11.3.2 内核态异常的分发过程 286
11.3.3 用户态异常的分发过程 287
11.3.4 归纳 289
11.4 结构化异常处理(SEH) 290
11.4.1 SEH简介 291
11.4.2 SEH的终结处理 291
11.4.3 SEH的异常处理 294
11.4.4 过滤表达式 295
11.4.5 异常处理块 299
11.4.6 嵌套使用终结处理和异常处理 301
11.5 向量化异常处理(VEH) 302
11.5.1 登记和注销 302
11.5.2 调用VEH 303
11.5.3 示例 304
11.6 本章总结 308
参考文献 308
第12章 未处理异常和JIT调试 309
12.1 简介 309
12.2 默认的异常处理器 311
12.2.1 BaseProcessStart函数中的SEH处理器 311
12.2.2 编译器插入的SEH处理器 312
12.2.3 基于信号的异常处理 314
12.2.4 试验:观察默认的异常处理器 315
12.2.5 BaseThreadStart函数中的SEH处理器 317
12.3 未处理异常过滤函数 318
12.3.1 Windows XP之前 319
12.3.2 Windows XP 322
12.4 应用程序错误对话框 328
12.4.1 用HardError机制提示应用程序错误 328
12.4.2 使用ReportFault API提示应用程序错误 330
12.5 JIT调试和Dr. Watson 334
12.5.1 配置JIT调试器 334
12.5.2 启动JIT调试器 336
12.5.3 自己编写JIT调试器 338
12.6 顶层异常过滤函数 340
12.6.1 注册 340
12.6.2 C运行时库的顶层过滤函数 341
12.6.3 执行 342
12.6.4 调试 342
12.7 Dr. Watson 343
12.7.1 配置和察看模式 344
12.7.2 安装为JIT调试器模式 346
12.7.3 JIT调试模式 346
12.8 DRWTSN32的日志文件 347
12.8.1 异常信息 347
12.8.2 系统信息 348
12.8.3 任务列表 348
12.8.4 模块列表 348
12.8.5 线程状态 349
12.8.6 函数调用序列 349
12.8.7 原始栈数据 350
12.9 用户态转储文件 351
12.9.1 文件格式概览 351
12.9.2 数据流 352
12.9.3 产生转储文件 353
12.9.4 读取转储文件 354
12.9.5 利用转储文件分析问题 355
12.10 本章总结 357
参考文献 357
第13章 硬错误和蓝屏 359
13.1 硬错误提示 359
13.1.1 缺盘错误 360
13.1.2 NtRaiseHardError 360
13.1.3 ExpRaiseHardError 361
13.1.4 CSRSS中的分发过程 363
13.2 蓝屏终止(BSOD) 366
13.2.1 简介 367
13.2.2 发起和产生过程 368
13.2.3 诊断蓝屏错误 370
13.2.4 手工触发蓝屏 371
13.3 系统转储文件 371
13.3.1 分类 371
13.3.2 文件格式 372
13.3.3 产生方法 373
13.4 分析系统转储文件 374
13.4.1 初步分析 374
13.4.2 线程和栈回溯 375
13.4.3 陷阱帧 376
13.4.4 自动分析 377
13.5 辅助的错误提示方法 380
13.5.1 MessageBeep 380
13.5.2 Beep函数 383
13.5.3 闪动窗口 384
13.6 配置错误提示机制 384
13.6.1 SetErrorMode API 385
13.6.2 IoSetThreadHardErrorMode 387
13.6.3 蓝屏后自动重启 387
13.7 防止滥用错误提示机制 389
13.8 本章总结 390
参考文献 390
第14章 错误报告 391
14.1 WER 1.0 392
14.1.1 客户端 392
14.1.2 报告模式 393
14.1.3 传输方式 394
14.2 系统错误报告 395
14.3 WER服务器端 397
14.3.1 WER服务 398
14.3.2 错误报告分类方法 398
14.3.3 报告回应(Response) 399
14.4 WER 2.0 399
14.4.1 模块变化 399
14.4.2 创建报告 400
14.4.3 提交报告 401
14.4.4 典型应用 402
14.5 CER 403
14.6 本章总结 404
参考文献 404
第15章 日志 405
15.1 日志简介 405
15.2 ELF的架构 406
15.2.1 ELF的日志文件 407
15.2.2 事件源 408
15.2.3 ELF服务 409
15.3 ELF的数据组织 409
15.3.1 日志记录 409
15.3.2 添加日志记录 410
15.3.3 API一览 412
15.4 察看和使用ELF日志 413
15.5 CLFS的组成和原理 414
15.5.1 组成 414
15.5.2 存储结构 415
15.5.3 LSN 416
15.6 CLFS的使用方法 416
15.6.1 创建日志文件 416
15.6.2 添加CLFS容器 417
15.6.3 创建编组区 417
15.6.4 添加日志记录 418
15.6.5 读日志记录 418
15.6.6 查询信息 419
15.6.7 管理和备份 420
15.7 本章总结 420
参考文献 420
第16章 事件追踪 421
16.1 简介 421
16.2 ETW的架构 422
16.3 提供ETW消息 424
16.4 控制ETW会话 425
16.5 消耗ETW消息 427
16.6 格式描述 428
16.6.1 MOF文件 429
16.6.2 WPP 430
16.7 NT Kernel Logger 432
16.7.1 观察NKL的追踪事件 432
16.7.2 编写代码控制NKL 434
16.7.3 NKL的实现 435
16.8 Global Logger Session 436
16.8.1 启动GLS会话 436
16.8.2 配置GLS 437
16.8.3 在驱动程序中使用GLS 438
16.8.4 Autologgers 438
16.8.5 BootVis工具 439
16.9 Crimson API 440
16.9.1 发布事件 440
16.9.2 消耗事件 441
16.9.3 格式描述 441
16.9.4 收集和观察事件 442
16.9.5 Crimson API的实现 443
16.10 本章总结 443
参考文献 444
第17章 WHEA 445
17.1 目标和架构 445
17.1.1 目标 445
17.1.2 架构 446
17.1.3 PSHED 448
17.2 错误源 450
17.2.1 标准的错误源 450
17.2.2 通过ACPI表来定义错误源 450
17.2.3 通过PSHED插件来报告错误源 451
17.3 错误处理过程 452
17.3.1 WHEA_ERROR_PACKET结构 452
17.3.2 处理过程 454
17.3.3 WHEA_ERROR_RECORD结构 455
17.3.4 Firmware First Model 457
17.4 错误持久化 457
17.4.1 ERST 457
17.4.2 工作过程 458
17.5 注入错误 459
17.6 本章总结 459
参考文献 460
第18章 内核调试引擎 461
18.1 概览 462
18.1.1 KD 462
18.1.2 角色 462
18.1.3 组成 463
18.1.4 模块文件 464
18.1.5 版本差异 465
18.2 连接 465
18.2.1 串行口 466
18.2.2 1394 467
18.2.3 USB 2.0 470
18.2.4 管道 472
18.2.5 选择连接方式 473
18.2.6 解决连接问题 474
18.3 启用 475
18.3.1 BOOT.INI 475
18.3.2 BCD 476
18.3.3 高级启动选项 478
18.4 初始化 478
18.4.1 Windows启动过程概述 478
18.4.2 第一次调用KdInitSystem 481
18.4.3 第二次调用KdInitSystem 482
18.4.4 通信扩展模块的阶段1初始化 482
18.5 内核调试协议 483
18.5.1 数据包 483
18.5.2 报告状态变化 485
18.5.3 访问目标系统 486
18.5.4 恢复目标系统执行 488
18.5.5 版本 489
18.5.6 典型对话过程 490
18.5.7 KdTalker 492
18.6 与内核交互 492
18.6.1 中断到调试器 493
18.6.2 KdpSendWaitContinue 493
18.6.3 退出调试器 495
18.6.4 轮询中断包 495
18.6.5 接收和报告异常事件 496
18.6.6 调试服务 497
18.6.7 打印调试信息 499
18.6.8 加载调试符号 500
18.6.9 更新系统文件 501
18.7 建立和维持连接 502
18.7.1 最早的调试机会 502
18.7.2 初始断点 505
18.7.3 断开和重新建立连接 508
18.8 本地内核调试 509
18.8.1 LiveKD 509
18.8.2 Windows自己的本地内核调试支持 509
18.8.3 安全问题 511
18.9 本章总结 511
参考文献 512
第19章 Windows的验证机制 513
19.1 简介 514
19.1.1 驱动程序验证器 514
19.1.2 应用程序验证器 514
19.1.3 WHQL测试 515
19.2 驱动验证器的工作原理 515
19.2.1 设计原理 515
19.2.2 初始化 516
19.2.3 挂接验证函数 517
19.2.4 验证函数的执行过程 519
19.2.5 报告验证失败 520
19.3 使用驱动验证器 521
19.3.1 验证项目 521
19.3.2 启用驱动验证 522
19.3.3 开始验证 523
19.3.4 观察验证情况 524
19.3.5 WinDBG的扩展命令 525
19.4 应用程序验证器的工作原理 526
19.4.1 原理和组成 526
19.4.2 初始化 527
19.4.3 挂接API 529
19.4.4 验证函数的执行过程 531
19.4.5 报告验证失败 532
19.4.6 验证停顿 533
19.5 使用应用程序验证器 533
19.5.1 应用验证管理器 533
19.5.2 验证项目 535
19.5.3 配置验证属性 536
19.5.4 配置验证停顿 536
19.5.5 编程调用 536
19.5.6 调试扩展 537
19.6 本章总结 537
参考文献 538
第4篇 编译器的调试支持 539
第20章 编译和编译期检查 541
20.1 程序的构建过程 541
20.1.1 链接器 542
20.1.2 加载器 543
20.2 编译 543
20.2.1 前端 543
20.2.2 后端 544
20.3 Visual C++编译器 544
20.3.1 MSVC简史 544
20.3.2 MSVC6 545
20.3.3 VS7和VS8 546
20.3.4 构建程序 548
20.3.5 调试 549
20.4 编译错误和警告 549
20.4.1 错误ID和来源 549
20.4.2 编译警告 550
20.5 编译期检查 551
20.5.1 未初始化的局部变量 552
20.5.2 类型不匹配 553
20.5.3 使用编译器指令 553
20.5.4 标注 554
20.5.5 驱动程序静态验证器(SDV) 554
20.6 标准标注语言 555
20.6.1 缓冲区标注符 555
20.6.2 高级标注符 557
20.7 本章总结 558
参考文献 558
第21章 运行库和运行期检查 559
21.1 C/C++运行库 559
21.1.1 C运行库 560
21.1.2 C++标准库 561
21.2 链接运行库 562
21.2.1 静态链接和动态链接 563
21.2.2 Lib文件 564
21.3 运行库的初始化和清理 565
21.3.1 介入方法 565
21.3.2 初始化 566
21.3.3 多个运行库实例 568
21.4 运行期检查 569
21.4.1 自动的运行期检查 570
21.4.2 断言(ASSERT) 571
21.4.3 _RPT宏 574
21.5 报告运行期检查错误 574
21.5.1 _CrtDbgReport 574
21.5.2 _CrtSetReportMode 577
21.5.3 _CrtSetReportFile 578
21.5.4 _CrtSetReportHook 578
21.5.5 _CrtSetReportHook2 579
21.5.6 使用其他函数报告RTC错误 579
21.6 本章总结 580
参考文献 580
第22章 栈和函数调用 581
22.1 简介 581
22.1.1 用户态栈和内核态栈 582
22.1.2 函数、过程和方法 584
22.2 栈的创建过程 585
22.2.1 内核态栈的创建 585
22.2.2 用户态栈的创建 586
22.2.3 跟踪用户态栈的创建过程 588
22.3 CALL和RET指令 590
22.3.1 CALL指令 590
22.3.2 RET指令 591
22.3.3 观察函数调用和返回过程 591
22.3.4 跨特权级调用 594
22.4 局部变量和栈帧 595
22.4.1 局部变量的分配和释放 595
22.4.2 EBP寄存器和栈帧 598
22.4.3 帧指针和栈帧的遍历 602
22.5 帧指针省略(FPO) 604
22.6 栈指针检查 606
22.7 调用协定 609
22.7.1 C调用协定 609
22.7.2 标准调用协定 610
22.7.3 快速调用协定 610
22.7.4 This调用协定 611
22.7.5 CLR调用协定 612
22.7.6 X64调用协定 612
22.7.7 通过编译器开关改变默认调用协定 613
22.7.8 函数返回值 613
22.7.9 归纳和补充 615
22.8 栈空间的增长和溢出 616
22.8.1 栈空间的自动增长 617
22.8.2 栈溢出 617
22.8.3 分配检查 620
22.9 栈下溢 623
22.10 缓冲区溢出 624
22.10.1 感受缓冲区溢出 624
22.10.2 缓冲区溢出攻击 626
22.11 变量检查 628
22.12 基于Cookie的安全检查 636
22.12.1 安全Cookie的产生、植入和检查 636
22.12.2 报告安全检查失败 638
22.12.3 编写安全的代码 640
22.13 本章总结 642
参考文献 642
第23章 堆和堆检查 643
23.1 理解堆 644
23.2 堆的创建和销毁 646
23.2.1 进程的默认堆 646
23.2.2 创建私有堆 647
23.2.3 堆列表 648
23.2.4 销毁堆 648
23.3 分配和释放堆块 649
23.3.1 HeapAlloc 649
23.3.2 CRT分配函数 650
23.3.3 释放从堆中分配的内存 651
23.3.4 GlobalAlloc和LocalAlloc 652
23.3.5 解除提交 653
23.4 堆的内部结构 654
23.4.1 结构和布局 654
23.4.2 HEAP结构 656
23.4.3 HEAP_SEGMENT结构 657
23.4.4 HEAP_ENTRY结构 658
23.4.5 分析堆块的分配和释放过程 658
23.4.6 使用!heap命令观察堆块信息 660
23.5 低碎片堆(LFH) 661
23.6 堆的调试支持 662
23.6.1 全局标志 662
23.6.2 释放检查 663
23.7 栈回溯数据库 666
23.7.1 工作原理 666
23.7.2 DH和UMDH工具 668
23.7.3 定位内存泄漏 668
23.8 堆溢出和检测 670
23.8.1 堆缓冲区溢出 670
23.8.2 调用时验证 673
23.8.3 堆尾检查(Tail Check) 674
23.9 页堆 677
23.9.1 总体结构 677
23.9.2 启用和观察页堆 678
23.9.3 堆块结构 679
23.9.4 检测溢出 682
23.10 准页堆 683
23.10.1 启用准页堆 683
23.10.2 结构布局 684
23.10.3 检测溢出 687
23.11 CRT堆 688
23.11.1 CRT堆的三种模式 688
23.11.2 SBH简介 689
23.11.3 创建和选择模式 690
23.11.4 CRT堆的终止 691
23.12 CRT堆的调试堆块 692
23.12.1 _CrtMemBlockHeader结构 692
23.12.2 块类型 693
23.12.3 分配堆块 694
23.13 CRT堆的调试功能 698
23.13.1 内存分配序号断点 698
23.13.2 分配挂钩 699
23.13.3 自动和手动检查 699
23.14 堆块转储 700
23.14.1 内存状态和检查点 700
23.14.2 _CrtMemDumpAllObjectsSince 701
23.14.3 转储挂钩 702
23.15 泄漏转储 704
23.15.1 _CrtDumpMemoryLeaks 704
23.15.2 何时调用 705
23.15.3 定位导致泄漏的源代码 706
23.16 本章总结 709
参考文献 710
第24章 异常处理代码的编译 711
24.1 概览 711
24.2 FS:[0]链条 713
24.2.1 TEB和TIB结构 713
24.2.2 ExceptionList字段 714
24.2.3 登记异常处理器 715
24.3 遍历FS:[0]链条 716
24.3.1 RtlDispatchException 716
24.3.2 KiUserExceptionDispatcher 720
24.4 执行异常处理函数 721
24.4.1 SehRaw实例 721
24.4.2 执行异常处理函数 722
24.5 __try{}__except()结构 724
24.5.1 与手工方法的对应关系 724
24.5.2 __try{}__except()结构的编译 725
24.5.3 范围表 727
24.5.4 TryLevel 728
24.5.5 __try{}__except()结构的执行 730
24.5.6 _SEH_prolog和_SEH_epilog 731
24.6 安全问题 732
24.6.1 安全Cookie 732
24.6.2 SAFESEH 735
24.6.3 基于表的异常处理 737
24.7 本章总结 737
参考文献 738
第25章 调试符号 739
25.1 名称修饰 739
25.1.1 C和C++ 740
25.1.2 C的名称修饰规则 741
25.1.3 C++的名称修饰规则 741
25.2 调试信息的存储格式 742
25.2.1 COFF格式 742
25.2.2 CodeView(CV)格式 743
25.2.3 PDB格式 744
25.2.4 DWARF格式 745
25.3 目标文件中的调试信息 745
25.3.1 IMAGE_FILE_HEADER结构 746
25.3.2 IMAGE_SECTION_HEADER结构 747
25.3.3 节的重定位信息和行号信息 747
25.3.4 存储调试数据的节 748
25.3.5 调试符号表 750
25.3.6 COFF字符串表 751
25.3.7 COFF符号例析 751
25.4 PE文件中的调试信息 753
25.4.1 PE文件布局 753
25.4.2 IMAGE_OPTIONAL_HEADER结构 755
25.4.3 调试数据目录 757
25.4.4 调试数据 758
25.4.5 使用WinDBG观察PE文件中的调试信息 760
25.4.6 调试信息的产生过程 761
25.5 DBG文件 762
25.5.1 从PE文件产生DBG文件 762
25.5.2 DBG文件的布局 762
25.6 PDB文件 764
25.6.1 复合文件 764
25.6.2 PDB文件布局 765
25.6.3 PDB签名 765
25.6.4 Magic代码 766
25.6.5 PDB_HEADER 767
25.6.6 根数据流——流目录 768
25.6.7 页分配表 768
25.6.8 访问PDB文件的方式 769
25.6.9 PDB文件的产生过程 770
25.7 有关的编译和链接选项 771
25.7.1 控制调试信息的编译选项 771
25.7.2 控制调试信息的链接选项 771
25.7.3 不同链接和编译选项的比较 773
25.8 PDB文件中的数据表 775
25.8.1 符号表 775
25.8.2 源文件表 777
25.8.3 节贡献表 777
25.8.4 段信息表 778
25.8.5 注入源代码表 778
25.8.6 帧数据表 779
25.9 本章总结 780
参考文献 780
第5篇 可调试性 781
第26章 可调试性概览 783
26.1 简介 783
26.2 Showstopper和未雨绸缪 784
26.2.1 NT 3.1的故事 784
26.2.2 未雨绸缪 786
26.3 基本原则 787
26.3.1 最短距离原则 787
26.3.2 最小范围原则 788
26.3.3 立刻终止原则 788
26.3.4 可追溯原则 789
26.3.5 可控制原则 790
26.3.6 可重复原则 791
26.3.7 可观察原则 791
26.3.8 可辨识原则 792
26.4 不可调试代码 792
26.4.1 系统的异常分发函数 792
26.4.2 提供调试功能的系统函数 793
26.4.3 对调试器敏感的函数 793
26.4.4 反跟踪和调试的程序 794
26.4.5 时间敏感的代码 794
26.4.6 应对措施 794
26.5 可调试性例析 794
26.5.1 Sanity Check和BSOD 795
26.5.2 可控制性 796
26.5.3 公开的符号文件 796
26.5.4 WER 797
26.5.5 ETW和日志 797
26.5.6 性能计数器 797
26.5.7 内建的内核调试引擎 798
26.5.8 手动触发崩溃 798
26.6 与安全、性能和商业秘密的关系 798
26.6.1 可调试性与安全 798
26.6.2 可调试性与商业秘密 799
26.6.3 可调试性与性能 799
26.7 本章总结 799
参考文献 800
第27章 可调试性的实现 801
27.1 角色和职责 801
27.1.1 架构师 801
27.1.2 程序员 802
27.1.3 测试人员 802
27.1.4 产品维护和技术支持工程师 803
27.1.5 管理者 804
27.2 可调试架构 804
27.2.1 日志 805
27.2.2 输出调试信息 805
27.2.3 转储(Dump) 806
27.2.4 基类 807
27.2.5 调试模型 808
27.3 通过栈回溯实现可追溯性 808
27.3.1 栈回溯的基本原理 809
27.3.2 利用DBGHELP函数回溯栈 810
27.3.3 利用RTL函数回溯栈 814
27.4 数据的可追溯性 815
27.4.1 基于数据断点的方法 816
27.4.2 使用对象封装技术来追踪数据变化 820
27.5 可观察性的实现 821
27.5.1 状态查询 821
27.5.2 WMI 822
27.5.3 性能计数器 824
27.5.4 转储(Dump) 827
27.5.5 打印或者输出调试信息 828
27.5.6 日志 829
27.6 自检和自动报告 830
27.6.1 BIST 830
27.6.2 软件自检 830
27.6.3 自动报告 831
27.7 本章总结 832
参考文献 832
第6篇 调试器 833
第28章 调试器概览 835
28.1 TX-0计算机和FLIT调试器 835
28.2 小型机和DDT调试器 837
28.2.1 PDP-1 837
28.2.2 TOPS-10操作系统和DDT-10 839
28.3 个人计算机和它的调试器 841
28.3.1 8086 Monitor 841
28.3.2 SYMDEB 842
28.3.3 CodeView调试器 842
28.3.4 Turbo Debugger 843
28.3.5 SoftICE 844
28.4 调试器的功能 845
28.4.1 建立和终止调试会话 845
28.4.2 控制被调试程序执行 845
28.4.3 访问内存 846
28.4.4 访问寄存器 846
28.4.5 断点(Breakpoints) 847
28.4.6 跟踪执行(Tracing) 849
28.4.7 观察栈和栈回溯 850
28.4.8 汇编和反汇编 850
28.4.9 源代码级调试 850
28.4.10 EnC 850
28.4.11 文件管理 851
28.4.12 接收和显示调试信息 851
28.4.13 转储(Dump) 851
28.5 分类标准 852
28.5.1 特权级别 852
28.5.2 操作系统 852
28.5.3 执行方式 852
28.5.4 处理器架构 853
28.5.5 编程语言 853
28.6 实现模型 853
28.6.1 海森伯效应 853
28.6.2 进程内调试模型 854
28.6.3 进程外调试模型 855
28.6.4 混合调试模型 856
28.6.5 内核调试模型 857
28.7 经典架构 859
28.7.1 基本单元 859
28.7.2 远程调试 860
28.7.3 多语言和多处理器架构调试 861
28.8 HPD标准 862
28.8.1 HPD标准简介 862
28.8.2 动作点 863
28.8.3 进程和线程的表示和命名 863
28.8.4 命令 864
28.9 本章总结 866
参考文献 866
第29章 WinDBG及其实现 867
29.1 WinDBG溯源 867
29.1.1 KD和NTSD诞生 868
29.1.2 WinDBG诞生 868
29.1.3 发行方式 869
29.1.4 版本历史 872
29.2 C阶段的架构 872
29.2.1 功能模块 872
29.2.2 远程调试 873
29.3 重构 875
29.3.1 版本历史 875
29.3.2 界面变化 877
29.3.3 模块变化 878
29.3.4 发布方式和NTSD问题 879
29.3.5 文件 879
29.4 调试器引擎的架构 881
29.4.1 概览 882
29.4.2 对外接口 883
29.4.3 DebugClient类 884
29.4.4 中间层 885
29.4.5 服务层 886
29.4.6 传输和连接层 886
29.5 调试目标 887
29.5.1 TargetInfo类 888
29.5.2 用户态目标 890
29.5.3 内核态目标 890
29.5.4 转储文件目标 891
29.6 调试会话 892
29.6.1 建立调试会话 892
29.6.2 调试循环 894
29.6.3 等待和处理调试事件 895
29.6.4 继续调试事件 897
29.6.5 结束调试会话 898
29.7 接收和处理命令 899
29.7.1 调试器的两种工作状态 900
29.7.2 进入命令状态 900
29.7.3 执行命令 902
29.7.4 结束命令状态 903
29.8 本章总结 904
参考文献 904
第30章 WinDBG用法详解 905
30.1 工作空间 905
30.2 命令概览 908
30.2.1 标准命令 908
30.2.2 元命令 909
30.2.3 扩展命令 910
30.3 用户界面 911
30.3.1 窗口概览 911
30.3.2 命令窗口和命令提示符 913
30.4 输入和执行命令 916
30.4.1 要点 916
30.4.2 表达式 917
30.4.3 伪寄存器 919
30.4.4 别名 920
30.4.5 循环和条件执行 921
30.4.6 进程和线程限定符 922
30.4.7 记录到文件 923
30.5 建立调试会话 923
30.5.1 附加到已经运行的进程 923
30.5.2 创建并调试新的进程 924
30.5.3 非入侵式调试 925
30.5.4 调试内核目标 925
30.5.5 本地内核调试 926
30.5.6 调试转储文件 926
30.5.7 远程调试 927
30.6 终止调试会话 927
30.6.1 停止调试 928
30.6.2 分离调试目标 928
30.6.3 抛弃被调试进程 928
30.6.4 杀死被调试进程 929
30.6.5 调试器终止或僵死 929
30.6.6 重新开始调试 929
30.7 理解上下文 930
30.7.1 登录会话上下文 930
30.7.2 进程上下文 931
30.7.3 寄存器上下文 931
30.7.4 局部(变量)上下文 932
30.8 调试符号 933
30.8.1 重要意义 933
30.8.2 符号搜索路径 934
30.8.3 符号服务器 935
30.8.4 加载符号文件 936
30.8.5 观察模块信息 938
30.8.6 检查符号 940
30.8.7 搜索符号 942
30.8.8 设置符号选项 942
30.8.9 加载不严格匹配的符号文件 943
30.9 事件处理 944
30.9.1 调试事件与异常的关系 944
30.9.2 两轮机会 945
30.9.3 定制事件处理方式 946
30.9.4 GH和GN命令 949
30.9.5 实验 949
30.10 控制调试目标 951
30.10.1 初始断点 951
30.10.2 俘获调试目标 952
30.10.3 继续运行 954
30.11 单步执行 955
30.11.1 概览 955
30.11.2 单步执行到指定地址 957
30.11.3 单步执行到下一个函数调用 958
30.11.4 单步执行到下一分支 958
30.11.5 追踪并监视 959
30.11.6 程序指针飞跃 961
30.11.7 归纳 961
30.12 使用断点 962
30.12.1 软件断点 962
30.12.2 硬件断点 964
30.12.3 条件断点 965
30.12.4 地址表达方法 967
30.12.5 设置针对线程的断点 968
30.12.6 管理断点 968
30.13 控制进程和线程 969
30.13.1 MulThrds程序 969
30.13.2 控制线程执行 970
30.13.3 多进程调试 972
30.14 观察栈 973
30.14.1 显示栈回溯 973
30.14.2 观察栈变量 976
30.15 分析内存 978
30.15.1 显示内存区域 978
30.15.2 显示字符串 979
30.15.3 显示数据类型 980
30.15.4 搜索内存 981
30.15.5 修改内存 982
30.15.6 使用物理内存地址 984
30.15.7 观察内存属性 984
30.16 遍历链表 987
30.16.1 结构定义 987
30.16.2 双向链表示例 988
30.16.3 单向链表示例 989
30.16.4 Dl命令 990
30.16.5 !list命令 991
30.17 调用目标程序的函数 992
30.17.1 调用示例 992
30.17.2 工作原理 992
30.17.3 限制条件和常见错误 994
30.18 命令程序 994
30.18.1 流程控制符号 994
30.18.2 变量 995
30.18.3 命令程序示例 995
30.18.4 执行命令程序 997
30.19 本章总结 997
参考文献 998
附录A 示例程序列表 999
附录B WinDBG标准命令列表 1001
索引 1003
|