使用真正的 GAS(在 Linux 上),您的代码可以组装成mov rdx, sign_extended_imm32
您想要的样子。
但是,是的,mov rdx, [0xc]
不幸的是,clang 将其组装到了一起。这可能是也可能不是错误,但这绝对是不兼容的。(MacOS 的gcc
命令根本不是 GNU Compiler Collection,它是 Apple Clang:LLVM 后端,clang 前端,与 GNU 项目完全无关。)
OFFSET hello_len
似乎不起作用。(我错误地认为它会在第一次猜测时出现,但 clang 不支持 OFFSET 运算符;它.intel_syntax
不是完全可用的。)
这是clang 的 bug 已经被报告了。另请参阅为什么这个简单的汇编程序在 AT&T 语法中工作,而不是在 Intel 语法中工作?
Clang 甚至无法组装自己的.intel_syntax noprefix
输出。
可能没有办法让铿锵英特尔语法使用符号的值(地址)作为立即数。
// hello.c
char hello[] = "abcdef";
char *foo() { return hello; }
clang -S
mov edi, offset hello
无法使用 clang 的内置汇编器组装的 打印件!https://godbolt.org/z/x7vmm4。
$ clang -fno-pie -O1 -S -masm=intel hello.c
$ clang -c hello.s
hello.s:10:18: error: cannot use more than one symbol in memory operand
mov eax, offset hello
^
$ clang --version
clang version 8.0.1 (tags/RELEASE_801/final)
Target: x86_64-pc-linux-gnu
...
IMO 这是一个错误,您应该在 clang 的https://bugs.llvm.org上报告它
(Linux 非 PIE 可执行文件可以利用静态地址位于虚拟地址空间的低 32 位中,mov r32, imm32
而不是使用相对于 RIP 的 LEA。当然不是mov r64, imm64
。)
解决方法:您不能只使用 C 预处理器。 . - hello
是上下文相关的;当位置不同时,它具有不同的值.
。所以文本替换不起作用。
丑陋的解决方法:切换到.att_syntax
并返回:
切换到.att_syntax
并返回mov $hello_len, %edx
丑陋而低效的解决方法:lea
这不适用于 64 位常量,但您可以使用lea
将符号地址放入寄存器。
disp32
不幸的是,当小常数是命名符号时,clang/LLVM 总是使用寻址模式,即使对于寄存器 + 小常数也是如此。我猜它真的把它当作一个可能有搬迁的地址。
鉴于此来源:
## your .rodata and = or .equ symbol definitions
_main:
mov eax, 0x2000004 # optimized from RAX
mov edi, 1
lea rsi, [rip + hello]
mov edx, hello_len # load
lea edx, [hello_len] # absolute disp32
lea edx, [rdi-1 + hello_len] # reg + disp8 hopefully
# mov esi, offset hello # clang chokes.
# mov rdx, OFFSET FLAT hello_len # clang still chokes
.att_syntax
lea -1+hello_len(%rdi), %edx
lea -1+12(%rdi), %edx
mov $hello_len, %edx
.intel_syntax noprefix
syscall
mov rax, 0x2000001
syscall
clang 将它组装成这个机器代码,由objdump -drwC -Mintel
. 请注意,LEA 需要一个 ModRM + SIB 以将 32 位绝对寻址模式编码为 64 位代码。
0: b8 04 00 00 02 mov eax,0x2000004 # efficient 5-byte mov r32, imm32
5: bf 01 00 00 00 mov edi,0x1
# RIP-relative LEA
a: 48 8d 35 00 00 00 00 lea rsi,[rip+0x0] # 11 <_main+0x11> d: R_X86_64_PC32 .data-0x4
11: 8b 14 25 0c 00 00 00 mov edx,DWORD PTR ds:0xc # the load we didn't want
18: 8d 14 25 0c 00 00 00 lea edx,ds:0xc # LEA from the same [disp32] addressing mode.
1f: 8d 97 0b 00 00 00 lea edx,[rdi+0xb] # [rdi+disp32] addressing mode, missed optimization to disp8
25: 8d 97 0b 00 00 00 lea edx,[rdi+0xb] # AT&T lea -1+hello_len(%rdi), %edx same problem
2b: 8d 57 0b lea edx,[rdi+0xb] # AT&T with lea hard-coded -1+12(%rdi)
2e: ba 0c 00 00 00 mov edx,0xc # AT&T mov $hello_len, %edx
33: 0f 05 syscall
35: 48 c7 c0 01 00 00 02 mov rax,0x2000001 # inefficient mov r64, sign_extended_imm32 from your source
3c: 0f 05 syscall
GAS 组装相同的来源使8d 57 0b lea edx,[rdi+0xb]
版本lea edx, [rdi-1 + hello_len]
。
请参阅https://codegolf.stackexchange.com/questions/132981/tips-for-golfing-in-x86-x64-machine-code/132985#132985 -来自已知常量寄存器的 LEA 是代码大小的胜利附近/小常数,实际上对性能很好。(只要已知常数不依赖于长链计算就可以了)。
但是正如您所看到的,clang 未能优化它并且仍然使用 reg+disp32 寻址模式,即使位移适合 disp8。它的代码大小仍然比[abs disp32]
需要 SIB 字节的代码大小要好一些;没有编码意味着的 SIB 字节[RIP + rel32]
。