使用gdb和QEMU调试bootloader/bios时如何跳过中断调用?

Ces*_*rod 5 assembly gdb qemu bootloader

出于教育目的,我已经从mikeos.berlios.de/write-your-own-os.html改编了这个引导加载程序,将其重写为专门加载地址0x7c00.

最终的代码是这样的:

[BITS 16]           ; Tells nasm to build 16 bits code
[ORG 0x7C00]        ; The address the code will start

start: 
    mov ax, 0       ; Reserves 4Kbytes after the bootloader
    add ax, 288 ; (4096 + 512)/ 16 bytes per paragraph 
    mov ss, ax 
    mov sp, 4096 
mov ax, 0   ; Sets the data segment 
    mov ds, ax 
    mov si, texto   ; Sets the text position 
    call imprime    ; Calls the printing routine
jmp $       ; Infinite loop 
    texto db 'It works! :-D', 0 
imprime:            ; Prints the text on screen
    mov ah, 0Eh     ; int 10h - printing function 
.repeat: 
    lodsb           ; Grabs one char 
    cmp al, 0 
    je .done        ; If char is zero, ends 
    int 10h         ; Else prints char 
jmp .repeat 
.done: 
ret 
times 510-($-$$) db 0 ; Fills the remaining boot sector with 0s 
dw 0xAA55             ; Standard boot signature
Run Code Online (Sandbox Code Playgroud)

我可以逐步完成程序,看看寄存器是否正在改变,以及正在执行的指令,使用gdb(si)步进并使用QEMU监视器(信息寄存器,x/i $ eip等)进行检查.

在我进入int 10h(BIOS打印例程)后,事情变得有点奇怪.如果我立刻执行500条指令,我可以在屏幕上看到字符"I"(我的文本字符串的第一个字符).所以我再次重新启动并迈出了400步(si 400),然后我一步一步地看到"我"打印的确切步骤.它从未发生过.我实际上一个接一个地走了200步,什么都没发生.我一踏上100步(si 100),我就再次在屏幕上打印"我".

所以,我想知道是否存在计时问题(一些系统中断会妨碍我逐步调试).还有什么可以呢?

无论如何,有没有办法跳过整个BIOS中断和其他功能,只需返回并继续步进引导加载程序代码?正如Peter Quiring在评论中所建议的,我尝试使用下一个.这没用.

(gdb) next 
Cannot find bounds of current function
Run Code Online (Sandbox Code Playgroud)

所以我尝试了nexti,它就像si一样.

谢谢!

Cir*_*四事件 5

我已经使用 Python 脚本自动化了您的程序:

  • 计算当前指令的长度
  • 在下一条指令上设置临时断点
  • 继续

这也适用于任何其他指令,但我没有看到很多其他用例,因为nexti已经跳过call.

class NextInstructionAddress(gdb.Command):
    """
Run until Next Instruction address.

Usage: nia

Put a temporary breakpoint at the address of the next instruction, and continue.

Useful to step over int interrupts.

See also: http://stackoverflow.com/questions/24491516/how-to-step-over-interrupt-calls-when-debugging-a-bootloader-bios-with-gdb-and-q
"""
    def __init__(self):
        super().__init__(
            'nia',
            gdb.COMMAND_BREAKPOINTS,
            gdb.COMPLETE_NONE,
            False
        )
    def invoke(self, arg, from_tty):
        frame = gdb.selected_frame()
        arch = frame.architecture()
        pc = gdb.selected_frame().pc()
        length = arch.disassemble(pc)[0]['length']
        gdb.Breakpoint('*' + str(pc + length), temporary = True)
        gdb.execute('continue')
NextInstructionAddress()
Run Code Online (Sandbox Code Playgroud)

只需将其放入~/.gdbinit.py并添加source ~/.gdbinit.py到您的~/.gdbinit文件中即可。

在 GDB 7.7.1、Ubuntu 14.04 上测试。


Ces*_*rod 4

这实际上是一种适合我的目的的解决方法。我所做的是设置断点,这样我就可以在 gdb 上使用“继续”和“si”,并按照屏幕上打印的消息进行操作,一次一个字符。以下是步骤。

在第一次运行中,我会执行引导加载程序,因此我实际上可以检查存储指令的内存位置。

Linux 外壳:

# qemu-system-i386 -fda loader.img -boot a -s -S -monitor stdio
QEMU 1.5.0 monitor - type 'help' for more information
(qemu) 
Run Code Online (Sandbox Code Playgroud)

其他 Linux shell(某些行已被抑制 [...]):

# gdb
GNU gdb (GDB) 7.6.1-ubuntu
[...]
(gdb) target remote localhost:1234
Remote debugging using localhost:1234
0x0000fff0 in ?? ()
(gdb) set architecture i8086
[...]
(gdb) br *0x7c00
Ponto de parada 1 at 0x7c00
(gdb) c
Continuando.
Breakpoint 1, 0x00007c00 in ?? ()
(gdb) si
0x00007c03 in ?? ()
Run Code Online (Sandbox Code Playgroud)

在我运行 QEMU 监视器的终端中,我在 gdb 上的每个 si 之后找到执行此命令的指令的地址:

(qemu) x /i $eip
0x00007c03:  add    $0x120,%ax
Run Code Online (Sandbox Code Playgroud)

对于 QEMU 新手来说,x 显示寄存器的内容,/i 将其转换为指令,$eip 是指令点寄存器。通过重复这些步骤,我找到了 lodsb 和 int 10h 指令的地址:

0x00007c29:  lods   %ds:(%si),%al 
0x00007c2e:  int    $0x10 
Run Code Online (Sandbox Code Playgroud)

因此,在 gdb 上我只是为这些附加位置设置断点:

(gdb) br *0x7c29
Ponto de parada 2 at 0x7c29
(gdb) br *0x7c2e
Ponto de parada 3 at 0x7c2e
Run Code Online (Sandbox Code Playgroud)

现在我可以在 gdb 上使用“继续”(c) 和 stepi (si) 的组合,并跳过整个 BIOS 内容。

可能有更好的方法来做到这一点。然而,就我的教学目的而言,这种方法效果很好。