GCC - 修改函数返回后继续执行的位置

时间:2015-11-22 11:02:06

标签: c gcc return memory-address gcc-extensions

是否有可能在GCC中做这样的事情?

void foo() {
    if (something()) returnSomewhereElse;
    else return;
}

void bar() {
    foo();
    return; // something failed, no point of continuing
    somewhereElse:
    // execution resumes here if something succeeds
    // ...
}
  • 这可以通过C和GCC扩展以便携方式实现,而无需使用特定于平台的程序集吗?
  • 堆栈状态在正常和更改的返回点之间不会改变,那么是否可以重用恢复堆栈的代码并从常规返回中注册状态?
  • 考虑到函数可能会或可能不会内联,如果调用它必须改变返回地址,如果内联它只能改变代码路径而不是当前函数返回地址,因为这会破坏代码
  • 备用返回点不需要是标签,但我希望GCC的标签扩展地址可以在这种情况下派上用场

只是澄清意图 - 它是关于错误处理。这个例子是一个最小的例子,只是为了说明事情。我打算在更深层次的上下文中使用它,以便在发生错误时停止执行。我还假设状态没有改变,我可能错了,因为在两个返回点之间没有添加额外的局部变量,所以我希望编译器生成的代码在foo的返回时可以重用为此节省了使用longjmp,设置和传递跳转缓冲区的开销。

示例“确实有意义”,因为它的目的是展示我想要实现的目标,而不是为什么以及如何在实际代码中有意义。

  

为什么你的想法更简单,只需返回一个值   foo()并让bar()返回或执行somewhereElse:   有条件?

这并不简单,你所建议的不适用于实践,只是在一个简单的例子的背景下,但它更好,因为:

1 - 它不涉及额外返回值

2 - 它不涉及额外检查值

3 - 它不涉及额外的跳跃

我可能错误地认为目标应该在这一点上清楚,并且在所有澄清和解释之后。我们的想法是从深度调用链中提供“转义代码路径”,而无需任何额外开销。通过重用编译器生成的代码来恢复前一个调用帧的状态,并简单地修改函数返回后执行恢复的指令。成功跳过“转义码路径”,发生的第一个错误进入它。

if (failure) return; // right into the escape code path
else {
    doMagickHere(); // to skip the escape code path
    return; // skip over the escape code path
}

//...
void bar() {
    some locals;
    foo();
    // enter escape code path here on foo failure so
    destroy(&locals); // cleanup
    return; // and we are done
    skipEscapeCodePath: // resume on foo success
    // escape path was skipped so locals are still valid
}

至于Basile Starynkevitch提出的longjmp是“有效”并且“甚至十亿longjmp仍然合理”的说法 - sizeof(jmp_buf)给了我156个字节,这显然是需要的空间保存几乎所有寄存器和一堆其他东西,以便以后可以恢复。这些都是很多操作,而这十亿次远远超出了我对“有效”和“合理”的个人理解。我的意思是,十亿个跳转缓冲区本身超过145 GIGABYTES 的内存,然后还有CPU时间开销。并不是很多系统甚至可以提供那种“合理”的系统。

1 个答案:

答案 0 :(得分:-9)

在给予一些额外的考虑之后,它并不像最初出现的那么简单。有一件事阻止它工作 - 函数代码不是上下文感知的 - 没有办法知道它被调用的框架,这有两个含义:

1 - 如果不可移植就修改指令指针很容易,因为每个实现都为它定义了一致的位置,它通常是堆栈中的第一件事,但是修改它的值以跳过转义陷阱也会跳过代码恢复前一帧状态,因为那个代码在那里,而不是在当前帧中 - 它不能执行状态恢复,因为它没有它的信息,如果要进行额外的检查和跳转则应采取补救措施省略的是复制两个位置的状态恢复代码,遗憾的是,这只能在程序集

中完成

2 - 需要跳过的指令数量也是未知的,并且取决于前一个堆栈帧的数量,取决于需要销毁的本地数量,它将变化,它不是一个统一的值,解决这个问题的方法是在调用函数时将错误和成功指令指针都推送到堆栈上,这样它就可以根据是否发生错误来恢复其中一个指针。不幸的是,这也只能在装配中完成。

似乎这样的方案只能在级别编译器上实现,要求它自己的调用约定,它会推送两个返回位置并在两者处插入状态恢复代码。而这种方法的潜在节省几乎不值得编写编译器。

相关问题