如何让 `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 功能。