gdb调试-基础
本文是对以下文章的摘录:
C语言中文网-gdb调试
调试前提
只有具备调试信息的可执行文件才可被调试,即,编译时需要添加 -g
参数:
1 | gcc main.c main -g |
调试时最好不要优化代码,使用 -O0
默认级别即可。对 GDB 调试器更友好的是 -Og
选项,-Og 对代码所做的优化程序介于 O0 ~ O1 之间,真正可做到“在保持快速编译和良好调试体验的同时,提供较为合理的优化级别”。
常用命令
调试指令 | 作 用 |
---|---|
(gdb) help | help commands 能够显示 commands 命令的具体用法 |
(gdb) file program | 调试 program 程序 |
(gdb) break xxx (gdb) b xxx |
在源代码指定的某一行设置断点,其中 xxx 用于指定具体打断点的位置,可以是函数名、行号(当前源文件)、指定文件中的行号(b main.c:3)等 |
(gdb) run (gdb) r |
执行被调试的程序,其会自动在第一个断点处暂停执行。其后可以传入参数 |
(gdb) start | 执行到 main 函数中的第一条指令停止。其后可以传入参数 |
(gdb) continue (gdb) c |
当程序在某一断点处停止运行后,使用该指令可以继续执行,直至遇到下一个断点或者程序结束 |
(gdb) next (gdb) n |
令程序一行代码一行代码的执行,也可指定一次执行的行数,不步入函数 |
(gdb) step (gdb) s |
令程序一行代码一行代码的执行,也可指定一次执行的行数,遇到函数则步入 |
(gdb) until (gdb) u |
使 GDB 调试器快速运行完当前的循环体,并运行至循环体外停止,可指定在某行停下 |
(gdb) finish | 直接执行完当前函数 |
(gdb) return | 立即跳出当前函数(即使有剩余代码也不会继续执行),并返回指定值 |
(gdb) print var (gdb) p var |
打印指定变量的值,也可以修改变量值:print var=1,高级用法参见print |
(gdb) display | 每次程序暂停后都打印指定变量,使用 undisplay 或 disable 来取消显示 |
(gdb) list (gdb) l |
显示源程序代码的内容,包括各行代码所在的行号 |
(gdb) delete | 删除指定编号的断点,不指定则删除全部 |
(gdb) clear | 删除指定行的断点,不指定则删除当前行的断点 |
(gdb) disable | 禁用指定编号的断点,不指定则禁用所有断点 |
(gdb) enable | 开启指定编号的断点,不指定则开启所有断点 |
(gdb) quit (gdb) q |
终止调试 |
-q -quiet -silent |
取消启动 GDB 调试器时打印的介绍信息和版权信息 |
–args prog arg1 (gdb) set args arg1 … |
调试可执行程序 prog 并传入参数 arg1 |
(gdb) cd | 修改 GDB 调试器的工作目录 |
(gdb) path xx/xx | 临时修改环境变量 |
(gdb) info xxx | 提供有关程序状态和调试环境的信息 |
(gdb) bt (gdb) backtrace (gdb) info stack |
显示函数的调用堆栈 |
(gdb) info frame | 显示当前函数栈帧的详细信息 |
(gdb) up | 返回上一函数栈帧 |
(gdb) down | 返回下一函数栈帧 |
(gdb) x | 显示指定地址处的值,有多种格式,详细可在 gdb 中输入 help x 查看 |
(gdb) disassemble | 反汇编指定地址处的代码,不指定则反汇编当前函数 |
(gdb) layout asm | 以汇编格式调试整个程序,可以使用 layout src 切换到源代码模式 |
(gdb)
表示已经运行 gdb,目前是 gdb 中的命令行。
break
gdb 中有三种断点——普通断点、观察断点、捕捉断点。
break 属于普通断点,其常用的语法格式有以下 2 种。
- (gdb) break location
- (gdb) break … if cond
location 的值 | 含 义 |
---|---|
linenum | linenum 是一个整数,表示要打断点处代码的行号,行号通过 list 命令可以看到。 |
filename:linenum | filename 表示源程序文件名;linenum 表示具体行数。 |
+ offset - offset | offset 为整数(假设值为 2),+offset 表示以当前程序暂停位置(例如第 4 行)为准,向后数 offset 行处(第 6 行)打断点。 |
function | function 表示程序中包含的函数的函数名,即 break 命令会在该函数内部的开头位置打断点,程序会执行到该函数第一行代码处暂停。 |
filename:function | filename 表示远程文件名;function 表示程序中函数的函数名。整体的意思是在指定文件 filename 中 function 函数的开头位置打断点。 |
1 | (gdb) b 7 if num>10 |
break 还有两种变体:tbreak 和 rbreak
tbreak 和 break 命令的用法和功能都非常相似,唯一的不同在于,使用 tbreak 命令打的断点仅会作用 1 次 。
rbreak 命令的作用对象是 C、C++ 程序中的函数,它会在指定函数的开头位置打断点 。rbreak 命令的使用语法格式为:
1 | (gdb) rbreak regex |
其中 regex 为一个正则表达式,程序中函数的函数名只要满足 regex 条件,rbreak 命令就会其内部的开头位置打断点。该方法的一个妙用可参见gdb调试-进阶 。
使用 delete 命令清除所有断点,info breakpoints 查看所有断点:
1 | (gdb) delete |
watch
watch 断点可以监控程序中某个变量或者表达式的值,只要发生改变,程序就会停止执行:
1 | (gdb) watch var |
watch 的变体还有 rwatch 和 awatch:
- rwatch 命令:只要程序中出现读取目标变量(表达式)的值的操作,程序就会停止运行;
- awatch 命令:只要程序中出现读取目标变量(表达式)的值或者改变值的操作,程序就会停止运行。
watch 命令有 硬件断点 和 软件断点 两种实现方式。软件断点监控目标变量后,GDB 调试器会以单步执行的方式运行程序,并且每行代码执行完毕后,都会检测该目标变量的值是否发生改变,因此软件断点会影响调试效率。硬件断点通过使用少量的寄存器(例如 32 位的 Intel x86 处理器提供有 4 个调试寄存器)来监控变量的值,而无需每次都主动检测变量是否改变,因此硬件断点不影响调试效率。
大多数 PowerPC 或者基于 x86 的操作系统,都支持采用硬件观点。并且 GDB 调试器在建立观察断点时,会优先尝试建立硬件观察点,只有当前环境不支持硬件观察点时,才会建立软件观察点。
catch
catch 捕捉断点的作用是监控程序中某一事件的发生 ,例如程序发生某种异常时、某一动态库被加载时等等,一旦目标时间发生,则程序停止执行。 tcatch 命令只监控一次事件的发生。
指令 | 描述 |
---|---|
catch exec | 捕获对exec的调用。 |
catch fork | 捕获对fork的调用。 |
catch load | 捕获共享库的加载。 |
catch unload | 捕获共享库的卸载。 |
catch signal | 按名称和/或编号捕获信号。 |
catch syscall | 按名称、组和/或编号捕获系统调用。 |
catch throw | 捕获被抛出的异常。 |
… | … |
break、watch 有 if 形式,而 catch 则没有,如果想要使其成为条件断点,则需要用到 condition 命令 :
1 | (gdb) info break |
frame
1 | (gdb)info frame <-- 打印当前栈帧的详细信息 |
info
常见的 info 命令如下:
命令 | 描述 |
---|---|
info breakpoints | 列出当前设置的所有普通断点 |
info watchpoints | 列出当前设置的所有观察断点 |
info registers | 显示所有寄存器的当前值 |
info address | 显示符号对应的地址 |
info threads | 列出当前正在运行的所有线程 |
info functions | 列出程序中定义的所有函数 |
info variables | 列出 当前所有可见 的变量 |
info locals | 打印当前函数中所有局部变量的值 |
info args | 打印参数值 |
info source | 显示当前源文件的名称和行号 |
info sources | 显示此程序包含的所有源文件 |
info frame | 显示当前帧的信息,包括函数名称和参数 |
info stack | 显示调用堆栈 |
info program | 显示正在调试的程序的名称和进程ID |
info sharedlibrary | 列出当前加载的共享库 |
info target | 显示当前目标的信息,例如目标文件或核心转储文件 |
info display | display 命令查看的目标变量或表达式 |
更多 info 参数可输入 help info 进行查看。
调试方式
-
调试尚未运行的程序:
1
2
3gdb program
或者
(gdb) file program -
调试正在运行的程序
1
2
3(gdb) attach program
或者
gdb -p 进程号这种方式可能需要 root 权限。调式完毕后可执行
detach
指令,使 GDB 调试器和程序分离:1
2
3(gdb) detach
或者直接退出
(gdb) q
核心转储
在 Linux 操作系统中,当程序执行发生异常崩溃时,系统可以将发生崩溃时的内存数据、调用堆栈情况等信息自动记录下载,并存储到一个文件中,该文件通常称为 core 文件,Linux 系统所具备的这种功能又称为核心转储(core dump)。当程序发生异常崩溃时,通过 GDB 调试 core 文件,往往可以更快速的解决问题。
默认情况下,Linux 系统不开启 core dump 这一功能,执行以下命令:
1 | [root@bogon demo]# ulimit -a |
如果 core file size(core 文件大小)对应的值为 0,表示当前系统未开启 core dump 功能。这种情况下,可以通过执行如下指令改变 core 文件的大小:
1 | [root@bogon demo]# ulimit -c unlimited |
假设执行 main.exe 发生数组越界错误生成 core 文件,则调试方法如下:
1 | [root@bogon demo]# gdb main.exe core |
如果没有生成 core,请参考此处 。
注意,Core文件中包含了程序运行时的内存映像(代码、数据和堆栈信息)、进程状态、系统信息、调试符号等,如果程序在崩溃时使用了大量的内存,那么 Core 文件可能会很大,可以使用 ulimit 命令来限制 Core 文件的最大大小。