博客园  :: 首页  :: 新随笔  :: 订阅 订阅  :: 管理

GDB查看变量值 print和display

Posted on 2020-10-19 19:29  面具下的戏命师  阅读(5914)  评论(0编辑  收藏  举报

GDB查看变量值 print和display

GDB 调试程序,最常用的方法是:单步调试或者断点调试程序,期间通过查看某个变量或者表达式的值,判断当前程序的执行过程是否正确,不断缩小异常或 Bug 位于代码中的范围,最终找到并修复。
对于在调试期间查看某个变量或表达式的值,GDB 调试器提供有 2 种方法,即使用 print 命令或者 display 命令。本节就对这 2 个命令的功能和用法做详细的讲解,整个讲解过程将以调试如下 C 语言程序为例:

#include <stdio.h>
int main(){
    int num,result=0,i=0;
    scanf("%d", &num);
    while(i<=num){
        result += i;
        i++;
    }  
    printf("result=%d\n", result);
    return 0;
}

GDB print命令

rint 命令的功能就是在 GDB 调试程序的过程中,输出或者修改指定变量或者表达式的值。print 命令可以缩写为 p,最常用的语法格式如下所示:

(gdb) print num
(gdb) p num

其中,参数 num 用来代指要查看或者修改的目标变量或者表达式。

示例:

[root@all c]# gdb main -q
Reading symbols from /root/c/main...done.
(gdb) l
1    #include <stdio.h>
2    int main(){
3        int num,result=0,i=0;
4        scanf("%d", &num);
5        while(i<=num){
6            result += i;
7            i++;
8        }  
9        printf("result=%d\n", result);
10        return 0;
(gdb) 
11    }
(gdb) b 4
Breakpoint 1 at 0x40054a: file main.c, line 4.
(gdb) r
Starting program: /root/c/main 

Breakpoint 1, main () at main.c:4
4        scanf("%d", &num);
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.149.el6.x86_64
(gdb) n
3
5        while(i<=num){
(gdb) p num
$1 = 3
(gdb) p num=4 
$2 = 4
(gdb) b 9
Breakpoint 2 at 0x400577: file main.c, line 9.
(gdb) c
Continuing.

Breakpoint 2, main () at main.c:9
9        printf("result=%d\n", result);
(gdb) p result
$3 = 10
(gdb) p result=20
$4 = 20

可以看到,调试 main 的过程中,我们先后使用 p 命令输出了程序中 num 和 result 变量的值,同时还使用该命令对它们的值做了修改。

GDB display命令

和 print 命令一样,display 命令也用于调试阶段查看某个变量或表达式的值,它们的区别是,使用 display 命令查看变量或表达式的值,每当程序暂停执行(例如单步执行)时,GDB 调试器都会自动帮我们打印出来,而 print 命令则不会。

display 命令没有缩写形式,常用的语法格式如下 2 种:

(gdb) display expr
(gdb) display/fmt expr

其中,expr 表示要查看的目标变量或表达式;参数 fmt 用于指定输出变量或表达式的格式,表 1 罗列了常用的一些 fmt 参数。

表 1 /fmt 常用的值
/fmt功 能
/x 以十六进制的形式打印出整数。
/d 以有符号、十进制的形式打印出整数。
/u 以无符号、十进制的形式打印出整数。
/o 以八进制的形式打印出整数。
/t 以二进制的形式打印出整数。
/f 以浮点数的形式打印变量或表达式的值。
/c 以字符形式打印变量或表达式的值。

注意,display 命令和 /fmt 之间不要留有空格。以 /x 为例,应写为 (gdb)display/x expr。

仍以main程序为例:

[root@all c]# gdb main -q
Reading symbols from /root/c/main...done.
(gdb) b 4 
Breakpoint 1 at 0x40054a: file main.c, line 4.
(gdb) b 9
Breakpoint 2 at 0x400577: file main.c, line 9.
(gdb) r
Starting program: /root/c/main 

Breakpoint 1, main () at main.c:4
4        scanf("%d", &num);
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.149.el6.x86_64
(gdb) display num
1: num = 32767
(gdb) display/t result
2: /t result = 0
(gdb) n
3
5        while(i<=num){
2: /t result = 0
1: num = 3
(gdb) c
Continuing.

Breakpoint 2, main () at main.c:9
9        printf("result=%d\n", result);
2: /t result = 110
1: num = 3
(gdb) c
Continuing.
result=6

Program exited normally.

可以看到,使用 display 命令查看 num 和 result 变量值时,不仅在执行该命令的同时会看到目标变量的值,后续每次程序停止执行时,GDB 调试器都会将目标变量的值打印出来。

事实上,对于使用 display 命令查看的目标变量或表达式,都会被记录在一张列表(称为自动显示列表)中。通过执行info dispaly命令,可以打印出这张表:

(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
2:      y      /t result
1:      y      num

其中,各列的含义为:

  • Num 列为各变量或表达式的编号,GDB 调试器为每个变量或表达式都分配有唯一的编号;
  • Enb 列表示当前各个变量(表达式)是处于激活状态还是禁用状态,如果处于激活状态(用 y 表示),则每次程序停止执行,该变量的值都会被打印出来;反之,如果处于禁用状态(用 n 表示),则该变量(表达式)的值不会被打印。
  • Expression 列:表示查看的变量或表达式。

对于不需要再打印值的变量或表达式,可以将其删除或者禁用。
1) 通过执行如下命令,即可删除自动显示列表中的变量或表达式:

(gdb) undisplay num...
(gdb) delete display num...

参数 num... 表示目标变量或表达式的编号,编号的个数可以是多个。
举个例子:

(gdb) undisplay 1
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
2:      y      /t result
(gdb)

可以看到,编号为 2 的 result 变量的 Enb 由 y 变成了 n。处于禁用状态的变量或表达式,程序停止执行时将不再自动打印出它们的值。
当然根据需要,也可以激活当前处于禁用状态的变量或表达式,执行如下命令即可:

(gdb) enable display num...

参数 num... 表示要激活的变量或表达式的编号,编号的个数可以是多个,表示一次性激活多个变量或表达式。
举个例子:

(gdb) enable display 2
(gdb) info display
Auto-display expressions now in effect:
Num Enb Expression
2:   y  /t result
(gdb) 

总的来说,每次程序停止执行时,GDB 调试器会将自动显示列表中处于激活状态下的变量或表达式的值打印出来,display 命令可以实现在查看目标变量或表达式的值的同时,将其添加到自动显示列表中,而 print 命令则只会打印出目标变量或表达式的值。

print命令的高级用法

print 命令还有更高级的功能和用法,例如以指定的格式输出变量或者表达式的值、输出数组中指定区间内的所有元素等等。和 print 命令最基本的用法相比,该命令的完整语法格式如下所示:

(gdb) print [options --] [/fmt] expr

格式中用 [ ] 括起来的部分是可选的,可以使用也可以省略。其中,各个参数的含义如下:

  1. options:表示该命令所支持的选项,这些选项可以控制 print 命令输出指定内容的变量或者表达式的值;
  2. fmt:指定输出变量或表达式值时所采用的格式;
  3. expr:指定要查看的变量或表达式。

1) 首先介绍 options 参数的用法,表 1 罗列了常用的几个 options 参数值。

表 1 print options参数的取值
options 参数功 能
-address on|off 查看某一指针变量的值时,是否同时打印其占用的内存地址,默认值为 on。该选项等同于单独执行 set print address on|off 命令。
-array on|off 是否以便于阅读的格式输出数组中的元素,默认值为 off。该选项等同于单独执行 set printf array on|off 命令。
-array-indexes on|off 对于非字符类型数组,在打印数组中每个元素值的同时,是否同时显示每个元素对应的数组下标,默认值为 off。该选项等同于单独执行 set print array-indexes on|off 命令。
-pretty on|off 以便于阅读的格式打印某个结构体变量的值,默认值为 off。该选项等同于单独执行 set print pretty on|off 命令。

注意,options 参数和 /fmt 或者 expr 之间,必须用--( 2 个 - 字符)分隔。此外,options 参数还有很多选项可以使用。

2) 和 display 命令一样,print 命令可允许自定义输出格式,表 2 罗列了几个常用的 /fmt 参数。

表 2 /fmt 常用的值
/fmt功 能
/x 以十六进制的形式打印出整数。
/d 以有符号、十进制的形式打印出整数。
/u 以无符号、十进制的形式打印出整数。
/o 以八进制的形式打印出整数。
/t 以二进制的形式打印出整数。
/f 以浮点数的形式打印变量或表达式的值。
/c 以字符形式打印变量或表达式的值。

当 print 命令不指定任何 options 参数时,print 和 /fmt 之间不用添加空格,例如以十六进制的形式输出 num 整形变量的值,执行命令为 (gdb) print/x num。

3) 我们知道,print 命令可以打印指定变量或表达式的值。当指定目标表达式时,除了表达式本身外,GDB 调试器还支持使用@::运算符。

