GDB 基本使用

2017-03-10 Friday     program

GNU Project Debugger, GDB 一个代码调试工具,通过系统提供的 ptrace 接口实现的控制进程,然后可以在进程内部查看信息,甚至调用函数。

简介

在使用 gdb 时,需要通过 -g 参数把调试信息加到可执行文件中,否则没有函数名、变量名、代码地址,所代替的全是运行时的内存地址。

> cc -g hello.c -o hello
> g++ -g hello.cpp -o hello

gdb 详细使用方式可以通过 gdb --help 查看。

注意,gdb 在保证命令不冲突的前提下,提供了简写命令,例如 list 查看源码,实际上通过 l 也可以。

启动

常见的有如下几种启动方式。

----- 直接通过GDB启动进程
$ gdb <program>
----- 调试Core文件
$ gdb <program> core
----- Attach到正在运行的进程上,也可以进入gdb后执行attach命令
$ gdb <program> <PID>

另外一些启动时的常用参数为。

-symbols/-s <file>
   指定文件读取符号表。
-se file
   从指定文件中读取符号表信息,并把他用在可执行文件中。
-core/-c <file>
   调试Core文件。
-directory/-d <directory>
   加入一个源文件的搜索路径,默认是PATH指定的路径。

是否查找到源文件,可以通过 list/l 命令查看。

参数设置

在通过 r/run 正式运行前,可以通过如下方式设置运行参数、环境变量等信息。

----- 程序的运行参数设置、查看
(gdb) set args <arguments>

----- 查看设置好的运行参数
(gdb) show args

内存查看

也就是 examine 命令,通常简写为 x ,对应的命令格式为 x/nfu <ADDR>

* n 显示内存个数;
* f 显示方式;
  - x 按十六进制格式显示变量;
  - d 按十进制格式显示变量;
  - u 按十进制格式显示无符号整型;
  - o 按八进制格式显示变量;
  - t 按二进制格式显示变量;
  - a 按十六进制格式显示变量;
  - i 指令地址格式;
  - c 按字符格式显示变量;
  - f 按浮点数格式显示变量;
* u 一个地址单元的长度;
  - b 单字节;
  - h 双字节;
  - w 四字节;
  - g 八字节;

信息查看

主要是通过 info 命令查看各种信息。

----- 查看函数信息,可以使用正则表达式
(gdb) info functions

变量查看

包括了全局、局部、静态变量,以及调用函数的参数。

----- 查看所有的全局和静态变量
(gdb) info variables
----- 当前栈的局部变量,包括了本函数中的静态变量
(gdb) info locals
----- 查看参数
(gdb) info args

其它

----- 查看版本信息,默认启动时会打印
(gdb) show version

----- 查看版权信息
(gdb) show copying
(gdb) show warranty

----- 退出时无需确认,直接退出
(gdb) set confirm off

----- 关闭分页,会将信息全部输出
(gdb) set pagination off
(gdb) set height 0

其中时通过 -q 或者 --quiet 可以禁止打印版本信息,

多线程

先介绍一下GDB多线程调试的基本命令。

info threads                                 显示所有线程,通过星号标示当前线程
thread ID                                    切换当前调试的线程为指定ID的线程。
thread apply <all|ID1 ID2 ... IDn> <command> 指定多个线程执行命令
break <filename:lineno> thread all           在所有线程上设置断点
set scheduler-locking off|on|step

调试多线程时,如果使用 step 或者 continue 命令调试当前被调试线程,其他线程也是同时执行的,可以通过如上的参数进行设置,参数的含义为:

  • off 默认值,不锁定任何线程,也就是所有线程都执行;
  • on 只有当前被调试程序会执行;
  • step 在单步的时候,除了next过一个函数以外,只有当前线程会执行。

死锁查看

(gdb) info threads                # 可以查看那些线程在等锁
(gdb) thread apply all bt

暂停

通过 BreakPoint、WatchPoint、CatchPoint 来设置程序在某段代码处或者满足某个条件时停止。

如下是一个测试程序。

#include <stdio.h>

int main(void)
{
        int i, sum = 0;

        for (i = 1; i <= 200; i++)
                sum += i;         // line #8
        printf("%d\n", sum);
        return 0;
}

Breakpoint

直接在某个指定的位置停止。

(gdb) break <FunctionName>                # 当前文件指定函数
(gdb) break <LineNumber>                  # 当前文件指定行数
(gdb) break <FileName:FunctionName>
(gdb) break <FileName:LineNumber>

