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

dvtswwa3  于 2022-11-13  发布在  iOS
关注(0)|答案(3)|浏览(146)

出于教学目的,我将此引导加载程序从mikeos.berlios.de/write-your-own-os.html改写为专门在地址0x 7 c 00加载。
最后的代码是:

[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监视器(info寄存器、x /i $eip等)进行检查。
在我进入10小时间隔后(BIOS打印例程),事情变得有点奇怪。如果我一次执行500条指令,我能看到“我”这个字(我的文本字符串的第一个字符)打印在屏幕上。所以我再次重新启动,走了400步(si 400)然后我一步一步地做,看看“我”在哪个确切的步骤中被打印出来。它从来没有发生过。我实际上一步一步地走了200步,什么也没有发生。当我一次踏出100步(si 100)时,我又在屏幕上打印出了“我”。
所以,我想知道是否有一个定时问题(一些系统中断妨碍了我一步一步的调试)。
无论如何,有没有办法跳过整个BIOS中断和其他功能,只是回去继续步进引导加载程序代码?正如评论中Peter Quiring所建议的,我尝试使用next。这没有工作。

(gdb) next 
Cannot find bounds of current function

所以我试了一下nexti,它的行为就像si一样。
谢谢你!

hk8txs48

hk8txs481#

我已经用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上进行了测试。

sirbozc5

sirbozc52#

这实际上是一个符合我的目的的解决方案。我所做的是设置断点,这样我就可以在gdb上使用“continue”和“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)

其他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 10 h指令的地址:

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

现在,我可以在gdb上使用“continue”(c)和stepi(si)的组合,并跳过整个BIOS的内容。
可能有更好的方法来做到这一点。然而,就我的教学目的而言,这种方法效果相当好。

gzjq41n4

gzjq41n43#

实际上QEMU已经考虑到了这种情况,QEMU gdbstub内部有两个标志:NOIRQ和NOTIMER。这两个标志将阻止irq被注入guest虚拟机并暂停单步模式下的计时器时钟仿真。您可以通过以下方式查询qemu的功能:

(gdb) maintenance packet qqemu.sstepbits
sending: "qqemu.sstepbits"
received: "ENABLE=1,NOIRQ=2,NOTIMER=4"

对于KVM,您的主机可能需要linux内核v5.12+来支持NOIRQ,它实现了ioctl KVM_CAP_SET_GUEST_DEBUG2。
但请注意,NOIRQ只会防止IRQ,仍会注入例外/陷阱。

相关问题