简体   繁体   English

segment:以常数偏移

[英]segment:offset in a constant

I'm trying to learn assembly. 我正在尝试学习汇编。 I've written this short real mode function that works as I want to. 我已经编写了这个简短的实模式函数,该函数可以按我的意愿工作。

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

I would like to have the segment:offset in a constant instead and eliminate the need to push/pop es+si. 我想将segment:offset设置为常量,而无需推送/弹出es + si。 Something like 就像是

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

above code compiles but does not return the intended result. 上面的代码可以编译,但不会返回预期的结果。

x86 doesn't have a way to load from an immediate-constant far-pointer in one instruction. x86没有一种方法可以在一条指令中从立即数常量指针加载。 There's a far jmp ptr16:16 that can set cs:ip to a 32-bit immediate, but for loads I don't see any option that doesn't use a segment register. 有一个很远的jmp ptr16:16可以将cs:ip设置为32位立即数,但是对于负载,我看不到任何不使用段寄存器的选项。


NASM docs suggest that it is/was typical in 16-bit calling conventions for DS to be call-preserved, but that ES could be considered call-clobbered for exactly the reason you want (in memory models that don't keep everything in a single segment). NASM文档建议在16位调用约定中将DS保留为通话是很常见的,但出于您的确切原因, ES可以被认为是呼叫拥挤的(在内存模型中,不能将所有内容保存在单段)。

So you could consider using a calling convention for this function that allows it to clobber ES . 因此,您可以考虑对该函数使用调用约定,以使其能够破坏ES (Or FS or GS if your code only needs to run on 386 or later.) (如果您的代码仅需要在386或更高版本上运行,则为FSGS 。)

You can also save instructions by reusing the same register for the segment. 您也可以通过对段重复使用相同的寄存器来保存指令。 You could have used si both times instead of bx , because you're done with bx as soon as you set es . 您可能两次都使用了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

As a bonus, you're loading into AL so it can use the special moffs encoding of mov that skips the ModR/M byte so it's just (ES segment override prefix) + A0 75 04 . 另外,您需要加载到AL中,以便它可以使用mov的特殊moffs编码来跳过ModR / M字节,因此它只是(ES段覆盖前缀)+ A0 75 04

You can of course still push/pop es and/or ax around this if you want, but this is obviously clunkier. 当然,如果需要,您仍然可以在此周围推/弹出es和/或ax ,但这显然比较笨拙。 And if you're going to save/restore a segment register, @Fifoernik points out it might as well be DS to save a prefix on the mov (unless you have any interrupt handlers that assume DS stays constant). 并且,如果您要保存/恢复段寄存器,@ 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

If you only care about 186 and newer, push 0040h / pop es would avoid clobbering AH. 如果你只关心186,更新, push 0040h / pop es将避免重挫啊。 (8086 doesn't have push imm8/imm16 ). (8086没有push imm8/imm16 )。


In your case, you can probably avoid modifying a segment register in the first place . 在您的情况下,您可能可以避免一开始就修改段寄存器 Notice that 0040h:0075h is a linear address of 0475h , which you can access with just an offset from any DS value from 0 to 47h . 注意, 0040h:0075h是的线性地址0475h ,这可以用只是一个从任何偏移访问DS从值047h (In real mode, linear = (segment << 4) + offset , ie left-shift by one hex digit). (在实模式下, linear = (segment << 4) + offset ,即左移一位十六进制数字)。

With DS=0, you can still access your boot-sector code/data ( according to this PC memory map , loaded at 07C0:0 , aka 0:7C00 ), and anywhere else in the low 64kiB of memory. 在DS = 0的情况下,您仍然可以访问引导扇区代码/数据( 根据此PC内存映射 ,加载于07C0:0 ,又称为0:7C00 )以及低64kB内存中的任何其他位置。

I don't actually write 16-bit code, so I'm not sure exactly how you'd tell NASM that you're going to set DS=0 and then have it generate offsets accordingly. 我实际上并没有编写16位代码,所以我不确定您如何告诉NASM您将要设置DS=0 ,然后使其相应地生成偏移量。 But hopefully that's possible. 但是希望这是可能的。

As a bonus, it saves code size: xor ax,ax is smaller than mov ax, imm16 . 另外,它节省了代码大小: xor ax,ax小于mov ax, imm16 But I guess if you were optimizing for code size, you'd push 0 / pop ds . 但是我想如果您要优化代码大小,则可以push 0 / pop ds (But 8086 doesn't have push imm , that came later). (但是8086没有后push imm )。 And I guess if you want stack pointers to be compatible with other pointers, you'd need to mov ss, ax / mov sp, whatever so that's more code. 而且我猜想,如果您希望堆栈指针与其他指针兼容,则需要mov ss, ax / mov sp, whatever等等,这需要更多代码。

But anyway, then your function would look like this. 但是无论如何,您的函数将如下所示。

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

At this point it's silly to make it a function. 在这一点上使其发挥作用是愚蠢的。 Make it a macro, or better, or %define BIOS_BDA_ndrives 0x0475 so you can do stuff like add dl, [BIOS_BDA_ndrives] . 将其设为宏,或者更好,或者%define BIOS_BDA_ndrives 0x0475以便您可以执行诸如add dl, [BIOS_BDA_ndrives] Or possibly %define BIOS_BDA_ndrives byte [0x0475] to get build-time type checking, eg mov ax, BIOS_BDA_ndrives would then fail to assemble with an operand-size mismatch. 或者可能是%define BIOS_BDA_ndrives byte [0x0475]以进行构建时类型检查,例如mov ax, BIOS_BDA_ndrives将无法以操作数大小不匹配的方式进行组装。

The segment part of an address always uses a segment register. 地址的段部分始终使用段寄存器。 You can use a constant for the offset. 您可以使用常数作为偏移量。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM