Wednesday, March 11, 2009

除錯經驗分享(1):EIP = 0所造成的Crash

相關知識:x86架構、Compiler、Windbg指令

今天遇到一個值得分享的問題,先看一下Crash發生時候的Stack:

   1:   
   2:  STACK_TEXT:  
   3:  WARNING: Frame IP not in any known module. Following frames may be wrong.
   4:  f7a22c60 f7944029 0000ffff f7a22c7c f794407a 0x0
   5:  f7a22c6c f794407a 0000ffff bb40e64e f7a22d4c BasicDriver!CheckParameterAndCallHelperFunction+0x19 [d:\XXX\basicdriver.c @ 25]
   6:  f7a22c7c 805777ff 824e5cc0 82328000 00000000 BasicDriver!DriverEntry+0x1a [d:\XXX\basicdriver.c @ 36]
   7:  f7a22d4c 8057790f 800009a8 00000001 00000000 nt!IopLoadDriver+0x66d
   8:  f7a22d74 80535c12 800009a8 00000000 827ba388 nt!IopLoadUnloadDriver+0x45
   9:  f7a22dac 805c71ec f7a76cf4 00000000 00000000 nt!ExpWorkerThread+0x100
  10:  f7a22ddc 80542de2 80535b12 00000001 00000000 nt!PspSystemThreadStartup+0x34
  11:  00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x16

請看奇怪的第四行,Symbol Path都設定了,怎麼會有0x0出現呢?再來看看當時的暫存器的值:

   1:  kd> .cxr 0xfffffffff7a22898
   2:  eax=0000ffff ebx=00000000 ecx=bb40e64e edx=8054be20 esi=e1f8b780 edi=824e5cc0
   3:  eip=00000000 esp=f7a22c64 ebp=f7a22c6c iopl=0         nv up ei pl nz na pe nc
   4:  cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00210206
   5:  00000000 ??              ???

    第三行的eip竟然顯示為0x00000000,難道今天忘記放乖乖在電腦上,所以硬體產生問題嗎?來檢查Crash發生時的x86指令(!analyze -v):

   1:  FOLLOWUP_IP: 
   2:  BasicDriver!CheckParameterAndCallHelperFunction+19 [d:\XXX\basicdriver.c @ 25]
   3:  f7944029 5d              pop     ebp
   4:   
   5:  FAILED_INSTRUCTION_ADDRESS: 
   6:  +689e952f01edd960
   7:  00000000 ??              ???

    由第7行可知是位於0x00000000的無意義資料,可見得機器的確是因為執行了EIP=0x00000000這個地方的東西而掛掉。第2、3行給了我們一些線索,呼叫0x00000000的犯人位於0xf7944029附近,讓我們來看一下CheckParameterAndCallHelperFunction所包含的指令:

   1:  kd> uf BasicDriver!CheckParameterAndCallHelperFunction
   2:  BasicDriver!CheckParameterAndCallHelperFunction [d:\XXX\basicdriver.c @ 17]:
   3:     17 f7944010 8bff            mov     edi,edi
   4:     17 f7944012 55              push    ebp
   5:     17 f7944013 8bec            mov     ebp,esp
   6:     19 f7944015 837d0800        cmp     dword ptr [ebp+8],0
   7:     19 f7944019 7d04            jge     BasicDriver!CheckParameterAndCallHelperFunction+0xf (f794401f)
   8:     21 f794401b 32c0            xor     al,al
   9:     21 f794401d eb0a            jmp     BasicDriver!CheckParameterAndCallHelperFunction+0x19 (f7944029)
  10:     24 f794401f 8b4508          mov     eax,dword ptr [ebp+8]
  11:     24 f7944022 50              push    eax
  12:     24 f7944023 ff15186094f7    call    dword ptr [BasicDriver!LittleHelper+0x8 (f7946018)]
  13:     25 f7944029 5d              pop     ebp
  14:     25 f794402a c20400          ret     4

    有人說每寫一行組語,就會嚇跑一半的讀者Tongue out我們只要簡單看幾個地方,首先是找到第13行的0xf7944029,跟!analyze -v說的一樣,是pop ebp。前一個指令,也就是第12行的地方會呼叫存放於f7946018的函式,讓我們來檢查一下:

   1:  kd> dd f7946018 L1
   2:  f7946018  00000000

    真是乾淨的清潔溜溜。原來錯誤是發生在程式使用了一個沒有初始化的函式指標(function pointer),而call指令會把新指令的位置設定給eip,所以就造成了eip=0x00000000的現象摟!

BOOLEAN CheckParameterAndCallHelperFunction(int iValue){
 if (iValue <0)
         return FALSE;

 return (fpHelperFunction)(iValue);
}


另外,Compiler可能會把function開頭的push ebp以及尾端的三個指令最佳化:

   1:     ......
   2:     17 f7944012 55              push    ebp
   3:     ......
   4:     24 f7944023 ff15186094f7    call    dword ptr [BasicDriver!LittleHelper+0x8 (f7946018)]
   5:     25 f7944029 5d              pop     ebp
   6:     25 f794402a c20400          ret     4
   7:                                最佳化,以jmp取代上述四個指令:
   8:                                 jmp [f7946018]

    也就是說,速度上可以節省掉push/pop ebp兩個指令。以jmp取代call的好處是jmp不會退回return address,那執行完會到哪邊呢?會回到上一次用call的地方,也就是本例中的BasicDriver!DriverEntry。這也是一個加速(反正都在尾巴了,乾脆直接的返回更上一層的function)這樣最佳化對程式很好,但是分析dump的時候負擔也就會增加了。

No comments: