GDB调试
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
1 GDB调试的基础指令
使用gdb调试下面的一段c程序:
#include <stdio.h>
#include <stdlib.h>
#define N 10
//使用1~20的随机数初始化数组
void init_arr(int* arr, int n)
{
for(int i=0;i<n;i++){
arr[i]=rand()%20+1;
}
}
//打印数组中的值
void print_arr(int* arr, int n)
{
for(int i=0;i<n;i++){
printf("arr[%d]=%d\n",i,arr[i]);
}
}
//选择排序
void select_sort(int* arr, int n)
{
int temp=0;
for(int i=0;i<n;i++){
int max=arr[i];
int max_index=i;
for(int j=i;j<n;j++){
if(arr[j]>max){
max=arr[j];
max_index=j;
}
}
temp=arr[i];
arr[i]=arr[max_index];
arr[max_index]=temp;
}
}
//主函数
int main(void)
{
int arr[N];
srand(time(NULL));
init_arr(arr,N);
print_arr(arr,N);
select_sort(arr,N);
printf("********After sort:********:\n");
print_arr(arr,N);
return 0;
}
首先介绍gdb中常用命令及示例:
1)进入gdb调试的方法,首先使用gcc生成带有调试信息的可执行文件,该文件比普通可执行文件要大;使用-g选项
gcc test1.c -o a.out -g
2)使用gdb调试a.out程序,使用命令
gdb a.out
3)在gdb中输入 l或者list命令,可以查看被调试程序的源码,但第一次输入一般显示在内存中尚存的代码;
4)使用list 1或者l 1命令,可以让源码从头开始显示;然后再输入 l 命令,显示后续的数值;
5)在所关注的地方插入断点,使用命令
b 23 #代表在23行插入断点,b是break的简写
6)输入命令 r 代表run,使程序运行,程序命中断点
7)使用n代表执行下一步,使用s代表单步执行,两者在简单的赋值或者运算语句的效果是相同的,但是对于函数语句的执行效果不同
n/next代表跳过函数执行的过程,直接到函数执行完成的下一步;
s/step会进入函数执行过程;
一般在执行系统调用函数的时候,一般会使用n,因为这些函数一般情况下是正确的,而且系统调用有可能内部嵌套函数比较多;执行自定义函数的时候,一般会使用s,因为这些函数是自己写的,可能会有逻辑错误,所以单步执行较为合适。
8)打印调试过程的信息
使用 p/print var #var代表变量名称
例如 p i #代表查看程序中i的值
9)continue是继续执行断点的后续指令;
若后面没有断点,将直接显示程序执行结果;
10)退出gdb当前调试;
gdb使用的大前提:是程序是自己写的,这样才对变量和变量的值的正确性有所判断
2 GDB调试的辅助指令
将上面的程序修改为下面的样式:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define N 10
//使用1~20的随机数初始化数组
void init_arr(int* arr, int n)
{
for(int i=0;i<n;i++){
arr[i]=rand()%20+1;
}
}
//打印数组中的值
void print_arr(int* arr, int n)
{
for(int i=0;i<n;i++){
printf("arr[%d]=%d\n",i,arr[i]);
}
}
//选择排序
void select_sort(int* arr, int n)
{
int temp=0;
for(int i=0;i<n;i++){
int max=arr[i];
int max_index=i;
for(int j=i;j<n;j++){
if(arr[j]>max){
max=arr[j];
max_index=j;
}
}
temp=arr[i];
arr[i]=arr[max_index];
arr[max_index]=temp;
}
}
//主函数
int main(void)
{
int arr[N];
char* str="helloworld";
srand(time(NULL));
init_arr(arr,N);
str[5]='W';
print_arr(arr,N);
select_sort(arr,N);
printf("********After sort:********:\n");
print_arr(arr,N);
return 0;
}
在主程序中有一行,将str[5]=‘W’,试图修改只读字符,就会发生段错误;对于段错误,在编译中并不会发生任何异常,但是在执行时,会显示“段错误“;
在gdb中调试段错误,无需单步调试,只需要直接运行程序,gdb会显示出现段错误的位置;
命令 | 简写 | 作用 |
---|---|---|
help | h | 按模块列出命令类 |
help class | 查看某一具体类型的具体命令 | |
list | l | 查看代码,可跟行号和函数名 |
quit | q | 退出gdb |
run | r | 全速运行程序 |
start | 单步执行,运行程序,停在第一行执行语句 | |
next | n | 逐过程执行,不会跳转到函数内部 |
step | s | 逐语句执行,遇到函数,会跳转到内部 |
backtrace | bt | 查看函数的调用的栈帧和层级关系 |
info | i | 查看GDB内部局部变量的数值,info breakpoints |
frame | f | 切换函数的栈帧 |
finish | 切换当前函数,返回到函数调用点 也可以用于结束主函数 | |
set | 设置变量的值 set var n=100 | |
run argv[1] argv[2] | 调试时命令行传参 | |
p | 打印变量和地址 | |
break | b | 设置断点,可根据行号 |
set args | 用于给main函数传入参数,也可使用run 参数,可以传入参数 |
delete | d | 删除断点 d breakpoints num |
---|---|---|
display | 设置观察变量(每次执行都重新显示变量的值) | |
undisplay | 取消观察变量(使用编号取消) | |
continue | c | 继续全速运行下面的代码 |
enable breakpoints | 启动断点 | |
disable breakpoints | 禁用断点 | |
x | 查看内存 x /20xw 显示20个单元,16进制,4字节每单元 | |
watch | 被设置观察点的变量发生修改时,打印显示 | |
i watch | 显示观察点 | |
core | ulimit -c 1024 开启core文件,调试时 gdb a.out core |
b 24 if i=5 (条件断点)
(gdb) b 24 if i=5
Breakpoint 3 at 0x12cb: file gdbtest1.c, line 24.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000000013d9 in main at gdbtest1.c:42
2 breakpoint keep y 0x0000000000001441 in main at gdbtest1.c:48
3 breakpoint keep y 0x00000000000012cb in select_sort at gdbtest1.c:24
stop only if i=5
此时打印变量i的值,显示 i=5
ptype var 查看变量类型
栈帧:随着函数调用而在stack上开辟的一片内存空间,用于存放函数调用时产生的局部变量和临时值
有多少个函数,就会有多少个栈帧(main函数也有栈帧),函数调用结束之后,栈帧消失。
每个函数只能访问自己的栈帧范围,而不能范围其它函数的栈帧。当然可以访问全局变量和全局函数;
当在一个函数的范围内,试图使用ptype查看另外一个不在这个函数范围内的变量的类型的时候,会出现错误。
此时,可以首先通过bt/backtrace查看栈帧列表,得到目前正在运行的栈帧,及其序号
之后通过 f/frame num切换栈的序号,然后可以再次调用ptype查看数据类型
3 GDB调试的常见错误
1)调试显示没有符号表被读取,请使用“file”命令
原因:说明在使用gcc生成可执行程序的时候,没有添加-g选项
解决方法:添加-g选项,重新生成可执行文件