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

一、 gdb安装

源代码编译gdb

 

1、下载源码

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

下载gdb源码包

 

下载 wget

网址:http://ftp.gnu.org/gnu/gdb/gdb-8.0.1.tar.gz

解压: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/profile
Shift + G
i
# No core files by default 0, unlimited is oo
ulimit -S -c unlimited > /dev/null 2>&1
wq!

source /etc/profile

 

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

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

 

su root

vi /etc/sysctl.conf
Shift + G
i

# open, add core.pid 
kernel.core_pattern = ./core_%t_%p_%e
kernel.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命令表示查看加载源码内容

 

下面将演示如何加断点

 

 

表示调试的程序开始运行

 

 

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

 

 

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

 

总结一下

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

 

到这里,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地址开始 234字节有符号十进制数输出

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

用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 == 817行设置一个断点并且只有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 callback

static 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 关闭又会回到你调试这个其它线程不阻塞

 

总结

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

 

info threads

thread id

set scheduler-locking on/off

 

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

posted @ 2021-02-01 11:13  panda胖胖秦  阅读(483)  评论(0)    收藏  举报