调试器GDB的基本使用方式(一)

GDB的功能及其丰富,我们按照调试的流程进行说明。基本用法很简单。

流程如下所示:

  1. 带着调试选项编译、构建调试对象。
  2. 启动调试器(GDB)
    1. 设置断点。
    2. 显示栈帧。
    3. 显示值。
    4. 继续执行。

一、准备

        通过 gcc 的 -g 选项生成调试信息

        gcc -Wall -O2 -g 源文件

        如果使用Makefile 构建,一般要给CFLAGS中指定 -g 选项。

        CFLAGS = -Wall -O2 -g  

        如果用configure 脚本生成 Makefile,可以这样用。

        ./configure CFLAGS=-Wall -O2 -g  

        构建方法通常会写在 INSTALL、README 等文件中,参考即可。

        编译器含有针对源代码中的各种各样的错误输出信息的功能,称为警告选项(Warning Option)。这些信息并一定是错误,但却指出了容易引发bug的编码方式。

        要用心写出整洁的代码,编译时不要出现警告甚至错误信息。编译错误是bug的最大来源。

        -Werror 选项可以在警告发生时,将其当作错误来处理。

        此外,发生编译器错误时不会生成可执行文件。

        

        编译器GCC加上优化选项后,实际的执行顺序可能由于优化与源代码顺序不同,因此利用调试器跟踪运行时,有时会执行到莫名其妙的地方,从而造成混乱。比如内联(inline)函数优化(去掉函数调用,而将函数代码在调用处展开),该函数名上就无法设置断点。这是因为内联优化从目标文件中去掉了该函数的入口点,符号表中也没有改函数的名称。

        优化还会将局部变量保存到寄存器中,因此无法显示该局部变量的内容,必须直接查看寄存器的值。由于这些副作用,有些人建议在调试时去掉优化选项进行编译和构建,但是我们不推荐这样做。

        为什么呢?

        使用C、C++ 这些过程式编程语言编程,就是利用编译器这个工具把我们期待的行为告诉计算机。尽管无需详细了解编译器优化选项的方方面面,但至少应当知道,优化编译选项可能会让执行顺序和源代码顺序不同。而优化选项就是在理解这一点的基础之上添加的,用来加快只想速度,所以没有必要特意去除。

        如果只是调试时去掉优化选项,那就必须管理有优化编译选项和无优化编译选项两种可执行文件。管理对象增加会导致管理成本增加,并不是好事。这会消耗大量成本,比如花费大量时间对没有优化选项的可执行文件进行调试,但实际上优化后的可执行文件中的bug并不存在;那么怎么管理同一源代码进行编译、构建出不同可执行文件呢?

        而且准备两个可执行文件的话,测试工作量肯定回变成两倍,管理成本也会上升。

二、启动

        gdb 可执行文件名

        通过emacs启动的方法是M-x gdb。

        启动后显示下述信息,出现gdb提示符。

skyesysi@VM-8-95-centos ~/hack_test]$ gdb a.out 
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.tl2
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /data/home/skyesysi/hack_test/a.out...done.
(gdb) 

设置断点

        可以在函数名和行号等上设置断点,程序运行后,到达断点就会自动暂停运行。此时可以查看该时刻的变量值,显示栈帧、重新设置断点或重新运行等。断点命令(break)可以简写为b。

格式:

        break        断点

        (gdb) b main
        Breakpoint 1 at 0x400400: file assemble.c, line 17.

断点可以通过函数名、当前文件内的行号来设置,也可以先制定文件名再指定行号,还可以指定与暂停位置的偏移量,或者用地址来设置。

格式:

        break        函数名

        break        行号

        break        文件名:行号

        break        文件名:函数名

        break        +偏移量

        break        - 偏移量

        break        *地址

【例】

上面例子中,分别对iseq_compile()函数、compile.c的516行、暂停往后3行、地址(0x8116d6)设置断点。

如果不指定断点位置,就在下一行代码上设置断点。

设置好的断点可以通过info break 确认。

运行

        用 run 命令开始运行,不加参数执行run,就会执行到设置断点的位置后暂停执行,可以简写为r。

格式:

        run  参数

 经常遇到的操作是在main() 上设置断点,然后执行到main() 函数,start 命令能达到同样的效果。

格式:

        start 

显示栈帧

        backtrace命令可以在遇到断点而暂停执行时显示栈帧。该命令简写为bt。次啊外,backtrace 的别名还有 where 和 info stack(简写为info s)。

格式:
        backtrace 

        bt

显示所有栈桢

        backtrace N

        bt N

只显示开头N个栈桢

        backtrace -N

        bt -N

只显示最后N个栈桢

        backtrace full 

        bt full

        backtrace full N

        bt full N

        backtrace full -N

        bt full -N

不仅显示backtrace,还要显示局部变量。

【例】

     

【例:只显示前三个栈帧】

【例:从外向内显示3个栈帧,及其局部变量】

        显示栈帧之后,就可以看到程序在何处停止(即断点位置),以及程序的调用路径。

 显示变量

        print 命令可以显示变量。print 可以简写为p。

格式:

        print 变量

        该例显示 argv[]。可以看出,argv[0] 中为可执行文件名(/home/hyoshiok/coreutils/src/uname),argv[1]中为第一个选项(-a)。

