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

我有一个内存位置,其中包含一个我想与另一个字符进行比较的字符(并且它不在堆栈的顶部,因此我不能只pop它)。 我如何参考内存位置的内容,以便比较它?

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


有关寻址模式(16/32 / 64bit)的更多扩展讨论,请参阅Agner Fog的“优化装配”指南,第3.3节。 除了其他内容外,该指南比这个答案更详细地描述了符号和/或32位位置无关代码的重定位。

另请参阅:AT&T(GNU)语法与不同寻址模式(包括间接跳转/调用)的NASM语法表。

另请参阅此答案底部的链接集合。


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

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]可以正常工作。 (所有关于常数的数学都是在组装时发生的,导致一个单一的恒定位移。)

寄存器的大小必须与所处模式的大小相同,除非您使用备用地址大小,需要额外的前缀字节。 窄指针在x32 ABI(长模式下的ILP32)之外很少有用。

例如,如果您想将al用作数组索引,则需要将其扩展为指针宽度。 (在将字节寄存器搞乱之前,将rax的高位已经清零有时是可能的,并且是实现这一点的好方法。)


一般情况下每个可能的子集都是可编码的,除了那些使用e/rsp*scale (在“正常”代码中显然没用的,在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作为基址寄存器的无位移编码意味着没有基址寄存器(例如[disp + reg*scale] )。

  • 即使没有索引寄存器, [e/rsp]需要一个SIB字节。 (不管是否有位移)。 代替[rsp]的mod / rm编码意味着有一个SIB字节。

  • 有关特殊情况的详细信息,请参见英特尔的ref手册中的表2-5以及周围章节。 (它们在32位和64位模式下是相同的,即使没有REX前缀,添加RIP相关编码也不会与其他编码冲突。)

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


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

    请注意,当使用地址大小前缀时,16位限制适用于32位代码,因此16位LEA算法具有高度的限制性。 但是,您可以解决这个问题: lea eax, [edx + ecx*2]设置ax = dx + cx*2 ,因为源寄存器高位中的垃圾没有效果。


    他们如何使用

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

    如果在esi有一个指针char array[]

  • 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]加载global_array[ecx + edx*2 + 10]显然,您可以使用单个寄存器来索引静态/全局数组。 即使是使用两个独立寄存器的2D阵列也是可能的。 (使用额外的指令对比例进行预缩放,对于除2,4,8以外的比例因子)。 请注意, global_array + 10数学是在链接时完成的。 目标文件(汇编器输出,链接器输入)通知链接器+10添加到最终绝对地址,将正确的4字节位移放入可执行文件(链接器输出)。 这就是为什么你不能在链接时间常量上使用不是汇编时间常数(例如符号地址)的任意表达式。

  • 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相对地址一起使用,只有固定的位移。 您仍然可以进行绝对寻址,甚至还有一种特殊形式的mov可以从64位绝对地址(而不是通常的32位符号扩展)加载。AT&T语法调用操作码movabs (也用于mov r64, imm64 ),而Intel / NASM语法仍将其称为mov一种形式。

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

    请注意,OS X将所有代码加载到低32位以外的地址,因此32位绝对地址无法使用。 可执行文件不需要与位置无关的代码,但您可能也会这样做,因为64位绝对寻址的效率低于RIP相对的效率。 macho64目标文件格式不支持Linux ELF所执行的32位绝对地址的重定位。 请确保不要在任何地方使用标签名称作为编译时间常量,除非像[global_array + constant]这样的有效地址,因为它可以组装到RIP相对寻址模式。 例如[global_array + rcx]是不允许的,因为RIP不能与任何其他寄存器一起使用,所以它必须与硬编码的global_array的绝对地址一起组装成32位的位移(这将被符号扩展为64B)。


    任何和所有这些寻址模式都可以与LEA一起使用,以不影响标志的奖励来进行整数运算,而不管它是否是有效地址。 [esi*4 + 10]通常只对LEA有用(除非位移是一个符号,而不是一个小常数)。 在机器代码中,没有单独的缩放寄存器编码,所以[esi*4]必须组合成[esi*4 + 0] ,其中4个字节的零用于32位的位移。 在一个指令中复制+移位通常是值得的,而不是缩短的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 / xmmword / ymmword指定:

    mov       dword [rsi + 10], 0xAB  ; NASM
    mov   dword ptr [rsi + 10], 0xAB  ; MASM and GNU .intex_syntax noprefix
    movl              $0xAB, 10(%rsi) # GNU(AT&T): operand size from insn suffix
    

    有关NASM语法有效地址和/或wikipedia x86条目的寻址模式部分,请参阅yasm文档。 该wiki页面说明了16bit模式下允许的内容。 这是32位寻址模式的另一个“备忘单”。

    还有一个更详细的16位寻址模式指南。 16位仍然具有与32位相同的寻址模式,所以如果您发现寻址模式混乱,请阅读它

    另请参阅x86 wiki页面的链接。

    链接地址: http://www.djcxy.com/p/72443.html

    上一篇: Referencing the contents of a memory location. (x86 addressing modes)

    下一篇: Getting "actual" registers from MCInsts (x86)