• Debugging with GDB: 查看数据


    Debugging with GDB: 查看数据

    程序变量

    在C/C++中,变量遵守作用域规则,有不同的生存期、链接类型。不同的函数中可能有相同的变量名,不同的文件中也可能有相同的文件名,必须无歧义地向GDB制定变量名。

    具有外部链接的符号

    非static的全局变量具有外部链接,全局只有一个,所以可以直接使用print命令打印。

    比如打印全局变量global

    p global
    

    具有内部链接的符号

    非static局部变量

    非static局部变量只在作用域内可见、生存期也只在作用域内,只有变量在作用域内时才能使用GDB查看。

    同名局部变量可以出现在不同函数中,因此在查看局部变量时要指定栈帧,如果不指定就使用当前栈帧。

    指定变量的方法为:

    • function::variable
    • 使用frame命令切换到变量查看栈帧,再使用print

    比如以下程序varaible.c

    void func2(int x)
    {
    	x = 100;
    }
    
    void func1(int x) {
    	x += 5;
    	func2(x);
    }
    
    int 
    main(int argc, char *argv[])
    {
    	int x = 20;
    	func1(x);
    	return 0;
    }
    
    
    

    编译:

    gcc -O0 -ggdb -o variable variable.c
    

    使用GDB调试,在进入func1main中的变量x

    Breakpoint 1, func1 (x=20) at /home/GDB/variable.c:7
    7               x += 5;
    (gdb) n
    8               func2(x);
    (gdb) p x
    $2 = 25
    (gdb) p main::x
    $3 = 20
    (gdb) bt
    #0  func1 (x=25) at /home/kongjun/c_practice/variable.c:8
    #1  0x0000555555555172 in main (argc=1, argv=0x7fffffffdfc8) at /home/GDB/variable.c:15
    (gdb) f 1
    #1  0x0000555555555172 in main (argc=1, argv=0x7fffffffdfc8) at /home/GDB/variable.c:15
    15              func1(x);
    (gdb) p x
    $4 = 20
    
    

    要避免在刚进入栈帧或即将退出栈帧时查看非static局部变量。在机器指令层面,建立栈帧和初始化变量一般由多条指令完成,如果在这时查看变量,很可能变量的值还没有被设置好,查看到错误的值,退出栈帧时同理。

    static变量

    static的全局变量和局部变量具有内部链接,一般存储在.data段或.bss段中。虽然static变量也只在作用域内可见,但是却存在于整个程序运行期间,鉴于这个特性,GDB允许在static局部变量所在作用域内查看变量。

    不同文件中可能有同名的static变量,所以必须指明变量所在的文件或函数。指示变量位置的方法如下:

    • function::variable
    • file::variable'file'::variable

    使用'file'::variable可以避免文件名和函数名冲突的情况。

    注意事项

    在编译程序时,编译器很可能会对变量或它所在的函数进行优化,比如消除不必要的函数调用、将循环,等,这是可能无法查看到变量真正的值,甚至无法查看到变量。上面的variable.c就是一个例子,变量x并没有真的被使用,在使用O2级别的优化编译时,变量x和函数func1func2会被直接消除,main直接返回。

    如果要查看变量的真实值,要么禁止优化,要么使用某些支持查看变量真实值的调试格式。

    在使用::记号时要小心和C++中的作用域运算符冲突,如果真的发生了冲突,GDB优先使用C++作用域运算符的语义。

    查看数组的值

    对于数组类型,GDB分析它的符号信息后获取了它的起始地址、长度,可以直接使用print命令查看。某些指针指向某个元素,但它实际上代表一个数组,GDB不知道它代表一个数组,无法直接使用print命令查看,这时需要我们手动地将其转换为数组。方法如下:

    • 使用类型转换
    • element@lenelement代表数组第一个元素,@len代表数组元素个数。

    arry.c

    int 
    main(int argc, char *argv[])
    {
    	int arry[] = {1, 2, 3, 4, 5, 6};
    	int *p = arry;
    
    	return 0;
    }
    
    

    使用GDB调试:

    (gdb) n
    6               int *p = arry;
    (gdb) p arry
    $2 = {1, 2, 3, 4, 5, 6}
    (gdb) n
    8               return 0;
    (gdb) p p
    $3 = (int *) 0x7fffffffdec0
    (gdb) p *p
    $4 = 1
    (gdb) p *p@6
    $5 = {1, 2, 3, 4, 5, 6}
    (gdb) p *p@9
    $6 = {1, 2, 3, 4, 5, 6, -8512, 32767, 1431654768}
    
    

    指针p指向数组第一个元素,实际上代表数组arry,但是被指向对象的类型却是int,GDB无法知道它代表数组。

    查看/查找内存

    查看内存指令为examine(x)。格式为:x /nfu,其中n是要重复的次数,f是打印格式,u是单元大小。具体的值参考Examining data

    GDB 除了查看内存,还提供了在内存中查找某个值的命令find。语法为:

    find [/sn] start_addr, +len, val1 [, val2, …]
    find [/sn] start_addr, end_addr, val1 [, val2, …]
    

    其中s是查找的变量的大小(b,h,w,g),n是匹配的最大个数。find命令可以根据查找的变量类型自动判断大小,因此不需要指定s,直接find即可,这样可以实现查找不同大小的变量。

    GDB 提供了变量$_,可以通过它访问到find匹配到的最后一个地址。

    程序如下:

    void
    hello ()
    {
      static char hello[] = "hello-hello";
      static struct { char c; short s; int i; }
        __attribute__ ((packed)) mixed
        = { 'c', 0x1234, 0x87654321 };
      printf ("%s
    ", hello);
    }
    

    GDB 调试:

    (gdb) find &hello[0], +sizeof(hello), "hello"
    0x804956d <hello.1620+6>
    1 pattern found
    (gdb) find &hello[0], +sizeof(hello), 'h', 'e', 'l', 'l', 'o'
    0x8049567 <hello.1620>
    0x804956d <hello.1620+6>
    2 patterns found.
    (gdb) find &hello[0], +sizeof(hello), {char[5]}"hello"
    0x8049567 <hello.1620>
    0x804956d <hello.1620+6>
    2 patterns found.
    (gdb) find /b1 &hello[0], +sizeof(hello), 'h', 0x65, 'l'
    0x8049567 <hello.1620>
    1 pattern found
    (gdb) find &mixed, +sizeof(mixed), (char) 'c', (short) 0x1234, (int) 0x87654321
    0x8049560 <mixed.1625>
    1 pattern found
    (gdb) print $numfound
    $1 = 1
    (gdb) print $_
    $2 = (void *) 0x8049560
    

    变量历史

    ​ 每次使用print打印变量的值,都会将该次打印的值记录在变量历史中,每次打印都有一个编号,这就是为什么print会输出类似$1 = 100的原因,其中$1中的1就是变量历史编号。

    可以使用show variables查看近 10 次变量历史,show variables n查看以历史编号 n 为中心的变量历史,show variables +打印上次print之前的 10 此变量历史。

    也可以使用$$$$N$N来打印变量历史中的值。$表示最近一次print$$表示上上次print$N表示编号为N的变量历史,$$N表示从上次print开始的变量历史号。如$$0等同于$(上次print),$$2前 3 次print

    convenience variable

    convenience variable 主要有两个用途:

    • 访问某些 GDB 记录的信息
    • 定义变量利用 GDB 命令实现自己需要的功能

    先介绍 GDB 记录的信息:

    • $_exitsignal: 杀死进程的信号。函数$_isvoid()可以判断进程是否被信号杀死。

    • $_exitcode: 进程退出码

    • $_thread: 进程编号

    • $_gthread: 全局进程编号

    还有其他一些变量,可以参考 GDB 使用手册。

    还可以利用 convenience variable 自动化一些操作。比如有一个指针数组,其中的指针指向对象,现在需要查看数组中指针指向的对象,使用 convenience variable 可以轻易完成这个任务:

    set $i = 0
    p *array[$i++]
    

    然后按不断按回车即可遍历数组并查看指针指向的对象。

    寄存器

    info registers查看通用寄存器,info all-registers查看包括浮点数寄存器在内的寄存器。

    print $<register>可以查看寄存器register的值,如访问 RISCV 寄存器 a0:print $a0

    将内存/变量拷贝到文件中

    有时可能想要比较多次运行过程中某变量或某块内存的值,GDB 没有直接提供这种功能,这时可以将内存/变量拷贝到文件中再比较。

    dump [format] memory filename start_addr end_addr
    dump [format] value filename expr
    append [binary] memory filename start_addr end_addr
    append [binary] value filename expr
    restore filename [binary] bias start end
    

    dump命令将内存/变量写入到文件filename中,append将内存/变量附加到文件中,restore命令将文件中的内容恢复到内存中。

    文件格式必须是以下之一:

    • binary

      Raw binary form.

    • ihex

      Intel hex format.

    • srec

      Motorola S-record format.

    • tekhex

      Tektronix Hex format.

    • verilog

      Verilog Hex format.

    如果不指定格式,dumpappend默认写入 binary 数据,实际上append暂时只支持 binary。restore可以自动判断格式,但由于 binary 无格式,必须手动指定。其他几种格式文件中以及记录了固定的地址,restore时不需要指定bias,binary 文件地址总是从 0 开始,因此restore filename binary bias start end实际上会将文件中的内容写入进程地址bias

    生成 core file

    generate-core-file [file]gcore [file]生成 core dump,如果不指定文件名,生成的文件名为core.pid

    改善 GDB 的输出

    GDB 默认print输出是最简的,部分内容不会被打印,并且没有格式化。可以通过设置一定的选项,改善输出,这里列出几个我个人觉得比较有用的选项:

    • set print object on: 当打印指向对象的指针时,显示其真实类型
    • set print array on: 用更好的格式打印数组,但是需要占用更多空间
    • set print pretty on: 打印结构体/类时使用缩进
    • set print vtbl on: 打印 C++ 虚函数表
  • 相关阅读:
    chart控件多个ChartArea
    winform chart画折线,波形图,多条数据
    C# Chart 折线图 多条数据展示
    task一个任务结束后执行另一个操作
    C#多线程同步 读写锁ReaderWriterLock的用法
    C# 多线程文件读写整理总结
    vue解决跨域问题
    接前端页面
    使用vue+zrender绘制体温单 三测单(2)
    使用vue+zrender绘制体温单 三测单(1)
  • 原文地址:https://www.cnblogs.com/kongj/p/14415579.html
Copyright © 2020-2023  润新知