当前位置:  开发笔记 > 前端 > 正文

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

如何解决《引用内存位置的内容.(x86寻址模式)》经验,为你挑选了1个好方法。

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

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



1> Peter Cordes..:

有关寻址模式(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)之外很少有用.

如果要使用movsxd作为数组索引,例如,需要零或符号扩展它来指针宽度.(在al弄乱字节寄存器之前,已经将高位已经置零有时是可能的,并且是实现此目的的好方法.)


一般情况的每个可能的子集都是可编码的,除了使用rax(在"普通"代码中显然无用,总是保持指向堆栈内存的指针[rsp]).

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

1B用于单寄存器模式(mod/rm(模式/寄存器或存储器))

2B用于双寄存器模式(mod/rm + SIB(Scale Index Base)字节)

位移可以是0,1或4个字节(符号扩展为32或64,具体取决于地址大小).因此,位移e/rsp*scale可以使用更紧凑的esp编码,节省3个字节[-128 to +127].

代码大小异常:

disp8本身只能用32位位移编码.智能汇编程序通过编码disp32as来解决这个问题[reg*scale],但该技巧仅适用于2的缩放.

在没有位移字节的情况下编码lea eax, [rdx*2]lea eax, [rdx + rdx]作为基址寄存器是不可能的,因此e/rbp编码为r13.[ebp]作为基址寄存器的无位移编码意味着没有基址寄存器(例如for [ebp + byte 0]).

ebp即使没有索引寄存器,也需要一个SIB字节.(是否有位移).指定的mod/rm编码[disp + reg*scale]意味着有一个SIB字节.

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

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


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

请注意,当使用地址大小前缀时,16位限制适用于32位代码,因此16位LEA数学具有高度限制性.但是,您可以解决这个问题:movzx eax, byte [esi]设置movsx,因为源寄存器的高位中的垃圾无效.


他们是如何使用的

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

如果你有一个指针mov al, byte_srcint*,

char array*:无效,不会组装.没有方括号,它根本不是负载.这是一个错误,因为寄存器的大小不同.

esi 加载指向的字节.

mov al, esi载荷mov al, [esi].

array[0]载荷*array.

mov al, [esi + ecx] 负载 array[ecx]

mov al, [esi + 10]来自array[10].在64位模式下,这可以是RIP相对地址.使用mov al, [esi + ecx*8 + 200]建议,来产生,而不必总是使用默认RIP相对地址array[ecx*8 + 200].无法直接使用具有RIP相对地址的索引寄存器.常规方法是mov al, [global_array + 10] global_array[10]或类似的.

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

mov al, [rax + rcx*8 + 10]根本不是负载,而是存储在指令中的立即常量.(注意你需要加前缀一个,.intel_syntax所以汇编程序知道它是一个常量,而不是一个符号.一些汇编程序也会接受mov al, [global_array + ecx + edx*2 + 10]).您可以使用符号作为直接常量,以将地址输入寄存器.

NASM:global_array[ecx + edx*2 + 10]组装成一个global_array + 10将地址放入esi的程序.

MASM:global_array需要做同样的事情.

MASM:disp32组装成一个负载: mov al, 0ABh.

在64位模式下,寻址全局符号通常使用RIP相对寻址完成,汇编器默认使用该0指令执行,或使用0xAB.没有索引寄存器可以与RIP相关地址一起使用,只能使用常量位移.您仍然可以进行绝对寻址,甚至还有一种特殊形式0ABh可以从64位绝对地址加载(而不是通常的32位符号扩展.)AT&T语法调用操作码mov esi, global_array(也用于mov esi, imm32),而英特尔/ NASM语法仍然称它为一种形式mov esi, OFFSET global_array.

用于mov esi, global_array将rip相对地址放入寄存器,因为mov esi, dword [global_array]会将非相对地址硬编码到指令字节中.

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


任何和所有这些寻址模式都可用于mov rdi, format_string进行整数数学运算,并且不会影响标志,无论它是否是有效地址. call printf通常只对LEA有用(除非位移是符号,而不是小常数).在机器代码中,没有单独的缩放寄存器编码,因此mov reg, imm32必须汇编为lea eax, [array]4位字节的32位移位.在一条指令中复制+ shift而不是更短的mov + shl仍然是值得的,因为通常uop吞吐量比代码大小更容易成为瓶颈,特别是在具有解码uop缓存的CPU上.


您可以指定段覆盖,如mov eax, imm32.段覆盖只是在通常的编码前添加前缀字节.其他所有内容都保持不变,语法相同.

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


如果操作数大小是不明确的(与立即和一个存储器的操作数的指令例如)时,使用LEA/ [esi*4 + 10]/ [esi*4]/ [esi*4 + 0]/ mov al, fs:[esi]/ mov eax, fs:[0]指定:

mov       dword [rsi + 10], 123   ; NASM
mov   dword ptr [rsi + 10], 123   ; MASM and GNU .intex_syntax noprefix

movl      $123, 10(%rsi)         # GNU(AT&T): operand size from mnemonic suffix

请参阅有关NASM语法有效地址的yasm文档,和/或维基百科x86条目中有关寻址模式的部分.维基页面说明16位模式允许的内容.这是32位寻址模式的另一个"备忘单".

对于16位,还有更详细的寻址模式指南.16位仍具有与32位相同的寻址模式,因此如果您发现寻址模式令人困惑,请继续阅读

另请参阅x86 wiki页面以获取链接.


尽管如此,16位代码仍然存在.并且用户确实将其标记为DOS,因此对于任何绊倒此问题和答案的人来说,对16位限制的解释可能是合理的.我已经看到的最好的经验法则相当容易理解和记忆,可以在第1.2.7节*一个简单的方法来记住这个[文件]的8086内存寻址模式*(http://www.ic .unicamp.br/~celio/mc404s2-03/addr_modes/intel_addr.html).我发现它比你链接的Wiki文章更好
推荐阅读
谢谢巷议
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有