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

标签 assembly x86 x86-16 bootloader real-mode

我正在尝试编写实模式引导加载程序,但目前在尝试启用 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

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

编辑:请注意,我的汇编代码开头没有 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

最佳答案

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

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

 push ds
 push si
 push es
 push di

 ; Snip other code

 pop di
 pos es
 pop si
 pop ds
 ret

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

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

 pos es

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

显然解决方法是将其更改为:

 pop es

关于assembly - x86实模式调用不保存返回地址,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64844975/

相关文章:

x86 - 当预取队列中只剩下 1 个字节时,8086 是否启动 1 个字节的代码获取?

assembly - 从汇编中的字符串中提取子字符串

assembly - 如何在没有 c 库中的 printf 的情况下在汇编级编程中打印整数? (itoa,整数到十进制的 ASCII 字符串)

assembly - 在汇编编程中如何将方法的结果传递回堆栈?

linux - 为什么我无法在自修改代码中单步执行 aeskeygenassist 指令?

assembly - 获取操作码的简单方法

X86 从 stdin 读取并写入 stdout,无需引用标准库

assembly - 为什么汇编语言运算时要在字符串后面加-2?

assembly - x64 似乎不像 x86 那样接受 END 指令中的入口点。语法上的这种变化有什么具体原因吗?

assembly - movl $_start, %eax 是什么意思?