【问题标题】:Why does cmov always return t_val? [duplicate]为什么 cmov 总是返回 t_val? [复制]
【发布时间】:2020-10-26 07:09:54
【问题描述】:

我想用上面的cmov函数实现的结果是,如果pred=true,返回t_val,否则返回f_val。但是在实际操作中,每次都会返回t_val。

#include<stdio.h>
#include <stdint.h>
#include <stdlib.h>
int cmov(uint8_t pred, uint32_t t_val, uint32_t f_val) {
uint32_t result;
 __asm__ volatile (
 "mov %2, %0;"
 "test %1, %1;"
 "cmovz %3, %0;"
 "test %2, %2;"
 : "=r" (result)
 : "r" (pred), "r" (t_val), "r" (f_val)
 : "cc"
 );
 return result;
 }
 
int main()  {  
  
     int a=1,b=4,c=5,d;
    int res = (a==3); //
    printf("res = %d\n",res);
    d = cmov(res,b,c);
    printf("d = %d\n",d);
    a=3;
    res = (a==3);
    d = cmov(res,b,c);
    printf("d = %d\n",d);
return 0;
};  

【问题讨论】:

  • 因为您两次都传递了非零的b,所以z 条件为假。如果不清楚,请使用调试器单步执行 asm。这似乎真的很低效;对输出使用"+r" 约束,因此您不需要在asm 模板中使用mov;让编译器为您完成result=t_valtest %2,%2 之后 cmoz 也无所作为;你甚至没有使用 GCC6 标志输出操作数。
  • test %2 %2 用于刷新标志位,防止部分信息泄露。第一次传入的res为0,第二次传入的res为1。这个在printf信息中可以看出。 b 非零会影响它吗?不是用 res 做选择吗?
  • 哦,是的,您将res 作为第一个参数传递的令人困惑的杂乱示例实际上是谓词,而不是结果。用可能的解释修正了我的答案:缺少早期破坏声明。不过,“泄露信息”毫无意义;如果攻击者可以看到 FLAGS 值,他们就可以看到寄存器值,并且谓词仍在寄存器中。

标签: c assembly x86 inline-assembly


【解决方案1】:

您在输出 ("=&amp;r") 上缺少一个 early-clobber,因此 GCC 可能会为输出选择相同的寄存器作为输入之一,可能是 pred。所以test %1,%1 可能正在测试t_val (b)。使用调试器单步执行 asm,和/或查看 GCC 的 asm 输出。 (在https://godbolt.org/gcc -S 上)。

这似乎效率很低;对输出使用"+r"(result) 约束(使用uint32_t result=t_val;),因此您不需要在asm 模板中使用mov;让编译器为您完成result=t_val,可能只需选择相同的寄存器即可。

test %2,%2之后 cmoz 也无所作为;您甚至没有使用 GCC6 标志输出操作数。这是一个完全浪费的指令。

另外,这不需要是volatile。输出是输入的纯函数,如果输出未使用,则根本不需要运行。

对于 just 一个 cmov 使用内联 asm 可能是个坏主意;使用-O3 编译并编写您的源代码,以便 GCC 认为将 if 转换为无分支代码是一个好主意。内联 asm 破坏了常量传播并破坏了其他优化。这种方式会迫使您在模板中使用 test 指令,而不是从早期的 add 或其他任何内容中读取 FLAGS 集,并且不让编译器为多个 cmov 或其他指令重用相同的 FLAGS 结果。 https://gcc.gnu.org/wiki/DontUseInlineAsm

或者,如果您无法手动控制编译器来制作您想要的 asm,请在 asm 中编写更多您的实际用例,而不仅仅是 cmov 的包装。

【讨论】:

  • 感谢您的回答。但是如果 t_val 是一个地址,如何将 t_val 地址对应的数据传输到结果中呢? cmovz 一次传输一个字节吗?
  • @cyj:如果需要,取消引用 cmov 的结果。或者,如果t_val 被保证是一个有效地址,即使条件为假,您也可以将其用作内存操作数。但是cmov can't do a conditional load: the load always happens with a memory source,提供 ALU 选择。不,当然cmov 一次不会传输一个字节。它只是两个输入之间的 ALU 选择,无论操作数大小是:16、32 或 64 位。 uops.info 表明它是现代 CPU 上的单个 uop。
  • 在您刚才的回答中,您提到了将 if 转换为无分支的代码。这实际上是我想做的。您能否对您回答中提到的这一点进行更详细的介绍?
  • @cyj: Conditional move (cmov) in GCC compiler / Convert GCC Inline Assembly CMOV to Visual Studio Assembler / Make gcc use conditional moves / gcc optimization flag -O3 makes code slower than -O2。出于某种安全原因,它是否需要无分支,例如定时侧通道没有依赖于数据的分支?如果没有,提示编译方向正确并让它做出调整决定。 (如果可能,使用配置文件引导优化)
  • 我想尝试使用 cmov 关闭一些时间侧通道或缓存侧通道。你认为可以做到吗?还是从效率的角度?
猜你喜欢
  • 1970-01-01
  • 2017-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-11
  • 2017-03-28
相关资源
最近更新 更多