【问题标题】:segment:offset in a constant段:常量中的偏移量
【发布时间】:2018-05-20 01:40:24
【问题描述】:

我正在努力学习汇编。我已经编写了这个简短的实模式函数,可以按我的意愿工作。

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

我希望将段:偏移改为常量,并消除推送/弹出 es+si 的需要。类似的东西

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

上面的代码编译但不返回预期的结果。

【问题讨论】:

  • 请将答案发布为answers,而不是问题的编辑。 (即,将您的更新复制到答案并回滚问题。)另外,如果您不介意破坏 AH 并将结果零扩展到 AX 中,请查看我的答案以获得避免一对推送/弹出的建议.您可以将段值放入 AX 等非“指针”寄存器中。
  • 您是在编写 DOS COM 程序还是引导加载程序?
  • 如果您仍然要保留段寄存器,那么最好使用DS 段寄存器。这样您就可以避免mov al,byte [es:si] 上的段覆盖前缀的额外代码字节。 => mov al, [si]
  • @MichaelPetch 一个小内核。

标签: assembly nasm x86-16


【解决方案1】:

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


NASM docs suggest 在 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,因此它可以使用特殊的 moffs encoding of mov 跳过 ModR/M 字节,因此它只是(ES 段覆盖前缀)+ A0 75 04

当然,如果您愿意,您仍然可以在此周围推送/弹出 es 和/或 ax,但这显然更笨重。如果您要保存/恢复段寄存器,@Fifoernik 指出,在mov 上保存前缀也可能是 DS(除非您有任何假定 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,您仍然可以访问您的引导扇区代码/数据(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 将因操作数大小不匹配而无法组装。

【讨论】:

  • "作为奖励" push es / pop es 的示例将受益于使用 DS 代替。不需要段覆盖前缀!
  • @Fifoernik:好主意。但是,假设 DS 在中断处理程序中具有特定值是否很常见?
  • @Fifoernik:在无序执行的 CPU 上,pop ds 将成为未来使用 DS 加载/存储的输入依赖项(即除了堆栈之外的几乎所有内容)。但是pop es 仍然可以与未来的 DS 并行执行:加载/存储。例如在 Conroe/Merom 上,pop sr 是 10 uop,每 17 个周期一个吞吐量,因此很容易出现乱序执行的空间。 (如果它在实模式下比在受保护模式下更快,则 IDK。Agner maybe only timed in compat / long modes。)
【解决方案2】:

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

【讨论】:

  • 所以 mov al, [es:my_offset] 并将 mov bx,0040h 更改为 mov bx,my_segment 如果我理解正确,我最好避免对内存地址进行硬编码?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-05-30
  • 2013-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-24
  • 2021-07-08
相关资源
最近更新 更多