返回   cpper编程论坛 > 技术杂烩
注册账号 论坛帮助 会员列表 日历事件 搜索 今日新帖 标记版面已读

技术杂烩 找不到地方的技术问题?这里!

回复
 
LinkBack 主题工具 显示模式
  #1 (permalink)  
旧 2005-03-02
Innocentius 的头像
版主
 
注册日期: 2002-09-11
住址: 上海
帖子: 562
文章: 12
Innocentius 正向着好的方向发展
发送 MSN 消息给 Innocentius
默认 如何进行事后调试【连载】

为了重新振兴ABP,我打算在这里连载关于事后调试的文章,因为没有这么多时间来写,所以只好一段一段地贴。大家有什么意见的问题请跟贴。谢谢。

―――――――――――――――――――――――――――――――――――――――

你写了一个程序,很开心地把它发布给用户。用户满心欢喜地运行它,突然Windows弹出了一个熟悉的窗口:

Application Error:
The instruction at “0x00411a28” referenced memory at “0x12345678”. The memory could not be “written”.
Click on OK to terminate the program.
Click CANCEL to debug program.

顿时用户的心中升起对你的无比仇恨,然而你就会收到用户愤怒的电话,并且知道了Windows的这个几乎没用的信息。

在这个信息中,你知道出现了一个内存访问错误,而且Windows也告诉你了这个错误发生在0x00411a28这个地方……但是,但是,等一下,即使你精通C++,即使你精通汇编语言,即使你精通计算机原理,你还是不知道这个0x00411a28表示什么,不是吗?在客户的计算机上没有调试器,即使有调试器也没有调试信息,即使有调试信息你也不可能到客户的机器上去调试,这该怎么办呢?

这是一种很常见并且很尴尬的情况。那怎么办呢?我们的第一反应通常就是在我们的开发机上设法重现这个错误,然而实际情况并不是很理想,因为客户机的软硬件环境和开发机的软硬件环境可能有很大的差别,客户机的运行数据和开发机的数据也有很大差别,这些都导致了错误很难重现。

为了避免这种情况,你需要学会事后调试,进一步的,如果能够让你的程序自己能够提供一些调试所必须的信息则更好。

让我们首先来看一下我们实际上需要的是什么:

Windows告诉了你一个地址:0x00411a28,你最想知道的是这个地址对应的是哪一句源代码,然后想知道在这个时刻的计算机运行的上下文信息,包括当时的堆栈、变量以及寄存器的值。让我们一个一个来解决:

第一个是源代码,我们希望能够知道的是问题发生在哪一个源码文件的第几行,退而求其次的是希望知道问题发生在哪个函数里。这就需要有一个源代码和目标代码对应的关系表,但是谁知道这个关系表呢?显然,对这件事情最清楚的就是编译程序了,毕竟是它把源代码翻译成机器指令的。

【待续,本文属 Innocentius 原创,转载请注明出处】
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #2 (permalink)  
旧 2005-03-02
zweily 的头像
版主
 
注册日期: 2002-09-24
帖子: 425
文章: 10
zweily 正向着好的方向发展
默认

支持一下,这里的确需要有人来带头重振了,呵呵
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #3 (permalink)  
旧 2005-03-02
普通会员
 
注册日期: 2005-02-19
帖子: 35
madeinhell 正向着好的方向发展
默认

我觉得应该是在发布时保留map文件(最好还有list文件),其实应该在发布时把当时的所有资源和编译文件都作一个备份。
这样的map文件就可以被用来找到内存对应的函数/数据,但是并不是百分百的可以捕捉。
不过我觉得最好还是像excel这样的,有一个全局捕捉功能,让用户看到这样的出错消息总不是很好。
期待楼住的进一步介绍。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #4 (permalink)  
旧 2005-03-02
Innocentius 的头像
版主
 
注册日期: 2002-09-11
住址: 上海
帖子: 562
文章: 12
Innocentius 正向着好的方向发展
发送 MSN 消息给 Innocentius
默认

显然Visual C++的调试程序是知道这个对应关系的,否则它怎么显示正在执行到源码的什么地方?C++编译器在生成目标代码的时候同时还会生成很多调试信息,这些调试信息是包含在OBJ文件中,然后由连接程序把这些调试信息整合起来成为一个调试文件,最典型的就是PDB文件。在这个文件中包含了和程序相关的所有信息,包括源代码和目标代码的对应行号、类型、变量、函数等等。那太好了,有了这个文件我们就可以知道那个该死的地址对应于什么地方了。

