【问题标题】:How addressing works in assembly x86 Real Mode? Why does label return different values?在汇编 x86 实模式下寻址如何工作?为什么标签返回不同的值?
【发布时间】:2021-01-15 03:42:43
【问题描述】:

我有两种用于引导加载程序的代码变体(它应该在引导加载程序代码的 512b 之后移动 1kb 堆栈)。 起始物理地址始终为 0x7c00(标签“start”)。 BIOS 将引导加载程序代码复制到 RAM 中。 当我使用“MOV SP,开始+1024+512”时:

  1. SP 将是 0x7c00 + 1024 + 512,因为物理地址应该是 SS:SP = 0
  2. SP 将是 0 + 1024 + 512,因为物理地址应该是 SS:SP = 0x07c0

但是如果我写“jmp start”处理器将总是去地址0x7c00。它会计算

  1. 0 + 0x07c0
  2. 0x07c0

为什么'start'在MOVLMP中返回不同的值?还是一样,但在 MOV 处理器中不添加段,而在 JMP 中添加?在这种情况下,“开始”是否会根据 DS:SI 而不是 CS:IP 计算,这是否也是真的? 另一个例子。如果我最后添加代码

mov SI, main
lodsb ;write data from main to AL

处理器将始终转到完整的物理地址段+偏移量,并在寄存器 AL 中获取值“S”。 SI 将仅等于偏移量吗?并且处理器会在 lodsb 执行期间添加段?

补充问题:

处理器如何执行“jmp main”?该指令位于“mov ds, ax”之上。因此 2 变体中的代码有错误,但它可以工作。

BIOS加载bootloader时CS寄存器的默认值是多少?显然 CS:IP 应该是 0x7c00。

1 个变种

[bits 16]
[org 0x7c00]
start:  ;offset= 0x7c00
jmp main
db    "Some data" ;actually fake BIOS Parameter Block(BPB)
main: 
mov ax, 0 
mov ds, ax ; data segment =0.
mov ss, ax ; stack segment = 0
mov sp, start+1024+512 ;stack pointer = 0x7c00+1024+512

2 个变种

[bits 16]
[org 0x0000]
start:  ;offset= 0x0000
jmp main
db    "Some data" ;actually fake BIOS Parameter Block(BPB) 
main:
mov ax, 0x07c0 
mov ds, ax ; data segment =0x07c0.
mov ss, ax ; stack segment =0x07c0.
mov sp, start+1024+512 ;stack pointer = 0+1024+512

【问题讨论】:

  • 你能多关注一下吗?这里似乎至少有 10 个问题,大约 9 个太多了,如果不写一整本关于 8086 内存架构的书,我什至不知道如何开始回答。
  • 但也许stackoverflow.com/a/43397557/634919 会有所帮助。一些 BIOS 将 CS:IP 设置为 0000:7c00,其他设置为 07c0:0000。标签start 发生的情况与此无关,取决于您在源代码中为org 选择的值。您确实需要在脑海中将段和偏移量分开,因为这就是 CPU 所做的;它只会在最后阶段将它们转换为物理地址。
  • jmp main 很可能是相对或“短”的跳跃。汇编器计算jmp main 指令本身和标签main 之间的字节数,并将差值编码到地址中​​。执行时,CPU 将该值添加到程序计数器 IP,并让 CS 保持不变。因此,无论段寄存器如何设置,无论代码在内存中加载到什么位置,它都能正常工作。
  • 谢谢@NateEldredge 您回答了我所有的问题=) 额外感谢 jmp。我忘记了这个机制。
  • 更正:它是jmp main之后的next指令与标签main之间的字节数。短直接跳转和近直接跳转都是这种情况,只是差异是否适合 8 位符号扩展立即数或 16 位的问题。

标签: assembly x86 nasm bootloader real-mode


【解决方案1】:

... 总是去地址 0x7c00。它会计算...

在分段内存模型中,您不仅应该考虑有效(物理)地址,还必须始终将地址视为实模式或 16 位保护模式下的 (16+16) 位值或32 位保护模式下的 (16+32) 位值。

假设您的程序包含指令mov al, cs:[100h]

这条指令将从地址​​CS:0x100 读取一些字节,实际上是(CS<<4)+0x100

如果执行跳转到0x7C0:0,该指令将访问地址(0x7C0<<4)+0x100=0x7D00的内存;如果你跳转到0:0x7C00,这条指令将访问地址(0<<4)+0x100=0x100的内存。

这意味着如果你跳转到0x7C0:00:0x7C00,你的程序会做一些不同的事情。为此,据说0x7C0:00:0x7C00是两个不同的地址。

假设main位于物理地址0x7C40

这意味着main的地址既不是0x7C40也不是0x40,而是0:0x7C40(在“变体1”中)或者是0x7C0:0x40(在“变体2”中),因为您总是必须将地址指定为一对段和偏移量。

在保护模式下的分段内存模型中,这更加复杂,使用正确的段更为重要!

SI 将仅等于偏移量吗?
同样,在这种情况下,“开始”是否会根据 DS:SI 而不是 CS:IP 计算?

lodsb指令访问地址DS:SIstosb访问ES:DI

这意味着SI 只保存偏移量,DS 只保存段。

变体 1 和 2 会将不同的值加载到 SI 寄存器,因为在一个变体中 main 位于地址 0:0x7C40(这意味着:SI=0x7C40)和 0x7C0:0x40 (SI=0x40)在另一个变体中。

所以在变体 1 中,您必须设置 DS=0,在变体 2 中,您必须设置 DS=0x7C0

在一种情况下,lodsb 将访问地址 0:0x7C40,在另一种情况下,lodsb 将访问地址 0x7C0:0x40。在这两种情况下,都会访问 RAM 中的相同字节:物理地址0x7C40

处理器如何执行“jmp main”?该指令位于“mov ds, ax”之上。因此 2 变体中的代码有错误,但它可以工作。

JMP 指令有两种变体:

一种变体不会将固定值写入IP 寄存器,但会向IP 寄存器添加一些常数值。因此,如果将0x40 添加到0x7C0:0,代码将在0x7C0:0x40 继续执行。如果将0x40 添加到0:0x7C00,代码将在0:0x7C40 继续执行。在这两种情况下,下一条指令都位于物理地址0x7C40。 (可能jmp main 就是这个变种。)

另一种变体采用一对段和偏移量作为参数。您不能跳转到地址0x7C40,但您可以跳转到地址0:0x7C40 或地址0x7C0:0x40

BIOS加载bootloader时CS寄存器的默认值是多少?

有几个 BIOS 会跳转到0x7C0:0,但标准似乎是0:0x7C00

因此,许多引导加载程序会跳转到0x7C0:0x60(例如)以确保CS 寄存器具有定义的值。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-12
    相关资源
    最近更新 更多