gdb 调试程序
linux环境的调试,可以使用printf语句跟踪程序的运行,也可以使用调试工具。
用gcc编译源程序的时候,编译后的可执行文件不会包含源程序代码,如果您打算编译后的程序可以被调试,编译的时候要加-g的参数:
gcc -g -o test test.c
然后就能使用gdb指令调试了
gdb test
命令 | 命令缩写 | 命令说明 |
---|---|---|
set args | 设置主程序的参数。 | |
break | b | 设置断点,b 20 表示在第20行设置断点,可以设置多个断点。 |
run | r | 开始运行程序, 程序运行到断点的位置会停下来,如果没有遇到断点,程序一直运行下去。 |
next | n | 执行当前行语句,如果该语句为函数调用,不会进入函数内部执行。 |
step | s | 执行当前行语句,如果该语句为函数调用,则进入函数执行其中的第一条语句。 |
p | 显示变量值,例如:p name表示显示变量name的值。 | |
continue | c | 继续程序的运行,直到遇到下一个断点。 |
set var name=value | 设置变量的值,假设程序有两个变量:int ii; char name[21];set var ii=10 把ii的值设置为10;set var name=“西施” 把name的值设置为"西施",注意,不是strcpy。 | |
quit | q | 退出gdb环境。 |
set args
的例子
程序正常执行:
./book119 /oracle/c/book1.c /tmp/book1.c
gdb调试:
gdb book119
(gdb) set args /oracle/c/book1.c /tmp/book1.c
!实践记录
在gdb中使用list
指令可以在gdb内查看源代码,但还是开另一个窗口更方便。
通常可以打开2个窗口,一个窗口显示代码、:set nu
;另一个窗口进行gdb
调试。
set var 变量名=值
可以将当前的作用域中名为i
的变量的值设为i
,可以通过这个改变程序的运行状态。
p 变量名
中,变量名可以是函数
这个函数必须在当前停止的位置前执行过一次。
(gdb) p strlen("123qe")
$5 = 5
当然这个函数必须在当前程序中有声明、实现。
关于set args
在设置参数时,可以不加双引号。但如果参数中有如空格之类的特殊字符,则必须得有双引号。
(gdb) set args "1" "2" "3" // 无任何输出反馈
// 运行程序,在断点停止后:
(gdb) r
Starting program: /root/coding/test "1" "2" "3"
...
(gdb) p argc
$1 = 4
(gdb) p argv[0]
$2 = 0x7fffffffe778 "/root/coding/test"
断点
info b
可以查看设置的断点
输入delete n(断点序号)
可删除相应断点
输出数组
p *arr@len
arr:数组名
len:数组长度
去掉星号(*)输出的就是地址
多文件调试中在特定文件设置断点
例如http.cpp
是生成项目的一个源文件。
b http.cpp:45
跳出当前循环
until
似乎至少要运行循环体一次。
core文件调试
core文件的简单介绍
在一个程序崩溃时,它一般会在指定目录下生成一个core文件。core文件仅仅是一个内存映象(同时加上调试信息),主要是用来调试的。
启用core文件生成
// core文件默认禁止生成
[root@localhost coding]# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
...
// 修改core文件的限制
[root@localhost coding]# ulimit -c unlimited
[root@localhost coding]# ulimit -a
core file size (blocks, -c) unlimited
...
使用
生成core文件
编写一个会导致运行错误的代码:
void foo()
{
int* i = 0;
*i = 1;
}
int main(int argc, char* argv[])
{
foo();
}
[root@localhost coding]# ./test
Segmentation fault (core dumped)
[root@localhost coding]# ls
... core.3301 ...
使用core文件
1、gdb -c core
[root@localhost coding]# gdb -c core
(gdb) file test // 先载入可执行文件test
Reading symbols from /root/coding/test...done.
(gdb) r // 运行
Starting program: /root/coding/test
Program received signal SIGSEGV, Segmentation fault.
0x000000000040065d in foo () at test.cpp:11
11 *i = 1;
Missing separate debuginfos, use: debuginfo-install glibc-2.17-324.el7_9.x86_64 libgcc-4.8.5-44.el7.x86_64 libstdc++-4.8.5-44.el7.x86_64
输出的信息指出源文件第11行的*i = 1;
导致了错误。
2、gdb 可执行文件名 输出的相应core文件
直接得到错误信息。
[root@localhost coding]# gdb test core.3301
...
[New LWP 3301]
Core was generated by `./test'.
Program terminated with signal 11, Segmentation fault.
#0 0x000000000040065d in foo () at test.cpp:11
11 *i = 1;
Missing separate debuginfos, use: debuginfo-install glibc-2.17-324.el7_9.x86_64 libgcc-4.8.5-44.el7.x86_64 libstdc++-4.8.5-44.el7.x86_64
bt
通过bt指令查看函数的调用栈。
在每个函数中都指出了错误位置。
(gdb) bt
#0 0x000000000040065d in foo () at test.cpp:11
#1 0x0000000000400679 in main (argc=1, argv=0x7ffc5ccd84c8) at test.cpp:18
调试运行中的文件
gdb 程序文件名 -p 进程编号
源文件
void foo()
{
for(int i = 0; i < 1000; ++i)
{
sleep(1);
printf("i=%d\n", i);
}
}
int main(int argc, char* argv[])
{
foo();
}
在另一个窗口运行./test
程序。
[root@localhost coding]# ps -ef|grep test
root 3675 2447 0 21:57 pts/1 00:00:00 ./test
root 3677 2106 0 21:57 pts/0 00:00:00 grep --color=auto test
[root@localhost coding]# gdb test -p 3675
程序停止。
之后就能向调试普通程序一样调试运行的程序了。
例如
// 修改循环条件i
(gdb) set var i=1
...
i=9
i=10
i=11
i=2
使用bt看一下程序的调用栈。
(gdb) bt
#0 0x00007fc44b58f8d0 in __nanosleep_nocancel () from /lib64/libc.so.6
#1 0x00007fc44b58f784 in sleep () from /lib64/libc.so.6
#2 0x00000000004006f8 in foo () at test.cpp:12
#3 0x000000000040072f in main (argc=1, argv=0x7ffe5ff59858) at test.cpp:22
多进程调试
设置要调试那个进程
(gdb) set follow-fork-mode parent(默认)
(gdb) set follow-fork-mode child
设置调试模式
设置调试一个进程的时候,其它进程时继续执行还是挂起。(默认执行)
(gdb) set detach-on-fork on/off
查看当前调试的进程
(gdb) info inferiors
(gdb) info inferiors
Num Description Executable
2 process 4443 /root/coding/test
* 1 process 4439 /root/coding/test
切换调试的进程
通常要配合detach-on-fork off
使用,否则未被调试的进程会一直在运行,可能会影响使用。(例如不断地向屏幕输出信息干扰使用)
inferior 进程序号
(gdb) inferior 2
[Switching to inferior 2 [process 4443] (/root/coding/test)]
[Switching to thread 2 (process 4443)]
#0 0x00007ffff72b2a02 in fork () from /lib64/libc.so.6
(gdb) info inferiors
Num Description Executable
* 2 process 4443 /root/coding/test
1 process 4439 /root/coding/test
例子
int main(int argc, char* argv[])
{
if(fork() != 0)
{
printf("父进程\n");
for(int i = 0; i < 10; ++i)
{
sleep(1);
printf("i = %d \n", i);
}
}
else
{
printf("子进程\n");
for(int i = 0; i < 10; ++i)
{
sleep(1);
printf("j = %d \n", i);
}
}
}
默认会调试父进程,在else处设置断点没有效果。
多线程调试
查看当前调试线程
(gdb) info threads
切换调试线程
(gdb) thread 线程编号
线程运行设置
只运行当前线程
set scheduler-locking on
运行全部线程
set scheduler-locking off
指定线程执行命令
指定某线程执行某gdb命令
thread apply 线程编号 gdb命令
全部线程执行某gdb命令
thread apply all gdb命令
例子
void* foo(void* thd)
{
int tid = (long)thd;
for(int i = 0; i < 10; ++i)
{
sleep(1);
printf("thread %d = %d\n", tid, i);
}
}
int main(int argc, char* argv[])
{
pthread_t tid[2];
pthread_create(&tid[0], NULL, foo, (void*)0);
pthread_create(&tid[1], NULL, foo, (void*)1);
for(int i = 0; i < 10; ++i)
{
sleep(1);
printf("main thread = %d\n", i);
}
pthread_join(tid[0], NULL);
pthread_join(tid[1], NULL);
}
线程的停止比较奇怪,一个线程触发断点时,其它线程不会像多进程那样继续运行,不断地向屏幕输出信息。而是会一起被中断。
但是在执行n
指令,让当前被调试的线程前进一步后,其余的线程也都会随之执行,且执行的速度快于当前被调试的线程:
(gdb) n
---------- thread 0 = 4 ----------
---------- thread 1 = 1 ----------
---------- main thread = 5 ----------
12 for(int i = 0; i < 10; ++i)
(gdb) n
14 sleep(1);
(gdb) n
---------- main thread = 6 ----------
---------- main thread = 7 ----------
15 printf("---------- thread %d = %d ----------\n", tid, i);
(gdb) n
---------- main thread = 8 ----------
---------- thread 1 = 2 ----------
当前被调试的是“thread 1”
,能看到“thread 1”
的进度比另外2个线程慢得多。
设置多个断点后,可能会在多个线程间切换:
(gdb) info threads
[New Thread 0x7ffff67cf700 (LWP 5039)]
Id Target Id Frame
3 Thread 0x7ffff67cf700 (LWP 5039) "test" 0x00007ffff70cf9c1 in clone () from /lib64/libc.so.6
* 2 Thread 0x7ffff6fd0700 (LWP 5038) "test" foo (thd=0x0) at test.cpp:12
1 Thread 0x7ffff7fe0740 (LWP 5037) "test" 0x00007ffff70cf9c1 in clone () from /lib64/libc.so.6
(gdb) c
Continuing.
[Switching to Thread 0x7ffff7fe0740 (LWP 5037)]
Breakpoint 2, main (argc=1, argv=0x7fffffffe518) at test.cpp:28
28 for(int i = 0; i < 10; ++i)
(gdb) info threads
Id Target Id Frame
3 Thread 0x7ffff67cf700 (LWP 5039) "test" foo (thd=0x1) at test.cpp:12
2 Thread 0x7ffff6fd0700 (LWP 5038) "test" 0x00007ffff70968ed in nanosleep () from /lib64/libc.so.6
* 1 Thread 0x7ffff7fe0740 (LWP 5037) "test" main (argc=1, argv=0x7fffffffe518) at test.cpp:28
通过日志进行调试
使用gdb
设置断点可能会破坏多线程的并发状态,通过调试器只能看到一个和谐的假象。
解决的方法就是输出log日志。
作者设计了一个框架,将头文件包含后,可以通过一个类对象,将程序的输出保存到一个文本文件中,之后通过查看文本文件分析多线程的运行状态。