但是,等一下,这个文件是 Microsoft 的专有文件格式,我们对它是如何组织的无从得知,这该如何是好呢?先不要着急,Microsoft也不是这么绝情,它提供了一个动态链接库叫做 DBGHELP.DLL,通过这个库我们就可以访问PDB文件了。然而这个库使用起来并不简单,需要编写一个程序,我们现在是火烧眉毛,来不及做这件事情了,容后续再说。

连接程序另外还会生成一个MAP文件,增加下面的命令行参数可以让LINK产生这个文件:

LINK /MAP:filename.map /MAPINFO:LINES …

这个命令行参数告诉LINK,产生一个名字为filename.map的文件,并且这个文件包含行号信息。这两个参数必须在连接你的程序时指定。有了这个文件以后我们可以来查找源文件行号和地址的对应关系了。注意,使用这个选项需要配合 /INCREMENTAL:NO 选项使用。

下面是一个简单的例子,假设有下面这个简单的C++程序:

代码:
1 void func() 2 { 3 int *p=0; 4 *p=0; 5 } 6 7 int main() 8 { 9 func(); 10 return 0; 11 } 12
显然,在第4行应该出现一个内存访问错误,运行这个程序,出现了下面这个信息:

Test.exe – Application Error
The instruction at “0x00401028” referenced memory at “0x00000000”. The memory could not be “written”.

这里报告了一个错误,地址是0x00401028。接下来让我们来看看MAP文件。文件很大,我们只看其中的一部分。

代码:
test Timestamp is 42257ef9 (Wed Mar 02 16:53:13 2005) Preferred load address is 00400000 Start Length Name Class 0001:00000000 0000d886H .text CODE 0002:00000000 000000fcH .idata$5 DATA 0002:00000100 00001f6bH .rdata DATA 0002:0000206c 00000040H .rdata$debug DATA … Address Publics by Value Rva+Base Lib:Object 0000:00000000 ___safe_se_handler_table 00000000 <absolute> 0000:00000000 __except_list 00000000 <absolute> 0000:00000000 ___safe_se_handler_count 00000000 <absolute> 0001:00000000 ?func@@YAXXZ 00401000 f test.obj 0001:00000040 _main 00401040 f test.obj 0001:00000080 __RTC_InitBase 00401080 f LIBCD:init.obj 0001:000000b0 __RTC_Shutdown 004010b0 f LIBCD:init.obj 0001:000000d0 __RTC_CheckEsp 004010d0 f LIBCD:stack.obj … Line numbers for .\debug\test.obj(d:\projects\private\test\test.cpp) segment .text 2 0001:00000000 3 0001:0000001e 4 0001:00000025 5 0001:0000002e 8 0001:00000040 9 0001:0000005e 10 0001:00000063 11 0001:00000065
一个MAP文件被分成这么几个部分:

代码:
test
这表示这个MAP文件的模块名称,虽然这里看不出什么用途,但是这一点实际上很重要,我们后面就会看到。

代码:
Timestamp is 42257ef9 (Wed Mar 02 16:53:13 2005)
这是文件的时间戳,这个时间戳并不是文件日期,而是保存在 EXE 文件内部的一个时间戳,通过这个时间戳可以用于确定MAP文件和EXE是对应的。

代码:
Preferred load address is 00400000
这是最佳载入地址,对于EXE来说通常都是 0x00400000,但是对于DLL来说可能实际的载入地址是不同的。这个基地址对于后面的计算很重要。

代码:
Start Length Name Class 0001:00000000 0000d886H .text CODE 0002:00000000 000000fcH .idata$5 DATA 0002:00000100 00001f6bH .rdata DATA 0002:0000206c 00000040H .rdata$debug DATA
这里是段表,我们目前关心的是 .text 段。这一段中包含了程序的实际代码。上面的数据表示,第一个段就是.text段(0001段),它的起始地址是0x00000000,长度是0xd886。按照x86的规定,一个段地址乘上0x10才是实际地址,因此实际地址是从0到0xd8860。由于Windows的PE文件就是内存映像,而PE文件有0x1000字节的头部,因此第一段的起始地址是在PE文件的0x1000处。如果PE文件被装入到0x400000地址处,那么第一段的实际地址应该在0x401000处。

