ret后说明,热/冷分裂的副作用和异常?

Instructions after ret, side effect of hot/cold splitting and exceptions?

提问人:Joseph Garvin 提问时间:4/30/2023 更新时间:4/30/2023 访问量:79

问:

我想检查 GCC 是否确保在抛出异常时为运行析构函数生成的额外代码放在二进制文件的冷部分,以使这些指令远离“快乐路径”并避免导致指令缓存压力。所以我在下面做了一个例子(Godbolt 链接),其中 GCC 无法判断是否会抛出异常。果然,我在组件中看到了一个单独的标签。但是在它上面有一个奇怪的神器:buzz(int) [clone .cold.0]:

.L9:
        mov     edi, OFFSET FLAT:.LC0
        xor     eax, eax
        call    printf
        mov     eax, ebx
        add     rsp, 16
        imul    eax, ebx
        add     eax, ebx
        pop     rbx
        ret
        mov     rbx, rax
        jmp     .L3
buzz(int) [clone .cold.0]:
.L3:
        mov     eax, DWORD PTR [rsp+12]
        ...

请注意,指令后面跟着 a 进入寒冷区域。但是之后的指令不是跳转目标(无标签)永远不应该运行。我想这可能是出于指令对齐的目的,但随后跳入冷部分似乎太奇怪了。这是怎么回事?为什么这里有这些说明?也许与解开工作原理的协议有关,它需要在那里,而异常展开表使它成为跳转目标,但它只是未标记?但如果是这样的话,为什么不直接跳到冷部分呢?retjmpret

C++ 代码:

#include <stdio.h>

extern int global;

struct Foo
{
    Foo(int x)
    : x(x)
    {}

    ~Foo() { 
        if(x % global == 0) {
            printf("Wow."); 
        }
    }
    int x;
};

void bar(Foo& f); // compiler can't see impl, has to assume could throw

// Type your code here, or load an example.
int buzz(int num) {
    {
       Foo foo(3);
       bar(foo);
    }
    return num * num + num;
}
C++ 异常 程序集 堆栈展开

评论

7赞 Jester 4/30/2023
Godbolt 过滤掉了它的标签,认为它未使用,但实际上它在 中被引用了(由于指令过滤,它也被隐藏了)。该代码是异常处理的一部分。.L5.gcc_except_table
1赞 Peter Cordes 4/30/2023
对于对齐,GCC 始终只使用 ,有时使用第 3 个操作数来指定填充限制。GAS 将其扩展到长 NOP 指令(至少在代码节内,如果将填充模式保留为默认值,则数据节中的零)。GCC 甚至不跟踪大小,因此它不知道它相对于对齐边界的位置。.p2align
2赞 Peter Cordes 4/30/2023
所以一般来说,你绝对可以排除像对齐这样的猜测。这要么是一个奇怪的遗漏优化,要么像这种情况一样,某些东西确实以某种方式引用了它。但它必须通过标签,因为 GCC 无法计算来自另一个标签的偏移量;它不跟踪指令长度。因此,请记住要怀疑 Godbolt 对“未使用”标签的过滤。mov rbx, rax
0赞 Joseph Garvin 4/30/2023
@PeterCordes两个优点,谢谢
0赞 Joseph Garvin 4/30/2023
@Jester啊,这更有意义。有没有不能直接指向的原因?然后可以省略 / 之后。.gcc_except_table.L3movjmpret

答: 暂无答案