segment:offset 常量

segment:offset in a constant

我正在努力学习组装。我已经编写了这个简短的实模式函数,它可以按我的意愿运行。

getndrives:
        push    bx
        push    es
        push    si
            mov     bx,0040h
            mov     es,bx
            mov     si,0075h
            mov     al,byte [es:si]
        pop     si
        pop     es
        pop     bx
    ret

我希望 segment:offset 成为常量,而不需要 push/pop es+si。像

biosmem EQU 0040h:0075h
mov al,[biosmem]

以上代码编译但未return预期结果。

地址的段部分总是使用段寄存器。您可以使用常量作为偏移量。

x86 无法在一条指令中从立即常量远指针加载。有一个 far jmp ptr16:16 可以将 cs:ip 设置为 32 位立即数,但是对于加载我没有看到任何不使用段寄存器的选项。


NASM docs suggest 它 is/was 在 16 位调用约定中是典型的 DS 被调用保留,但是 ES 可以被认为是调用破坏的正是您想要的原因(在不将所有内容保存在单个段中的内存模型中)。

因此您可以考虑对该函数使用允许其破坏的调用约定 ES。 (或者 FSGS 如果您的代码只需要在 386 或更高版本上 运行。)

您还可以通过为段重复使用相同的寄存器来保存指令。您可以两次都使用 si 而不是 bx,因为一旦设置 es.

,您就已经完成了 bx
getndrives:
 ;; return in AL (zero-extended to AX; the upper byte of 0040h happens to be 0)
 ;; clobbers: AH, ES
    mov     ax, 0x0040
    mov     es, ax
    mov     al, [es:0x0475]
    ret

作为奖励,您正在加载到 AL,因此它可以使用跳过 ModR/M 字节的特殊 moffs encoding of mov,因此它只是(ES 段覆盖前缀)+ A0 75 04 .

如果你愿意,你当然仍然可以push/pop es and/or ax,但这显然更笨拙。如果你要 save/restore 一个段寄存器,@Fifoernik 指出它也可能是 DS 在 mov 上保存一个前缀(除非你有任何假设 DS 保持不变的中断处理程序) .

getndrives:
 ;; return in AL (zero-extended to AX; the upper byte of 0040h happens to be 0)
 ;; clobbers: AH,  or nothing if you uncomment the push/pop of AX
    push    es
    ;push    ax                 ; you might as well use a reg other than AX to simplify this, if you do want to preserve AH, too.  And also for performance on CPUs that don't rename low8 partial regs separately, so mov al,[mem] has a dependency on the pop.
      mov     ax, 0x0040
      mov     es, ax            ; or use DS if you don't need it in any interrupt handler
    ;pop     ax
      mov     al, [es:0x0475]
    pop     es
    ret

如果您只关心 186 和更新版本,push 0040h / pop es 会避免破坏 AH。 (8086没有push imm8/imm16).


在你的情况下,你可能首先避免修改段寄存器。请注意,0040h:0075h0475h 的线性地址,您可以使用从 047h 的任何 DS 值的偏移量访问它。 (在实模式下,linear = (segment << 4) + offset,即左移一位十六进制数)。

如果 DS=0,您仍然可以访问您的引导扇区 code/data(according to this PC memory map,加载在 07C0:0,又名 0:7C00),以及其他任何地方低 64kiB 内存。

我实际上并没有编写 16 位代码,所以我不确定您如何告诉 NASM 您将要设置 DS=0 然后让它生成相应地抵消。但希望这是可能的。

作为奖励,它节省了代码大小:xor ax,ax 小于 mov ax, imm16。但我想如果你正在优化代码大小,你会 push 0 / pop ds。 (但 8086 没有 push imm,后来才出现)。我想如果你想让堆栈指针与其他指针兼容,你需要 mov ss, ax / mov sp, whatever 这样代码就更多了。

但是无论如何,你的函数看起来像这样。

getndrives:
 ;; return in AL
 ;; requires/assumes: DS=0
    mov     al, [0x0475]
    ret

在这一点上让它成为一个函数是愚蠢的。让它成为一个宏,或者更好,或者 %define BIOS_BDA_ndrives 0x0475 这样你就可以做像 add dl, [BIOS_BDA_ndrives] 这样的事情。或者可能 %define BIOS_BDA_ndrives byte [0x0475] 进行构建时类型检查,例如mov ax, BIOS_BDA_ndrives 将因操作数大小不匹配而无法 assemble。