【待续,本文属 Innocentius 原创,转载请注明出处】
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #5 (permalink)  
旧 2005-03-03
jinfeng_wang 的头像
高级会员
 
注册日期: 2002-11-14
帖子: 117
jinfeng_wang 正向着好的方向发展
默认

呵呵,不得不支持一下。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #6 (permalink)  
旧 2005-03-03
高级会员
 
注册日期: 2003-05-26
帖子: 146
wangwh 正向着好的方向发展
发送 MSN 消息给 wangwh
默认

支持,小弟也会写文章以资支持!
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #7 (permalink)  
旧 2005-03-03
Innocentius 的头像
版主
 
注册日期: 2002-09-11
住址: 上海
帖子: 562
文章: 12
Innocentius 正向着好的方向发展
发送 MSN 消息给 Innocentius
默认

代码:
Address Publics by Value Rva+Base Lib:Object 0000:00000000 ___safe_se_handler_table 00000000 <absolute> 0000:00000000 __except_list 00000000 <absolute> 0000:00000000 ___safe_se_handler_count 00000000 <absolute> 0001:00000000 ?func@@YAXXZ 00401000 f test.obj 0001:00000040 _main 00401040 f test.obj 0001:00000080 __RTC_InitBase 00401080 f LIBCD:init.obj 0001:000000b0 __RTC_Shutdown 004010b0 f LIBCD:init.obj 0001:000000d0 __RTC_CheckEsp 004010d0 f LIBCD:stack.obj
这是公共符号表,在这个表中将列出所有公共符号的地址和名称,所谓公共符号就是在汇编语言中声明为PUBLIC的符号,也就是在其它汇编文件中可以通过EXTERN得到的符号名称。对于C++来说,如果一个全局变量、常量和函数没有被声明为static,那么它就自动声明为公共符号。

这个公共符号表是按照地址顺序排列的,第一列是Address,是这个符号所在的地址,以 段号:偏移 地址的形式表示,段号根据前面段表确定,偏移地址表示在这个段中的位置。这意味着如果我们要知道这个符号的确切地址,则需要知道段的首地址,然后加上偏移地址,段的首地址根据段表确定。

第二列是 Publics by Value,是公共符号的名称,也就是我们一般意义上的变量名、常量名以及函数名。这里需要注意的是,在这里列出来的名字是经过修饰的名字,例如我们写的func()函数实际上的名字是?func@@YAXXZ,main函数的实际名字是_main。关于这一点我们会在后面再详细讨论的。

第三列是Rva+Base,表示对象的实际地址。对于Visual C++ 6.0以后的LINK会在MAP文件里面列出这个字段,但是较早的版本以及其它软件开发商,比如Borland的连接程序则没有列出这个字段,因此我们需要知道一下这个字段是怎么得到的。RVA是“相对虚拟地址”,前面已经说过,EXE文件就是程序的内存映像,它和在内存中程序的保存形式是完全一样的,因此在程序中的所有使用地址的地方都应该确定下来。然而由于EXE和DLL可能被装入到内存的任意地方,在编译时不会知道最终的地址是什么,因此只能将程序中所有使用地址的地方用一个相对于这个EXE文件的头部的形式表示,这个地址形式称为RVA。实际地址则是由RVA加上装入EXE或DLL文件时的基地址得到的(为了提高装入程序的性能,实际上LINK会把它希望的实际地址保存在EXE和DLL文件中,也就是把RVA加上前面所提到的默认装入地址(Preferred load address),如果实际的装入地址和默认装入地址相同,那么装入程序就可以省去一次重定位的过程,使得装入速度有所提高,这对于EXE来说通常都是可行的,然而对于DLL一般来说做不到。然而你可以在LINK的时候指定DLL的默认装入地址,这样可以提高DLL的装入速度,也可以使用REBASE实用程序改变一个现有DLL的默认装入地址)。

