GDB常用功能

一,GDB调试器简介

GDB是Linux下的常用调试器,主要用于调试源代码、调试运行中的进程和查看core dump文件。

Linux下的调试器主要有gdb、cgdb、ddd、eclipse。GDB调试器的运行速度快、能够进行源代码同步显示。

使用 -tui 选项开启gdb命令输入和源代码的分屏显示,tui即Terminal User Interface。

二,GDB常用调试命令

a) 调试可执行文件

以源代码为/home/zebra/目录下的test.c文件产生的可执行文件test为例(可执行文件使用gcc进行编译,并使用-g选项以产生调试信息),进行命令的说明(详细源代码参见第三部分:三,调试实例分析 )。

gdb调试源代码流程:

1,开启gdb程序,即运行如下命令: gdb -q (-q用以使得gdb不输出gdb程序的版本等信息)

2,指定调试的二进制文件:file test

3,list查看源代码

4,设定断点 breakpoint main

breakpoint sub

上述分别在main函数和sub函数处设定了断点。

断点可以设置在任意已知源代码文件的某一行,某一个函数,同时可以指定是设置在哪个/哪些线程上(见下边描述)。

5,运行可执行文件:

run

6,这样程序会运行到断点处停止下来,gdb会打印当前断点的信息。

7,使用s 或者 n进行单步调试。

s即step,n即next都是执行一条语句,然后停下来。

如果想执行一条汇编语句,则可以使用 si ,ni,即step instruction,next instruction。

8,bt命令查看当前的调用栈,bt即backtrace。

9,info frame查看函数帧信息。

10,frame n 进入某个函数帧(编号为n)

11,info 命令可以对当前的函数帧的寄存器、局部变量、函数的参数进行查看。

info register;info local;info args。

12,disassemble对当前函数对应的二进制进行反汇编。

13,x/nfu address 查看内存其中 address是内存开始的地址,从该地址向高地址增加,

x是examinate的缩写,n表示重复次数,f表示输出格式,u表示内存大小的单位(默认是字,即4个字节)。

一般我都用 x/nx address,即打印n个从address开始的内存,每个是4字节,以十六进制打印。

14,continue,执行至该函数退出

15,info threads,显示当前可调试的所有线程

16,thread <ID>,切换当前调试的线程为指定ID的线程break File:LineNumber thread <ID|ALL> if x==y。

17,thread apply <ID1 ID2...|ALL> command让一个/多个/所有线程执行GDB的命令command。

18,set scheduler-locking off|on|step,设定线程运行的锁定模式,

off表示各个线程同时进行,On表示仅当前线程可运行,step则是在执行step命令单步执行的时候,其他线程不运行。

19,q 退出调试。


20, rbreak . 在所有函数上加上断点。

rbreak REGEX
这个命令可以在所有满足正则表达式REGEX的函数上设置断点。

命令中使用的正则表达式和grep中使用的类似。

当调试C++程序的时候,rbreak用在给不属于任何class的重载函数设置断点很方便。

rbreak可以直接向一个程序的所有函数设置断点,比如:
(gdb) rbreak .

rbreak FILE:REGEX
设置断点到一个文件中的符合该正则表达式的函数上,例如:

(gdb) rbreak file.c:. 在file.c的所有函数上设置断点。

21, thread apply all backtrace 查看所有线程调用栈。


b) 调试正在运行的进程

gdb可用以调试正在运行的进程。只需要知道进程的进程号。

gdb调试进程流程:

1,gdb -p PID指定调试的进程ID号。

或者先进入gdb再指定: gdb

attach PID。

2,bt查看当前进程执行的调用栈

3,info threads查看当前可调试的线程。

其他和上述a)类似。

另:

Gdb package中包含 /usr/bin/gstack命令(依赖gdb)取得对应PID的栈信息。
Usage: gstack <process-id>

c)查看core dump文件

一些信号的默认行为是导致一个进程终止并且产生一个core dump文件。core dump文件包含该进程的终止时内存映像的信息。导致进程产生core dump的信号可以在signal(7)中查阅到。

Unix定义的产生core dump文件的信号列表如下:

名字说明缺省动作
SIGABRT 异常终止(abort)止w/core
SIGBUS 硬件故障 终止w/core
SIGEMT 硬件故障 终止w/core
SIGFPE 算术异常 终止w/core
SIGILL 非法硬件指令 终止w/core
SIGIOT 硬件故障 终止w/core
SIGQUIT 终端退出符 终止w/core
SIGSEGV 无效存储访问 终止w/core
SIGSYS 无效系统调用 终止w/core
SIGTRAP 硬件故障 终止w/core
SIGXCPU CPU限制 终止w/core
SIGXFSZ 文件 度限制 终止w/core

软件容易产生的引发core dump的信号有SIGABRT,SIGFPE,SIGSEGV。

产生core dump后,我们使用gdb进行分析。流程如下:

1,gdb -c core文件名称 /home/zebra/test (二进制文件名)

2,bt查看调用栈以便获取程序发生core dump时执行的函数

3,其他与 a)类似

三,调试实例分析

下边举个调试的例子,该例子是一个由于非法访问内存,将会产生core文件的简单程序。

程序使用如下命令编译: gcc -g -o test test.c,这样便产生了程序test。

其源代码test.c位于/root/zebra下,源代码为(在gdb里使用list命令进行查看):

(gdb) list
1 /* it will result in SIGSEGV */
2 int sub(int a,int b,int c){
3
4 *(int *)a=16;
5 return 0;
6 }
7
8 int main()
9 {
10 int a=0;
(gdb)
11 int b=1;
12 int c=2;
13 sub(a,b,c);
14 return 0;
15 }
16

a)调试源代码

指令: 输出和意义:

1,gdb -q 进入调试器,-q表示不打印gdb的版本等信息

2,file test 加载调试的程序

3,b main 在main函数处设置断点

4,b sub 在sub函数处设置断点

5,r Breakpoint 1, main () at test.c:10

10 int a=0;

运行至第一个断点,即main函数处,此时第10行的int a=0尚未执行

6,bt (gdb) bt
#0 main () at test.c:10

此时我们查看一下函数调用栈,发现调用栈中只有一个函数,即main。

7,info args (gdb) info args
No arguments.

发现没有参数(main函数没有参数)

8,info locals (gdb) info locals
a = 32767
b = 0
c = 0

查看当前的局部变量信息,发现a、b、c的值都是随机的,因为赋值语句尚未执行。

9,info register

rbp 0x7fffffffe350 0x7fffffffe350
rsp 0x7fffffffe340 0x7fffffffe340
rip 0x400513 0x400513 <main+8>

cs 0xe033 57395

上述是一部分当前的寄存器信息。

10,info f 0

(gdb) info f 0
Stack frame at 0x7fffffffe360:
rip = 0x400513 in main (test.c:10); saved rip 0x7ffff7a9ebc6
source language c.
Arglist at 0x7fffffffe350, args:
Locals at 0x7fffffffe350, Previous frame's sp is 0x7fffffffe360
Saved registers:
rbp at 0x7fffffffe350, rip at 0x7fffffffe358

打印此时的该函数帧的信息。

11,s 11 int b=1;

单步执行到int b=1(该句尚未执行)

12,s

12 int c=2;

单步执行到int c=2(该句尚未执行)

13,s

(gdb) s
13 sub(a,b,c);

单步执行到sub(a,b,c)(该句尚未执行)

14,info locals a = 0
b = 1
c = 2

查看发现,a=0,b=1,c=2

15,bt (gdb) bt
#0 main () at test.c:13

还是只有main,因为行数到了sub(a,b,c)那一行,但是尚未跳转进入执行

16,s

Breakpoint 2, sub (a=0, b=1, c=2) at test.c:4
4 *(int *)a=16;

到达第二个断点,即进入sub(int a,int b.int c)函数,但是*(int *)a=16尚未执行

17,bt #0 sub (a=0, b=1, c=2) at test.c:4
#1 0x0000000000400536 in main () at test.c:13

当前已经有两个函数被调用

18,info args (gdb) info args
a = 0
b = 1
c = 2

