如何使用GDB分解内存范围?

72

我尝试使用GDB反汇编程序以查看系统调用的汇编指令(我认为是INT指令)和处理程序。我编写了一个小程序(见下文),用于打开和关闭文件。

我能够通过GDB跟踪到fopen的调用,直到它执行了一个call指令。

但是当我尝试告诉GDB“disassemble 0x....”(调用地址)时,它回应说:“没有函数包含指定的地址。”

是否可能强制GDB对该内存地址进行反汇编(或在汇编语言中尽可能好地显示它)? 如果可以,如何操作?

#include <stdio.h>
#include <stdlib.h>

int main() {
    FILE* f;
    f = fopen("main.c", "r");
    if (!f) { 
      perror("open");
      return -1;
    }
    fclose(f);
    return 0;
}

2
fopen()不是系统调用,它是对C标准库的调用。你为什么认为必须通过INT指令进行系统调用呢? - anon
我可能错了,但我们被教导说fopen调用最终会导致系统调用内核打开文件并返回文件描述符? - Patrick
1
Patrick:是的,但不需要直接这样做。通常它调用libc函数,然后进入内核。但是调用内核可能不仅使用int(这很慢),而且还可以使用syscall/sysenter,具体取决于处理器架构... - k3a
11个回答

126

是的,disassemble不是在这里使用的最佳命令。 你需要的命令是"x/i"(作为指令检查):

(gdb) x/i 0xdeadbeef

4
谢谢!以下是翻译内容:为了拆卸二进制块、拆卸 ROM、在二进制镜像文件中检查指令等操作,可以使用以下说明。编写一个小的 C 程序来使用 fread() 将二进制块读入缓冲区,然后在缓冲区上执行 'x /i' 命令即可。 - user188012
@user188012 如果你想反汇编一个二进制文件,更简单的方法是使用像ndisasm或类似的独立反汇编器。 - Barry Kelly
2
你可以使用 x/i $pc 来获取当前指令的地址,也就是 PC 的指令。 - k3a
6
你可以使用"(gdb) x/<数字>i 0x地址"来打印<数字>条指令,例如"(gdb) x/10i 0x地址"将打印10条指令。 - GramThanos
这里的地址0xdeadbeef有任何特殊意义吗?我的意思是问它是否是任何特殊地址? - shane
显示剩余3条评论

56

你只想反汇编你实际的主函数吗?如果是,请尝试这样做:

(gdb) info line main 
(gdb) disas STARTADDRESS ENDADDRESS

就像这样:

USER@MACHINE /cygdrive/c/prog/dsa
$ gcc-3.exe -g main.c

USER@MACHINE /cygdrive/c/prog/dsa
$ gdb a.exe
GNU gdb 6.8.0.20080328-cvs (cygwin-special)
...
(gdb) info line main
Line 3 of "main.c" starts at address 0x401050 <main> and ends at 0x401075 <main+
(gdb) disas 0x401050 0x401075
Dump of assembler code from 0x401050 to 0x401075:
0x00401050 <main+0>:    push   %ebp
0x00401051 <main+1>:    mov    %esp,%ebp
0x00401053 <main+3>:    sub    $0x18,%esp
0x00401056 <main+6>:    and    $0xfffffff0,%esp
0x00401059 <main+9>:    mov    $0x0,%eax
0x0040105e <main+14>:   add    $0xf,%eax
0x00401061 <main+17>:   add    $0xf,%eax
0x00401064 <main+20>:   shr    $0x4,%eax
0x00401067 <main+23>:   shl    $0x4,%eax
0x0040106a <main+26>:   mov    %eax,-0xc(%ebp)
0x0040106d <main+29>:   mov    -0xc(%ebp),%eax
0x00401070 <main+32>:   call   0x4010c4 <_alloca>
End of assembler dump.

我没有看到你的系统中断调用。不过,我好像记得最后一次尝试在汇编中进行系统调用时使用了INT 21h。


好的,那么我将尝试在未来寻找 INT 21h。谢谢你的提示。 但是我想尝试跟踪从 fopen() 开始的调用序列(在你的代码中没有看到它...)“向下”,直到我能看到 INT 命令。 - Patrick
1
托管它 - 最好的方法是同时使用您和Falaina的答案。 我不得不使用gcc --static main.c静态编译它,然后使用gdb/objdump深入了解C库。 最终,它导致调用__open_nocancel,它执行了INT 0x80。感谢你们两个。 - Patrick
11
注意:在GDB 7.7版本中,disas 0x401050 0x401075的语法将无法工作。您需要将其改为disas 0x401050,0x401075。此外,您可能需要添加前缀“/m”以显示源代码周围的内容:disas \m 0x401050,0x401075 - Hi-Angel
1
@Patrick,虽然这是很久以前的事情了,但值得注意的是INT 0x80只是Linux的做法。也就是说,Linux的系统调用处理程序在中断128处注册。其他操作系统可能会有所不同 - 这是确实存在的。 - sherrellbc

34

虽然这不是您问题的直接答案,但由于您似乎只想要对二进制文件进行反汇编,也许您可以使用objdump命令:

objdump -d program

这应该会给你它的反汇编。如果你想要源代码注释,可以添加-S

对于 -S,我不知道它可以包含源代码。 - Hi-Angel
如果程序编译为不同的架构,这将无法工作 :( - ucczs

7

fopen()是C库函数,因此您的代码中不会看到任何系统调用指令,只有正常的函数调用。 在某个时刻,它确实通过跳板调用了open(2),但是这是通过跳转到由内核提供给每个进程的VDSO页面来完成的。 然后,VDSO提供了用于进行系统调用的代码。 在现代处理器上,将使用SYSCALL或SYSENTER指令,但也可以在x86处理器上使用INT 80h。


7
您可以通过添加-S开关来强制gcc直接输出汇编代码。
gcc -S hello.c

3

gdb disassemble命令有一个/m选项,可以在指令旁边包含源代码。这相当于使用objdump -S命令,但额外的好处是只针对感兴趣的函数(或地址范围)进行操作。


3
如果你想查看带有INTC调用的反汇编代码,只需使用objdump -d命令即可,但在编译时请使用-static选项。否则,fopen函数不会被编译进elf文件中并将在运行时链接。

2

这个“accepted”并不完全正确。在某些情况下它是有效的。

 (gdb) disas STARTADDRESS ENDADDRESS

最高票答案是正确的。如果您不想了解为什么它是正确的,请不要继续阅读。
 (gdb) x/i 0xdeadbeef

使用一个适当的无意义十六进制地址。


我有一個STM32並使用PIC重定位了代碼。正常啟動地址為0x8000000,具有0x200向量表。所以正常進入點是0x8000200。然而,我已經將二進制編程到了0x80040200(相差兩個NOR閃存扇區),並希望在那裡調試。

gdb的問題是“file foo.elf”顯示代碼位於第一個範圍內。特殊命令(如'disassemble')實際上將查看主機上的二進制文件。對於交叉調試情況,gdb必須查看遠程內存,這可能很昂貴。因此,“x /i”(作為代碼檢查)似乎是最好的選擇。 gdb依賴的調試信息(程序開始/結束位置)不存在於隨機二進制塊中。


为了在嵌入式交叉系统上结合上述关于PIC代码的答案,

您需要创建多个elf文件,每个文件对应一个可能的目标位置。使用GDB的file命令选择具有正确符号位置的文件。


这不适用于交叉开发

你可以使用 生成gcc调试符号。步骤如下:

  1. 构建正常的链接地址。
  2. 提取符号。
  3. 使用带有偏移量的 symbol-file 用于运行时地址。
  (gdb) help symbol-file
  Load symbol table from executable file FILE.
  Usage: symbol-file [-readnow | -readnever] [-o OFF] FILE
  OFF is an optional offset which is added to each section address.

然后,你可以切换符号文件以使用重新定位的运行地址来使用第一个答案。


如果您有这样一种情况,即代码已经被重定位,但数据是绝对的,您需要链接两次并选择重定位的elf文件(只有符号被重定位,代码是相同的)。对于NOR闪存而言,这是理想的XIP(执行就地)方式,因为内存设备的.text.rodata.data.bss不同。也就是说,许多低中端嵌入式设备。然而,在GCC上,不支持此代码生成选项(至少在ARM上是如此)。您必须使用“静态基本”寄存器(例如,像U-boot一样使用r9)。


symbol-file在交叉调试中无法工作,因为gdb认为机器是主机类型。您需要加载两个elf文件,并将第二个地址elf加载到远程调试PIC代码中。您可以比较提取的“bin”文件以查看绝对地址的位置,并确保它们被修复。 - artless noise
覆盖是一种反转,这里必须选择GDB认为在地址范围内的符号。x /i始终有效,但地址不会被解码且难以理解。 - artless noise
我的主要观点是,'x/i hexaddress' 始终有效,但它不提供符号信息。它只是原始反汇编。答案是提供提示来让第一种形式起作用,这样您实际上就可以在反汇编中使用符号常量。 - artless noise

1

你不必使用gdb,GCC会做到。

 gcc -S foo.c

这将创建 foo.s 文件,该文件是汇编代码。
gcc -m32 -c -g -Wa,-a,-ad foo.c > foo.lst

以上版本将创建一个包含由其生成的C和汇编代码的列表文件。GCC常见问题解答

0

将内存范围反汇编为C的完整示例

/opt/gcc-arm-none-eabi-9-2019-q4-major/bin/arm-none-eabi-gdb

(gdb)file /root/ncs/zephyr/samples/hello_world/build_nrf9160dk_nrf9160ns/zephyr/zephyr.elf
(gdb) directory /root/ncs/zephyr/samples/hello_world/src
#here you want 1
(gdb) info line* 0x000328C0
#here you want 2, -0x04 ~ +0x04 is your range size
(gdb) disassemble /m 0x000328C0-0x04, 0x000328C0+0x04
#here with binary code
(gdb) disassemble /r 0x000328C0-0x04, 0x000328C0+0x04
(gdb) info thread
(gdb) interpreter-exec mi -thread-info

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接