【Linux调试技术】程序控制

简介: 作者:gnuhpc 出处:http://www.cnblogs.com/gnuhpc/ 1.让程序停下来的三种模式 ·        断点(breakpoint):让程序在特定的地点停止执行。

作者:gnuhpc
出处:http://www.cnblogs.com/gnuhpc/

1.让程序停下来的三种模式

·        断点(breakpoint):让程序在特定的地点停止执行。

·        观察点(watchpoint):让程序在特定的内存地址(或者是一个涉及多个地址的表达式)的值发生变化时停止执行。注意,你不能给一个尚没有在栈帧中的表达式或变量设定观察点,换句话说,常常在程序停下来后才去设置观察点。在设定观察点后,栈帧中不存在所监控的变量时,观察点自动删除。

·        捕捉点(catchpoint):让程序在发生特定事件时停止执行。

注:

·        GDB文档中统称这三种程序暂停手段为breakpoint,例如在GDBdelete命令的帮助手册中就是这么描述的,它实际上指代的是这三种暂停手段,本文中以breakpoints统称三种模式,以中文进行分别称呼。

·        GDB执行程序到断点(成为断点被hit)时,它并没有执行断点指向的那一行,而是将要指向断点指向的那一行。

·        GDB是以机器指令为单位进行执行的,并非是以程序代码行来进行的,这个可能会带来一些困惑,下文有例子详述。

2.GDB breakpoints的查看

命令:i b = info breakpoints。返回列表每一列的含义如下:

·        Identifier breakpoints的唯一标识。

·        Type :该breakpoints属于上述三种模式中的哪一个(breakpoint, watchpoint, catchpoint

·        Disposition:该breakpoints下次被hit以后的状态(keepdeldis分别对应保留、删除、不使能)

·        Enable Status:该breakpoints是否使能。

·        Address:该breakpoints物理地址。

·        Location :若属于断点则指的是断点在哪个文件的第几行,若是观察点则指的是被观察的变量

3.GDB 程序控制的设置

·        断点设置:

o   设置普通断点:break function/line_number/filename:line_number/filename:function. 该断点在被删除或不使能前一直有效。

o   设置临时断点:tbreak function/line_number/filename:line_number/filename:function. 该断点在被hit一次后自动删除。

o   设置一次性断点:enable once breakpoint-list,这个与临时断点的不同是该断点会在被hit一次后不使能,而不是删除。

o   设置正则表达式断点:rbreak regexp 注意该正则表达式是grep型的正则,不是perlshell的正则语法。

o   设置条件断点:break break-args if (condition) ,例如break main if argc > 1

§  这个与观察点不同的是,观察点只要所观察的表达式或变量的值有变化程序就停下,而条件断点必须满足所指条件。条件断点在调试循环的时候非常有用,例如break if (i == 70000)

§  在已经添加的断点上加上条件使用cond id condition,例如:cond 3 i == 3;想去掉条件转化为普通断点则直接使用cond id,例如,cond 3

§  注意,这里的条件外的括号有没有都行,条件中可以使用<, <=, ==, !=, >, >=, &&, ||,&, |, ^, >>, <<,+, -, x, /, %等运算符,也可以使用方法,例如:break test.c:myfunc if ! check_variable_sanity(i),这里的方法返回值一定要是int,否则该条件就会被误读。

o   删除断点:

§  delete breakpoint_list     列表中为断点的ID,以空格隔开

§  delete          删除全部断点

§  clear           删除下一个GDB将要执行的指令处的断点

§  clear function/filename:function/line_number/filename:line_number 删除特定地点的断点

o   使能断点:enable breakpoint-list

o   不使能断点:disable breakpoint-list

o   跳过断点:ignore id numbers 表示跳过id表示的断点numbers次。

o   注意:

§  若设置断点是以函数名进行的话,C++中函数的重载会带来麻烦,该同名函数会都被设置上断点。请使用如下格式在C++中进行函数断点的设置:TestClass::testFunc(int)

§  设置的断点可能并非是你想放置的那一行。例如:

§    1: int main(void)

§   

§    2: {

§   

§    3:     int i;

§   

§    4:     i=3;

§   

§    5:     return 0;

§   

  6: }

 

我们不使用编译器优化进行编译,然后加载到GDB中,如下:
$ gcc -g3 -Wall -Wextra -o test1 test1.c
$ gdb test1
(gdb) break main
Breakpoint 1 at 0x6: file test1.c, line 4.

我们发现显然#4并非是main函数的入口,这是因为这一行是该函数第一行虽然产生了机器码,但是GDB并不认为这对调试有帮助,于是它就将断点设置在第一行对调试有帮助的代码上。

我们使用编译器优化再进行编译,情况会更加令人困惑,如下:

$ gcc -O9 -g3 -Wall -Wextra -o test1 test1.c
$ gdb test1
(gdb) break main
Breakpoint 1 at 0x3: file test1.c, line 6.

GCC发现i变量一直就没有使用,所以通过优化直接忽略了,于是程序第一行产生机器码的代码恰好是main函数的最后一行。

因此,建议在不影响的情况下,程序调试时将编译器优化选项关闭。

 

§  同一行有多个断点时,程序只会停下一次,实际上GDB使用的是其中ID最小的那个。

§  在多文件调试中,常常希望GDB在并非当前文件的部分设置断点,而GDB默认关注的是含有main函数的文件,此时你可以使用list functionname、或者单步调试等方式进入另一个文件的源代码中进行设置,此时你设置的行号就是针对这个文件的源代码了。

§  当你利用代码行进行断点设置时,重新编译程序并在GDBreload后,断点可能因为你代码行数的变化而发生相对位置变化(GDB指向的行数),这样的情况下使用DDD直接对原断点进行拖动是最方便的方法,它不会影响该断点的状态和条件,只会改变它所指的位置,从而省去了del一个断点后再在新位置添加设置新断点的麻烦。

§  DDD中还可以RedoUndo对断点的操作。

·        观察点设置:

o   设置写观察点:watch i ; watch (i | j > 12) && i > 24 && strlen(name) > 6,这是两种观察点设置的方式(变量或表达式)。写观察点在该变量的值被程序修改后立刻中止。注意,很多平台都有硬件支持的观察点,默认GDB是优先使用的就是这些,若暂时不可用,GDB会使用VM技术实现观察点,这样的好处是硬件的速度较快。

o   设置读观察点:rwatch

o   设置读写观察点:awatch

o   举例:下列简单程序,可以首先在main函数入口设置断点,然后在该断点被hit时设置观察点。

o     1: #include <stdio.h>

o    

o     2:

o    

o     3: int main(int argc, char **argv)

o    

o     4: {

o    

o     5:   int x = 30;

o    

o     6:   int y = 10;

o    

o     7:

o    

o     8:   x = y;

o    

o     9:

o    

o    10:   return 0;

o    

 11: }

 

这是个非常简单的程序,在main函数入口处断点被hit后我们可以设置rwatch x进行变量监视。

·         

·        程序恢复:

o   单步执行:

§  单步跳过:n = next跳过调用方法的细节,将该行视为一行代码进行执行。next 3表示连续执行三次next

§  单步进入:s = step 进入调用方法的细节。

o   执行到下一断点:c = continue,程序继续执行直到hit下一个断点。

o   执行到下一栈帧:fin = finish,程序继续执行直到当前栈帧完成。这个常常被用来完成所谓step out的工作,在你不小心按到了step时(你本意其实是想单步跳过),你就可以使用finish跳出该方法。当然,如果你进入了一个迭代函数中的多层以内,可能一个临时断点+continue或者until会更加有用,后者见下文。

o   执行到具有更高内存地址的机器指令:u = until (后边可以跟上funtionname/linenumber),应用的场景见下边的代码,在我们进入了这个循环后我们想跳出来执行循环后的代码,此时我们当然可以在循环后的第一行代码设置临时断点,然后continue到那,但这个操作会比较麻烦,最好的方式是使用until,该命令使程序继续运行知道遇到一个具有更高内存地址的机器指令时停止,而在循环被编译成机器指令时,会将循环条件放在循环体的最底部,所以利用until正好跳出循环进入下一机器指令(P.S. 你可以使用GCC-s查看生成的机器指令以便更好的理解这个命令的运行方式):

o     1: ...previous code...

o    

o     2: int i = 9999;

o    

o     3: while (i--) {

o    

o     4:    printf("i is %d/n", i);

o    

o     5:    ... lots of code ...

o    

o     6: }

o    

  7: ...future code...

 

·        程序反向调试:

o   这是GDB7以后新加入的功能,如果你在调试的时候发现自己已经错过了想调试的地方,这个操作可以使你不必重新开始调试而直接返回已经执行过的代码进行调试。我们使用下边一个非常简单的程序对这个新版本的功能加以说明:

o     1: #include <stdio.h>

o    

o     2: void foo() {    

o    

o     3:     printf("inside foo()");    

o    

o     4:     int x = 6;    

o    

o     5:     x += 2;

o    

o     6: }

o    

o     7:

o    

o     8: int main() {    

o    

o     9:     int x = 0;    

o    

o    10:     x = x+2;    

o    

o    11:     foo();    

o    

o    12:     printf("x = %d/n", x);    

o    

o    13:     x = 4;    

o    

o    14:     return(0);

o    

o    15: }

o    

 16:

 

我们编译一下然后在main函数处设置断点,然后使用record命令开始记录,这是使用反向调试必须的开始步骤,最后进行两步单步调试,打印x的值:

(gdb) b main
Breakpoint 1 at 0x804840d: file test.c, line 9.
(gdb) record
Process record: the program is not being run.
(gdb) r
Starting program: /home/gnuhpc/test

Breakpoint 1, main () at test.c:9
9               int x = 0;
(gdb
) record
(gdb) n
10              x = x+2;
(gdb)
11              foo();
(gdb) print x
$1 = 2


此时x=2,现在我们反向一步单步调试,打印x的值:

(gdb) reverse-next
10              x = x+2;
(gdb) p x
$2 = 0


这正是我们想要的。对于断点,反向调试也支持类似正向调试时的continue语句,我们在15行设置断点,然后continue到这个断点:

(gdb) b 15
Breakpoint 2 at 0x8048441: file test.c, line 15.
(gdb) c
Continuing.
inside foo()x = 2

Breakpoint 2, main () at test.c:15
15      }


