在GCC内联汇编中影响内存操作数寻址模式的早期破坏者的不正确行为的具体示例?

以下摘自GCC 手册的 Extended Asm docs,关于使用asm关键字在 C 中嵌入汇编指令:

如果一个输出参数 ( a ) 允许寄存器约束而另一个输出参数 ( b ) 允许内存约束,则会出现同样的问题。GCC 生成的用于访问b 中的内存地址的代码
可以包含可能由a共享的寄存器,并且 GCC 将这些寄存器视为 asm 的输入。如上所述,GCC 假设在写入任何输出之前消耗此类输入寄存器。如果 asm 语句在使用 b 之前写入 a,则此假设可能会导致不正确的行为。与对寄存器约束结合“&”改性剂确保修改一个不影响通过引用的地址b. 否则,位置b,如果未定义
一个使用之前改性b

斜体句子表示如果 asm 语句a在使用b.

我无法弄清楚这种“不正确的行为”是如何发生的,所以我希望有一个具体的 asm 代码示例来演示“不正确的行为”,以便我可以深入理解这一段。

当两个这样的 asm 代码并行运行时,我可以察觉到问题,但上面的段落没有提到多处理场景。

如果我们只有一个单核的CPU,能否请您出示一个asm代码,可能会产生这样的错误行为,即修改a影响引用的地址,b使得其位置b未定义。

我唯一熟悉的汇编语言是 Intel x86 汇编,因此请让示例针对该平台。

回答

考虑以下示例:

extern int* foo();
int bar()
{
    int r;

    __asm__(
        "mov $0, %0 nt"
        "add %1, %0"
    : "=r" (r) : "m" (*foo()));

    return r;
}

通常的调用约定将返回值放入eax寄存器。因此,编译器很有可能决定使用eax自始至终,以避免不必要的复制。生成的程序集可能如下所示:

extern int* foo();
int bar()
{
    int r;

    __asm__(
        "mov $0, %0 nt"
        "add %1, %0"
    : "=r" (r) : "m" (*foo()));

    return r;
}

请注意,下一条指令之前的mov $0, %eaxeax尝试使用它来引用输入参数,因此此代码将崩溃。使用早期的 clobber,您会强制编译器选择不同的寄存器。就我而言,结果代码是:

        subl    $12, %esp
        call    foo
        mov $0, %eax
        add (%eax), %eax
        addl    $12, %esp
        ret

编译器可以改为将结果移动foo()edx(或任何其他空闲寄存器)中,如下所示:

        subl    $12, %esp
        call    foo
        mov $0, %edx
        add (%eax), %edx
        addl    $12, %esp
        movl    %edx, %eax
        ret

这个例子对输入参数使用了内存约束,但这个概念同样适用于输出。

  • Indeed, https://godbolt.org/z/x4WMY7dns shows exactly the breakage you're talking about, and that an `"=&r"` early-clobber output avoids it. (With GCC11 for x86-64)

回答

鉴于下面的代码,Apple Clang 11-O3使用(%rax)a%eaxfor b

void foo(int *a)
{
    __asm__(
            "nop    # a is %[a].n"
            "nop    # b is %[b].n"
            "nop    # c is %[c].n"
            "nop    # d is %[d].n"
            "nop    # e is %[e].n"
            "nop    # f is %[f].n"
            "nop    # g is %[g].n"
            "nop    # h is %[h].n"
            "nop    # i is %[i].n"
            "nop    # j is %[j].n"
            "nop    # k is %[k].n"
            "nop    # l is %[l].n"
            "nop    # m is %[m].n"
            "nop    # n is %[n].n"
            "nop    # o is %[o].n"
        :
            [a] "=m" (a[ 0]),
            [b] "=r" (a[ 1]),
            [c] "=r" (a[ 2]),
            [d] "=r" (a[ 3]),
            [e] "=r" (a[ 4]),
            [f] "=r" (a[ 5]),
            [g] "=r" (a[ 6]),
            [h] "=r" (a[ 7]),
            [i] "=r" (a[ 8]),
            [j] "=r" (a[ 9]),
            [k] "=r" (a[10]),
            [l] "=r" (a[11]),
            [m] "=r" (a[12]),
            [n] "=r" (a[13]),
            [o] "=r" (a[14])
        );
}

因此,如果将nop指令和注释替换为%[b]之前写入 的实际指令%[a],它们将破坏 所需的地址%[a]

  • I was wondering why GCC and clang are so reluctant to reuse a reg, preferring even to save/restore call-preserved regs like RBX. Turns out it's just because it needs the array pointer `int *a` after the asm statement, to get the outputs from their registers into `a[...]`. Only if you fully max out the number of reg outputs you can have will gcc or clang choose EDI as an output https://godbolt.org/z/9eeWfezzo. (With clang doing a really horrible job in the code after, spilling them *all* to the stack instead of just 1 to make room to reload the pointer, then copying 4B at a time.)
  • `bar()` in the same Godbolt link just returns the sum of 2 of the output, not storing to `a[]` after the asm statement; then clang will happily choose EDI as an output before picking any of R8D..R15D, just 5 register outputs. (GCC still picks R8D before that, though.)
  • And BTW, you used `[h]` twice. GCC, and mainline clang, [both error on that](https://godbolt.org/z/YvW69zzPe); I assume Apple Clang does to, and that was a copy-paste glitch. Also, for ease of Godbolt use, I often do `nop # comment` so the line isn't filtered out. (GCC does a pure text substitution so it doesn't even have to be valid asm, unlike clang's built-in assembler)
  • @PeterCordes: Re “I assume Apple Clang does [too]”: It does not!

以上是在GCC内联汇编中影响内存操作数寻址模式的早期破坏者的不正确行为的具体示例?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>