全系统栈崩溃是什么意思鬼

相关的新闻
网友点击排行携程系统大规模崩溃 疑遭“内鬼”报复
来源:北京青年报
  昨日上午,携程因部分服务器遭到不明攻击,官网和手机APP全部陷入瘫痪状态。按照携程一季度财报公布的数据,携程宕机的损失为平均每小时106.48万美元。
  多名网友昨天表示,携程网及APP陷入瘫痪状态,页面无法打开。对此,携程方面官方回应称,部分服务器疑似遭到不明攻击,导致官方网站及APP暂时无法正常使用,目前系统正在逐步恢复中。而安全专家则表示,此次事故或有“内鬼”抑或黑客定向攻击所致,目前来看,问题复杂且严重,短时间内难以恢复。截至21时左右,北京青年报记者登录携程网站发现,服务均已恢复。
  实况:携程网及APP陷入瘫痪状态
  多名网友昨天上午11点左右表示,携程网及APP陷入瘫痪状态,页面无法打开。有媒体实测,通过百度推广点击进入携程网,页面显示404报错,虽然点击“返回首页”后依然可以进入携程网,但其功能和其他链接均无法使用。按照携程一季度财报公布的数据,携程宕机的损失为平均每小时106.48万美元。
  昨天下午,北京青年报记者登录携程官网看到,在首页顶部挂出“携程网站暂时无法提供服务,正在紧急修复中&&您可以访问艺龙旅行网”的通知。18时,艺龙网在官方微博上表示,“因遭受网络攻击,艺龙网首页出现部分用户无法访问的情况,目前已恢复正常。”
  携程网为艺龙网股东。5月22日,携程出资约4亿美元购买艺龙网37.6%的股权。
  猜测:有人内部报复?
  对于本次携程宕机的严重性,网上有传言称,携程全线酒店数据库遭到了物理删除。一位自称携程员工的网友说:“所有节点上的业务代码都被干掉了,业务部门那边都在忙。周围同事也觉得诡异,因为发布日志都没了,具体原因还在查,我个人猜测是有人内部报复。”另有疑似携程员工聊天记录显示,目前携程发布系统仍然无法使用,数据仍然被持续删除中。
  还有网友分析称,一般网站都有异地容灾和介质备份,携程这样级别网站的数据库都是按照机房完全物理损毁做备份以及容灾方案的,不会容忍宕机10分钟,从目前的情况看,以往的容灾方案很可能无效,携程也没及时披露相关信息,说明很有可能携程有内鬼。
  回应:部分服务器疑似遭到不明攻击
  对于携程网及APP陷入瘫痪状态,携程相关人士回应称,5月28日上午11:09,因携程部分服务器疑似遭到不明攻击,导致官方网站及APP暂时无法正常使用,目前系统正在逐步恢复中,详细原因也还在调查中。经过紧急排查,携程数据没有丢失,预订数据也保存完整。在恢复过程中,对用户造成的不便,公司深表歉意。
  专家:“内鬼”抑或黑客定向攻击
  猎豹移动安全专家李铁军分析,携程服务中断极有可能是内部管理失控导致,通常黑客从外部攻击很难做到数据大量丢失,且备份还原不至于拖延太久。通常黑客入侵往往只是悄无声息地拿走核心数据,一般不会进行破坏性操作。携程这次事故从其公开的信息来看,像内部人员所为(有可能已经造成数据损失),具体损失有多严重,只能等待官方的进一步消息。数据损失的表现是,有可能是在这期间订过的机票酒店信息查不到了,机票还好说,可以查航空公司,酒店可能会影响入住。
  360安全专家也指出,携程昨天发生的代码和数据被删除事件,有可能是具有高级管理权限的内部人员所为,也有可能是遭受了黑客针对性的定向攻击。从现在的情况看,携程被删除的代码8小时内还没有恢复,有可能是重新上线的过程中遭遇攻击者的阻碍,影响了服务恢复的进度。否则在有备份的情况下,代码和数据一般能在一小时内恢复。
  观察:企业应加强自身数据管理和安全防护
  用户和数据是携程等类似网站的重要资产,数据损失会对用户服务造成严重影响,长时间网站瘫痪也会导致用户流失。此类安全事故对所有企业来说都会带来惨痛的教训。
  腾讯安全专家建议企业做好安全自查。普通用户遇到此事不要过于恐慌,可紧密关注官方回应,如遇信息泄露,必要时可修改银行支付密码,以免造成经济损失。李铁军也表示,预防灾难性事件发生,需要做两点。一是严格的权限管理(内控)和灾难预防;二是做好数据备份,让网站在最短时间内恢复。文/本报记者 吴琳琳 图片制作/王慧
  相关新闻
  500强企业超半数曾遭黑客攻击
  Verizon最新发布的《2015数据泄露调查报告》显示,2014年事件调查中,影响的组织覆盖95个国家,其中有61个报告了问题,涉及79790个安全事件,超过2000条确认的数据泄露,500强企业中超半数曾遭受过黑客攻击,SONY(索尼公司)、APPLE(苹果公司)、JPMORGAN CHASE(摩根大通银行)等机构都曾上榜。
  来自中国的信息安全问题更加触目惊心,2011年到2014年互联网公开的安全事故已导致11.3亿用户信息泄露。福布斯上榜的中国企业中,大多数企业都曾经不同程度遭受过攻击或出现信息泄露,特别是一些掌握大量民众个人信息的通信运营商,如中国移动、中国电信、中国联通、淘宝、携程、腾讯,以及众多保险、金融知名企业都成为信息泄露的“重灾区”。
  据360安全专家介绍,天眼实验室独立发现并监测到的情况显示,针对大量敏感行业和用户的专业性黑客攻击开始蔓延。在“互联网+”时代,企业面临的安全挑战会越来越严峻。补天漏洞响应平台上,目前已有四万多个有效漏洞,如果被黑客恶意利用,将造成灾难性的后果,呼吁企业应重视漏洞响应,及时进行修复。
