coredump原理探究 Windows 筆記


coredump原理探究 Windows 筆記

原文鏈接:https://blog.csdn.net/xuzhina/article/details/8247701
感謝原作者,侵刪

 

 

一、環境搭建

1、Win7捕獲程序dump

注冊表HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/Windows Error Reporting/LocalDumps中新建幾個key

 

抓dump注冊表配置

抓dump注冊表配置

 

2、Windbg符號表設置(Symbols Search Path)

自動下載
D:\Debug\Symbols;SRV\*D:\Debug\Symbols\*http://msdl.microsoft.com/download/symbols;D:\Debug\Dump
手動下載
https://developer.microsoft.com/en-us/windows/hardware/download-symbols

二、WinDbg命令

命令 含義 實例
x 顯示所有上下文中符合某種模式的符號 x Test!m*
bp 設置一個或多個軟件斷點;可通過
組合、地址、條件、選項來設置多種類型的斷點
bp Test!main
u 顯示出內存里某段程序的匯編 u Test!main
g 開始執行指令的進程或線程;當出現下列情況,執行停止:
1、進程結束;2、執行到斷點;3、某個時間導致調試器終止
 
dd 顯示指定范圍內存單位的內容(雙字dword) dd esp L 8
da 顯示指定內存開始的字符串 da 004020dc
db 單字節顯示內存 db esp
t 執行一條指令或一行代碼,並顯示出所有寄存器和狀態的值 Trace
p 執行一條指令或一行代碼,並顯示出所有寄存器和狀態的值
當函數調用或中斷發生時,也只是作為一條指令執行
Step
kbn 顯示指定線程的棧幀,並顯示相關信息  
.frame n 切換到棧幀n  
ln 查找就近的符號 ln 004010e0
dt -v 結構體名 地址 打印結構體 dt -v _HEAP_ENTRY 00730000
!heap -a 查看堆信息  
!heap -x 地址 查看地址所屬的堆塊信息 !heap -x 00730000
!heap -hf 地址 查看堆上所有的堆塊 !heap -hf 00730000

三、函數棧幀

1、棧內存布局

返回地址ret
上一棧幀地址fp
局部變量
從右往左壓入參數
返回地址ret
......

2、棧溢出

往局部變量中寫數據越界,導致淹沒了fp和ret,函數返回時ebp、eip就會被寫入非法值
此時fp/ret對的鏈表關系被破壞,調試器無法顯示正確的函數棧

3、棧的規律

  1. esp的值不會被覆蓋,永遠指向棧頂
  2. fp/ret對的鏈表沒有被完全破壞,往高地址的一些地方還是保持這種關系
  3. 從棧頂到棧底,內存地址由低到高。如果幀指針fp1的內容是fp2,fp2的內容是fp3,那么存在:esp<fp1<fp2<fp3
  4. 如果兩對(fp1,ret2)、(fp2,ret2)符合條件:fp1的內容剛好是fp2,那么它們除了滿足第3點外,還滿足:ln ret1和ln ret2都能列出函數符號,ret2的上一條指令一定是調用ret1所在的函數

4、定位棧溢出問題的經驗方法

  1. dd esp查看由esp開始的內存
  2. 找到某個內容比esp的值稍大、數值相差不是太遠的內存單元,稱為FP1。下一個單元稱為RET1
  3. ln查看RET1的內容。如果沒有顯示函數符號,跳過FP1,回到第2步
  4. dd *FP1 L 2得到兩個內存單元(FP2,RET2)。如果FP2的內容小於FP1的內容,跳過FP1,回到第2步
  5. ln查看RET2的內容。如果沒有顯示函數符號,跳過FP1,回到第2步;如果OK,跳到FP2,回到第4步

四、函數逆向

匯編代碼中跳轉和循環為函數的骨架,要優先尋找

五、C內存布局

1、基本類型

類型 特征
char byte ptr 擠在一起
short word ptr 占用四字節空間
int dword ptr
long dword ptr win64采用LLP64標准,Windows系統中long和int是相同的
float dword ptr 單精度占四字節,要配合浮點計算指令確認
double qword ptr 雙精度占八字節,要配合浮點計算指令確認
指針 lea

2、數組類型

