XCrashReport:异常处理和崩溃报告——第二部分






4.93/5 (32投票s)
为您的应用程序添加基本的异常处理和崩溃报告
引言
在第一部分中,我讨论了调试已投入生产或已交付给客户的程序时遇到的问题。在开发人员的工作站上,程序可能工作正常,但在客户的系统上,会出现随机崩溃。要解决这个问题,你需要知道程序在哪里崩溃,在第一部分中我讨论了一种确定崩溃位置的方法。但是如果这还不够怎么办?有时,了解你是如何到达崩溃点的会很有帮助 - 换句话说,你需要查看调用堆栈。在本文中,我将描述一种简单的方法,使调用堆栈显示出来。
下一步:深入堆栈
正如你在第一部分中看到的ERRORLOG.TXT文件列表中,堆栈已被转储并且可以检查。以目前的形式,分析堆栈转储将非常繁琐。在没有任何外部干预或第三方工具的情况下,我们能做些什么来简化堆栈分析吗?
答案是肯定的,如果你愿意对源代码进行一些小的修改。这些类型的修改称为源代码插桩,各种工具使用插桩来达到诸如确定测试覆盖率、测量性能瓶颈等目的。在我们的例子中,我们将对源代码进行插桩,以使堆栈转储更具可读性。
准备工作
我们使用与 ATL 字符串转换宏(如 T2A
)相同的技术。USES_CONVERSION
宏使用 _alloca
在堆栈上分配字节,然后在函数退出时自动释放这些字节。我们使用 FUNCTION_STACK_TRACE
宏也做了同样的事情
#define FUNCTION_STACK_TRACE(x) \ char * function_stack_trace_buffer = (char *) _alloca(8); \ CopyMemory(function_stack_trace_buffer, x, 8);
这会将 8 个字节从 x 复制到堆栈。但这 8 个字节是什么?我们选择约定前 4 个字节表示数字模块代码,后 4 个字节表示 alpha 函数代码。所以 Test2 程序看起来像
void CTest2Dlg::OnButton1() { FUNCTION_STACK_TRACE("0001AAAA"); Func1(1); } void CTest2Dlg::Func1(int n) { FUNCTION_STACK_TRACE("0001AAAB"); Func2(n); } void CTest2Dlg::Func2(int n) { FUNCTION_STACK_TRACE("0001AAAC"); Func3(n); } void CTest2Dlg::Func3(int n) { FUNCTION_STACK_TRACE("0001AAAD"); int *p = 0; if (n) *p = n; }
在本示例中,前四个字节始终为 0001
,因为这些函数都在同一个模块中。
理论付诸实践
这是 Test2.exe 的 ERRORLOG.TXT 的样子
Test2 caused an Access Violation (0xc0000005)
in module Test2.exe at 001b:00402da7.
Exception handler called in ExceptionAttacher.cpp - AfxWinMain.
Error occurred at 10/18/2003 21:06:56.
D:\temp1\XCrashReportTest\1.1\Test2\Release\Test2.exe, run by hdietrich.
Operating system: Windows XP (5.1.2600).
1 processor(s), type 586.
30% memory in use.
1024 MBytes physical memory.
712 MBytes physical memory free.
2462 MBytes paging file.
2221 MBytes paging file free.
2048 MBytes user address space.
2033 MBytes user address space free.
Write to location 00000000 caused an access violation.
Context:
EDI: 0x0012fe70 ESI: 0x0012fe70 EAX: 0x00000001
EBX: 0x00000001 ECX: 0x31303030 EDX: 0x44414141
EIP: 0x00402da7 EBP: 0x0012f7d4 SegCs: 0x0000001b
EFlags: 0x00010202 ESP: 0x0012f7cc SegSs: 0x00000023
Bytes at CS:EIP:
a3 00 00 00 00 8d 65 00 5d c2 04 00 90 90 90 90
Stack:
0x0012f7cc: 31303030 44414141 0012f7ec 00402d6e 0001AAAD....n-@.
0x0012f7dc: 00000001 31303030 43414141 0012fe70 ....0001AAACp...
0x0012f7ec: 0012f804 00402d2e 00000001 31303030 .....-@.....0001
0x0012f7fc: 42414141 0012fe70 0012f81c 00402cec AAABp........,@.
0x0012f80c: 00000001 31303030 41414141 004043c0 ....0001AAAA.C@.
0x0012f81c: 0012f82c 73dd23d8 004043c0 00000111 ,....#.s.C@.....
0x0012f82c: 0012f85c 73dd22ae 0012fe70 000003e8 \....".sp.......
0x0012f83c: 00000000 00402cc0 00000000 0000000c .....,@.........
0x0012f84c: 00000000 00000000 0012fe70 000003e8 ........p.......
0x0012f85c: 0012f880 73dd8fc5 000003e8 00000000 .......s........
0x0012f86c: 00000000 00000000 000003e8 0012fe70 ............p...
.
.
.
堆栈转储显示序列:0001AAAA、0001AAAB、0001AAAC,最后是 0001AAAD。即使不查看指令指针,也很清楚程序在哪里崩溃。
摘要
这是一种在事后分析中使用堆栈转储的简单方法。我们将可识别的标记写入堆栈,这些标记引导我们通过调用堆栈。
存在一些缺点。首先,必须修改源代码。这可能可以使用 DevStudio 宏半自动化,甚至可以使用外部工具完全自动化。其次,你必须跟踪所有模块和函数代码。第三,你必须有自律性,在新源代码中继续这样做。第四,当你调用没有源代码的第三方库时,此技术毫无用处。
在第 3 部分中,我将向你展示我是如何克服这些缺点的。
修订历史
版本 1.1 - 2003 年 10 月 19 日
- 首次公开发布
用法
此软件已发布到公共领域。你可以随意使用它,但你不得出售此源代码。如果你修改或扩展它,请考虑在此处发布新代码以供大家分享。此软件按“原样”提供,不提供任何明示或暗示的保证。对于此软件可能造成的任何损害或业务损失,我概不负责。