如何用"x/i $ pc"在GDB中反汇编16位x86引导扇区代码?它被视为32位

Cir*_*四事件 7 x86 assembly gdb qemu disassembly

例如,使用BIOS打印a到屏幕的引导扇区main.asm:

org 0x7c00
bits 16
cli
mov ax, 0x0E61
int 0x10
hlt
times 510 - ($-$$) db 0
dw 0xaa55
Run Code Online (Sandbox Code Playgroud)

然后:

nasm -o main.img main.asm
qemu-system-i386 -hda main.img -S -s &
gdb -ex 'target remote localhost:1234' \
    -ex 'break *0x7c00' \
    -ex 'continue' \
    -ex 'x/3i $pc'
Run Code Online (Sandbox Code Playgroud)

我明白了:

0x7c00:      cli    
0x7c01:      mov    $0x10cd0e61,%eax
0x7c06:      hlt 
Run Code Online (Sandbox Code Playgroud)

所以看起来它mov ax, 0x0E61被解释为32位mov %eax并且将下一条指令int 0x10作为数据.

我怎么能告诉GDB这是16位代码?

也可以看看:

Mic*_*tch 16

正如Jester在评论中正确指出的那样,你只需要在使用set architecture i8086时使用gdb它,以便它知道假定采用16位8086指令格式.您可以在此处了解gdb目标.

我将此作为答案添加,因为在评论中难以解释.如果单独组装和链接事物,则可以生成调试信息,然后可以使用它们gdb来提供源级调试,即使远程执行16位代码也是如此.为此,我们稍微修改您的程序集文件:

;org 0x7c00    - remove as it may be rejected when assembling
;                with elf format. We can specify it on command
;                line or via a linker script.
bits 16

; Use a label for our main entry point so we can break on it
; by name in the debugger
main:
    cli
    mov ax, 0x0E61
    int 0x10
    hlt
    times 510 - ($-$$) db 0
    dw 0xaa55
Run Code Online (Sandbox Code Playgroud)

我添加了一些注释来确定所做的微不足道的更改.现在我们可以使用这些命令来汇编我们的文件,使其包含矮化格式的调试输出.我们将它链接到最终的精灵图像.此精灵图像可用于符号调试gdb.然后我们可以将elf格式转换为平面二进制格式objcopy

nasm -f elf32 -g3 -F dwarf main.asm -o main.o
ld -Ttext=0x7c00 -melf_i386 main.o -o main.elf
objcopy -O binary main.elf main.img

qemu-system-i386 -hda main.img -S -s &
gdb main.elf \
        -ex 'target remote localhost:1234' \
        -ex 'set architecture i8086' \
        -ex 'layout src' \
        -ex 'layout regs' \
        -ex 'break main' \
        -ex 'continue'
Run Code Online (Sandbox Code Playgroud)

我做了一些小改动.我main.elf在启动时使用该文件(带有符号信息)gdb.

我还为汇编代码和寄存器添加了一些更有用的布局,这些寄存器可以使命令行上的调试更容易.我也打破了main(不是地址).由于调试信息,我们的汇编文件中的源代码也应该出现.如果您更喜欢看原始装配layout asm,layout src则可以使用而不是.

这个一般概念可以在其他平台上使用NASMLD支持的其他格式.elf32并且elf_i386必须针对特定环境修改调试类型.我的示例针对了解Linux Elf32二进制文件的系统.


使用GDB/QEMU调试16位实模式引导加载程序

不幸的是,默认情况下gdb不执行segment:offset计算,并将使用EIP中的值作为断点.您必须将断点指定为32位地址(EIP).

当涉及到单步实模式代码时,它可能很麻烦,因为gdb它不处理实模式分割.如果您进入中断处理程序,您将发现gdb将显示相对于EIP的汇编代码.有效地gdb将向您显示错误的内存位置的反汇编,因为它没有考虑到CS.值得庆幸的是,有人创建了一个GDB脚本来提供帮助.将脚本下载到您的开发目录,然后运行QEMU,例如:

qemu-system-i386 -hda main.img -S -s &
gdb -ix gdbinit_real_mode.txt main.elf \
        -ex 'target remote localhost:1234' \
        -ex 'break main' \
        -ex 'continue'
Run Code Online (Sandbox Code Playgroud)

该脚本负责将架构设置为i8086,然后将其自身挂钩gdb.它提供了许多新的宏,可以使16位代码更容易步进.

break_int:在软件中断向量上添加断点(好的旧MS DOS和BIOS公开其API的方式)

break_int_if_ah:在软件中断上添加条件断点.AH必须等于给定参数.这用于过滤中断的服务调用.例如,有时您只想在调用中断10h的函数AH = 0h时中断(更改屏幕模式).

stepo:这是一个用于'跳过'函数和中断调用的kabalistic宏.它是如何工作的 ?提取当前指令的操作码,如果是函数或中断调用,则计算"下一个"指令地址,在该地址上添加临时断点并调用"继续"函数.

step_until_ret:这用于单步执行,直到我们遇到'RET'指令.

step_until_iret:这用于单步执行,直到我们遇到'IRET'指令.

step_until_int:这用于单步执行,直到我们遇到'INT'指令.

此脚本还使用计算的分段打印出地址和寄存器.每次执行指令后的输出如下所示:

