通过 setcontext 从信号处理程序返回

PSk*_*cik 7 c linux assembly signals x86-64

我试图使用 a 的第三个参数SA_SIGINFO sigaction直接跳转到中断的上下文。

想到了这个:

void action(int Sig, siginfo_t *Info, void *Uctx) { 
    ucontext_t *uc = Uctx; setcontext(uc); 
}
Run Code Online (Sandbox Code Playgroud)

将具有与以下相同的效果:

void action(int Sig, siginfo_t *Info, void *Uctx) { 
    return; 
}
Run Code Online (Sandbox Code Playgroud)

但奇怪的是它接受三个信号(调用 setcontext 调用处理程序),然后出现段错误setcontext

Dump of assembler code for function setcontext:
   0x00007ffff7a34180 <+0>:     push   %rdi
   0x00007ffff7a34181 <+1>:     lea    0x128(%rdi),%rsi
   0x00007ffff7a34188 <+8>:     xor    %edx,%edx
   0x00007ffff7a3418a <+10>:    mov    $0x2,%edi
   0x00007ffff7a3418f <+15>:    mov    $0x8,%r10d
   0x00007ffff7a34195 <+21>:    mov    $0xe,%eax
   0x00007ffff7a3419a <+26>:    syscall
   0x00007ffff7a3419c <+28>:    pop    %rdi
   0x00007ffff7a3419d <+29>:    cmp    $0xfffffffffffff001,%rax
   0x00007ffff7a341a3 <+35>:    jae    0x7ffff7a34200 <setcontext+128>
   0x00007ffff7a341a5 <+37>:    mov    0xe0(%rdi),%rcx
--Type <RET> for more, q to quit, c to continue without paging--
   0x00007ffff7a341ac <+44>:    fldenv (%rcx)
=> 0x00007ffff7a341ae <+46>:    ldmxcsr 0x1c0(%rdi)
   0x00007ffff7a341b5 <+53>:    mov    0xa0(%rdi),%rsp
   0x00007ffff7a341bc <+60>:    mov    0x80(%rdi),%rbx
Run Code Online (Sandbox Code Playgroud)

strace 显示的故障地址为 0(可捕获的 SIGSEGV)。

这是一个使用计时器发送三个信号的示例程序:

Dump of assembler code for function setcontext:
   0x00007ffff7a34180 <+0>:     push   %rdi
   0x00007ffff7a34181 <+1>:     lea    0x128(%rdi),%rsi
   0x00007ffff7a34188 <+8>:     xor    %edx,%edx
   0x00007ffff7a3418a <+10>:    mov    $0x2,%edi
   0x00007ffff7a3418f <+15>:    mov    $0x8,%r10d
   0x00007ffff7a34195 <+21>:    mov    $0xe,%eax
   0x00007ffff7a3419a <+26>:    syscall
   0x00007ffff7a3419c <+28>:    pop    %rdi
   0x00007ffff7a3419d <+29>:    cmp    $0xfffffffffffff001,%rax
   0x00007ffff7a341a3 <+35>:    jae    0x7ffff7a34200 <setcontext+128>
   0x00007ffff7a341a5 <+37>:    mov    0xe0(%rdi),%rcx
--Type <RET> for more, q to quit, c to continue without paging--
   0x00007ffff7a341ac <+44>:    fldenv (%rcx)
=> 0x00007ffff7a341ae <+46>:    ldmxcsr 0x1c0(%rdi)
   0x00007ffff7a341b5 <+53>:    mov    0xa0(%rdi),%rsp
   0x00007ffff7a341bc <+60>:    mov    0x80(%rdi),%rbx
Run Code Online (Sandbox Code Playgroud)

这种情况是怎么回事?

Nat*_*dge 5

我认为这根本不起作用:您只能使用从orsetcontext获取的上下文进行调用,而不是将上下文传递给信号处理程序。getcontextmakecontext

手册页间接地暗示了这一点:

如果上下文是通过调用信号处理程序获得的,那么旧的标准文本会说“程序继续执行被信号中断的指令之后的程序指令”。不过这句话在SUSv2中被删除了,目前的判决是“结果未明”。

另外,glibc源码setcontext有一条评论:

此实现仅用于同步上下文切换。因此,它不必恢复 PRESERVED 状态以外的任何内容。

事实上,它不会尝试恢复任何浮点寄存器,并且它会归零rax(就像getcontext返回 0 一样)。对于尝试恢复不希望其寄存器自发更改的代码来说,这将是非常糟糕的。

用户空间中的抢占式多任务处理等功能需要异步上下文切换。我认为这个想法是,既然pthreads现在已经牢固地建立起来,人们应该不需要它,所以它不被支持。 getcontext/setcontext是从更早的时代开始的,事实上已经从 POSIX 规范中删除了,前提是应该使用 pthreads。


这种特殊的崩溃似乎是由于 的内核布局struct ucontext_t与 libc 的预期不匹配造成的。特别是,libc 期望浮点状态,包括 的保存值mxcsr,位于 内的特定偏移量处struct ucontext_t。然而,内核将浮点状态推送到堆栈上的单独位置(恰好与 libc 期望的位置重叠),并在struct ucontext_t. 因此 libcsetcontext尝试将一些垃圾值加载到 中mxcsr,其中设置了一些保留位 16-31,这会导致一般保护错误。

然而,如上所述,这种不匹配是最不重要的问题。