我们来看一下main函数。main函数的公共名字是_main,它所在的地址是0001:00000040,它所在的段是0001,从前面的段表可以查到它是第一个段,起始地址是0x00000000,而我们前面提到过,第一个段的起始地址实际上距离EXE文件的头部是0x1000,因此这个段的实际开始地址是0x1000,加上段内偏移地址0x40,那么可以得到_main的RVA是0x1040,再加上这个模块的默认装入地址0x400000,那么可以得到结果是0x401040,也就是第三列看到的Rva+Base的值。

第四列Lib:Object是这个符号所在的OBJ文件,我们知道OBJ文件和CPP文件基本上是一一对应的,因此通过这个信息可以知道对应的CPP文件是什么。

代码:
Line numbers for .\debug\test.obj(d:\projects\private\test\test.cpp) segment .text 2 0001:00000000 3 0001:0000001e 4 0001:00000025 5 0001:0000002e 8 0001:00000040 9 0001:0000005e 10 0001:00000063 11 0001:00000065
最后一部分是行号信息。第一句话表示源文件名,以及这个文件中哪个段的行号信息是包含在下面的列表中的。上面的例子可以看到文件名是 .\debug\test.obj 和 d:\projects\private\test\test.cpp,段是 .text。如果一个文件有好几个段,那么它可能被分布在不同的行号信息列表中。接下来的部分就是行号信息,第一个数字是行号,第二个地址是对应的段地址。这里就表示第8行对应于地址0001:00000040,就是刚才我们看到的main函数的地址,也就是源代码中main函数的开始地方。

好了,有了上面的知识,我们再来看地址0x00401028表示什么信息。

这个地址是一个绝对内存地址,而行号信息中只有段偏移地址,我们需要做一个转换才能完成这件事情。首先把0x00401028减去基地址0x00400000,得到RVA0x1028,然后减去EXE文件头的0x1000,得到0x28,然而我们查段表,发现0001段从0x00000000开始,长度是0xd886,因此0x28肯定就包含在0001段内,这样我们就可以得到绝对地址0x00401028的段偏移地址是0001:00000028。然后我们搜索公共符号表,发现?func@@YAXXZ函数的段地址从0001:00000000到0001:00000040,那么0001:00000028就包含在这个地址范围内,因此我们可以确定,这个错误地址是属于func函数的。进一步的,我们查行号表,看到在test.cpp文件中,第4行的地址是0001:00000025,第5行的地址是0001:0000002e,那么这说明0001:00000028地址应该位于由test.cpp的第4行源代码生成的机器指令之中(我们应该知道一行C++程序通常会生成好几条机器指令,因此错误地址很可能没有和行号对准)。

一般来说到这一步我们就能知道问题出现在什么地方了,如果需要更加详细的信息,那么我们可以继续看C++编译器生成的汇编语言文件。下面是这个文件的片断:
代码:
_TEXT SEGMENT _p$ = -8 ; size = 4 ?func@@YAXXZ PROC NEAR ; func, COMDAT ; 2 : { 00000 55 push ebp 00001 8b ec mov ebp, esp 00003 81 ec cc 00 00 00 sub esp, 204 ; 000000ccH 00009 53 push ebx 0000a 56 push esi 0000b 57 push edi 0000c 8d bd 34 ff ff ff lea edi, DWORD PTR [ebp-204] 00012 b9 33 00 00 00 mov ecx, 51 ; 00000033H 00017 b8 cc cc cc cc mov eax, -858993460 ; ccccccccH 0001c f3 ab rep stosd ; 3 : int *p=0; 0001e c7 45 f8 00 00 00 00 mov DWORD PTR _p$[ebp], 0 ; 4 : *p=0; 00025 8b 45 f8 mov eax, DWORD PTR _p$[ebp] 00028 c7 00 00 00 00 00 mov DWORD PTR [eax], 0 ; 5 : } 0002e 5f pop edi 0002f 5e pop esi 00030 5b pop ebx 00031 8b e5 mov esp, ebp 00033 5d pop ebp 00034 c3 ret 0 ?func@@YAXXZ ENDP ; func _TEXT ENDS
在这个文件中我们可以找到具体错误是发生在哪一条指令中的。需要注意的是C++生成的汇编语言文件中都以函数开始作为偏移基准,因此还需要把一个段偏移地址转换为相对于函数开始的偏移地址,方法是在公共符号表中找到这个函数,然后把段偏移地址减去这个函数的开始段偏移地址就可以了。在我们的这个例子中函数的段偏移地址是0001:00000000,因此函数内的偏移地址和它的段偏移地址是一样的。

