Dump调用堆栈的原理以及异常信息的反馈

Dump 调用堆栈的原理以及异常信息的反馈

动机:

在游戏开发过程中,我们利用 QA 部门来做产品的质量保证,尽可能将绝大部分错误消化在内部,保证游戏的版本质量,但是 QA 部门毕竟有他的局限性,尽管经过严格的测试也很难保证将所有的问题一网打尽.

 

通过在 Log 中转储的错误信息,我们可以进一步找出问题,但是 Log 文件产生在终端,我们拿到的也仅仅是公司内部测试部门产生的 Log 文件,显然公司内部得到的信息是很有限的,如果能从玩家那里拿到异常信息,我们才能最快的去解决问题,尽可能在错误产生重大影响之前将其解决,所以我们有必要从被动的获取异常信息,转为主动去获取.

 

可行性 :

       在错误发生时 Dump 调用堆栈,可以让我们知道错误发生的位置,这比已往普通的 LOG 更加有效的多.我们可以将出错的堆栈地址反馈回来.这一切在终端出现异常的时候自动进行. Windows 操作系统提供的 SEH 结构化异常机制可能让我们在程序崩溃的瞬间处理这些事情.

 

效率问题 :

       SEH windows 的异常机制,除非在编译时候特别指定不使用,否则总有默认的 SEH 处理机制, kernel32.dll 中有默认的 SEH 处理接口,当我们需要自己处理异常的时候,我们的处理点会挂接在异常处理链的最前端,这种链类似 Hook 的链.链的头部放在 fs[0] 的位置.也就是说效率的问题是可以不必考虑,

 

 

具体实现 :

       通过阅读反汇编代码可以了解函数调用过程中堆栈的结构 :

      

       1 函数调用时 CALL 将下一行指令地址压入堆栈

       2 函数运行第一行会将 EBP 压入堆栈

       3 保存当前堆栈地址到 EBP (mov ebp,esp)

      

       再遇到 call 时从第一步执行,所以每次第二步压入堆栈的都是上一层函数调用的 ESP 地址,而这个地址 +4 字节偏移则是当前调用函数返回后的下一条指令,也就是上一层函数的地址,所以我们只要知道当前函数的 EBP ( 也就是当前函数的栈顶 ) 就能够遍历得到所有调用堆栈层次.

       dumpebp.jpg

我们将windows SEH 结构化异常引入后,可以在异常发生的时候得到当前的EBP值,从而通过这个值得到整个调用堆栈的地址.

 

在发布工程的时候,我们只需要生成map文件,就可以通过这个地址得到崩溃位置.使用HTTP GET 或POST方式可以将我们所需要的崩溃信息提交到我们指定的网站.这种方式只是通过URL参数来提交数据,只需要使用API InternetOpenUrl就可以很方便的将信息提交.此外如果不使用HTTP方式,我们也可以在这个时候创建新的socket 对指定的服务器进行连接来传输数据.

    
    static TCHAR hdrs[] 
= _T("Content-Type: application/x-www-form-urlencoded"); 
    static 
const TCHAR* accept= _T("Accept: */*"); 
        static TCHAR action[]=_T("datecomit.aspx");//预提交的页面
        static TCHAR server[]=_T("192.168.9.119");//提交的server地址

    static TCHAR frmdata[
1024={0}; 
    _tcscpy(frmdata,_T("message=this is a test message");  //提交数据, message为提交名字   
    
    
// for clarity, error-checking has been removed 
    HINTERNET hSession 
= InternetOpen("MyAgent"
    INTERNET_OPEN_TYPE_PRECONFIG, 
NULLNULL0); 
    HINTERNET hConnect 
= InternetConnect(hSession, server
    INTERNET_DEFAULT_HTTP_PORT, 
NULLNULL, INTERNET_SERVICE_HTTP, 01); 
    HINTERNET hRequest 
= HttpOpenRequest(hConnect, "POST", actionNULLNULL&accept, 01); 
    HttpSendRequest(hRequest, hdrs, strlen(hdrs), frmdata, strlen(frmdata)); 

 

此后我们只需要定期观察所提交的内容,便可以立即得知是否有异常出现.根据同一异常出现的几率可以得知是否是致命的错误,是否需要紧急更新.

 


posted on 2007-03-27 16:32 修一居士 阅读(5274) 评论(7)  编辑 收藏 引用

评论

# re: Dump调用堆栈的原理以及异常信息的反馈 2007-03-28 15:47 Navi

Windows 操作系统提供的 SHE 结构化异常机制可能让我们在程序崩溃的瞬间处理这些事情

SEH not SHE.  回复  更多评论   

# re: Dump调用堆栈的原理以及异常信息的反馈 2007-03-29 13:31 南斗

笔误,已改  回复  更多评论   

# re: Dump调用堆栈的原理以及异常信息的反馈 2007-05-11 22:02 nick

坦白说, 通过这种方式获取调用栈并不理想.
首先是繁琐.
其次是有局限. 遇上 FPO 就不行了. 而现在的程序, 尤其是游戏程序, 哪个不是优化到极致.

其实最好的办法还是 minidump. 一个几十K的 dump 文件就可以包含完整的调用栈了. 再大一点的文件就可以包含临时变量、参数等的值了.
可以看看 www.debuginfo.com 上面关于 minidump 的文章.

我的 blog 上也有一个例子. 基本上是抄 debuginfo 的. 呵呵  回复  更多评论   

# re: Dump调用堆栈的原理以及异常信息的反馈 2007-05-26 00:29 南斗

获取简单的堆栈地址已经完全够用了,用minidump弄那么大的dump文件我还要传回服务器你不觉得很恐怖吗,利用调用堆栈地址在map文件中就可以找到所有的问题了,我在我的项目中一直用这个方法 ;)  回复  更多评论   

# re: Dump调用堆栈的原理以及异常信息的反馈 2008-03-20 11:01 Bright

@nick
不错啊!终于找到能产生MiniDump文件的方法了,非常感谢!!  回复  更多评论   

# re: Dump调用堆栈的原理以及异常信息的反馈 2008-07-29 16:07 ershu

不是很好的方法。还有其他的方法,我们也使用过。都不是很好。信息不准确。

minidump是我们试过最好的方法。微软自己也用它。

minidump压缩一下以后才10几k?上传到你们的服务器就不行了?
我们的服务器工作得很好。  回复  更多评论   

# re: Dump调用堆栈的原理以及异常信息的反馈 2010-07-07 12:00 南斗

@ershu
呵呵 这要看你的具体应用了,我这里应用在游戏客户端的崩溃记录,同时在线的用户数量可能有几十万,毕竟为了节约成本只会配置这样一台崩溃信息记录服务器,所以上传还是尽量越少越好。

而实际上我们也不需要那么详细的崩溃记录信息,minidump大部分记录了当前的进程、线程、以及内存状况,实际上我们只需要简单的堆栈信息就够用了。

对于信息记录不准确的问题,我想这个一般都是因为当前指令指针错误,跳跃到了非法的地址,而无法通过ebp反推出堆栈信息,目前应该是无论何种方法都解决不了的。  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理


导航

<2007年7月>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

统计

常用链接

留言簿(3)

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