Re: Signal delivery order

From: Gábor Melis
Date: Sun Mar 15 2009 - 18:07:22 EST


On Domingo 15 Marzo 2009, Oleg Nesterov wrote:
> On 03/15, Gábor Melis wrote:
> > On Domingo 15 Marzo 2009, Oleg Nesterov wrote:
> > > If test_signal (SIGUSR1) is blocked, this means it is already
> > > delivered, and the handler will be invoked when we return from
> > > sigsegv_handler(), please see below.
> >
> > SIGUSR1 is delivered, its sigmask is added to the current mask but
> > the handler is not yet invoked and in this instant synchronous
> > sigsegv is delivered, its handler invoked?
>
> Can't understand the question. Could you reiterate?

No need, my question was answered below.

> > > When sigprocmask(SIG_UNBLOCK) returns, both signals are
> > > delivered. The kernel deques 1 first, then 2. This means that the
> > > handler for "2" will be called first.
> >
> > My mental model that matches what I quickly glean from the sources
> > (from kernel/signal.c, arch/x86/kernel/signal_32.c) goes like this:
> >
> > - signal 1 and signal 2 are generated and made pending
> > - they are unblocked by sigprocmask
> > - signal 1 is delivered: signals in its mask (only itself here) are
> > blocked
>
> yes.
>
> the kernel changes ip (instruction pointer) to sig_1.
>
> > its handler is invoked
>
> no.
>
> We never return to user-space with a pending signal. We dequeue
> signal 2 too, and change ip to sig_2.
>
> Now, since there are no more pending signals, we return to the user
> space, and start sig_2().

I see. I guess in addition to changing the ip, the stack frobbing magic
arranges that sig_2 returns to sig_1 or some code that calls sig_1.

From the point of view of sig_2 it seems that sig_1 is already invoked
because it has its sigmask in effect and the ip in the ucontext of
sig_2 points to sig_1 as the attached signal-test.c shows:

sig_1=80485a7
sig_2=80485ed
2 1
eip: 80485a7
1 0
eip: b7fab424

The revised signal-delivery-order.c (also attached) outputs:

test_handler=8048727
sigsegv_handler=804872c
eip: 8048727
esp: b7d94cb8

which shows that sigsegv_handler also has incorrect eip in the context.
#include <signal.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/ucontext.h>
#include <unistd.h>

int test_signal;
int *page_address;
pthread_t tid;

int is_signal_blocked(int signum)
{
sigset_t set;
pthread_sigmask(SIG_BLOCK, 0, &set);
return (sigismember(&set, signum));
}

void *eip(struct ucontext *context)
{
return (void *)context->uc_mcontext.gregs[14];
}

void test_handler(int signal, siginfo_t *info, void *context)
{
}

void sigsegv_handler(int signal, siginfo_t *info, void *context)
{
/* The test signal is blocked only in test_handler. */
if (is_signal_blocked(test_signal)) {
printf("eip: %x\n", eip(context));
_exit(27);
}
mprotect(page_address, 4096, PROT_READ | PROT_WRITE);
}

void *make_faults(void *arg)
{
while (1) {
mprotect(page_address, 4096, PROT_NONE);
*page_address = 1;
}
}

void *reserve_page(void)
{
int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE;
void *actual = mmap(0, 4096, PROT_NONE, flags, -1, 0);
if (actual == MAP_FAILED) {
perror("mmap");
return 0;
}
return actual;
}

void install_handlers(void)
{
struct sigaction sa;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = test_handler;
sigaction(test_signal, &sa, 0);
sa.sa_sigaction = sigsegv_handler;
sigaction(SIGSEGV, &sa, 0);
}

void test_with_pthread_kill()
{
page_address = (int *)reserve_page();
install_handlers();
if (pthread_create(&tid, 0, make_faults, 0) < 0)
perror("pthread_create");
while(1) {
pthread_kill(tid, test_signal);
}
}

void test_with_kill()
{
pid_t pid = fork();
if (pid == 0) {
page_address = (int *)reserve_page();
install_handlers();
make_faults(0);
} else {
while (1) {
kill(pid, test_signal);
}
}
}

int main(void)
{
test_signal = SIGUSR1;
printf("test_handler=%x\n", test_handler);
printf("sigsegv_handler=%x\n", sigsegv_handler);
test_with_pthread_kill();
/* Forking and kill()ing works as expected: */
/* test_with_kill(); */
}
#include <syscall.h>
#include <signal.h>
#include <stdio.h>
#include <ucontext.h>

int is_blocked(int sig)
{
sigset_t set;
sigprocmask(SIG_BLOCK, NULL, &set);
return sigismember(&set, sig);
}

void *eip(struct ucontext *context)
{
return (void *)context->uc_mcontext.gregs[14];
}

void sig_1(int sig, siginfo_t *info, struct ucontext *context)
{
printf("%d %d\n", sig, is_blocked(2));
printf("eip: %x\n", eip(context));
}

void sig_2(int sig, siginfo_t *info, struct ucontext *context)
{
printf("%d %d\n", sig, is_blocked(1));
printf("eip: %x\n", eip(context));
}

int tkill(int tid, int sig)
{
syscall(SYS_tkill, tid, sig);
}

int main(void)
{
sigset_t set;

struct sigaction sa;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = sig_1;
sigaction(1, &sa, 0);
sa.sa_sigaction = sig_2;
sigaction(2, &sa, 0);

sigemptyset(&set);
sigaddset(&set, 1);
sigaddset(&set, 2);
printf("sig_1=%x\n", sig_1);
printf("sig_2=%x\n", sig_2);
sigprocmask(SIG_BLOCK, &set, NULL);

kill(getpid(), 1);
kill(getpid(), 2);

sigprocmask(SIG_UNBLOCK, &set, NULL);

return 0;
}