GDB 命令脚本的编写以及调试技巧汇总

144 篇文章 18 订阅
98 篇文章 7 订阅

在GDB调试程序的时候,如果程序带有很长的参数列表,或者调试命令本身很长,需要频繁启动调试会话时,频繁输入参数或者命令严重拖慢调试节奏,这里记录一个GDB非常有用的参数-x,可以将调试参数和调试命令以调试脚本的形式提供给GDB调试会话,这样就不用频繁输入调试命令和参数了。

Linux上,万事不决问"男人","man"就是这个男人(还好是单数,如果是men就有点儿邪恶了), 我们先man一把gdb,看官方文档对 "-x"选项的解释。

所以,我们可以在gdb -x参数后面,跟随一个gdb命令脚本来进行程序调试,脚本的格式和.gdbinit相同,以文本的形式提供。不同之处在于.gdbinit是默认的GDB脚本,无需选项指定,如果当前目录或者环境目录存在,GDB会自动调用执行,但是对于任意名字的GDB 脚本文件,则需要 -x选项指定了。

比如,在调试某款平台的时候,我写了两个脚本,第一个脚本是ddrinit,用于初始化内存,使内存可用,内容如下:

第二个是osinit,用于内存初始化完毕后,加载程序固件.

使用方法很简单,执行gdb -x ddrinit后,q退出,接着执行gdb -x osinit,运行固件就可以调试了。

同样的方式可以在Linux Native平台上调试native 应用:

比如,调试darknet的时候,由于darknet的推理命令特别的长,我们就可以用这种方式,将参数列表写在gdb脚本文件中,不用每次都在GDB会话中set args输入。

./darknet detector test ./cfg/coco.data ./cfg/yolov3.cfg ./yolov3.weights data/dog.jpg -i 0 -thresh 0.25

编辑gdb脚本文件,命名为args.

 接着执行命令

 gdb ./darknet -x args

就可以愉快的调试了:

其它调试技巧

其它调试技巧

我们在使用GNU工具链(gcc/g++)编译器编译程序的时候,使用-g选项编译可调试的目标文件,有的时候,虽然目标文件不带调试信息,但是我们不想更换目标文件,仍然想在现有的不带调试信息的目标文件上编译,有什么办法么?

有的,方法简单来说,就是先编译生成一个带调试信息的目标文件,然后用这个目标文件中的调试信息调试原来不带调试信息的目标文件,示意图如下:

我们就以如下C++程序为例来说明:

#include <iostream>
using namespace std;
 
class Base
{
public:
    virtual void fn() { cout << "In Base class" << endl; }
};
class Sub :public Base
{
public:
    virtual void fn() { cout << "In Sub class" << endl; }
};
 
int main(int argc, char **argv)
{
    Base *p;
 
    Base bc;
    Sub  sc;
    Sub  mc;
	
    p=&bc;
    p->fn();
    p=&sc;
    p->fn();
    p=&mc;
    p->fn();
 
    return 0;
}

首先编译两个可执行文件版本,debug和不带debug的。

$ g++ cpp.cpp -O0 -g -o debug
$ g++ cpp.cpp -O0 -o nodebug
~/czl/cpp$ g++ cpp.cpp -O0 -g -o debug
~/czl/cpp$ g++ cpp.cpp -O0 -o nodebug
~/czl/cpp$ ls
cpp.cpp  debug  nodebug

可以看到nodebug的程序确实是无法调试的

我们用如下命令就可以实现用nodebug的调试信息调试debug.

$ gdb --symbol=$debugfile -exec=$nodebugfile

可以看到,确实可以i调试了,如下:

~/czl/cpp$ gdb --symbol=debug -exec=nodebug
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Copyright (C) 2018 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-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from debug...done.
(gdb) b main
Breakpoint 1 at 0xaa9: file cpp.cpp, line 16.
(gdb) r
Starting program: /home/xxx/czl/cpp/nodebug
warning: Probes-based dynamic linker interface failed.
Reverting to original interface.


Breakpoint 1, main (argc=1, argv=0x7fffffffe278) at cpp.cpp:16
16      {
(gdb) l
11      public:
12          virtual void fn() { cout << "In Sub class" << endl; }
13      };
14
15      int main(int argc, char **argv)
16      {
17          Base *p;
18
19          Base bc;
20          Sub  sc;
(gdb)

或者我们把debug文件中的符号表提取出来单独组成一个文件

$ objcopy --only-keep-debug debug debug.symbol 
~/czl/cpp$ objcopy --only-keep-debug debug debug.symbol
~/czl/cpp$ ls
cpp.cpp  debug  debug.symbol  nodebug

然后,在单独引用debug文件即可

~/czl/cpp$ gdb --symbol=debug.symbol -exec=nodebug
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Copyright (C) 2018 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-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from debug.symbol...done.
(gdb) b main
Breakpoint 1 at 0xaa9: file cpp.cpp, line 16.
(gdb) r
Starting program: /home/xxx/czl/cpp/nodebug
warning: Probes-based dynamic linker interface failed.
Reverting to original interface.


Breakpoint 1, main (argc=1, argv=0x7fffffffe278) at cpp.cpp:16
16      {
(gdb) l
11      public:
12          virtual void fn() { cout << "In Sub class" << endl; }
13      };
14
15      int main(int argc, char **argv)
16      {
17          Base *p;
18
19          Base bc;
20          Sub  sc;
(gdb)

最后一种方法,gdb运行后,通过symbol-file debug.symbol 命令添加调试信息。

~/czl/cpp$ gdb nodebug
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Copyright (C) 2018 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-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from nodebug...(no debugging symbols found)...done.
(gdb) symbol-file debug.symbol
Reading symbols from debug.symbol...done.
(gdb) b main
Breakpoint 1 at 0xaa9: file cpp.cpp, line 16.
(gdb) r
Starting program: /home/xxx/czl/cpp/nodebug
warning: Probes-based dynamic linker interface failed.
Reverting to original interface.


Breakpoint 1, main (argc=1, argv=0x7fffffffe278) at cpp.cpp:16
16      {
(gdb) l
11      public:
12          virtual void fn() { cout << "In Sub class" << endl; }
13      };
14
15      int main(int argc, char **argv)
16      {
17          Base *p;
18
19          Base bc;
20          Sub  sc;
(gdb)

用一个文件的调试信息调试另一个文件,必要条件是这两个目标文件对应的源码没有发生过改动,这种改动包括但不限于逻辑改动,比如,也不能有添加空行之类的等等,这样会导致debug信息发生变化,debug不准。

catch point

如果遇到没有符号信息,无法通过打断点调试程序的时候,可以使用catchpoint,catchpoint从字面意思理解,是捕获断点,其主要监测信号的产生。例如c++的throw,或者加载库的时候,产生断点行为。如下图,调试一个不带符号信息的可执行程序的时候,使用如下命令抓取程序退出时的系统调用。

$ catch syscall exit_group

可以看到,即便没有调试信息,也成功抓取到了系统调用的点,位于../sysdeps/unix/sysv/linux/_exit.c文件,上面一条指令恰好是syscall.

29│ 31      in ../sysdeps/unix/sysv/linux/_exit.c
30│    0x00007ffff7ac6aaf <+47>:    mov    %edx,%edi
31│    0x00007ffff7ac6ab1 <+49>:    mov    %r8d,%eax
32│    0x00007ffff7ac6ab4 <+52>:    syscall
33├──> 0x00007ffff7ac6ab6 <+54>:    cmp    $0xfffffffffffff000,%rax
34│    0x00007ffff7ac6abc <+60>:    jbe    0x7ffff7ac6aa0 <__GI__exit+32>
35│    0x00007ffff7ac6abe <+62>:    neg    %eax
36│    0x00007ffff7ac6ac0 <+64>:    mov    %eax,%fs:(%r9)
37│    0x00007ffff7ac6ac4 <+68>:    jmp    0x7ffff7ac6aa0 <__GI__exit+32>
38│    0x00007ffff7ac6ac6 <+70>:    nopw   %cs:0x0(%rax,%rax,1)

gdb中可以通过help catch命令查看支持哪些cache

(gdb) help catch
Set catchpoints to catch events.

List of catch subcommands:

catch assert -- Catch failed Ada assertions
catch catch -- Catch an exception
catch exception -- Catch Ada exceptions
catch exec -- Catch calls to exec
catch fork -- Catch calls to fork
catch handlers -- Catch Ada exceptions
catch load -- Catch loads of shared libraries
catch rethrow -- Catch an exception
catch signal -- Catch signals by their names and/or numbers
catch syscall -- Catch system calls by their names
catch throw -- Catch an exception
catch unload -- Catch unloads of shared libraries
catch vfork -- Catch calls to vfork

Type "help catch" followed by catch subcommand name for full documentation.
Type "apropos word" to search for commands related to "word".
Command name abbreviations are allowed if unambiguous.
(gdb)

gdb quick reference.pdf

gdbserver&gdb

如下图,HOST和Target也可以是同一台机器:

#include <stdio.h>

int main(void)
{
    printf("%s line %d.\n", __func__, __LINE__);

    return 0;
}

编译:gcc -O0 main.c -g3 -o main

首先启动gdbserver:

$ gdbserver localhost:1234 main

其次在GDB端连接GDBServer:

zlcao@zlcao-RedmiBook-14:~/workspace/gdbsrv$ gdb
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Copyright (C) 2018 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-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
Reading /home/zlcao/workspace/gdbsrv/main from remote target...
warning: File transfers from remote targets can be slow. Use "set sysroot" to access files locally instead.
Reading /home/zlcao/workspace/gdbsrv/main from remote target...
Reading symbols from target:/home/zlcao/workspace/gdbsrv/main...done.
Reading /lib64/ld-linux-x86-64.so.2 from remote target...
Reading /lib64/ld-linux-x86-64.so.2 from remote target...
Reading symbols from target:/lib64/ld-linux-x86-64.so.2...Reading /lib64/ld-2.27.so from remote target...
Reading /lib64/.debug/ld-2.27.so from remote target...
(no debugging symbols found)...done.
0x00007ffff7dd4090 in ?? () from target:/lib64/ld-linux-x86-64.so.2
(gdb) add-symbol-file ./main
The address where ./main has been loaded is missing
(gdb) set solib-search-path /usr/lib
Reading /lib64/ld-linux-x86-64.so.2 from remote target...
Reading /lib64/ld-linux-x86-64.so.2 from remote target...
Reading /lib64/ld-linux-x86-64.so.2 from remote target...
Reading symbols from target:/lib64/ld-linux-x86-64.so.2...Reading /lib64/ld-2.27.so from remote target...
Reading /lib64/.debug/ld-2.27.so from remote target...
(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x55555555464e: file main.c, line 5.
(gdb) c
Continuing.
Reading /lib/x86_64-linux-gnu/libc.so.6 from remote target...
Reading /lib/x86_64-linux-gnu/libc-2.27.so from remote target...
Reading /lib/x86_64-linux-gnu/.debug/libc-2.27.so from remote target...

Breakpoint 1, main () at main.c:5
5	    printf("%s line %d.\n", __func__, __LINE__);
(gdb) l
1	#include <stdio.h>
2	
3	int main(void)
4	{
5	    printf("%s line %d.\n", __func__, __LINE__);
6	
7	    return 0;
8	}
(gdb) 

gdbserver attach模式

get process mapping

info proc mapping/info proc map

gdb attach

不但gdbserver可以attach, gdb本身也可以attach到一个进程上调试,gdb attach是gdb中的一个重要命令,它可以让调试器连接正在运行的进程并获得它们的控制权,通过gdbattach可以在运行时分析进程,查看其内部状态和内存等信息,毕竟,再需要调试一个程序之前,你是不知道需要调试它的。

gdbattach的用法如下,其中PID是待调试进程的PID。

(gdb) attach [pid]

之后就可以正常输入GDB命令调试进程了,比如下面的查看所有线程的调用栈命令:

(gdb) thread apply all bt

将LOG记录在外部文件中,set logging on ,输出gdb的打印信息,把每次打印的结果输出到文件(当前目录,默认是gdb.txt), 便于分析。

(gdb) set logging on
(gdb) set pagination off
(gdb) set height 0
(gdb) thread apply all bt
(gdb) set logging off

Q&A

有的时候,当你通过gdb的x命令查看指定内存地址的数据时,会出现无法访问的错误:

0x7ffff7ff6000: Cannot access memory at address 0x7ffff7ff6000

这段内存虚拟空间是有映射的,并且在程序中可以正常访问,为何在GDB中无法访问呢?这很可能是被访问的虚拟地址在建立页表映射时,其VMA被设置了VM_IO属性,设置了VM_IO属性的VMA,是无法被ptrace系统调用访问的,而GDB是基于PTRACE实现的。

写一个小的内核驱动模块,使用VM_IO实现其MMAP函数,然后在GDB中访问此MMAP出来的虚拟地址作测试,发现确实如此:

原因分析:

ptrace会调用下面的函数进行有效性检查,调用驱动定义的vm_ops->access函数:

在内核中,这个access一般指向generic_access_phys

在其实现中,会调用follow_phys检查其是否存在 VM_IO 和 VM_PFNMAP两个FLAG,如果有的话,立刻返回,ptrace执行失败。

抓取callstack,是从gdb上下文下来的:

当用户驱动不定义access handler时,为何GDB也无法访问?当注释掉access实现后,现象依然完全一样,这是为何?

原因分析,get_user_pages_remote返回可以被pin的PAGE 个数,由于这种区域没有PAGE对象对应,所以其返回为小于0或0,所以走第一个分支,access不定义的情况下,实际操作BUF的长度为0,所以也不会成功,但是会调用__access_remote_vm函数,至此,GDB无法访问虚拟地址的问题得到解答。

既然我们可以自定义access函数,我们就可以在access函数中自定义访问这片VMA的方法实现其功能。或者直接返回len来简单验证一下:

此时GDB就可以正常访问这块虚拟内存了,如上图。

问题的关键是struct vm_operations_struct中access的实现:

GDB调试QEMU时总是不断的断在received signal SIGUSR1上,影响调试,这是为何?

用GDB调试qemu, QEMU会不断的fire SIGUSR1,GDB就会不停的停在一些不是你设定的断点上,要禁止SIGUSER1,就可以顺利调试QEMU了,方法如下:

(gdb) info signals SIGUSR1
(gdb) handle SIGUSR1 noprint nostop

gdb插件

围绕GDB有众多插件,这些插件作为补充,提高了GDB的易用性,pwndbg插件就是其中之一,pwndbg界面非常华丽,集成众多功能窗口,使调试更加便捷。

pwndbg项目地址:https://github.com/pwndbg/pwndbg

下载下来后,根据README操作,既可以下载安装DEB格式的安装包,也可以从源码执行setup.sh安装。

pwndbg基于python3.x,较高版本的python会避免一些安装依赖问题。这里使用DEB包方式安装.

Release 2023.07.17 packages (debian10-final, ubuntu18.04-final) · pwndbg/pwndbg · GitHub

将pwndbg的启动命令添加到 ~/.gdbinit中:

只有一句话:

source /home/zlcao/pwndbg/gdbinit.py

安装必要的PYTHON包依赖后,就可以启动GDB调试测试程序了,pwndbg启动后界面如下:

$ gdb -q ./a.out

可以看到pwndbg的窗口配色非常漂亮,支持BACKTRACE,STACK,SOURCE (CODE), DISASM / x86-64 / set emulate on, REGISTERS / show-flags off / show-compact-regs off等多种窗口,使调试过程更加立体和直观。

SEGMENT FAULT自动触发显示异常指令,瞬间定位问题:

pwndbg支持额外的调试指令,具体可以查看pwndbg手册。

peda插件

peda插件是另一个广泛使用的GDB调试插件,项目地址在:

https://github.com/longld/peda

其优势之一是安装非常简单,根据GITHUB文档,只需要三步:

git clone https://github.com/longld/peda.git ~/peda
echo "source ~/peda/peda.py" >> ~/.gdbinit
echo "DONE! debug your program with gdb and enjoy"

插件之间不能组合使用,使用peda需要注释调pwndbg.

之后就可以使用PEDA调试程序了,peda的调试界面如下,发现PEDA的一个优势是调试时不会刷屏,非常类似于IDE的操作,和PWNDBG算各有优劣吧。

被调试进程的状态

被调试进程的调度信息,内核堆栈以及进程状态如下图所示,进程被被调试时进入tracing stop状态,进程不再执行,调度时间(包含虚拟时间和真实执行时间)不再增加,可以根据进程状态找到GDB条时器进程。


参考文章

pwndbg的安装和gdb使用-CSDN博客

mmap - Examining mmaped addresses using GDB - Stack Overflow

c++ - GDB can't access mmap()'d kernel allocated memory? - Stack Overflow

gdb符号表_Thanos小可爱的博客-CSDN博客_gdb指定符号表

C++多态性分析和与Linux内核中的多态性实现的共性和差异比较_papaofdoudou的博客-CSDN博客_c++和linux

本文结束!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

papaofdoudou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值