此时我们在foo函数处加上断点,然后反向continue

(gdb) b foo
Breakpoint 3 at 0x80483ea: file test.c, line 3.
(gdb)
reverse-continue
Continuing.

Breakpoint 3, foo () at test.c:3
3               printf("inside foo()");


程序回到foo入口处。网上有文献反向调试指出必须使用软件观察点,事实并非如此:

·        (gdb) watch x
Hardware watchpoint 4: x
(gdb) reverse-continue
Continuing.
Hardware watchpoint 4: x

·        Old value = 6
New value = 134513384
foo () at test.c:4
4               int x = 6;
(gdb) n
Hardware watchpoint 4: x

·        Old value = 134513384
New value = 6
foo () at test.c:5
5               x += 2;

·         

·        由于篇幅有限我们在此提供手册上的反向调试命令解释资料,并且附上一篇教程,读者可以自行进行使用学习。

·        停止后的命令列表:

o   在程序中止执行时,用户可能会进行一系列操作,GDB提供了这个操作的自动化,类似于批处理脚本一样将需要操作的命令进行批量执行。语法为:

commands breakpoint-id
...
commands
...
end

o   例如我们调试斐波那契数列的程序:

o     1: #include <stdio.h>

o    

o     2: int fibonacci(int n);

o    

o     3: int main(void)

o    

o     4: {

o    

o     5:     printf("Fibonacci(3) is %d./n", fibonacci(3));

o    

o     6:     return 0;

o    

o     7: }

o    

o     8: int fibonacci(int n)

o    

o     9: {

o    

o    10:     if(n<=0||n==1)

o    

o    11:        return 1;

o    

o    12:     else

o    

o    13:        return fibonacci(n-1) + fibonacci(n-2);

o    

 14: }

 

由于这是一个递归函数,我们为了追寻递归,要查看程序以什么顺序调用fibonacci()传入的值,当然你可以使用printf进行查看,只是这个方法看起来很土。我们如下进行调试:(gdb) break fibonacci然后设置命令列表:(gdb) command 1
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end".
>silent
>printf "fibonacci was passed %d./n", n
>continue
>end
(gdb) run
Starting program: fibonacci
fibonacci was passed 3.
fibonacci was passed 2.
fibonacci was passed 1.
fibonacci was passed 0.
fibonacci was passed 1.
Fibonacci(3) is 3.
Program exited normally.
(gdb)

