gdb 笔记(07)— 自动显示变量值、显示源代码、监视变量或内存、查看内存

本文介绍了如何使用GDB的display命令自动显示变量值,包括多变量设置和管理;展示了如何在调试时查看源代码和使用watch命令监视变量变化;详细讲解了内存查看技术,涉及字符串、整型、结构体和内存对齐。
摘要由CSDN通过智能技术生成

1. 自动显示变量的值

使用 print 或者 p 命令来显示变量的值,但是有一个问题,即如果想要查看某个变量的值,需要不停地使用 print 命令。这对于需要观察那些不停变化的变量值来说,使用 p 命令就不太方便了,因为需要使用多次。

gdb 还有另外一个 display 命令,每次程序暂停都可以自动显示变量值。语法如下:

display 变量名

后面可以跟多个变量名,比如 display {var1,var2,var3}

我们使用 display 来自动观察 fun_test 中的变量 x ,而不是使用 print 命令,源码 demo.cpp 代码如下:

#include <iostream>
#include <string>

void fun_test(int a, const char *str)
{
    printf("a is %d, str is %s\n", a, str);
}

int main(int argc, char *argv[])
{
    for (int i = 0; i < 10; i++)
    {
        fun_test(i, "test");
    }
}

调试过程如下:

(gdb) b fun_test
Breakpoint 1 at 0x799: file demo.cpp, line 6.
(gdb) r
Starting program: /home/wohu/cppProject/book_debug/chapter_3.1/demo 

Breakpoint 1, fun_test (a=0, str=0x5555555548e9 "test") at demo.cpp:6
6	    printf("a is %d, str is %s\n", a, str);
(gdb) p a
$1 = 0
(gdb) p a
$2 = 0
(gdb) c
Continuing.
a is 0, str is test

Breakpoint 1, fun_test (a=1, str=0x5555555548e9 "test") at demo.cpp:6
6	    printf("a is %d, str is %s\n", a, str);
(gdb) p a
$3 = 1
(gdb) c 
Continuing.
a is 1, str is test

Breakpoint 1, fun_test (a=2, str=0x5555555548e9 "test") at demo.cpp:6
6	    printf("a is %d, str is %s\n", a, str);
(gdb) p a
$4 = 2
(gdb) display a
1: a = 2
(gdb) c
Continuing.
a is 2, str is test

Breakpoint 1, fun_test (a=3, str=0x5555555548e9 "test") at demo.cpp:6
6	    printf("a is %d, str is %s\n", a, str);
1: a = 3
(gdb) c
Continuing.
a is 3, str is test

Breakpoint 1, fun_test (a=4, str=0x5555555548e9 "test") at demo.cpp:6
6	    printf("a is %d, str is %s\n", a, str);
1: a = 4
(gdb) c
Continuing.
a is 4, str is test

Breakpoint 1, fun_test (a=5, str=0x5555555548e9 "test") at demo.cpp:6
6	    printf("a is %d, str is %s\n", a, str);
1: a = 5
(gdb)

如果 display 命令后面跟多个变量名,则必须要求这些变量的类型相同(比如都是整型变量)。如果长度不相同,则需要分开使用。

可以在 gdb 中输入 info display 命令来查看已经设置的自动显示的变量信息,如图所示。

