Linux下彙編偵錯程式GDB的使用

      GDB 是GNU開源組織釋出的一個強大的Linux/Unix下的程式除錯工具。大家是否早已習慣了Windows下圖形介面方式像VC、BCB等IDE的偵錯程式,但如果你是在Linux平臺下做軟體除錯,你會發現GDB這個除錯工具有比VC、BCB的圖形化偵錯程式更強大的功能。
先來看個例項:

 

  1. reader@cg:~/source $ gdb -q  
  2. (gdb) set dis intel  
  3. (gdb) quit  
  4. reader@cg:~/source $ echo "set dis intel" > ~/.gdbinit  
  5. reader@cg:~/source $ cat ~/.gdbinit  
  6. set dis intel  
  7. reader@cg:~/source $  

現在將GDB配置成為了使用Intel語法,讓我們首先來認識Intel語法。在Intel語語法中,彙編指令一般遵循下面這種形式:
operation <destination>, <source>
目的運算元和源運算元可以是暫存器、記憶體地址或數值。操作通常是直觀的助記符:mov操作會將源運算元中的值移動到目的運算元中,sub操作會減去,inc指令會增加等。例如,下面的指令將會扣ESP中的值移動到EBP中,然後從ESP中減去8(結果儲存在ESP中)。
8048375:        89 e5                 mov    ebp,esp
8048377:        83 ec 08              sub    esp,0x8
還有用於控制執行流程的操作。cmp操作用於對數值進行比較,並且基本上所有以j為首字母的操作都用於轉移到程式碼的不同部分(轉移到哪一部分取決於比較的結果)。下面的例子中,首先將位於EBP中的一個4位元組的值減去4與數值9進行比較。下一條指令是如果小於等於則轉移的簡寫,它參考的是前一個比較的結果。如果那個數小於或等於9,那麼程式就會轉移到Ox8048393處的指令執行。否則,就轉向下一條無條件轉移指令執行。如果那個數不小於或等於9,那麼程式執行就會轉移到Ox80483a6處。
804838b:        83 7d fc 09           cmp    DWORD PTR [ebp-4],0x9
804838f:        7e 02                 jle    8048393 <main+0x1f>
8048391:        eb 13                 jmp    80483a6 <main+0x32>
這些例子來自於我們先前的反彙編,並且我們已經將除錯工具配置為使用Intel語法,所以讓我們使用除錯工具在彙編指令級別上單步除錯第一個程式吧。
GCC編譯程式可以使用-g標記來包含附加的除錯資訊,這些除錯資訊會使得GDB能夠訪問原始碼。
 

  1. reader@cg:~/source $ gcc -g firstprog.c   
  2. reader@cg:~/source $ ls -l a.out  
  3. -rwxr-xr-x 1 matrix users 11977 Jul 4 17:29 a.out  
  4. reader@cg:~/source $ gdb -q ./a.out  
  5. Using host libthread_db library "/lib/libthread_db.so.1".  
  6. (gdb) list  
  7. 1       #include <stdio.h> 
  8. 2  
  9. 3       int main()  
  10. 4       {  
  11. 5               int i;  
  12. 6               for(i=0; i < 10; i++)  
  13. 7               {  
  14.  8                       printf("Hello, world! ");  
  15. 9               }  
  16. 10      }  
  17. (gdb) disassemble main  
  18. Dump of assembler code for function main():  
  19. 0x08048384 <main+0>:    push   ebp  
  20. 0x08048385 <main+1>:    mov    ebp,esp  
  21. 0x08048387 <main+3>:    sub    esp,0x8  
  22. 0x0804838a <main+6>:    and    esp,0xfffffff0  
  23. 0x0804838d <main+9>:    mov    eax,0x0  
  24. 0x08048392 <main+14>:   sub    esp,eax   
  25. 0x08048394 <main+16>:   mov    DWORD PTR [ebp-4],0x0   
  26. 0x0804839b <main+23>:   cmp    DWORD PTR [ebp-4],0x9  
  27. 0x0804839f <main+27>:   jle    0x80483a3 <main+31> 
  28. 0x080483a1 <main+29>:   jmp    0x80483b6 <main+50> 
  29. 0x080483a3 <main+31>:   mov    DWORD PTR [esp],0x80484d4  
  30. 0x080483aa <main+38>:   call   0x80482a8 <_init+56> 
  31. 0x080483af <main+43>:   lea    eax,[ebp-4]  
  32. 0x080483b2 <main+46>:   inc    DWORD PTR [eax]  
  33. 0x080483b4 <main+48>:   jmp    0x804839b <main+23> 
  34. 0x080483b6 <main+50>:   leave  
  35. 0x080483b7 <main+51>:   ret  
  36. End of assembler dump.  
  37. (gdb) break main  
  38. Breakpoint 1 at 0x8048394: file firstprog.c, line 6.  
  39. (gdb) run  
  40. Starting program: /cg/a.out  
  41.  
  42. Breakpoint 1, main() at firstprog.c:6  
  43. 6               for(i=0; i < 10; i++)  
  44. (gdb) info register eip  
  45. eip            0x8048394        0x8048394  
  46. (gdb)  
  47.  

先列出了原始碼,並且顯示了main()函式的反彙編。然後,在main()函式開始的地方設定了一個斷點,並且開始執行程式。這個斷點只是告訴除錯工具當程式執行到該點時暫停程式的執行。由於斷點設定在了main()函式開始的地方,實際上在執行main()中的任何指令之前,程式會到達該斷點並且暫停。然後,顯示了EIP(指令指標)的值。
EIP包含一個記憶體地址,該地址指向main()函式的反彙編指令中的一條指令(以粗體顯示)。我們把在這之前的指令(以斜體顯示)一起稱為函式序言,它們由編譯程式生成,用於為main()函式的區域性變數設定記憶體空間。在C語言中需要宣告變數的部分原因就是輔助建立這部分程式碼。除錯工具知道這部分程式碼是自動產生的,並且聰明地將其跳過。我們隨後會詳細討論函式序言,但現在我們可以從GDB中獲得它的一些資訊並暫時將其跳過。
GDB除錯工具提供了一個直接檢查記憶體的方法,即使用命令x,它是檢查(examine)的簡寫。對於任何Hacker來說,檢查記憶體都是一項很關鍵的技術。大多數Hacker的漏洞發掘很像魔術——它們似乎令人驚訝並且不可思議,除非您知道這些戲法和誤導。但是使用像GDB這樣的除錯工具,程式執行的每個方面都可以被確定地檢查、暫停、單步跟蹤並且可以隨心所欲地重複。因為一個正在執行的程式的主體是處理器和若干記憶體段,所以檢查記憶體是檢視到底正在幹什麼的首要方法。