素槿
Published on 2025-08-22 / 4 Visits
0

用GDB调试汇编代码

1. 常用命令

启动gdb

$ gdb ./a.out
... ...
Reading symbols from ./a.out...
(gdb)

可以直接输入start开始调试,gdb会自动停在main函数开始执行的地方:

(gdb) start

也可以先指定断点再执行:

(gdb) break main	# 在main函数处打一个断点
(gdb) break	5		  # 在源文件的第五行第一个断点

使用run可以直接执行到断点处:

(gdb) run

单步调试:

(gdb) s		# step 单步执行,遇到函数时进入函数内部
(gdb) n		# next 单步执行,遇到函数不进入函数内部

查看寄存器:

(gdb) p $rsp		      # 显示$rsp寄存器的地址
(gdb) x $rsp		      # 显示$rsp寄存器的地址和值
(gdb) info registers  # 显示所有寄存器的地址和值
(gdb) x $rsp + 8	    # 显示$rsp+8的地址和值
(gdb) l				        # 显示源代码

如果定义了字符串常量,如:msg,可以使用如下命令查看

(gdb) p (char*)msg

(gdb) x格式化输出选项:x/nxy

  • n:输出的字段数
  • x:c(字符), d(十进制),x(十六进制)
  • y:显示的字段的长度------b(字节),h(16位字),w(32位字)
(gdb) x/13cb &msg	# 输出13位以字节编码的字符
(gdb) x/20x $rsp	# 输出从$rsp开始的连续20个地址的值

2. 实际例子

使用以下代码为例:

# 目标: 了解函数的工作原理
#       这个程序将计算2^3+5^2的值
#
# main中的所有东西都存在寄存器中,所以data字段没有任何东西

.section .data

.section .text

msg:
    .asciz "output: %lld\n"
#   .string "output: %lld\n"    # 这是另一种定义方法 
.globl main

main:
    push    $3          # push第二个参数
    push    $2          # push第一个参数
    call    power       # 调用函数
    add     $16, %rsp   # 移回栈指针
    push    %rax        # 保存第一个运算结果

    push    $2          # push第二个参数
    push    $5          # push第一个参数
    call    power       # 调用函数
    add     $16, %rsp   # 移回栈指针

    pop     %rbx        # 将第一个运算结果从栈中
                        # 弹出到%rbx中
    add     %rax, %rbx  # 将两个结果相加,保存到%ebx

    # printf("output: %lld\n", %rsi)
    mov     $0, %rax
    mov     $msg, %rdi
    mov     %rbx, %rsi
    call    printf

    # exit(0)
    mov     $0, %rdi  
    call    exit


# 目标: 计算数的N次方
#
# 输入: 第一个参数 - 底数
#       第二个参数 - 指数
#
# 输出: 计算结果
#
# NOTES: 指数必须大于等于1
#
# 变量:
#       %rbx - 保存底数
#       %rcx - 保存指数
#       -8(%rbp) - 保存当前结果
#       %rax - 临时变量

.type power, @function
power:
    push    %rbp            # 保存旧的基址寄存器
    mov     %rsp, %rbp      # 使栈指针为基址指针
    sub     $8, %esp        # 开辟空间存储本地变量

    mov     16(%rbp), %rbx  # 保存第一个参数到%rbx
    mov     24(%rbp), %rcx  # 保存第二个参数到%rcx

    mov     %rbx, -8(%rbp)  # 保存当前结果

power_loop_start:
    cmp     $1, %rcx        # 如果指数为1,结束
    je      end_power
    mov     -8(%rbp), %rax  # 将当前结果保存移到%rax
    imul    %rbx, %rax      # 当前结果乘以底数

    mov     %rax, -8(%rbp)  # 保存当前结果
    dec     %rcx            # 指数减一
    jmp     power_loop_start

end_power:
    mov     -8(%rbp), %rax  # 返回值移到%rax
    mov     %rbp, %rsp      # 重置栈指针
    pop     %rbp            # 重置基址指针
    ret

# to run this file, follow the commands below
# $ gcc -no-pie power.s
# $ ./a.out
# output: 33

编译

$ gcc -g -no-pie power.s -o power

使用 -g选项生成调试信息。

开始调试

运行 sudo gdb ./power,进入调试命令行:

$ gdb power
GNU gdb (Ubuntu 9.1-0ubuntu1) 9.1
Copyright (C) 2020 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 "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from power...
(gdb) 

打断点:

使用 break main 在main符号处打断点或 break 16 在第十六行打断点。

(gdb) break main
Breakpoint 1 at 0x401144: file power.s, line 16.

(gdb) break 16
Breakpoint 1 at 0x401144: file power.s, line 16.

运行至断点处:

(gdb) run
Starting program: /home/ngx/NoteBook/programing_from_the_ground_up/power 

Breakpoint 1, main () at power.s:16
16	    push    $3          # push第二个参数

列出代码:

(gdb) list
11	    .string "output: %lld\n"
12
13	.globl main
14
15	main:
16	    push    $3          # push第二个参数
17	    push    $2          # push第一个参数
18	    call    power       # 调用函数
19	    add     $16, %rsp   # 移回栈指针
20	    push    %rax        # 保存第一个运算结果

查看rsp寄存器的地址:

(gdb) print $rsp
$1 = (void *) 0x7fffffffde38

单步执行,next:

(gdb) n
main () at power.s:17
17	    push    $2          # push第一个参数

查看rsp寄存器的值:

(gdb) x $rsp
0x7fffffffde30:	0x00000003

查看所有寄存器的值:

(gdb) info registers 
rax            0x401144            4198724
rbx            0x4011c0            4198848
rcx            0x4011c0            4198848
rdx            0x7fffffffdf38      140737488346936
rsi            0x7fffffffdf28      140737488346920
rdi            0x1                 1
rbp            0x0                 0x0
rsp            0x7fffffffde30      0x7fffffffde30
r8             0x0                 0
r9             0x7ffff7fe0d50      140737354009936
r10            0x5                 5
r11            0x0                 0
r12            0x401050            4198480
r13            0x7fffffffdf20      140737488346912
r14            0x0                 0
r15            0x0                 0
rip            0x401146            0x401146 <main+2>
eflags         0x246               [ PF ZF IF ]
cs             0x33                51
ss             0x2b                43
ds             0x0                 0
es             0x0                 0
fs             0x0                 0
gs             0x0                 0

单步执行后,显示$rsp+8的地址和值:

(gdb) n
main () at power.s:18
18	    call    power       # 调用函数
(gdb) x $rsp+8
0x7fffffffde30:	0x00000003

输出定义的字符串:

(gdb) x/13cb &msg
0x401136 <msg>:	111 'o'	117 'u'	116 't'	112 'p'	117 'u'	116 't'	58 ':'	32 ' '
0x40113e <msg+8>:	37 '%'	108 'l'	108 'l'	100 'd'	10 '\n'

(gdb) x/s msg
0x401136 <msg>:	"output: %lld\n"

x格式化输出 -- x/nxy

  • n -- 输出的地段数
  • x -- c(字符),d(十进制), x(十六进制)
  • y -- 显示的字段的长度,b(字节),h(16位字),w(32位字)

退出:

(gdb) q
A debugging session is active.

	Inferior 1 [process 23560] will be killed.

Quit anyway? (y or n) uy