Assembly 组件8086-从堆栈推送和弹出寄存器不工作
我是一个初学者,出于某种原因,推和弹出堆栈对我不起作用。我的引导加载程序:Assembly 组件8086-从堆栈推送和弹出寄存器不工作,assembly,x86-16,qemu,Assembly,X86 16,Qemu,我是一个初学者,出于某种原因,推和弹出堆栈对我不起作用。我的引导加载程序: org 0x7c00 bits 16 jmp main print: pop bx mov al, [bx] mov ah, 0eh int 10h ret main: mov bx, msg push bx call print cli hlt msg: db 'Hello World!', 0 times 510 -
org 0x7c00
bits 16
jmp main
print:
pop bx
mov al, [bx]
mov ah, 0eh
int 10h
ret
main:
mov bx, msg
push bx
call print
cli
hlt
msg: db 'Hello World!', 0
times 510 - ($-$$) db 0
dw 0xAA55
我认为这应该做的是,将地址msg
从bx
推到堆栈上,然后将其检索到bx
。然而,情况似乎并非如此<代码>'H'无法打印。而是打印一个'-'
。如果我使用msg
作为有效地址,它会起作用
编辑:正如邓肯指出的那样,调用
指令将返回地址推到堆栈顶部,这使得上述程序将该返回地址用于BIOS中断!我现在pop
将返回地址转换成dx
然后pop
转换成bx
,完成后使用bx
和jmp
的值转换成dx
org 0x7c00
bits 16
jmp main
print:
pop dx
pop bx
mov al, [bx]
mov ah, 0eh
int 10h
jmp dx
main:
mov bx, msg
push bx
call print
cli
hlt
msg: db 'Hello World!', 0
times 510 - ($-$$) db 0
dw 0xAA55
call
指令将程序计数器推到堆栈上,然后ret
从堆栈中弹出顶部值并跳转到它
因此,您不能在调用前按参数并在调用中弹出参数来将参数传递给函数,因为保存的pc会挡住它的去路
选项可能包括设置帧指针,以便访问仍在堆栈上的参数,然后将其作为返回的一部分弹出
或者对于这样简单的事情,您可以将返回地址弹出到另一个寄存器中,而不是跳转到它。从
jmp far 0:main
开始,以规范化cs:ip
,因为有些BIOS会使用cs=0
调用您,而有些则使用cs=0x07c0
调用您
print:
pop bx
mov al, [bx]
mov ah, 0eh
int 10h
ret
也许您在shell漏洞利用中见过类似的情况,但正确的用法如下所示:
call print ; this will put `msg` at top of stack!
msg: db 'H'
print:
pop bx ; load `msg` address
... no RET !! (there's nowhere to return to)
这是一个技巧,当您不知道代码将位于何处时,如何将正确的偏移量放入堆栈中(shell攻击通常会进入一些缓冲区溢出区域,带有一些随机地址)
这在引导加载程序中是不需要的,因为您位于固定位置0000:7C00
,并且可以这样进行组装,所以对于像print
这样的自定义过程,使用一些自定义的“通过寄存器传递参数值”调用约定是可以的,根本不涉及堆栈(除了通过call+ret
对使用它存储返回地址之外)
在使用堆栈之前,也要初始化堆栈,即在main的开头,您可能需要执行以下操作,例如:
main:
; set ss:sp to 0000:7C00 (so you have about ~29kB of RAM for stack)
xor ax,ax
mov ss,ax ; disables interrupts for 1 more instruction
mov sp,0x7C00 ; so sp set must follow the `ss` setup
当然,不要将它用于任何深度递归,29kB是一个小堆栈(但对于合理编写的引导加载程序来说已经足够了,只需从磁盘加载一些内核,实际上100-200B的堆栈就足够了)
当然可以设置
ds
!就像您设置mov al[bx]
像ds
一样,已经设置好了。在您的情况下,最简单的事情是将所有内容保持在0000:7C00
,因此在mov ss,ax
之前,您也可以执行mov ds,ax
。如果您知道返回地址的长度和/或其他推送值,您可以直接寻址,例如:mov bx,2[sp]
在汇编这样的语言中,这是一个很好的实践吗?我有OOP背景。@ArjavGarg在尝试编写像bootloader这样棘手的东西之前,首先学习汇编基础知识是一个很好的实践,在这种情况下,您从一开始就要与非常恶劣的环境作斗争(初始状态非常模糊且有缺陷,因为BIOS供应商不太关心,加上除了BIOS服务外,几乎没有可调用的服务)。我仍然不明白,编写自己的引导加载程序甚至裸机操作系统,而不是使用稳定的操作系统环境来编写一些有趣的东西,比如玩一些图形和使用GUI调试器,有什么吸引力。操作系统开发更复杂。我已经完成了OpenGL和2d Java图形。这似乎是下一个逻辑问题step@ArjavGarg对我来说,下一个逻辑步骤听起来像是在汇编中进行OGL+2D,因为您已经知道了原理,可以专注于学习汇编本身,熟悉基本指令、算术、内存管理、堆栈使用等……然后再次启动加载程序(至少是初始部分)必须在16b实模式下,这是另一个PITA,所以…是的,这没关系,如果你想做引导加载程序,它很好,只是不要期望它很容易,它会咬你,比任何Java都要多出很多次,需要更多的注意细节和微观管理一切。当然,还有一个问题是dx
在int10h
调用中保留。该“修复”在我看来是不合理的复杂和脆弱。如果有什么问题的话,您可以在pop bx
之后立即执行push dx
,然后使用ret
,因此您不必担心dx
,但是如果您坚持通过堆栈传递参数,您应该执行详细的push bp
mov bp,sp
naive-C堆栈frame thing(然后mov bx,[bp+4]
是第一个参数),因为这对任何有经验的ASM程序员来说都很容易阅读,所以以后帮助你会更容易。@ArjavGarg花了几周时间阅读并尝试其他源代码。你甚至可能想阅读grub
,它必须在真实场景中工作。我自己从来没有做过引导加载程序,对我来说太无聊了,我更喜欢real应用程序编码,所以我不知道编写bootloader时可能出现的所有问题,但肯定不可能将它们放在简短的答案中,因为它至少有几十页的文本。您所拥有的可能已经在qemu或bochs下工作,我想这两个选项都可以通过某种方式进行调试?bochs是肯定的。