学习GDB(一)

一、停下来环顾程序

1、暂停机制

     三种方法可以通知GDB暂停程序的执行:

     - 断点:通知GDB在特定位置暂停执行。

     - 监视点:通知GDB当特定内存(或者涉及一个或多个位置的表达式)的值发生变化时暂停执行。

     - 捕获点:通知GDB当特定事件发生时暂停执行。

     GDB文档中把这三种机制全部认为是断点,而delete命令也是删除全部这三种类型。

2、暂停概述

     在程序特定“位置”设置断点,到那一点时,调试器会暂停程序执行。GDB中的“位置”含义非常灵活,它可以是各种源代码行、代码地址、源代码文件中的行号或者函数入口等。

     GDB中设置断点的那一行是将要执行的下一行源代码(可以认为GDB在源代码的断点行和前一行之间等待)。

     GDB的工作针对的是机器语言指令,而不是源代码行,一行代码可能对应于数行机器语言。GDB之所以可以在源代码行工作,是因为可执行文件中包括了额外的信息。

3、断点跟踪

     程序员创建的每一个断点(包括三种类型)都被标识为从1开始的唯一整数标识符。这个标识符用来执行该断点上的各种操作。调试器还有列出所有断点及其属性的方式。

     创建断点:break  行号/函数名  ;

     GDB会告知你分配给该断点的编号。

     查看断点编号:info  breakpionts  ;

     删除断点: delete  编号  ;

4、设置断点

     GDB指定断点的方式:

     -break  function  ;在函数function()的入口(第一行可执行代码)处设置断点。

     -break  line_number  ;在当前活动源代码文件的line_number处设置断点。

     -break  filename:line_number  ;在源代码文件filename的line_number处设置断点,如果filename不在当前工作目录中,则可给出相对路径名或完全路径名来帮助GDB查找该文件。

     -break  filename:function  ;在文件filename中的函数function()的入口处设置断点。

      在某个函数上设置断点.当使用允许函数重载的语言(如:C++,如果使用static限定符声明带文件作用域,C语言中也可以有重载函数)时,有可能同时在几个重载的函数上设置了断点;

      比 如:CLogManager类有三个重载的成员函数insert(const struct log_mo_simple&)、insert(const struct log_mt_simple&)、 insert(const struct log_st_simple&)

      设置断点: break CLogManager::insert

      执行该命令之后,gdb会找到实现CLogManager类的源文件,然后在这三个重载的成员函数上都设置一个断点;

      要没有歧义,可以通过如在源代码行号设置断点等方法。

     -tbreak  filename:function/line_number  ;临时断点设置,即首次到达后就会被自动删除的断点。

     -break  +offset/-offset  ;在当前选中栈帧中正在执行的源代码行前面或后面设置断点偏移行数。

     -break  *address  ;在虚拟内存地址处设置断点。这对于程序没有调试信息的部分(比如当源代码不可用时,或者对于共享库)是必须的。    

     GDB实际设置断点的位置可能与请求断点的位置不同,如:

int main(void)
{
  int i;
  i = 3;
  
  return 0;
}

     不加优化的编译这个程序,并尝试在main()的入口处设置断点。以为会放在第一行,而实际放在第四行。

     一个原因是这一行是可执行代码。GDB实际使用的是机器语言指令工作,有了增强的符号表后,会产生一种GDB使用源代码行的错觉。实际上,声明i确实会产生机器码,但GDB认为这对于我们的代码调试没用,所以直接在第四行设置断点。

     其他断点类型

     -hbreak  ;设置硬件辅助断点,可以在内存中设置断点,不需要修改该内存位置的内容。需要硬件支持,主要用于EEPROM/ROM调试。临时断点设置为:-thbreak。

     -rbreak  ;采用grep风格的正则表达式,并在匹配该正则表达式的任何函数入口处设置断点。

     优化编译程序后,断点设置更不明确,GDB扮演了一个更主动的角色,这是在调试完前永远不应当优化代码的原因之一。

     在同一行设置多个断点,GDB只会中断一次。在具有多个断点的代码行上,触发中断的断点将是编号最小的断点。

     捕获点

     -catch  ;设置捕获点,这类似于断点,但能通过如抛出异常、捕获异常、发信号通知、调用fork()、加载和卸载库以及其他很多事件触发。

