Linux信号处理。如何获取被中断指令的地址?

5
有没有办法找出被某些信号中断的机器指令的地址?假设我们在由sigaction()建立的处理程序中,并且可以访问传递的siginfo_tucontext_t
就我所看,手册上没有提到这一点。
2个回答

6
让我们看一个关于Linux和x86架构的示例。
#include<stdio.h>
#define __USE_GNU
#include<signal.h>
#include<ucontext.h>

void myhandle(int mysignal, siginfo_t *si, void* arg)
{    
  ucontext_t *context = (ucontext_t *)arg;
  printf("Address from where crash happen is %x \n",context->uc_mcontext.gregs[REG_RIP]);
  context->uc_mcontext.gregs[REG_RIP] = context->uc_mcontext.gregs[REG_RIP] + 0x04 ;

}

int main(int argc, char *argv[])
{
  struct sigaction action;
  action.sa_sigaction = &myhandle;
  action.sa_flags = SA_SIGINFO;
  sigaction(11,&action,NULL);

  printf("Before segfault\n");

  int *a=NULL;
  int b;
  b =*a; // Here crash will hapen

  printf("I am still alive\n");

  return 0;
}

现在编译并运行,查看反编译指令集。
jeegar@jeegar:~/stackoverflow$ gcc -g test1.c  -o test1.o
jeegar@jeegar:~/stackoverflow$ ./test1.o 
Before segfault
Signal is 11
Address from where crash happen is 40065b 
I am still alive
jeegar@jeegar:~/stackoverflow$ objdump -S test1.o 

在这里进行对象转储
  printf("Before segfault\n");
  400645:   bf a8 07 40 00          mov    $0x4007a8,%edi
  40064a:   e8 21 fe ff ff          callq  400470 <puts@plt>

  int *a=NULL;
  40064f:   48 c7 45 f0 00 00 00    movq   $0x0,-0x10(%rbp)
  400656:   00 
  int b;
  b =*a; // Here crash will hapen
  400657:   48 8b 45 f0             mov    -0x10(%rbp),%rax
  40065b:   8b 00                   mov    (%rax),%eax
  40065d:   89 45 fc                mov    %eax,-0x4(%rbp)

  printf("I am still alive\n");
  400660:   bf b8 07 40 00          mov    $0x4007b8,%edi
  400665:   e8 06 fe ff ff          callq  400470 <puts@plt>

在地址40065b处,存在机器码并且你的代码的哪一行执行了它。


这里我给出了一个示例代码,其中发生了分段错误,并且在系统的Seg fault信号中调用了一个处理程序,在其中获取了最后一个执行的机器周期的地址并打印该地址。为了验证该地址,我还展示了该代码的对象转储,并且分段故障行的机器指令匹配。

我认为这就是你想要的。


2
在信号处理程序中,标记不安全的函数。在 C 中,您不需要强制转换将 void* 转换为任何其他数据指针。 - Maxim Egorushkin
3
虽然Maxim是正确的(不要在信号处理程序中使用printf),但我认为这对于本帖的目的(解释OP所问的内容)是无关紧要的。而将(void *)强制转换是一种风格问题。投票支持。 - Ctx
消除实现上的粗糙之处,这个答案讲述了与https://dev59.com/dpPfa4cB1Zd3GeqPCV9a#34990181相同的方法。请参见下面我的评论。 - Sergio
@Ctx 我不同意,不必要的代码会使您的应用程序变得更糟。让事情变得更大很容易,但让它们变得更好却很难。想象一下,你去看医生,他告诉你一些你知道是完全错误的事情。你可能会找另一个医生。 - Maxim Egorushkin
@EOF 是的,这也不是必需的。但作为一般性的,我不能确定这个崩溃发生的位置,为了避免进一步跳转到错误的地址并且不会错过任何指令,我只是将4作为示例保留下来... - Jeegar Patel
显示剩余6条评论

4

不具备可移植性。但这是针对x86_64的:

ucontext_t结构包含寄存器REG_RIP的值,该寄存器应该保存您要查找的值。这是从信号处理程序返回后将执行的第一条指令。

其他架构应该有类似的寄存器(在x86_32上为EIP等)。


是的,但不幸的是,不能保证处理程序后的第一条指令就是被中断的指令,因为处理程序可能通过几个包装器来调用,因此这个伪指令指向的可能是libc或其他东西内部的内容,而不是我们的代码。无论如何,感谢您的回答。 - Sergio
是的,更准确地说,我需要获取应用程序端指令的地址(而不是libc的指令),这些指令将在信号处理程序之后执行。例如,在导致SIGSEGV的指令之后立即执行的地址。 - Sergio
@Serhio 但这只是REG_RIP中的确切地址。我认为你在这里有些混淆了。 - Ctx
正如我们在Jeegar的回答中所看到的,在x64上,信号处理程序的帧会在触发信号的帧之后打开,因此当处理程序返回时,我们将回到自己的帧。但是不能保证在我们的信号发出者帧和信号处理程序帧之间不会有任何中间堆栈帧。至少在ARM Android上,我看到它们之间有三个中间帧,结果ucontext_t中的IP将我带到这些内部帧的最后一个,而不是信号发出者。或者,我们可以遍历uc_link列表以查看其他上下文。但我们没有迹象知道何时停止。 - Sergio
1
@Serhio 不,当处理程序返回时,寄存器会被重置为在信号发生时保存在内核中的ucontext_t结构中的状态(RIP、RSP、RBP等)。而且你似乎混淆了指令和堆栈指针。 - Ctx
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接