• GDB调试指南-变量查看


    前言

    在启动调试以及设置断点之后,就到了我们非常关键的一步-查看变量。GDB调试最大的目的之一就是走查代码,查看运行结果是否符合预期。既然如此,我们就不得不了解一些查看各种类型变量的方法,以帮助我们进一步定位问题。

    准备工作

    在查看变量之前,需要先启动调试并设置断点,该部分内容可参考《GDB调试指南-启动调试》和《GDB调试指南-断点设置》。后面的内容都基于在某个位置已经断住。

    本文辅助说明程序如下:
    testGdb.c

    //testGdb.c
    #include<stdio.h>
    #include<stdlib.h>
    #include"testGdb.h"
    int main(void)
    {
        int a = 10; //整型
        int b[] = {1,2,3,5};  //数组
        char c[] = "hello,shouwang";//字符数组
        /*申请内存,失败时退出*/    
        int *d = (int*)malloc(a*sizeof(int));
        if(NULL == d)
        {
            printf("malloc error ");
            return -1;
        }
        /*赋值*/
        for(int i=0; i < 10;i++)
        {
            d[i] = i;
        }
        free(d);
        d = NULL;
        float e = 8.5f;
        return 0;
    }

    testGdb.h

    int a = 11;

    编译:

    $ gcc -g -o testGdb testGdb.o

    变量查看

    打印基本类型变量,数组,字符数组

    最常见的使用便是使用print(可简写为p)打印变量内容。
    例如,打印基本类型,数组,字符数组等直接使用p 变量名即可:

    (gdb) p a
    $1 = 10
    (gdb) p b
    $2 = {1, 2, 3, 5}
    (gdb) p c
    $3 = "hello,shouwang"
    (gdb) 

    当然有时候,多个函数或者多个文件会有同一个变量名,这个时候可以在前面加上文件名或者函数名来区分:

    (gdb) p 'testGdb.h'::a
    $1 = 11
    (gdb) p 'main'::b
    $2 = {1, 2, 3, 5}
    (gdb) 

    这里所打印的a值是我们定义在testGdb.h文件里的,而b值是main函数中的b。

    打印指针指向内容

    如果还是使用上面的方式打印指针指向的内容,那么打印出来的只是指针地址而已,例如:

    (gdb) p d
    $1 = (int *) 0x602010
    (gdb) 

    而如果想要打印指针指向的内容,需要解引用:

    (gdb) p *d
    $2 = 0
    (gdb) p *d@10
    $3 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    (gdb) 

    从上面可以看到,仅仅使用*只能打印第一个值,如果要打印多个值,后面跟上@并加上要打印的长度。
    或者@后面跟上变量值:

    (gdb) p *d@a
    $2 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    (gdb) 

    由于a的值为10,并且是作为整型指针数据长度,因此后面可以直接跟着a,也可以打印出所有内容。

    另外值得一提的是,$可表示上一个变量,而假设此时有一个链表linkNode,它有next成员代表下一个节点,则可使用下面方式不断打印链表内容:

    (gdb) p *linkNode
    (这里显示linkNode节点内容)
    (gdb) p *$.next
    (这里显示linkNode节点下一个节点的内容)

    如果想要查看前面数组的内容,你可以将下标一个一个累加,还可以定义一个类似UNIX环境变量,例如:

    (gdb) set $index=0
    (gdb) p b[$index++]
    $11 = 1
    (gdb) p b[$index++]
    $12 = 2
    (gdb) p b[$index++]
    $13 = 3

    这样就不需要每次修改下标去打印啦。

    按照特定格式打印变量

    对于简单的数据,print默认的打印方式已经足够了,它会根据变量类型的格式打印出来,但是有时候这还不够,我们需要更多的格式控制。常见格式控制字符如下:

    • x 按十六进制格式显示变量。
    • d 按十进制格式显示变量。
    • u 按十六进制格式显示无符号整型。
    • o 按八进制格式显示变量。
    • t 按二进制格式显示变量。
    • a 按十六进制格式显示变量。
    • c 按字符格式显示变量。
    • f 按浮点数格式显示变量。

    还是以辅助程序来说明,正常方式打印字符数组c:

    (gdb) p c
    $18 = "hello,shouwang"

    但是如果我们要查看它的十六进制格式打印呢?

    (gdb) p/x c
    $19 = {0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x73, 0x68, 0x6f, 0x75, 0x77, 0x61, 
      0x6e, 0x67, 0x0}
    (gdb)

    但是如果我们想用这种方式查看浮点数的二进制格式是怎样的是不行的,因为直接打印它首先会被转换成整型,因此最终会得到8:

    (gdb) p e
    $1 = 8.5
    (gdb) p/t e
    $2 = 1000
    (gdb) 

    那么就需要另外一种查看方式了。

    查看内存内容

    examine(简写为x)可以用来查看内存地址中的值。语法如下:

    x/[n][f][u] addr

    其中:

    • n 表示要显示的内存单元数,默认值为1
    • f 表示要打印的格式,前面已经提到了格式控制字符
    • u 要打印的单元长度
    • addr 内存地址

    单元类型常见有如下:

    • b 字节
    • h 半字,即双字节
    • w 字,即四字节
    • g 八字节

    我们通过一个实例来看,假如我们要把float变量e按照二进制方式打印,并且打印单位是一字节:

    (gdb) x/4tb &e
    0x7fffffffdbd4:    00000000    00000000    00001000    01000001
    (gdb) 

    可以看到,变量e的四个字节都以二进制的方式打印出来了。

    自动显示变量内容

    假设我们希望程序断住时,就显示某个变量的值,可以使用display命令。

    (gdb) display e
    1: e = 8.5

    那么每次程序断住时,就会打印e的值。要查看哪些变量被设置了display,可以使用:

    (gdb)info display
    Auto-display expressions now in effect:
    Num Enb Expression
    1:   y  b
    2:   y  e

    如果想要清除可以使用

    delete display num #num为前面变量前的编号,不带num时清除所有。

    或者去使能:

    disable display num  #num为前面变量前的编号,不带num时去使能所有

    微信公众号【编程珠玑】:专注但不限于分享计算机编程基础,Linux,C语言,C++,算法,数据库等编程相关[原创]技术文章,号内包含大量经典电子书和视频学习资源。欢迎一起交流学习,一起修炼计算机“内功”,知其然,更知其所以然。

    公众号编程珠玑公众号编程珠玑

    查看寄存器内容

    (gdb)info registers
    rax            0x0    0
    rbx            0x0    0
    rcx            0x7ffff7dd1b00    140737351850752
    rdx            0x0    0
    rsi            0x7ffff7dd1b30    140737351850800
    rdi            0xffffffff    4294967295
    rbp            0x7fffffffdc10    0x7fffffffdc10
    (内容过多未显示完全)

    总结

    通过不同方式查看变量值或者内存值能够极大的帮助我们判断程序的运行是否符合我们的预期,如果发现观察的值不是我们预期的时候,就需要检查我们的代码了。

  • 相关阅读:
    解决spring boot JavaMailSender部分收件人错误导致发送失败的问题
    Linux设备驱动开发基础--内核定时器
    Linux中断分层--工作队列
    Linux中断分层--软中断和tasklet
    深入理解函数线程安全与可重入
    Linux中断处理流程
    Linux混杂设备驱动--按键设备驱动
    Linux字符设备驱动--Led设备驱动
    Linux字符设备简单示例
    Linux内核硬件访问技术
  • 原文地址:https://www.cnblogs.com/bianchengzhuji/p/10526176.html
Copyright © 2020-2023  润新知