類型 特征
char 基地址 + 索引值 * 1
short 基地址 + 索引值 * 2 數組時會擠在一起,注意與基本類型的區別
int 基地址 + 索引值 * 4
long 基地址 + 索引值 * 4
float 基地址 + 索引值 * 4 單精度占四字節,要配合浮點計算指令確認 
double 基地址 + 索引值 * 4 雙精度占八字節,要配合浮點計算指令確認 
指針 基地址 + 索引值 * 4 

3、結構體

  1. 成員全是基本類型的結構體: 先把一個基地址放到某寄存器中,訪問成員時在基址上加上前面所有成員的大小,每個成員與基址的偏移量不固定
  2. 復合類型構成的結構體: 同上,沒有特別
  3. 結構體數組: 找到數組的首地址;根據索引找到每個元素的地址;以每個元素的地址作為結構體基址,獲取成員變量的地址

六、C++內存布局

1、類的內存布局

類的成員變量排列與結構體相同

2、this指針

調用類的成員函數時,this指針放在ecx寄存器中傳遞,不入棧

  1. 調用函數時的匯編代碼: 
  2. lea ecx,[ebp-1
  3. call Test!Test::print (00ec1030) 
  4. 被調函數開始的匯編代碼: 
  5. mov dword ptr [ebp-4],ecx 
  6. mov eax,dword ptr [ebp-4
  7. push eax 

3、虛函數表及虛表指針

 

虛函數表及虛表指針

虛函數表及虛表指針

 

4、單繼承

子類先調用基類的構造函數,再初始化自己的成員變量,然后設置虛表指針
子類虛函數表分布規律:

  1. 重載基類的虛函數,按照基類虛函數聲明順序排列,和子類聲明順序無關
  2. 子類獨有的虛函數,按照虛函數的聲明順序排列,追加在重載虛函數的后面

5、多繼承(無公共基類)

  1. 子類對象的大小等於各個基類的大小(虛表指針+成員變量)加上自身成員變量的大小
  2. 各基類在子類里的“隱含對象”是按照繼承順序來排列的,和基類的聲明、定義順序無關
  3. 每個基類都(盡可能)有自己的虛函數表;子類獨有的虛函數追加到第一個虛函數表的后面;子類重載所有虛表中的同名虛函數
  4. 子類對象指針轉換成基類指針,實際上是把子類對象包含的對應基類的“隱含對象”的地址賦值給基類指針
  5. 當一個虛函數在多個虛表中都出現時,實際上只會完全重載第一個虛表中的該函數。其余虛表中重載代碼是通過調整this指針為子類的地址,然后跳轉到子類對應函數來實現
  1. 0:000> u 012e10a8 L 5 
  2. Test![thunk]:Child::print`adjustor{12}': 
  3. 012e10a8 83e90c sub ecx,0Ch // ecx是基類“隱含對象”的地址,需調整 
  4. 012e10ab e9e0ffffff jmp Test!Child::print (012e1090) 

七、STL容器內存布局

1、vector

一個vector在棧上占三個單元(指針):
第一個_Myfirst指向vector元素的開始
第二個_Mylast指向vector元素結束的下一個位置
第三個_Myend指向vector空間結束的位置
注意:vector的begin()、end()、push_back()等成員函數的匯編,最后是:ret 4

 

vector

vector

 

2、list

  1. list有兩個成員:第一個_Myhead指向鏈表的頭部節點,第二個_Size表明鏈表中的節點元素個數
  2. 鏈表中的每個節點包含三個成員:第一個_Next指向下一個節點,第二個_Prev指向前一個節點,第三個_Myval存儲節點的值
  3. list初始化時會生成一個頭節點
  4. 頭節點的_Next指向鏈表第一個節點,鏈表最后一個節點的_Next指向頭節點
  5. 頭節點的_Prev指向鏈表最后一個節點,鏈表第一個節點的_Prev指向頭節點
    注意:圖中_Prev指針應該都指向節點起始位置,而不是_Prev指針的位置。這里只是為了突出兩個鏈路

 

list

list

 

3、map

  1. map有兩個成員:_Myhead指向頭節點,_Mysize表明map包含的元素個數
  2. 頭節點的三個指針分別指向樹的最左節點、樹的根節點、樹的最右節點
  3. 樹的根節點的_Parent指向頭節點
  4. 樹的葉子節點的_Left、_Right指向頭節點

 

enter description here

map

 

4、set

由於map、set本身的定義都沒有聲明任何成員變量,所有的成員變量都是從_Tree繼承過來的,唯一的區別是traits的定義不一樣,因此:set的特征和map類似

  1. template<class _Kty, 
  2. class _Pr = less<_Kty>, 
  3. class _Alloc = allocator<_Kty> > 
  4. class set : public _Tree<_Tset_traits<_Kty, _Pr, _Alloc, false> > 
  5. { ...... } 
  6.  
  7. template<class _Kty, 
  8. class _Ty, 
  9. class _Pr = less<_Kty>, 
  10. class _Alloc = allocator<pair<const _Kty, _Ty> > > 
  11. class map : public _Tree<_Tmap_traits<_Kty, _Ty, _Pr, _Alloc, false> > 
  12. { ...... } 

5、iterator

  1. vector的iterator只有一個成員_Ptr,取值范圍:vec._Myfirst <= _Ptr < vec._Mylast
  2. list的iterator也只有一個成員_Ptr,指向list中的每個節點(頭節點除外)
  3. map和set的iterator也只有一個成員_Ptr,指向map或set的節點,且iterator的遍歷采用中序遍歷

實際調試中,set的iterator指向節點的值在for循環中是按照0、1、2、3......、f的順序遍歷

 

set的iterator

set_iterator

 

6、string

string有三個成員:聯合體_Bx,緊接着的是字符串長度_Mysize,預留空間大小_Myres

  1. 當_Mysize < _BUF_SIZE(16)時,字符串存儲在_Bx的_Buf里
  2. 當_Mysize >= _BUF_SIZE(16)時,字符串存儲在_Bx的_Ptr指向的內存中
  1. // TEMPLATE CLASS _String_val 
  2. template<class _Val_types> 
  3. class _String_val : public _Container_base 
  4. { // base class for basic_string to hold data 
  5. public
  6. ...... 
  7. enum { // length of internal buffer, [1, 16] 
  8. _BUF_SIZE = 16 / sizeof (value_type) < 1  
  9. ? 1 
  10. : 16 / sizeof (value_type) 
  11. }; 
  12. ...... 
  13. union _Bxty { // storage for small buffer or pointer to larger one 
  14. value_type _Buf[_BUF_SIZE]; 
  15. pointer _Ptr; 
  16. char _Alias[_BUF_SIZE]; // to permit aliasing 
  17. } _Bx; 
  18. size_type _Mysize; // current length of string 
  19. size_type _Myres; // current storage reserved for string 
  20. }; 

八、堆結構

1、NT內核堆的改造

文檔中介紹的堆結構是XP環境下的。MS從Vista開始對NT內核做了較大改動,其中包括堆的改造。最直觀的改造:

  1. _HEAP中采用鏈表方式管理_HEAP_SEGMENT,解除數組的限制
  2. _HEAP_ENTRY結構進行了編碼,引入隨機性,增強堆的安全性
  3. 取消空閑堆塊鏈表的頭節點數組,直接使用鏈表管理空閑堆塊,即_HEAP中FreeLists從[128]的_LIST_ENTRY數組改為單個元素

2、Win7下堆的結構

  1. struct _HEAP_ENTRY { 
  2. SHORT Size; /* 當前塊的大小。 
  3. 直接dt -v打出來的值是經過了編碼的, 
  4. 實際值需要和_HEAP中的Encoding做一次異或,取最低的兩個字節。 
  5. 真正的塊的字節數還需要Size*8。 
  6. 這個值包含了這個堆塊頭結構_HEAP_ENTRY的8字節 
  7. */ 
  8. // ……省略若干字段 
  9. SHORT PreviousSize; 
  10. // ……省略若干字段 
  11. BYTE UnusedBytes; // 未使用的字節數 
  12. // ……省略若干字段 
  13. }; /* 整個_HEAP_ENTRY結構共8字節, 
  14. 后面緊接着申請出來的內存塊,malloc或new返回的也是指向這個內存塊的指針, 
  15. 這個指針的值一定是8的倍數, 
  16. 再后面緊接着的就是下一個堆塊  
  17. */ 
  18.  
  19. struct _HEAP_SEGMENT { 
  20. _HEAP_ENTRY Entry; 
  21. UINT SegmentSignature; 
  22. UINT SegmentFlags; 
  23. _LIST_ENTRY SegmentListEntry; /* segment list的入口, 
  24. 指示當前_HEAP_SEGMENT節點在segment list中的位置, 
  25. 各_HEAP_SEGMENT通過這個字段連接 */ 
  26. _PHEAP Heap; /* 指向所屬的_HEAP */ 
  27. // ……省略若干字段 
  28. _HEAP_ENTRY* FirstEntry; 
  29. _HEAP_ENTRY* LastValidEntry; 
  30. // ……省略若干字段 
  31. _LIST_ENTRY UCRSegmentList; 
  32. }; 
  33.  
  34. struct _HEAP { 
  35. _HEAP_SEGMENT Segment; /* 第一個段 */ 
  36. // ……省略若干字段 
  37. UINT EncodeFlagMask; /* 是否啟用編碼功能 */ 
  38. _HEAP_ENTRY Encoding; /* 編碼key, 
  39. 用這個結構和每個堆塊頭結構_HEAP_ENTRY做異或  
  40. */ 
  41. // ……省略若干字段 
  42. _LIST_ENTRY SegmentList; /* segment list的頭節點, 
  43. 分別指向第一個、最后一個_HEAP_SEGMENT的SegmentListEntry  
  44. */ 
  45. // ……省略若干字段 
  46. _LIST_ENTRY FreeLists; /* 空閑堆塊鏈表頭節點, 
  47. 分別指向第一個、最后一個_HEAP_FREE_ENTRY的FreeList, 
  48. XP中這里是[128]的_LIST_ENTRY數組  
  49. */ 
  50. // ……省略若干字段 
  51. _HEAP_TUNING_PARAMETERS TuningParameters; 
  52. }; 
  53.  
  54. /* 空閑堆塊結構 */ 
  55. struct _HEAP_FREE_ENTRY { 
  56. _HEAP_ENTRY Entry; 
  57. _LIST_ENTRY FreeList; /* free list的入口, 
  58. 指示當前空閑堆塊在鏈表中的位置 
  59. 各空閑堆塊通過這個字段連接 
  60. 如果兩個節點在內存中連續則合並 
  61. */ 
  62. }; 

 

heap

heap

 

3、堆塊的調試

獲取一個地址所屬堆塊的信息

  1. 0:000> !heap -x 00730000 
  2. Entry User Heap Segment Size PrevSize Unused Flags 
  3. ----------------------------------------------------------------------------- 
  4. 00730000 00730008 00730000 00730000 588 0 1 busy 
  5. 注意:這里的Size為58816進制 

獲取一個堆塊的大小

  1. 0:000> dt -v _HEAP_ENTRY 00730000 直接打印_HEAP_ENTRY結構 
  2. ntdll!_HEAP_ENTRY 
  3. struct _HEAP_ENTRY, 19 elements, 0x8 bytes 
  4. +0x000 Size : 0x9496 顯然不對 
  5. …… 
  6. +0x007 UnusedBytes : 0x1 '' 
  7. …… 
  8. 0:000> dd 00730000+0x050 L 4 獲取Encoding結構體。Encoding相對_HEAP的偏移是0x050 
  9. 00730050 47329427 000024e0 3b5c5f17 00000000 Encoding低4字節的值為47329427 
  10. 0:000> dd 00730000 L 4 打印_HEAP_ENTRY結構的值 
  11. 00730000 f7339496 010024e0 ffeeffee 00000000 打印出的值f7339496是原始值和Encoding經過異或后得到的 
  12. 0:000> ? f7339496 ^ 47329427 要求原始值只需要當前值和Encoding再異或一遍 
  13. Evaluate expression: -1342111567 = b00100b1 低地址的兩字節就是原始的Size 
  14. 0:000> ? 00b1 * 8 實際堆塊的字節數還要Size*8 
  15. Evaluate expression: 1416 = 00000588 

4、heap corruption問題

常見原因:

  1. free導致coredump:
    free了野指針:需要檢查指針的正確性。例如:是否在!heap -hf所列范圍內、是否是8的倍數等
    堆塊寫越界:需要檢查前后堆塊的Size和PreSize
  2. malloc導致coredump:
    一般是因為堆塊寫越界,破壞了空閑堆塊的結構。!heap -hf可以找到同一個堆塊即是free又是busy的狀態

九、dll hell問題

dll hell常導致虛函數的漂移,本質上就是一個dll之間版本不匹配的問題。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM