一、gdb安装【源代码编译gdb】
-
下载源码
网址: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/
-
配置
在解压目录下找到configure,执行./configure
等待配置完成
-
make && make install 等待
-
查看: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 删除有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关闭。又会回到你调试的这个,其它线程不阻塞。
gdb多线程调试,常用就是以下这3个实用命令:
info threads
thread id
set scheduler-locking on/off
分别是:查看,切换,设置同步调试。到这里,多线程调试基本完毕。