(责任编辑:UF030)
&&&&&&</div
官方一再推带薪休假,甚至将鼓励“周五下午+周末”小短假。[]
图解财经:
今日主角:
社区热帖推荐
用镜头记录下他们的生活……[]
客服热线:86-10-
客服邮箱:新浪广告共享计划>
广告共享计划
记录程序崩溃时的调用堆栈
为程序添加自动发送Email功能
转载&#9660;
最近在开发中要为部门的软件产品加上crash
report功能,研究了很多关于Windows平台下的debug技术。最终方案为minidump file +
email汇报方式,对于debug的相关总结我会再写一篇,这篇就先总结一下自动发送Email功能的实现方法。另外我还写了一个demo程序,虽然
UI比较简单,不过发送Email的基本功能已经有了,并且用了5种库实现,一是为了比较库的性能,二是通过比较库的实现,我们可以学习一下代码设计和实
现方面的技巧。
其实当今的软件应用与网络结合越来越紧密,我们为软件加上Email发送功能有很多好处。比如可以发送软件错误报告信息,有利于软件debug;可以在帮助中加入用户意见和使用体验等信息,通过邮件汇总升级;可以在用户卸载软件时请用户选择卸载原因,收集用户使用习惯等。
发送邮件用到的协议是SMTP(Simple Mail Transfer
Protocol,简单邮件传输协议),是一种基于TCP/IP的上层应用协议,定义了邮件由源地址到目的地址的传送规则。关于SMTP协议的具体内容,大家可以参考相关资料。我们要进行Email发送,需要两个条件:
1、支持SMTP的库
也就是完成SMTP功能的代码,由于SMTP非常简单,因此往往就是一个封装类,或者一组API。当然也可以自己用socket实现SMTP,不过如果不是为了练手,还是直接用已有的开源代码吧,毕竟简单且稳定。
2、一个发送邮箱账户和一个接收邮箱账户
邮箱账户应该不是什么难事,大家随便去申请两个免费邮箱就行了。需要说明的是,发送邮件时要先登录邮件服务器根据帐号和密码进行认证,因此发送邮箱必须支
持SMTP访问,比如126邮箱从07年开始便不再对新申请用户支持SMTP访问,因此126邮箱账户必须是07年以前申请的才行,否则会在认证时返回
550错误码,提示用户被锁定。还有些邮箱如gmail需要SSL支持加密方式,对某些库来说可能就不支持了。
我们先看一下demo程序界面,了解发送邮件需要哪些基本参数。
&&&&可知参数包括:发送端邮箱账户和密码、接收端邮箱账户、邮件主题、内容、附件等信息,另外还要指定邮件
发送服务器地址,在此我们认为是”smtp.host”形式,如按图中设置发送服务器地址即为。一般库还会支持指定发送者用户
名、认证方式、多个接收地址、抄送地址、html格式正文、多个附件等。
下面介绍几个比较不错的SMTP库,都是用C++开发的。
1、jwSMTP ()
jwSMTP是一个开源库,支持跨平台,SMTP常用功能都支持的不错,应用起来也非常方便。jwSMTP提供了一个封装类,接口简单易用。最难得的是它
自带的文档和示例都非常完善丰富,因此也是我最终选择应用的库。在应用过程中,该库运行非常稳定,没有发现什么问题,建议大家使用jwSMTP。
以下是应用代码:
jwsmtp::mailer mail( (LPCTSTR)m_strToUser, (LPCTSTR)m_strFormUser,
(LPCTSTR)m_strSubject, (LPCTSTR)m_strContent, (LPCTSTR)m_strServer,
jwsmtp::mailer::SMTP_PORT, false);
mail.username((LPCTSTR)m_strFormUser);
mail.password((LPCTSTR)m_strFromPassword);
mail.authtype(jwsmtp::mailer::PLAIN);
mail.attach((LPCTSTR)m_strAttachment);
mail.send();
std::string strCode = mail.response();
2、Windows MAPI
微软已经为Windows系统开发者提供了SMTP协议的开发接口——MAPI(Messaging Application
Progrmming Interface)。MAPI还是比较复杂的,我们如果只发送邮件的话,只用其中的Simple
MAPI就足够了,用起来十分简单。Simple
MAPI提供了一组API函数以及相关结构定义,只要在代码中包含mapi.h头文件即可。但是要想应用MAPI,必须要有Windows邮件系统支持,如安装Outlook等应用软件,还要在其中配置好profile。
开发步骤为:加载mapi32.dll库 —& 通过GetProcAddress函数得到API函数地址 —&
设置MapiMessage等结构体参数 —& 调用MAPISendMail函数发送邮件。
由于MAPI开发中的诸多限制,我们应用起来不是很方便,有兴趣的可以研究demo中的代码。
3、free libsmtp ()
libsmtp也是一个开源C++库,从封装的结构看它提供了两个类,比jwSMTP要复杂,面向对象的效果不是太理想。它最大的缺陷就是还没有支持附件
功能,我怀疑可能是我没找到更新的版本,但是sourcesforge上确实没有。它的特色是提供了一个相应的异常类,支持异常使得代码用起来更加安全。
以下是应用代码:
mail::Smtp smtp((LPCTSTR)m_strServer, 25, strName,
(LPCTSTR)m_strFromPassword);
mail::Mail email((LPCTSTR)m_strFormUser, (LPCTSTR)m_strToUser);
email.setSubject((LPCTSTR)m_strSubject);
email.setMessage((LPCTSTR)m_strContent);
smtp.sendMail(email);
catch (mail::Exception& e)
printf(e.why().c_str());
4、SMailer ()
这个是国内ITer写的封装类,用起来还不错。但是跟free libsmtp一样,封装性做的不是太好,使用不太方便;也支持异常。
以下为应用代码:
MUtils::WinSockH
SMailer::MailI
info.setSenderName((LPCTSTR)m_strFormUser);
info.setSenderAddress((LPCTSTR)m_strFormUser);
info.addReceiver((LPCTSTR)m_strToUser, (LPCTSTR)m_strToUser);
info.setSubject((LPCTSTR)m_strSubject);
info.setPriority(SMailer::Priority::normal);
info.addMimeContent(&SMailer::TextPlainContent((LPCTSTR)m_strContent));
info.addMimeContent(&SMailer::AppOctStrmContent((LPCTSTR)m_strAttachment));
SMailer::MailSender sender((LPCTSTR)m_strServer,
(LPCTSTR)m_strFormUser, (LPCTSTR)m_strFromPassword);
sender.setMail(&SMailer::MailWrapper(&info));
sender.sendMail();
catch (SMailer::MailException& e)
printf(e.what());
catch (...)
最后这个是在网上无意找到的,没有查到正式的名字和主页,姑且这么叫吧。看源码是很早以前写得了,以至于我使用的时候并没有发送成功,也没时间调试具体原
因。里面封装的类很多,使用起来也比较麻烦。放进来的原因,一是它专门为MFC应用程序写得,二是可以研究一下源码,因为数据结构没有用到STL,很多都
是自己设计的。
好了,先介绍到这里,demo的源码放在下面,大家可以看看参考一下。
(在打开的网页中点击表格中的free按钮,然后填下验证码,点download就可以了)
或者到我的网盘去下载:
调试Release发布版程序的Crash错误(一)
( 23:21:42)
转载&#9660;
&方案一:崩溃地址
这种方案只能对VC7以前的版本开发的程序使用。&
1、崩溃地址
&&&&&所谓崩溃地址就是引起程序崩溃的内存地址,在WinXP下应用程序crash的对话框如下图:
上面第2张图中画红线的值为crash的代码偏移地址,第3张图为即crash绝对地址;一般引起crash的原因多为内存操作错误,我们用这两个地址和MAP文件就能定位出错的代码行。
2、MAP文件
MAP文件是记录应用程序信息的文件(文本文件),里面大概包含了程序的全局符号、源码模块名、源码文件和行号等信息,而这些信息能够帮助我们定位出错的代码行。
怎样生成MAP文件呢?以VC6为例,在 Project
Settings -& C/C++&-& Debug
info中,选择 Line Numbers Only ;在 Project
Settings -& Link 中,选择 Generate
mapfile项,并在Project Options 里面输入
/MAPINFO:LINES
/MAPINFO:EXPORTS,重新编译程序就会生成.map文件。
以上设置对应的编译链接选项分别分:
— 表示生成pdb调试信息;
/MAP[:filename]
— 表示生成map文件名;
/MAPINFO:EXPORTS&
— 表示生成的map文件中加入exported
functions(生成DLL文件时);
/MAPINFO:LINES&
— 表示生成的map文件中加入代码行信息。
由于/MAPINFO:LINES选项在VC8以后的版本中不再支持,因此通过MAP文件中的信息和crash地址定位出错代码行就比较困难了,所以这种方案只能在VC7及以前的版本中使用。
&&&&一个MAP文件片段示例如下:&
图中Rva+Base列的地址为该行函数对应的函数绝对地址,Address列中冒号后面的地址为函数相对偏移地址。&&&
3、定位crash代码
有了上面的介绍,定位crash代码就很简单了。用下面的公式来进行定位:
崩溃行偏移
= 崩溃地址 - 崩溃函数绝对地址 +
函数相对偏移
我们首先根据崩溃地址(绝对地址),按照找到第2张图中Rva+Base列的地址找到发生崩溃的函数(即崩溃地址大于该函数行的Rva+Base地址且小于下个函数的地址),然后找到该行对应的函数相对偏移地址,带入公式中,就得到了崩溃行偏移,该值表示崩溃行的代码相对于代码所在函数的偏移量。用该值去与第3张图中对应函数冒号后面的偏移量去比较,最接近的值前面的那个十进制数即为代码所在函数中的行号。
ok,到此我们已经成功找到了崩溃的代码行,只不过这种方法还是比较费力,并且限制比较多,我们看看下面的方案。
调试Release发布版程序的Crash错误(二)
( 16:37:25)
转载&#9660;
上篇给出的方案一还要补充几句。通过“crash地址 +
MAP文件”来定位出错代码位置虽然需要经过比较复杂的地址计算,但却是最简单实现的方式。如果仅仅想通过崩溃地址定位出错的函数,就更加方便了。我在网上找到一个解析MAP文件的小工具,可以非常清晰的列出每个函数的地址,并且可以将分析表格导出为Excel文件。工具下载地址:,工具目录下VCMapper.exe。
另外上篇主要参考两篇文章:
方案二:崩溃地址 + MAP文件 +
由于VC8以后的版本都不再支持MAP文件中产生代码行信息,因此我们寻找另一种定位方式:COD文件。
1、COD文件
COD文件是一个包含了汇编码、二进制机器码和源代码对应信息的文件,每一个cpp都对应一个COD文件。通过这个文件,我们可以非常方便地进行定位。
在VC6中生成COD文件的设置方式为:Project Settings -& C/C++,在 Category 中选
Listing Files,在 Listing file type 组合框中选 Assembly,Machine code,and
source。在VC8中生成COD文件的设置方式为:Project Properties -& C/C++ -&
Output Files -& Assembler Output 项,选择 Assembly,Machine code,and
Source(/Facs)。
2、定位崩溃行
下面通过举例进行说明。现在我有一个基于对话框的MFC应用程序CrashTest,在CCrashTestDlg::OnInitDialog函数中写入导致crash的代码语句(第99行),源文件如下:
根据崩溃地址(0x)以及MAP文件(定位片段图片如下),定位crash函数为OnInitDialog;并且我们可以很容易地计算出崩溃地址相对于崩溃函数的偏移量为
0x - 0x = 0xC3。
再来看看CrashTestDlg.cod文件,我们根据文件中源码信息找到OnInitDialog函数信息片段:
可以看到图片中第一行为OnInitDialog函数汇编代码的起始行;找到“int * p =
NULL;”这一句源码,其前面的98表示这行代码在源文件中的行号,下面的000c1表示相对于函数开始位置的偏移量,后面的“33
c0”为机器码,“xor eax,eax”为汇编码。那么我们根据前面算出来的偏移量0xC3,找到对应出错的语句为99行:“*p =
总结一下定位步骤:
1) 根据公式 崩溃语句在函数中偏移地址 = 崩溃地址 - 崩溃函数地址 计算出偏移量X;
2) 根据公式 崩溃语句在COD文件中地址 = 崩溃函数在COD文件中地址
计算出地址Y。其中崩溃函数在COD文件中地址为COD文件中函数起始括号“{”后面表明的地址,一般情况下为0x0000;
3) 根据Y在COD文件中找到对应代码行。
ok,方案二介绍完了。这种方法最大的好处是没有VC开发环境版本限制,而且COD文件里面包含的信息更加丰富,不但可以帮助我们定位crash,还能帮我们分析很多东西。当然,这也导致编译生成了很多信息文件。
调试Release发布版程序的Crash错误(三)
( 21:13:38)
转载&#9660;
根据前面两篇博文,我们要定位崩溃行代码,必须要自己根据相关信息文件进行计算。如果需要处理的量比较大,恐怕会很费力气。有没有更简单快速的办法呢?
最直接的想法就是写一个小工具,根据规则和信息进行自动定位,不过开发起来也是要费一番功夫的。令人开心的是,我们可以找到类似的工具,而且是开源免费的!程序员的世界也许很多时候都是这么单纯而乐于分享!
方案三:崩溃地址 + PDB文件 +
CrashFinder
CrashFinder是一个开源工具,作者是John
Robbin,大家可以去他的blog上去找关于CrashFinder的信息。我们这里以CrashFinder2.5版本为例介绍,相关文章链接为:
1、PDB文件
PDB(Program
Database)文件中包含了exe程序所有的调试相关信息,具体可以查阅MSDN。当编译选项设置为/Zi,链接选项设置为/DEBUG,/OPT:REF时,就会生成工程的.pdb文件。具体到VC2005中,就是
Project Propertise -& C/C++ -& General -& Debug
Information Format 项设置为 Program Database(/Zi),Linker -&
Debugging -& Generate Debug Info 项设置为 Yes(/Debug),Linker -&
Optimization -& References&项设置为
Eliminate&Unreferenced Data(/OPT:REF)。
只要设置以上选项,release版本也能生成PDB文件。当然,对应的应用程序也会稍大。
2、CrashFinder
CrashFinder能够运行需要两个条件:一是系统必须要有dbghelp.dll文件;二是PDB文件必须与exe文件在一个路径下。对于dbghelp.dll,一般在系统system32路径下都有,如果没有下载一个放到这个目录下就可以了。
先看一下CrashFinder的界面。
用起来也非常简单。首先选择File-&New或点击工具栏新建按钮,选择要调试的exe文件打开,会发现exe及所依赖的dll文件信息都已经加
载进来。在下半部分的编辑框中输入崩溃地址(16进制),点右边的“Find”按钮,就会在下面显示崩溃的源文件路径、名称以及崩溃所在行号了,如下图所
用CrashFinder进行crash定位真的非常方便。但是我在使用过程中发现了一个bug,每次启动程序后,直接新建的话加载进来的exe模块都显示叉,提示找不到debug
symbols。但是用打开按钮随便打开一个文件失败后,再新建就能成功。猜测可能是直接新建,定位PDB文件时的路径不对引起的。有源码,但是懒的看了呵呵,大家感兴趣可以试一下。
好了,方案三就介绍到这里,后面还有更加强大的方案 : )
调试Release发布版程序的Crash错误(四)
( 16:27:18)
转载&#9660;
前面几个方案都是直接定位crash的代码位置,但是在比较大型的程序中,只知道这个信息还是远远不够的,我们希望知道更多关于调用函数顺序及变量值等信息,也就是crash时调用堆栈信息。
方案四:SetUnhandledExceptionFilter +
StackWalker
这个方案需要自己动手往工程里添加代码了。要实现上面的想法,需要做两件事情:1、需要在crash时有机会对程序堆栈进行处理;2、对堆栈信息进行收集。
1、SetUnhandleExceptionFilter函数
Windows平台下的C++程序异常通常可分为两种:结构化异常(Structured
Exception,可以理解为与操作系统相关的异常)和C++异常。对于结构化异常处理(SEH),可以找到很多资料,在此不细说。对于crash错
误,一般由未被正常捕获的异常引起,Windows操作系统提供了一个API函数可以在程序crash之前有机会处理这些异常,就是
SetUnhandleExceptionFilter函数。(C++也有一个类似函数set_terminate可以处理未被捕获的C++异常。)
SetUnhandleExceptionFilter函数声明如下:
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI
SetUnhandledExceptionFilter(
__in&&&&&&&&&
LPTOP_LEVEL_EXCEPTION_FILTER
lpTopLevelExceptionFilter
其中 LPTOP_LEVEL_EXCEPTION_FILTER 定义如下:
typedef LONG (WINAPI *PTOP_LEVEL_EXCEPTION_FILTER)(
__in struct _EXCEPTION_POINTERS *ExceptionInfo
typedef PTOP_LEVEL_EXCEPTION_FILTER
LPTOP_LEVEL_EXCEPTION_FILTER;
简单来说,SetUnhandleExceptionFilter允许我们设置一个自己的函数作为全局SEH过滤函数,当程序crash前会调用我们的函数进行处理。我们可以利用的是
_EXCEPTION_POINTERS
结构类型的变量ExceptionInfo,它包含了对异常的描述以及发生异常的线程状态,过滤函数可以通过返回不同的值来让系统继续运行或退出应用程序。
关于 SetUnhandleExceptionFilter 函数的具体用法和示例请参考MSDN。
2、StackWalker
现在我们已经有机会可以在crash之前对程序状态信息进行处理了,只需要生成并保存堆栈信息就大功告成了。Windows的dbghelp.dll库提
供了一个函数可以得到当前堆栈信息:StackWalk64(在Win2K以前版本中为StackWalk)。该函数声明如下:
BOOL WINAPI StackWalk64(
__in&&&&&&&&&
DWORD MachineType,
__in&&&&&&&&&
HANDLE hProcess,
__in&&&&&&&&&
HANDLE hThread,
__in_out&&&&&
LPSTACKFRAME64 StackFrame,
__in_out&&&&&
PVOID ContextRecord,
__in&&&&&&&&&
PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
__in&&&&&&&&&
PFUNCTION_TABLE_ACCESS_ROUTINE64
FunctionTableAccessRoutine,
__in&&&&&&&&&
PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
__in&&&&&&&&&
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
该函数的具体用法可以参考MSDN。在这里推荐一个牛人写好的StackWalker,可以直接拿来用,开源的。StackWalker提供了一个基类,
给出了几个简单的接口,可以方便地生成堆栈信息,并且支持一系列VC版本,非常好用。我们可以自己写一个子类,并重载虚函数OnOutput,就可以将堆
栈信息输出为特定格式了。StackWalker的地址为:。
不过对于Release版本来说,StackWalk64函数获得的堆栈信息有可能不完整。如果异常是由MFC的模块抛出,那么获得的堆栈可能缺少前面调
用模块信息。另外,StackWalk64需要最新的dbghelp.dll文件支持才能工作;要正确输出crash的函数名和行号,需要要pdb文件支
持。以上不足有可能影响输出信息的完整性和效果,而对于发布在外的程序,要带上pdb文件几乎不可能,因此这个方案还是有缺憾的,比较适用于本地的
release版本调试。
下一篇我们将介绍一个更加完善的解决方案。
调试Release发布版程序的Crash错误(五)
( 12:59:35)
转载&#9660;
当我们把自己的release版本程序发布出去以后,一般都是在用户的机器上运行。这种情况下,对于第四种方案,因为需要pdb文件才能够正确生成堆栈调
用的函数行号及代码行号,因此方案四只适用于本地release版的调试,否则只能生成不完整的堆栈信息。对于前三种方案,其实只需要用户告知崩溃地址,
然后在本地查找crash地址就可以了,但是定位crash的过程非常不方便,如果crash的情况比较多,前三种方案都不合适。而且,前三种方案均不能
生成堆栈调用信息,对于debug的作用有限。
下面我们就来看一个更加完善的解决方案。
方案五:SetUnhandledExceptionFilter +
SetUnhandleExceptionFilter函数我们已经介绍过了,本方案的思路还是要利用我们自己的异常处理函数,来生成minidump文件。
1、Minidump概念
minidump(小存储器转储)可以理解为一个dump文件,里面记录了能够帮助调试crash的最小有用信息。实际上,如果你在 系统属性
-& 高级 -& 启动和故障恢复 -& 设置 -& 写入调试信息 中选择“小内存转储(64
KB)”的话,当系统意外停止时都会在C:\Windows\Minidump\路径下生成一个.dmp后缀的文件,这个文件就是minidump文件,只不过这个是内核态的minidump。
&&&我们要生成的是用户态的minidump,文件中包含了程序运行的模块信息、线程信息、堆栈调用信息等。而且为了符合其mini的特性,dump文件是压缩过的。
2、生成minidump文件
生成minidump文件的API函数是MiniDumpWriteDump,该函数需要dbghelp.lib支持,其原型如下:
BOOL WINAPI MiniDumpWriteDump(
__in&&&&&&&&&
HANDLE hProcess,
__in&&&&&&&&&
DWORD ProcessId,
__in&&&&&&&&&
HANDLE hFile,
__in&&&&&&&&&
MINIDUMP_TYPE DumpType,
__in&&&&&&&&&
PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
__in&&&&&&&&&
PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
__in&&&&&&&&&
PMINIDUMP_CALLBACK_INFORMATION CallbackParam
在我们的异常处理函数中加入以下代码:
HANDLE hFile = ::CreateFile(
_T("E:\\dumpfile.dmp"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
if( hFile != INVALID_HANDLE_VALUE)
MINIDUMP_EXCEPTION_INFORMATION
einfo.ThreadId = ::GetCurrentThreadId();
einfo.ExceptionPointers = pExI
einfo.ClientPointers = FALSE;
::MiniDumpWriteDump(::GetCurrentProcess(), ::GetCurrentProcessId(),
hFile, MiniDumpNormal, &einfo, NULL, NULL);
::CloseHandle(hFile);
其中,pExInfo变量为异常处理函数PEXCEPTION_POINTERS类型的参数。具体请参考MSDN。
3、调试minidump
&&&&调试dump文件首先需要pdb文件,因此我们build程序时需要设置
Debug Infomation Format 为 “Program
Database(/Zi)”。其次,我们还要确保所用的dump文件与源代码、exe、pdb文件版本是一致的,这要求我们必须维护好程序版本信息。
调试minidump最方便的环境就是VS了,我们只要将.dmp、.exe、.pdb文件放在一个路径下,保证源代码文件的路径与编译时的路径一致就可以了,剩下的就是VS帮我们完成。双击.dmp文件或者在文件打开工程中选择“dump
files”,加载dump文件,然后按F5运行就能直接恢复crash时的现场了,你可以定位crash的代码,可以查看调用堆栈,可以查看线程和模块信息...一切都跟你设置断点调试一样,太强大了!看个截图吧。
需要注意的是,对于release版的程序来说,很多代码是经过编译器优化过的,因此定位的时候可能会有所偏差,大家可以考虑设置选项去掉代码优化。
其他可以调试minidump的工具还有WinDbg等,大家可以查阅相关资料。
本文主要参考了这篇文章:。
下一篇,我们将给出一个调试release发布程序的完美解决方案,适合用户量较大的应用发布程序的调试。
调试Release发布版程序的Crash错误(六)
( 19:48:04)
转载&#9660;
上一篇我们已经给出了方案,能够非常方便的通过dump文件对crash错误进行调试和定位;从整个流程上看还差最后一步,即怎样拿到crash时产生的
dump文件。如果可以让用户把文件发送过来自然不错,但对于类似免费共享软件等在互联网上发布的程序呢?我们的用户是不确定的,而且用户量有可能非常
大,即使我们能想办法联系到用户,总不能挨个去收集crash信息吧。
我们需要一种方案,能够提供crash信息汇报功能。
我们可以架设一台服务器专门进行信息收集,只要客户端在crash时正确汇报即可,但是相应的维护成本和开发难度也不可忽视。有没有更简单的方法呢?还记得我的博文“”吗?这就是简单有效的方法!
方案六:minidump +
我们只需要在异常处理时,先生成minidump信息文件,再用email方式将文件发送到指定邮箱就行了。剩下的就是我们每天查看邮箱,提取dump文件进行调试了。
1、Email功能
首先我们来看一下email发送都需要哪些相关信息。
a、发送端邮箱帐户;
b、接收端邮箱帐户;
c、email标题,一般应有软件名称及版本信息;
d、email正文,一般应有简单的crash信息提示,以区别不同原因造成的crash;
&&&&e、email附件,当然就是我们的dump文件了,还可以加上软件生成的log文件等。
当然,对于标题应该尽量多加一些信息区别引起crash的原因,比如将crash的地址信息加到标题中;因为当每天有成百上千的crash汇报上来,重复
的crash占大多数,把时间都花在区分它们身上有点太浪费。由此看来,前面方案中提到的StackWalker还是有些用处的,我们可以用它来生成一些
crash的文字描述信息,写到标题或正文中去。
dump文件的大小是否适合作为邮件的附件呢?实际上minidump产生的文件一般在几K到几十K之间,作为email的附件没有任何问题。
关于发送email相关技术细节,已经在“”文中介绍了,大家可以参考。其实,对接受邮箱中邮件的处理还是很费时费力的,大家可以考虑写一些脚本将处理流程自动化,提高效率。
2、google breakpad
google breakpad是一个开源的跨平台crash
report系统,光从开源和跨平台这两个特点上来看,它就足以称的上是一个完善而有效的工具了。其实,breakpad在整个crash
report层次上给出了一个系统级的解决方案,也就是说它几乎能适应各种软件、各种平台的应用要求。
breakpad的整体思路跟上面介绍的方案是相似的,只不过最后提交dump文件的方式更加完善。大家有兴趣可以去它的官方网址查阅相关资料:。
ok,关于调试release发布程序的crash错误系列文章就写完了。这几篇文章给出的方案由简单到复杂,由简陋到完善,对crash调试有了一个比较全面的总结。当然,其中涉及到的概念和技术还很多,需要我们去不断学习和领悟,也希望大家能够互相交流。
最近有个用户遇到程序Crash问题,但我们的机器都不能重现,于是在网上搜了一把,发现有个MSJExceptionHandler类还比较好用,故整理了一下供大家参考。
这个类的使用方法很简单,只要把这个类加入到你的工程(不管是MFC,com,dll都可以)中一起编译就可以了,由于在这个类的实现文件中把定义了一个全局的类对象,所以不用加入任何代码,连#include都不需要。
一、VS2005创建一个基于对话框的工程testCrash
1.将msjexhnd.h和msjexhnd.cpp加入到这个工程
&& 此时编译程序会提示错误fatal error
C1010: unexpected end of file while looking for precompiled header.
Did you forget to add '#include "stdafx.h"' to your source?
2.工程中选中msjexhnd.cpp右键&属性,在c/c++&Precompiled
Headers&Create/Use Precompiled Headers选择Not Using Precompiled
Headers,Ok
&& 编译程序,成功。
3.首先设置工程为Release编译,然后选中工程右键&属性,在c/c++&Output
Files&Assembler Output选择Assembly, Machine Code and Source
这个选项将为每个源文件(*.cpp)生成机器码、汇编码和源代码的对应表,可以在“Release”目录下找到和查看这些文件。
然后在Linker&Debugging&Generate Map File选择Yes
(/MAP),这个选项将生成编译后的函数地址和函数名的对应表。
点击ok后rebuild此工程,可以在Release目录找到testCrash.map,testCrashDlg.cod
4.在工程中加入测试代码,并重新编译程序
void CtestCrashDlg::OnBnClickedOk()
&// TODO: Add your control notification handler
&int *p=NULL;
&*p = 0;&//给空指针赋值
二、查找Crash
1.运行testCrash.exe,点击ok按钮,程序crash
此时会在exe同一目录下生成文件&testCrash.RPT,你可以自己定义此文件位置及名字,具体看MSJExceptionHandler的构造函数。
2.用文本方式打开testCrash.RPT,可以看到这一行
& Fault address:&
01: d:\myown\test\testcrash\release\testCrash.exe
& 注意01:就是程序崩溃的地址
3.打开testCrash.map,可以找到
testCrashDlg.obj
f i testCrashDlg.obj
由于崩溃地址是01:,大于0,小于0,所以可以肯定是CtestCrashDlg::OnBnClickedOk里崩溃。
& 并且相对地址是
进制),代码对应在testCrashDlg.cod因为最后面显示的是testCrashDlg.obj
4.打开testCrashDlg.cod,找到
_TEXT&SEGMENT
CtestCrashDlg::OnBnClickedOk, COMDAT
; _this$ = ecx
; 155& : &// TODO: Add your
control notification handler code here
; 156& : &int *p=NULL;
& 00000&33
c0&& xor& eax,
; 157& : &*p = 0;
& 00002&89
00&& mov& DWORD
PTR [eax], eax
; 158& : &OnOK();
前面带分号的是注释,不带的是汇编代码,汇编代码前面5位数是代码在此函数的相对地址,00002就是偏移2,正是我们要找的崩溃的地方。
它上面的一行是注释实际的源代码; 157& : &*p =
好,终于找到元凶!
在程序release之后,不可避免的会存在一些bug,测试人员和最终用户如何在发现bug之后指导开发人员进行更正呢?在MS的网站上,有一篇名为""的文章,讲述了如何把程序崩溃时的函数调用情况记录为日志的方法,对此感兴趣的读者可以去看一看原文,那里提供源代码和原理的说明。
文章的作者提供了一个MSJExceptionHandler类来实现这一功能,这个类的使用方法很简单,只要把这个类加入到你的工程中并和你的程
序一起编译就可以了,由于在这个类的实现文件中把自己定义为一个全局的类对象,所以,不用加入任何代码,#include都不需要。
当程序崩溃时,MSJExceptionHandler就会把崩溃时的堆栈调用情况记录在一个.rpt文件中,软件的测试人员或最终用户只要把这个
文件发给你,而你使用记事本打开这个文件就可以查看崩溃原因了。你需要在发行软件的时候,为你的程序生成一个或几个map文件,用于定位出错的文件和函
数。(我的中有关于生成map文件和定位错误的详细说明)为了方便使用,这里附上该类的完整代码:
// msjexhnd.h
#ifndef __MSJEXHND_H__
#define __MSJEXHND_H__
class MSJExceptionHandler
MSJExceptionHandler( );
~MSJExceptionHandler( );
SetLogFileName( PTSTR pszLogFileName );
&&& // entry point where control comes on
an unhandled exception
&&& static LONG
WINAPI MSJUnhandledExceptionFilter(
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
PEXCEPTION_POINTERS pExceptionInfo );
&&& // where report info
is extracted and generated
&&& static void
GenerateExceptionReport( PEXCEPTION_POINTERS pExceptionInfo );
// Helper functions
&&& static
LPTSTR GetExceptionString( DWORD dwCode );
&&& static BOOL
GetLogicalAddress(& PVOID addr, PTSTR szModule,
DWORD len,
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
DWORD& section, DWORD& offset );
&&& static void
IntelStackWalk( PCONTEXT pContext );
&&& static int
__cdecl _tprintf(const TCHAR * format, ...);
// Variables used by the class
&&& static TCHAR
m_szLogFileName[MAX_PATH];
&&& static
LPTOP_LEVEL_EXCEPTION_FILTER m_previousF
&&& static
HANDLE m_hReportF
extern MSJExceptionHandler
g_MSJExceptionH&&
//& global instance of
// msjexhnd.cpp
//==========================================
// Matt Pietrek
// Microsoft Systems Journal, April 1997
// FILE: MSJEXHND.CPP
//==========================================
#include "msjexhnd.h"
//============================== Global
Variables =============================
// Declare the static variables of the MSJExceptionHandler
TCHAR MSJExceptionHandler::m_szLogFileName[MAX_PATH];
LPTOP_LEVEL_EXCEPTION_FILTER
MSJExceptionHandler::m_previousF
HANDLE MSJExceptionHandler::m_hReportF
MSJExceptionHandler g_MSJExceptionH&
// Declare global instance of
//============================== Class
Methods =============================
//=============
// Constructor
//=============
MSJExceptionHandler::MSJExceptionHandler( )
&&& // Install the unhandled exception filter
m_previousFilter =
SetUnhandledExceptionFilter(MSJUnhandledExceptionFilter);
// Figure out what the report file will be
named, and store it away
GetModuleFileName( 0, m_szLogFileName, MAX_PATH );
Look for the '.' before the "EXE" extension.&
Replace the extension
&&& // with
&&& PTSTR pszDot
= _tcsrchr( m_szLogFileName, _T('.') );
&&& if ( pszDot
pszDot++;&& // Advance past the
if ( _tcslen(pszDot) &= 3 )
&&&&&&&&&&&
_tcscpy( pszDot, _T("RPT") );&&
// "RPT" -& "Report"
//============
// Destructor
//============
MSJExceptionHandler::~MSJExceptionHandler( )
SetUnhandledExceptionFilter( m_previousFilter );
//==============================================================
// Lets user change the name of the report file to be
//==============================================================
void MSJExceptionHandler::SetLogFileName( PTSTR pszLogFileName
&&& _tcscpy(
m_szLogFileName, pszLogFileName );
//===========================================================
// Entry point where control comes on an unhandled exception
//===========================================================
LONG WINAPI MSJExceptionHandler::MSJUnhandledExceptionFilter(
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
PEXCEPTION_POINTERS pExceptionInfo )
m_hReportFile = CreateFile( m_szLogFileName,
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
GENERIC_WRITE,
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
OPEN_ALWAYS,
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
FILE_FLAG_WRITE_THROUGH,
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
m_hReportFile )
SetFilePointer( m_hReportFile, 0, 0, FILE_END );
GenerateExceptionReport( pExceptionInfo );
CloseHandle( m_hReportFile );
m_hReportFile = 0;
m_previousFilter )
return m_previousFilter( pExceptionInfo );
return EXCEPTION_CONTINUE_SEARCH;
//===========================================================================
// Open the report file, and write the desired information to
it.& Called by
MSJUnhandledExceptionFilter&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
//===========================================================================
void MSJExceptionHandler::GenerateExceptionReport(
PEXCEPTION_POINTERS pExceptionInfo )
&&& // Start out with a
&&& _tprintf(
_T("//=====================================================\n")
PEXCEPTION_RECORD pExceptionRecord =
pExceptionInfo-&ExceptionR
// First print information about the type of
_tprintf(&& _T("Exception code: X
&&&&&&&&&&&&&&&
pExceptionRecord-&ExceptionCode,
&&&&&&&&&&&&&&&
GetExceptionString(pExceptionRecord-&ExceptionCode) );
// Now print information about where the
fault occured
szFaultingModule[MAX_PATH];
GetLogicalAddress(&
pExceptionRecord-&ExceptionAddress,
&&&&&&&&&&&&&&&&&&&&&&&
szFaultingModule,
&&&&&&&&&&&&&&&&&&&&&&&
sizeof( szFaultingModule ),
&&&&&&&&&&&&&&&&&&&&&&&
section, offset );
&&& _tprintf(
_T("Fault address:& X X:X %s\n"),
&&&&&&&&&&&&&&&
pExceptionRecord-&ExceptionAddress,
&&&&&&&&&&&&&&&
section, offset, szFaultingModule );
&&& PCONTEXT
pCtx = pExceptionInfo-&ContextR
// Show the registers
&&& #ifdef
_M_IX86& // Intel Only!
&&& _tprintf(
_T("\nRegisters:\n") );
_tprintf(_T("EAX:X\nEBX:X\nECX:X\nEDX:X\nESI:X\nEDI:X\n"),
&&&&&&&&&&&
pCtx-&Eax, pCtx-&Ebx, pCtx-&Ecx, pCtx-&Edx,
pCtx-&Esi, pCtx-&Edi );
&&& _tprintf(
_T("CS:EIP:X:X\n"), pCtx-&SegCs, pCtx-&Eip );
&&& _tprintf(
_T("SS:ESP:X:X& EBP:X\n"),
&&&&&&&&&&&&&&&
pCtx-&SegSs, pCtx-&Esp, pCtx-&Ebp );
&&& _tprintf(
_T("DS:X& ES:X&
FS:X& GS:X\n"),
&&&&&&&&&&&&&&&
pCtx-&SegDs, pCtx-&SegEs, pCtx-&SegFs, pCtx-&SegGs
&&& _tprintf(
_T("Flags:X\n"), pCtx-&EFlags );
// Walk the stack using x86 specific
IntelStackWalk( pCtx );
&&& _tprintf(
_T("\n") );
//======================================================================
// Given an exception code, returns a pointer to a static string
// description of the
exception&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
//======================================================================
LPTSTR MSJExceptionHandler::GetExceptionString( DWORD dwCode
&&& #define
EXCEPTION( x ) case EXCEPTION_##x: return _T(#x);
&&& switch (
EXCEPTION( ACCESS_VIOLATION )
EXCEPTION( DATATYPE_MISALIGNMENT )
EXCEPTION( BREAKPOINT )
EXCEPTION( SINGLE_STEP )
EXCEPTION( ARRAY_BOUNDS_EXCEEDED )
EXCEPTION( FLT_DENORMAL_OPERAND )
EXCEPTION( FLT_DIVIDE_BY_ZERO )
EXCEPTION( FLT_INEXACT_RESULT )
EXCEPTION( FLT_INVALID_OPERATION )
EXCEPTION( FLT_OVERFLOW )
EXCEPTION( FLT_STACK_CHECK )
EXCEPTION( FLT_UNDERFLOW )
EXCEPTION( INT_DIVIDE_BY_ZERO )
EXCEPTION( INT_OVERFLOW )
EXCEPTION( PRIV_INSTRUCTION )
EXCEPTION( IN_PAGE_ERROR )
EXCEPTION( ILLEGAL_INSTRUCTION )
EXCEPTION( NONCONTINUABLE_EXCEPTION )
EXCEPTION( STACK_OVERFLOW )
EXCEPTION( INVALID_DISPOSITION )
EXCEPTION( GUARD_PAGE )
EXCEPTION( INVALID_HANDLE )
If not one of the "known" exceptions, try to get the string
&&& // from
NTDLL.DLL's message table.
&&& static
TCHAR szBuffer[512] = { 0 };
FormatMessage(& FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_FROM_HMODULE,
&&&&&&&&&&&&&&&&&&&
GetModuleHandle( _T("NTDLL.DLL") ),
&&&&&&&&&&&&&&&&&&&
dwCode, 0, szBuffer, sizeof( szBuffer ), 0 );
&&& return
//==============================================================================
// Given a linear address, locates the module, section, and offset
containing&
address.&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
// Note: the szModule paramater buffer is an output buffer of
length specified
// by the len parameter (in
characters!)&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
//==============================================================================
BOOL MSJExceptionHandler::GetLogicalAddress(
PVOID addr, PTSTR szModule, DWORD len, DWORD& section,
DWORD& offset )
MEMORY_BASIC_INFORMATION
!VirtualQuery( addr, &mbi, sizeof(mbi) ) )
return FALSE;
hMod = (DWORD)mbi.AllocationB
!GetModuleFileName( (HMODULE)hMod, szModule, len ) )
return FALSE;
// Point to the DOS header in
PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)hM
// From the DOS header, find the NT (PE)
PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(hMod +
pDosHdr-&e_lfanew);
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION( pNtHdr );
&&& DWORD rva
= (DWORD)addr - hM // RVA is offset from module load address
Iterate through the section table, looking for the one that
encompasses
&&& // the
linear address.
(&& unsigned i = 0;
&&&&&&&&&&&
i & pNtHdr-&FileHeader.NumberOfS
&&&&&&&&&&&
i++, pSection++ )
DWORD sectionStart = pSection-&VirtualA
DWORD sectionEnd = sectionStart
&&&&&&&&&&&&&&&&&&&
+ max(pSection-&SizeOfRawData,
pSection-&Misc.VirtualSize);
// Is the address in this
section???
if ( (rva &= sectionStart) && (rva &= sectionEnd)
&&&&&&&&&&&
// Yes, address is in the section.& Calculate
section and offset,
&&&&&&&&&&&
// and store in the "section" & "offset" params, which
&&&&&&&&&&&
// passed by reference.
&&&&&&&&&&&
section = i+1;
&&&&&&&&&&&
offset = rva - sectionS
&&&&&&&&&&&
return TRUE;
&&& return
FALSE;&& //
Should never get here!
//============================================================
// Walks the stack, and writes the results to the report file
//============================================================
void MSJExceptionHandler::IntelStackWalk( PCONTEXT pContext )
&&& _tprintf(
_T("\nCall stack:\n") );
&&& _tprintf(
_T("Address&&
Logical addr& Module\n") );
&&& DWORD pc
= pContext-&E
&&& PDWORD
pFrame, pPrevF
&&& pFrame =
(PDWORD)pContext-&E
TCHAR szModule[MAX_PATH] = _T("");
DWORD section = 0, offset = 0;
GetLogicalAddress((PVOID)pc,
szModule,sizeof(szModule),section,offset );
_tprintf( _T("X& X& X:X
&&&&&&&&&&&&&&&&&&&
pc, pFrame, section, offset, szModule );
pc = pFrame[1];
pPrevFrame = pF
pFrame = (PDWORD)pFrame[0]; // precede to
next higher frame on stack
if ( (DWORD)pFrame & 3
// Frame pointer must be aligned on
a&&&&&&&&&&&
&&&&&&&&&&&&&&&&&
// DWORD boundary.& Bail if
if ( pFrame &= pPrevFrame )
&&&&&&&&&&&
// Can two DWORDs be read from the supposed
address?&&&&&&&&&
if ( IsBadWritePtr(pFrame, sizeof(PVOID)*2) )
&&&&&&&&&&&
&&& } while (
//============================================================================
// Helper function that writes to the report file, and allows the
user to use
// printf style
formating&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
//============================================================================
int __cdecl MSJExceptionHandler::_tprintf(const TCHAR * format,
szBuff[1024];
&&& va_list
&&& va_start(
argptr, format );
&&& retValue =
wvsprintf( szBuff, format, argptr );
&&& va_end(
WriteFile( m_hReportFile, szBuff, retValue * sizeof(TCHAR),
&cbWritten, 0 );
&&& return
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

我要回帖

更多关于 iphone崩溃日志是什么 的文章

 

随机推荐