通过 setcontext 从信号处理程序返回
Returning from a signal handler via setcontext
我正在尝试使用 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 调用处理程序),然后出现段错误
在 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 [=12=]x2,%edi
0x00007ffff7a3418f <+15>: mov [=12=]x8,%r10d
0x00007ffff7a34195 <+21>: mov [=12=]xe,%eax
0x00007ffff7a3419a <+26>: syscall
0x00007ffff7a3419c <+28>: pop %rdi
0x00007ffff7a3419d <+29>: cmp [=12=]xfffffffffffff001,%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);
}
}
这种情况是怎么回事?
我认为这根本就不起作用:您只能使用从 getcontext
或 makecontext
获得的上下文调用 setcontext
,而不是使用上下文传递给信号处理程序。
The man page 暗暗暗示:
If the context was obtained by a call to a signal handler, then old standard text says that "program execution continues with the program instruction following the instruction interrupted by the signal". However, this sentence was removed in SUSv2, and the present verdict is "the result is unspecified".
另外,the glibc source of setcontext
有评论:
This implementation is intended to be used for synchronous context
switches only. Therefore, it does not have to restore anything
other than the PRESERVED state.
实际上,它不会尝试恢复任何浮点寄存器,并且会将 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,这会导致一般保护错误。
但是,如上所述,这种不匹配是最小的问题。
我正在尝试使用 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 调用处理程序),然后出现段错误
在 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 [=12=]x2,%edi
0x00007ffff7a3418f <+15>: mov [=12=]x8,%r10d
0x00007ffff7a34195 <+21>: mov [=12=]xe,%eax
0x00007ffff7a3419a <+26>: syscall
0x00007ffff7a3419c <+28>: pop %rdi
0x00007ffff7a3419d <+29>: cmp [=12=]xfffffffffffff001,%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);
}
}
这种情况是怎么回事?
我认为这根本就不起作用:您只能使用从 getcontext
或 makecontext
获得的上下文调用 setcontext
,而不是使用上下文传递给信号处理程序。
The man page 暗暗暗示:
If the context was obtained by a call to a signal handler, then old standard text says that "program execution continues with the program instruction following the instruction interrupted by the signal". However, this sentence was removed in SUSv2, and the present verdict is "the result is unspecified".
另外,the glibc source of setcontext
有评论:
This implementation is intended to be used for synchronous context switches only. Therefore, it does not have to restore anything other than the PRESERVED state.
实际上,它不会尝试恢复任何浮点寄存器,并且会将 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,这会导致一般保护错误。
但是,如上所述,这种不匹配是最小的问题。