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
。 (或者 FS
或 GS
如果您的代码只需要在 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:0075h
是 0475h
的线性地址,您可以使用从 0
到 47h
的任何 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。
我正在努力学习组装。我已经编写了这个简短的实模式函数,它可以按我的意愿运行。
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
。 (或者 FS
或 GS
如果您的代码只需要在 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:0075h
是 0475h
的线性地址,您可以使用从 0
到 47h
的任何 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。