引用内存位置的内容。 (x86寻址模式)

时间:2015-12-03 04:50:23

标签: assembly x86 masm addressing-mode x86-16

我有一个内存位置,其中包含我想要与另一个角色进行比较的角色(并且它不在堆栈的顶部,所以我不能只是pop它)。如何引用内存位置的内容以便进行比较?

基本上我该如何在语法上做到这一点。

2 个答案:

答案 0 :(得分:15)

有关寻址模式(16/32/64位)的更多扩展讨论,请参见Agner Fog's "Optimizing Assembly" guide,第3.3节。该指南比符号和/或32位位置无关代码的重定位的答案要详细得多。

另请参阅:table of AT&T(GNU) syntax vs. NASM syntax for different addressing modes,包括间接跳转/呼叫。

另请参阅本答复底部的链接集。

建议欢迎,尤其是哪些部分是有用/有趣的,哪些部分不是。

x86(32和64位)有几种寻址模式可供选择。他们都是以下形式:

[base_reg + index_reg*scale + displacement]    ; or a subset of this
[RIP + displacement]   ; or RIP-relative: 64bit only.  No index reg is allowed

(其中,比例为1,2,4或8,位移是带符号的32位常数)。 所有其他形式(RIP相对除外)是其中的子集,其中省略了一个或多个组件。这意味着您不需要使用归零index_reg来访问[rsi]。在asm源代码中,编写内容的顺序并不重要:[5 + rax + rsp + 15*4 + MY_ASSEMBLER_MACRO*2]工作正常。 (关于常数的所有数学都在汇编时发生,导致单个恒定位移。)

寄存器的大小必须与您所处的模式相同,除非you use an alternate address-size需要额外的前缀字节。窄指针在x32 ABI (ILP32 in long mode)之外很少有用。

如果您想要use al as an array index, for example,则需要将其零或符号扩展为指针宽度。 (在弄乱字节寄存器之前,rax的高位已经归零有时是可能的,并且是实现此目的的好方法。)