显示寄存器

        info registers 可以显示寄存器,简写为info reg。

(gdb)  info reg
rax            0x0                 0
rbx            0x4f3c020           83083296
rcx            0x0                 0
rdx            0x0                 0
rsi            0x305c598           50709912
rdi            0x0                 0
rbp            0x7f012b357870      0x7f012b357870
rsp            0x7f012b357860      0x7f012b357860
r8             0xffffffff          4294967295
r9             0x7f                127
r10            0x1                 1
r11            0x246               582
r12            0x1                 1
r13            0x4f6c068           83279976
r14            0x49d8710           77432592
r15            0x2ac3580           44840320
rip            0x43bdac            0x43bdac <qemu_bh_delete+28>
eflags         0x10246             [ PF ZF IF RF ]
cs             0x33                51
ss             0x2b                43
ds             0x2b                43
es             0x2b                43
fs             0x0                 0
gs             0x0                 0

在寄存器名之前添加$,即可显示各个寄存器的内容

(gdb) p $rax
$1 = 0

   显示可以使用以下格式,如表2-3所示。

        格式:

        p / 格式 变量

        表2-3 显示寄存器可使用的格式

 

 

        十进制数的97为ASCII字符的 ‘a’ 。

        程序指针可以写为$pc,也可写为eip,两者都可以显示。这是因为Intel IA-32 架构中的程序指针名为eip。

(gdb) p $pc
$2 = (void (*)()) 0x43bdac <qemu_bh_delete+28>
(gdb) p $rip
$3 = (void (*)()) 0x43bdac <qemu_bh_delete+28>
(gdb) 

 用 x 命令可以显示内存的内容。x 这个名字的由来是 eXaming。

        格式:

        x / 格式 地址

(gdb) x $pc
0x43bdac <qemu_bh_delete+28>:   0x001847c6
(gdb) x/i $pc
=> 0x43bdac <qemu_bh_delete+28>:        movb   $0x0,0x18(%rdi)

此处 x / i 意为显示汇编指令。

一般使用 x 命令时,格式为 x / NFU ADDR。此处ADDR 为希望显示的地址,N为重复次数,F为前面讲过的格式(x、d、u、o、t、a、c、f、s、i),U为表2-4 中所示的单位。

 

下面显示从 pc 所指的地址开始的10条指令。

(gdb) x/10i $pc
=> 0x43bdac <qemu_bh_delete+28>:        movb   $0x0,0x18(%rdi)
   0x43bdb0 <qemu_bh_delete+32>:        movb   $0x1,0x1a(%rdi)
   0x43bdb4 <qemu_bh_delete+36>:        mov    -0x8(%rbp),%rax
   0x43bdb8 <qemu_bh_delete+40>:        xor    %fs:0x28,%rax
   0x43bdc1 <qemu_bh_delete+49>:        jne    0x43bdc5 <qemu_bh_delete+53>
   0x43bdc3 <qemu_bh_delete+51>:        leaveq 
   0x43bdc4 <qemu_bh_delete+52>:        retq   
   0x43bdc5 <qemu_bh_delete+53>:        callq  0x439308 <__stack_chk_fail@plt>
   0x43bdca:    nopw   0x0(%rax,%rax,1)
   0x43bdd0 <qemu_bh_update_timeout>:   push   %rbp

也有反汇编的命令 disassemble,简写为disas

格式:

        disassemble。

        disassemble 程序计数器。

        disassemble 开始地址 结束地址。

格式1 反汇编整个函数,2为反汇编程序计数器所在函数的整个函数,3为反汇编开始到结束的地址

(gdb) 
Dump of assembler code for function qemu_bh_delete:
   0x000000000043bd90 <+0>:     push   %rbp
   0x000000000043bd91 <+1>:     mov    %rsp,%rbp
   0x000000000043bd94 <+4>:     sub    $0x10,%rsp
   0x000000000043bd98 <+8>:     callq  0x529f40 <mcount>
   0x000000000043bd9d <+13>:    mov    %fs:0x28,%rax
   0x000000000043bda6 <+22>:    mov    %rax,-0x8(%rbp)
   0x000000000043bdaa <+26>:    xor    %eax,%eax
=> 0x000000000043bdac <+28>:    movb   $0x0,0x18(%rdi)
   0x000000000043bdb0 <+32>:    movb   $0x1,0x1a(%rdi)
   0x000000000043bdb4 <+36>:    mov    -0x8(%rbp),%rax
   0x000000000043bdb8 <+40>:    xor    %fs:0x28,%rax
   0x000000000043bdc1 <+49>:    jne    0x43bdc5 <qemu_bh_delete+53>
   0x000000000043bdc3 <+51>:    leaveq 
   0x000000000043bdc4 <+52>:    retq   
   0x000000000043bdc5 <+53>:    callq  0x439308 <__stack_chk_fail@plt>
End of assembler dump.

首先在任意位置暂停执行程序,即可像上例哪样自由显示任意变量和地址,通过确认其值与预期是否相同,以确认是否存在bug。

单步执行

        单步执行的意思是根据源代码一行一行的执行。

        执行源代码中的一行命令是next (简写为n)。执行时如果遇到函数调用,可能想执行到函数内部,此时可以用step(简写为s)命令。

        例如,下例中

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值