玩转Linux GDB & pdb
一、GDB调试🐯
-
watch -n
指令的使用:# 每隔1s在终端打印一次当前系统内存使用情况 watch -n 1 "cat /proc/meminfo" # 每隔1s查看当前系统中所有正在运行的进程 # ps:查看系统进程; -e:显示所有进程;-f:全格式 # ps -aux指令也用于查看进程。两者的输出风格不同,内容几乎无差别,一般推荐使用-elf watch -n 1 "ps -elf"
-
nm
指令的使用:# 查看可执行文件或者动态链接库的符号表(函数、变量等) nm ***.exe # 加上grep可以精确定位 nm ***.exe | grep 待查名称
-
gdb
指令:# 打开一个可视化的gdb调试终端,开始调试程序文件 gdb -tui **.exe # 此外,也可以对正在运行中的进程,切入gdb调试 gdb -p 进程PID # or gdb attach 进程PID
- 进入调试:
(gdb)l 数字n # (小写L)显示出代码从第n行开始的内容,默认显示10行;之后再次输入l,会再往后输出10行 (gdb)set args model/yolo4_tf.xmodel 0 -t 8 # set args:设置程序启动参数 (gdb)run # run指令:进入主程序,立即开始执行,直到遇到断点或者程序结束 (gdb)start # start指令:进入主程序,停在main()主函数入口处;等待下一步指示(手动打断点等) (gdb)break n (简写b n) # 在第n行处设置断点(可以带上代码路径和代码名称: b /usr/codeprj/OAGUPDATE.cpp:578) (gdb)break func # 在函数func()的入口处设置断点,如:break cb_button (gdb)info b # 显示当前程序的断点设置情况 (gdb)delete 断点号n # 删除第n个断点 (gdb)clear 行号n # 清除第n行的断点,每行可能有多个不同的断点 (gdb)disable 断点号n # 暂停第n个断点 (gdb)enable 断点号n # 开启第n个断点 (gdb)delete breakpoints # 清除所有断点 (gdb)quit # 使用 quit 命令退出当前gdb调试进程;之后如果再次启动调试,会消除上一次调试操作中建立的所有断点 (gdb)continue # 继续执行代码,直到遇到下一个断点或者代码执行结束! # GDB 调试器共提供了3种可实现单步调试程序的方法,即使用 next、step 和 until 命令 (gdb)next # 当遇到包含调用函数的语句时,无论函数内部包含多少行代码,next指令都会一步执行完 (gdb)step # 当step命令所执行的代码行中包含函数时,会进入该函数内部,在函数第一行代码处停止执行 (gdb)finish/return # 结束当前执行的函数:finish命令会执行函数到正常退出;而return命令是立即结束执行当前函数并返回,即剩下未执行的也不管了
- 打印变量:
(gdb)print num # 输出或者修改指定变量或者表达式的值 (gdb)print file::variable # file用于指定具体的文件名,variable表示要查看的目标变量或表达式 (gdb)print function::variable # funciton 用于指定具体所在函数的函数名 (gdb)p/x variable # 按十六进制格式显示变量 (gdb)p/d variable # 按十进制显示 (gdb)p/u variable # 按十六进制显示无符号整型 (gdb)p/o variable # 按八进制显示变量 (gdb)p/t variable # 按二进制显示变量 (gdb)p/c variable # 按字符格式显示变量 (gdb)p/f variable # 按浮点数显示变量
- 打印数组:
# 打印数组内容的同时显示出数组下标 set print array-indexes on # 数组默认打印200个元素,但也可以设置具体个数 set print elements 具体数字num # num为0时,即不限制元素个数 # 选择打印数组范围 print *(arr+起始地址)@num # 打印arr+128后的6个元素 print *(arr+128)@6 print arr # 打印arr数组
- 使用
backtrace
查看栈桢信息(函数调用的顺序):-
当我们阅读代码和查找BUG时,往往有一个烦恼。就是我们不知道函数的调用顺序。而这些函数调用顺序对应我们理解程序结构,程序运行过程是很有帮助的。关于函数的信息都存放在栈中。
# 栈帧就是一个函数执行的环境:函数参数、函数的局部变量、函数执行完后返回到哪里等等 bt:backtrace (gdb)bt # 查看当前所处函数栈帧的数据 (gdb)bt full # 打印当前所处函数栈帧的所有参数信息 # 定位栈异常的指令: (gdb)frame N # 切换到栈编号为N的栈帧 N:bt对应的栈编号 (gdb)info frame # 打印当前函数(N)所处的栈桢信息 (gdb)info locals # 打印当前函数内的局部变量 (gdb)info args # 查看当前函数参数
-
示例:
//frame.c #include <stdio.h> int sum(int n) { int ret = 0; if( n > 0 ) { ret = n + sum(n-1); } return ret; } int main() { int s = 0; s = sum(10); printf("sum = %d\n", s); return 0; }
-
示例代码的调试过程:
1. 设置断点:设置到递归结束标志的位置:
(gdb) start The program being debugged has been started already. Start it from the beginning? (y or n) y Temporary breakpoint 4 at 0x80483f9: file frame.c, line 19. Starting program: /home/delphi/workspace/test.out Temporary breakpoint 4, main () at frame.c:19 19 int s = 0; (gdb) break sum if n==0 # 设置sum函数中, n==0 时的数据断点。 Breakpoint 5 at 0x80483ca: file frame.c, line 6. (gdb) info break # 查看断点信息 Num Type Disp Enb Address What 5 breakpoint keep y 0x080483ca in sum at frame.c:6 stop only if n==0
2. 查看函数调用过程:
(gdb) continue Continuing. Breakpoint 5, sum (n=0) at frame.c:6 6 int ret = 0; # 通过栈桢号判断函数的调用顺序:上一行的函数被下一行的函数调用 (gdb) backtrace # 查看函数调用的顺序 #0 sum (n=0) at frame.c:6 #1 0x080483e5 in sum (n=1) at frame.c:10 #2 0x080483e5 in sum (n=2) at frame.c:10 #3 0x080483e5 in sum (n=3) at frame.c:10 #4 0x080483e5 in sum (n=4) at frame.c:10 #5 0x080483e5 in sum (n=5) at frame.c:10 #6 0x080483e5 in sum (n=6) at frame.c:10 #7 0x080483e5 in sum (n=7) at frame.c:10 #8 0x080483e5 in sum (n=8) at frame.c:10 #9 0x080483e5 in sum (n=9) at frame.c:10 #10 0x080483e5 in sum (n=10) at frame.c:10 #11 0x0804840d in main () at frame.c:21
3. 分析函数调用过程:
(gdb) next # 单步执行,不进入函数 8 if( n > 0 ) (gdb) next 13 return ret; (gdb) info args # 查看当前函数参数的值 n = 0 (gdb) frame 7 # 切换栈编号为7的上下文中 #7 0x080483e5 in sum (n=7) at frame.c:10 10 ret = n + sum(n-1); (gdb) info args # 查看栈编号为7时函数参数的值 n = 7 (gdb) info locals # 查看当前局部变量ret的值 ret = 0 # 计算结果 (gdb) frame 0 #0 sum (n=0) at frame.c:13 13 return ret; (gdb) info registers # 查看当前寄存器的值 eax 0x0 0 ecx 0x241be83d 605808701 edx 0x1 1 ebx 0x287ff4 2654196 esp 0xbffff070 0xbffff070 ebp 0xbffff098 0xbffff098 esi 0x0 0 edi 0x0 0 eip 0x80483eb 0x80483eb <sum+39> eflags 0x200246 [ PF ZF IF ID ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 (gdb) info frame # 查看当前栈帧的详细信息 Stack level 0, frame at 0xbffff0a0: eip = 0x80483eb in sum (frame.c:13); saved eip 0x80483e5 called by frame at 0xbffff0d0 source language c. Arglist at 0xbffff098, args: n=0 Locals at 0xbffff098, Previous frame's sp is 0xbffff0a0 # 上一个栈指针地址 Saved registers: # 将下面值保存到寄存器中 ebp at 0xbffff098, eip at 0xbffff09c (gdb) x /1wx 0xbffff098 # 查看ebp地址中的值 0xbffff098: 0xbffff0c8 (gdb) next 14 } (gdb) next 13 return ret; (gdb) info args n = 1 (gdb) info registers # 查看栈帧编号为1的寄存器值 eax 0x1 1 ecx 0x241be83d 605808701 edx 0x1 1 ebx 0x287ff4 2654196 esp 0xbffff0a0 0xbffff0a0 ebp 0xbffff0c8 0xbffff0c8 esi 0x0 0 edi 0x0 0 eip 0x80483eb 0x80483eb <sum+39> eflags 0x200202 [ IF ID ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 (gdb) info locals ret = 1 # 计算结果
-
-
-
调试执行异常崩溃的程序:
-
当然,如果直接使用
gdb **.exe
的方式调试程序,就不用这种定位崩溃方式了! -
在Linux操作系统中,当程序执行发生异常崩溃时,系统可以将发生崩溃时的内存数据、调用堆栈情况等信息自动记录下载,并存储到一个文件中,该文件通常称为core文件,Linux系统所具备的这种功能又称为核心转储(core dump)。幸运的是,GDB对
core
文件的分析和调试提供有非常强大的功能支持,当程序发生异常崩溃时,通过GDB调试产生的core
文件,往往可以更快速的解决问题。 -
如何设置
core dump
文件在系统的生成存放目录:Linux-Coredump分析基础 ubuntu如何生成core文件 core文件去哪了 -
写个代码验证一下:
#include <stdio.h> int main() { char *a = NULL; *a = 2; // 这行一看就是有BUG return 0; }
-
编译:
- CMake配置GDB调试选项:修改CMakeLists.txt文件
# -g:一定不能少,否则后面用gdb调试时候不能直观地显示出BUG所在位置 $ g++ -g -o test core.cpp $ ./test Segmentation fault (core dumped) <-- 发生段错误,并生成了 core 文件
-
可以根据生成时间查找
core dump
文件:# 这里假设core文件被存放在/home/homework/coresave路径下 ls /home/homework/coresave -hl | grep test -rw-rw-rw- 1 root root 400K Mar 13 15:08 core.test.27725.1615619332 -rw-rw-rw- 1 root root 400K Mar 13 15:26 core.test.7791.1615620408 -rw-rw-rw- 1 root root 540K Mar 11 10:29 core.test.1868.1615429740 -rw-rw-rw- 1 root root 400K Mar 13 15:07 core.test.26880.1615619264 -rw-rw-rw- 1 root root 404K Mar 3 19:42 core.test.28802.1614771771
-
用
gdb
进行调试:# test: 要调试的可执行文件的名称 $ gdb test /home/homework/coresave/core.test1.7791.1615620408 Reading symbols from /home/zhudi/project/linux/blog/gdb/test...done. warning: core file may not match specified executable file. [New LWP 7791] Core was generated by `./test'. Program terminated with signal 11, Segmentation fault. #0 0x00000000004005bd in main () at core.cpp:5 5 *a = 2;
-
由此可见,程序崩溃了在第五行,定位到了出现问题的代码位置。
-
二、pdb调试😸
-
注意,因为 Python 是一种解释型语言,可以通过
pdb
shell 执行命令。ipdb
是一种增强型的pdb
,它使用IPython
作为 REPL并开启了 tab 补全、语法高亮、更好的回溯和更好的内省,同时还保留了pdb
模块相同的接口。 -
调试python代码,可以使用ipdb调试工具
python -m ipdb bubble.py
-
进入调试过程后,基本操作指令和gdb大同小异:
# 显示当前行附近的11行或继续执行之前的源码显示 $ l(ist) # 打断点 $ b 6 # 在第六行 # 一步一步执行,遇到函数就进入 $ step # 继续执行直到当前函数的下一条语句或者 return 语句 $ next # 继续运行,直到执行结束/出现报错/遇到断点 $ c # 代码因某种原因停下后,打印数据;可以配合step使用 $ p 变量名 $ p locals() # 打印当前时刻所有变量 # 继续执行直到当前函数返回 $ return # 停止调试 $ q # 重启调试 $ restart
-
-
它们都对类 C 语言的调试进行了优化,它允许您探索任意进程及其机器状态:寄存器、堆栈、程序计数器等。