如何使用“x/i $pc"反汇编 GDB 中的 16 位 x86 引导扇区代码?它被视为 32 位 [英] How to disassemble 16-bit x86 boot sector code in GDB with "x/i $pc"? It gets treated as 32-bit

查看:37
本文介绍了如何使用“x/i $pc"反汇编 GDB 中的 16 位 x86 引导扇区代码?它被视为 32 位的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

例如,对于引导扇区,BIOS 将 a 打印到屏幕 main.asm:

For example, with a boot sector that BIOS prints a to the screen main.asm:

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

那么:

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'

我明白了:

0x7c00:      cli    
0x7c01:      mov    $0x10cd0e61,%eax
0x7c06:      hlt 

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

So it looks like the mov ax, 0x0E61 was interpreted as a 32-bit mov %eax and ate up the next instruction int 0x10 as data.

如何告诉 GDB 这是 16 位代码?

How can I tell GDB that this is 16-bit code?

另见:

  • in 2007 a GDB dev replied "use objdump" https://www.sourceware.org/ml/gdb/2007-03/msg00308.html as explained at How do I disassemble raw x86 code? Maybe it was implemented meantime?
  • superset: Using GDB in 16-bit mode
  • similar, but the OP got an error there, so maybe it is something else? How can I disassembly win16 with GDB

推荐答案

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

As Jester correctly pointed out in a comment, you just need to use set architecture i8086 when using gdb so that it knows to assume 16-bit 8086 instruction format. You can learn about the gdb targets here.

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

I'm adding this as an answer because it was too hard to explain in a comment. If you assemble and link things separately you can generate debug information that can then be used by gdb to provide source level debugging even when done remotely against 16-bit code. To do this we modify your assembly file slightly:

;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

我添加了一些注释来确定所做的微不足道的更改.现在我们可以使用这样的命令来组装我们的文件,以便它包含 dwarf 格式的调试输出.我们将它链接到最终的精灵图像.这个精灵图像可以被 gdb 用于符号调试.然后我们可以使用 objcopy

I've added some comments to identify the trivial changes made. Now we can use commands like these to assemble our file so that it contains debug output in the dwarf format. We link it to a final elf image. This elf image can be used for symbolic debugging by gdb. We can then convert the elf format to a flat binary with 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'

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

I've made some minor changes. I use the main.elf file (with symblic information) when starting up gdb.

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

I also add some more useful layouts for assembly code and the registers that may make debugging on the command line easier. I also break on main (not the address). The source code from our assembly file should also appear because of the debugging information. You can use layout asm instead of layout src if you prefer to see the raw assembly.

这个一般概念适用于其他平台上NASMLD支持的其他格式.elf32elf_i386 以及调试类型必须针对特定环境进行修改.我的示例针对理解 Linux Elf32 二进制文件的系统.

This general concept can work on other formats supported by NASM and LD on other platforms. elf32 and elf_i386 as well as the debugging type will have to be modified for the specific environment. My sample targets systems that understand Linux Elf32 binaries.

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

Unfortunately by default gdb doesn't do segment:offset calculations and will use the value in EIP for breakpoints. You have to specify breakpoints as 32-bit addresses (EIP).

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

When it comes to stepping through real mode code it can be cumbersome because gdb doesn't handle real mode segmentation. If you step into an interrupt handler you'll discover gdb will display the assembly code relative to EIP. Effectively gdb will be showing you disassembly of the wrong memory location since it didn't account for CS. Thankfully someone has created a GDB script to help. Download the script to your development directory and then run QEMU with something like:

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'

脚本负责将架构设置为 i8086,然后将自身挂接到 gdb.它提供了许多新宏,可以使单步调试 16 位代码更容易.

The script takes care of setting the architecture to i8086 and then hooks itself into gdb. It provides a number of new macros that can make stepping through 16 bit code easier.

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

break_int : adds a breakpoint on a software interrupt vector (the way the good old MS DOS and BIOS expose their APIs)

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

break_int_if_ah : adds a conditional breakpoint on a software interrupt. AH has to be equals to the given parameter. This is used to filter service calls of interrupts. For instance, you sometimes only wants to break when the function AH=0h of the interruption 10h is called (change screen mode).

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

stepo : this is a kabalistic macro used to 'step-over' function and interrupt calls. How does it work ? The opcode of the current instruction is extracted and if it is a function or interrupt call, the "next" instruction address is computed, a temporary breakpoint is added on that address and the 'continue' function is called.

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

step_until_ret : this is used to singlestep until we encounter a 'RET' instruction.

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

step_until_iret : this is used to singlestep until we encounter an 'IRET' instruction.

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

step_until_int : this is used to singlestep until we encounter an 'INT' instruction.

这个脚本还打印出地址和寄存器,并计算出分段计算.每条指令执行后的输出如下所示:

This script also prints out addresses and registers with segmentation calculated in. Output after each instruction execution looks like:

---------------------------[ 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

这篇关于如何使用“x/i $pc"反汇编 GDB 中的 16 位 x86 引导扇区代码?它被视为 32 位的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