惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Latest news
Latest news
T
Troy Hunt's Blog
V
Vulnerabilities – Threatpost
L
LINUX DO - 热门话题
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Simon Willison's Weblog
Simon Willison's Weblog
V
V2EX
博客园 - 司徒正美
B
Blog RSS Feed
AWS News Blog
AWS News Blog
MyScale Blog
MyScale Blog
Scott Helme
Scott Helme
Cisco Talos Blog
Cisco Talos Blog
Last Week in AI
Last Week in AI
NISL@THU
NISL@THU
博客园 - Franky
P
Proofpoint News Feed
博客园_首页
C
CERT Recently Published Vulnerability Notes
雷峰网
雷峰网
S
Schneier on Security
P
Proofpoint News Feed
Hugging Face - Blog
Hugging Face - Blog
G
GRAHAM CLULEY
博客园 - 三生石上(FineUI控件)
月光博客
月光博客
WordPress大学
WordPress大学
The Hacker News
The Hacker News
T
Threatpost
阮一峰的网络日志
阮一峰的网络日志
A
Arctic Wolf
Microsoft Azure Blog
Microsoft Azure Blog
T
The Exploit Database - CXSecurity.com
Engineering at Meta
Engineering at Meta
罗磊的独立博客
T
The Blog of Author Tim Ferriss
D
Darknet – Hacking Tools, Hacker News & Cyber Security
I
Intezer
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
K
Kaspersky official blog
SecWiki News
SecWiki News
云风的 BLOG
云风的 BLOG
美团技术团队
C
Cybersecurity and Infrastructure Security Agency CISA
博客园 - 【当耐特】
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Security Latest
Security Latest
C
Cyber Attacks, Cyber Crime and Cyber Security
B
Blog
S
Security Affairs

博客园 - 天马行空(笨笨)

libcurl的使用(转) 使用Visual Studio 2010 Team System中的架构师工具(设计与建模) Silverlight 框架介绍 Silverlight 介绍(转) Pocket PC 模拟器上网设置 解决继承窗体或用户控件时“visual继承当前被禁用,因为基类引用设备特定的组件或包含 p/invoke”问题(转) 操作MSN(IM)相关的资源 用户中心 - 博客园 压缩SQLServer数据库日志的一个存储过程 .net的辅助工具列表 Eclipse+JDK+API中文帮助文档的java开发环境搭建 淺談如何使用Delphi 2009的泛型容器類別 Delphi2009带来了什么 NET开源项目链接(转载) Google Chrome 源码下载 版本控制工具SVN和CVS 用DOS命令安装删除服务 我要向党和人民道歉:我错了(韩国行有感)--转贴 论韩寒"大师"与鲁迅先生
使用MAP文件快速定位程序崩溃代码行(转)
天马行空(笨笨) · 2009-08-13 · via 博客园 - 天马行空(笨笨)

      作为程序员,平时最担心见到的事情就是程序发生了崩溃,无论是指针越界还是非法操作,都将给我们的应用系统造成巨大的损失。但在一个大型系统的测试过程中,初期出现程序崩溃似乎成了不可避免的事。其实测试中出现程序崩溃并不可怕,反而是测试的成功。我们更为关心的是程序中的哪一行导致了系统崩溃,这样我们才能有针对性的进行改正。
  在VC中,我们可以利用出现程序崩溃时VC的自动跳转,定位到出错代码行。但在大量的压力测试时,尤其是多线程测试时,同时出现几十个错,这时VC本身的出错跳转往往会失灵。
  在这里我们介绍一种辅助查找程序崩溃代码行的好方法,它的核心就是利用编译时生成MAP文件中的信息来定位代码行。
下面就开始我们的介绍。
  首先我们必须生成程序的MAP文件。那么什么是 MAP 文件呢?简单地讲, MAP 文件是程序的全局符号、源文件和代码行号信息的唯一的文本表示方法,是整个程序工程信息的静态文本。它可以在任何地方、任何时候使用,不需要有额外的程序进行支持,仅仅通过一个文本阅读工具如Ultra Edit就可以打开了。而且,这是唯一能找出程序崩溃代码行的救星。
  那么我们应该如何生成MAP文件呢?在 VC 中,我们可以按下 Alt+F7,打开“Project Settings”选项页,选择 C/C++ 选项卡,并在最下面的 Project Options 里面输入:/Zd ,然后要选择 Link 选项卡,选中“Generate mapfile”复选框,并在最下面的 Project Options 里面输入:/mapinfo:lines,表示生成 MAP 文件时,加入行信息。最后按下 F7 来编译生成 EXE 可执行文件和 MAP 文件,此时可以在工程的Debug目录下找到刚刚生成的MAP文件,文件名为“工程名.map”。
  通过上面的步骤,已经得到了 MAP 文件,那么我们该如何利用它呢?让我们从一个简单的实例入手,一步一步演示使用MAP文件定位程序崩溃行的过程。
首先假设我们的VC工程中有下面这个文件:
//*****************************************************
// 程序名称:演示如何通过崩溃地址找出源代码的出错行
// 作者:刘可
// 日期:2003-6-19
// 本程序会产生“除0错误”,所以会导致
// 程序崩溃,弹出“非法操作”对话框。
//******************************************************
#i nclude

int crashtest(int a,int b)
{
int c;
c = a/b;
return c;
}

void main(void)
{
int a = 30;
int b = 0;
int ret;
printf("let's begin crash test...\n");
ret = crashtest(a,b);
}

很显然本程序有“除0错误”,在 Debug 方式下编译,运行时会产生“非法操作”。我们记录下产生崩溃的地址——在我的机器上是 0x0040102f 。这个在不同的机器上可能地址不同,但记下这个地址我们下面将要使用。
我们打开它的 MAP 文件:(这里列出我们比较关心的内容,其他的就略过了)

