日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

怎样重建一个损坏的调用堆栈(callstack)

發布時間:2025/3/15 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 怎样重建一个损坏的调用堆栈(callstack) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
原文作者:Aaron Ballman
原文時間:2011年07月04日
原文地址:http://blog.aaronballman.com/2011/07/reconstructing-a-corrupted-stack-crawl/

翻譯:magictong

時間:2014年05月29日夜

后記:可惜原始的DUMP文件作者并沒有上傳



? ? ? ? 在我的日常工作中,我經常閱讀來之微軟WinQual(譯注:https://sysdev.microsoft.com/ http://en.wikipedia.org/wiki/Winqual)的報告。這些報告里面一般包含著dump文件(譯注:崩潰轉儲文件,我們一般都是叫dump文件,是一種軟件崩潰之后產生的文件,可用于事后調試),從這些dump文件里面我可以分析出一些常用的軟件里面到底出了什么問題,造成它崩潰了??偠灾?#xff0c;這是一個超贊的系統,我強烈建議各個獨立軟件開發商(原文:ISV)去上面注冊(尤其是這個系統對任何人都是免費的,只要你的可執行文件是正確簽名的)。最近我拿到了一個堆棧已經被嚴重破壞了的dump文件,我想和大家討論一下怎么使用Windbg工具來重建它的調用堆棧(callstack)。

? ? ? ??在開始之前,讓我們先看看一個原始的調用堆棧是什么樣子的,在Windbg里面運行“k”命令即可。
? ? ? ??0:000> k
? ? ? ??ChildEBP RetAddr ?
? ? ? ??028b89cc 77c75350 ntdll!KiFastSystemCallRet
? ? ? ??028b89d0 77c4b208 ntdll!ZwTerminateProcess+0xc
? ? ? ??028b89e0 763e41ec ntdll!RtlExitUserProcess+0x7a
? ? ? ??028b89f4 10056386 kernel32!ExitProcess+0x12
? ? ? ??WARNING: Stack unwind information not available. Following frames may be wrong.
? ? ? ??028b89fc 100565a0 EyeOneIO!I1_SynchronizeWhitebases+0xf0f6
? ? ? ??028b8a0c 10054803 EyeOneIO!I1_SynchronizeWhitebases+0xf310
? ? ? ??00000000 00000000 EyeOneIO!I1_SynchronizeWhitebases+0xd573

? ? ? ??從上面的調用堆棧來看,有幾個特征表明這個堆棧已經被破壞了。首先,調用堆棧的基址不可能從0x00000000開始。通常情況下,它從main函數的入口地址開始,或者從一個線程的入口地址開始,但是從上面的調用堆棧來看我們沒看看到這個特征。另外,Windbg也發出了“Stack unwind information not available. Following frames may be wrong.”的警告(譯注:這句警告的意思就是說,下面的棧幀可能是錯誤的)。

? ? ? ??第一步,既然堆棧已經錯誤了,我們當然需要重建當前執行現成的堆棧,并找到當前現成堆棧的起始位置。這里有個簡單的擴展命令可以查看,使用!teb即可(譯注:!teb用于查看當前線程執行環境):

? ? ? ??0:000> !teb
? ? ? ??TEB at 7ffdb000
? ? ? ??? ? ExceptionList: ? ? ? ?028b8a28
? ? ? ??? ? StackBase: ? ? ? ? ? ?028c0000
? ? ? ??? ? StackLimit: ? ? ? ? ? 028b6000
? ? ? ??? ? SubSystemTib: ? ? ? ? 00000000
? ? ? ??? ? FiberData: ? ? ? ? ? ?00001e00
? ? ? ??? ? ArbitraryUserPointer: 00000000
? ? ? ??? ? Self: ? ? ? ? ? ? ? ? 7ffdb000
? ? ? ??? ? EnvironmentPointer: ? 00000000
? ? ? ??? ? ClientId: ? ? ? ? ? ? 00000a4c . 00000e3c
? ? ? ??? ? RpcHandle: ? ? ? ? ? ?00000000
? ? ? ??? ? Tls Storage: ? ? ? ? ?7ffdb02c
? ? ? ??? ? PEB Address: ? ? ? ? ?7ffdf000
? ? ? ??? ? LastErrorValue: ? ? ? 14007
? ? ? ??? ? LastStatusValue: ? ? ?c0150008
?? ? ? ???? Count Owned Locks: ? ?0
?? ? ? ???? HardErrorMode: ? ? ? ?0

? ? ? ??看上面!teb命令顯示的結果里面,StackBase和StackLimit告訴了我們當前線程的堆棧在內存中的范圍,因此我們現在可以轉儲這個范圍內的地址,然后從里面尋找一些有意義和有用的東西(譯注:就是把內存地址和對應的符號地址對應起來,然后尋找和當前的線程有關的調用堆棧)。Windbg里面有個專門的dds命令就是用來做這個事情的,dds命令需要你指定一個起始地址,然后它從給定的起始地址開始轉儲一定范圍內的地址,并且嘗試把每個地址里面的內容和符合(symbol)對應起來(譯注:假如可以對應的話)。dds轉儲的內容包含三列數據,第一列顯示的是順序遞增的地址,第二列是顯示地址里面的數據,第三列是符號名稱,如果地址里面的數據可以被成功解析為一個符號的話,否則第三列就是顯示的空白。

把真實的棧轉儲出來看看(省略了一些無關項):
(譯注:使用命令 dds 028b6000,要顯示更后面的內容可以在028b6000的后面加上一個偏移之后再對新地址使用 dds 命令)

? ? ? ??028b6000 ?00000000
? ? ? ??...
? ? ? ??028bf9d8 ?00000000
? ? ? ??028bf9dc ?00000000
? ? ? ??028bf9e0 ?79035b7f
? ? ? ??028bf9e4 ?028bfa1c
? ? ? ??028bf9e8 ?6e760b5b i1IO!i1IO::measureOneStrip+0xbb
? ? ? ??028bf9ec ?42b840fc
? ? ? ??...
? ? ? ??028bfa18 ?00000000
? ? ? ??028bfa1c ?028bfd98
? ? ? ??028bfa20 ?6e763387 i1IO!i1IO::_measureSingleRowScanThreaded+0x1467
? ? ? ??028bfa24 ?42b840fc
? ? ? ??...
? ? ? ??028bfd94 ?00000006
? ? ? ??028bfd98 ?028bfe2c
? ? ? ??028bfd9c ?6e761062 i1IO!i1IO::_advancedMeasureThreaded+0x222
? ? ? ??028bfda0 ?013a8520
? ? ? ??028bfda4 ?79035e2e
? ? ? ??...
? ? ? ??028bfe28 ?00000000
? ? ? ??028bfe2c ?028bfe38
? ? ? ??028bfe30 ?763ed0e9 kernel32!BaseThreadInitThunk+0xe
? ? ? ??028bfe34 ?012118e0
? ? ? ??028bfe38 ?028bfe78
? ? ? ??028bfe3c ?77c516c3 ntdll!__RtlUserThreadStart+0x23
? ? ? ??028bfe40 ?012118e0
? ? ? ??...
? ? ? ??028bfe74 ?00000000
? ? ? ??028bfe78 ?028bfe90
? ? ? ??028bfe7c ?77c51696 ntdll!_RtlUserThreadStart+0x1b
? ? ? ??028bfe80 ?6e760e40 i1IO!i1IO::_advancedMeasureThreaded
? ? ? ??...
? ? ? ??028c0000 ?????????

? ? ? ??實際上轉儲出來的堆棧比上面列出來的大得多,不過為了簡單起見,我只保留一些相關的部分。

? ? ? ??現在要做的第一件事情就是定位到callstack的起始位置。在這個例子里面,RtlUserThreadStart看起來很像是這個起始位置,因為它是線程的起始調用函數。在找到起始點之后,獲取起始點的前一個堆棧地址A(第一列),然后在堆棧的內容里面(第二列)尋找是否有等于A的堆棧B(向低地址尋找,因為堆棧是向低地址增長的),然后再在堆棧內容里面尋找是否有等于B的堆棧地址C……,按照這種方法不停的搜索內存,直到不能再找到任何東西或者找到空地址。
? ? ? ??(譯注:這個就是利用的標準函數棧幀的基本原理,對此處不理解的可以去了解下標準函數棧幀,一般沒有經過FPO優化的調用函數鏈,可以通過EBP的值在整個堆棧上面串聯起來,其實Windbg自己也是這么找的,而本文討論的恰恰是因為堆棧被破壞之后,Windbg找不到正確的callstack之后,我們怎么手動恢復的問題)

? ? ? ??在我們這個例子里面,我們從下面的堆棧開始找:

? ? ? ??028bfe78 ?028bfe90
? ? ? ??028bfe7c ?77c51696 ntdll!_RtlUserThreadStart+0x1b

? ? ? ??搜索地址028bfe78,得到下面的堆棧:

? ? ? ??028bfe38 ?028bfe78
? ? ? ??028bfe3c ?77c516c3 ntdll!__RtlUserThreadStart+0x23

? ? ? ??搜索地址028bfe38,得到下面的堆棧:

? ? ? ??028bfe2c ?028bfe38
? ? ? ??028bfe30 ?763ed0e9 kernel32!BaseThreadInitThunk+0xe

? ? ? ??搜索地址028bfe2c,得到下面的堆棧:

? ? ? ??028bfd98 ?028bfe2c
? ? ? ??028bfd9c ?6e761062 i1IO!i1IO::_advancedMeasureThreaded+0x222

? ? ? ??搜索地址028bfd98,得到下面的堆棧:

? ? ? ??028bfa1c ?028bfd98
? ? ? ??028bfa20 ?6e763387 i1IO!i1IO::_measureSingleRowScanThreaded+0x1467

? ? ? ??搜索地址028bfa1c,得到下面的堆棧:

? ? ? ??028bf9e4 ?028bfa1c
? ? ? ??028bf9e8 ?6e760b5b i1IO!i1IO::measureOneStrip+0xbb

? ? ? ??現在,繼續搜索028bf9e4已經不能再在堆棧里面找到信息了,也就是說我們可能已經找到了最終出問題的函數位置,我們可以使用Windbg嘗試修復我們的callstack,當然我們需要給它我們上面找到的這些信息。其實很簡單,只要上面沒找錯,我們給 k 命令指明一個確定地址,通過 L 參數傳遞進去(譯注:用上面我們最后找到的028bfa1c),那么Windbg馬上就會給我們一個更加友好的callstack信息。

? ? ? ??0:000> k L=028bf9e4
? ? ? ??ChildEBP RetAddr ?
? ? ? ??028b89cc 77c75350 ntdll!KiFastSystemCallRet
? ? ? ??028b89d0 77c4b208 ntdll!ZwTerminateProcess+0xc
? ? ? ??028bf9e4 6e760b5b ntdll!RtlExitUserProcess+0x7a
? ? ? ??028bfa1c 6e763387 i1IO!i1IO::measureOneStrip+0xbb
? ? ? ??028bfd98 6e761062 i1IO!i1IO::_measureSingleRowScanThreaded+0x1467
? ? ? ??028bfe2c 763ed0e9 i1IO!i1IO::_advancedMeasureThreaded+0x222
? ? ? ??028bfe38 77c516c3 kernel32!BaseThreadInitThunk+0xe
? ? ? ??028bfe78 77c51696 ntdll!__RtlUserThreadStart+0x23
? ? ? ??028bfe90 00000000 ntdll!_RtlUserThreadStart+0x1b

? ? ? ??現在我們看到的callstack是不是更加完整并且合理了?!沒有了調用棧幀錯誤的警告,而且callstack的調用基址也正常了。

希望上面介紹的這種方法能給你的調試工作帶來一些幫助。

總結

以上是生活随笔為你收集整理的怎样重建一个损坏的调用堆栈(callstack)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。