发现有a=0,b=1,c=2 ,即函数获得了正确的参数。

19,info locals (gdb) info locals
No locals.

发现没有局部变量

20,info register

rbp 0x7fffffffe330 0x7fffffffe330
rsp 0x7fffffffe330 0x7fffffffe330

rip 0x4004f9 0x4004f9 <sub+13>
cs 0xe033 57395

查看当前的寄存器信息

21,s

Program received signal SIGSEGV, Segmentation fault.
0x00000000004004fe in sub (a=0, b=1, c=2) at test.c:4

4 *(int *)a=16;

此时显示程序接收到信号SIGSEGV,Segmentation fault。

22,disassemble

0x00000000004004ec <sub+0>: push %rbp
0x00000000004004ed <sub+1>: mov %rsp,%rbp
0x00000000004004f0 <sub+4>: mov %edi,-0x4(%rbp)
0x00000000004004f3 <sub+7>: mov %esi,-0x8(%rbp)
0x00000000004004f6 <sub+10>: mov %edx,-0xc(%rbp)
0x00000000004004f9 <sub+13>: mov -0x4(%rbp),%eax
0x00000000004004fc <sub+16>: cltq
0x00000000004004fe <sub+18>: movl $0x10,(%rax)
0x0000000000400504 <sub+24>: mov $0x0,%eax
0x0000000000400509 <sub+29>: leaveq
0x000000000040050a <sub+30>: retq

查看汇编代码,发现有对应赋值语句,

使用的是rax寄存器存储值a,即此时rax=0,然后寄存器间接寻址,

指向该寄存器值的内存处即0处,写入数值16,发生core dump。

为了便于整体分析,顺便反汇编下main函数:

disassemble main

0x000000000040050b <main+0>: push %rbp
0x000000000040050c <main+1>: mov %rsp,%rbp
0x000000000040050f <main+4>: sub $0x10,%rsp
0x0000000000400513 <main+8>: movl $0x0,-0xc(%rbp)
0x000000000040051a <main+15>: movl $0x1,-0x8(%rbp)
0x0000000000400521 <main+22>: movl $0x2,-0x4(%rbp)
0x0000000000400528 <main+29>: mov -0x4(%rbp),%edx
0x000000000040052b <main+32>: mov -0x8(%rbp),%esi
0x000000000040052e <main+35>: mov -0xc(%rbp),%edi
0x0000000000400531 <main+38>: callq 0x4004ec <sub>
0x0000000000400536 <main+43>: mov $0x0,%eax
0x000000000040053b <main+48>: leaveq
0x000000000040053c <main+49>: retq

顺便看下当前(运行到接受到SIGSEGV,程序尚未处理该信号)的栈区的信息:

(gdb) x/24x 0x7fffffffe320
0x7fffffffe320: 0xf7ffa720 0x00000002 0x00000001 0x00000000
0x7fffffffe330: 0xffffe350 0x00007fff 0x00400536 0x00000000
0x7fffffffe340: 0xffffe420 0x00000000 0x00000001 0x00000002
0x7fffffffe350: 0x00000000 0x00000000 0xf7a9ebc6 0x00007fff
0x7fffffffe360: 0x00000000 0x00000000 0xffffe428 0x00007fff
0x7fffffffe370: 0x00000001 0x00000001 0x0040050b 0x00000000

b)查看core dump文件

假设运行程序test,产生core dump文件名为core,且与当前目录(core文件生成需要进行配置、包括其生成目录、生成文件名等)。

使用gdb进行查看:

1,gdb -c core test/gdb test core

Program terminated with signal 11, Segmentation fault.
#0 0x00000000004004fe in sub (a=0, b=1, c=2) at test.c:4
4 *(int *)a=16;

查看产生core dump的原因:signal 11,Segmentation fault。

2,bt/where 查看

#0 0x00000000004004fe in sub (a=0, b=1, c=2) at test.c:4
#1 0x0000000000400536 in main () at test.c:13

查看在哪个函数调用时,产生的core dump,函数位于源文件哪一行。

3,disassemble 由于本程序很简单,查看汇编代码以定位错误。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值