2008年6月6日 星期五

在 Linux 下使用 GNU AS 編寫組合語言 - 使用 gdb 進行除錯

日前有寫一篇『在 Linux 下使用 GNU AS 編寫組合語言』,現在以 cpuid 的範例來示範如何使用 gdb 除錯。

首先我們在組譯的時候,需加上 -gstabs 參數,as 會將除錯所需要的資訊編進去,以便 gdb 除錯。
[ cpuid:c9s-desktop : 19:05:43 ] $ as -gstabs -o cpuid.o cpuid.s
[ cpuid:c9s-desktop : 19:05:58 ] $ ld -o cpuid cpuid.o
除錯,只需要將連結完的執行檔丟給 gdb 即可:
[ cpuid:c9s-desktop : 19:06:04 ] $ gdb cpuid
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
(gdb)
這樣就進入 gdb 了。

接下來我們可以在 _start 標籤之位址插入中斷點,然後將此程式執行,程式應在 _start 中斷
(gdb) break *_start
Breakpoint 1 at 0x8048074: file cpuid.s, line 7.
(gdb) run
Starting program: /home/src/asm-code/cpuid/cpuid
The processor Vendor ID is 'GenuineIntel'

Program exited normally.
(gdb) quit
怎麼回事,中斷點無效了?這是 gdb 一個 Bug,他把一開始的中斷點_start忽略了。XD

經過測試發現似乎是在一開始的第一個指令中斷點會無法作用?解法是,在 _start 後加入一個 nop 指令。nop 指令並沒有功能,他不做任何事情。
.globl _start
_start:
nop
movl $0, %eax
cpuid
movl $output,%edi
movl %ebx, 28(%edi)
接下來重新組譯後,使用 gdb 除錯:
(gdb) break *_start+1
Breakpoint 1 at 0x8048075: file cpuid.s, line 8.
(gdb) run
Starting program: /home/src/asm-code/cpuid/cpuid

Breakpoint 1, _start () at cpuid.s:8
8 movl $0, %eax
Current language: auto; currently asm
因為 nop 只有一個 byte ,我們將中斷點設置在 _start 位址後的一個 byte ,就會停在中斷點了。

接著你可使用 s 或 n 來作單步執行。 (s for step , n for next )

gdb 的基本指令:
info registers顯示所有暫存器內容
print印出特定暫存器或者變數的值
x印出特定記憶體位置的內容

print 可搭配不同的修飾符來選擇以何種格式印出 ( print/d 印出十進位數值 , print/t 印出二進位數值 , print/x 印出十六進制數值 )

此外 x 指令也可搭配修飾符來選擇格式

x/nyz
  • n 代表要印出幾個欄位 ( 1 , 2 , 3 ... )
  • y 為輸出格式,可為 c (字元) , d (十進制) , x (十六進制)
  • z 為大小
其中 z 可為:
  • b 為 byte
  • h 為 16 bit word (halt-word)
  • w 為 32 bit word
譬如:
(gdb) x/42xb *_start
0x8048074 <_start>: 0x90 0xb8 0x00 0x00 0x00 0x00 0x0f 0xa2
0x804807c <_start+8>: 0xbf 0xac 0x90 0x04 0x08 0x89 0x5f 0x1c
0x8048084 <_start+16>: 0x89 0x4f 0x24 0xb8 0x04 0x00 0x00 0x00
0x804808c <_start+24>: 0xbb 0x01 0x00 0x00 0x00 0xb9 0xac 0x90
0x8048094 <_start+32>: 0x04 0x08 0xba 0x2a 0x00 0x00 0x00 0xcd
0x804809c <_start+40>: 0x80 0xb8

(gdb) print/x $ebx
$1 = 0x756e6547

(gdb)
其中 x/42xb 代表印出 _start 標籤開始之後的 42 個 Byte ,並以 16 進制印出。 print/x 代表以 16 進制印出。 print/d 以十進制,print/t 以二進制。

以上簡略介紹至此。