首先你跟着我做开启core崩溃状态采集. 可以通过ulimit -c
查看,如果是0
表示没有开启. 开启按照下面操作:
sudo gedit /etc/profile
在/etc/profile
最后一行添加下面几句话设置全局开启 core文件调试,大小不限.
# No core files by default 0, unlimited is oo
ulimit -S -c unlimited > /dev/null 2>&1
最后立即生效.
source /etc/profile
再跟着我做, 因为生成的core
文件同名会覆盖. 这里为其加上一个core
命名规则, 让其变成[core.pid]
格式.
sudo gedit /etc/sysctl.conf
在该文件的最后的加上如下几句话,并保存
# open, add core.pid
kernel.core_pattern = ./core_%t_%p_%e
kernel.core_uses_pid = 1
立即启用
sudo sysctl -p /etc/sysctl.conf
最后是ulimit -c
与cat /proc/sys/kernel/core_uses_pid
查看,下面状态表示core启用都搞好了.
如果显示没有开启成功,可以试试注销系统或者重启
第一个演示代码heoo.c
#include
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 heoo.out heoo.c
gdb heoo.out
gdb heoo.out
表示gdb
加载heoo.out
开始调试. 如果需要使用gdb
调试的话编译的时候gcc
需要加上-g
命令.
其中l
命令表示 查看加载源码内容. .
下面将演示如何加断点,使用命令b 函数名
或者b 行数
,r
表示调试的程序开始运行.
p
命令表示 打印值. n
表示过程调试, 到下一步. 不管子过程如何都不进入. 直接一次跳过.
下面的s 表示单步调试, 遇到子函数,会进入函数内部调试.
总结一下 . l
查看源码 ,b
加断点, r
开始运行调试, n
下一步, s
下一步但是会进入子函数. p
输出数据. c
跳过直到下一个断点处,watch 变量名
给变量添加监视点,whatis 变量名
打印变量名的类型, finish
跳出当前代码(之前跳入调试),q
表示程序退出.
到这里gdb 基本会用了. 是不是也很容易. 直白. 小代码可以随便调试了.
看到这里基础知识普及完毕了. 后面可以不看了. 有机会再看. 好那我们接着扯.
开始扯一点, linux总是敲命令操作, 也很不安全. 有时候晕了. 写这样编译命令.
gcc -g -Wall -o heoo.c heoo.out
非常恐怖, heoo.c
代码删除了. heoo.out => heoo.c
先创建后生成失败退出. 原先的内容被抹掉了. 哈哈. 服务器开发, 经验不足, 熟练度不够.自己都怕自己.
首先看 用到的调试文件houge.c
#include
#include
#include
/*
* 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
同样需要仔细看下面图中使用的命令. 首先对前言部分加深一些. 看下面
这个图是前言的补充, c
跳过直到下一个断点处, q
表示程序退出.
在houge.c
中我们开始调试. 输入下面指令进行运行:
gcc -g -Wall -o houge.out houge.c
./houge.out
一运行段错误, 出现了我们的 core.pid
文件
通过gdb houge.out core.27047
开始调试. 马上定位出来了错误原因.
刚开始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 表示从那个地址开始
如下如所示,很简单b 17 if i == 8
. 在17行设置一个断点,并且只有i==8
的时候才会触发.
d
后面跟断点索引1,2,3…clear
行数或名称. 删除哪一行断点. 看下面演示到这里 介绍的gdb调试技巧基本都够用了. 感觉用图形ide,例如vs调试也就用到这些了.
估计gdb调试突破20min过去了.够用了. 后面可以不用看了.
加入你正在使用GDB7.0以上版本的调试器并且运行在支持反向调试的平台,你就可以用以下几条命令来调试程序:
reverse-continue
反向运行程序知道遇到一个能使程序中断的事件(比如断点,观察点,异常)。
reverse-step
反向运行程序到上一次被执行的源代码行。
reverse-stepi
反向运行程序到上一条机器指令
reverse-next
反向运行到上一次被执行的源代码行,但是不进入函数。
reverse-nexti
反向运行到上一条机器指令,除非这条指令用来返回一个函数调用、整个函数将会被反向执行。
reverse-finish
反向运行程序回到调用当前函数的地方。
set exec-direction [forward | reverse]
设置程序运行方向,可以用平常的命令step
和continue
等来执行反向的调试命令。
上面的反向运行也可以理解为撤销后面运行的语句所产生的效果,回到以前的状态。
好的,接下来我们来试试看如何反向调试。
首先确认自己的平台支持进程记录回放(Process Record and Replay),当在调试器启用进程记录回放功能时,调试器会记录下子进程,也就是被调试进程的每一步的运行状态与上一步运行状态的差异,需要撤销的时候就可以很方便回到上一步。
假设我们有以下C程序:
int main(int argc, const char *argv[])
{
int a = 0;
a = 1;
a = 2;
return 0;
}
将它编译并加上调试符号:
gcc -Wall -g a.c
开始调试
gdb a.out
接下来设置一个断点在第三行:
(gdb) b 3
Breakpoint 1 at 0x804839a: file a.c, line 3.
运行,程序会在第三行的地方停下来:
(gdb) r
Starting program: /home/cheryl/a.out
Breakpoint 1, main (argc=1, argv=0xbffff3e4) at a.c:3
3 int a = 0;
给变量a设置监视点方便我们观察:
(gdb) watch a
Hardware watchpoint 2: a
启动进程记录回放:
(gdb) record
现在每运行一步调试器都会记录下变化,以便回溯。我们连续执行3条语句。
(gdb) n
4 a = 1;
(gdb)
Hardware watchpoint 2: a
Old value = 0
New value = 1
main (argc=1, argv=0xbffff3e4) at a.c:5
5 a = 2;
(gdb)
Hardware watchpoint 2: a
Old value = 1
New value = 2
main (argc=1, argv=0xbffff3e4) at a.c:6
6 return 0;
可以看到,a的值先是从0变为了1,然后变为2,如果想让程序倒退回到以前的状态怎么办?可以用reverse-next命令:
(gdb) reverse-next
Hardware watchpoint 2: a
Old value = 2
New value = 1
main (argc=1, argv=0xbffff3e4) at a.c:5
5 a = 2;
(gdb)
Hardware watchpoint 2: a
Old value = 1
New value = 0
main (argc=1, argv=0xbffff3e4) at a.c:4
4 a = 1;
(gdb)
No more reverse-execution history.
main (argc=1, argv=0xbffff3e4) at a.c:3
3 int a = 0;
(gdb)
这样程序就倒退到了我们启动进程记录回放的地方,a的值经过两步回到了最初的状态。
若需要关闭进程记录回放,可以使用record stop:
(gdb) record stop
Process record is stoped and all execution log is deleted.
set print address on
打开地址输出,当程序显示函数信息时,GDB会显出函数的参数地址。系统默认为打开的,
show print address
查看当前地址显示选项是否打开。
set print array on
打开数组显示,打开后当数组显示时,每个元素占一行,如果不打开的话,每个元素则以逗号分隔。这个选项默认是关闭的。与之相关的两个命令如下,我就不再多说了。
set print array off
show print array
set print elements
这个选项主要是设置数组的,如果你的数组太大了,那么就可以指定一个来指定数据显示的最大长度,当到达这个长度时,GDB就不再往下显示了。如果设置为0,则表示不限制。
show print elements
查看print elements的选项信息。
set print null-stop
如果打开了这个选项,那么当显示字符串时,遇到结束符则停止显示。这个选项默认为off。
set print pretty on
如果打开printf pretty这个选项,那么当GDB显示结构体时会比较漂亮。
set print pretty off
show print pretty
set print union on
set print union off
show print union
打印 C 中的联合体。默认是 on 。
可以用下面的方法来显示数组
p *array@len
其中p
相当于print
,array
就是数组首地址,也可以是数组名,len
是想要显示的数组的长度。
比如我有一个数组的定义
int a[] = {1, 2, 3, 4, 5};
那么想要显示的时候就可以写:
p *a@5
这样就会显示数组a中的所有元素。
也可以使用display
在每一步调试的时候都显示:
display *a@5
取消显示就用undisplay
,不过这时候要写显示的号码。
darknet
源代码是makefile管理的,之前不会在Linux调试大型项目,今天探索了一下,这里介绍一下。
从这里下载源代码
修改makefile文件中DEBUG=0
改为DEBUG=1
进行调试。其中编译选项-O0
,意思是不进行编译优化,gdb在默认情况下会使用-O2
,会出现print变量中出现
。
接着编译源代码:
make clean
make
根目录会出现darknet
可执行文件。
在工程根目录运行如下命令下载权重:
wget https://pjreddie.com/media/files/yolov3-tiny.weights
终端输入如下语句,开始调试
gdb ./darknet
在gdb
命令中输入运行程序需要的参数类型
set args detect cfg/yolov3-tiny.cfg yolov3-tiny.weights data/dog.jpg
为了对整个工程进行调试,这里需要将src
目录添加进来,在gdb
命令中输入如下指令:
DIR ./src
在gdb
命令中为main
函数设置断点
b main
开始调试,在gdb
命令中输入r
,回车,发现程序停留在第一行。
接着可以在第435行,即char *outfile = find_char_arg(argc, argv, "-out", 0);
,打上断点b 435
;
在gdb
命令中输入b parser.c:761
在子函数parser.c
的761行打上断点;
输入c
,回车,程序跳到下一个断点,即停留下一个断点所在行;
输入n
单步执行,不跳入子函数。
输入s
命令单步执行并跳入此处调用的子函数;
输入print 变量名
或者p 变量名
即可查看该变量值;输入finish
跳出子函数;
输入q
结束调试。
到这里实战中用的机会少了, 也就老鸟会用上些. 这部分可以调试,不好调试. 一般一调估计小半天就走了. 好,那我们处理最后10min.
首先看上面命令
如果你需要用到上面gdb功能, 查看和导出宏的话.还需要gcc 支持,生成的时候加上 -ggdb3如下
gcc -Wall -ggdb3 -o houge.out houge.c
就可以使用了. 扩展一下 对于 gcc 编译的有个过程叫做 预编译gcc -E -o *.i *.c.
这时候处理多数宏,直接展开, 也可以查看最后结果. 也算也是一个黑科技.
首先看测试用例dasheng.c
#include
#include
#include
// 声明一个都用的量
static int _old;
// 线程跑的函数
static void* _run(void* arg) {
int piyo = 10;
int n = *(int*)arg;
int i;
//设置线程分离
pthread_detach(pthread_self());
for(i=0; i
编译命令
gcc -Wall -g -o dasheng.out dasheng.c -lpthread
那先看下面测试图
上面info threads
查看所有运行的线程信息. *
表示当前调试的线程.
后面l _run
表示查看 _run
附近代码. 当然还有l 16
查看16行附近文件内容.
gdb多线程切换 测试如下
thread 3表示切换到第三个线程, info threads 第一列id 就是 thread 切换的id.
上面测试线程 就算你切换到 thread 3. 其它线程还是在跑的. 我们用下面命令 只让待调试的线程跑. 其它线程阻塞.
set scheduler-locking on
开始多线程单独调试. 不用了 设置set scheduler-locking off
关闭. 又会回到你调试这个, 其它线程不阻塞.
总结 多线程调试常用就这三个实用命令
分别是查看,切换,设置同步调试.到这里多线程调试基本完毕了.
首先看liaobude.c
测试代码
#include
#include
#include
#include
#include
#include
// 声明一个都用的量
static int _old;
// 线程跑的函数
static void _run(int n) {
int piyo = 10;
int i;
++n;
for(i=0; i=0 || errno==EINTR)
continue;
break;
}
puts("end");
// 这里继续等待
for(i=0; i<190; ++i){
printf("等待 有缘人[%d]!\n", i);
sleep(1);
}
return 0;
}
编译命令
gcc -Wall -g -o liaobude.out liaobude.c
其实对多进程调试, 先介绍一个 常用的, 调试正在运行的程序. 首先让./liaobude.out
跑起来.
再通过ps -ef
找到需要调试的进程. 复制进程文件描述符pid.
这时候启动gdb.
attach pid
gdb就把pid那个进程加载进来了. 加载的进程会阻塞到当前正在运行的地方. 直到使用命令控制. 这个功能还是非常猛的.
最后介绍 进程调试的有关命令(需要最新的gdb才会支持). 多进程的调试思路和多线程调试流程很相似.
GDB可以同时调试多个程序。
只需要设置follow-fork-mode(默认值:parent)和detach-on-fork(默认值:on)即可。
设置方法:set follow-fork-mode [parent|child] set detach-on-fork [on|off]
查询正在调试的进程:info inferiors
切换调试的进程: inferior
具体的意思有
set follow-fork-mode [parent|child] set detach-on-fork [on|off]
parent on 只调试主进程(gdb默认)
child on 只调试子进程
parent off 同时调试两个进程,gdb跟主进程,子进程block在fork位置
child off 同时调试两个进程,gdb跟子进程,主进程block在fork位置
更加详细的 gdb 多进程调试demo 可以参照 http://blog.csdn.net/pbymw8iwm/article/details/7876797
使用方式和线程调试思路是一样的. 就是gdb 的命令换了字符. 工作中多进程调试遇到少.
遇到了很少用gdb调试. 会用下面2种调试好办法
写单元测试
打日志检测日志,分析
到这里 gdb30分钟内容讲解完毕. 多试试写写练一练, gdb基本突破没有问题.
Linux基础 30分钟GDB调试快速突破
gdb调试4–回退
更多关于人工智能、Python、C++、计算机等知识,欢迎访问我的个人博客进行交流, 点这里~~