long buf[64]; register long rrax asm ("rax"); register long rrbx asm ("rbx"); register long rrsi asm ("rsi"); rrax = 0x34; rrbx = 0x39; __asm__ __volatile__ ("movq $buf,%rsi"); __asm__ __volatile__ ("movq %rax,0(%rsi);"); __asm__ __volatile__ ("movq %rbx,8(%rsi);"); printf( "buf[0] = %lx,buf[1] = %lx!\n",buf[0],buf[1] );
我得到以下输出:
buf[0] = 0,buf[1] = 346161cbc0!
而应该是:
buf[0] = 34,buf[1] = 39!
任何想法为什么它不能正常工作,如何解决?
解决方法
__asm__ ( "movq %1,0(%0)\n\t" "movq %2,8(%0)" : /* Outputs (none) */ : "r"(buf),"r"(rrax),"r"(rrbx) /* Inputs */ : "memory"); /* Clobbered */
你通常也想让GCC处理大部分的mov,注册选择等 – 即使你明确地约束寄存器(rrax是stil%rax),让信息流过GCC也可以得到意想不到的结果.
__volatile__错了.
__volatile__存在的原因是,您可以保证编译器将您的代码准确地放在哪里…这对于此代码是完全不必要的保证.实现高级功能(如内存障碍)是必要的,但如果只修改内存和寄存器,则几乎完全没有价值.
GCC已经知道它不能在printf之后移动此程序集,因为printf调用访问buf,并且buf可能被程序集破坏. GCC已经知道在rrax = 0x39之前它不能移动程序集;因为rax是汇编代码的输入.那么__volatile__可以给你什么?没有.
如果你的代码在没有__volatile__的情况下不起作用,那么在代码中有一个错误,应该是固定的,而不是添加__volatile__,希望能使一切都更好. __volatile__关键字不是魔术,不应该如此对待.
替代方案:
__volatile__是否需要您的原始代码?不,只要正确地标记输入和输入值.
/* The "S" constraint means %rsi,"b" means %rbx,and "a" means %rax The inputs and clobbered values are specified. There is no output so that section is blank. */ rsi = (long) buf; __asm__ ("movq %%rax,0(%%rsi)" : : "a"(rrax),"S"(RSSi) : "memory"); __asm__ ("movq %%rbx,0(%%rsi)" : : "b"(rrbx),"S"(rrsi) : "memory");
为什么__volatile__不帮助你在这里:
rrax = 0x34; /* Dead code */
GCC完全删除上述行的权利,因为上述问题的代码声称它从不使用rrax.
一个更清楚的例子
long global; void store_5(void) { register long rax asm ("rax"); rax = 5; __asm__ __volatile__ ("movq %%rax,(global)"); }
反汇编或多或少是你期望的在-O0,
movl $5,%rax movq %rax,(global)
但是,优化关闭后,您可以对装配进行相当的笨拙.我们来试试-O2:
movq %rax,(global)
哎呦! rax = 5;走?它是死码,因为%rax从来没有在功能中使用 – 至少在GCC知道的情况下.海湾合作委员会不会在集会内窥视.当我们删除__volatile__时会发生什么?
; empty
那么你可能会认为__volatile__是通过保持GCC不要丢弃你的宝贵的会议来为你服务的,但这只是掩盖了GCC认为你的议会没有做任何事情的事实. GCC认为您的程序集没有任何输入,不产生任何输出,并且没有内存.你最好把它拉直出来:
long global; void store_5(void) { register long rax asm ("rax"); rax = 5; __asm__ __volatile__ ("movq %%rax,(global)" : : : "memory"); }
现在我们得到以下输出:
movq %rax,(global)
更好.但是,如果您告诉GCC有关输入,那么将确保%rax首先被正确初始化:
long global; void store_5(void) { register long rax asm ("rax"); rax = 5; __asm__ ("movq %%rax,(global)" : : "a"(rax) : "memory"); }
输出,优化:
movl $5,%eax movq %rax,(global)
正确!而且我们甚至不需要使用__volatile__.
为什么__volatile__存在?
__volatile__的主要正确用法是如果您的汇编代码除了输入,输出或内存之外还执行其他操作.也许它与GCC不了解的特殊寄存器混淆,或影响IO.您在Linux内核中看到很多,但在用户空间中经常被滥用.
__volatile__关键字非常诱人,因为我们C程序员经常喜欢认为我们几乎已经在汇编语言中编程了.不是. C编译器进行了大量的数据流分析,所以您需要解释汇编代码到编译器的数据流.这样,编译器就可以像处理它所生成的程序集一样安全地操纵你的程序集.