2

我有以下在 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实际上试图移动位于 addresshello_len的值,而不是hello_len自身的值。

如果我使用 AT&T 语法,则该行将movq $hello_len, %rdx正常工作。clang 版本的 GAS 英特尔语法中的等价物是什么?

4

2 回答 2

2

使用真正的 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 -Smov 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]

于 2019-11-11T02:23:45.927 回答
1

如果您将操作码更改为:

lea rax, hello_len

有用。在旧的 unix as、= 或更详细的 .set 中,对左值进行操作。在这个现实中,hello_len 是一个地址;特别是地址 12。

我不记得=在 masm 语法中。我记得equ也有类似的用途,但都没有详细说明。我们主要使用cpp(偶尔使用 awk)为我们做提升,并避免了 asm 特性。

于 2019-11-11T05:50:24.153 回答