My friends, my life, my style - James S.F. Hsieh

1/26/2011

Crash Dump - 要怎麼看 call stack (1)

Q: Call stack 怎麼看?
A: 使用高階語言通常不需要了解 call stack 的 layout, 但是當你需要分析 dump 的時候, 這就是必備的知識. x86 CPU 使用兩個 register 來記錄 stack frame 的位置:

  • SP/ESP/RSP: Stack pointer for top address of the stack.
  • BP/EBP/RBP: Stack base pointer/Frame pointer for holding the address of the current stack frame.
一個 stack frame 包含了 引數, context 與返回位址, 區域變數. 當程式呼叫某個函數時, 首先會根據 calling convention 來推入引數, 然後 call 指令會將相關的 context 跟返回位址推入堆疊, 然後修正 stack frame:

push ebp <= 將 Frame pointer 推入堆疊
mov ebp, esp <= 把 Frame pointer 修正到新的位指, 該位址就是舊的 EBP 所在處
sub esp, X <= 把 Stack pointer 移動來配置區域變數 X 表示區域變數需要的大小 (x86 堆疊是往小的位址成長)

所以, 我們可以預料, EBP 永遠指到上一個 EBP


以下是個簡單的例子來分析一下 stack, 堆疊的頭尾在 003e0000~003dfec4, 最後一個 EBP 是 003dff2c, 所以, 以下紅色的部分是 previous frame pointer, 藍色則是 return address. 這兩個部分是不需要知道每個 function 的 calling convention 就能夠推出來的部分. 我們能夠看出這個 stack 是從 ntdll!_RtlUserThreadStart 成長的, 所以這樣判對的 call stack 是正確的!


利用 r 拿到當下的 context

0:000> r
eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=003dff08 edi=00000000
eip=7722fd21 esp=003dfec4 ebp=003dff2c iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
ntdll!NtDelayExecution+0x15:
7722fd21 83c404          add     esp,4


利用 !teb 來知道 stack 的 base 跟 limit

0:000> !teb
TEB at 7efdd000
    ExceptionList:        003dff1c
    StackBase:            003e0000
    StackLimit:           003d6000
    SubSystemTib:         00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 00000000
    Self:                 7efdd000
    EnvironmentPointer:   00000000
    ClientId:             00000a18 . 00000734
    RpcHandle:            00000000
    Tls Storage:          7efdd02c
    PEB Address:          7efde000
    LastErrorValue:       0
    LastStatusValue:      c0000135
    Count Owned Locks:    0
    HardErrorMode:        0


我們用 dds 或是 kd 指令把 raw stack 展開


0:000> dds 003dfec4 003e0000 
003dfec4  7722fd21 ntdll!NtDelayExecution+0x15
003dfec8  76902c50 KERNELBASE!SleepEx+0x65
003dfecc  00000000
003dfed0  003dff08
003dfed4  d78faaa0
003dfed8  00dd337c CrashLab!__native_startup_lock
003dfedc  00000001
003dfee0  00000000
003dfee4  00000024
003dfee8  00000001
003dfeec  00000000
003dfef0  00000000
003dfef4  00000000
003dfef8  00000000
003dfefc  00000000
003dff00  00000000
003dff04  00000000
003dff08  c4653600
003dff0c  ffffffff
003dff10  00000000
003dff14  003dfed4
003dff18  00000000
003dff1c  003dff78
003dff20  76925eb0 KERNELBASE!_except_handler4
003dff24  a1202d44
003dff28  00000000
003dff2c  003dff3c <= Stack frame
003dff30  76903520 KERNELBASE!Sleep+0xf  
003dff34  000186a0
003dff38  00000000
003dff3c  003dff88 <= Stack frame
003dff40  00dd100b CrashLab!wmain+0xb [d:\codes\crashlab\crashlab\crashlab.cpp @ 49]
003dff44  000186a0
003dff48  00dd117d CrashLab!__tmainCRTStartup+0x10f [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 583]
003dff4c  00000001
003dff50  00571280
003dff54  005760a8
003dff58  d780fa7f
003dff5c  00000000
003dff60  00000000
003dff64  7efde000
003dff68  00000000
003dff6c  00000000
003dff70  003dff58
003dff74  cc5814dc
003dff78  003dffc4
003dff7c  00dd16f5 CrashLab!_except_handler4
003dff80  d760246f
003dff84  00000000
003dff88  003dff94 <= Stack frame
003dff8c  75453677 kernel32!BaseThreadInitThunk+0xe
003dff90  7efde000
003dff94  003dffd4 <= Stack frame
003dff98  77249d42 ntdll!__RtlUserThreadStart+0x70
003dff9c  7efde000
003dffa0  6f7de307
003dffa4  00000000
003dffa8  00000000
003dffac  7efde000
003dffb0  00000000
003dffb4  00000000
003dffb8  00000000
003dffbc  003dffa0
003dffc0  00000000
003dffc4  ffffffff
003dffc8  772803dd ntdll!_except_handler4
003dffcc  1863df43
003dffd0  00000000
003dffd4  003dffec <= Stack frame
003dffd8  77249d15 ntdll!_RtlUserThreadStart+0x1b
003dffdc  00dd12c5 CrashLab!wmainCRTStartup [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 393]
003dffe0  7efde000
003dffe4  00000000
003dffe8  00000000
003dffec  00000000 <= Stack frame
003dfff0  00000000
003dfff4  00dd12c5 CrashLab!wmainCRTStartup [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 393]
003dfff8  7efde000
003dfffc  00000000
003e0000  ????????

有時候 context 中的 EBP 可能會是錯的, 那就得猜一下了! 如果幸運的找到正確的 EBP, 你將可以使用 "k L = BasePtr [FrameCount]" 來忽略錯誤的 stack frame. 假如正確的 EBP 是 003dff44.

0:000> k L = 003dff44 10
ChildEBP RetAddr  
003dfec4 76902c50 ntdll!NtDelayExecution+0x15
003dff44 00dd117d KERNELBASE!SleepEx+0x65
003dff88 75453677 CrashLab!__tmainCRTStartup+0x10f
003dff94 77249d42 kernel32!BaseThreadInitThunk+0xe
003dffd4 77249d15 ntdll!__RtlUserThreadStart+0x70
003dffec 00000000 ntdll!_RtlUserThreadStart+0x1b

有幾種最佳化的技巧會讓這個技巧無法使用:
  1. Inline:  inline 利用行內拓展來避免函數呼叫, 所以根本沒有 stack frame. 
  2. Frame pointer omission (FPO): 當 FPO enable 時, function 的呼叫可能就不在維護 EBP (有些條件還是需要), 在這種狀況, 你就沒有 EBP 可以參考. 詳細可以參考這篇文章 http://www.nynaeve.net/?p=91