【问题标题】:Weird SSE assembler instructions for double negation用于双重否定的奇怪 SSE 汇编程序指令
【发布时间】:2017-07-17 09:30:13
【问题描述】:

GCC 和 Clang 编译器似乎使用了一些黑魔法。 C 代码只是否定 double 的值,但汇编指令涉及按位 XOR 和指令指针。有人可以解释发生了什么以及为什么它是最佳解决方案。谢谢。

test.c的内容:

void function(double *a, double *b) {
    *a = -(*b); // This line.
}

生成的汇编指令:

(gcc)
0000000000000000 <function>:
 0: f2 0f 10 06             movsd  xmm0,QWORD PTR [rsi]
 4: 66 0f 57 05 00 00 00    xorpd  xmm0,XMMWORD PTR [rip+0x0]        # c <function+0xc>
 b: 00 
 c: f2 0f 11 07             movsd  QWORD PTR [rdi],xmm0
10: c3                      ret 

(clang)
0000000000000000 <function>:
 0: f2 0f 10 06             movsd  xmm0,QWORD PTR [rsi]
 4: 0f 57 05 00 00 00 00    xorps  xmm0,XMMWORD PTR [rip+0x0]        # b <function+0xb>
 b: 0f 13 07                movlps QWORD PTR [rdi],xmm0
 e: c3                      ret    

地址0x4 的汇编指令代表“这一行”,但我不明白它是如何工作的。 xorpd/xorps 指令应该是逐位的 XORPTR [rip] 是指令指针。

我怀疑在执行时rip 指向了0f 57 05 00 00 00 0f 字节带附近的某个位置,但我不太明白这是如何工作的,以及为什么两个编译器都选择这种方法。

附:我应该指出这是使用-O3编译的

【问题讨论】:

  • 我无法重现这个。两个编译器都与一个表示 -0.0 的常量进行异或,它们从内存中加载。这对RIP-relative offsetting 有意义。没有意义的是,您的反汇编显示它们将下一条指令的字节加载为浮点常量。那是不对的。您是否有可能以某种方式从拆卸中剥离了偏移量?还是你的反汇编程序很困惑?
  • 我绝对不会手动更改任何内容。使用上面的逐字代码和命令gcc test.c -c -O3 -o test.oobjdump -S -M intel test.o 这是我得到的输出。我将重申我知道PTR [rip] 是什么,并且您可以通过更改双精度位的前导位来反转符号。我问的原因是因为这两个想法并没有在我的脑海中混合。
  • 您越确定自己没有犯错,您犯错的可能性就越大。从控制台复制了错误的部分。抱歉,汇编列表现在应该是正确的。奇怪的xorrip 仍然存在。
  • 这看起来像未链接的代码,其中偏移量尚未修复。因此[rip + 0x0]part。这是 RIP 相对寻址,但偏移量还没有被链接器放置到位。请参阅接受的答案,这显然使用了正确链接的代码。

标签: gcc assembly x86-64 sse magic-numbers


【解决方案1】:

对我来说,gcc-S -O3 相同代码选项的输出是:

    .file   "test.c"
    .text
    .p2align 4,,15
    .globl  function
    .type   function, @function
function:
.LFB0:
    .cfi_startproc
    movsd   (%rsi), %xmm0
    xorpd   .LC0(%rip), %xmm0
    movsd   %xmm0, (%rdi)
    ret
    .cfi_endproc
.LFE0:
    .size   function, .-function
    .section    .rodata.cst16,"aM",@progbits,16
    .align 16
.LC0:
    .long   0
    .long   -2147483648
    .long   0
    .long   0
    .ident  "GCC: (Ubuntu 6.3.0-12ubuntu2) 6.3.0 20170406"
    .section    .note.GNU-stack,"",@progbits

这里xorpd指令使用指令指针相对寻址,偏移量指向.LC0标签,64位值0x8000000000000000(第63位设置为1)。

.LC0:
    .long   0
    .long   -2147483648

如果你的编译器是大端的,这些行被交换了。

0x8000000000000000 对双精度值进行异或运算会将符号位(即第 63 位)设置为 1,以获得负值。

clang 使用xorps 指令的方式与此异或双精度值的前 32 位的方式相同。

如果您使用-r 选项运行对象转储,它将显示在运行程序之前应在程序上完成的重定位。

objdump -d test.o -r

test.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <function>:
   0:   f2 0f 10 06             movsd  (%rsi),%xmm0
   4:   66 0f 57 05 00 00 00    xorpd  0x0(%rip),%xmm0        # c <function+0xc>
   b:   00 
            8: R_X86_64_PC32    .LC0-0x4
   c:   f2 0f 11 07             movsd  %xmm0,(%rdi)
  10:   c3                      retq   

Disassembly of section .text.startup:

0000000000000000 <main>:
   0:   31 c0                   xor    %eax,%eax
   2:   c3                      retq   

&lt;function + 0xb&gt;,我们有一个类型为 R_X86_64_PC32 的重定位。

PS:我使用的是 gcc 6.3.0

【讨论】:

  • 正确,但不是第 0 位设置为 1,第 63 位(符号位)设置为 1,因此十六进制值 0x8000000000000000。
【解决方案2】:

xorps xmm0,XMMWORD PTR [rip+0x0]

[]包围的指令的任何部分都是对内存的间接引用。 在这种情况下,引用地址 RIP+0 处的内存
(我怀疑它实际上是 RIP+0,您可能已经编辑了实际偏移量)

X64 指令集增加了instruction pointer relative addressing。这意味着您可以在程序中拥有(通常是只读的)数据,即使程序在内存中移动,您也可以轻松处理这些数据。

XOR xmm0,Y 反转 xmm0 中在 Y 中设置的所有位。
否定涉及反转符号位,这就是使用 xor 的原因。特别是xorpd/s,因为我们正在处理双重响应。单浮子。

【讨论】:

  • 我很确定我没有手动更改任何内容。也许,您可以发布您得到的输出,以便我可以尝试比较。
  • @RuRo,您可能没有使用--reloc 选项运行objdump。然后你会得到没有重定位的原始指令,这些指令将用于在执行之前重写一些参数。
  • @FlorianWeimer 该死。你是对的。我只是假设没有重定位,因为没有什么可以链接的。如果他知道,我们的教授会生气的。我们在课堂上完全学会了这一点。
  • @RuRo:我喜欢alias disas='objdump -drwC -Mintel'
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-05-11
  • 1970-01-01
  • 2015-04-08
  • 1970-01-01
  • 1970-01-01
  • 2014-11-10
  • 1970-01-01
相关资源
最近更新 更多