(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
1:   y  a (cannot be evaluated in the current context)
(gdb) 

如果不需要某些变量自动显示,则可以使用 undisplay 编号 的方式来取消自动变量的显示。例如,想要取消上面变量 a 的自动显示,则可以在 gdb 里面输入 undisplay 1 命令,a 变量的自动显示被取消,如图所示。这样一来,再次使用 info display 命令的时候不会显示 a 的信息。

(gdb) undisplay 1
(gdb) info display
There are no auto-display expressions now.
(gdb) 

如果要取消所有变量的自动显示,可以使用 undisplay 命令来。在使用 undisplay 命令时会收到确认信息,确认是否全部取消自动显示。如果输入 y ,则取消所有的自动显示。delete display 命令也可以删除所有的自动显示。如果只想删除部分变量的自动显示,可以使用 delete display 序号 的方式。

比如要删除中的{x,a}的自动显示,则输入以下命令:

delete display 1

除了删除自动显示,还可以暂时禁用自动显示,在需要的时候可以再次启用某些变量的自动显示。比如图3-64中自动显示的编号 1 和 2,如果要暂时禁用编号为 1 的自动显示,则可以使用以下命令:

disable display 1

如果想要恢复编号为1的变量的自动显示,则可以使用以下命令来恢复:

enable display 1

2. 显示源代码

gdb 可以在调试的时候显示源代码信息。查看源代码的命令是 list 或者 l 。当程序命中断点或者暂停后可以使用 list 命令查看相关的源代码。

因为加上编译选项 -g 后,生成的可执行文件中包含调试信息,并且保存了对应的源文件信息(只是保存了源文件名等信息),所以在查看源代码时,要确保对应的源文件存在,否则无法查看。

在程序中断时,可以使用 l 命令来查看源代码信息。默认情况下,使用 l 命令可以显示10行源代码—当前代码行的前面 5 行和后面 5 行,

(gdb) c
Continuing.

Breakpoint 1, fun_test (a=7, str=0x5555555548e9 "test") at demo.cpp:6
6	    printf("a is %d, str is %s\n", a, str);
(gdb) l
1	#include <iostream>
2	#include <string>
3	
4	void fun_test(int a, const char *str)
5	{
6	    printf("a is %d, str is %s\n", a, str);
7	}
8	
9	int main(int argc, char *argv[])
10	{
(gdb) 

继续执行 l 命令,则会继续从当前行往后显示10行代码。如果执行 l- 命令,则会从当前代码行往前显示10行代码,

(gdb) l
11	    for (int i = 0; i < 10; i++)
12	    {
13	        fun_test(i, "test");
14	    }
15	}
(gdb) 

执行 l 命令时,每次默认显示 10 行代码,如果觉得每次显示的代码太少,可以通过 set listsize 命令来改变每次显示代码的行数。比如,希望每次能够显示 20 行代码,可执行下述命令:

set listsize 20

这样,每次调用 list 命令时,就会显示 20 行代码。

还可以使用 list 命令查看指定函数的代码,语法为 list 函数名 。比如,我们要查看 fun_test 函数的源代码,可以使用下述命令:

list fun_test

显示函数与显示普通代码的规则相同。

  • 受到行数的限制,比如我们刚设置了每次显示20行代码,则显示 fun_test 函数时只显示20行代码;
  • 仍然会以上下文的方式显示函数代码,即显示函数前面 10 行代码和函数后面 10 行代码;
(gdb) l fun_test
1	#include <iostream>
2	#include <string>
3	
4	void fun_test(int a, const char *str)
5	{
6	    printf("a is %d, str is %s\n", a, str);
7	}
8	
9	int main(int argc, char *argv[])
10	{
(gdb)

如果想要查看指定文件的指定行代码,则可以使用下述命令:

list 文件名:行号

如果查看的是当前文件的代码行,则可以省略文件名。比如我们要查看 demo.cpp 的第 100 行代码,则可以使用下述命令进行查看:

l 100

示例过程

(gdb) l demo.cpp:9
4	void fun_test(int a, const char *str)
5	{
6	    printf("a is %d, str is %s\n", a, str);
7	}
8	
9	int main(int argc, char *argv[])
10	{
11	    for (int i = 0; i < 10; i++)
12	    {
13	        fun_test(i, "test");
(gdb) l 13
8	
9	int main(int argc, char *argv[])
10	{
11	    for (int i = 0; i < 10; i++)
12	    {
13	        fun_test(i, "test");
14	    }
15	}
(gdb) 

3. watch

watch 可以用来监视一个变量或者一段内存,当这个变量或者该内存处的值发生变化时,GDB 就会中断下来。被监视的某个变量或者某个内存地址会产生一个 watch point(观察点)。

适用场景:
有一个变量其值被意外地改掉了,通过单步调试或者挨个检查使用该变量的代码工作量会非常大,如何快速地定位到该变量在哪里被修改了?其实使用 watch 命令就可以通过添加硬件断点来达到监视数据变化的目的。

watch 命令的使用方式是 watch 变量名或内存地址 ,一般有以下几种形式:

  • 形式一:整型变量
int i;
watch i
  • 形式二:指针类型
char *p;
watch p 与 watch *p

注意:watch pwatch *p 是有区别的,前者是查看 *(&p),是 p 变量本身;后者是 p 所指内存的内容。我们需要查看地址,因为目的是要看某内存地址上的数据是怎样变化的。

  • 形式三:watch 一个数组或内存区间
char buf[128];
watch buf

这里是对 buf 的 128 个数据进行了监视,此时不是采用硬件断点,而是用软中断实现的。用软中断方式去检查内存变量是比较耗费 CPU 资源的,精确地指明地址是硬件中断。

注意:当设置的观察点是一个局部变量时,局部变量无效后,观察点也会失效。在观察点失效时 GDB 可能会提示如下信息:

Watchpoint 2 deleted because the program has left the block in which its expression is valid.

4. 查看内存

test_memory 函数内容
120定义了字符串类型、int 变量和一个结构体,使用 gdb 启动调试,执行 b test_memory 命令为test_memory 函数设置一个断点,或者直接在 124 行设置一个断点。断点命中时,使用 x 命令查看各个变量的内存信息。x 命令的语法如下:

x /选项 地址

先查看字符串变量 str 的内存信息。执行 x str ,默认以十六进制显示。由于 str 是字符串,所以也可以使用字符串的方式查看。使用命令 x /s str 还可以以十进制方式查看、设定显示的宽度等,如图所示。
71以十六进制方式查看时,显示的内存内容为0x74736574,对应的字符分别为t、s、e、t,即在内存中存储的内容刚好与我们看到的情况相反。如果以 x /s str 的方式查看,则会直接显示字符串的内容。

再来查看 int 型变量 number 在内存中的信息。因为 number 不是一个指针,所以我们要首先找到它的地址。可以使用 p &number 命令来查看 number 对应的地址,然后再使用 x 来查看 number 地址对应的内存数据。当然也可以直接使用 x &number 的方式来查看 number 地址对应的内存信息,如图所示。
72在代码中,我们为 number 赋值为 0x12345678 ,但是内存中显示的却是 0x78563412 ,原因是字节在x86 架构中是按照小端方式存储的,第2章中已经介绍过。小端存储是指字节序数据的尾端数据存放在低地址部分,所以与我们看到的顺序刚好相反。

变量node存储的数据是一个结构体类型。来看看node在内存中到底是如何存储的。在gdb中输入命令x/16s node,如图3-73所示。
73从图中可以看到,node在内存中的存储顺序与结构体中声明成员的顺序一致,即按照性别、ID和姓名来存储。显示性别的起始地址是0x614e70,显示ID的起始地址是0x614e74,“d”对应的是十进制的100。可以发现,ID的起始地址和性别的起始地址相差4,但是我们定义gender成员时使用的是char gender[3],明明只声明了3字节,最后却在内存中占用了4字节。

这是因为结构体在内存中会进行对齐和补齐操作,默认是按照4字节对齐。尽管声明的是3字节,但是要按照4字节去对齐,所以需要补齐1字节,这导致gender占用了4字节空间。同样,成员Name也会补齐到8字节,所以整个结构体在内存中会占用16字节,可以使用p sizeof(TEST_NODE)命令进行查看。在图3-73中也可以发现,整个结构体的大小是16字节。

命令x并不局限于查看变量的内存信息,无论是函数地址、变量地址,还是其他地址,只要地址合法而且可以访问,都可以使用 x 命令来查看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wohu007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值