gdb安装
一般linux系统中都安装好了,如果没有可以使用下面命令
#:apt-get update
#:apt-get install gdb
gdb使用
一般要使用gdb调试某个程序,都需要程序中带了符号表,这样才能清晰的看到调试的每一行代码、调用的堆栈信息、变量名、函数名、寄存器、内存信息等,所以使用gcc编译程序时,需要加上 -g 选项:
gcc test.c -g -o test
gdb成功加载后会有下面的提示:
Reading symbols from /root/test/test...done.
如果没有符号表(没有加-g 或者 程序strip后)
Reading symbols from /root/test/test...(no debugging symbols found)...done.
gdb成功加载后就可以用各种调试命令了:
命令 | 描述 |
backtrace(bt) | 查看各级函数调用及参数 |
finish | 连续运行到当前函数返回为止,然后停下来等待命令 |
return | 结束当前调用函数并返回指定值,到上一层函数调用处,立即返回 |
until(u) | 运行到指定行停下来 |
jump(j) | 将当前程序执行流跳转到指定行或地址 |
frame(f) 帧编号 | 选择栈帧 |
info(i) locals | 查看当前栈帧局部变量的值 |
list(l) | 列出源代码,接着上次的位置往下列,每次列十行 list+ list- list file:linenumber某文件某行开始 |
list 行号 | 列出第几行开始的源代码 |
list 函数名 | 列出某个函数的源代码 |
next(n) | 执行下一行语句 |
print(p) | 打印表达式的值,通过表达式的值可以修改变量的值或者调用函数 |
quit(q) | 退出gdb调试环境 |
set var | 修改变量的值 |
start | 开始执行程序,停在main函数第一行语句前面等待命令 |
step(s) | 执行下一行语句,如果有函数则进入到函数中 |
break(b) | 在某一行/函数开头设置断点 |
break(b)… if… | 设置条件断点 |
tbreak(tb) | 添加临时断点 |
enable | 启用某个断点 |
disable | 禁用某个断点 |
continue(c) | 从当前位置开始连续运行程序 |
delete breakpoints 断点号 | 删掉此号的断点 |
display 变量名 | 跟踪查看某个变量,每次停下来都显示它的值 |
disable breakpoints 断点号 | 禁用此断点 |
info(i) breakpoints | 查看当前设置了哪些断点 |
run(r) | 从头开始连续运行程序 |
undisplay 跟踪显示行号 | 取消跟踪显示 |
watch | 设置观察点 |
info(i) watchpoints | 查看当前设置了哪些观察点 |
ptype | 查看变量类型 |
x | 从某个位置开始打印存储单元的内容,全部当成字节来看,而不区分哪个字节属于哪个变量 |
disassemble(dis) | 反汇编当前函数或者指定的函数,单独用disassemble命令是反汇编当前函数,如果disassemble命令后面跟函数名或地址则反汇编指定的函数。 |
info registers | 可以显示所有寄存器的当前值。在gdb中表示寄存器名时前面要加个$,例如p $esp可以打印esp寄存器的值。 |
set follow-fork-mode child/parent | 设置gdb在fork之后跟踪子进程/父进程 |
set args 'command-line' | 给执行的程序传命令行参数 |
show args | 查看设置的命令行参数 |
thread | 切换到指定线程 |
使用 GDB 调试程序一般有三种方式:
- gdb filename 直接调试可执行程序
- gdb attach pid 附加进程
- gdb filename corename 调试core文件
自定义core文件名字:
/proc/sys/kernel/core_uses_pid
可以控制产生的 core 文件的文件名中是否添加 PID 作为扩展,如果添加则文件内容为 1,否则为 0;/proc/sys/kernel/core_pattern 可以设置格式化的 core 文件保存位置或文件名。修改方式如下:
echo "/corefile/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
参数名称 | 参数含义 |
---|---|
%p | 添加 pid 到 core 文件名中 |
%u | 添加当前 uid 到 core 文件名中 |
%g | 添加当前 gid 到 core 文件名中 |
%s | 添加导致产生 core 的信号到 core 文件名中 |
%t | 添加 core 文件生成时间(UNIX)到 core 文件名中 |
%h | 添加主机名到 core 文件名中 |
%e | 添加程序名到 core 文件名中 |
1、直接gdb可执行程序
2、附加进程
程序启动后,可以使用 gdb attach 进程ID 来将gdb调试器附加到该进程ID的程序上,attach后程序会停下来,用continue命令继续执行,
调试完后可以在gdb中执行detach命令分离
3、调试core文件
有时候程序运行已经崩溃,此时如果产生了core文件,我们可以通过core来调试,当然产不产生core文件依赖是否配置
[root@izbp11ddoyj3i4745fb2nbz test]# ulimit -a
core file size (blocks, -c) 0 //为0表示不产生core文件,可以使用ulimit -c unlimited修改为不限制大小
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 15088
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 65535
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 15088
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
一般core文件都是叫core.12958之类的
[root@izbp11ddoyj3i4745fb2nbz test]# gdb test core.12958
修改core文件名称
/proc/sys/kernel/core_uses_pid //写0不添加pid为core文件扩展,写1为添加
/proc/sys/kernel/core_pattern //设置格式化的core文件路径及文件名
echo "/corefile/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
参数名称 | 参数含义(英文) | 参数含义(中文) |
---|---|---|
%p | insert pid into filename | 添加 pid 到 core 文件名中 |
%u | insert current uid into filename | 添加当前 uid 到 core 文件名中 |
%g | insert current gid into filename | 添加当前 gid 到 core 文件名中 |
%s | insert signal that caused the coredump into the filename | 添加导致产生 core 的信号到 core 文件名中 |
%t | insert UNIX time that the coredump occurred into filename | 添加 core 文件生成时间(UNIX)到 core 文件名中 |
%h | insert hostname where the coredump happened into filename | 添加主机名到 core 文件名中 |
%e | insert coredumping executable name into filename | 添加程序名到 core 文件名中 |
一些命令及用法的描述
jump:程序执行跳转
jump <location>
可以是程序的行号或者函数的地址,jump 会让程序执行流跳转到指定位置执行,但是由于中间的代码没执行导致不可预知的错误
until:运行到什么条件停止
until + 行数
until + 条件 //例如until i == 200
disassemble:反汇编
dis 函数名
gdb默认反汇编为AT&T格式,可以通过show disassembly-flavor 查看,一般通过 set disassembly-flavor intel 来设置成intel汇编格式
x 打印内存地址的值
x /nfu <addr>
x 是 examine 的缩写
n表示要显示的内存单元的个数
f表示显示方式, 可取如下值
x 按十六进制格式显示变量。
d 按十进制格式显示变量。
u 按十进制格式显示无符号整型。
o 按八进制格式显示变量。
t 按二进制格式显示变量。
a 按十六进制格式显示变量。
i 指令地址格式
c 按字符格式显示变量。
f 按浮点数格式显示变量。
u表示一个地址单元的长度,
b表示单字节,
h表示双字节,
w表示四字节,默认是这个
g表示八字节
例如:x/3xh buf buf中读取 3个单位,双字节为一个单位,十六进制显示
set args和show args
set args 传递命令行参数给程序,如果想清除已经设置好的,那么执行set args不加任何参数即可
tbreak 临时断点
该断点出发一次后自动删除
注意:GDB中的断点调试分为软件断点和硬件断点两种类型:
- 软件断点:由非法指令异常实现(软件实现)
- 硬件断点:由硬件特性实现(数量有限) 需要cpu支持
通常情况下,我们使用的都是软件断点。那么,什么情况下我们需要用到硬件断点和数据断点呢?
1. 当代码位于只读存储器(Flash)时,只能通过硬件断点调试。
2. 硬件断点需要硬件支持,数量有限
3. GDB中通过hbreak命令支持硬件断点
4. hbreak与break使用方式完全一致
watch
监视一个变量或者一段内存,当这个变量或者该内存处的值发生变化时,GDB 就会中断下来
监视变量
int i;
watch i
监视指针
char *p;
watch p 与 watch *p
监视数组或者内存空间
char buf[128];
watch buf
这里是对 buf 的 128 个数据进行了监视,此时不是采用硬件断点,而是用软中断实现的。用软中断方式去检查内存变量是比较耗费 CPU 资源的,精确地指明地址是硬件中断
display
监视的变量或者内存地址,每次程序中断下来都会自动输出这些变量或内存的值
将print打印结果显示完整
当使用 print 命令打印一个字符串或者字符数组时,如果该字符串太长,print 命令默认显示不全的,我们可以通过在 GDB 中输入 set print element 0 命令设置一下,这样再次使用 print 命令就能完整地显示该变量的所有字符串了
让被gdb调试的程序接收信号
请看如下代码
void prog_exit(int signo)
{
std::cout << "program recv signal [" << signo << "] to exit." << std::endl;
}
int main(int argc, char* argv[])
{
//设置信号处理
signal(SIGCHLD, SIG_DFL);
signal(SIGPIPE, SIG_IGN);
signal(SIGINT, prog_exit);
signal(SIGTERM, prog_exit);
int ch;
bool bdaemon = false;
while ((ch = getopt(argc, argv, "d")) != -1)
{
switch (ch)
{
case 'd':
bdaemon = true;
break;
}
}
if (bdaemon)
daemon_run();
//省略无关代码...
}
在这个程序中,我们接收到 Ctrl + C 信号(对应信号 SIGINT)时会简单打印一行信息,而当用 GDB 调试这个程序时,由于 Ctrl + C 默认会被 GDB 接收到(让调试器中断下来),导致无法模拟程序接收这一信号。解决这个问题有两种方式:
- 在 GDB 中使用 signal 函数手动给程序发送信号,这里就是 signal SIGINT;
- 改变 GDB 信号处理的设置,通过 handle SIGINT nostop print 告诉 GDB 在接收到 SIGINT 时不要停止,并把该信号传递给调试目标程序 。
多线程下禁止线程切换
多线程程序执行时候使用n,可能跳到不同的执行语句,这是多线程cpu时间片切换到其他线程导致的,我们可以使用如下命令锁定当前线程:set scheduler-locking on(锁定)/off(解除)
gdb调试多进程
一个进程fork出来的子进程,有两种方式调试
1、gdb attach 到子进程
2、GDB 调试器提供了一个选项叫 follow-fork,可以使用 show follow-fork mode 查看当前值,也可以通过 set follow-fork mode 来设置是当一个进程 fork 出新的子进程时,GDB 是继续调试父进程还是子进程(取值是 child),默认是父进程( 取值是 parent)。
(gdb) show follow-fork mode
Debugger response to a program call of fork or vfork is "parent".
(gdb) set follow-fork child (gdb) show follow-fork mode
Debugger response to a program call of fork or vfork is "child".
自定义gdb调试命令
在某些场景下,我们需要根据自己的程序情况,制定一些可以在调试时输出程序特定信息的命令,这在 GDB 中很容易做到,只要在 Linux 当前用户家(home)目录下,如 root 用户是 “/root” 目录,非 root 用户则对应 “/home/ 用户名”目录。
在上述目录中自定义一个名叫 .gdbinit 文件,在 Linux 系统中以点号开头的文件名一般都是隐藏文件,因此 .gdbinit 也是一个隐藏文件,可以使用 ls -a 命令查看(a 的含义是 all 的意思,即显示所有文件,当然也就包括显示隐藏文件);如果不存在,使用 vim 或者 touch 命令创建一个就可以,然后在这个文件中写上你自定义命令的 shell 脚本即可。