在这里我们可以找到地址0x0028的指令是 mov DWORD PTR [eax], 0,这条指令表示把数值0写入由eax寄存器所保存的地址中去。而eax寄存器保存的地址在前一条指令中赋值:mov eax, DWORD PTR _p$[ebp],这里ebp是当前函数的栈帧基址寄存器,_p$被定义为-8,表示变量p在堆栈上的相对位置,再前面一条指令是mov DWORD PTR _p$[ebp], 0,表示把0赋值到变量p里面去。这样这三条指令完成了这样一个操作序列:把0赋值给p,把p赋值给eax,把0写入到eax所指定的内存,这里eax就是0,因此实际执行的结果就是把数值0写入地址0。我们知道Win98/2000/XP的进程地址空间中,把从地址0开始的64K(Win98是32K)作为不可写/不可读的内存页保护起来了,所有在这个地址空间中进行的读写操作都会引起操作系统结构化异常,而且如果这个异常没有被处理,则会被Windows捕获,然后就显示了这样一个错误信息。

至此,我们算是彻底把这个错误找到了根源。然而这还不是全部……

【待续,本文属 Innocentius 原创,转载请注明出处】
__________________
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #8 (permalink)  
旧 2005-03-03
Innocentius 的头像
版主
 
注册日期: 2002-09-11
住址: 上海
帖子: 562
文章: 12
Innocentius 正向着好的方向发展
发送 MSN 消息给 Innocentius
默认

非常感谢大家支持,这篇东西我很早就想写了,由于种种原因一直拖到现在。这将是一个比较长的连载,起因是前一阵子有几个BUG很难解决,因此看了一些这方面的资料,并且写了一些用于调试的实用程序。这篇连载就算是我自己的一份总结,而且我觉得这个对于大家可能也是有用的,所以就贴上来。

因为现在时间比较紧,很多内容没有查阅资料,仅凭借记忆,所以错误在所难免。如果有错误请及时提出来。谢谢。
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #9 (permalink)  
旧 2005-03-03
初级会员
 
注册日期: 2005-03-03
住址: Shanghai China
帖子: 2
因为coding所以beauty 正向着好的方向发展
默认

原来是这样啊

明白了 :P
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #10 (permalink)  
旧 2005-03-03
cat cat 当前离线
高级会员
 
注册日期: 2003-11-06
帖子: 1,563
文章: 6
cat 正向着好的方向发展
默认

好文啊,实用啊。等有点实际经验了也写
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #11 (permalink)  
旧 2005-03-03
yumagi 的头像
高级会员
 
注册日期: 2003-06-09
住址: 上海
帖子: 314
yumagi 正向着好的方向发展
发送 MSN 消息给 yumagi
默认

真实世界里的程序,嗯,好复杂。比COD里的复杂多了,呵呵
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #12 (permalink)  
旧 2005-03-04
cat cat 当前离线
高级会员
 
注册日期: 2003-11-06
帖子: 1,563
文章: 6
cat 正向着好的方向发展
默认

以前老飘在上面玩,现在发现底层也很有趣
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #13 (permalink)  
旧 2005-03-04
Innocentius 的头像
版主
 
注册日期: 2002-09-11
住址: 上海
帖子: 562
文章: 12
Innocentius 正向着好的方向发展
发送 MSN 消息给 Innocentius
默认

这个步骤有些复杂,如果难得查一次,或许你有这个兴趣,如果需要经常查这些信息,你就会很郁闷,因为其中涉及到很多数据和计算。大家知道计算机科学的发展源于人的惰性,因此我们为了让我们更加舒服一些就需要做进一步的考虑。

如果发生这种关键性错误时我们能够捕获这个错误并且让我们的程序自己来显示出现在什么地方,那有多好呢?

要实现这个技术,我们需要解决下面这些问题:

1、怎么来捕获这个错误?
2、捕获错误以后怎么通过程序来完成上述的动作?
3、获得这些信息以后如何把它记录下来?

这三个问题实际上就覆盖了一个很大范围的知识。让我们来一个个解决。

1、 怎么来捕获这个错误?

