在 C 中更改程序计数器

Change Program Counter in C

我想更改程序计数器(指令指针)的内容。我想通过覆盖 C 中的系统信号处理程序,我可以获得系统堆栈帧中的指针。从那里我可以获得函数的 return 地址并更改它。

但是,我得到了栈中的指针,但是我不知道 return 地址在栈帧上的具体存储位置。

void signal_handler(int signal){

   char* ptr = (char*) & signal;
   // As, signal is stored on the paramter list of the stack, 
   // I get the address in the current stack frame. From , here
   // I want to change the return address(that is stored in the 
   // current stack frame).
}

如果您只是想跳转到一个地址,而不需要为您要跳转到的上下文设置堆栈,或者从您要离开的上下文中清除它,您可以使用 GNU C computed-goto

void *ptr;
/* ... */
ptr = &&foo;

// then somewhere else

goto *ptr;

据我了解,这几乎等同于

asm volatile("jmp *ptr");  // AT&T syntax for memory-indirect jump

尝试从段错误中恢复的正确方法是 to use setjmp / longjmp强烈不建议在那个时候尝试做太多事情。使用此功能打印自定义消息、记录内容并退出。如果您的代码导致了段错误,它很可能在尝试访问一些不允许访问的内存之前已经涂掉了您程序的一些数据。这甚至可能破坏了堆栈上的 return 地址。

即使 asm 指令映射 1:1 到代码行(它们没有),如果跳过一行代码,大多数程序都会严重中断。更不用说如果您尝试恢复执行的数据或代码出现问题,您只会出现段错误。

发生段错误后,程序处于不确定状态。您不能再依赖任何具有正确值的东西(例如寄存器)。

即使您 可以 return 到故障后的下一条指令,除非您的信号处理程序 [在上下文中] 反汇编有问题的指令,并将寄存器值更改为补偿,你现在有一个不值得信任的程序,它可能会继续出现段错误或[更糟]操作并产生更灾难性的结果(例如错误文件上的unlink,等)。

但是,由于信号"trampoline",你不能这样做。有关详细信息,请参阅 sigreturn 手册页。给你的信号处理程序的堆栈帧甚至不一定是正常的。

您必须将程序恢复到已知的 "safe" 状态。做到这一点的唯一方法是 [正如我在上面的评论中提到的] 是使用 sigsetjmp 设置恢复点并在信号处理程序中执行 siglongjmp旁注: 使用这些类似于在 C++ 中使用异常,但您必须手动完成更多工作。

我已经做了很多段错误捕获信号处理程序和恢复,但它们都涉及使用 sigsetjmp/siglongjmp

这也引出了一个问题:为什么不直接调试您的程序,使其一开始就不会出现段错误?您有什么特殊需求无法做到这一点?