如何让 `mov rdx, symbol` 移动符号值而不是 clang 英特尔语法中符号地址处的值?
How to get `mov rdx, symbol` to move symbol value and not value at symbol's address in clang intel-syntax?
我有以下代码,我在 macOS 上使用 clang:
.intel_syntax noprefix
.data
hello: .ascii "Hello world\n"
hello_len = . - hello
.text
.globl _main
_main:
mov rax, 0x2000004
mov rdi, 1
lea rsi, [rip + hello]
mov rdx, hello_len # <-------
syscall
mov rax, 0x2000001
syscall
虽然它看起来应该打印 "Hello World" 并退出,但它实际上会出现段错误。事实证明这是因为 mov rdx, hello_len
实际上试图移动地址 hello_len
的值,而不是 hello_len
本身的值。
如果我使用 AT&T 语法,该行将是 movq $hello_len, %rdx
,它可以正常工作。 clang 版本的 GAS intel 语法的等价物是什么?
使用真正的 GAS(在 Linux 上),您的代码 assemble 可以达到您想要的 mov rdx, sign_extended_imm32
。
但是,是的,不幸的是 assemble 将其发送到 mov rdx, [0xc]
。这可能是也可能不是错误,但绝对是不兼容的。 (MacOS 的 gcc
命令根本不是 GNU 编译器集合,它是 Apple Clang:LLVM 后端,clang 前端,与 GNU 项目完全无关。)
OFFSET hello_len
好像不行。 (我一开始就错误地认为它会,但 clang 不支持 OFFSET 运算符;它 .intel_syntax
不能完全使用。)
这是clang bug has already been reported. See also
Clang 甚至 assemble 它自己的 .intel_syntax noprefix
输出。
可能没有办法让 clang Intel 语法使用符号的值(地址)作为立即数。
// hello.c
char hello[] = "abcdef";
char *foo() { return hello; }
clang -S
打印 mov edi, offset hello
而不会 assemble 使用 clang 的内置 assembler! 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 可执行文件可以通过使用 mov r32, imm32
而不是 RIP 相关 LEA 来利用虚拟地址 space 低 32 位中的静态地址。并且当然不是 mov r64, imm64
.)
解决方法:不能只使用 C 预处理器。 . - hello
是上下文相关的;当 .
处于不同的位置时,它具有不同的值。所以文本替换不起作用。
丑陋的解决方法:切换到 .att_syntax
并返回:
切换到 .att_syntax
并返回 mov $hello_len, %edx
丑陋且低效的解决方法:lea
这不适用于 64 位常量,但您可以使用 lea
将符号地址放入寄存器。
不幸的是clang/LLVM总是使用disp32
寻址模式,即使对于寄存器+小常量,当小常量是命名符号时也是如此。我猜它真的是把它当作一个可能有搬迁的地址。
鉴于此来源:
## 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 assembles 它到这个机器代码,因为 objdump -drwC -Mintel
disassembled。请注意,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]
.
如果您将操作码更改为:
lea rax, hello_len
有效。在 old unix 中,= 或更冗长的 .set 对左值进行操作。在这个现实中,hello_len 是一个地址;具体地址 12.
我记不起 masm 语法中的 =。我记得 equ 有类似的目的,但都没有明确说明。
我们主要使用 cpp(偶尔使用 awk)来为我们完成提升,并避免使用 asm 功能。
我有以下代码,我在 macOS 上使用 clang:
.intel_syntax noprefix
.data
hello: .ascii "Hello world\n"
hello_len = . - hello
.text
.globl _main
_main:
mov rax, 0x2000004
mov rdi, 1
lea rsi, [rip + hello]
mov rdx, hello_len # <-------
syscall
mov rax, 0x2000001
syscall
虽然它看起来应该打印 "Hello World" 并退出,但它实际上会出现段错误。事实证明这是因为 mov rdx, hello_len
实际上试图移动地址 hello_len
的值,而不是 hello_len
本身的值。
如果我使用 AT&T 语法,该行将是 movq $hello_len, %rdx
,它可以正常工作。 clang 版本的 GAS intel 语法的等价物是什么?
使用真正的 GAS(在 Linux 上),您的代码 assemble 可以达到您想要的 mov rdx, sign_extended_imm32
。
但是,是的,不幸的是 assemble 将其发送到 mov rdx, [0xc]
。这可能是也可能不是错误,但绝对是不兼容的。 (MacOS 的 gcc
命令根本不是 GNU 编译器集合,它是 Apple Clang:LLVM 后端,clang 前端,与 GNU 项目完全无关。)
OFFSET hello_len
好像不行。 (我一开始就错误地认为它会,但 clang 不支持 OFFSET 运算符;它 .intel_syntax
不能完全使用。)
这是clang bug has already been reported. See also
Clang 甚至 assemble 它自己的 .intel_syntax noprefix
输出。
可能没有办法让 clang Intel 语法使用符号的值(地址)作为立即数。
// hello.c
char hello[] = "abcdef";
char *foo() { return hello; }
clang -S
打印 mov edi, offset hello
而不会 assemble 使用 clang 的内置 assembler! 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 可执行文件可以通过使用 mov r32, imm32
而不是 RIP 相关 LEA 来利用虚拟地址 space 低 32 位中的静态地址。并且当然不是 mov r64, imm64
.)
解决方法:不能只使用 C 预处理器。 . - hello
是上下文相关的;当 .
处于不同的位置时,它具有不同的值。所以文本替换不起作用。
丑陋的解决方法:切换到 .att_syntax
并返回:
切换到 .att_syntax
并返回 mov $hello_len, %edx
丑陋且低效的解决方法:lea
这不适用于 64 位常量,但您可以使用 lea
将符号地址放入寄存器。
不幸的是clang/LLVM总是使用disp32
寻址模式,即使对于寄存器+小常量,当小常量是命名符号时也是如此。我猜它真的是把它当作一个可能有搬迁的地址。
鉴于此来源:
## 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 assembles 它到这个机器代码,因为 objdump -drwC -Mintel
disassembled。请注意,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]
.
如果您将操作码更改为:
lea rax, hello_len
有效。在 old unix 中,= 或更冗长的 .set 对左值进行操作。在这个现实中,hello_len 是一个地址;具体地址 12.
我记不起 masm 语法中的 =。我记得 equ 有类似的目的,但都没有明确说明。 我们主要使用 cpp(偶尔使用 awk)来为我们完成提升,并避免使用 asm 功能。