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

How to properly setup SS, BP and SP in x86 Real Mode?

我想知道如何正确地做到这一点,因为我这样做的方式行不通。

当用 7C00h 设置 BP 寄存器时,然后用 BP 设置 SP 寄存器,然后压入一些 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 也不起作用。请伸出援手。

查看 7C00h 值,您可能正在使用引导加载程序。
并且您希望堆栈驻留在引导加载程序 下方

您必须做出的一个重要选择是您希望如何继续使用在启动时生效的分段寻址方案。

ORG 7C00h

这表示代码的第一个字节将位于偏移量 7C00h 处。为了使其正常工作,您必须将段寄存器初始化为 0000h。请记住,引导加载程序由 BIOS 在线性地址 00007C00h 加载,相当于 segment:offset 对 0000h:7C00h.
如果您要更改 SP 寄存器,则还要更改 SS 段寄存器。您不知道它在代码开头包含什么,您应该(大多数)始终一前一后地修改这些寄存器。先赋值SS,赋值后直接赋值SPmovpopSS 会阻止此指令和后续指令之间的多种中断,以便您可以安全地设置一致的(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].

组织 0000h

这表示代码的第一个字节将位于偏移量 0000h。为了使其正常工作,您必须将一些段寄存器初始化为 07C0h。请记住,引导加载程序由 BIOS 在线性地址 00007C00h 加载,相当于 segment:offset 对 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

组织 0200h

我加入这个是为了表明线性地址有很多翻译成 segment:offset。

ORG 0200h 表示代码的第一个字节将位于偏移量 0200h 处。为了使其正常工作,您必须将段寄存器初始化为 07A0h。请记住,引导加载程序由 BIOS 在线性地址 00007C00h 加载,相当于 segment:offset 对 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]获取字符。

正确设置BP的方法是不打扰。您没有理由浪费 "stack frame pointer" 的 7 个宝贵的通用寄存器之一来匹配您未使用的其他语言的设计不佳的调用约定。另请注意,某些 BIOS 函数(例如 "int 0x10, ah=0x13, write string")使用 BP 来传递参数。

同理,你也没有理由在栈上传递参数。例如;对于您的 "print character" 代码,您可以在 AL 中传递要打印的字符并删除 mov al, ... 以使代码更小(如果您正在编写 "must fit in < 512 bytes" 引导,这非常重要代码,这也是您不想浪费 space 设置和销毁无用堆栈帧指针的部分原因。

对于ss:sp;它们应该被视为一对(描述堆栈的地址);并且您需要为您想要的堆栈选择一个位置(基于您计划如何使用所有其他内存)。我建议画一点 "my physical memory layout" 图(假设您想要使用内存的其他区域来做各种事情 - 加载更多引导代码的区域,加载内核时使用的磁盘 IO 缓冲区区域,某处放置视频模式信息,某处放置固件的内存映射,...)。

请注意(至少根据我的经验)大多数人在使用实模式启动代码时最终都想在实模式和保护模式或长模式之间切换(无论他们最初是否意识到这一点);在这种情况下,将所有段寄存器设置为零要容易得多,这样 "offset in segment" (几乎)总是等于 "physical address" (如果你不这样做,你可能最终会导致错误不小心弄错了分段)。请注意,如果 SS 在实模式下为零(并且 "SS.base" 在保护模式下为零),您可以零扩展 SP(例如“movzx esp,sp”)并继续对两个实模式使用相同的堆栈模式和 32 位保护模式。此外,(在快速 "does the CPU meet my minimum requirements?" 检查之后)您可以在实模式下使用 32 位指令;并且(如果 ESP 已被零扩展)您可以在实模式下执行类似(例如)“mov al,[esp+10]”的操作 if/when 您需要更灵活的(32 位)寻址模式。