简体   繁体   English

即使我有问题,GCC也不会在我的内联asm函数调用周围推送寄存器

[英]GCC doesn't push registers around my inline asm function call even though I have clobbers

I have a function (C) that modifies "ecx" (or any other registers) 我有一个修改(ecx)(或任何其他寄存器)的函数(C)

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;
}

now i want to call this function in another function which that function moves a value in "ecx" before calling "proc" function 现在我想在另一个函数中调用该函数,该函数在调用“ proc”函数之前将其值移至“ ecx”中

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;
}

in this function, (55) is moved into "ecx" register and then "proc" function is called (which modifies "ecx"). 在该函数中,将(55)移入“ ecx”寄存器,然后调用“ proc”函数(修改“ ecx”)。 in this situation, "proc" function Must push "ecx" first and pop it at the end but it's not going to happen !!!! 在这种情况下,“ proc”功能必须先按下“ ecx”,然后将其弹出,但是这种情况不会发生! this is the assembly source with (-O3) optimiaztion level 这是(-O3)优化级别的组装源

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

why GCC is not going to use (push) and (pop) for "ecx" register ?? 为什么GCC不会为“ ecx”寄存器使用(push)和(pop)? i used "ecx" clobber too !!!!! 我也用过“ ecx”垃圾!

You are using inline asm completely wrong. 您正在使用内联汇编完全错误。 Your input/output constraints need to fully describe the inputs / outputs of each asm statement. 您的输入/输出约束需要完全描述每个asm语句的输入/输出。 To get data between asm statements, you have to hold it in C variables between them. 要在asm语句之间获取数据,必须将它们保存在它们之间的C变量中。

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. 同样,在内联汇编中call通常是不安全的,特别是在System V ABI的x86-64代码中,它踩到了gcc可能一直在保存东西的红色区域。 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. 您可以使用sub $128, %rsp先使用sub $128, %rsp跳过红色区域,也可以像普通人一样从纯C进行调用,以便编译器知道这一点。 (Remember that call pushes a return address.) Your inline asm doesn't even make sense; (请记住, call推送一个寄信人地址。)您的内联汇编甚至没有意义; your proc takes an arg but you didn't do anything in the caller to pass one. 您的proc需要一个arg,但是您没有在调用方中做任何事情来传递一个。

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. proc由编译器生成的代码也可能破坏了其他所有调用阻塞的寄存器,因此您至少需要在这些寄存器上声明Clobbers。 Or hand-write the whole function in asm so you know what to put in clobbers. 或在asm中手写整个功能,这样您就知道要放入什么东西了。

why GCC is not going to use (push) and (pop) for "ecx" register ?? 为什么GCC不会为“ ecx”寄存器使用(push)和(pop)? i used "ecx" clobber too !!!!! 我也用过“ ecx”垃圾!

An ecx clobber tells GCC that this asm statement destroys whatever GCC had in ECX previously. ecx泄漏告诉GCC,此asm语句破坏了ECX以前在GCC中拥有的所有内容。 Using an ECX clobber in two separate inline-asm statements doesn't declare any kind of data dependency between them. 在两个单独的inline-asm语句中使用ECX Clobber不会声明它们之间的任何类型的数据依赖关系。

It's not equivalent to declaring a register-asm local variable like 这不等于声明一个register-asm局部变量,例如
register int foo asm("ecx"); that you use as a "+r" (foo) operand to the first and last asm statement. 用作第一个和最后一个asm语句的"+r" (foo)操作数。 (Or more simply that you use with a "+c" constraint to make an ordinary variable pick ECX). (或更简单地说,您可以使用"+c"约束使普通变量选择ECX)。

From GCC's point of view, your source means only what the constraints + clobbers tell it. 从GCC的角度来看,您的来源仅表示约束+障碍物所说明的内容。

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;
}

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. 我怀疑您希望最后一个asm语句为"+r"(ret)来读取/写入C变量ret而不是告诉GCC它仅是输出。 Because your asm uses it as an input as well as output as the destination of an add . 因为您的asm会将其用作输入,也将输出用作add的目的地。

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. 在第二个asm语句中添加诸如# %%0 = %0 %%1 = %1类的注释以查看选择了哪个寄存器"=r""r"约束可能会很有趣。 On the Godbolt compiler explorer : Godbolt编译器浏览器上

# 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

That accident of picking EAX as the add destinatino might not happen after this function inlines into something else. 在此函数内联到其他内容之后,可能不会发生选择EAX作为添加目的地的事故。 or GCC happens to put some compiler-generated instructions between asm statements. 或GCC碰巧在asm语句之间放置了一些编译器生成的指令。 ( asm volatile is barrier to compile-time reordering but not not a strog one. It only definitely stops optimizing away entirely). asm volatile是编译时重新排序的障碍,但不是rog脚的障碍。它肯定会完全停止优化)。

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). (默认情况下,Godbolt会删除注释行,因此有时将它们附加到其他指令或nop上很方便)。

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. 如您所见,这是64位代码(按照x86-64 SysV调用约定, n到达EDI中,就像您构建代码的方式一样),因此push %ecx将不可编码。 push %rcx would be. push %rcx

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. 当然,如果GCC确实希望使用"ecx"挡板保留过去的asm语句值,那么它将只使用mov %ecx, %edx或不在挡板列表中的任何其他调用密集的寄存器。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM