如何在x86实模式下正确设置SS、BP和SP?

6
我想知道如何正确地实现它,因为我现在的方式不起作用。
当将BP寄存器设置为7C00h,然后将SP寄存器设置为BP,之后推送一些ASCII,并从内存中获取数据以使用INT 10h打印时,它可以正常工作。
mov ax, 7C00h
mov bp, ax
mov sp, bp

push 'A'

mov ah, 0Eh
mov al, [7BFEh]
int 10h

实际输出结果为:

A

但当我这样做时:
mov ax, 7C00h
mov ss, ax
mov bp, ax
mov sp, bp

...

它停止工作了。中断被调用,光标移动,但没有任何打印输出。同时将SS设置为0也不起作用。请帮忙解决。


2
将SS设置为0。然后您可以将SP和BP都设置为0x7c00。接下来,您需要使用mov al, SS:[7BFEh]访问堆栈值,因为处理器隐式执行mov al, DS:[7BFEh],所以您需要进行SS覆盖。 - Michael Petch
你可能也应该将DS设置为0。 - Michael Petch
1
请确保按顺序设置SS和SP,如果您要设置SS。仅设置SS会暂停中断,直到执行下一条指令的末尾,并且在新的SS /旧的SP =可能导致灾难性的中断发生时发生故障。 - Peter Cordes
2个回答

6
看到这个7C00h的值,你可能正在开发一个引导程序。 并且你想要让堆栈位于引导程序下方
你需要做出的一个重要选择是如何处理启动时生效的分段寻址方案。

ORG 7C00h

这表示代码的第一个字节将在偏移量7C00h处。为了使其正常工作,您必须将段寄存器初始化为0000h。请记住,BIOS以线性地址00007C00h加载了引导程序,相当于段:偏移对0000h:7C00h。 如果您要更改SP寄存器,则同时更改SS段寄存器。您不知道代码开始时这些寄存器包含什么,并且应该(大多数情况下)总是同时修改这些寄存器。首先分配SS,然后直接分配SP。对SSmovpop在此和以下指令之间阻止了许多种类型的中断,因此您可以安全地设置一致的(2个寄存器的)堆栈指针。
mov ss, ax
mov bp, ax     <== This ignored the above safeguard!
mov sp, bp
ORG  7C00h

mov  bp, 7C00h
xor  ax, ax
mov  ds, ax
mov  es, ax
mov  ss, ax      ; \  Keep these close together
mov  sp, bp      ; / 

push 'A'         ; This writes 0000h:7BFEh

mov  bx, 0007h   ; DisplayPage and GraphicsColor
mov  al, [7BFEh] ; This requires DS=0
mov  ah, 0Eh     ; BIOS.Teletype
int  10h

作为替代方案,因为你已经设置了BP=7C00h,你可以通过以下方式读取堆栈中的字符:mov al, [bp-2]

ORG 0000h

这表示代码的第一个字节将位于偏移量0000h处。为了使其正常工作,您需要将某些段寄存器初始化为07C0h。请记住,BIOS将引导加载程序加载到线性地址00007C00h,这相当于段:偏移对07C0h:0000h。

由于堆栈必须位于引导加载程序下面,所以SS段寄存器将与其他段寄存器不同!

ORG  0000h

mov  bp, 7C00h
mov  ax, 07C0h
mov  ds, ax
mov  es, ax
xor  ax, ax
mov  ss, ax      ; \  Keep these close together
mov  sp, bp      ; / 

push 'A'         ; This writes 0000h:7BFEh

mov  bx, 0007h   ; DisplayPage and GraphicsColor
mov  al, [bp-2]  ; This uses SS by default
mov  ah, 0Eh     ; BIOS.Teletype
int  10h

ORG 0200h

这里提到线性地址有多种转换为段:偏移量。

ORG 0200h 表示代码的第一字节将位于 offset 0200h 处。要使其正常工作,您需要将段寄存器初始化为 07A0h。请记住,BIOS 将引导加载程序加载到线性地址 00007C00h,这相当于段:偏移量对 07A0h:0200h。

由于 512 字节的堆栈位于引导加载程序 下面,所以 SS 段寄存器将再次与其他段寄存器相等!

ORG  0200h

mov  bp, 0200h
mov  ax, 07A0h
mov  ds, ax
mov  es, ax
mov  ss, ax      ; \  Keep these close together
mov  sp, bp      ; / 

push 'A'         ; This writes 07A0h:01FEh

mov  bx, 0007h   ; DisplayPage and GraphicsColor
mov  al, [bp-2]  ; This uses SS by default
mov  ah, 0Eh     ; BIOS.Teletype
int  10h

你还可以通过mov al, [01FEh]来提取字符。


2
正确设置BP的方法是不要去管它。你没有理由浪费7个宝贵的通用寄存器中的一个来“堆栈帧指针”,以匹配你没有使用的其他语言的设计不良的调用约定。另外请注意,一些BIOS函数(例如“int 0x10, ah=0x13, write string”)使用BP传递参数。
出于同样的原因,你也没有理由在堆栈上传递参数。例如:对于你的“打印字符”代码,你可以将要打印的字符传递给AL,并删除mov al, ...以使代码更小(如果你正在编写“必须适合<512字节”的引导代码,则这点非常重要,这也是你不想浪费空间设置和销毁无用的堆栈帧指针的原因之一)。
对于ss:sp;它们应该被视为一对(描述堆栈地址的地址);你需要选择一个位置来放置堆栈(基于你计划如何使用所有其他内存)。我建议画一个小的“我的物理内存布局”图(假设你想要使用其他区域的内存进行各种操作 - 加载更多引导代码的区域,加载内核时使用的磁盘IO缓冲区的区域,放置视频模式信息的区域,放置固件内存映射的区域,...)。
请注意,(至少在我的经验中)大多数玩弄实模式引导代码的人最终都想要在实模式和受保护模式或长模式之间切换(无论他们最初是否意识到这一点);在这种情况下,将所有段寄存器设置为零会更容易,以便“段内偏移”(offset in segment)(几乎)总是等于“物理地址”(如果你不这样做,你可能会因为错误地得到分段而导致错误)。请注意,如果在实模式下SS为零(并且在受保护模式下“SS.base”为零),则可以零扩展SP(例如,“movzx esp,sp”)并继续在实模式和32位受保护模式下使用同一个堆栈。此外,在快速检查“CPU是否符合我的最低要求?”后,您可以在实模式下使用32位指令;(如果已经进行了零扩展),您可以执行像“mov al,[esp+10]”这样的操作,以获得更灵活的(32位)寻址模式。

1
“你不想浪费空间来设置和销毁无用的堆栈帧指针的部分原因是,在引导加载程序设置bp为7C00h(或BPB所在的任何位置)可以使用bp + imm8寻址访问变量,这比imm16寻址更短。” - ecm
1
@ecm:当堆栈中除了返回地址之外没有其他内容时(因为不需要本地变量,参数是通过寄存器传递的),你几乎不需要关心你永远不需要的指令的成本。 - Brendan
没错,但对于一种非常常见的加载器,您确实在堆栈中及其上方有变量。 - ecm
1
@ecm,我认为这取决于你如何开发引导程序。我通常使用bp作为一个暂存寄存器,并且不通过堆栈传递参数(因此我不需要使用以bp为基址的内存操作数)。我通常尝试通过寄存器传递所需内容。通常我最终只推送寄存器并弹出它们以在函数调用中保留寄存器。 - Michael Petch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接