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

Posted

技术标签:

【中文标题】如何在 x86 实模式下正确设置 SS、BP 和 SP?【英文标题】:How to properly setup SS, BP and SP in x86 Real Mode? 【发布时间】:2021-12-28 06:44:49 【问题描述】:

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

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

实际输出是

一个

但是当我这样做时:

mov ax, 7C00h
mov ss, ax
mov bp, ax
mov sp, bp

...

它停止工作。调用中断,光标移动,但没有打印任何内容。将SS 设置为 0 也不起作用。请帮忙。

【问题讨论】:

设置 SS 为 0。然后你可以设置 SP 为 0x7c00 和 BP 为 0x7c00。然后您必须使用mov al, SS:[7BFEh] 访问堆栈值,因为处理器正在隐式执行mov al, DS:[7BFEh],因此您需要进行SS 覆盖。 您或许也应该考虑将 DS 设置为 0。 如果你设置了 SS,请确保你设置 SS 然后 SP 一起,按这个顺序。设置 SS 只会推迟中断直到下一条指令结束,并且有一个新 SS / 旧 SP 的中断进入 = 可能的灾难。 【参考方案1】:

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

出于同样的原因,您也没有理由在堆栈上传递参数。例如;对于您的“打印字符”代码,您可以在AL 中传递要打印的字符并删除mov al, ... 以使代码更小(如果您正在编写“必须适合

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

请注意(至少根据我的经验),大多数使用实模式启动代码的人最终都希望在实模式和保护模式或长模式之间切换(无论他们最初是否意识到);在这种情况下,将所有段寄存器设置为零要容易得多,这样“段中的偏移量”(几乎)总是等于“物理地址”(如果你不这样做,你最终可能会遇到由不小心分割错误)。请注意,如果 SS 在实模式下为零(并且“SS.base”在保护模式下为零),您可以零扩展 SP(例如“movzx esp,sp”)并继续对实模式和 32- 使用相同的堆栈位保护模式。此外,(在快速“CPU 是否满足我的最低要求?”检查后)您可以在实模式下使用 32 位指令;并且(如果ESP 已被零扩展)如果/当您需要更灵活的(32 位)寻址模式时,您可以在实模式下执行诸如(例如)“mov al,[esp+10]”之类的操作。

【讨论】:

“您不想浪费空间设置和破坏无用的堆栈帧指针的部分原因”实际上在引导加载程序中将 bp 设置为 7C00h(或 BPB 所在的任何位置)允许使用bp + imm8 寻址,比 imm16 寻址短。 @ecm:当堆栈只包含返回地址时(因为不需要在寄存器中传递局部变量和参数),几乎没有理由关心您不需要多少指令成本。 对,但是对于一种非常常见的加载器类型,您确实在堆栈中和堆栈之上有变量。 @ecm,我认为这真的取决于你如何开发引导加载程序。我通常使用bp 作为临时寄存器,并且不通过堆栈传递参数(所以我不需要使用以bp 为基础的内存操作数)。我通常尝试通过寄存器传递所需的内容。通常我最终只推送寄存器并弹出它们以在函数调用中保留寄存器。【参考方案2】:

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

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

ORG 7C00h

这表示代码的第一个字节将位于偏移量 7C00h。为此,您必须将段寄存器初始化为 0000h。请记住,引导加载程序是由 BIOS 在线性地址 00007C00h 处加载的,这相当于段:偏移量对 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] 读取堆叠字符。

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

组织 0200h

我包含这个是为了表明线性地址对段:偏移量有很多转换。

ORG 0200h 表示代码的第一个字节将位于偏移量 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] 获取字符。

【讨论】:

以上是关于如何在 x86 实模式下正确设置 SS、BP 和 SP?的主要内容,如果未能解决你的问题,请参考以下文章

实模式下的影子寄存器

01保护模式

实模式与保护模式详解二:地址映射

实模式与保护模式下的分段分页机制

X86保护模式入门简介

《80X86汇编语言程序设计教程》十九 操作系统类指令与输入输出保护