gdb调试技术

一、gdb概述

GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。
有以下4个作用:
1、启动程序,可以设置程序运行的参数或者环境。
2、可让程序stop在断点。
3、当程序被stop时,可以检查此时程序所发生的事。
4、动态的改变你程序的执行条件(如改变变量值)。

二、gdb使用

1、使用准备

在这里插入图片描述
如图可以知道,调试一般可以本地调试和远程调试。
准备工作:

1.1、安装gdb

安装适应的gdb,也可安装gdb-multiarch,如arm64切换到arm64

sudo apt install gdb-multiarch
gdb-multiarch
#之后执行
(gdb) set architecture aarch64
The target architecture is assumed to be aarch64

1.2、编译带有调试信息目标文件

编译时,必须要把调试信息加到可执行文件中。例如使用编译器(cc/gcc/g++)的 -g 参数即可。
如果没有-g,将看不见程序的函数名和变量名,代替它们的全是运行时的内存地址。
要用gdb调试程序,必须在编译时加上-g和-ggdb选项,-g选项的作用是在可执行文件中加入源文件信息,但并不是将源文件嵌入可执行文件,所以在调试时必须保证gdb必须能找到源文件。
-g 和 -ggdb 都是令 gcc 生成调试信息,但是它们也是有区别的:

g该选项可以利用操作系统的“原生格式(native format)”生成调试信息。GDB 可以直接利用这个信息,其它调试器也可以使用这个调试信息;
ggdb使 GCC为GDB 生成专用的更为丰富的调试信息,但是,此时就不能用其他的调试器来进行调试了 (如ddx);

-g 和 -ggdb 也是分级别的:

g1级别1(-g1)不包含局部变量和与行号有关的调试信息,因此只能够用于回溯跟踪和堆栈转储之用;
g2这是默认的级别,此时产生的调试信息包括扩展的符号表、行号、局部或外部变量信息;
g3包含级别2中的所有调试信息,以及源代码中定义的宏;

关于调试信息:
在这里插入图片描述

1.3、如果远程调试

目标机上需安装gdbserver,这里不做暂时详细研究。

2、使用方法

2.1、启动调试

2.1.1、调试应用程序

1、开始一个程序调试:

gdb -args program a b c   //abc为程序参数,无参数 gdb program
gdb -args test a   //example

2、开始一个正在运行的程序调试:

gdb attach <pid>   //调试正在运行的程序,也可进入gdb后attach <pid> 
root@yf:~# ps -a
  PID TTY      TIME     CMD
  557 pts/0    00:00:00 test
  570 ?        00:00:00 ps
root@XP5:~# 
root@XP5:~# 
root@XP5:~# gdb attach 557
2.1.2、GDB调试qemu-内核

qemu中包含有gdbserver功能,利用这个功能我们可以直接对kernel进行gdb调试;
-S freeze CPU at startup (use ‘c’ to start execution)
-s shorthand for -gdb tcp::1234
带参数启动,qemu系统在启动时就会暂停,并且使用gdbserver创建gdb调试端口1234
Plain Text
-S //在启动脚本增加qemu启动参数-S

./run-qemu.sh        //启动qenu
pc上另外开一个调试Shell窗口
gdb-multiarch       //启动调试
target remote:1234  //gdb里面输入连接qemnu端口

注意这个 file vmlinux //

2.1.3、GDB调试 coredump

在这里插入图片描述
配置并产生coredump文件(cornerstone默认就使能了coredump),参考:https://www.cnblogs.com/arnoldlu/p/11160510.html

# enable coredump default
/bin/mkdir /var/coredump
/bin/chmod 777 /var/coredump
# coredump 文件名加上 pid
/bin/echo 1 >/proc/sys/kernel/core_uses_pid
# 设定coredump 保存路径和命名方式
/bin/echo '/var/coredump/core-%e-%p-%t' > /proc/sys/kernel/core_pattern
# coredump 大小无上限
ulimit -c unlimited
# enable coredump end

开启gdb coredump调试

gdb program core  //core是程序非法执行后core dump后产生的文件
gdb test core-test_thread_0-419-1688546899  //example

