在堆栈中转储寄存器以进行保守的堆栈扫描

The*_*kis 7 c stack garbage-collection cpu-registers compiler-optimization

我在C中写了一个非侵入性的保守GC,我对它的堆栈扫描阶段的正确性有些担忧.

具体来说,没有启用编译器优化,它工作正常,因为每个局部变量(指向一个对象)在堆栈上"可预测地"分配.在-O3,GC错过了一些有效的引用,我认为这是因为编译器选择使用寄存器(而不是GC扫描的堆栈)来处理某些变量和函数参数传递而GC不是(但是编程处理.(如果您怀疑这仍然不应该发生,并且我误解了问题的根源,请告诉我.)

除了一些基本要求(必须使用GC_malloc而不是mallocGC对象,不指向来自非GC堆的GC堆,当然,没有明确调用free),GC不应该有来自客户端的任何更多要求代码或编译器.因此,不需要客户端代码中的任何其他特殊模式.类似地,强制使用此GC编译的程序使用特殊的编译器标志(抑制堆栈优化)应该是最后的选择.有了这两个,这就是我的问题的构建.

我正试图找到一种方法使GC -O3无缝地处理案例(使用堆栈优化).这是我的思路(假设,更准确地说):

  1. 无论GCC(或任何有效的编译器)与优化有多远,它都不会到目前为止会损害程序的正确性.
  2. 如果[1]成立,我相信(至少在C的实际实现中)如果可达到的(在任何调用级别分配为本地)变量将始终可访问,则它必须在堆栈上或在当前的寄存器设置,但不是其他任何地方.
  3. 如果[2]成立,则应该意味着在GC循环开始时简单地将所有寄存器转储到堆栈将保证后续堆栈扫描现在也可以找到先前错过的引用.
  4. 另外,如果[1]和[2]成立,那么如果编译器允许覆盖任何变量(主要是基于寄存器的变量),则意味着它已经执行了某种程度的代码分析并且证明了该变量未在该函数中的任何其他位置使用.所以,从这个意义上说,如果我们在堆栈或寄存器转储中没有看到变量,即使它仍然在范围内(理论上),这也不应该引起警报(对我来说),因为编译器已经通过摆脱死亡参考,简单地帮助了GC.

问题1:是否所有我的假设4是否正确?

问题2:强制注册转储的最便携方式是什么?我发现一些消息来源声称一个简单的setjmp电话会产生这种效果.它是否正确?

Mat*_*son 1

Q1. 是的,我相信你的所有四个陈述都是正确的(至少如果我们忽略编译器错误的话!)

Q2。setjmp 将保存一些寄存器,但不一定是所有寄存器。但是,它应该足以满足您的目的,因为任何未由 setjmp 保存的寄存器都应该保存在堆栈上。

我猜如果有人在某个缓冲区中存储看起来像地址的东西,即使事实并非如此,你的方案可能会出错。

您还必须记住,有时人们会用指针做“有趣”的事情。例如

struct blah
{
    size_t size;
    char *file;
    int line;
};


struct blah *p = malloc(sizeof(struct blah) + size); 
... more lines of code goes here to fll in size, file and line in blah. 
void *np = (p+1); 
Run Code Online (Sandbox Code Playgroud)

这意味着您存储的指针根本不指向块的开头。