前情提要:
跳转执行
命令 | 功能 |
---|---|
jump <line> | 跳转到第line行 |
jump [+ | -] <num> | 跳转当当前行向下 \ 上 num行 |
jump *<addr> | 跳转到addr地址的代码处,地址前要加*号 |
jump <label> | 跳转到指定标签,该标签是goto语句的标签 |
jump <function> | 跳转到指定函数内,跳转的函数可以没有出现在当前函数中。只推荐在当前函数内跳转,跳转到其他函数可能会出问题 |
中间跳过的代码是不会执行的,跳到的位置如果没有断点,那么GDB会自动继续往后执行,所以一般会在跳转到的地方添加断点,且跳转命令不会更改当前栈帧、堆栈指针、程序计数器以外的任何寄存器。
反向调试
Ubuntu 24.04、gdb (Ubuntu 15.0.50.20240403-0ubuntu1)环境下,在record模式下无法执行cout、printf等与外设有关的函数,其他环境没有测试。
命令 | 功能 |
---|---|
record | 开始录制回放,只有录制了回放的部分才能反向调试 |
record-stop | 结束录制回放 |
reverse-finish | 回退执行到在所选函数被调用之前 |
reverse-next / rn | 倒退执行,不进入函数 |
reverse-setp / rs | 倒退执行,进入函数 |
reverse-continue / rc | 倒退执行到上一个断点处 |
多进程调试
命令 | 功能 |
---|---|
set follow-fork-mode <child | parent> | 设置多进程的跟随模式,child为fork后跟随子进程,parent为跟随父进程,默认为parent模式 |
show follow-fork-mode | 查询当前多进程跟随模式 |
set detach-on-fork <on | off> | 设置在fork函数创建进程后,gdb是否会detach另一个不被跟随的进程,默认为on,如果想调试多进程,必须设置为off |
i inferiors | 查看当前被调试的进程,id旁的*号代表是当前进程。 gdb用inferior来表示一个被调试进程的状态信息(包括内存空间,进程空间等)。通常情况下,一个inferior代表一个进程,有时候一个inferior不一定会绑定一个进程。这是gdb内部的概念与定义,inferior可以为空。 |
add-inferior | 添加一个空inferior,空inferior可以用于attch另一个需要调试的进程,但必须先切换到空inferior |
remove-inferiors <id> | 移除一个空inferior,且不能删除当前inferior |
inferior <id> | 切换到对应inferior |
detach inferiors <id> | detach对应inferior的进程,也可以切换到对应inferior让然后直接detach |
show schedule-multiple | 显示当前多进程运行模式 |
set schedule-multiple <on | off> | 设置多进程运行模式,如果为off,那么只有当前inferior绑定的进程才会受gdb控制运行,其余进程都会被阻塞。如果为on,那么所有inferior对应的进程都会受gdb命令控制,而不是一直阻塞在一个地方。 为on模式下,n就是所有进程一起向下执行一步,c就是所有进程一起执行到下一个断点处。在off模式下,n和c就只有当前进程会执行命令,其余进程全部阻塞 |
调用程序内外部函数
命令 | 功能 |
---|---|
p / call <expression> | 对表达式求值,并打印结果值。表达式可以是被调试程序中的自定义函数,也可以是C库函数和操作符(即使是在原程序中没有调用这些函数也可以直接调用)。如果是执行函数,需要在函数中传入参数 |
在调用sum(int, int)时直接传入参数:
如果是通过p调用,那么即使返回值是void也会显示出来;如果是通过call调用,如果返回值是void就不会显示
这种调用方式会真实地执行一次函数,所以可以通过在函数内加断点来调试函数
调用C库函数malloc示例
在release程序(没有调试符号很多事都做不了)中仍然可以通过p和call来调用函数,如果不知掉程序中有哪些函数,可以通过 i functions来打印程序中的全部函数
调试时跳过指定函数
s命令不会进入该函数,但是仍然会执行,用于跳过构造函数等一些不重要或不需要关注或连环调用的函数(如srand(static_cast<unsigned int>(time(nullptr))),调用srand的过程中连环调用了time)。
命令 | 功能 |
---|---|
skip <function> | 如果存在同名函数就会跳过所有同名函数,可以通过添加限定符来解决这个问题,如 skip test()就只会跳过无参的test函数,skip test(int)就只会跳过int类型的test函数 |
skip file <filename> | 跳过目标文件中的所有函数 |
skip -gfi <dir> / <*.*> | 通过通配符的方式来跳过函数,*可以替换。 如:skip -gfi lib/*.cpp就是跳过lib目录中所有后缀为cpp的文件中的所有函数 |
调试发行版程序
命令(以下都是在bash中执行的命令) | 功能 |
---|---|
objcopy --only-keep-debug <debug file> <debuginfo file> | 将debug版本程序中的调试符号单独提取出来存到一个文件中 |
gdb --symbol=<debug file | debuginfo file> -exec=<release file> | 通过加载debug版本的调试符号来调试release版本的程序 |
gdb <debug file> <core file> | 调试release或debug版本的core dump文件 |
修改可执行文件
流程 | 功能 |
---|---|
gdb -write <exe> | 通过写方式来调试程序,可以是debug程序,也可以是release程序,一般情况下gdb都是以只读方式调试的 |
disassemble /mr <function> | 查看需要修改的函数的反汇编 |
然后通过 p / set来修改内存,记住要判断机器是大端字节序还是小端字节序 | |
q | 退出 |
内存泄漏检查
命令 | 功能 |
---|---|
call malloc_stats() | 查看当前进程和当前线程的内存分配情况.主要用于检查某个函数是否发生了内存泄漏,在函数执行前调用一下,在函数执行后调用一下,看看二者是否相同,如果不相同那么就内存泄漏了 |
call malloc_info(0, stdout) | malloc_info的输出格式为xml,主要需要关心的字段为rest(剩了多少)。然后也是通过比较被检查函数调用前后的rest字段对应数值的大小是否变化来判断是否内存泄漏 |
内存检查(通过gcc-g++编译器来实现)
命令 | 功能 |
---|---|
gcc / g++ -fsanitize=address | 检查内存泄漏,检查栈溢出,检查堆溢出,检查全局内存溢出,检查释放后再使用 |
该选项只有在debug模式下支持(即加上-g选项)。
远程调试
服务端 / 被调试机
命令 | 功能 |
---|---|
gdbserver <本机ip:gdbserver将使用的端口> <exe> | 通过gdbserver新启动一个程序来调试 |
gdbserver <本机ip:gdbserver将使用的端口> --attach <pid> | 通过gdbserver附上一个已经启动的程序来调试 |
被调试机在启动gdbserver后就不能自主退出了,只有在连接上调试机且调试机退出后才会自动退出(kill除外)。
客户端 / 调试机
流程 | 功能 / 解释 |
---|---|
gdb(在bash中) | 直接启动gdb |
target remote <被调试机ip:被调试机gsbserver使用的端口) | 连接被调试机 |
调试 | 远端调试几乎与本机调试一模一样,但是 远端调试不能使用run,是使用continue来开始执行程序的 远程调试需要需要两端配合起来调试,因为虽然gdb的控制权在调试机手上,但是输入、输出都在被调试机器上 |
quit / q / detach | 退出调试,调试机退出了,被调试机会自动退出 |