誰動了我的指標? (轉)
誰動了我的指標?
譯者序:
本文介紹了一種在過程中尋找懸掛指標(野指標)的方法,這種方法是透過對new和delete運算子的過載來實現的。
這種方法不是完美的,它是以除錯期的洩露為代價來實現的,因為文中出現的程式碼是絕不能出現在一個最終釋出的產品中的,只能在除錯時使用。
在VC中,在除錯環境下,可以簡單的透過把new替換成DE_NEW來實現功能更強更方便的指標檢測,詳情可參考MSDN。DEBUG_NEW的實現思路與本文有相通的地方,因此文章中介紹的方法雖然不是最佳的,但還算實用,更重要的是,它提供給我們一種新的思路。
簡介:
前幾天發生了這樣一件事,我正在除錯一個,這個程式用了一大堆亂七八糟的指標來處理一個連結串列,最終在一個指向連結串列結點的指標上出了問題。我們預計它應當指向的是一個虛基類的。我想到第一個問題是:指標所指的地方真的有一個物件嗎?出問題的指標值可以被4整除,並且不是NULL的,所以可以斷定它曾經是一個有效的指標。透過使用的記憶體檢視視窗(View->Debug ->Memory)我們發現這個指標所指的資料是FE EE FE EE FE EE ...這通常意味著記憶體是曾經是被分配了的,但現在卻處於一種未分配的狀態。不知是誰、在什麼地方把我的指標所指的記憶體區域給釋放掉了。我想要找出一種方案來查出我的資料到底是怎麼會被釋放的。
背景:
我最終透過過載了new和delete運算子找到了我丟失的資料。當一個被時,引數會首先被壓到棧上後,然後返回地址也會被壓到棧上。我們可以在new和delete運算子的函式中把這些資訊從棧上提取出來,幫助我們除錯程式。
程式碼:
在經歷了幾次錯誤的猜測後,我決定求助於過載new和delete運算子來幫我找到我的指標所指向的資料。下面的new運算子的實現把返回地址從棧上提了出來。這個返回地址位於傳遞過來的引數和第一個區域性變數的地址之間。的設定、呼叫函式的方法、的體系結構都會引響到這個返回地址的實際位置,所以您在使用下面程式碼的時候,要根據您的實際情況做一些調整。一旦new運算子獲得了返回地址,它就在將要實際分配的記憶體前面分配額外的16個位元組的空間來存放這個返回地址和實際的分配的記憶體大小,並且把實際要分配的記憶體塊首地址返回。
對於delete運算子,你可以看到,它不再釋放空間。它用與new同樣的方法把返回地址提取出來,寫到實際分配空間大小的後面(譯者注:就是上面分配的16個位元組的第9到第12個位元組),在最後四個位元組中填上DE AD BE EF(譯者注:四個十六進位制數,當成單詞來看正好是dead beef,用來表示記憶體已釋放真是很形象!),並且把剩餘的空間(譯者注:就是原本實際應該分配而現在應該要釋放掉的空間)都填上一個重複的值。
現在,如果程式由於一個錯誤的指標而出錯,我只需開啟記憶體檢視視窗,找到出錯的指標所指的地方,再往前找16個位元組。這裡的值就是呼叫new運算子的地址,接下來四個位元組就是實際分配的記憶體大小,第三個四個位元組是呼叫delete運算子的地址,最後四個位元組應該是DE AD BE EF。接下的實際分配過的記憶體內容應該是77 77 77 77。
要透過這兩個返回地址在源程式中分別找到對應的new和delete,可以這樣做:首先把表示地址的四個位元組的內容倒序排一下,這樣才能得到真正的地址,這裡因為在平臺上位元組序是低位在前的。下一步,在上右擊點選,選“Go To Diassembly”。在反的視窗上的左邊一欄就是機器程式碼對應的記憶體地址。按Ctrl + G或選擇Edit->Go To...並輸入你找到的地址之一。反彙編的視窗就將滾動到對應的new或delete的函式呼叫位置。要回到源程式只需再次右鍵單擊,選擇“Go To ”。您就可以看到相應的new或delete的呼叫了。
現在您就可以很方便的找出您的資料是何時丟失的了。至於要找出為什麼delete會被呼叫,就要靠您自己了。
#include
void * ::operator new(size_t size)
{
int stackVar;
unsigned long stackVarAddr = (unsigned long)&stackVar;
unsigned long argAddr = (unsigned long)&size;
void ** retAddrAddr = (void **)(stackVarAddr/2 + argAddr/2 + 2);
void * retAddr = * retAddrAddr;
unsigned char *retBuffer = (unsigned char*)malloc(size + 16);
memset(retBuffer, 0, 16);
memcpy(retBuffer, &retAddr, sizeof(retAddr));
memcpy(retBuffer + 4, &size, sizeof(size));
return retBuffer + 16;
}
void ::operator delete(void *buf)
{
int stackVar;
if(!buf)
return;
unsigned long stackVarAddr = (unsigned long)&stackVar;
unsigned long argAddr = (unsigned long)&buf;
void ** retAddrAddr = (void **)(stackVarAddr/2 + argAddr/2 + 2);
void * retAddr = * retAddrAddr;
unsigned char* buf2 = (unsigned char*)buf;
buf2 -= 8;
memcpy(buf2, &retAddr, sizeof(retAddr));
size_t size;
buf2 -= 4;
memcpy(&size, buf2, sizeof(buf2));
buf2 += 8;
buf2[0] = 0xde;
buf2[1] = 0xad;
buf2[2] = 0xbe;
buf2[3] = 0xef;
buf2 += 4;
memset(buf2, 0x7777, size);
// deallocating destroys saved addresses, so don't
// buf -= 16;
// free(buf);
}
其它值得關注的地方:
這段程式碼同樣可以用於記憶體洩露的檢測。只需修改delete運算子使它真正的去釋放記憶體,並且在程式退出前,用__heapwalk遍歷所有已分配的記憶體塊並把呼叫new的地址提取出來,這就將得到一份沒有被delete匹配的new呼叫列表。
還要注意的是:這裡列出的程式碼只能在除錯的時候去使用,如果你把它段程式碼放到最終的產品中,會導致程式執行時記憶體被大量的消耗。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-956795/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 誰動了我的 Redis ?Redis
- 誰動了我的MySQL?MySql
- 我眼中的指標 (轉)指標
- 誰動了我的程式碼!?
- ES資料沒了?誰動了我的資料?
- 誰動了我的DevOps:DevOps風險測繪dev
- Js 跟手轉動的羅盤指標JS指標
- 誰說Java無指標, JAVA連結串列指標也好煩 - Java 指標迴歸Java指標
- 指標 (轉)指標
- 誰動了你的mail(),PHP?AIPHP
- 指標問題的一點體會(區別 [指向指標的指標] 與 [指標的指標] .) (轉)指標
- 誰動了我的 Linux?原來 history 可以這麼強大!Linux
- 誰動了我的記憶體之 PHP 記憶體溢位PHP記憶體溢位
- 誰動了我的記憶體之PHP記憶體溢位PHP記憶體溢位
- 誰動了我的資料?如何防止資料偷偷溜走?
- JavaScript誰動了你的程式碼JavaScript
- CR指標(轉載)指標
- 淺談指標 (轉)指標
- 《資料安全法》正式實施 動了誰的乳酪?紅了誰的櫻桃?
- 誰阻擋了Linux的步伐?(轉)Linux
- 不同人對BUG的反應,程式設計師:誰動了我的程式碼?程式設計師
- C++指標轉換C++指標
- 人氣指標(轉載)指標
- 隨機指標(轉載)隨機指標
- 改變滑鼠指標 (轉)指標
- SMART POINTER(智慧指標) (轉)指標
- NULL 指標、零指標、野指標Null指標
- 《資料安全法》動了誰的“乳酪”
- 解析VC++6中的指標 (轉)C++指標
- C#中的函式指標 (轉)C#函式指標
- 誰貪佔了我的系統資源 php-fpmPHP
- 利用動態建立自動化介面實現VB的函式指標呼叫 (轉)函式指標
- 布林線指標(轉載)指標
- 寶塔線指標(轉載)指標
- 震盪量指標(轉載)指標
- Guru of the week:#18 迭代指標. (轉)指標
- 函式指標淺談 (轉)函式指標
- 野指標 空指標指標