启动管理
# 开启调试
gdb <executable>
# 给a.out传参调试
gdb --args a.out "hello"
# 设置main参数
(gdb) set args <arg1> <arg2>
# 退出调试
q, quit
断点调试
行号断点
有源码情况下可以使用断点调试,包括普通断点和条件断点等:
# 1.显示源码
layout src #layout split 会同时显示汇编和源码
# 或者ctrl+x+a
# 2.设置断点
start # 开始运行并在main函数处断点
b 5 # 在第5行断点
b 5 if a==3 # 在第5行断点,当栈内变量a等于3时
# 3.设置自动变量
display <变量1>,<变量2>
# 4.运行
c
# 5.查看函数栈调用信息
bt
# 6.查看函数栈内的变量信息
info locals
# 7.单步调试
s #执行到下一条语句
n #执行到下一行
变量变更断点
watch <变量>
事件断点
# 子进程断点
catch [ fork | exec ]
# 信号断点
catch signal <信号>
# 事件断点
catch syscall <系统调用>
coredump调试
自动coredump文件
程序崩溃时默认不会生成coredump文件,需要以下配置才能生成coredump文件:
# 1.设置coredump文件大小为无限
ulimit -c unlimited
# 2.在root用户中设置coredump文件生成路径,这种方式设置的参数值只在当前会话中有效,系统重启后会失效。
sudo bash -c 'echo "core.%e" > /proc/sys/kernel/core_pattern'
设置后可以使用coredump文件进行调试崩溃时的函数栈信息:
# 有源码调试
gdb <可执行程序> <coredump文件>
# 无源码调试
gdb -c <coredump文件>
手动coredump文件
如果程序出现死锁,会一直处于等待状态,这时需要手动coredump:
# 手动产生coredump文件
gdb attach <进程id>
(gdb) gcore <coredump名>
(gdb) detach
(gdb) q
# 调试coredump文件
gdb <可执行程序> <coredump文件>
无源码调试
gcc -g main.c
时会添加符号信息和调试信息;
gcc main.c
时会添加符号信息;
strip
会去除符号信息和调试信息;
# 从第一条指令调试
starti
# 单步指令调试
si
ni
# 查看寄存器信息
info registers
x/4xw 0x1000
# 设置断点
break *0x1234
常见段错误
段错误(Segmentation fault)通常是由程序访问了未分配的内存或者已经释放的内存、越界访问数组或指针等导致的。具体原因可能包括:
- 访问了空指针或者已经释放的指针:程序试图读取或写入一个指向空地址的指针,或者试图释放已经被释放的内存块。
- 访问了非法地址:程序访问了一个不属于它的内存地址空间,比如试图访问未分配的内存、访问已经释放的内存等。
- 栈溢出:程序使用了太多的栈空间,导致栈溢出,比如函数递归调用过多或者局部变量太多等。
- 越界访问数组或指针:程序访问了数组或指针的非法下标或指针偏移量,比如访问数组的负数下标、访问数组的最后一个元素之后的位置等。
- 内存对齐问题:程序试图访问未对齐的内存地址。
- 动态链接库问题:程序在动态链接库中调用了一个不存在的函数或者访问了一个不属于它的动态链接库中的地址等。
除了以上几种情况外,还有可能是硬件故障或操作系统错误等原因导致的段错误。因此,对于遇到段错误的情况,需要具体分析具体情况,并根据错误提示信息进行调试和排查。
多进程调试
如果想调试子进程,父进程自由执行:
# 调试fork时创建的子进程
set follow-fork-mode child
# 调试exec创建的子进程
#set follow-exec-mode new
默认状态下,gdb继续调试父进程,子进程自由执行,如果想调试子进程,则新开一个gdb附加到子进程:
(gdb) attach <子进程>
如果想在gdb上同时调试父子进程,也就是子进程创建后即中断:
# 关闭子进程分离模式
set detach-on-fork off
# 查看进程信息
info inferiors
# 切换到子进程
inferior <子进程号>
注意:
如果父进程进入了等待状态或死锁,不归还终端,可以使用Ctrl + c
命令中断进程,并查看状态。
多线程调试
# 查看线程
info threads
# 切换线程
thread <线程id>
# 设置断点
b <断点号> thread <线程id>
# 为线程执行gdb命令
thead apply <线程号1> <线程号2> info locals
远程调试
# 客户机器安装gdbserver
sudo apt install gdbserver
# 客户机关闭防火墙
# 本机远程目标机ip远程调试,由于关了防火墙,端口号可以是任意的
gdbserver <ip>:<端口号> <可执行程序>
target remote <ip>:<端口号> <可执行程序>
发行版调试
# 输出调试版的符号表
objcopy --only-keep-debug <调试版二进制> <符号表>
# 使用符号表调试发布版
gdb --symbol=<符号表> -exec=<发布版>
其他
获取程序的入口地址
# 方法1
readelf -h a.out | grep Entry
# 方法2
(gdb) info files
杂项
# 刷新界面
(gdb) refresh
# 切换函数栈帧
(gdb) frame <帧号>
# 调用shell命令
(gdb) shell <命令>
# 中断程序
Ctrl + c