5、展开GDB示例

    在任何时间点上,GDB都有一个焦点,可以将它看作当前“活动”文件。这意味着除非对命令做了限定,否则都是在GDB的焦点文件上执行命令。下面动作会使焦点会转移到不同文件上;

     -向不同的源文件应用list命令。

     -进入位于不同的源代码文件中的代码。

     -当在不同源代码文件中执行代码时GDB遇到断点。

    离开GDB的命令是:-quit  ;

6、断点的持久性

     不用在修改和重新编译代码时退出GDB,因为这样做很繁琐,还会重新进入断点。当不退出GDB修改编译时,执行run命令,GDB会感知到代码已修改,并自动重新加载新版本。

     修改代码后,代码会动,而断点本身不会动。即可能会在那一行不包括原来其上设置断点的语句。因此,需要通过删除这个断点并重新设置一个新的来移动断点。

     保存断点的方法是把断点储存在源代码所在目录(或者从中调用GDB的目录)的.gdbinit启动文件中。

7、在GDB中删除断点

     -delete  breakpoint-list  ;删除断点使用数值标识符。可以是个数字,如delete 2;也可以是一个数字列表,如:delete 2 4 6;

     -delete  ;删除所有断点。在.gdbinit启动脚本文件中设置:set confirm off,可关闭GDB要确认删除操作。

     -clear  ;清除GDB将要执行的下一个指令处的断点。适用于要删除GDB已经到达的断点的情况。

     -clear   function 、clear  filename:function、clear line_number和clear filename:line_number  ;根据具体位置清楚断点,与break类似。

8、在GDB中禁用断点

     要保留断点以便以后使用,暂时又不希望GDB停止执行,就可以禁用它们。

     -disable  breakpoint-list  ;禁用断点。不带参数则禁用所有断点。

     -enable  breakpoint-list  ;启用断点。不带参数则启用所有断点。

     -enable  once  breakpoint-list  ;使断点下次引起GDB暂停执行后被禁用。

9、进一步浏览断点的属性

     每一个断点都有各个属性--行号、加在断点上的条件(如果有的话)、当前启用/禁用状态等。

     -info breakpoints  ;获得设置的所有断点的清单,以及它们的属性。详细分析如下:

     (1)标识符(Num):断点的唯一标识符。

     (2)类型(Typ):这个字段指出该断点是断点、监视点还是捕获点。

     (3)部署(Disp):每个断点都有一个部署,指示断点下次引起GDB暂停程度的执行后该断点上会发生什么事情,可能的部署有以下3种:

              -保持(keep),下次到达断点后不会改变断点。这是新建断点的默认部署。

              -删除(del),下次到达断点后删除该断点。使用tbreak命令创建的任何断点都使这样的断点。

              -禁用(dis),下次到达断点时会禁用该断点。这是使用enable once命令设置的断点。

     (4)启用状态(Enb):这个字段说明断点当前是启用还是禁用。

     (5)地址(Address):这是内存中设置断点的位置。这主要用于汇编语言程序员,或者试图调试没有扩充的符号表编译的可执行文件的人。

     (6)位置(What):各个断点位于源代码中的特定行上。What字段显示了断点所在位置的行号和文件名。对于监视点,这个字段点表示正在监视哪个位置。

10、恢复执行

     -next  ;执行下一行代码,执行函数时不会在其中暂停,然后在调用之后的第一条语句处暂停。把函数调用看做一行代码,并在一个操作中执行整个函数,这称为单步越过(stepping over)函数。后面可加一个可选数值参数:

-next line_number  ;表示next执行的额外行数。

     -step  ;执行下一行代码,执行函数时会进入其中,在其中暂停,然后在函数的第一条语句处暂停。称为单步进入(stepping into)函数。后面可加一个可选数值参数:-step line_number  ;表示step执行的额外行数。

     GDB不会在不具有调试信息的代码(即符号表)内停止,如C库中的printf() 。

     -continue  ;恢复GDB程序执行,直到触发断点或程序结束。后面可加一个可选数值参数:-continue  number  ;这个数字要求GDB忽略下面n个断点。

     -finish  ;简称fin,恢复GDB程序执行,直到恰好在当前栈帧完成之前为止。例如,不在main()函数中,finish命令会导致GDB恢复执行,直到恰好在函数返回之后为止。finish的另一个常见用途是当不小心单步进入原本希望单步跨过的函数时,这种情况下,finish可以将你正好返回到使用next会位于的位置。如果在一个递归函数中,finish只会将你带到递归的上一层。

     -until  ;执行程序,直到到达当前循环下一个源代码。until实际上做的是执行程序直到它到达内存地址比当前内存地址更高的机器指令,而不是直到达到源代码中的更大的行号。disassemble p/x $pc来输出当前位置。until命令也可以接受源代码中的位置作为参数:-until  line_number、-until  function、-until  filename:line_number、-until  filename:function。

