即使我有Clobbers,GCC也不会在我的内联asm函数调用周围推送寄存器 [英] GCC doesn't push registers around my inline asm function call even though I have clobbers

查看:24
本文介绍了即使我有Clobbers,GCC也不会在我的内联asm函数调用周围推送寄存器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个修改(ecx)(或任何其他寄存器)的函数(C)

I have a function (C) that modifies "ecx" (or any other registers)

int proc(int n) {
    int ret;
    asm volatile ("movl %1, %%ecx\n\t" // mov (n) to ecx
                  "addl $10, %%ecx\n\t" // add (10) to ecx (n)
                  "movl %%ecx, %0" /* ret = n + 10 */
                  : "=r" (ret) : "r" (n) : "ecx");
    return ret;
}

现在我想在另一个函数中调用此函数,该函数在调用"proc"函数之前将其值移至"ecx"中

now i want to call this function in another function which that function moves a value in "ecx" before calling "proc" function

int main_proc(int n) {
    asm volatile ("movl     $55, %%ecx" ::: "ecx"); /// mov (55) to ecx
    int ret;
    asm volatile ("call     proc" : "=r" (ret) : "r" (n) : "ecx"); // ecx is modified in proc function and the value of ecx is not 55 anymore even with "ecx" clobber

    asm volatile ("addl     %%ecx, %0" : "=r" (ret));

    return ret;
}

在此函数中,将(55)移入"ecx"寄存器,然后调用"proc"函数(修改"ecx").在这种情况下,"proc"功能必须先按下"ecx",然后将其弹出,但是这种情况不会发生!这是具有(-O3)优化级别的组装源

in this function, (55) is moved into "ecx" register and then "proc" function is called (which modifies "ecx"). in this situation, "proc" function Must push "ecx" first and pop it at the end but it's not going to happen !!!! this is the assembly source with (-O3) optimiaztion level

proc:
        movl %edi, %ecx
        addl $10, %ecx
        movl %ecx, %eax
        ret
main_proc:
        movl     $55, %ecx
        call     proc
        addl     %ecx, %eax
        ret

为什么GCC不会对"ecx"寄存器使用(push)和(pop)?我也用过"ecx"垃圾桶!!!!!

why GCC is not going to use (push) and (pop) for "ecx" register ?? i used "ecx" clobber too !!!!!

推荐答案

您正在使用内联asm完全错误.您的输入/输出约束需要完全描述每个asm语句的输入/输出.要在asm语句之间获取数据,必须将它们保存在它们之间的C变量中.

You are using inline asm completely wrong. Your input/output constraints need to fully describe the inputs / outputs of each asm statement. To get data between asm statements, you have to hold it in C variables between them.

此外, call 通常在内联asm中也不安全,特别是在System V ABI的x86-64代码中,它踩到了gcc可能一直在存放东西的红色区域.没有办法宣布这一点.您可以先使用 sub $ 128,%rsp 跳过红色区域,或者可以像普通人一样从纯C进行调用,这样编译器就知道了.(请记住, call 会推送一个寄信人地址.)您的内联汇编甚至没有任何意义;您的 proc 需要一个arg,但是您在调用方中没有执行任何操作来传递一个arg.

Also, call isn't safe inside inline asm in general, and specifically in x86-64 code for the System V ABI it steps on the red-zone where gcc might have been keeping things. There's no way to declare a clobber on that. You could use sub $128, %rsp first to skip past the red zone, or you could make calls from pure C like a normal person so the compiler knows about it. (Remember that call pushes a return address.) Your inline asm doesn't even make sense; your proc takes an arg but you didn't do anything in the caller to pass one.

proc 中由编译器生成的代码也可能破坏了任何其他调用阻塞的寄存器,因此您至少需要在这些寄存器上声明Clobbers.或在asm中手写整个功能,这样您就知道要放入clobber中的内容了.

The compiler-generated code in proc could have also destroyed any other call-clobbered registers, so you at least need to declare clobbers on those registers. Or hand-write the whole function in asm so you know what to put in clobbers.

为什么GCC不会对"ecx"寄存器使用(push)和(pop)?我也用过"ecx"垃圾桶!!!!!

why GCC is not going to use (push) and (pop) for "ecx" register ?? i used "ecx" clobber too !!!!!

