x86 实模式下的段大小 [英] Segment size in x86 real mode

查看:22
本文介绍了x86 实模式下的段大小的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对实模式下段的大小有一个疑问,因为它们不能超过 64K,但可以是 小于那个. 我的问题是这些段大小和基地址是如何初始化的?就像在保护模式下有 GDT 和 LDT.实模式段也可以重叠,不相交或相邻.就像 BIOS 有一些保留区域用于特定的东西,比如引导代码、视频缓冲区等,汇编程序需要做这样的事情吗?

解决方案

实模式下的段限制是 64k,即使在可以通过前缀使用 32 位地址大小的 386 或更高版本的 CPU 上也是如此

强>.例如mov ax, [edx + ecx*4] 在实模式下仍然限制为 64 kiB 的偏移量.

如果超过此限制,则会引发 #GP 异常.(或者 #SS 如果段是 SS).

16 位地址大小不能超过 64k 段限制,因为像 [bx + si] 这样的寻址模式以 16 位换行.因此,只有在实模式下使用 0x67 地址大小前缀(在 386 中添加)的代码才能遇到段限制.8086 不必检查限制,只需添加 Sreg <<4 到寻址模式的偏移量,隐式限制为 64k.

在可能的最高地址的 64k 内开始的段在 8086 上以 1MiB 回绕,如果 A20 被禁用,则在以后的 CPU 上.否则,对于类似 FFFF:FFFF seg:off = 0x10ffef 线性的地址,它们会扩展超过 1MiB.请参阅什么是分段以及如何在 8086 模式下解决这些问题?

<小时>

如果切换到保护模式并设置段寄存器,CPU 会在内部缓存段描述(基本 + 限制),即使切换回 16 位实模式也是如此.这种情况称为虚幻模式.

在 16 位模式下写入段寄存器只会将段基址设置为 value <<<;4 不改变限制,所以 unreal 模式对于 CS 以外的段来说有些持久.CS:EIP 是特殊的,特别是如果您需要避免在从中断或其他情况返回时将 EIP 截断为 16 位.请参阅 osdev wiki 链接.

push/pop/call/ret 根据B使用SS:ESPSS:SP> 当前堆栈段描述符中的标志;地址大小前缀只影响诸如 push word [eax]push word [si] 之类的东西.

在实模式下将值写入段寄存器时,将忽略 GDT/LDT.该值直接用于设置缓存段基数,根本不作为选择器.

(每个段都是独立的;虚幻模式不是像保护与真实那样的实际模式;CPU 处于实模式.例如,写入 FS 寄存器会使该段恢复正常的实模式行为,但是不会改变其他的.它只是在具有更大限制的缓存段描述符的实模式下的名称,因此您可以使用 32 位地址大小来获得更大的平面地址空间.通常使用 base=0 和 limit=4G)

AFAIK,无法在实模式下查询段的内部限制值.lsl 直接从内存中的GDT/LDT,不是来自内部值(所以不是你想要的),反正在实模式下也不可用.

有关有意或无意地将段从虚幻模式中取出的更多详细信息,请参阅对此答案的评论.

286 和 386 CPU 支持 a LOADALL 指令从实模式设置段限制,但后来的 CPU 没有它.评论者说,SMM(系统管理模式)或许可以在现代 x86 上做类似的事情.

I have one doubt regarding the size of segments in real mode as they can't be more than 64K but can be less than that. My question is how these segment size and base address is initialized ? Like there are GDT's and LDT's in protected mode.Real mode segments can also overlapped,disjoint or adjacent.Like BIOS has some reserved area for specific things like boot code,video buffer etc does assembly programs need to do something like that ?

解决方案

The segment limit in real mode is 64k, even on a 386 or later CPU where you can use 32-bit address-size via prefixes. e.g. mov ax, [edx + ecx*4] is still limited to offsets of 64 kiB in real mode.

If you exceed this limit, it raises a #GP exception. (Or #SS if the segment was SS).

16-bit address-size can't exceed 64k segment limits, because addressing modes like [bx + si] wrap at 16 bits. So only code using the 0x67 address-size prefix (added in 386) in real mode can run into segment limits. 8086 didn't have to check limits, just add Sreg << 4 to the offset from an addressing mode, making the limit implicitly 64k.

Segments that start within 64k of the highest possible address wrap around at 1MiB on 8086, and on later CPUs if A20 is disabled. Otherwise they extend past 1MiB for an address like FFFF:FFFF seg:off = 0x10ffef linear. See What are Segments and how can they be addressed in 8086 mode?


If you switch to protected mode and set a segment register, the CPU keeps the segment description (base + limit) cached internally, even across switching back to 16-bit real mode. This situation is called unreal mode.

Writing to a segment register in 16-bit mode only sets the segment base to value << 4 without changing the limit, so unreal mode is somewhat durable for segments other than CS. CS:EIP is special, especially if you need to avoid truncating EIP to 16 bits when returning from interrupts or whatever. See that osdev wiki link.

push/pop/call/ret use SS:ESP or SS:SP according to the B flag in the current stack-segment descriptor; the address-size prefix only affects stuff like push word [eax] vs. push word [si].

The GDT / LDT are ignored when you write a value to a segment register in real mode. The value is used directly to set the cached segment base, not as a selector at all.

(Each segment is separate; unreal mode isn't an actual mode like protected vs. real; the CPU is in real mode. Writing the FS register, for example, puts that segment back into normal real-mode behaviour, but doesn't change the others. It's just a name for being in real mode with cached segment descriptors with larger limits, so you can use 32-bit address-size for a larger flat address space. Often with base=0 and limit=4G)

AFAIK, there's no way to query the internal limit value of a segment in real mode. lsl loads the segment-limit value directly from a descriptor in the GDT / LDT in memory, not from the internal value (so it's not what you want), and it's not available in real mode anyway.

See comments on this answer for more details about taking segments out of unreal mode intentionally or unintentionally.

286 and 386 CPUs supported a LOADALL instruction which could set segment limits from real mode, but later CPUs don't have it. Commenters say that SMM (system management mode) may be able to do something similar on modern x86.

这篇关于x86 实模式下的段大小的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