11、条件断点

     -break  break-args if (condition)  ;其中break-args是可以传递给break以指定断点位置的任何参数。condition是下面定义的布尔表达式。括着condition的圆括号是可选的。中断条件也极其灵活,包括:

            -相等、逻辑和不相等运算符(<、<=、==、!=、>、>=、&&、||等)。

            -按位和移位运算符(&、|、^、>>、<<等)。

            -算术运算符(+、-、*、/、%)。

            -你自己的函数,只要它们被链接到程序中,如,

break test.c:myfunc if !check_variable_sanity(i)

            -库函数,只要该库被链接到代码中,如,

break 4 if strlen(mystring) == 0

     由于优先级的次序规则在起作用,因此可能需要使用括号将一些结构括起来,如,

(x & y) == 0

     此外,如果在GDB表达式中使用不是用调试符号编译(几乎肯定是这种情况)的库函数,那么唯一能在条件中使用的返回值类型为int。换而言之,如果没有调试信息,GDB会假设函数的返回值是int类型。这种假设有时候会导致曲解:

(gdb) print cos(0.0)
$1 = 14368

     然而,强制类型转换也不行:

(gdb) print (double)cos(0.0)
$1 = 14336

     实际上,有一种可以在GDB表达式中使用不返回int的函数,但这种方法相当神秘,技巧在于使用指向函数的恰当数据类型定义GDB方便变量。

(gdb) set $p = (double (*) (double))cos
(gdb) ptype $p
type = double(*)()
(gdb) print cos(3.1415926)
$2 = 14368
(gdb) print $p(3.1415926)
$3 = -1

     可以对正常断点设置条件设置条件将它们变成条件断点。例如,如果设置了断点3作为无条件断点,但现在希望加入条件i==3,只要键入:

(gdb)cond 3 i==3

     如果以后要删除条件,并保持该断点,只需键入:

(gdb)con 3

12、断点命令列表

     -commands  ;使用commands命令设置命令列表:

commands breakpoint-number
...
commands
...
end

     其中breakpoint-number是要将命令添到其上的断点的标识符,commands是用新行分隔的任何有效GDB命令列表。逐条键入命令,然后键入end表示输入结束。从那以后,每当GDB在这个断点处中断时,它都会执行你输入的任何命令。例如,

(gdb) command 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>silent
>printf "mimiasd was passed %d.\n",n
>continue
>end

     可用GDB的define命令创建宏,这样可以在其他程序或者该程序的其他代码行中做这样的事,

(gdb) define mimiasd_print_and_go
Type commands for definition of "mimiasd_print_and_go".
End with a line saying just "end".
>printf $arg0,$arg1
>continue
>end

     要像上述代码那样使用这个宏,应键入:

(gdb) commands 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>silent 
>mimiasd_print_and_go "mimiasd was passed %d\n" n
>end

     注意,mimiasd_print_and_go的参数之间没有逗号。可以将宏保存在.gdbinit文件中以便其他程序使用。参数最多可以有10个。-show user可以得到所有宏的列表。

     实际上,任何有效的GDB表达式都可以进入命令列表。可以使用库函数,甚至自己的函数,只要它被链接到执行文件即可。可以使用它们的返回值,只要它们返回的值是int类型。例如:

(gdb) command 1
Type commands for breakpoint(s) 1, one per line.
End with a line saying just "end".
>silent
>printf "mimiasd was passed %d.\n",strlen(string)
>continue
>end

13、监视点

     监视点是一种特殊类型的断点,它类似于正常断点,是要求GDB会暂停程序执行的指令。区别在于监视点没有“住在”某一行源代码中。取而代之的是,监视点是指示GDB每当某个表达式改变原值就暂停执行的指令。一旦变量不再存在于调用栈的任何帧中(当包含局部变量的函数返回时),GDB会自动删除监视点。监视点只能监视单线程的变量。

     13.1  设置监视点

     -watch  var  ;该命令会导致每当var改变值时GDB都会中断。

     -watch  var expression  ;当变量满足表达式expression,且发生变化时GDB会中断。

 

 

转载于:https://my.oschina.net/u/2537915/blog/636199

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值