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

时间:2014-06-30 13:42:25

标签: 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

我可以逐步完成程序,看看寄存器是否正在改变,以及正在执行的指令,使用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

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

谢谢!

2 个答案:

答案 0 :(得分:3)

这实际上是一个符合我目的的工作。我所做的是设置断点,以便我可以使用"继续"在gdb和" si"并按照屏幕上打印的信息,一次一个字符。以下是步骤。

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

Linux shell:

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

其他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 ?? ()

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

(qemu) x /i $eip
0x00007c03:  add    $0x120,%ax

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

0x00007c29:  lods   %ds:(%si),%al 
0x00007c2e:  int    $0x10 

所以,在gdb上我只是设置了这些附加位置的断点:

(gdb) br *0x7c29
Ponto de parada 2 at 0x7c29
(gdb) br *0x7c2e
Ponto de parada 3 at 0x7c2e

现在我可以结合使用"继续" (c)和gdb上的stepi(si)并跳过整个BIOS的东西。

可能有更好的方法来做到这一点。但是,出于我的教学目的,这种方法效果很好。

答案 1 :(得分:2)

我使用以下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()

将其放入~/.gdbinit.py并将source ~/.gdbinit.py添加到~/.gdbinit文件中。

在GDB 7.7.1,Ubuntu 14.04上测试。