GDB调试
概念
-
GDB
是由 GNU 软件系统社区提供的调试工具,同GCC
配套组成了一套完整的开发环境,GDB
是 Linux 和许多类 Unix 系统中的标准开发环境 -
一般来说,
GDB
主要帮助你完成下面四个方面的功能- 启动程序,可以按照自定义的要求随心所欲的运行程序
- 可让被调试的程序在所指定的调置的断点处停住(断点可以是条件表达式)
- 当程序被停住时,可以检查此时程序中所发生的事
- 可以改变程序,将一个 BUG 产生的影响修正从而测试其他 BUG
-
在GDB(GNU Debugger)中,命令的缩写是为了提高用户的效率和便捷性而设计的。GDB允许用户使用命令的前几个字符作为缩写,只要这些缩写是唯一的并且不与其他命令冲突。这种设计使得用户在调试过程中可以更快速地输入命令,提高工作效率。
-
命令缩写的规则:
- GDB命令可以被缩写为其前几个字符,只要这些字符足以唯一地标识该命令。
- 例如,
list
命令可以缩写为l
或li
,因为在GDB中没有其他命令以l
或li
开头。
-
命令示例:
list
命令用于显示源代码,可以缩写为l
或li
。info break
命令用于显示断点信息,可以缩写为i b
。
-
具体例子:
-
list 命令:
- 完整命令:
list
- 缩写形式:
l
或li
- 解释:在GDB中,没有其他命令以
l
或li
开头,因此l
和li
都可以唯一地标识list
命令。
- 完整命令:
-
info break 命令:
- 完整命令:
info break
- 缩写形式:
i b
- 解释:
info
命令有很多子命令,但break
子命令可以缩写为b
,因此info break
可以缩写为i b
。
- 完整命令:
-
-
准备工作
-
安装gdb
- sudo apt update
- sudo apt install gdb
-
安装后查看gdb是否安装成功
- gdb -v
- gdb -v
-
使用以下命令编译:
gcc -g -Wall program.c -o program
- 通常,在为调试而编译时,我们会关掉编译器的优化选项(
-O
), 并打开调试选项(-g
)。另外,-Wall
在尽量不影响程序行为的情况下选项打开所有warning,也可以发现许多问题,避免一些不必要的 BUG -g
选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证gdb
能找到源文件
- 通常,在为调试而编译时,我们会关掉编译器的优化选项(
-
注:当在
gdb
中直接使用回车
时,会默认执行上一条命令
常用命令
说明
- program1.c:源文件如下
点击查看代码
#include <stdio.h>
int sum(int a,int b);
int main(int argc, char *argv[])
{
for(int i = 0; i < argc; i++){
printf("argv[%d]: %s\n", i, argv[i]);
}
printf("Hello world\n");
for(int i = 1; i < 5; i++){
printf("%d\n", i);
}
int a = 1, b = 2;
printf("a+b = %d\n", sum(a,b));
return 0;
}
int sum(int a, int b){
return a+b;
}
- program2.c:源文件如下
点击查看代码
int main()
{
pid_t pid = fork();
if(pid > 0){
for(int i = 0; i < 5; i++){
printf("I am parent process, pid = %d\n", getpid());
sleep(1);
}
}else if(pid == 0){
for(int i = 0; i < 5; i++){
printf("I am child process, pid = %d\n", getpid());
sleep(1);
}
}else{
perror("fork() failed");
exit(1);
}
return 0;
}
启动与退出
-
启动:
gdb 可执行程序
或者gdb
file [filename]
- 方法一
- 方法二
- 方法一
-
退出:
quit/q
给程序设置参数/获取设置参数
-
设置参数:
set args 10 20
或者gdb --args program1 10 20
- 方法一
- 方法二
- 方法一
-
获取设置参数:
show args
GDB使用帮助
help
查看当前文件代码
-
默认位置显示,下翻:
list/l
-
默认位置显示,上翻:
list/l -
-
从指定的行显示:
list/l 行号
-
从指定的函数显示:
list/l 函数名
-
注:查看时会显示前后文
查看非当前文件代码
-
编译运行并使用
gdb program
- 注意:要查看其他文件,需要在生成可执行文件
program
时,确保所有源文件都被编译并链接 - 增加swap.h:源文件如下
点击查看代码
#ifndef __SWAP_H #define __SWAP_H void swap(int *a, int *b){ *a ^= *b; *b ^= *a; *a ^= *b; } #endif
- 修改program1.c:源代码如下
点击查看代码
#include <stdio.h> #include "swap.h" int sum(int a,int b); int main(int argc, char *argv[]) { for(int i = 0; i < argc; i++){ printf("argv[%d]: %s\n", i, argv[i]); } printf("Hello world\n"); for(int i = 1; i < 5; i++){ printf("%d\n", i); } int a = 1, b = 2; printf("before swap: a=%d, b=%d\n", a, b); printf("a+b = %d\n", sum(a,b)); swap(&a, &b); printf("after swap: a=%d, b=%d\n", a, b); return 0; } int sum(int a, int b){ return a+b; }
- 编译链接:gcc -g -Wall program1.c -o program1
- 注意:要查看其他文件,需要在生成可执行文件
-
显示当前调试会话中已加载的源文件列表:
info sources
-
从指定文件指定的行显示:
list/l 文件名:行号
-
从指定文件指定的函数显示:
list/l 文件名:函数名
-
查看及设置显示的行数
- 查看显示的行数:
show list/listsize
- 设置显示的行数:
set list/listsize
- 查看显示的行数:
断点操作
-
查看断点:
i/info b/break
-
设置一般断点
b/break 行号
b/break 函数名
b/break 文件名:行号
b/break 文件名:函数
-
设置条件断点(一般用在循环的位置):
b/break 10 if i==5
-
删除断点:
d/del/delete 断点编号
-
设置断点无效:
dis/disable 断点编号
-
设置断点生效:
ena/enable 断点编号
-
忽略断点n次:
ignore 断点编号 忽略次数
调试操作
- 现有断点
-
运行
GDB
程序 (重新调试程序)-
程序停在第一行:
start
-
遇到断点才停:
run
-
-
继续运行,到下一个断点停:
c/continue
-
向下执行一行代码(不会进入函数体):
n/next
-
向下单步调试(遇到函数进入函数体)
-
s/step
-
跳出函数体:
finish
-
变量操作/监视
-
打印变量值:
p/print 变量名/表达式
-
打印变量类型:
pt/ptype 变量名/表达式
-
自动变量操作
-
自动打印指定变量的值:
display 变量名
-
查看自动变量:
i/info dis/display
-
取消自动变量:
undis/undisplay 编号
-
其它操作
-
设置变量值:
set var 变量名=变量值 (循环中用的较多)
-
跳出循环:
until 希望跳转到的行号
多线程操作
-
说明:使用gdb调试时,gdb默认只能跟踪一个进程,可以在fork函数调用前,通过指令设置gdb调试工具来跟踪父进程或子进程,默认gdb跟踪父进程
-
调试program2可执行程序
-
查看调试父进程或子进程:
show follow-fork-mode
-
设置调试父进程或子进程:
set follow-fork-mode [parent(default) | child
-
调试模式
-
说明:默认为on,表示调试当前进程时,其余进程继续运行,如果为off,则调试当前进程时,其余进程被gdb挂起
-
查看当前调试模式:
show detach-on-fork
-
设置on:
set detach-on-fork on
-
设置off:
set detach-on-fork off
-
查看当前调试的进程:
i/info i/inferiors
- 当设置调试子进程,其余进程为挂起状态时
- 当设置调试子进程,其余进程为挂起状态时
-
切换当前进程的调试:
inferior id
- 切换到父进程
- 切换到父进程
-
使进程脱离gdb调试,不再挂起:
detach inferiors id
- 子进程脱离挂起状态
- 子进程脱离挂起状态
-