将函数标记为脏(指定调用方应保存所有寄存器)

marking a function dirty (specifying that caller should save all registers)

本文关键字:保存 方应 寄存器 调用 函数 记为      更新时间:2023-10-16

我有一个函数f(),它会破坏函数调用中的所有寄存器,堆栈指针除外。如何将此信息传达给gcc,以便调用方在调用f()之前保存它想要保存的所有寄存器?

编辑:我正在编写一个协程处理程序,它需要在切换到另一个协程前保存状态,我希望保存尽可能少的状态。假设我们有一个函数yield(),标记为"脏",它会破坏除堆栈指针之外的所有内容。这个函数将在控制返回到我们的协程后神奇地返回,但最好是调用方而不是被调用方(因为调用方最清楚需要保存哪些寄存器(在调用yield()之前保存所有需要保存的寄存器,然后再恢复它们。

这个想法违反了最小惊奇原则,因此应该避免。

作为调用方,我不希望在调用函数时担心注册内容。此外,如果您希望调用者处理它,为什么不在f((内部处理它呢?调用方不仅不需要知道,而且只需要一个实现。根据您的建议,每个调用方都需要处理保存/恢复上下文,这可能非常容易出错。

基本解决方案可以使用RAII在f((中处理此问题。类似于:

class ContextGuard {
  public:
    // Stores the current context
    ContextGuard() { ... }
    // Restores the current context
    ~ContextGuard() { ... }
  private:
     ...
};
...
void f() {
   ContextGuard contextGuard; // Saves the current context
   ... // Do stuff that mucks with the registers
} <-- contextGuard is destroyed here and the context is restored.

如何处理存储/恢复上下文是另一回事。Boost.Context可以为你提供你需要的东西。

最后一点,除了堆栈指针之外,还有一些关于寄存器的其他复杂性。在这里,您需要考虑特定于平台的函数调用约定,可能还有其他因素。如果你以这样一种方式处理寄存器,以至于你认为它们需要恢复,那么你必须真正理解你打算在其上运行此代码的所有平台的复杂性。

您还可以使用C函数setjmp和longjump。

setjmp就是这么做的。它将上下文存储到jmp_buf变量中。

之后,您可以直接调用longjump来恢复上下文。

示例实现类似于:

void f()
{
    jmp_buf my_jump_buffer;
    if (setjmp(my_jump_buffer) == 0) {
        // bla bla destroying any register you want (except sp)
        // if you need to change sp, you have to make the buffer a global var
        longjmp(my_jump_buffer, 1); // Will restore the context
    }
}

最后,我用了这个:

#if defined(__GNUC__)
# if defined(i386) || defined(__i386) || defined(__i386__)
#  define clobber_all() asm volatile ("":::"eax", "ebx", "ecx", "edx", "esi", "edi", "ebp", "cc");
# elif defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64)
#  define clobber_all() asm volatile ("":::"rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", "cc");
# elif defined(__arm__)
#  define clobber_all() asm volatile ("":::"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "lr", "cc");
# elif defined(__aarch64__)
#  define clobber_all() asm volatile ("":::"x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", "x29", "x30", "cc");
# else
#  error "unsupported architecture"
# endif
#else
# error "unsupported compiler"
#endif

阻塞sp显示一个警告,这就是为什么我没有包括sp。您可以将clobber_all()包含在您的函数序言中:

void f()
{
  clobber_all();
  // blah, blah, ...
}

它将通知编译器(几乎(所有通用寄存器的内容都已被破坏。