---------------------------[ STACK ]---
D2EA F000 0000 0000 6F62 0000 0000 0000
7784 0000 7C00 0000 0080 0000 0000 0000
---------------------------[ DS:SI ]---
00000000: 53 FF 00 F0 53 FF 00 F0 C3 E2 00 F0 53 FF 00 F0  S...S.......S...
00000010: 53 FF 00 F0 53 FF 00 F0 53 FF 00 F0 53 FF 00 F0  S...S...S...S...
00000020: A5 FE 00 F0 87 E9 00 F0 76 D6 00 F0 76 D6 00 F0  ........v...v...
00000030: 76 D6 00 F0 76 D6 00 F0 57 EF 00 F0 76 D6 00 F0  v...v...W...v...
---------------------------[ ES:DI ]---
00000000: 53 FF 00 F0 53 FF 00 F0 C3 E2 00 F0 53 FF 00 F0  S...S.......S...
00000010: 53 FF 00 F0 53 FF 00 F0 53 FF 00 F0 53 FF 00 F0  S...S...S...S...
00000020: A5 FE 00 F0 87 E9 00 F0 76 D6 00 F0 76 D6 00 F0  ........v...v...
00000030: 76 D6 00 F0 76 D6 00 F0 57 EF 00 F0 76 D6 00 F0  v...v...W...v...
----------------------------[ CPU ]----
AX: AA55 BX: 0000 CX: 0000 DX: 0080
SI: 0000 DI: 0000 SP: 6F2C BP: 0000
CS: 0000 DS: 0000 ES: 0000 SS: 0000

IP: 7C00 EIP:00007C00
CS:IP: 0000:7C00 (0x07C00)
SS:SP: 0000:6F2C (0x06F2C)
SS:BP: 0000:0000 (0x00000)
OF <0>  DF <0>  IF <1>  TF <0>  SF <0>  ZF <0>  AF <0>  PF <0>  CF <0>
ID <0>  VIP <0> VIF <0> AC <0>  VM <0>  RF <0>  NT <0>  IOPL <0>
---------------------------[ CODE ]----
=> 0x7c00 <main>:       cli
   0x7c01:      mov    ax,0xe61
   0x7c04:      int    0x10
   0x7c06:      hlt
   0x7c07:      add    BYTE PTR [bx+si],al
   0x7c09:      add    BYTE PTR [bx+si],al
   0x7c0b:      add    BYTE PTR [bx+si],al
   0x7c0d:      add    BYTE PTR [bx+si],al
   0x7c0f:      add    BYTE PTR [bx+si],al
   0x7c11:      add    BYTE PTR [bx+si],al
Run Code Online (Sandbox Code Playgroud)

  • 不工作了。我曾经可以使用 `set architecture i8086` 来调试引导加载程序,但现在已经坏了,`set architecture` 对 GDB 8.0.1 中的反汇编没有影响。 (4认同)

Mat*_*har 10

此处已经提供的答案是正确的,似乎与最新版本的gdb和/或qemu.

这是关于具有当前详细信息的源软件的未解决问题

TL; 博士

当在实模式下qemu会协商错误的架构(i386)时,您需要覆盖它:

  1. 下载描述文件 - target.xml (gist)
  2. 启动 gdb 并连接到您的目标 ( target remote ...)
  3. 使用描述文件设置目标架构 - set tdesc filename target.xml

在 gdb 上设置架构

通常,当你调试ELFPE或任何其他目标文件GDB可以从文件标题推断架构。当您调试引导加载程序时,没有要读取的目标文件,因此您可以gdb自己告诉架构(在引导加载程序的情况下,arch 将是i8086):

set architecture <arch>
Run Code Online (Sandbox Code Playgroud)

注意:当附加到 qemu 虚拟机时,实际上不需要告诉 gdb 所需的架构,qemu 会通过协议为您协商这些信息qXfer

覆盖目标架构

如上所述,调试qemuVMqemu时实际上会将其架构协商为gdb,当面向 32 位 x86 时,架构可能是i386,这不是我们想要的实模式架构。

目前,gdb 中似乎存在一个问题,导致它在目标架构 (i386) 和用户提供的架构 (i8086) 之间选择最“功能强大的兼容架构”。由于gdb认为i386作为一个适当的超集i8086,它使用它来代替。选择i386会导致所有操作数默认为 32 位(而不是 16 位),这会导致反汇编器错误。

您可以通过指定target.xml 描述文件来覆盖目标架构:

set tdesc filename <file>
Run Code Online (Sandbox Code Playgroud)

我从源代码制作了这个描述文件qemu并将架构更改为 i8086。

  • 我是你的赞成票。应该注意的是,在我的示例中,我实际上将引导加载程序构建为 ELF 文件,然后从 ELF 文件生成二进制文件。您可以在 QEMU 中启动二进制文件,并在使用 GDB 运行时为符号调试信息指定 ELF 文件。我实际上是这样做的,这样我就可以在 QEMU 运行二进制文件(引导加载程序)的情况下获取符号信息 (2认同)

Cir*_*四事件 1

它适用于:

set architecture i8086
Run Code Online (Sandbox Code Playgroud)

正如小丑所提到的。

set architecture记录在: https: //sourceware.org/gdb/onlinedocs/gdb/Targets.html,我们可以通过以下方式获取目标列表:

set architecture
Run Code Online (Sandbox Code Playgroud)

(无参数)或 GDB 提示符上的 Tab 补全。