一般情况的每个可能的子集都是可编码的,除了使用e/rsp*scale的情况(在#34;正常"代码中始终保持指向堆栈内存的esp中显然没用)。

通常,编码的代码大小为:

  • 1B用于单寄存器模式(mod / rm(模式/寄存器或存储器))
  • 2B用于双寄存器模式(mod / rm + SIB(Scale Index Base)字节)
  • 位移可以是0,1或4个字节(符号扩展为32或64,具体取决于地址大小)。因此,[-128 to +127]的替换可以使用更紧凑的disp8编码,相对于disp32可以节省3个字节。

代码大小例外:

  • [reg*scale]本身只能用32位位移进行编码。智能汇编程序通过将lea eax, [rdx*2]编码为lea eax, [rdx + rdx]来解决此问题,但该技巧仅适用于按2缩放。

  • 在没有位移字节的情况下,将e/rbpr13编码为基址寄存器是不可能的,因此[ebp]编码为[ebp + byte 0]。使用ebp作为基址寄存器的无位移编码意味着有 no 基址寄存器(例如[disp + reg*scale])。

  • [e/rsp]需要一个SIB字节,即使没有索引寄存器也是如此。 (无论是否有位移)。指定[rsp]的mod / rm编码意味着它是一个SIB字节。

有关特殊情况的详细信息,请参阅英特尔参考手册中的表2-5和周围部分。 (它们在32位和64位模式下相同。即使没有REX前缀,添加RIP相对编码也不会与任何其他编码冲突。)

对于性能而言,为获得更小的x86机器代码而花费额外的指令通常是不值得的。在具有uop缓存的Intel CPU上,它比L1 I $小,并且是更宝贵的资源。最小化融合域uop通常更重要。

16位地址大小不能使用SIB字节,因此所有的一个和两个寄存器寻址模式都被编码为单个mod / rm字节。 reg1可以是BX或BP,reg2可以是SI或DI(或者您可以自己使用这4个寄存器中的任何一个)。缩放不可用。 16位代码已经过时,原因很多,包括这个代码,如果你不必要的话,不值得学习。

请注意,当使用地址大小前缀时,16位限制适用于32位代码,因此16位LEA数学具有高度限制性。但是,您可以解决这个问题:lea eax, [edx + ecx*2]设置ax = dx + cx*2because garbage in the upper bits of the source registers has no effect

他们如何使用

该表与可能的寻址模式的硬件编码并不完全匹配,因为我区分使用标签(例如全局或静态数据)与使用小的恒定位移。因此,我将介绍硬件寻址模式+链接器对符号的支持。

如果char array[]中有指针esi

  • mov al, esi:无效,无法组装。没有方括号,它根本不是负载。这是一个错误,因为寄存器的大小不同。

  • mov al, [esi]加载指向的字节。

  • mov al, [esi + ecx]加载array[ecx]

  • mov al, [esi + 10]加载array[10]

  • mov al, [esi + ecx*8 + 200]加载array[ecx*8 + 200]

  • mov al, [global_array + 10]global_array[10]加载。在64位模式下,这可以是RIP相对地址。建议使用DEFAULT REL默认生成RIP相对地址,而不必始终使用[rel global_array + 10]。无法直接使用具有RIP相对地址的索引寄存器。常规方法是lea rax, [global_array] mov al, [rax + rcx*8 + 10]或类似方法。

  • 来自mov al, [global_array + ecx + edx*2 + 10]
  • global_array[ecx + edx*2 + 10]加载显然,您可以使用单个寄存器索引静态/全局数组。即使是使用两个独立寄存器的2D阵列也是可能的。 (对于除2,4或8之外的比例因子,使用额外指令预缩放一个)。请注意,global_array + 10数学是在链接时完成的。目标文件(汇编器输出,链接器输入)通知链接器+10添加到最终的绝对地址,将正确的4字节位移放入可执行文件(链接器输出)。这就是you can't use arbitrary expressions on link-time constants that aren't assemble-time constants(例如符号地址)。

  • 的原因
  • mov al, 0ABh根本不是加载,而是存储在指令中的立即常量。 (请注意,您需要在0前加上前缀,以便汇编程序知道它是常量,而不是符号。某些汇编程序也会接受0xAB)。 可以使用符号作为直接常量,以便将地址输入寄存器。

    • NASM:mov esi, global_array组装成mov esi, imm32,将地址放入esi。
    • MASM:mov esi, OFFSET global_array需要做同样的事情。
    • MASM:mov esi, global_array组装成一个负载:mov esi, dword [global_array]

    在64位模式下,寻址全局符号通常使用RIP相对寻址,汇编器默认使用DEFAULT REL指令或mov al, [rel global_array + 10]进行寻址。没有索引寄存器可以与RIP相关地址一起使用,只能使用常量位移。你可以仍然进行绝对寻址,甚至可以从64位绝对地址(rather than the usual 32bit sign-extended加载mov的特殊形式。)AT& T语法调用操作码movabs(也用于mov r64, imm64),而英特尔/ NASM语法仍称其为mov的形式。

    使用lea rsi, [rel global_array]将rip相对地址送入寄存器,因为mov reg, imm会将非相对地址硬编码到指令字节中。

    请注意,OS X将所有代码加载到低32位以外的地址,因此32位绝对寻址不可用。对于可执行文件,与位置无关的代码不是所需,但您可能也是如此,因为64位绝对寻址的效率低于RIP相对效率。 {ELF}的方式The macho64 object file format doesn't support relocations for 32-bit absolute addresses。确保不要在任何地方使用标签名作为编译时常量,除非在[global_array + constant]之类的有效地址中,因为它可以组合到RIP相对寻址模式。例如不允许[global_array + rcx],因为RIP不能与任何其他寄存器一起使用,因此必须使用global_array的绝对地址进行汇编,硬编码为32位位移({{ 3}})。

任何和所有这些寻址模式都可以是which will be sign-extended to 64b,无论它是否是有效地址。 [esi*4 + 10]通常仅对LEA有用(除非位移是符号,而不是小常数)。在机器代码中,单独的缩放寄存器没有编码,因此[esi*4]必须汇编到[esi*4 + 0],对于32位位移,需要4个字节的零。在一条指令中复制+ shift而不是更短的mov + shl仍然是值得的,因为通常uop吞吐量比代码大小更容易成为瓶颈,特别是在具有解码uop缓存的CPU上。

您可以指定mov al, fs:[esi]之类的段覆盖。段覆盖只是在通常的编码前添加前缀字节。其他所有内容保持不变,语法相同。

您甚至可以使用RIP相对寻址的段覆盖。 32位绝对寻址比RIP相对要多一个字节进行编码,因此mov eax, fs:[0]可以使用产生已知绝对地址的相对位移进行最有效的编码。即选择rel32使RIP + rel32 = 0.YASM将使用mov ecx, [fs: rel 0]执行此操作,但NASM始终使用disp32绝对寻址,忽略rel说明符。我还没有测试MASM或气体。

如果操作数大小不明确(例如,在具有立即数和内存操作数的指令中),请使用byte / word / dword / qword / { {1}} / xmmword指定:

ymmword

请参阅used with LEA to do integer math with a bonus of not affecting flags和/或the yasm docs for NASM-syntax effective addresses。维基页面说明16位模式允许的内容。这是wikipedia x86 entry's section on addressing modes

还有another "cheat sheet" for 32bit addressing modes。 16位仍具有与32位相同的寻址模式,因此,如果您发现寻址模式混乱,请继续阅读

另请参阅more detailed guide to addressing modes, for 16bit wiki页面了解链接。

答案 1 :(得分:1)

这是一份快速的备忘单,从this site检索。它显示了可用于寻址x86程序集中的主内存的各种方法:

prefabs[slot].AddRange(slotPrefabs);

在您的特定情况下,如果项目位于堆栈基础+------------------------+----------------------------+-----------------------------+ | Mode | Intel | AT&T | +------------------------+----------------------------+-----------------------------+ | Absolute | MOV EAX, [0100] | movl 0x0100, %eax | | Register | MOV EAX, [ESI] | movl (%esi), %eax | | Reg + Off | MOV EAX, [EBP-8] | movl -8(%ebp), %eax | | Reg*Scale + Off | MOV EAX, [EBX*4 + 0100] | movl 0x100(,%ebx,4), %eax | | Base + Reg*Scale + Off | MOV EAX, [EDX + EBX*4 + 8] | movl 0x8(%edx,%ebx,4), %eax | +------------------------+----------------------------+-----------------------------+ 偏移量处,请使用4表示法:

EBP

这会将项目复制到寄存器Reg + Off中。