在 x86 实模式下调用不保存 return 地址
call in x86 real mode does not save return address
我正在尝试编写一个实模式引导加载程序,但我目前在尝试启用 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
。然后处理器确实执行了一个近调用来纠正这里的地址 但是 没有 return 地址被推到堆栈上(我已经用 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
通常人们可能会关闭这个问题,因为它是由印刷错误引起的,但错误一开始并不一定很明显。在调试器中必须密切注意观察正在执行的指令。
这让我摸不着头脑,因为当我在调试器中查看序列时:
push ds
push si
push es
push di
; Snip other code
pop di
pos es
pop si
pop ds
ret
只显示处理器执行 3 POP
s 和一个 ret
显然有 4 POP
指令。由于处理器没有执行足够的 POP,return 地址不正确并且 ret
return 到内存的错误部分并导致意外行为。
这个问题是相当微不足道的,由于运气不好,一条指令生成没有错误,但不是你想要的指令。如果你仔细观察,这就是罪魁祸首:
pos es
有错字。 POS
应该是 POP
。起初我的大脑没有捕捉到它。 pos
被视为标签,而 es
是段覆盖,因此可以单独出现在一行中。这导致生成指令 es pop si
。
显然解决方法是将其更改为:
pop es
我正在尝试编写一个实模式引导加载程序,但我目前在尝试启用 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
。然后处理器确实执行了一个近调用来纠正这里的地址 但是 没有 return 地址被推到堆栈上(我已经用 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
通常人们可能会关闭这个问题,因为它是由印刷错误引起的,但错误一开始并不一定很明显。在调试器中必须密切注意观察正在执行的指令。
这让我摸不着头脑,因为当我在调试器中查看序列时:
push ds
push si
push es
push di
; Snip other code
pop di
pos es
pop si
pop ds
ret
只显示处理器执行 3 POP
s 和一个 ret
显然有 4 POP
指令。由于处理器没有执行足够的 POP,return 地址不正确并且 ret
return 到内存的错误部分并导致意外行为。
这个问题是相当微不足道的,由于运气不好,一条指令生成没有错误,但不是你想要的指令。如果你仔细观察,这就是罪魁祸首:
pos es
有错字。 POS
应该是 POP
。起初我的大脑没有捕捉到它。 pos
被视为标签,而 es
是段覆盖,因此可以单独出现在一行中。这导致生成指令 es pop si
。
显然解决方法是将其更改为:
pop es