·        这里就能很清晰地看到函数被调用的情况了。当然,你可以将上述命令列表写成一个宏(最多支持10个传入参数,我们使用了两个):

·        (gdb) define print_and_go
Redefine command "print_and_go"? (y or n) y
Type commands for definition of "print_and_go".
End with a line saying just "end".
>printf $arg0, $arg1
>continue
>end

·        使用的时候非常方便:

·        (gdb) commands 1
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end".
>silent

·        >print_and_go "fibonacci() was passed %d/n" n
>end

·        你甚至可以将这个宏存放在.gdbinit文件(GDB启动时自加载文件)中,以后方便使用,关于gdbinit的使用下文自有介绍。

附注:后文将介绍catchpoint的用法和实例,在此略过。

参考文献:
Art of Debugging

Linux® Debugging and Performance Tuning: Tips and Techniques

 

作者:gnuhpc
出处:http://www.cnblogs.com/gnuhpc/


               作者:gnuhpc
               出处:http://www.cnblogs.com/gnuhpc/
               除非另有声明,本网站采用知识共享“署名 2.5 中国大陆”许可协议授权。


分享到:

相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
目录
相关文章
|
3月前
|
安全 Linux Shell
Linux上执行内存中的脚本和程序
【9月更文挑战第3天】在 Linux 系统中,可以通过多种方式执行内存中的脚本和程序:一是使用 `eval` 命令直接执行内存中的脚本内容;二是利用管道将脚本内容传递给 `bash` 解释器执行;三是将编译好的程序复制到 `/dev/shm` 并执行。这些方法虽便捷,但也需谨慎操作以避免安全风险。
202 6
|
4月前
|
网络协议 Linux
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
Linux查看端口监听情况,以及Linux查看某个端口对应的进程号和程序
697 2
|
4月前
|
Linux Python
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
linux上根据运行程序的进程号,查看程序所在的绝对路径。linux查看进程启动的时间
72 2
|
29天前
|
缓存 NoSQL Linux
Linux调试
本文介绍了Linux调试、性能分析和追踪的培训资料,涵盖调试、性能分析和追踪的基础知识及常用工具。
222 6
Linux调试
|
2月前
|
运维 Java Linux
【运维基础知识】Linux服务器下手写启停Java程序脚本start.sh stop.sh及详细说明
### 启动Java程序脚本 `start.sh` 此脚本用于启动一个Java程序,设置JVM字符集为GBK,最大堆内存为3000M,并将程序的日志输出到`output.log`文件中,同时在后台运行。 ### 停止Java程序脚本 `stop.sh` 此脚本用于停止指定名称的服务(如`QuoteServer`),通过查找并终止该服务的Java进程,输出操作结果以确认是否成功。
61 1
|
2月前
|
Linux 虚拟化
Vmware 傻瓜式安装(不可不知道的Linux基础知识和技术 01)
本文介绍了VMware虚拟机的下载与安装步骤。首先,通过提供的网盘链接下载VMware安装包。接着,详细描述了安装流程,包括接受协议、选择安装路径(建议避免系统C盘)、取消更新选项等。最后,输入许可证密钥完成安装,并展示了打开虚拟机后的主界面。整个过程简单易懂,适合新手操作。
152 1
|
3月前
|
消息中间件 分布式计算 Java
Linux环境下 java程序提交spark任务到Yarn报错
Linux环境下 java程序提交spark任务到Yarn报错
47 5
|
3月前
|
安全 Linux Android开发
Linux CFI (Control-flow integrity)技术相关资料汇总
Linux CFI (Control-flow integrity)技术相关资料汇总
|
4月前
|
NoSQL Linux C语言
Linux GDB 调试
Linux GDB 调试
67 10
|
4月前
|
NoSQL Linux C语言
嵌入式GDB调试Linux C程序或交叉编译(开发板)
【8月更文挑战第24天】本文档介绍了如何在嵌入式环境下使用GDB调试Linux C程序及进行交叉编译。调试步骤包括:编译程序时加入`-g`选项以生成调试信息;启动GDB并加载程序;设置断点;运行程序至断点;单步执行代码;查看变量值;继续执行或退出GDB。对于交叉编译,需安装对应架构的交叉编译工具链,配置编译环境,使用工具链编译程序,并将程序传输到开发板进行调试。过程中可能遇到工具链不匹配等问题,需针对性解决。
118 3