2.2、调试程序

2.2.1、入门命令

help命令只是例出gdb的命令种类,如果要看种类中的命令,可以使用 help 命令,如:help breakpoints,查看设置断点的所有命令。

(gdb) help
List of classes of commands:

aliases -- User-defined aliases of other commands.
breakpoints -- Making program stop at certain points.
data -- Examining data.
files -- Specifying and examining files.
internals -- Maintenance commands.
obscure -- Obscure features.
running -- Running the program.
stack -- Examining the stack.
status -- Status inquiries.
support -- Support facilities.
tracepoints -- Tracing of program execution without stopping the program.
user-defined -- User-defined commands.

运行程序:
• 执行run(简写r)命令用于运行代码,在程序结束或者遇到断点处停下;
• 执行start,程序停留在main();
设置断点:
• 设置断点,b + 行号,就设置断点了,运行就会停在断点处;
• 可以用info b看我们设了那些断点;clear + 行号;
• b filename:linenum ,在源文件filename的linenum行处停住;
单步调试:
• continue(简写c):继续执行,到下一个断点处(或运行结束)。
• next(简写n):单步执行,跳过子函数。
• strp(简写s):单步执行,进入子函数。
查看变量:
ptype 变量名 显示变量的类型
print(简写p)+变量名

(gdb) p test_a
$2 = {test_c = 0 '\000', b = 0x0, if_t = true}
(gdb) 

其他
watch 使你能监视一个变量的值而不管它何时被改变
使用finish运行到函数到正常退出
set 设置变量的值。例如:set a=66 将把66保存到a变量中
return 强制从当前函数返回
list 查看源代码,No symbol table is loaded. Use the “file” command,编译时需要 -g

2.2.2、进阶命令

https://www.kancloud.cn/wizardforcel/gdb-tips-100/146708
gdb官方文档:https://sourceware.org/gdb/onlinedocs/gdb/

3、使用技巧

3.1、多线程

查看线程:
info threads ;其中Frame:显示的是线程执行到哪个函数;
也可以用“i threads [Id…]”指定打印某些线程的信息

(gdb) info threads

切换线程:thread [num]
通过线程的编号切换到指定的线程。例如:thread 2 //切换到编号为 2 的线程。
锁定线程:如果想在调试一个线程时,让其它线程暂停执行,可以使用 set scheduler-locking on 命令

(gdb) set scheduler-locking on
(gdb) r

补充:调试多进程可以新开窗口 attach 更加方便。
使用info inferiors 可以查看进程

3.2、查看栈信息

当程序调用了一个函数,函数的地址,函数参数,函数内的局部变量都会被压入“栈”(Stack)中。可以用GDB命令来查看当前的栈中的信息。
打印当前的函数调用栈的所有信息 Backtrace (bt);bt full显示栈中所有帧的完全信息;

(gdb) bt

查看当前栈层的信息 f(frame);info f 这个命令会打印出更为详细的当前栈层的信息,运行时的内存地址。比如:函数地址,调用函数的地址,被调用函数的地址,目前的函数是由什么样的程序语言写成的、函数参数地址及值、局部变量的地址等等

(gdb) info f

查看当前函数各个参数的值 info args

(gdb) info args
a = 0x7fe4000b60
b = 0x7feabff7b8
timeout = 1000
(gdb) 

打印出当前函数中所有局部变量及其值,info locals

(gdb) info locals
res = <optimized out>
a = 549399295992
b = <optimized out>
__func__ = "test_send"
end = <optimized out>
index = <optimized out>

info catch
打印出当前的函数中的异常处理信息。

3.3、查看寄存器的值

可以使用 i registers ;使用“i all-registers”命令,可以输出所有寄存器的内容;
要打印单个寄存器的值,可以使用 i registers regname 或者 p $regname ;

