纯干货:Linux系统下的调试神器gdb

2,349 阅读5分钟

一、gdb安装【源代码编译gdb】

  1. 下载源码

    网址:ftp.gnu.org/gnu/gdb

    下载gdb源码包

    下载wget

    网址:

    ftp.gnu.org/gnu/gdb/gdb…

    解压:tar -zxvf gdb-8.0.1.tar.gz/

  2. 配置

    在解压目录下找到configure,执行./configure

    等待配置完成

  3. make && make install 等待

  4. 查看:gdb –v。查看是否成功

**二、**gdb基本调试

(一)gdb 开始调试开始上手

1、开启core,采集程序崩溃的状态

首先你跟着我做开启core崩溃状态采集,可以通过 ulimit -c 查看。如果是0表示没有开启。

开启后按照下面操作:

su root vi /etc/profileShift + Gi# No core files by default 0, unlimited is ooulimit -S -c unlimited > /dev/null 2>&1wq! source /etc/profile

上面shell操作是在 /etc/profile 最后一行添加,上面设置全局开启core文件调试,大小不限,最后,立即生效。

再跟着我做,因为生成的core文件同名会覆盖。这里为其加上一个core命名规则,让其变成 [core.pid] 格式。

su root vi /etc/sysctl.confShift + Gi # open, add core.pid kernel.core_pattern = ./core_%t_%p_%ekernel.core_uses_pid = 1 wq! sysctl -p /etc/sysctl.conf

在 /etc/sysctl.conf文件中添加系统配置,之后立即启用,最后是下面状态表示core启用都搞好了。

kernel.core_pattern模式参数:

%% 单个%字符

%p 所dump进程的进程ID

%u 所dump进程的实际用户ID

%g 所dump进程的实际组ID

%s 导致本次core dump的信号

%t core dump的时间 (由1970年1月1日计起的秒数)

%h 主机名

%e 程序文件名

2、简单接触gdb,开始调试 r n p:

第一个演示代码 monkey.c

#include <stdio.h> int g_var = 0; static int _add(int a, int b) {    printf("_add callad, a:%d, b:%d\n", a, b);    return a+b;} int main(void) {    int n = 1;        printf("one n=%d, g_var=%d\n", n, g_var);    ++n;    --n;        g_var += 20;    g_var -= 10;    n = _add(1, g_var);    printf("two n=%d, g_var=%d\n", n, g_var);        return 0;}

编译gcc -g -Wall -o monkey.out monkey.c

第一个命令 gdb monkey.out 表示 gdb加载 monkey.out 开始调试。如果需要使用gdb调试的话编译的时候,gcc需要加上 -g命令。

其中l命令表示:查看加载源码内容。

下面将演示如何加断点:

r 表示调试的程序开始运行。

p 命令表示打印值,n表示过程调试,到下一步;不管子过程如何都不进入,直接一次跳过。

上面用的 s 表示单步调试,遇到子函数,会进入函数内部调试。

总结一下:

l 查看源码,br加断点,r开始运行调试,n下一步, s下一步但是会进入子函数,p输出数据。

到这里,gdb 基本会用了,是不是也很容易、直白?小代码可以随便调试了。

看到这,基础知识普及完毕,那我们接着来聊一聊。

(二)gdb其它开发中用的命令

编译命令:

gcc -g -Wall monkey.c -o monkey.out

1、gdb 其它常用命令用法 c q b info

首先看,用到的调试文件king.c

#include <stdio.h>#include <stdlib.h>#include <time.h> /* * arr 只能是数组 * 返回当前数组长度 */#define LEN(arr) (sizeof(arr)/sizeof(*arr)) // 简单数组打印函数static void _parrs(int a[], int len) {    int i = -1;    puts("当前数组内容值如下:");     while(++i < len)         printf("%d ", a[i]);        putchar('\n');} // 简单包装宏, arr必须是数组#define PARRS(arr) \    _parrs(arr, LEN(arr)) #define _INT_OLD (23) /* * 主函数,简单测试 * 测试 core文件,  * 测试 宏调试 * 测试 堆栈内存信息 */int main(void) {    int i;    int a[_INT_OLD];    int* ptr = NULL;         // 来个随机数填充值吧    srand((unsigned)time(NULL));    for(i=0; i<LEN(a); ++i)        a[i] = rand()%222;        PARRS(a);     //全员加double, 包含一个错误方便测试    for(i=1; i<=LEN(a); ++i)        a[i] <<= 1;    PARRS(a);     // 为了错,强制错    *ptr = 0;     return 0;}