从机制上讲,这个错误是一个未处理SEH,也就是所谓的结构化异常。我们知道C++等语言支持异常处理,但是这些异常仅仅限制在这种语言的范围内(.NET 的异常覆盖整个 CLR,但是对我们来说范围还是不够广)。而SEH 是整个操作系统范围内的异常处理,它包括硬件和软件异常两种情况。我们在C++中可以通过下面的语句形式来捕获SEH的异常:

代码:
__try { ... } __except(...) { }
一种可行的处理方法是在 main 函数或者 WinMain 函数中增加一个最顶层的__try和__except,然而这种情况仅仅对单线程程序有效,而且更大的问题是在某些情况下main和WinMain函数不是我们写的(例如MFC),这时这个方法就没有办法了。

幸运的是操作系统提供了一个函数:SetUnhandledExceptionFilter,通过这个函数可以给进程安装一个未处理异常过滤器——这个名字是和所谓__except部分的异常过滤器对应的——当一个进程中任何一个线程出现异常并且没有被处理时都会调用这个异常过滤器。这是一个好机会,作为一个异常过滤器,它可以得到很多有用的信息。让我们来看一下能得到些什么:
下面是这个函数的原型:

代码:
LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter( LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter );
其中lpTopLevelExceptionFilter是一个函数指针,应该具有下面的原型:

代码:
LONG WINAPI UnhandledExceptionFilter( STRUCT _EXCEPTION_POINTERS* ExceptionInfo );
它幸桓霾问?,指向_EXCEPTION_POINTERS结构,这个结构包含下面内容:

代码:
typedef struct _EXCEPTION_POINTERS { PEXCEPTION_RECORD ExceptionRecord; PCONTEXT ContextRecord; } EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
在这个结构中包含了两个指针,一个指向异常记录,另外一个指向上下文环境记录。异常记录包含了发生异常的详细信息,上下文环境记录包含了发生异常时的CPU状态信息。

代码:
typedef struct _EXCEPTION_RECORD { DWORD ExceptionCode; DWORD ExceptionFlags; struct _EXCEPTION_RECORD* ExceptionRecord; PVOID ExceptionAddress; DWORD NumberParameters; ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; } EXCEPTION_RECORD, *PEXCEPTION_RECORD;
在异常记录结构中,最重要的信息是ExceptionCode,它包含了异常代码。对于内存访问违例来说,它的异常代码就是EXCEPTION_ACCESS_VIOLATION。当然还包括其它的异常代码。

第二个字段是ExceptionFlags,如果它是0,那么表示这个异常是可以恢复的,如果是EXCEPTION_NONCONTINUABLE那么表明这个异常是不能恢复的(在C++中,一旦抛出一个异常那么它就不可能再回到抛出异常的地方继续执行,而SEH则可以)。

第三个字段是前一个未处理异常结构。和C++异常不同,SEH异常可以嵌套抛出,可以在异常处理过程中继续抛出异常。从这里可以看到多个异常被组织成了一个链表,因此你在异常过滤器中可以追踪到底发生了多少异常。

第四个字段表明了发生异常的地址,就是Windows显示给你看到的那个地址。

第五个字段表明,对于这个异常,有多少个附加的参数。参数是保存在第六个字段中的。到目前为止,只有EXCEPTION_ACCESS_VIOLATION异常会包含2个参数,第一个参数,也就是 ExceptionInformation[0] 表示对内存读写状态。如果是因为读内存造成异常的,那么这个参数是0,如果是因为写内存造成异常的,那么这个参数就是1。第二个参数是访问违例的内存地址。

上面提到的这个错误实际上就是这个异常结构中内容的一种可视形式,现在我们可以重新回头来看一下造成这个错误的异常信息:

ExceptionCode: EXCEPTION_ACCESS_VIOLATION
ExceptionFlags: 0
ExceptionRecord: NULL
ExceptionAddress: 0x00401028
NumberParameters: 2
ExceptionInformation[0]: 1
ExceptionInformation[1]: 0x00000000

上下文环境记录是用于保存发生异常时的CPU状态,它是在Windows SDK中唯一一个和硬件相关的数据结构,目前我们不需要用它,但是后面会用到。

我们所要做的第一 步 现在已经清楚了,我们只需要安装一个全局的未处理异常过滤器,在这个过滤器中就能得到出现异常的详细信息。下一步就是要把这些信息转换成更加容易理解的形式。

