GDB详解

总结学习GDB的知识点

GDB的初始化文件

GDB会在执行的时候运行一些初始化文件。这些初始化文件或者说是配置文件有三个等级。

系统配置文件

用户配置文件

本地配置文件

系统配置文件

首先运行的是系统初始化文件:

1
2
/etc/gdb/gdbinit
/etc/gdb/gdbinit.d

其中gdbinit这是单个系统范围的初始化文件。它的位置由 –with-system-gdbinit 配置选项指定。它在 GDB 启动时首先加载,然后再处理命令行选项。

gdbinit.d这是系统范围的初始化目录。它的位置由 –with-system-gdbinit-dir 配置选项指定。当 GDB 启动时,在处理命令行选项之前,此目录中的文件会在 system.gdbinit (如果启用)之后立即按字母顺序加载。文件需要具有可识别的脚本语言扩展名 ( .py / .scm ) 或使用 .gdb 扩展名命名才能解释为常规 GDB 命令。GDB 不会递归到该目录的任何子目录。

用户配置文件

系统配置文件的作用范围是所有的用户,用户的配置文件是作用于单个用户。

1
2
3
$XDG_CONFIG_HOME/gdb/gdbinit
$HOME/.config/gdb/gdbinit
$HOME/.gdbinit

其中$XDG_CONFIG_HOME是一个gdb的环境变量。

不管是系统配置文件还是用户配置文件,都可以使用-nx命令行选项来阻止加载系统范围的初始化文件

如果想要执行本地初始化文件,而不是系统的,或者是用户的则需要在$XDG_CONFIG_HOME/gdb/gdbinit或者是$HOME/.config/gdb/gdbinit中加入:add-auto-load-safe-path /path/to/your/local/gdbinit

如果 $XDG_CONFIG_HOME 环境变量未设置,则默认路径为 $HOME/.config。

1
2
3
4
5
add-auto-load-safe-path /your/path/.gdbinit

#或者是这样,但是这相当于关闭了安全路径

add-auto-load-safe-path /

当然你也可以直接指定配置文件通过-x命令

1
gdb -x ./path/.gdbinit

在 GDB 中可以使用下面这个命令查看当前的加载的初始化文件:

1
show auto-load safe-path

三者的执行顺序

系统初始化->用户初始化->本地初始化

并且,最后执行的初始化文件具有决定性的作用。

小细节

gdb的命令很多,gdb把之分成许多个种类。help命令只是例出gdb的命令种类,如果要看种类中的命令,可以使用 help <class> 命令,如:help breakpoints,查看设置断点的所有命令。也可以直接help <command>来查看命令的帮助。

参考资料

1.4 GDB启动流程

下面我们讲述一下GDB的一个启动流程:

  1. 根据命令行的指定初始化对应的命令解释器(参看上一节GDB的执行模式)

  2. 读取系统级别的init文件(即在编译gdb时通过--with-system-gdbinit选项指定的文件),并执行其中的命令

  3. 读取home目录的init文件并执行其中的命令

  4. 按顺序执行由-iex选项指定的命令,或-ix选项指定的文件中的命令。你也可以使用-ex或者-x来替换前面的选项,但是在这种情况下其会在gdbinit之前被执行

  5. 处理命令行选项和参数

  6. 如当前目录并不是home目录,且set auto-load local-gdbinit值为on时,则会读取和执行当前工作目录下的gdbinit文件。

  7. 如果命令行指定了要调试的program,或者要attach到一个进程,或者一个core dump文件,那么gdb会加载该program所需要的自动加载文件或共享库。假如你想要在gdb启动时禁止这个自动加载,那么类似于如下:

1
# gdb -iex "set auto-load python-scripts off" myprogram

说明: 这里并不能用-ex选项,因为这会使得关闭auto-load太晚

  1. 执行-ex选项指定的命令,或-x选项指定的文件中的命令

  2. 读取history file中的历史命令

.gdbinit文件的配置

基本的语法组成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
define <command>

