Sha*_*avi 4 assembly x86-64 nasm micro-optimization shellcode
我是 x86_64 汇编编程的新手。我正在用 x86_64 程序集编写简单的“Hello World”程序。下面是我的代码,它运行得很好。
global _start
section .data
msg: db "Hello to the world of SLAE64", 0x0a
mlen equ $-msg
section .text
_start:
mov rax, 1
mov rdi, 1
mov rsi, msg
mov rdx, mlen
syscall
mov rax, 60
mov rdi, 4
syscall
Run Code Online (Sandbox Code Playgroud)
现在,当我在 gdb 中反汇编时,它会给出以下输出:
(gdb) disas
Dump of assembler code for function _start:
=> 0x00000000004000b0 <+0>: mov eax,0x1
0x00000000004000b5 <+5>: mov edi,0x1
0x00000000004000ba <+10>: movabs rsi,0x6000d8
0x00000000004000c4 <+20>: mov edx,0x1d
0x00000000004000c9 <+25>: syscall
0x00000000004000cb <+27>: mov eax,0x3c
0x00000000004000d0 <+32>: mov edi,0x4
0x00000000004000d5 <+37>: syscall
End of assembler dump.
Run Code Online (Sandbox Code Playgroud)
我的问题是为什么 NASM 会这样行事?我知道它会根据操作码更改指令,但我不确定寄存器的行为是否相同。
这种行为也会影响可执行文件的功能吗?
我在 i5 处理器上使用 VMware 中安装的 Ubuntu 16.04(64 位)。
先感谢您。
在 64 位模式下mov eax, 1将清除rax寄存器的上半部分(参见此处的解释),因此mov eax, 1在语义上等同于mov rax, 1.
然而,前者保留了REX.W(48h数字)前缀(指定 x86-64 引入的寄存器所必需的字节),两条指令的操作码相同(0b8h后跟 DWORD 或 QWORD)。
所以汇编器继续并选择最短的形式。
这是NASM的典型行为,参见第3.3节的NASM手册,其中的例子[eax*2]被组装为[eax+eax]不伤害disp32后场SIB字节1([eax*2]仅可编码为[eax*2+disp32]其中汇编集disp32为0)。
我无法迫使NASM发出真正的mov rax, 1指令(即48 B8 01 00 00 00 00 00 00 00连的前缀来指示)o64。
如果需要真正mov rax, 1的(这不是您的情况),则必须使用db类似的方法手动组装它。
编辑:Peter Cordes 的回答表明,事实上,有一种方法可以告诉 NASM不要使用strict修饰符优化指令。
mov rax, STRICT 1产生指令的10字节版本(mov r64, imm64),而mov rax, STRICT DWORD 1产生一个7字节的版本(mov r64, imm32这里imm32是符号扩展使用之前)。
旁注:最好使用RIP 相对寻址,这样可以避免 64 位立即数(从而减少代码大小),并且在 MacOS 中是强制性的(以防万一)。
更改mov esi, msg为lea esi, [REL msg](RIP-relative 是一种寻址模式,因此它需要一个“寻址”,即方括号,以避免从我们使用的lea仅计算有效地址但不访问的地址中读取)。
您可以使用该指令DEFAULT REL来避免REL在每次内存访问中输入。
我的印象是Mach-O文件格式需要 PIC 代码,但情况可能并非如此。
1个的规模指数基地字节,用于编码所述新的寻址模式引回,然后用32位模式。
TL:DR:您可以使用以下命令覆盖它
\nmov eax, 1(明确使用最佳操作数大小)b8 01 00 00 00mov rax, strict dword 1(符号扩展 32 位立即数)48 c7 c0 01 00 00 00mov rax, strict qword 1(64 位立即数,如movabsAT&T 语法)48 b8 01 00 00 00 00 00 00 00mov rax, strict 1相当于此,并且是禁用 NASM 优化后得到的结果。)这是一种完全安全且有用的优化,类似于在写入时使用 8 位立即数而不是 32 位立即数add eax, 1。
NASM 仅当指令的较短形式具有相同的架构效果时才进行优化,因为mov eax,1隐式地将 RAX 的高 32 位清零。请注意,这与NASM 无法优化它add rax, 0不同:只有像/或等不依赖于 32 与 64 位寄存器的旧值的指令才能通过这种方式进行优化。(NASM 不会优化或其他归零习惯;您应该始终手动使用 32 位操作数大小进行异或归零。)add eax, 0mov r32,...mov r64,...xor eax,eaxxor rax,rax
您可以使用nasm -O1(默认为-Oxmultipass)禁用它,但请注意,在这种情况下您将获得 10 字节mov rax, strict qword 1:显然 NASM 并不打算真正用于低于正常优化的情况。没有设置它将使用不会改变反汇编的最短编码(例如 7-byte mov rax, sign_extended_imm32= mov rax, strict dword 1)。
-O0和之间的区别-O1在于 imm8 与 imm32,例如add rax, 1\
n 48 83 C0 01( add r/m64, sign_extended_imm8) 与-O1, vs.
\n 48 05 01000000( add rax, sign_extended_imm32) 与nasm -O0。
\n有趣的是,它仍然通过选择暗示 RAX 目的地而不是采用 ModRM 字节的特殊情况操作码来进行优化。不幸的是-O1,并没有优化立即大小mov(其中sign_extended_imm8是不可能的。)
strict如果您在某处需要特定的编码,请使用而不是禁用优化来请求。
请注意,YASM 不会执行此操作数大小优化,因此如果您关心代码中的代码大小(甚至是间接出于性能原因),最好在 asm 源代码中自行进行优化使用其他 NASM 兼容的汇编器进行组装。
\n对于如果数字非常大(或负)则 32 位和 64 位操作数大小不相等的指令,即使您使用 NASM 而不是 YASM 进行汇编,也需要显式使用 32 位操作数大小,如果您想要尺寸/性能优势。\n在 x86-64 中使用 32 位寄存器/指令的优势
\nGAS 将使用 as 进行此优化-Os,例如gcc -Wa,-Os -c foo.S,但不幸的是这不是默认设置。(gcc -O选项不会影响传递给 的选项as,即使显式输入是 a.s或.S。gcc -O3 -Wa,-Os foo.c如果您有任何内联汇编,您不确定是否已手动优化,假设它不是手动优化的,那么使用是一个好主意出于对齐原因使用更长的指令。)
对于没有设置高位的 32 位常量,将它们扩展到 64 位的零或符号会给出相同的结果。因此,汇编mov rax, 1为 5 字节mov r32, imm32(隐式零扩展为 64 位)而不是 7 字节是纯粹的优化mov r/m64, sign_extended_imm32。
(有关x86-64 允许的形式的更多详细信息,请参阅x86-64 中 movq 和 movabsq 之间的区别mov;AT&T 语法对 10 字节立即数形式有一个特殊名称,但 NASM 没有。)
在所有当前的 x86 CPU 上,它与 7 字节编码之间的唯一性能差异是代码大小,因此只有对齐和 L1I$ 压力等间接影响是一个因素。在内部,它只是一个 mov-immediate,因此这种优化也不会改变代码的微架构效果(当然除了代码大小/对齐/它在 uop 缓存中的打包方式)。
\n10 字节mov r64, imm64编码的代码大小更差。如果该常量实际上设置了任何高位,那么它在 Intel Sandybridge 系列 CPU 上的 uop 缓存中的效率会特别低(在 uop 缓存中使用 2 个条目,并且可能需要一个额外的周期来从 uop 缓存中读取)。但是,如果常量在 -2^31 .. +2^31 范围(有符号 32 位)内,则它在内部存储的效率也一样,仅使用单个 uop 缓存条目,即使它是在使用 64 位立即数的 x86 机器代码。(参见Agner Fog 的 microarch 文档,表 9.1. Sandybridge 部分的 \xce\xbcop 缓存中不同指令的大小)
从有多少种方法将寄存器设置为零?,您可以强制使用三种编码中的任何一种:
\nmov eax, 1 ; 5 bytes to encode (B8 imm32)\nmov rax, strict dword 1 ; 7 bytes: REX mov r/m64, sign-extended-imm32. NASM optimizes mov rax,1 to the 5B version, but dword or strict dword stops it for some reason\nmov rax, strict qword 1 ; 10 bytes to encode (REX B8 imm64). movabs mnemonic for AT&T. Normally assemblers choose smaller encodings if the operand fits, but strict qword forces the imm64.\nRun Code Online (Sandbox Code Playgroud)\n请注意,NASM 使用 10 字节编码(AT&T 语法称为movabs,因此objdump),该地址是链接时间常量,但在汇编时未知。
YASM 选择mov r64, imm32,即它假定标签地址为 32 位的代码模型,除非您使用mov rsi, strict qword msg
YASM\ 的行为通常很好(尽管mov r32, imm32像 C 编译器那样用于静态绝对地址会更好)。默认的非 PIC 代码模型将所有静态代码/数据放入低 2GiB 的虚拟地址空间中,因此零或符号扩展的 32 位常量可以保存地址。
如果您需要 64 位标签地址,通常应该使用它lea r64, [rel address]来执行 RIP 相关的 LEA。(至少在 Linux 上,位置相关代码可以进入低 32 位,因此除非您使用大型/巨大代码模型,否则任何时候您需要关心 64 位标签地址,您也会您应该使用 RIP 相对 LEA 以避免需要绝对地址常量的文本重定位的 PIC 代码。
即gcc 和其他编译器会使用mov esi, msg, 或lea rsi, [rel msg], nevermov rsi, msg。
\n请参阅如何将函数或标签的地址加载到寄存器中
| 归档时间: |
|
| 查看次数: |
1274 次 |
| 最近记录: |