一、gdb安装【源代码编译gdb】
-
下载源码
下载gdb源码包
下载wget
网址:
解压:tar -zxvf gdb-8.0.1.tar.gz/
-
配置
在解压目录下找到configure,执行./configure
等待配置完成
-
make && make install 等待
-
查看: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
分别是:查看,切换,设置同步调试。到这里,多线程调试基本完毕。