x86实模式调用不保存返回地址

Pet*_*ter 3 x86 assembly real-mode bootloader x86-16

我正在尝试编写一个实模式引导加载程序,但我目前在尝试启用 A20 线时遇到了问题。到目前为止,这是我的代码,我正在使用 NASM 进行组装:

[bits 16]

[global _start]

jmp _start

bios_print:
 lodsb
 test al, al
 jz bios_print_done
 mov ah, 0x0E
 mov bh, 0
 int 0x10
 jmp bios_print

bios_print_done:
 ret

a20_is_enabled:
 push ds
 push si
 push es
 push di

 xor ax, ax
 mov ds, ax
 mov si, BOOT_ID_OFFS

 mov ax, BOOT_ID_OFFS_PLUS_1MB_SEGM
 mov es, ax
 mov di, BOOT_ID_OFFS_PLUS_1MB_OFFS

 cmp word [es:di], BOOT_ID

 mov ax, 1
 jne a20_is_enabled_done

 mov ax, word [ds:si]
 xor ax, ax
 mov [ds:si], ax

 cmp word [es:di], BOOT_ID

 push ax
 xor ax, ax
 mov [ds:si], ax
 pop ax

 mov ax, 1
 jne a20_is_enabled_done

 mov ax, 0

a20_is_enabled_done:
 pop di
 pos es
 pop si
 pop ds

 ret

a20_enable_bios:
 mov ax, 0x2403
 int 0x15
 jc a20_enable_bios_failure
 test ah, ah
 jnz a20_enable_bios_failure

 mov ax, 0x2401
 int 0x15
 jc a20_enable_bios_failure
 test ah, ah
 jnz a20_enable_bios_failure

 mov ax, 1
 jmp a20_enable_bios_done

a20_enable_bios_failure:
 mov ax, 0

a20_enable_bios_done:
 ret

a20_enable:

 push si

 mov si, word MSG_A20_TRY_BIOS
 call bios_print

 pop si

 call a20_enable_bios

 test ax, ax
 jz a20_enable_failure

 call a20_is_enabled

 test ax, ax
 jnz a20_enable_success

a20_enable_failure:

 push si

 mov si, word MSG_A20_FAILURE
 call bios_print

 pop si

 mov ax, 0
 jmp a20_enable_done

a20_enable_success:

 push si

 mov si, word MSG_A20_SUCCESS
 call bios_print

 pop si

 mov ax, 1

a20_enable_done:
 ret

_start:
 xor ax, ax
 mov ds, ax

 cld

 cli

 push si

 mov si, word MSG_GREETING
 call bios_print

 pop si

 call a20_enable

 test ax, ax
 jz boot_error

 ; TODO

boot_error:
 jmp boot_error

BOOT_ID equ 0xAA55
BOOT_ID_OFFS equ 0x7DFE
BOOT_ID_OFFS_PLUS_1MB_SEGM equ 0xFFFF
BOOT_ID_OFFS_PLUS_1MB_OFFS equ BOOT_ID_OFFS + (0x1 << 20) - (BOOT_ID_OFFS_PLUS_1MB_SEGM << 4)

MSG_GREETING db 'Hello from the bootloader', 0xA, 0xD, 0
MSG_A20_TRY_BIOS db 'Trying to enable A20 line via BIOS interrupt', 0xA, 0xD, 0
MSG_A20_SUCCESS db 'Successfully enabled A20 line', 0xA, 0xD, 0
MSG_A20_FAILURE db 'Failed to enable A20 line', 0xA, 0xD, 0

times 510-($-$$) db 0
dw BOOT_ID
Run Code Online (Sandbox Code Playgroud)

问题是a20_is_enabled应该检查 A20 线在a20_enable_bios通过 BIOS 中断激活后是否启用的功能(我知道这不是万无一失的,更多代码将在此处跟进)。当我调试代码时,一切似乎都很好,直到call a20_is_enabled. 然后处理器确实执行了对此处正确地址的近调用,没有返回地址被推送到堆栈上(我已经用 gdb 进行了验证)。所以当在ret中执行时a20_is_enabled,指令指针被设置为某个垃圾地址。为什么是这样?

编辑:请注意,ORG 0x7C00在我的汇编代码的开头没有。这是因为我首先创建了一个 elf 文件,以便我可以使用 gdb 调试我的代码,但它不能很好地使用ORG,所以我实际上是这样做的:

nasm -f elf32 -g -F dwarf boot.asm -o boot.o
ld -Ttext=0x7c00 -melf_i386 boot.o -o boot.elf
objcopy -O binary boot.elf boot.bin
Run Code Online (Sandbox Code Playgroud)

Mic*_*tch 7

通常人们可能会关闭这个问题,因为它是由印刷错误引起的,但错误一开始不一定很明显。必须在调试器中密切注意观察正在执行的指令。

这让我摸不着头脑,因为当我在调试器中查看序列时:

 push ds
 push si
 push es
 push di

 ; Snip other code

 pop di
 pos es
 pop si
 pop ds
 ret
Run Code Online (Sandbox Code Playgroud)

只显示处理器执行 3 POPs 和 aret当明显有 4POP条指令时。由于处理器没有执行足够的 POP,返回地址不正确并ret返回到错误的内存部分并导致意外行为。

这个问题相当微不足道,而且由于运气不好,指令没有错误地产生,但不是您想要的指令。如果你仔细观察,这就是罪魁祸首:

 pos es
Run Code Online (Sandbox Code Playgroud)

有一个错字。POS应该是POP。一开始我的大脑没有抓住它。pos被视为标签并且es是段覆盖,因此可以单独出现在一行上。这导致指令es pop si被产生。

显然,修复方法是将其更改为:

 pop es
Run Code Online (Sandbox Code Playgroud)

  • 不错的发现。多年来我一直想在 NASM 中获得对此的可选警告。请参阅[添加标签无冒号警告](https://bugzilla.nasm.us/show_bug.cgi?id=3392632)。 (4认同)
  • @ecm:有趣的是,当我弄清楚它时,我正要发表评论并指出问题并将其作为一个简单的印刷错误关闭它,并认为我花了足够的时间在这个愚蠢的问题上,也许它是值得给未来一个答案。我不喜欢没有冒号的标签。MASM 没有帮助,因为我不喜欢数据和代码中存在冒号的规则。无论如何,我希望看到 BOCH 向我们展示更好的解码和 NASM 以及更多警告,我认为您的补丁对于这种情况和其他情况是个好主意。 (3认同)
  • 100%同意。如果 NASM 默认情况下没有警告您这一点,那就糟糕了。如果它*根本*不能警告您这一点,那就太糟糕了。“label: prefix”单独出现在一行似乎不太可能,如果你确实想要的话,你可以用冒号来明确说明。在我看来,“标签前缀”永远不会在正常的源代码中单独出现在一行上,即使标签不容易被误认为是一条指令。简单地禁用接受非“:”标签的选项也可能有用,或者相反,@ecm,可以让汇编器强制执行标准 NASM 样式。 (3认同)
  • 天啊,我看了几分钟,还是没看到。很棒的收获。 (2认同)
  • @ecm:我同意。关于带有指令的行上没有冒号的标签的警告在这里会很方便,哈哈。这是我醒来后的第一个问题,反复查看原始代码后我并没有意识到这个错字。BOCHs 没有帮我任何忙,只是告诉我有 3 个弹出指令。这对我来说一直是个谜,直到我对代码进行了 objdump,并且 objdump 很友善地指出指令是“es pop si”。如果 BOCH 告诉我编码的内容会很方便。当我看到并最终仔细观察时,问题就很明显了 (2认同)