Dump
調(diào)用堆棧的原理以及異常信息的反饋
動(dòng)機(jī):
在游戲開發(fā)過程中,我們利用
QA
部門來做產(chǎn)品的質(zhì)量保證,盡可能將絕大部分錯(cuò)誤消化在內(nèi)部,保證游戲的版本質(zhì)量,但是
QA
部門畢竟有他的局限性,盡管經(jīng)過嚴(yán)格的測(cè)試也很難保證將所有的問題一網(wǎng)打盡.
通過在
Log
中轉(zhuǎn)儲(chǔ)的錯(cuò)誤信息,我們可以進(jìn)一步找出問題,但是
Log
文件產(chǎn)生在終端,我們拿到的也僅僅是公司內(nèi)部測(cè)試部門產(chǎn)生的
Log
文件,顯然公司內(nèi)部得到的信息是很有限的,如果能從玩家那里拿到異常信息,我們才能最快的去解決問題,盡可能在錯(cuò)誤產(chǎn)生重大影響之前將其解決,所以我們有必要從被動(dòng)的獲取異常信息,轉(zhuǎn)為主動(dòng)去獲取.
可行性
:
在錯(cuò)誤發(fā)生時(shí)
Dump
調(diào)用堆棧,可以讓我們知道錯(cuò)誤發(fā)生的位置,這比已往普通的
LOG
更加有效的多.我們可以將出錯(cuò)的堆棧地址反饋回來.這一切在終端出現(xiàn)異常的時(shí)候自動(dòng)進(jìn)行.
Windows
操作系統(tǒng)提供的
SEH
結(jié)構(gòu)化異常機(jī)制可能讓我們?cè)诔绦虮罎⒌乃查g處理這些事情.
效率問題
:
SEH
是
windows
的異常機(jī)制,除非在編譯時(shí)候特別指定不使用,否則總有默認(rèn)的
SEH
處理機(jī)制,
kernel32.dll
中有默認(rèn)的
SEH
處理接口,當(dāng)我們需要自己處理異常的時(shí)候,我們的處理點(diǎn)會(huì)掛接在異常處理鏈的最前端,這種鏈類似
Hook
的鏈.鏈的頭部放在
fs[0]
的位置.也就是說效率的問題是可以不必考慮,
具體實(shí)現(xiàn)
:
通過閱讀反匯編代碼可以了解函數(shù)調(diào)用過程中堆棧的結(jié)構(gòu)
:
1
函數(shù)調(diào)用時(shí)
CALL
將下一行指令地址壓入堆棧
2
函數(shù)運(yùn)行第一行會(huì)將
EBP
壓入堆棧
3
保存當(dāng)前堆棧地址到
EBP (mov ebp,esp)
再遇到
call
時(shí)從第一步執(zhí)行,所以每次第二步壓入堆棧的都是上一層函數(shù)調(diào)用的
ESP
地址,而這個(gè)地址
+4
字節(jié)偏移則是當(dāng)前調(diào)用函數(shù)返回后的下一條指令,也就是上一層函數(shù)的地址,所以我們只要知道當(dāng)前函數(shù)的
EBP
值
(
也就是當(dāng)前函數(shù)的棧頂
)
就能夠遍歷得到所有調(diào)用堆棧層次.

我們將windows SEH 結(jié)構(gòu)化異常引入后,可以在異常發(fā)生的時(shí)候得到當(dāng)前的EBP值,從而通過這個(gè)值得到整個(gè)調(diào)用堆棧的地址.
在發(fā)布工程的時(shí)候,我們只需要生成map文件,就可以通過這個(gè)地址得到崩潰位置.使用HTTP GET 或POST方式可以將我們所需要的崩潰信息提交到我們指定的網(wǎng)站.這種方式只是通過URL參數(shù)來提交數(shù)據(jù),只需要使用API InternetOpenUrl就可以很方便的將信息提交.此外如果不使用HTTP方式,我們也可以在這個(gè)時(shí)候創(chuàng)建新的socket 對(duì)指定的服務(wù)器進(jìn)行連接來傳輸數(shù)據(jù).
static TCHAR hdrs[] = _T("Content-Type: application/x-www-form-urlencoded");
static const TCHAR* accept= _T("Accept: */*");
static TCHAR action[]=_T("datecomit.aspx");//預(yù)提交的頁面
static TCHAR server[]=_T("192.168.9.119");//提交的server地址


static TCHAR frmdata[1024] ={0};
_tcscpy(frmdata,_T("message=this is a test message");
//提交數(shù)據(jù), 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));
此后我們只需要定期觀察所提交的內(nèi)容,便可以立即得知是否有異常出現(xiàn).根據(jù)同一異常出現(xiàn)的幾率可以得知是否是致命的錯(cuò)誤,是否需要緊急更新.