gdb调试
什么是gbd
gdb是GUN debugger的缩写,是编程调试工具
gdb功能
启动程序,可以按照用户自定义的要求随心所欲的运行程序。
可让被调试的程序在用户所指定的调试的断点出停住(断电可以是条件表达式)。
当程序停住时,可以检查此时程序中所发生的事。
动态改变程序的执行环境。
运行程序
run(r)
run arg1 arg2 ...
产看源码
list(l) - 查看最近10行源码
list fun - 产看fun函数源码
list file:fun - 查看file文件中的fun函数源码
设置断点
break 行号
break fun
break file:行号
break file:fun
break if <condition> - 条件成立时程序停住
info break(i b) - 产看断点
watch expr - 一旦expr值发生改变,程序停住
delete n - 删除断点
单步调试
continue(c) - 运行至下一个断点
step(s) - 单步跟踪,进入函数,类似于VC中sep in
next(n) - 单步跟踪,不进入函数,类似于VC中sep out
finish - 运行程序,直到当前函数完成返回。并打印函数返回时的堆栈地址和返回值及参数等信息
until - 当厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体
gdb常用命令总结
命令 | 功能 |
---|---|
回车键 | 重复上一命令 |
start | 单步执行,运行程序,停在第一执行语句 |
run(r) | 运行 |
list(l) | 列出源代码 |
break(b) | 设置断点 |
info break(i b) | 查看断点信息 |
continue(c) | 继续程序运行,直到下一个断点 |
watch | 设置观察点 |
step(s) | 单步跟踪,step in |
next(n) | 单步跟踪,step out |
finish | 运行程序,直到当前函数返回 |
until(u) | 退出循环 |
print(p) | 产看运行的变量以及表达式 |
backtrace(bt) | 查看函数的调用的栈帧和层级关系 |
display | 追踪查看具体变量值 |
undisplay | 取消追踪观察变量 |
enable breakpoints | 启用断点 |
disable breakpoints | 禁用断点 |
查看运行时数据
print - 查看变量值
ptype - 产看类型
print array - 查看数组
print *array@len - 查看动态内存
print x=5 - 改变运行时数据
程序错误
- 编译错误
编写程序的时候没有符合语言规范导致编译错误 - 运行时错误
编译器检查不出这种错误,但在运行的时候可能会导致程序崩溃 - 逻辑错误
编译和运行都很顺利,但是程序没有干它该干的事情
gdb调试逻辑错误
#include <stdio.h>
int main(void)
{
int i;
char str[6] = "hello";
char reverse_str[6] = "";
printf("str : %s\n",str);
for(i=0; i<5; ++i)
reverse_str[5-i] = str[i];
printf("reverse : %s\n",reverse_str);
return 0;
}
gdb调试段错误
段错误是由于访问非法地址而产生的错误
访问系统数据区,尤其是往系统保护的内存地址写数据。最常见就是给一个指针以0地址
内存越界(数组越界,变量类型不一致等)访问到不属于你的内存区域
#include <stdio.h>
#include <stdlib.h>
void segfault()
{
int *p = NULL;
*p = 100;
}
int main(void)
{
// segfault();
char buf[1] = "a";
//越界可能会报错,也可能不会报错
buf[10] = 'A';
printf("%c\n",buf[10]);
buf[100003] = 'A';
printf("%c\n",buf[100003]);
return 0;
}
结果
Reading symbols from segfault...done.
(gdb) r
Starting program: /home/s/桌面/segfault
Program received signal SIGSEGV, Segmentation fault.
0x00000000004004c2 in segfault () at segfault.c:7
7 *p = 100;
(gdb) bt
#0 0x00000000004004c2 in segfault () at segfault.c:7
#1 0x00000000004004d9 in main () at segfault.c:12
(gdb)
core文件调试
core文件
在程序崩溃时,一般会生成一个文件叫core文件。core文件记录的是程序崩溃时的内存映像,并加入调试信息。core文件生成的过程叫做core dump。
设置core文件
ulimit -c 查看core-dump状态
ulimit -c 数字(如:ulimit -c 1024)
ulimit -c unlimited
sysctl -w kernel.core_pattern=/tmp/core-%e.%p.%h.%t 设置core文件路径
//设置core文件用pid命名
sudo sh -c 'echo "1" > /proc/sys/kernel/core_uses_pid'
gdb利用core文件调试
gdb 可执行文件 core文件
gdb pipe core.16780
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x00000000004004ed in main (argc=1, argv=0x7fffc79ac0b8) at pipe.c:21
21 *p =100;
(gdb) bt
#0 0x00000000004004ed in main (argc=1, argv=0x7fffc79ac0b8) at pipe.c:21
(gdb)
多线程
thread thread_id - 用于在线程之间切换的命令
info threads - 用于查询当前存在的线程信息
thread apply [thread-id-list] [all] args - 对一系列的线程应用某一个命令
set print thread-events - 控制是否打印线程启动、退出消息
set scheduler-locking mode: 用于设置锁定线程的模式(scheduler locking mode)。其适用于程序正常执行、record mode以及重放模式。
1) mode为off时,则不锁定任何线程,即所有线程在任何时间都可以被执行;
2) mode为on时,则锁定其他线程,只有当前线程执行;
3) mode为step时,则当在进行单步调试(single-stepping)时只有当前线程会运行,其他的线程将不会获得运行的机会,这样就可以使得调试的焦点
只集中于当前线程。但是假如执行的时'continue'、'until'、'finish'这样的非单步调试命令的话,则其他的线程也会运行。
一般来说,除非一个线程在其运行时间片内遇到断点(breakpoint),否则GDB一般并不会从当前调试线程切换到该线程。
show scheduler-locking: 用于显示当前的锁定模式
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <ctype.h>
#include <pthread.h>
#define MAX_THREAD 3
unsigned long long main_counter,counter[MAX_THREAD];
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* thread_worker(void *p)
{
int thread_num;
thread_num = (int)p;
for(;;)
{
counter[thread_num]++;
main_counter++;
}
}
int main(int argc, char* argv[])
{
int i,rtn,ch;
pthread_t pthread_id[MAX_THREAD] = {0};
for(i=0; i<MAX_THREAD; ++i)
{
pthread_create(&pthread_id[i], NULL, thread_worker, (void*)i);
}
do
{
unsigned long long sum = 0;
for(i=0; i<MAX_THREAD; ++i)
{
sum += counter[i];
printf("%llu",counter[i]);
}
} while ((ch = getchar()) != 'q');
return 0;
}
获取进程pid
ps -aL | grep thread
18539 18539 pts/1 00:00:00 thread
18539 18540 pts/1 00:06:12 thread
18539 18541 pts/1 00:06:12 thread
18539 18542 pts/1 00:06:11 thread
获取主线程和子线程的关系
pstree -p 18539
thread(18539)─┬─{thread}(18540)
├─{thread}(18541)
└─{thread}(18542)
pstack来查看线程栈结构
pstack pid
gdb thread
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from thread...done.
(gdb) l
11 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
12
13 void* thread_worker(void *p)
14 {
15 int thread_num;
16 thread_num = (int)p;
17 for(;;)
18 {
19 counter[thread_num]++;
20 main_counter++;
(gdb) l
21 }
22 }
23
24 int main(int argc, char* argv[])
25 {
26 int i,rtn,ch;
27 pthread_t pthread_id[MAX_THREAD] = {0};
28 for(i=0; i<MAX_THREAD; ++i)
29 {
30 pthread_create(&pthread_id[i], NULL, thread_worker, (void*)i);
(gdb) b 30
Breakpoint 1 at 0x400776: file thread.c, line 30.
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000400776 in main at thread.c:30
(gdb) r
Starting program: /home/dw/sf/test/thread
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, main (argc=1, argv=0x7fffffffe3b8) at thread.c:30
30 pthread_create(&pthread_id[i], NULL, thread_worker, (void*)i);
(gdb) n
[New Thread 0x7ffff77ef700 (LWP 19213)]
28 for(i=0; i<MAX_THREAD; ++i)
(gdb) info threads
Id Target Id Frame
* 1 Thread 0x7ffff7fcf700 (LWP 19209) "thread" main (argc=1, argv=0x7fffffffe3b8) at thread.c:28
2 Thread 0x7ffff77ef700 (LWP 19213) "thread" 0x0000000000400716 in thread_worker (p=0x0) at thread.c:19
(gdb) n
Thread 1 "thread" hit Breakpoint 1, main (argc=1, argv=0x7fffffffe3b8) at thread.c:30
30 pthread_create(&pthread_id[i], NULL, thread_worker, (void*)i);
(gdb) n
[New Thread 0x7ffff6fee700 (LWP 19215)]
28 for(i=0; i<MAX_THREAD; ++i)
(gdb) info threads
Id Target Id Frame
* 1 Thread 0x7ffff7fcf700 (LWP 19209) "thread" main (argc=1, argv=0x7fffffffe3b8) at thread.c:28
2 Thread 0x7ffff77ef700 (LWP 19213) "thread" 0x000000000040072e in thread_worker (p=0x0) at thread.c:20
3 Thread 0x7ffff6fee700 (LWP 19215) "thread" thread_worker (p=0x1) at thread.c:19
(gdb) n
Thread 1 "thread" hit Breakpoint 1, main (argc=1, argv=0x7fffffffe3b8) at thread.c:30
30 pthread_create(&pthread_id[i], NULL, thread_worker, (void*)i);
(gdb) n
[New Thread 0x7ffff67ed700 (LWP 19218)]
28 for(i=0; i<MAX_THREAD; ++i)
(gdb) info threads
Id Target Id Frame
* 1 Thread 0x7ffff7fcf700 (LWP 19209) "thread" main (argc=1, argv=0x7fffffffe3b8) at thread.c:28
2 Thread 0x7ffff77ef700 (LWP 19213) "thread" thread_worker (p=0x0) at thread.c:21
3 Thread 0x7ffff6fee700 (LWP 19215) "thread" 0x000000000040072a in thread_worker (p=0x1) at thread.c:20
4 Thread 0x7ffff67ed700 (LWP 19218) "thread" 0x0000000000400716 in thread_worker (p=0x2) at thread.c:19
(gdb) n
35 unsigned long long sum = 0;
(gdb) print counter
$1 = {1436041, 174348, 63267}
(gdb) set scheduler-locking on
(gdb) info threads
Id Target Id Frame
* 1 Thread 0x7ffff7fcf700 (LWP 19209) "thread" main (argc=1, argv=0x7fffffffe3b8) at thread.c:35
2 Thread 0x7ffff77ef700 (LWP 19213) "thread" 0x000000000040072e in thread_worker (p=0x0) at thread.c:20
3 Thread 0x7ffff6fee700 (LWP 19215) "thread" 0x0000000000400712 in thread_worker (p=0x1) at thread.c:19
4 Thread 0x7ffff67ed700 (LWP 19218) "thread" 0x0000000000400712 in thread_worker (p=0x2) at thread.c:19
(gdb) thread 2
[Switching to thread 2 (Thread 0x7ffff77ef700 (LWP 19213))]
#0 0x000000000040072e in thread_worker (p=0x0) at thread.c:20
20 main_counter++;
(gdb) n
21 }
(gdb) n
19 counter[thread_num]++;
(gdb) n
20 main_counter++;
(gdb) n
21 }
(gdb) printf counter
Bad format string, missing '"'.
(gdb) print counter
$2 = {1436042, 174348, 63267}
(gdb) n
19 counter[thread_num]++;
(gdb) n
20 main_counter++;
(gdb) n
21 }
(gdb) print counter
$3 = {1436043, 174348, 63267}
(gdb)
多进程
set follow-fork-mode [parent|child] - 设置GDB调试器如何对fork或者vfork进行响应(默认mode是parent)
show follow-fork-mode - 显示当前的跟随模式
set detach-on-fork mode - 用于告诉GDB在fork()之后是否分离其中的一个进程,或者同时保持对他们的控制。mode可取值为
on: 子进程或者父进程将会被分离(取决于follow-fork-mode),使得该进程可以独立的运行。这是默认值
off: 子进程和父进程都会在GDB的控制之下。其中一个进程(取决于follow-fork-mode)可以像平常那样进行调试,而另一个进程处于挂起状态
show detach-on-fork: 用于显示detach-on-fork模式的值
info inferiors - 命令来查看当前处于GDB控制之下的进程,并使用inferior命令来进行切换。
detach inferiors - 命令来使得该进程独立的运行
kill inferiors - 命令来将该进程杀死
set follow-exec-mode mode - 当程序调用exec之后,GDB相应的行为。exec调用会替换一个进程的镜像。mode取值可以为:
new: GDB会创建一个新的inferior,并将该进程重新绑定到新的inferior。在执行exec之前的所运行的程序可以通过重启原先的inferior(original inferior)来进行 重启
same: GDB会将exec之后的新镜像加载到同一个inferior中,以替换原来的镜像。在执行exec之后如果要重启该inferior,那么可以通过运行run命令。这是默认模式
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
pid_t pid;
pid = fork();
if(pid == -1)
{
perror("fork");
exit(EXIT_FAILURE);
}
if(pid == 0)
{
printf("child pid : %d\n",getpid());
}
printf("parent : %d\n",getpid());
return 0;
}
默认调试父进程
Breakpoint 1, main (argc=1, argv=0x7fffffffe3b8) at process.c:9
9 pid = fork();
(gdb) n
child pid : 19468
parent : 19468
10 if(pid == -1)
(gdb)
16 if(pid == 0)
(gdb) n
21 printf("parent : %d\n",getpid());
(gdb) n
parent : 19464
23 return 0;
(gdb) n
24 }
(gdb) n
调试子进程
(gdb) b 10
Breakpoint 1 at 0x40065d: file process.c, line 10.
(gdb) set follow-fork-mode child
(gdb) n
The program is not being run.
(gdb) r
Starting program: /home/dw/sf/test/process
[New process 19484]
parent : 19480
[Switching to process 19484]
Thread 2.1 "process" hit Breakpoint 1, main (argc=1, argv=0x7fffffffe3b8) at process.c:10
10 if(pid == -1)
(gdb) n
16 if(pid == 0)
(gdb) n
18 printf("child pid : %d\n",getpid());
(gdb) n
child pid : 19484
21 printf("parent : %d\n",getpid());
(gdb) n
parent : 19484
23 return 0;
同时调试父进程和子进程
(gdb) set detach-on-fork off
(gdb) start
Temporary breakpoint 1 at 0x400655: file process.c, line 9.
Starting program: /home/dw/sf/test/process
Temporary breakpoint 1, main (argc=1, argv=0x7fffffffe3b8) at process.c:9
9 pid = fork();
(gdb) n
[New process 19823]
10 if(pid == -1)
(gdb) info inferiors
Num Description Executable
* 1 process 19819 /home/dw/sf/test/process
2 process 19823 /home/dw/sf/test/process
(gdb) n
16 if(pid > 0)
(gdb) n
18 printf("parent : %d\n",getpid());
(gdb) n
parent : 19819
19 exit(EXIT_SUCCESS);
(gdb) n
[Inferior 1 (process 19819) exited normally]
(gdb) info inferiors
Num Description Executable
* 1 <null> /home/dw/sf/test/process
2 process 19823 /home/dw/sf/test/process
(gdb) inferior 2
[Switching to inferior 2 [process 19823] (/home/dw/sf/test/process)]
[Switching to thread 2.1 (process 19823)]
Reading symbols from /usr/lib/debug/lib/x86_64-linux-gnu/libc-2.23.so...done.
Reading symbols from /usr/lib/debug/lib/x86_64-linux-gnu/ld-2.23.so...done.
#0 0x00007ffff7ad941a in __libc_fork () at ../sysdeps/nptl/fork.c:145
145 ../sysdeps/nptl/fork.c: 没有那个文件或目录.
(gdb) n
152 in ../sysdeps/nptl/fork.c
(gdb)
145 in ../sysdeps/nptl/fork.c
(gdb)
152 in ../sysdeps/nptl/fork.c
(gdb)
156 in ../sysdeps/nptl/fork.c
(gdb)
159 in ../sysdeps/nptl/fork.c
(gdb)
163 in ../sysdeps/nptl/fork.c
(gdb)
168 in ../sysdeps/nptl/fork.c
(gdb)
169 in ../sysdeps/nptl/fork.c
(gdb)
170 in ../sysdeps/nptl/fork.c
(gdb)
181 in ../sysdeps/nptl/fork.c
(gdb)
192 in ../sysdeps/nptl/fork.c
(gdb)
208 in ../sysdeps/nptl/fork.c
(gdb)
205 in ../sysdeps/nptl/fork.c
(gdb)
208 in ../sysdeps/nptl/fork.c
(gdb)
230 in ../sysdeps/nptl/fork.c
(gdb)
264 in ../sysdeps/nptl/fork.c
(gdb)
main (argc=1, argv=0x7fffffffe3b8) at process.c:10
10 if(pid == -1)
(gdb)
16 if(pid > 0)
(gdb)
22 printf("child pid : %d\n",getpid());
(gdb)
child pid : 19823
25 return 0;
(gdb)
26 }
(gdb)
__libc_start_main (main=0x400646 <main>, argc=1, argv=0x7fffffffe3b8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe3a8) at ../csu/libc-start.c:325
325 ../csu/libc-start.c: 没有那个文件或目录.
(gdb) info inferiors
Num Description Executable
1 <null> /home/dw/sf/test/process
* 2 process 19823 /home/dw/sf/test/process
(gdb) n
[Inferior 2 (process 19823) exited normally]
(gdb) info inferiors
Num Description Executable
1 <null> /home/dw/sf/test/process
* 2 <null> /home/dw/sf/test/process
(gdb)
设置用于返回的书签
GDB能够将程序的运行状态保存为snapshot,这被称为checkpoint,后续我们就可以通过相应的命令返回到该checkpoint。回退到一个checkpoint,会使得所有发生在该checkpoint之后的操作都会被做undo。这包括内存的修改、寄存器的修改、甚至是系统的状态(有一些限制)。事实上,类似于回到保存checkpoint的时间点。因此,当你在单步调试程序,并且认为快接近有错误的代码点时,你就可以先保存一个checkpoint。然后,你继续进行调试,假如碰巧错过了该关键代码段,这时你就可以回退到该checkpoint并从该位置继续进行调试,而不用完全从头开始来调试整个程序。
checkpoint - 将调试程序的当前执行状态保存为一个snapshot。本命令不携带任何参数,但是其实GDB内部对于每一个checkpoint都会指定一个整数ID,这有些类似于breakpoint ID.
info checkpoints - 列出当前调试session所保存的checkpoints。
restart checkpoint-id - 重新装载checkpoint-id位置的程序状态。所有的程序变量、寄存器、栈帧等都会被恢复为在保存该checkpoint时的状态。实际上,GDB类似于将时间拨回到保存该checkpoint的时间点。
delete checkpoint checkpoint-id - 删除以前保存的某个checkpoint
返回到前一个保存的checkpoint时,将会恢复该调试程序的用户状态,也会恢复一部分的操作系统状态,包括文件指针。恢复时,并不会对一个文件中的数据执行un-write操作,但是会将文件指针恢复到原来的位置,因此之前所写的数据可以被overwritten。对于那些以读模式打开的文件,文件指针将会恢复到原来所读的位置。
checkpoint的潜在优势
在有一些系统上,比如GNU/Linux,通常情况下由于安全原因每一个新进程的地址空间都是随机的。这就使得几乎不太可能在一个绝对的地址上设置一个breakpoint或者watchpoint,因为在程序下一次重启时,程序中symbol的绝对路径可能发生改变。然而一个checkpoint,等价于一个进程拷贝。因此假如你在main的开始就创建一个checkpoint,后续返回到该checkpoint而不是重启程序,这就可以避免受到重启程序地址随机这一情况的影响。通过返回checkpoint,可以使得程序的symbols仍保持在原来的位置
#include <stdlib.h>
#include <stdio.h>
static int func()
{
static int i = 0;
++i;
if (i == 2) {
return 1;
}
return 0;
}
static int func3()
{
return func();
}
static int func2()
{
return func();
}
static int func1()
{
return func();
}
int main()
{
int ret = 0;
ret += func1();
ret += func2();
ret += func3();
return ret;
}
gdb checkpoint
(gdb) start
Temporary breakpoint 1 at 0x40053a: file checkpoint.c, line 32.
Starting program: /home/dw/sf/test/checkpoint
Temporary breakpoint 1, main () at checkpoint.c:32
32 int ret = 0;
(gdb) n
34 ret += func1();
(gdb) checkpoint
checkpoint 1: fork returned pid 19943.
(gdb) info checkpoints
* 0 process 19936 (main process) at 0x400541, file checkpoint.c, line 34
1 process 19943 at 0x400541, file checkpoint.c, line 34
(gdb) n
35 ret += func2();
(gdb) p ret
$1 = 0
(gdb) n
36 ret += func3();
(gdb) p ret
$2 = 1
(gdb) info checkpoints
* 0 process 19936 (main process) at 0x40055b, file checkpoint.c, line 36
1 process 19943 at 0x400541, file checkpoint.c, line 34
(gdb) restart 1
Switching to process 19943
#0 main () at checkpoint.c:34
34 ret += func1();
(gdb) p ret
$3 = 0
(gdb)
关于线程与进程详细参考
https://ivanzz1001.github.io/records/post/cplusplus/2018/08/19/cpluscplus-gdbusage_part2