<code>

end

document <command> 当在gdb中输入help command时显示的文本

<help text>


Usage: 简要命令的描述 使用命令help user查看

end

如果输入 help user 命令,您会看到已在 .gdbinit 文件中输入的所有用户命令的摘要。.gdbinit 用户定义命令的设计者提供了一个重要特性,您在编写自己的命令时不应忽略该特性:document … end 子句。随着这些命令数量的增加,维护有关命令如何工作的功能文档将变得非常关键。

所以我们可以通过这个文件定制自己的gdb

在.gdbinit文件中定义钩子函数

钩子函数就是在某事件发生时执行这个函数,gdb中的钩子函数语法:

当运行具有钩子函数的命令foo时,这个钩子函数会先于命令foo运行。使用语法hook-foo

语法hookpost-foo是指在foo命令运行完之后,再运行钩子函数。

例子:

1
2
3
4
5
6
7
8
9
10
11
define hook-echo
echo <<<---
end

define hookpost-echo
echo --->>>\n
end

(gdb) echo Hello World
<<<---Hello World--->>>
(gdb)

注意:钩子函数可以为所有的单数命令定义,但是不可以为命令的简称定义。如可以为backtrace命令写钩子,但是不可以为bt命令写钩子。尽管这两个命令含义是一样的。

具有多个单词的命令如何编写钩子?

我们可以通过将hook-或hookpost-添加到命令的最后一个字来钩子多字命令,例如define target hook-remote“将钩子添加到target remote。

如果在执行钩子期间发生错误,GDB命令的执行将停止,GDB将发出提示(在您实际键入的命令有机会运行之前)。

如果尝试定义与任何已知命令都不匹配的钩子,则会从define命令得到警告。

参考资料

https://blog.csdn.net/wads23456/article/details/105064833

已经定义好的.gdbinit文件

钩子函数

https://blog.csdn.net/kking_edc/article/details/114746806

在.gdbinit文件中定义钩子函数

gdb插件

gdb的工作原理

gdb 通过系统调用 ptrace 来接管一个进程的执行。ptrace 系统调用提供了一种方法使得父进程可以观察和控制其它进程的执行,检查和改变其核心映像以及寄存器。它主要用来实现断点调试和系统调用跟踪。ptrace 系统调用的原型如下:

1
2
#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
  • pid_t pid:指示 ptrace 要跟踪的进程。

  • *_void addr_:指示要监控的内存地址。

  • *_void data_:存放读取出的或者要写入的数据。

  • enum __ptrace_request request:决定了系统调用的功能,几个主要的选项:

    • _PTRACE_TRACEME_:表示此进程将被父进程跟踪,任何信号(除了 SIGKILL)都会暂停子进程,接着阻塞于 wait() 等待的父进程被唤醒。子进程内部对 exec() 的调用将发出 SIGTRAP 信号,这可以让父进程在子进程新程序开始运行之前就完全控制它。

    • _PTRACE_ATTACH_:attach 到一个指定的进程,使其成为当前进程跟踪的子进程,而子进程的行为等同于它进行了一次 PTRACE_TRACEME 操作。但需要注意的是,虽然当前进程成为被跟踪进程的父进程,但是子进程使用 getppid() 的到的仍将是其原始父进程的 pid。

    • _PTRACE_CONT_:继续运行之前停止的子进程。可同时向子进程交付指定的信号。

断点的实现

断点的功能是通过内核信号实现的,在 x86 架构上,内核向某个地址打入断点,实际上就是往该地址写入断点指令 INT 3,即 0xCC 。目标程序运行到这条指令之后会触发 SIGTRAP 信号,gdb 捕获这个信号,并根据目标程序当前停止的位置查询 gdb 维护的断点链表,若发现在该地址确实存在断点,则可判定为断点命中。


GDB详解
https://ysc2.github.io/ysc2.github.io/2023/11/26/GDB详解/
作者
Ysc
发布于
2023年11月26日
许可协议