带堆栈操作的 GCC 内联汇编

标签 gcc assembly x86 inline-assembly

我需要这样的内联汇编代码:

  • 我有 一对 (因此,它是平衡的)组件内部的推/弹出操作
  • 我在内存中也有一个变量(所以,不是注册)作为输入

  • 像这样:
    __asm__ __volatile__ ("push %%eax\n\t"
            // ... some operations that use ECX as a temporary
            "mov %0, %%ecx\n\t"
            // ... some other operation
            "pop %%eax"
    : : "m"(foo));
    // foo is my local variable, that is to say, on stack
    

    反汇编编译后的代码时,编译器给出的内存地址如0xc(%esp) ,它是相对于 esp ,因此,这段代码将无法正常工作,因为我有一个 push操作前mov .
    因此,我怎么能告诉编译我不喜欢 foo相对于 esp ,但任何类似 -8(%ebp)相对于 ebp。

    附言您可以建议我可以放eax在 Clobbers 里面,但它只是一个示例代码 .我不想展示我不接受此解决方案的完整原因。

    最佳答案

    当您有任何内存输入/输出时,通常应该避免在 inline-asm 中修改 ESP,因此您不必禁用优化或强制编译器以其他方式使用 EBP 制作堆栈帧。一大优势是然后您(或编译器)可以使用 EBP 作为额外的空闲寄存器 ;如果您已经不得不溢出/重新加载东西,则可能会显着加速。如果您正在编写内联汇编,大概这是一个热点,因此值得花费额外的代码大小来使用 ESP 相对寻址模式。

    在 x86-64 代码中,安全使用 push/pop 还有一个额外的障碍,因为 you can't tell the compiler you want to clobber the red-zone低于 RSP。 (您可以使用 -mno-red-zone 进行编译,但无法从 C 源代码中禁用它。)您可能会遇到问题 like this您在堆栈上破坏编译器数据的地方。但是,没有 32 位 x86 ABI 具有红色区域,因此这仅适用于 x86-64 System V。(或具有红色区域的非 x86 ISA。)

    您只需要-fno-omit-frame-pointer对于那个函数,如果你想做像 push 这样的 asm-only 的东西作为堆栈数据结构,因此存在可变数量的推送。或者如果优化代码大小。

    你总是可以在 asm 中编写一个完整的非内联函数并将其放在一个单独的文件中,然后你就有了完全的控制权。但只有在您的函数足够大以值得调用/ret 开销时才这样做,例如如果它包括一个完整的循环;不要让编译器call C 内部循环中的一个简短的非循环函数,破坏所有调用破坏的寄存器,并且必须确保全局变量同步。

    看来您正在使用 push/pop内联汇编,因为你没有足够的寄存器,需要保存/重新加载一些东西。 您不需要使用 push/pop 进行保存/恢复。相反,使用带有 "=m" 的虚拟输出操作数让编译器为您分配堆栈空间的约束 ,并使用 mov到/从这些插槽。 (当然,您不仅限于 mov ;如果您只需要一次或两次该值,那么将内存源操作数用于 ALU 指令可能是一种胜利。)

    这对于代码大小来说可能会稍差一些,但对于性能来说通常不会更差(并且可能会更好)。如果这还不够好,请在 asm 中编写整个函数(或整个循环),这样您就不必与编译器搏斗。

    int foo(char *p, int a, int b) {
        int t1,t2;  // dummy output spill slots
        int r1,r2;  // dummy output tmp registers
        int res;
    
        asm ("# operands: %0  %1  %2  %3  %4  %5  %6  %7  %8\n\t"
             "imull  $123, %[b], %[res]\n\t"
             "mov   %[res], %[spill1]\n\t"
             "mov   %[a], %%ecx\n\t"
             "mov   %[b], %[tmp1]\n\t"  // let the compiler allocate tmp regs, unless you need specific regs e.g. for a shift count
             "mov   %[spill1], %[res]\n\t"
        : [res] "=&r" (res),
          [tmp1] "=&r" (r1), [tmp2] "=&r" (r2),  // early-clobber
          [spill1] "=m" (t1), [spill2] "=&rm" (t2)  // allow spilling to a register if there are spare regs
          , [p] "+&r" (p)
          , "+m" (*(char (*)[]) p) // dummy in/output instead of memory clobber
        : [a] "rmi" (a), [b] "rm" (b)  // a can be an immediate, but b can't
        : "ecx"
        );
    
        return res;
    
        // p unused in the rest of the function
        // so it's really just an input to the asm,
        // which the asm is allowed to destroy
    }
    

    这与 gcc7.3 -O3 -m32 编译为以下 asm on the Godbolt compiler explorer .请注意显示编译器为所有模板操作数选择的内容的 asm 注释:它选择了 12(%esp)%[spill1]%edi%[spill2] (因为我使用 "=&rm" 作为该操作数,所以编译器在 asm 之外保存/恢复 %edi,并将其提供给我们作为该虚拟操作数)。
    foo(char*, int, int):
        pushl   %ebp
        pushl   %edi
        pushl   %esi
        pushl   %ebx
        subl    $16, %esp
        movl    36(%esp), %edx
        movl    %edx, %ebp
    #APP
    # 19 "/tmp/compiler-explorer-compiler118120-55-w92ge8.v797i/example.cpp" 1
            # operands: %eax  %ebx  %esi  12(%esp)  %edi  %ebp  (%edx)  40(%esp)  44(%esp)
        imull  $123, 44(%esp), %eax
        mov   %eax, 12(%esp)
        mov   40(%esp), %ecx
        mov   44(%esp), %ebx
        mov   12(%esp), %eax
    
    # 0 "" 2
    #NO_APP
        addl    $16, %esp
        popl    %ebx
        popl    %esi
        popl    %edi
        popl    %ebp
        ret
    

    嗯,告诉编译器我们修改哪个内存的虚拟内存操作数似乎导致了专用寄存器,我猜是因为 p操作数是 early-clobber,所以它不能使用相同的寄存器。如果您确信其他输入都不会使用与 p 相同的寄存器,我想您可能会冒着放弃早期破坏的风险。 . (即它们没有相同的值)。

    关于带堆栈操作的 GCC 内联汇编,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48853757/

    相关文章:

    汇编语言 : + vs add

    gcc - 测试指令和与指令 x86

    c++ - GCC printf 优化

    c - 警告 : initializer element is not computable at load time

    c - 仅当我在我的 C 项目中使用代码时出现的 list.h 语法错误

    performance - 最快的 64 位人口计数(汉明权重)

    c - 使用 GCC 编译时出现汇编错误

    c - 从内存中获取操作数的最坏情况时间

    gcc - 在 32 位模式下编译 gcc 原子操作时出现链接错误

    multithreading - 在 x86 上,如果 [mem] 不是 32 位对齐, "lock inc [mem]"还能正常工作吗?