在 king.c 中我们开始调试。一运行段错误,出现了我们的 core.pid 文件

通过 gdb core.18605.king.out 开始调试,马上定位出来了错误原因。

2、调试内存堆栈信息

刚开始 print a ,在main中当做数组处理,打印的信息多,后面在_add函数中, a就是个形参数组地址。

主要看 info args ,查看当前函数参数值

info locals 看当前函数栈上值信息,info registers 表示查看寄存器值。

然后再查看内存信息

需要记得东西多一些。先看图:

x /23dw a 意思是:查看从a地址开始 23个 4字节,有符号十进制数,输出。

关于x 更加详细见下列步骤:

用gdb查看内存格式:

x /nfu ptr

说明:

x 是 examine 的缩写

n 表示要显示的内存单元的个数

f 表示显示方式, 可取如下值

x 按十六进制格式显示变量

d 按十进制格式显示变量

u 按十进制格式显示无符号整型

o 按八进制格式显示变量

t 按二进制格式显示变量

a 按十六进制格式显示变量

i 指令地址格式

c 按字符格式显示变量

f 按浮点数格式显示变量

u表示一个地址单元的长度

b表示单字节

h表示双字节

w表示四字节

g表示八字节

Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal),

t(binary), f(float), a(address), i(instruction), c(char) and s(string).

Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes)

ptr 表示从那个地址开始

这个命令常用于监测内存变化,调试中特别常用

3、gdb设置条件断点

很简单:**b 17 if i == 8。**在17行设置一个断点,并且只有i==8的时候才会触发。

4、gdb 删除断点

gdb 删除有d 后面跟断点索引1,2,3

clear 行数或名称,删除哪一行断点,看接下来的演示:

到这里,介绍的gdb调试技巧基本都够用了。感觉用图形ide,例如vs调试,也就用到这些了。

三、gdb高级调试——调试多线程

首先看测试用例 FightingSaintBuddha.c

#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <sys/syscall.h>#include <sys/types.h> #define MAX_NUM 1000// thread callbackstatic void* _run(void* arg) {    int piyo = 10;        char* name = (char*)arg;    int i;        for(i = 0; i < MAX_NUM; ++i) {        printf("[thread %u]: <%d, hi!> from %s\n", syscall(SYS_gettid), i, name);		sleep(1);    }     return NULL;}#define _INT_PTX (3)char* thread_name[] = {"A", "B", "C", "D", "E", "F", "G", "H"}; int main(void) {    int i;    pthread_t tx[_INT_PTX];     puts("main beign");    	printf("main thread id :%u\n",/*syscall(SYS_gettid)*/getpid());     for(i=0; i<_INT_PTX; ++i) {        //create threads        rt = pthread_create(tx+i, NULL, _run, thread_name[i]);        if(rt < 0) {            printf("pthread_create create error! rt = %d, i=%d\n", rt, i);            break;        }    }       for(i=0; i<_INT_PTX; ++i) {        pthread_join(tx[i], NULL);	}         puts("end");         return 0;}  

编译命令

gcc -Wall -g -o FightingSaintBuddha.out FightingSaintBuddha.c -lpthread

那先看一下下方测试图:

上面 info threads 查看所有运行的线程信息,*表示当前调试的线程

后面 l _run 表示查看 _run附近代码。当然,还有 l 16 查看16行附近文件内容。

gdb多线程切换,测试如下:

thread 2表示切换到第2个线程,info threads第一列id 就是 thread 切换的id。

我们用下面命令,只让待调试的线程跑,其它线程阻塞。

set scheduler-locking on开始多线程单独调试。

如果不用了,则设置set scheduler-locking off关闭。又会回到你调试的这个,其它线程不阻塞。

gdb多线程调试,常用就是以下这3个实用命令:

info threads

thread id

set scheduler-locking on/off

分别是:查看,切换,设置同步调试。到这里,多线程调试基本完毕。