YASM/NASM x86 程序集中立即数与方括号的基本使用

Posted

技术标签:

【中文标题】YASM/NASM x86 程序集中立即数与方括号的基本使用【英文标题】:Basic use of immediates vs. square brackets in YASM/NASM x86 assembly 【发布时间】:2021-11-15 14:30:35 【问题描述】:

假设我声明了以下内容:

section .bss
buffer    resb     1

这些说明在section .text

mov    al, 5                    ; mov-immediate
mov    [buffer], al             ; store
mov    bl, [buffer]             ; load
mov    cl, buffer               ; mov-immediate?

我是否正确理解 bl 将包含值 5,而 cl 将包含变量 buffer 的内存地址?

我对两者之间的差异感到困惑

将立即数移入寄存器, 将寄存器移动到立即数(里面有什么,数据还是地址?)和 将立即数移动到不带括号的寄存器中 例如,mov cl, buffermov cl, [buffer]

更新:阅读回复后,我认为以下摘要是准确的:

mov edi, array 将第零个数组索引的内存地址放在edi 中。即标签地址。 mov byte [edi], 3 将 VALUE 3 放入数组的第 0 个索引中 在add edi, 3 之后,edi 现在包含数组第三个索引的内存地址 mov al, [array] 将第 0 个索引处的 DATA 加载到 almov al, [array+3] 将第三个索引处的 DATA 加载到 almov [al], [array] 无效,因为x86 can't encode 2 explicit memory operands,并且因为al 只有 8 位,即使在 16 位寻址模式下也不能使用。 Referencing the contents of a memory location. (x86 addressing modes) mov array, 3 无效,因为您不能说“嘿,我不喜欢存储 array 的偏移量,所以我将其称为 3”。立即数只能是源操作数。 mov byte [array], 3 将值 3 放入数组的第零索引(第一个字节)。 The byte specifier is needed 避免内存指令、立即操作数的字节/字/双字之间的歧义。否则,这将是一个汇编时错误(操作数大小不明确)。

如果其中任何一个是错误的,请说明。 (编者注:我修复了语法错误/歧义,因此有效的实际上有效的 NASM 语法。并链接了其他问答以获取详细信息)

【问题讨论】:

What do the brackets mean in x86 asm?的可能重复 array resb 0 在标签“array”处保留一个零长度空间。例如,如果您想要 .bss 部分中包含 10 个字节条目的数组,则应指定 resb 10 【参考方案1】:

方括号本质上类似于解引用运算符(例如,类似于 C 中的 *)。

所以,像

mov REG, x

x 的值移动到REG,而

mov REG, [x]

x 指向的内存位置的值移动到REG。请注意,如果x 是标签,则其值为该标签的地址。

至于你的问题:

我理解 bl 将包含值 5 和 cl 是否正确 会包含变量缓冲区的内存地址吗?

是的,你是对的。但请注意,由于CL 只有 8 位宽,它只会包含buffer 地址的最低有效字节。

【讨论】:

cl 将只包含地址的 8 个最低有效位。 @Alex:是的,我刚刚意识到:-) 当你说“如果 x 是一个标签,它的值就是那个标签的地址”是不是意味着如果 x 的地址是 0x1234,那么当它是一个标签时的值也是 0x1234? @InstructionPointer:在 MASM 语法中,mov eax, var 是一个负载,与[var] 相同。在 NASM 语法中,mov eax, var 是标签地址的 mov-immediate。 (MASM 语法 mov eax, OFFSET var). If you want a load, always use mov eax, [var]` 以避免歧义。如果您想要地址,则没有明确表示地址的可移植语法。(除了在 x86-64 PIC 代码中 lea rdi, [var]是最好的选择,假设 default rel 用于 NASM 语法。除了 GAS .intel_syntax 需要 lea rdi, [var + rip] 用于与当前位置相关的地址...)【参考方案2】:

确实,你的想法是对的,就是bl会包含5,cl会包含buffer的内存地址(其实label buffer本身就是内存地址)。


现在,让我解释一下你提到的操作之间的区别:

可以使用mov reg,imm 将立即数移动到寄存器中。可能令人困惑的是标签(例如缓冲区)本身就是包含地址的立即数。

您不能真正将寄存器移动到立即数,因为立即数是常量,例如 2FF1Ah。您可以将寄存器移动到常量指向的位置。您可以像mov [const], reg 那样做。

您也可以使用间接寻址,如mov reg2,[reg1],前提是 reg1 指向一个有效位置,它会将 reg1 指向的值传递给 reg2。


所以,mov cl, buffer 会将缓冲区的 地址 移动到 cl(它可能会或可能不会给出正确的地址,因为 cl 只有一个字节长),而 mov cl, [buffer] 将获得实际价值。

总结

当你使用[a]时,那么你指的是a指向的地方的值。例如,如果a是F5B1,那么[a]指的是RAM中的地址F5B1 em>。 标签是地址,即像F5B1 这样的值。 存储在寄存器中的值不必引用为 [reg],因为寄存器没有地址。事实上,寄存器可以被认为是立即值。

【讨论】:

我不精通组装,但是在您顶部的中间要点中,组装的最后一个示例行不应该反过来吗?即mov [const], reg ? @Coldblackice: mov [const], reg 是一个内存存储,地址为const。这是一个奇怪的名称选择,但所有符号地址都是链接时常量。例如您可以使用dd const 在某处(例如在数据部分)发出一个 4 字节的绝对地址。或者const 可以是数字地址,例如如果您正在编写处理固定内存地址的 VGA 内存等传统硬件的代码。例如 mov [0x0], ax 在 DS 中具有正确的值。【参考方案3】:

你明白了。但是,有一些细节值得牢记:

    地址可以而且通常大于 8 位可以容纳的地址(cl 是 8 位,cx 是 16 位,ecx 是 32 位,rcx 是 64 位)。所以,cl 很可能不等于变量buffer 的地址。它只有地址的最低有效 8 位。 如果有中断例程或线程可以抢占上述代码和/或访问bufferbl 中的值可能与 5 不同。中断的中断例程在无法保存寄存器值时实际上可能会影响任何寄存器.

【讨论】:

不要忘记 ecx 仅在 386 或更高版本中可用,而 rcx 在 x86-64 中可用。 @byrondrossos:正确。而 x86 涵盖 i80386。【参考方案4】:

对于所有使用立即数作为操作数来将值写入 ram 位置(或用于计算)的指令,我们必须指定要访问的字节数。因为我们的汇编无法知道我们是否只想访问一个 byte、一个 word 或一个 doppleword,例如,如果立即值是较低的值,如以下说明所示。

array db 0FFh, 0FFh, 0FFh, 0FFh
mov byte [array], 3

结果:

array db 03h, 0FFh, 0FFh, 0FFh

....

mov word [array], 3

结果:

array db 03h, 00h, 0FFh, 0FFh

....

mov dword [array], 3

结果:

array db 03h, 00h, 00h, 00h

德克

【讨论】:

以上是关于YASM/NASM x86 程序集中立即数与方括号的基本使用的主要内容,如果未能解决你的问题,请参考以下文章

win7的操作系统打不开汇编编译器(yasm&nasm)

在 X86 程序集中访问和移动字节

如何在 x86 程序集中按值传递结构

x86 程序集中的 SSE2 寄存器

如何在 Visual Studio 2017 的 x86 程序集中使用 printf?

使用 div 指令的 x86 NASM 程序集中的浮点异常 [重复]