gdb用法详解

一.一个简单的调试示例

1.调试程序

//debugtest.c
#include <stdio.h>

int main(void)
{
	int i;
	char input;

	for(i=0;i<2;i++){
		printf("pleast input a character:\n");
		scanf("%c",&input);
		switch(input){
			case 'a':
				printf("You input a!\n");
			case 'b':
				printf("You input b!\n");
			default:
				printf("Your input is not a or b!\n");
		}
	}
	return 0;
}

2.使用gcc编译程

    *加-g选项

   

3.进入gdb调试环境

//加载可执行程序到gdb中,也可以先执行gdb进入环境,再通过file <文件名>进行加载。

root@ubuntu:/home# gdb debugtest.out 

//GDB版本信息
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
Copyright (C) 2012 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".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/debugtest.out...done.

 

//run命令执行可执行程序
(gdb) run    

Starting program: /home/debugtest.out 
pleast input a character:
af
You input a!
You input b!
Your input is not a or b!
pleast input a character:
Your input is not a or b!
[Inferior 1 (process 3228) exited normally]

在GDB中加载可执行程序的目的不是执行,而是进行调试。非常有用的2个概念就是断点和观察点。断点的设置是“break 行号”,要知道具体的行号,可以用list命令显示可执行文件中的带行号代码信息。
(gdb)
(gdb) list

1    #include <stdio.h>
2    
3    int main(void)
4    {
5        int i;
6        char input;
7    
8        for(i=0;i<2;i++){
9            printf("pleast input a character:\n");
10            scanf("%c",&input);
(gdb) 
11            switch(input){
12                case 'a':
13                    printf("You input a!\n");
14                case 'b':
15                    printf("You input b!\n");
16                default:
17                    printf("Your input is not a or b!\n");
18            }
19        }
20        return 0;
(gdb) 
21    }
 

//添加断点
(gdb) break 10
Breakpoint 1 at 0x40057f: file debugtest.c, line 10.
(gdb) break 13
Breakpoint 2 at 0x4005ab: file debugtest.c, line 13.

 

//info break可以查看当前设置的断点信息
(gdb) info break 
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000040057f in main at debugtest.c:10
2       breakpoint     keep y   0x00000000004005ab in main at debugtest.c:13
(gdb) 

 

//禁用断点2
(gdb) disable break  1
(gdb) 
(gdb) info break 

Num     Type           Disp Enb Address            What
1       breakpoint     keep n   0x000000000040057f in main at debugtest.c:10
2       breakpoint     keep y   0x00000000004005ab in main at debugtest.c:13
(gdb) 

//允许断点2

(gdb) enable break  1
(gdb) 
(gdb) info break 

Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000040057f in main at debugtest.c:10
2       breakpoint     keep y   0x00000000004005ab in main at debugtest.c:13
(gdb) 

 

//执行带断点的程序
(gdb) run

//gdb提示开始执行/home/下面的debugtest.out 可执行程序

Starting program: /home/debugtest.out 
pleast input a character:

//gdb提示遇到第一个断点以及断点信息

Breakpoint 1, main () at debugtest.c:10
10            scanf("%c",&input);

 

//设置观察点,在程序运行过程中,变量取值变化时给出提示
(gdb) watch input

//gdb提示观察点设置成功
Hardware watchpoint 3: input

 

//next表示单步执行遇到函数不进入,step表示单步执行遇到函数时进入,如果执行到下一个断点使用continue
(gdb) next

//输入参数字符a回车
a

//gdb提示参数a发生变化

Hardware watchpoint 3: input

Old value = 0 '\000'
New value = 97 'a'

//gdb提示程序进入了库函数
0x00007ffff7a723cf in _IO_vfscanf () from /lib/x86_64-linux-gnu/libc.so.6


(gdb) next

//gdb提示程序退出库函数,调用了函数库中的scanf

Single stepping until exit from function _IO_vfscanf,
which has no line number information.
0x00007ffff7a790bd in __isoc99_scanf () from /lib/x86_64-linux-gnu/libc.so.6
 

(gdb) next
//进入switch case判断

Single stepping until exit from function __isoc99_scanf,
which has no line number information.
main () at debugtest.c:11
11            switch(input){

(gdb) next

//遇到第二个断点,当input=a时,执行所有的分支情况

Breakpoint 2, main () at debugtest.c:13
13                    printf("You input a!\n");

 

(gdb) next
You input a!
15                    printf("You input b!\n");


(gdb) next
You input b!
17                    printf("Your input is not a or b!\n");

(gdb) next

//开始第二轮循环
Your input is not a or b!
8        for(i=0;i<2;i++){

(gdb) next

9            printf("pleast input a character:\n");


(gdb) next

//遇到断点1

pleast input a character:

Breakpoint 1, main () at debugtest.c:10
10            scanf("%c",&input);

(gdb) next

//直接回车,相当于把\n赋值给input,遇到监视变量

Hardware watchpoint 3: input

Old value = 97 'a'
New value = 10 '\n'
0x00007ffff7a723cf in _IO_vfscanf () from /lib/x86_64-linux-gnu/libc.so.6

 

(gdb) next

//gdb提示程序进入了库函数

Single stepping until exit from function _IO_vfscanf,
which has no line number information.
0x00007ffff7a790bd in __isoc99_scanf () from /lib/x86_64-linux-gnu/libc.so.6

 

(gdb) next

//gdb提示程序退出了库函数,进入switch判断

Single stepping until exit from function __isoc99_scanf,
which has no line number information.
main () at debugtest.c:11
11            switch(input){


(gdb) next

//不与a.b匹配打印信息正确
17                    printf("Your input is not a or b!\n");


(gdb) next

Your input is not a or b!

//程序判断是否还要执行for循环
8        for(i=0;i<2;i++){


(gdb) next

//程序返回0值
20      return 0;


(gdb) next
21 } 


(gdb) next

//退出main函数,程序执行结束
0x00007ffff7a3c76d in __libc_start_main () from /lib/x86_64-linux-gnu/libc.so.6


(gdb) next
Single stepping until exit from function __libc_start_main,
which has no line number information.
[Inferior 1 (process 3248) exited normally]


(gdb) info break

//查看断电执行信息

Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000040057f in main at debugtest.c:10
         breakpoint already hit 2 times
2       breakpoint     keep y   0x00000000004005ab in main at debugtest.c:13
         breakpoint already hit 1 time
(gdb)

 

 以上代码流程说明输入字符与第一个case匹配的时候,则会执行后面所有情况的printf语句;而满足最后一种情况的输入时,则只会打印最后一个printf信息。因此switch...case结构没有问题,主要问题在于满足某个case时,执行完语句没有及时跳出结构。每个printf 后面增加break即可。

 

二.命令详解

1.(gdb) shell

gdb也支持shell命令,也支持通过TAB键shell的自动补全,比如:

(gdb) shell ls -l (查看文件信息)
(gdb) shell cd /home (更改目录)
(gdb) shell clear (清屏)

2.输出格式

通常GDB会根据变量的类型输出变量的值,但你也可以自定义GDB的输出的格式。
x  按十六进制格式显示变量。
a  按十六进制格式显示变量。
u  按十六进制格式显示无符号整型。
d  按十进制格式显示变量。
o  按八进制格式显示变量。
t  按二进制格式显示变量。 
c  按字符格式显示变量。
f  按浮点数格式显示变量。

例如:

(gdb) p i      //默认十进制 
$1 = 101   
 
(gdb) p/x i    //十六进制
$2 = 0x65
(gdb) p/a i
$5 = 0x65

(gdb) p/t i    //二进制
$6 = 1100101
 
(gdb) p/c i   //字符格式
$3 = 101 'e'
 
(gdb) p/f i   //浮点型格式
$4 = 1.41531145e-43

3.查看内存

你可以使用examine命令(简写是x)来查看内存地址中的值。x命令的语法如下所示:

x/<n/f/u> <addr> 

n、f、u是可选的参数。

n 是一个正整数,表示显示内存的长度,也就是说从当前地址向后显示几个地址的内容。

f 表示显示的格式,参见上面。如果地址所指的是字符串,那么格式可以是s,如果地十是指令地址,那么格式可以是i。

u 表示从当前地址往后请求的字节数,如果不指定的话,GDB默认是4个bytes。u参数可以用下面的字符来代替,b表示单字节,h表示双字节,w表示四字节,g表示八字节。当我们指定了字节长度后,GDB会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来。

<addr>表示一个内存地址。

n/f/u三个参数可以一起使用。例如:

命令:x/3uh 0x54320 表示,从内存地址0x54320读取内容,h表示以双字节为一个单位,3表示三个单位,u表示按十六进制显示。

4.查看栈信息

当程序被停住了,你需要做的第一件事就是查看程序是在哪里停住的。当你的程序调用了一个函数,函数的地址,函数参数,函数内的局部变量都会被压入“栈”(Stack)中。你可以用GDB命令来查看当前的栈中的信息。

下面是一些查看函数调用栈信息的GDB命令:

backtrace 
bt 
    打印当前的函数调用栈的所有信息。如:
 
    (gdb) bt
    #0  func (n=250) at tst.c:6
    #1  0x08048524 in main (argc=1, argv=0xbffff674) at tst.c:30
    #2  0x400409ed in __libc_start_main () from /lib/libc.so.6
 
    从上可以看出函数的调用栈信息:__libc_start_main --> main() --> func()
 
backtrace <n>
bt <n> 
    n是一个正整数,表示只打印栈顶上n层的栈信息。
 
backtrace <-n> 
bt <-n> 
    -n表一个负整数,表示只打印栈底下n层的栈信息。

如果你要查看某一层的信息,你需要在切换当前的栈,一般来说,程序停止时,最顶层的栈就是当前栈,如果你要查看栈下面层的详细信息,首先要做的是切换当前栈。

frame <n> 
f <n> 
    n是一个从0开始的整数,是栈中的层编号。比如:frame 0,表示栈顶,frame 1,表示栈的第二层。
 
up <n>
    表示向栈的上面移动n层,可以不打n,表示向上移动一层。 
 
down <n> 
    表示向栈的下面移动n层,可以不打n,表示向下移动一层。 
 
上面的命令,都会打印出移动到的栈层的信息。如果你不想让其打出信息。你可以使用这三个命令:
 
        select-frame <n> 对应于 frame 命令。
        up-silently <n> 对应于 up 命令。
        down-silently <n> 对应于 down 命令。

查看当前栈层的信息,你可以用以下GDB命令:

frame 或 f 
    会打印出这些信息:栈的层编号,当前的函数名,函数参数值,函数所在文件及行号,函数执行到的语句。
 
info frame 
info f 
    这个命令会打印出更为详细的当前栈层的信息,只不过,大多数都是运行时的内内地址。比如:函数地址,调用函数的地址,被调用函数的地址,目前的函数是由什么样的程序语言写成的、函数参数地址及值、局部变量的地址等等。如:
        (gdb) info f
        Stack level 0, frame at 0xbffff5d4:
         eip = 0x804845d in func (tst.c:6); saved eip 0x8048524
         called by frame at 0xbffff60c
         source language c.
         Arglist at 0xbffff5d4, args: n=250
         Locals at 0xbffff5d4, Previous frame's sp is 0x0
         Saved registers:
          ebp at 0xbffff5d4, eip at 0xbffff5d8
 
 info args
    打印出当前函数的参数名及其值。
 
 info locals
    打印出当前函数中所有局部变量及其值。
 
 info catch
    打印出当前的函数中的异常处理信息。

 

 

4.自动显示

你可以设置一些自动显示的变量,当程序停住时,或是在你单步跟踪时,这些变量会自动显示。相关的GDB命令是display。

display <expr> 
display/<fmt> <expr> 
display/<fmt> <addr>
expr是一个表达式,fmt表示显示的格式,addr表示内存地址,当你用display设定好了一个或多个表达式后,只要你的程序被停下来,GDB会自动显示你所设置的这些表达式的值。
 
格式i和s同样被display支持,一个非常有用的命令是:
    display/i $pc
$pc是GDB的环境变量,表示着指令的地址,/i则表示输出格式为机器指令码,也就是汇编。于是当程序停下后,就会出现源代码和机器指令码相对应的情形,这是一个很有意思的功能。
 
下面是一些和display相关的GDB命令:
undisplay <dnums...>
delete display <dnums...>
删除自动显示,dnums意为所设置好了的自动显式的编号。如果要同时删除几个,编号可以用空格分隔,如果要删除一个范围内的编号,可以用减号表示(如:2-5)
 
disable display <dnums...>
enable display <dnums...>
disable和enalbe不删除自动显示的设置,而只是让其失效和恢复。
 
info display
查看display设置的自动显示的信息。GDB会打出一张表格,向你报告当然调试中设置了多少个自动显示设置,其中包括,设置的编号,表达式,是否enable。

5.修改变量的值

修改被调试程序运行时的变量值,在GDB中很容易实现,使用GDB的print命令即可完成。如:

 

    (gdb) print x=4

 

x=4这个表达式是C/C++的语法,意为把变量x的值修改为4,如果你当前调试的语言是Pascal,那么你可以使用Pascal的语法:x:=4。

 

在某些时候,很有可能你的变量和GDB中的参数冲突,如:

 

    (gdb) whatis width

    type = double

    (gdb) p width

    $4 = 13

    (gdb) set width=47

    Invalid syntax in expression.

 

因为,set width是GDB的命令,所以,出现了“Invalid syntax in expression”的设置错误,此时,你可以使用set var命令来告诉GDB,width不是你GDB的参数,而是程序的变量名,如:

 

    (gdb) set var width=47

另外,还可能有些情况,GDB并不报告这种错误,所以保险起见,在你改变程序变量取值时,最好都使用set var格式的GDB命令。

6.跳转执行

般来说,被调试程序会按照程序代码的运行顺序依次执行。GDB提供了乱序执行的功能,也就是说,GDB可以修改程序的执行顺序,可以让程序执行随意跳跃。这个功能可以由GDB的jump命令来完:

jump <linespec>

指定下一条语句的运行点。<linespce>可以是文件的行号,可以是file:line格式,可以是+num这种偏移量格式。表式着下一条运行语句从哪里开始。

jump <address>

这里的<address>是代码行的内存地址。

注意,jump命令不会改变当前的程序栈中的内容,所以,当你从一个函数跳到另一个函数时,当函数运行完返回时进行弹栈操作时必然会发生错误,可能结果还是非常奇怪的,甚至于产生程序Core Dump。所以最好是同一个函数中进行跳转。

熟悉汇编的人都知道,程序运行时,有一个寄存器用于保存当前代码所在的内存地址。所以,jump命令也就是改变了这个寄存器中的值。于是,你可以使用“set $pc”来更改跳转执行的地址。如:

set $pc = 0x485

7.产生信号量

使用singal命令,可以产生一个信号量给被调试的程序。如:中断信号Ctrl+C。这非常方便于程序的调试,可以在程序运行的任意位置设置断点,并在该断点用GDB产生一个信号量,这种精确地在某处产生信号非常有利程序的调试。

语法是:signal <singal>,UNIX的系统信号量通常从1到15。所以<singal>取值也在这个范围。

single命令和shell的kill命令不同,系统的kill命令发信号给被调试程序时,是由GDB截获的,而single命令所发出一信号则是直接发给被调试程序的。

8.强制函数返回

如果你的调试断点在某个函数中,并还有语句没有执行完。你可以使用return命令强制函数忽略还没有执行的语句并返回。

return

return <expression>

使用return命令取消当前函数的执行,并立即返回,如果指定了<expression>,那么该表达式的值会被认作函数的返回值。

9.强制调用函数

call <expr>

表达式中可以一是函数,以此达到强制调用函数的目的。并显示函数的返回值,如果函数返回值是void,那么就不显示。

另一个相似的命令也可以完成这一功能——print,print后面可以跟表达式,所以也可以用他来调用函数,print和call的不同是,如果函数返回void,call则不显示,print则显示函数返回值,并把该值存入历史数据中。

 

其他几个命令:

delete break n    //删除第n个断点

print             //遇到断点时,可以使用print input打印变量input的值

whatis            //遇到断点时,可以使用whatis input打印变量input的类型

Quit              //退出GDB调试环境。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值