一个 ecx 容器告诉GCC,此asm语句破坏了ECX以前的 GCC .在两个单独的inline-asm语句中使用ECX隔离器不会声明它们之间的任何类型的数据依赖性.

An ecx clobber tells GCC that this asm statement destroys whatever GCC had in ECX previously. Using an ECX clobber in two separate inline-asm statements doesn't declare any kind of data dependency between them.

这并不等同于声明一个寄存器asm局部变量,如
将int foo asm("ecx"); 注册为第一个和最后一个asm语句的"+ r"(foo)操作数.(或更简单地说,您将其与"+ c" 约束一起使用,以使普通变量选择ECX.)

It's not equivalent to declaring a register-asm local variable like
register int foo asm("ecx"); that you use as a "+r" (foo) operand to the first and last asm statement. (Or more simply that you use with a "+c" constraint to make an ordinary variable pick ECX).

从GCC的角度来看,您的消息来源仅意味着约束+ Clobbers所说的内容.

From GCC's point of view, your source means only what the constraints + clobbers tell it.

int main_proc(int n) {
    asm volatile ("movl     $55, %%ecx" ::: "ecx");
      // ^^ black box that destroys ECX and produces no outputs
    int ret;
    asm volatile ("call     proc" : "=r" (ret) : "r" (n) : "ecx");
      // ^^ black box that can take `n` in any register, and can produce `ret` in any reg.  And destroys ECX.

    asm volatile ("addl     %%ecx, %0" : "=r" (ret));
     // ^^ black box with no inputs that can produce a new value for `ret` in any register

    return ret;
}

我怀疑您希望最后一个asm语句为"+ r"(ret)来读取/写入C变量 ret ,而不是告诉GCC它已输出-只要.因为您的asm会将其用作 add 的输入和输出.

I suspect you wanted the last asm statement to be "+r"(ret) to read/write the C variable ret instead of telling GCC that it was output-only. Because your asm uses it as an input as well as output as the destination of an add.

在第二个asm语句中添加诸如#%% 0 =%0 %% 1 =%1 之类的注释以查看哪个注册了"= r" "r" 约束.在> Godbolt编译器浏览器 :

It might be interesting to add comments like # %%0 = %0 %%1 = %1 inside your 2nd asm statement to see which registers the "=r" and "r" constraints picked. On the Godbolt compiler explorer:

# gcc9.2 -O3 
main_proc:
        movl     $55, %ecx
        call     proc         # %0 = %edi   %1 = %edi
        addl     %ecx, %eax    # "=r" happened to pick EAX,
                      # which happens to still hold the return value from  proc
        ret

在此函数内联到其他内容之后,可能不会发生选择EAX作为添加目的地的事故.或GCC碰巧在asm语句之间放置了一些编译器生成的指令.( asm volatile 是编译时重新排序的障碍,但不是st脚的障碍.它肯定完全停止了优化工作.)

That accident of picking EAX as the add destinatino might not happen after this function inlines into something else. or GCC happens to put some compiler-generated instructions between asm statements. (asm volatile is barrier to compile-time reordering but not not a strog one. It only definitely stops optimizing away entirely).

请记住,内联汇编模板纯粹是文本替换;要求编译器将操作数填充到注释中与模板字符串中的其他任何地方都没有不同.(默认情况下,Godbolt会删除注释行,因此有时将它们附加到其他说明或nop上很方便).

Remember that inline asm templates are purely text substitution; asking the compiler to fill in an operand into a comment is no different from anywhere else in the template string. (Godbolt strips comment lines by default so sometimes it's handy to tack them onto other instructions, or onto a nop).

如您所见,这是64位代码(按照x86-64 SysV调用约定, n 进入EDI,就像您构建代码的方式一样),因此 push%ecx 无法编码. push%rcx 是.

As you can see, this is 64-bit code (n arrives in EDI as per the x86-64 SysV calling convention, like how you built your code), so push %ecx wouldn't be encodeable. push %rcx would be.

当然,如果GCC确实想使用"ecx" 标记来保留asm语句后的值,那么它将只使用 mov%ecx,%edx 或其他不在Clobber列表中的其他呼叫密集型寄存器.

Of course if GCC actually wanted to keep a value around past an asm statement with an "ecx" clobber, it would have just used mov %ecx, %edx or whatever other call-clobbered register that wasn't in the clobber list.

这篇关于即使我有Clobbers,GCC也不会在我的内联asm函数调用周围推送寄存器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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