纯干货: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命令表示:查看加载源码内容。

 

下面将演示如何加断点:

 

 

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 删除有后面跟断点索引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关闭。又会回到你调试的这个,其它线程不阻塞。

 

 

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

 

info threads

thread id

set scheduler-locking on/off

 

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值