这种做法好吗?

时间:2012-01-11 11:54:44

标签: c linux gcc assembly x86-64

对于某些功能,我需要切换堆栈,以便原始堆栈保持不变。为此,我写了两个宏,如下所示。

#define SAVE_STACK()    __asm__ __volatile__ ( "mov %%rsp, %0; mov %1, %%rsp" : \
"=m" (saved_sp) : "m" (temp_sp) );
#define RESTORE_STACK() __asm__ __volatile__ ( "mov %0, %%rsp" : \
"=m" (saved_sp) );

此处 temp_sp saved_sp 是线程局部变量。 temp_sp 指向我们使用的临时堆栈。对于我希望不修改其原始堆栈的函数,我在开头放置SAVE_STACK,在底部放置RESTORE_STACK。例如,像这样。

int some_func(int param1, int param2)
{
 int a, b, r;
 SAVE_STACK();
 // Function Body here
 .....................
 RESTORE_STACK();
 return r;
}

现在我的问题是这种方法是否合适。在x86(64位)上,通过 rbp 寄存器访问局部变量和参数,并在函数序言中减去 rsp ,直到函数结尾添加它才被触及把它恢复到原来的价值。因此,我认为这里没有问题。

我不确定,如果在上下文切换和信号存在的情况下这是正确的。如果函数内联或尾调用优化(使用 jmp 而不是调用),我也不确定这是否正确。你看到这种方法有什么问题或副作用吗?

1 个答案:

答案 0 :(得分:4)

使用上面显示的代码,我可以想到以下破坏:

  1. 在x86 / x64上,如果认为合适的话,GCC将使用prologues / epilogues“装饰”你的函数,并且你无法阻止它这样做(就像在ARM上__attribute__((__naked__))迫使代码没有序言/结语的创作,也就是没有堆栈设置) 这可能最终会在切换堆栈之前将堆栈/创建引用分配给堆栈内存位置。更糟糕的是,再次,由于编译器的选择,在切换堆栈之前将这样的地址放入非易失性寄存器中,它可能会混淆到两个位置(堆栈指针 - 相对的一个你改变而另一个 - 相对的一个)这是一样的)。

  2. 同样,在x86 / x64上,ABI建议对叶子函数进行优化(“红色区域”),其中没有分配堆栈帧,函数可以使用128个“低于”末尾的堆栈字节。除非您的内存缓冲区考虑到这一点,否则可能会出现您不期望的超支。

  3. 信号在备用堆栈上处理(请参阅sigaltstack())并进行自己的堆栈切换可能会使您的代码在信号处理程序中无法调用。它肯定会使它不可重入,并且取决于你在何处/如何检索“堆栈位置”也肯定会使它成为非线程安全的。

  4. 通常,如果要在不同的堆栈上运行特定的代码,为什么不能:

    • 在另一个线程中运行它(每个线程获得不同的堆栈)?
    • 触发器,例如SIGUSR1并在信号处理程序中运行您的代码(您可以配置为使用不同的堆栈)?
    • 通过makecontext() / swapcontext()运行它(请参阅联机帮助页中的示例)?

    修改

    由于你说“你想要比较两个进程的内存”,再次,有不同的方法,特别是外部进程跟踪 - 附加一个“调试器”(可以是一个进程)你写自己使用ptrace()来控制你想要监控的东西,并让它代表你追踪的那些人处理例如断点/检查点,以执行你需要的验证。这也更灵活,因为它不需要更改您检查的代码。