对于以下代码:
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!
有什么想法为什么它不能正常工作,以及如何解决?
您破坏了内存,但是不告诉GCC,因此GCC可以buf在程序集调用中缓存值。如果要使用输入和输出,请告知GCC所有相关信息。
buf
__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还是会得到意想不到的结果。
mov
%rax
__volatile__
__volatile__存在的原因是,您可以保证编译器将您的代码准确地放置在该位置……这对于此代码是 完全不必要的 保证。这是实现内存屏障等高级功能所必需的,但如果仅修改内存和寄存器,则几乎是一文不值。
GCC已经知道它不能在此之后移动该程序集,printf因为该printf调用访问buf,并且buf可能被该程序集破坏。GCC已经知道它之前不能移动程序集,rrax=0x39;因为它rax是程序集代码的输入。那么你__volatile__能得到什么呢?没有。
printf
rrax=0x39;
rax
如果您的代码无法正常运行,__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。
rrax
long global; void store_5(void) { register long rax asm ("rax"); rax = 5; __asm__ __volatile__ ("movq %%rax, (global)"); }
拆卸程度与您预期的差不多-O0,
-O0
movl $5, %rax movq %rax, (global)
但是,关闭优化功能后,您对组装的了解可能会很草率。让我们尝试-O2:
-O2
movq %rax, (global)
哎呀!哪儿rax = 5;去了?它是无效代码,因为%rax从未在函数中使用过- 至少就GCC所知。GCC不会窥视内部装配体。移除后会发生什么__volatile__?
rax = 5;
; empty
好吧,您可能会认为__volatile__通过阻止GCC丢弃您宝贵的程序集为您提供服务,但这只是掩盖了GCC认为您的程序集没有 做 任何事情的事实。GCC认为您的程序集不接受任何输入,不产生任何输出,并且不占用任何内存。您最好理顺一下:
long global; void store_5(void) { register long rax asm ("rax"); rax = 5; __asm__ __volatile__ ("movq %%rax, (global)" : : : "memory"); }
现在我们得到以下输出:
更好。但是,如果您将输入信息告知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__是,如果汇编代码执行除输入,输出或破坏内存之外的其他操作。也许它与GCC不知道或影响IO的特殊寄存器弄混了。您在Linux内核中经常看到它,但是它在用户空间中经常被滥用。
该__volatile__关键字是非常诱人的,因为我们的C程序员往往喜欢把我们 几乎 在汇编语言编程了。不是。C编译器进行了大量数据流分析- 因此您需要为汇编代码向编译器说明数据流。这样,编译器可以安全地操纵程序集的块,就像操纵其生成的程序集一样。
如果发现自己使用__volatile__了很多东西,则可以选择在汇编文件中编写整个函数或模块。