【注意】需要特别注意的一个问题是,如果当前处于调试程序的控制下,例如由Visual C++调试你的程序时,这个全局未处理异常过滤器是不会被调用的,有些资料上说这是一个BUG,然而我觉得不是。不管怎么样这个目前是事实。可以通过调用IsDebuggerPresent函数来判断。这个函数在Windows 95下不存在,因此需要在包含Windows.h前加上下面语句:

代码:
#define _WIN32_WINNT 0x0400
【待续,本文属 Innocentius 原创,转载请注明出处】
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #14 (permalink)  
旧 2005-03-04
cber 的头像
初级会员
 
注册日期: 2003-06-10
帖子: 20
cber 正向着好的方向发展
默认

标题起得太具有迷惑性了,我刚看到时还以为是一个通用的技巧呢,结果却只是一个针对VC的文章
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #15 (permalink)  
旧 2005-03-04
高级会员
 
注册日期: 2002-09-16
帖子: 1,087
文章: 1
SpitFire 正向着好的方向发展
默认

没VC也行,比如windbg(当然是ms出的工具了)
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #16 (permalink)  
旧 2005-03-04
cber 的头像
初级会员
 
注册日期: 2003-06-10
帖子: 20
cber 正向着好的方向发展
默认

我说的VC,指的是用VC来开发的意思
不知道这篇文章对于CBC有没有什么指导意义
我们以前开发时倒是碰到过一个Solaris下没有能用的gdb(当时是死活找不到for SPARC的64 bit edition gdb)时的debug问题,我觉得那次的解决经验就具有xNix下的通用性,等我有空来稍微写写
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #17 (permalink)  
旧 2005-03-04
Innocentius 的头像
版主
 
注册日期: 2002-09-11
住址: 上海
帖子: 562
文章: 12
Innocentius 正向着好的方向发展
发送 MSN 消息给 Innocentius
默认

你说的CBC是什么?
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #18 (permalink)  
旧 2005-03-05
cat cat 当前离线
高级会员
 
注册日期: 2003-11-06
帖子: 1,563
文章: 6
cat 正向着好的方向发展
默认

大概是BCB吧
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #19 (permalink)  
旧 2005-03-05
yumagi 的头像
高级会员
 
注册日期: 2003-06-09
住址: 上海
帖子: 314
yumagi 正向着好的方向发展
发送 MSN 消息给 yumagi
默认

引用:
作者: cber
我说的VC,指的是用VC来开发的意思
不知道这篇文章对于CBC有没有什么指导意义
我们以前开发时倒是碰到过一个Solaris下没有能用的gdb(当时是死活找不到for SPARC的64 bit edition gdb)时的debug问题,我觉得那次的解决经验就具有xNix下的通用性,等我有空来稍微写写
非常期待
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
  #20 (permalink)  
旧 2005-03-06
Elminster 的头像
超级版主
 
注册日期: 2002-09-09
帖子: 1,764
Elminster 正向着好的方向发展
默认

引用:
作者: cber
我说的VC,指的是用VC来开发的意思
不知道这篇文章对于CBC有没有什么指导意义
我们以前开发时倒是碰到过一个Solaris下没有能用的gdb(当时是死活找不到for SPARC的64 bit edition gdb)时的debug问题,我觉得那次的解决经验就具有xNix下的通用性,等我有空来稍微写写
你这个懒人,我拭目以待看你会拖到什么时候再来写这东西,嘿嘿嘿嘿 ……
Digg this Post!Add Post to del.icio.usBookmark Post in TechnoratiFurl this Post!
回复时引用此帖
回复

书签

主题工具
显示模式

发帖规则
不可以发表新主题
不可以发表回复
不可以上传附件
不可以编辑自己的帖子

启用 BB 代码
论坛启用 表情符号
论坛启用 [IMG] 代码
论坛禁用 HTML 代码
Trackbacks are 启用
Pingbacks are 启用
Refbacks are 启用



所有时间均为格林尼治时间 +9。现在的时间是 07:48 AM


Powered by vBulletin® 版本 3.7.0
版权所有 ©2000 - 2009,Jelsoft Enterprises Ltd.
(C) Copy Right All Right Reserved 2001 - 2007

Search Engine Friendly URLs by vBSEO 3.1.0