(gdb) i all-registers
x0             0x0                 0
x1             0x0                 0
x2             0x0                 0
x3             0xb                 11
x4             0x7f849e8c99        547685829785
x5             0x5581bfa8b8        367249041592
x6             0x6572207265767265  7309940820876358245
x7             0xb                 11
x8             0x62                98
x9             0x6977203130303035  7599578291844952117
x10            0x7272652030206874  8246765056589588596
x11            0xa65646f6320726f   749115342594273903
x12            0x353220203a61626c  3833161555636216428
x13            0x545345542d2d205d  6076276550090432605
x14            0x545345542d2d205d  6076276550090432605
x15            0x0                 0
x16            0x7f84cb8eb8        547688779448
x17            0x7f84af76c0        547686938304
x18            0x0                 0
x19            0x7f70002308        547339903752
x20            0x7f84ccc000        547688857600
x21            0x7f70002328        547339903784
x22            0x7f70002310        547339903760
--Type <RET> for more, q to quit, c to continue without paging--
x23            0x7f84ccc000        547688857600
x24            0x0                 0
x25            0x100050000         4295294976
x26            0x7f84ccb000        547688853504
x27            0x7f70000bf0        547339897840
x28            0x7f84cccfc4        547688861636
x29            0x7f76092810        547441158160
x30            0x7f84cc835c        547688842076
sp             0x7f760927f0        0x7f760927f0
pc             0x7f84cc835c        0x7f84cc835c <test_send+264>
cpsr           0x60001000          [ EL=0 SSBS C Z ]
#v0-v31
#fpsr
#fpcr
#q0-q31
#d0-d31
#s0-s31
#h0-h31
#b0-b31

这里看一下调试bug常见的arm64寄存器

x0~x7:传递子程序的参数和返回值,使用时不需要保存,多余的参数用堆栈传递,64位的返回结果保存在x0中。
x8:用于保存子程序的返回地址,使用时不需要保存。
x9~x15:用于存储临时变量、计算中间结果和函数参数传递。在函数调用期间,这些寄存器的值可以被破坏。
x16~x17:用于异常处理和指令相关场景。
x18:平台寄存器,它的使用与平台相关。
x19~x28:临时寄存器,可用于存储局部变量。
x29:帧指针寄存器(FP),用于保存栈底地址;
x30:链接寄存器(LR),保存调用跳转指令bl指令的下一条指令的内存地址;
SP:栈顶寄存器,用于保存栈顶地址;
PC: 保存将要执行的指令的地址(操作系统决定其值,不能改写);
cpsr:状态寄存器,按位起作用;

补充说明:
(Caller-saved register(又名易失性寄存器AKA volatile registers, or call-clobbered)用于保存不需要在各个调用之间保留的临时数据。)
(Callee-saved register(又称非易失性寄存器AKA non-volatile registers, or call-preserved)用于保存应在每次调用中保留的长寿命值。当调用者进行过程调用时,可以期望这些寄存器在被调用者返回后将保持相同的值,这使被调用者有责任在返回调用者之前保存它们并恢复它们)
详细情况参考ARM64手册如下章节。

函数调用:每个函数调用,都会有 入栈 和 出栈 操作。
函数入栈:
保存返回地址:将返回地址(函数调用指令的下一条指令地址)压入堆栈,以便函数执行完后能够返回到调用者。
保存寄存器:将需要在函数内部使用的寄存器的值保存到堆栈中,以便在函数执行期间不会丢失这些值。
分配局部变量空间:调整堆栈指针的位置,为局部变量分配空间。

函数出栈:
恢复寄存器:将之前保存在堆栈中的寄存器值恢复回来,确保函数返回后寄存器的值与函数调用前保持一致。
释放局部变量空间:通过调整堆栈指针的位置,释放之前为局部变量分配的空间。
恢复返回地址:从堆栈中弹出之前保存的返回地址,并跳转到该地址,将控制权返回给调用者。