abort(工程名)

Timestamp is 3ef16533 (Thu Jun 19 15:24:35 2003)

Preferred load address is 00400000

Start    Length    Name        Class
0001:00000000 0001081dH .text        CODE
0002:00000000 000013baH .rdata        DATA
0002:000013ba 00000000H .edata        DATA
0003:00000000 00000104H .CRT$XCA       DATA
0003:00000104 00000104H .CRT$XCZ       DATA
0003:00000208 00000104H .CRT$XIA       DATA
0003:0000030c 00000109H .CRT$XIC       DATA
0003:00000418 00000104H .CRT$XIZ       DATA
0003:0000051c 00000104H .CRT$XPA       DATA
0003:00000620 00000104H .CRT$XPX       DATA
0003:00000724 00000104H .CRT$XPZ       DATA
0003:00000828  00000104H .CRT$XTA       DATA
0003:0000092c  00000104H .CRT$XTZ       DATA
0003:00000a30  00003236H .data        DATA
0003:00003c68  000019c8H .bss        DATA
0004:00000000  00000014H .idata$2       DATA
0004:00000014  00000014H .idata$3       DATA
0004:00000028  00000120H .idata$       DATA
0004:00000148  00000120H .idata$5        DATA
0004:00000268  000004f4H .idata$6       DATA

Address Publics by Value Rva+Base Lib:Object

0001:00000020 ?crashtest@@YAHHH@Z 00401020 f main.obj
0001:0000003c _main 0040103c f main.obj
0001:000000b0 _printf 004010b0 f LIBCD:printf.obj
0001:00000130 __chkesp 00401130 f LIBCD:chkesp.obj
0001:00000170 _mainCRTStartup 00401170 f LIBCD:crt0.obj
0001:000002a0 __amsg_exit 004012a0 f LIBCD:crt0.obj
0001:00000300 __stbuf 00401300 f LIBCD:_sftbuf.obj
0001:00000460 __ftbuf 00401460 f LIBCD:_sftbuf.obj
0001:00000520 __output 00401520 f LIBCD:output.obj
0001:000013c0 ___initstdio 004023c0 f LIBCD:_file.obj
0001:000014f0 ___endstdio 004024f0 f LIBCD:_file.obj
0001:00001510 __CrtDbgBreak 00402510 f LIBCD:dbgrpt.obj
0001:00001520 __CrtSetReportMode 00402520 f LIBCD:dbgrpt.obj
0001:00001580 __CrtSetReportFile 00402580 f LIBCD:dbgrpt.obj
0001:00001600 __CrtSetReportHook 00402600 f LIBCD:dbgrpt.obj
0001:00001620 __CrtDbgReport 00402620 f LIBCD:dbgrpt.obj

  如果仔细浏览 Rva+Base 这栏,我们可以发现第一个比崩溃地址 0x0040102f 大的函数地址是 0x0040103c ,所以在 0x0040103c 这个地址之前的那个入口就是产生崩溃的函数,也就是这行:

0001:00000020 ?crashtest@@YAHHH@Z 00401020 f main.obj

  因此,发生崩溃的函数就是 ?crashtest@@YAHHH@Z,所有以问号开头的函数名称都是 C++ 修饰的名称。所以在我们的源程序中,这个发生崩溃的函数就是 crashtest ()!

  现在我们便轻而易举地知道了发生崩溃的函数名称。把它记下来,然后我们将要直接定位发生崩溃的代码行了。我们注意 MAP 文件的最后部分——代码行信息(Line numbers information),它是以这样的形式显示的:

Line numbers for .\Debug\main.obj(D:\我的工作\技术\出异常例子abort\main.cpp) segment .text

12 0001:00000020 14 0001:0000002b 15 0001:00000035 16 0001:00000038
19 0001:0000003c 20 0001:00000057 21 0001:0000005e 23 0001:00000065
24 0001:00000072 25 0001:00000085

  第一个数字代表在源代码中的代码行号,第二个数是该代码行在所属的代码段中的偏移量。如果要查找代码行号,需要使用下面的公式做一些十六进制的减法运算:

崩溃行偏移 = 崩溃地址(Crash Address)- 基地址(ImageBase Address)- 0x1000

  为什么要这样做呢?因为我们得到的崩溃地址都是由 偏移地址(Rva)+ 基地址(Base)得来的,所以在计算行号的时候要把基地址减去。一般情况下,基地址的值是 0x00400000 。另外,由于一般的 PE 文件的代码段都是从 0x1000 偏移开始的,所以也必须减去 0x1000 。
  所以我们的:崩溃行偏移 = 0x0040102f - 0x00400000 - 0x1000 = 0x2f
我们在MAP 文件的中的代码行信息里查找不超过计算结果0x2f,但却最接近的数。发现是 main.cpp 文件中的:

14 0001:0000002b

  也就意味着在源代码中的第 14 行!让我们来看看源代码,注意注释行和空行也要计算在内,程序的第14行为:

c = a/b;

果然就是第 14 行啊,它发生了“除0异常”!

  方法已经介绍完了,从今以后,我们就可以精确地定位到源代码中的崩溃行,而且只要编译器可以生成 MAP 文件,无论在WIN平台还是UNIX平台,本方法都是适用的。
  本文我们只是列举了一个非常简单的“除0异常”例子,使用MAP文件的效力或许还不十分明显。但相信在我们的大型应用系统调试中,使用MAP文件的辅助方法来快速定位发生程序崩溃的函数以及代码行,将会为我们的程序调试工作节省大量时间和精力,提高我们的调试质量。我们甚至可以要求远地用户直接提供程序崩溃的地址,然后就可以在自己机器上利用MAP文件静态地找到出错的那行,并在程序中进行相应修正了。