【问题标题】:Returning from a signal handler via setcontext通过 setcontext 从信号处理程序返回
【发布时间】:2021-11-13 04:29:09
【问题描述】:

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

这样想:

void action(int Sig, siginfo_t *Info, void *Uctx) { 
    ucontext_t *uc = Uctx; setcontext(uc); 
}

和刚才的效果一样:

void action(int Sig, siginfo_t *Info, void *Uctx) { 
    return; 
}

但奇怪的是它接受三个信号(调用 setcontext-calling 处理程序),然后它会出现段错误 在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

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

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

#include <unistd.h>
#include <sys/time.h>
#include <ucontext.h>
#include <signal.h>
  
void action(int Sig, siginfo_t *Info, void *Uctx) { 
    ucontext_t *uc = Uctx; setcontext(uc); 
}
int main(void) {
    char ch[100];
    sigaction(SIGALRM, &(struct sigaction){.sa_sigaction = action, .sa_flags = SA_SIGINFO}, 0);
    setitimer(ITIMER_REAL, &(struct itimerval){.it_interval.tv_sec = 1,.it_value.tv_sec = 1}, 0);
    write(1, "enter\n", 6);
    for (;;) {
        write(1, "{\n", 2);
        read(0, &ch[0], sizeof(ch));
        write(1, "}\n", 2);
    }
}

在这种情况下发生了什么?

【问题讨论】:

  • FWIW,我从像这样的信号处理程序返回的唯一成功是sigsetjmp()siglongjmp()这可能是相关的:stackoverflow.com/questions/20755260/…
  • 有趣。我可以在 Linux amd64 上重现这一点,但不能使用 -m32 或 arm64。段错误在setcontext 中,当它尝试ldmxcsr 时使用可能无效的值。
  • 如果我在循环中将循环体替换为stmxcsr,则该值与处理程序在uc-&gt;__fpregs_mem.mxcsr 中看到的不匹配,并在恢复上下文时更改。 *uc 结构中的 mxcsr 值似乎不正确,有时无效(设置了一些 16-31 位)。目前不确定它是否应该是正确的。
  • 有点奇怪的是uc-&gt;uc_mcontext.fpregs 似乎指向正确填充的_libc_fpstate,包括mxcsr 的正确值。但是setcontext 不是从那里加载mxcsr,而是从uc-&gt;__fpregs_mem.mxcsr 加载。 .fpregs 指向 within .__fpregs_mem,24 个字节。这看起来很奇怪。这不是getcontext 的设置方式,我不认为。
  • 是的,看起来 libc 认为 fpregs 包含在 struct ucontext_t 本身中,而内核将它们作为单独的结构放在堆栈上,并在 uc-&gt;uc_mcontext.fpregs 中带有指向它的指针。目前还不清楚这种不匹配是否是一个错误,或者它们是否永远不会相同。

标签: c linux assembly signals x86-64


【解决方案1】:

我认为这根本不适合工作:您只应该使用从 getcontextmakecontext 获得的上下文来调用 setcontext,而不是使用传递给信号处理程序的上下文。

The man page 暗示这个:

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

另外,the glibc source of setcontext 有评论:

此实现旨在用于同步上下文 只开关。因此,它不必恢复任何东西 除了 PRESERVED 状态。

确实,它不会尝试恢复任何浮点寄存器,它会将rax 归零(getcontext 返回 0)。这对于尝试恢复不期望其寄存器自发更改的代码是非常糟糕的。

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


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

但是,如上所述,这种不匹配是最少的问题。

【讨论】:

    猜你喜欢
    • 2012-01-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-24
    • 1970-01-01
    • 2012-06-04
    相关资源
    最近更新 更多