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
值
(
也就是当前函数的栈顶
)
就能够遍历得到所有调用堆栈层次.
我们将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, NULL, NULL, 0);
HINTERNET hConnect = InternetConnect(hSession, server,
INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 1);
HINTERNET hRequest = HttpOpenRequest(hConnect, "POST", action, NULL, NULL, &accept, 0, 1);
HttpSendRequest(hRequest, hdrs, strlen(hdrs), frmdata, strlen(frmdata));
此后我们只需要定期观察所提交的内容,便可以立即得知是否有异常出现.根据同一异常出现的几率可以得知是否是致命的错误,是否需要紧急更新.