stp x29, x30, [sp, #-64] //将栈底寄存器x29和返回地址寄存器x30的值存储到相对于堆栈指针sp偏移-64字节的内存地址上,并更新堆栈指针的值。
mov x29, sp //将堆栈指针的值存储到栈底寄存器x29中,用于维护堆栈帧。

ldp x29, x30,[sp], [#64] //从堆栈中恢复之前保存的栈底寄存器x29和返回地址寄存器x30的值,并更新堆栈指针的值以移除已恢复的数据。
Ret //控制权返回到调用此函数的位置

3.4、汇编

查看对应汇编 disassemble
自动反汇编后面要执行的代码:

(gdb) set disassemble-next-line on

(gdb) n
144        in /home/main.c
=> 0x0000005555551d08 <main+72>:        ff ff 07 a9        stp        xzr, xzr, [sp, #120]
(gdb) 

disas /m fun 命令将函数代码和汇编指令映射起来(当然编译产物是需要带有调试信息的,详情见使用准备章节);使用 disassemble /r 命令可以用16进制形式显示程序的原始机器码 ;

(gdb) disas /m xrte_init
Dump of assembler code for function xrte_init:
42        in /home/yanfan/test.c
   0x0000007ff7f73c64 <+0>:        stp        x29, x30, [sp, #-80]!
   0x0000007ff7f73c68 <+4>:        adrp        x0, 0x7ff7f9e000
   0x0000007ff7f73c6c <+8>:        mov        x29, sp
   0x0000007ff7f73c70 <+12>:        ldr        x0, [x0, #4048]
   0x0000007ff7f73c74 <+16>:        stp        x19, x20, [sp, #16]
   0x0000007ff7f73c80 <+28>:        stp        x21, x22, [sp, #32]
   0x0000007ff7f73c84 <+32>:        ldr        x1, [x0]
   0x0000007ff7f73c88 <+36>:        str        x1, [sp, #72]
   0x0000007ff7f73c8c <+40>:        mov        x1, #0x0                           // #0

43        in /home/init.c

   0x0000007ff7f73c78 <+20>:        adrp        x20, 0x7ff7f9f000
   0x0000007ff7f73c7c <+24>:        add        x20, x20, #0x8
   0x0000007ff7f73c90 <+44>:        mov        x2, x20
   0x0000007ff7f73c94 <+48>:        mov        w1, #0x1                           // #1
   0x0000007ff7f73c98 <+52>:        mov        w0, #0x2                           // #2
--Type <RET> for more, q to quit, c to continue without paging--

查看某一行所对应的地址范围 i line 42
只想查看这一条语句对应的汇编代码,可以使用 disassemble [Start],[End]

(gdb)  i line 156
Line 156 of "/home/main.c" starts at address 0x5555551d44 <main+132> and ends at 0x5555551d54 <main+148>.
(gdb) disassemble 0x5555551d44 , 0x5555551d54
Dump of assembler code from 0x5555551d44 to 0x5555551d54:
=> 0x0000005555551d44 <main+132>:        ldr        x1, [x19, #8]
   0x0000005555551d48 <main+136>:        adrp        x0, 0x5555554000 <xipc_async_send_res_callback+164>
   0x0000005555551d4c <main+140>:        add        x0, x0, #0xab7
   0x0000005555551d50 <main+144>:        bl        0x5555551c60 <printf@plt>
End of assembler dump.

3.5、查看内存

x/nfu addr
x addr
x Use the x command to examine memory.
n、f、u是可选的参数:
n 是一个正整数,表示显示内存的长度,也就是说从当前地址向后显示几个地址的内容。
f 表示显示的格式,参见上面。如果地址所指的是字符串,那么格式可以是s,如果地十是指令地址,那么格式可以是i。
u 表示从当前地址往后请求的字节数,如果不指定的话,GDB默认是4个bytes。

3.6、设置显示选项

地址、函数参数、结构体、联合体、数组、字符等。
例如:

(gdb) p *array@len
$1 = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40}

如果是静态数组的话,可以直接用print数组名,就可以显示数组中所有数据的内容了。
可使用ptype i 查看变量类型。
补充:打印所有线程堆栈
在gdb中使用 thread apply all bt 查看所用线程堆栈信息

三、gdb调试实战

1、在线调试

2、离线调试coredump

查看函数调用栈,查看寄存器、查看汇编、分析挂哪里、梳理代码,解bug

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

起风就扬帆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值