(gdb) info breakpoints                    # 查看断点信息

(gdb) delete <NUM1 NUM2>                  # 根据上述的序号删除
(gdb) delete <Range>

(gdb) enable/disable

(gdb) clear <...>                         # 指定行删除,类似上述的break设置方式

另外,也可以通过 tbreak 设置临时断点,也就是在执行到一次之后会立即退出。

指定地址

在调试汇编程序时,如果没有调试信息,就需要在程序地址上打断点,方法为 b *ADDRESS

程序入口

当没有调试信息时,是无法通过 start 命令启动并在入口处暂停的,可以通过如下方式获取程序入口。

保存断点

可以将断点保存在某个文件中,然后下次直接加载再次使用。

(gdb) save breakpoints <FilenameToSave>
(gdb) source <FilenameToSave>

注意,如果通过行号指定,那么当源码修改之后可能会是非预期的。

条件断点

可以通过 break ... if cond 设置条件断点,也就是当满足某个条件时,断点才会触发。在上述示例的第 8 行,根据变量 i 设置一个条件断点。

(gdb) start
Temporary breakpoint 1 at 0x40059e: file main.c, line 5.
Starting program: /tmp/test/a.out
Temporary breakpoint 1, main () at main.c:5
5               int i, sum = 0;
(gdb) break 8 if i == 100
Breakpoint 2 at 0x4005ae: file main.c, line 8.
(gdb) c
Continuing.

Breakpoint 2, main () at main.c:8
8                       sum += i;
(gdb) print sum
$1 = 4950
(gdb) print i
$2 = 100

忽略 N 次断点

通过命令 ignore bnum count 设置,意味着在接下来 count 次编号为 bnum 的断点触发都不会让程序中断,只有第 count + 1 次断点触发才会让程序中断。

(gdb) start
Temporary breakpoint 3 at 0x40059e: file main.c, line 5.
Starting program: /tmp/foobar/a.out

Temporary breakpoint 3, main () at main.c:5
5               int i, sum = 0;
(gdb) break 8
Breakpoint 1 at 0x4005ae: file main.c, line 8.
(gdb) ignore 1 5
Will ignore next 5 crossings of breakpoint 1.
(gdb) c
Continuing.

Breakpoint 5, main () at main.c:8
8                       sum += i;
(gdb) p i
$4 = 6

如果想让断点下次就生效,可以将 count 置为 0 ,也就是 ignore 1 0

Watchpoint

当某个变量或者表达式发生变化时暂停,可扩展为变量读、写时停止。

上述示例可以通过 watch i 设置,或者直接通过地址,假设地址为 0x7fffffffdeec ,那么也可以通过 watch *(int *)0x7fffffffdeec 设置观察点,两者作用相同。

当变量修改后,会打印老的以及新的值。

硬件观测点

观测点分为两类:软件观察点 (Software Watchpoint) 和硬件观察点 (Hardware Watchpoint)。其使用软件观察点的方式就是单步执行程序同时测试变量的值,这样会导致程序的执行速度变慢。

现在很多 CPU 提供了硬件观测点,不过因为寄存器有限,所以只能设置有限观测点,可以通过 disable 关闭不需要的观测点。

当触发观测点后,才会告知是否启用硬件观测点,也就是 Hardware watchpoint num 信息。

(gdb) show can-use-hw-watchpoints
(gdb) set can-use-hw-watchpoints 0

另外,可以设置读写观测点,不过只针对硬件生效。

  • 读观测点 rwatch,只对硬件观测点生效。
  • 读写观测点 awatch,只对硬件观测点生效。

针对线程

可以通过 watch expr thread threadnum 设置观察点只针对特定线程生效。

Catchpoint

其作用是在发生某种事件时候停止运行,常用的有如下几类:

  • C++ 异常,throw 抛出异常,catch 捕获异常;
  • 程序调用,针对的是不同的系统 API 接口,包括了 exec fork vfork
  • 动态库,加载或者卸载动态库会停止,也可以指定动态库的名称 load unload
  • 系统调用,可以指定系统调用号或名称 syscall


如果喜欢这里的文章,而且又不差钱的话,欢迎打赏个早餐 ^_^


About This Blog

Recent Posts

Categories

Related Links

  • RTEMS
    RTEMS
  • GNU
  • Linux Kernel
  • Arduino

Search


This Site was built by Jin Yang, generated with Jekyll, and hosted on GitHub Pages
©2013-2019 – Jin Yang