在 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 以二進制。

以上簡略介紹至此。