@运算符用于输出数组中指定区域的元素,使用格式如下:

(gdb) print first@len

其中,参数 first 用于指定数组查看区域内的首个元素的值;参数 len 用于指令自 first 元素开始查看的元素个数。
假设有一个 array 数组,其定义如下:

int array[5] = {1,2,3,4};

如果我们想查看第 1 个元素和第 2 个元素的值,可以执行如下指令:

(gdb) print array[0]@2
$1 = {1, 2}

当程序中包含多个作用域不同但名称相同的变量或表达式时,可以借助::运算符明确指定要查看的目标变量或表达式。::运算符的语法格式如下:

(gdb) print file::variable
(gdb) print function::variable

其中 file 用于指定具体的文件名,funciton 用于指定具体所在函数的函数名,variable 表示要查看的目标变量或表达式。
举个例子:

#include <stdio.h>
int num = 10;
int main(){
    int num = 20;
    return 0;
}

假设该程序存储在 main.c 文件中,则使用 GDB 调试至第 5 行(return 0)处暂停后,通过执行如下命令,即可查看 num 全局变量的值:

(gdb) print 'main.c'::num
$1 = 10

而通过执行如下命令,可以查看 num 局部变量的值:

(gdb) p main::num
$1 = 20

当然,由于 GDB 调试就暂停在 main() 函数中,因此即便不指明main::,这里的 num 默认指代的也是 num 局部变量。
为了让读者彻底搞清楚 print 命令的用法,这里以一段 C 语言程序为例,为大家演示以上所讲 print 命令的用法:

(gdb) l
1 #include <stdio.h>
2 typedef struct website{
3     char *url;
4     int time;
5 }web;
6 int num = 10;
7 int main(){
8     int num = 20;
9     int array[10]={1,2,3,4,5,6};
10     web LanguageC = {"http://c.biancheng.net",6};
(gdb)
11     return 0;
12 }
(gdb) b 11
Breakpoint 1 at 0x11cf: file main.c, line 11.
(gdb) r
Starting program: ~/demo/main.exe

Breakpoint 1, main () at main.c:11
11     return 0;
(gdb) print -address on -- LanguageC.url                      <-- 打印 url 指针的同时,输出其所在的内存地址
$1 = 0x555555556004 "http://c.biancheng.net"
(gdb) print -address off -- LanguageC.url                      <-- 打印 url 指针,但不输出其所在的内存地址
$2 = "http://c.biancheng.net"
(gdb) print -pretty on -- LanguageC                               <-- 以便于阅读的方式,输出结构体的值
$3 = {
  url = 0x555555556004 "http://c.biancheng.net",
  time = 6
}
(gdb) print LanguageC                                                    <-- 以压缩的格式输出结构体的值
$4 = {url = 0x555555556004 "http://c.biancheng.net", time = 6}
(gdb) print/x num                                                            <-- 以十六进制的形式输出局部变量 num 的值
$5 = 0x14
(gdb) print array[1]@2                                                     <-- 从 array[1] 处开始,输出 array 数组中后续的 2 个元素的值
$6 = {2, 3}
(gdb) print num                                                                <-- 输出局部变量 num 的值
$7 = 20
(gdb) print 'main.c'::num                                                  <-- 输出全局变量 num 的值
$8 = 10
(gdb)