为什么此内联程序集不能为每个指令使用单独的asm volatile语句? [英] Why is this inline assembly not working with a separate asm volatile statement for each instruction?

查看:91
本文介绍了为什么此内联程序集不能为每个指令使用单独的asm volatile语句?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对于以下代码:

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!

有什么想法为什么它不能正常工作,以及如何解决?

Any ideas why it is not working properly, and how to solve it?

推荐答案

您破坏了内存,但是不告诉GCC,因此GCC可以在程序集调用中将值缓存在buf中.如果您想使用输入和输出,请告诉GCC所有内容.

You clobber memory but don't tell GCC about it, so GCC can cache values in buf across assembly calls. If you want to use inputs and outputs, tell GCC about everything.

__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,否则您将得到意料之外的信息结果.

You also generally want to let GCC handle most of the mov, register selection, etc -- even if you explicitly constrain the registers (rrax is stil %rax) let the information flow through GCC or you will get unexpected results.

__volatile__存在的原因是,因此您可以保证编译器将您的代码准确地放置在它所在的位置...这是此代码的完全不必要的保证.这是实现内存屏障等高级功能所必需的,但如果仅修改内存和寄存器,则几乎毫无用处.

The reason __volatile__ exists is so you can guarantee that the compiler places your code exactly where it is... which is a completely unnecessary guarantee for this code. It's necessary for implementing advanced features such as memory barriers, but almost completely worthless if you are only modifying memory and registers.

GCC已经知道它不能在printf之后移动该程序集,因为printf调用访问buf,并且buf可能会被程序集所破坏. GCC已经知道它不能在rrax=0x39;之前移动程序集,因为rax是程序集代码的输入.那么__volatile__有什么用呢?没事.

GCC already knows that it can't move this assembly after printf because the printf call accesses buf, and buf could be clobbered by the assembly. GCC already knows that it can't move the assembly before rrax=0x39; because rax is an input to the assembly code. So what does __volatile__ get you? Nothing.

如果您的代码在没有__volatile__的情况下无法正常工作,则该代码中应存在一个错误,应该对其进行修复,而不是仅添加__volatile__并希望可以使一切变得更好. __volatile__关键字不是魔术,因此不应被视为

If your code does not work without __volatile__ then there is an error in the code which should be fixed instead of just adding __volatile__ and hoping that makes everything better. The __volatile__ keyword is not magic and should not be treated as such.

替代修补程序:

您的原始代码是否需要__volatile__?否.只需正确标记输入和缓冲值即可.

Is __volatile__ necessary for your original code? No. Just mark the inputs and clobber values correctly.

/* 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__在这里对您没有帮助:

Why __volatile__ doesn't help you here:

rrax = 0x34; /* Dead code */

由于上述问题中的代码声称它从未使用过rrax,因此GCC完全有权删除上面的行.

GCC is well within its rights to completely delete the above line, since the code in the question above claims that it never uses rrax.

long global;
void store_5(void)
{
    register long rax asm ("rax");
    rax = 5;
    __asm__ __volatile__ ("movq %%rax, (global)");
}

反汇编或多或少地与您在-O0所期望的一样

The disassembly is more or less as you expect it at -O0,

movl $5, %rax
movq %rax, (global)

但是在关闭优化的情况下,您对组装的态度可能很草率.让我们尝试-O2:

But with optimization off, you can be fairly sloppy about assembly. Let's try -O2:

movq %rax, (global)

哇! rax = 5;去哪里了?这是无效代码,因为函数中从未使用过%rax -至少就GCC所知. GCC不会窥视装配体内部.删除__volatile__会发生什么?

Whoops! Where did rax = 5; go? It's dead code, since %rax is never used in the function — at least as far as GCC knows. GCC doesn't peek inside assembly. What happens when we remove __volatile__?

; empty

好吧,您可能会认为__volatile__通过阻止GCC丢弃您宝贵的程序集为您提供服务,但这只是掩盖了GCC认为您的程序集没有做任何事情的事实. GCC认为您的程序集不接受任何输入,不产生任何输出,并且不占用任何内存.您最好将其理顺:

Well, you might think __volatile__ is doing you a service by keeping GCC from discarding your precious assembly, but it's just masking the fact that GCC thinks your assembly isn't doing anything. GCC thinks your assembly takes no inputs, produces no outputs, and clobbers no memory. You had better straighten it out:

long global;
void store_5(void)
{
    register long rax asm ("rax");
    rax = 5;
    __asm__ __volatile__ ("movq %%rax, (global)" : : : "memory");
}

现在我们得到以下输出:

Now we get the following output:

movq %rax, (global)

更好.但是,如果您将输入信息告知GCC,则将确保首先正确初始化%rax:

Better. But if you tell GCC about the inputs, it will make sure that %rax is properly initialized first:

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__.

Correct! And we don't even need to use __volatile__.

__volatile__的主要正确用法是,如果汇编代码执行除输入,输出或破坏内存之外的其他操作.也许它与GCC不知道或影响IO的特殊寄存器弄混了.您在Linux内核中经常看到它,但是它在用户空间中经常被滥用.

The primary correct use for __volatile__ is if your assembly code does something else besides input, output, or clobbering memory. Perhaps it messes with special registers which GCC doesn't know about, or affects IO. You see it a lot in the Linux kernel, but it's misused very often in user space.

__volatile__关键字非常具有诱惑力,因为我们C程序员经常喜欢认为我们已经几乎正在使用汇编语言进行编程.不是. C编译器进行了大量的数据流分析-因此,您需要为汇编代码向编译器说明数据流.这样,编译器就可以安全地操纵程序集块,就像操纵其生成的程序集一样.

The __volatile__ keyword is very tempting because we C programmers often like to think we're almost programming in assembly language already. We're not. C compilers do a lot of data flow analysis — so you need to explain the data flow to the compiler for your assembly code. That way, the compiler can safely manipulate your chunk of assembly just like it manipulates the assembly that it generates.

如果您发现自己经常使用__volatile__,则可以选择在汇编文件中编写整个函数或模块.

If you find yourself using __volatile__ a lot, as an alternative you could write an entire function or module in an assembly file.

这篇关于为什么此内联程序集不能为每个指令使用单独的asm volatile语句?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