macOS `sigaction()` handler with `SA_SIGINFO` does

2019-06-21 12:53发布

问题:

I'm trying to write a signal handler which needs to know the pid of the process that sends the signal. I'm having no luck with getting anything useful from the siginfo_t passed into my handler on macOS 10.14 with Xcode 10.

I've reduced my code to the below minimal sample to demonstrate the issue. In this sample I spawn a child process to send the signal I want to test which is defaulted to SIGTERM, but no other signal I've tried works any better.

Assuming you want to build and test this on a mac, you probably want to tell lldb to not stop when receiving a signal. You can use this lldb command: pro hand -p true -s false SIGTERM.

I'm also compiling with C++, but I believe I have excised all of that and the sample code should be pure C now.

Note that it doesn't matter if the signal originates from a child, terminal, or another process the result is always that si_pid is always 0 (along with everything other than the si_signo and si_addr). It doesnt matter how many times I send the signal, so it seems to not be simply a race condition.

How can I get the pid of the process sending the signal on macOS 10.14? I don't recall having this issue on 10.12 which is what I was using before.

This is just a sample to demostrate the problem, so please ignore anything that isn't actually causing a problem.

If the code seems like it should work as I expect, then I would be interested in seeing comments about systems that it works on too.

#include <unistd.h>
#include <signal.h>
#include <stdio.h>

volatile sig_atomic_t histogram[3] = {0,0,0};
volatile sig_atomic_t signaled = 0;
const int testsig = SIGTERM;

void sigaction_handler(int sig, siginfo_t* info, void* context)
{
    switch (info->si_pid) {
        case 0:
        case 1:
            histogram[info->si_pid]++;
            break;

        default:
            histogram[2]++;
            break;
    }
    signaled = 1;
}

int main(int argc, const char * argv[]) {

    pid_t mainpid = getpid();
    pid_t pid = fork();
    if (pid == 0) {
        while (kill(mainpid, 0) == 0) {
            sleep(1);
            kill(mainpid, testsig);
        }
        _exit(0);
    }

    struct sigaction sigAction;
    memset( &sigAction, 0, sizeof( sigAction ) );

    sigAction.sa_sigaction = sigaction_handler;
    sigemptyset (&sigAction.sa_mask);
    sigAction.sa_flags = SA_SIGINFO;
    sigaction(testsig, &sigAction, NULL);

    while (1) {
        if (signaled) {
            printf("pid 0: %d, pid 1: %d, others: %d\n", histogram[0], histogram[1], histogram[2]);
            signaled = 0;
        }
        sleep(1);
    }
}

回答1:

I'm currently using macOS Mojave 10.14.1.

How can I get the pid of the process sending the signal on macOS 10.14? I don't recall having this issue on 10.12 which is what I was using before.

The following code meets your wish simply. If you send SIGTERM, you can see pid of sender process.

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

static void hdl (int sig, siginfo_t *siginfo, void *context)
{
    printf ("Sending PID: %ld, UID: %ld\n",
            (long)siginfo->si_pid, (long)siginfo->si_uid);
}

int main (int argc, char *argv[])
{
    struct sigaction act;

    fprintf(stderr, "%i pp %i\n",getpid(), getppid());

    memset (&act, '\0', sizeof(act));

    /* Use the sa_sigaction field because the handles has two additional parameters */
    act.sa_sigaction = &hdl;

    /* The SA_SIGINFO flag tells sigaction() to use the sa_sigaction field, not sa_handler. */
    act.sa_flags = SA_SIGINFO;

    if (sigaction(SIGTERM, &act, NULL) < 0) {
        perror ("sigaction");
        return 1;
    }

    while (1)
        sleep (10);

    return 0;
}

For your code,

Rule of thumb: Don't forget to carry burial procedures out even though you are sure that child process ends prior parent process. By invoking wait(...) you tell the operating system that I'm done my things for my child so now you can clean allocated fields etc.

I'd prefer initialize signal utilities prior forking what if the parent process doesn't have a chance to register signal action? Moreover, I don't understand why you handle 0 and 1 cases in switch. Intrinsically the cases aren't hit, so always omitted.

In addition, you didn't use break in your if condition within main(). It doesn't go in if after a while yet the following circumstance which is not anticipated and desirable is that the program stays forever in while() loop. I'd prefer to put signaled into condition of while() loop.

At last but not least, due to sleep() call in child process until signaled is turned out 0, SIGTERM is caught several times successfully. When signaled is 0, the loop stops.

#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <memory.h>
#include <sys/wait.h>

volatile sig_atomic_t histogram[3] = {0,0,0};
volatile sig_atomic_t signaled = 0;
const int testsig = SIGTERM;

void sigaction_handler(int sig, siginfo_t* info, void* context)
{
    switch (info->si_pid) {
    case 0:
    case 1:
        histogram[info->si_pid]++;
        break;

    default:
        fprintf(stderr, "sender pid -> %i\n", info->si_pid);
        histogram[2]++;
        break;
    }
    signaled = 1;
}

int main(int argc, const char * argv[]) {


    struct sigaction sigAction;
    memset( &sigAction, 0, sizeof( sigAction ) );

    sigAction.sa_sigaction = sigaction_handler;
    sigemptyset (&sigAction.sa_mask);
    sigAction.sa_flags = SA_SIGINFO;
    sigaction(testsig, &sigAction, NULL);

    pid_t mainpid = getpid();
    pid_t pid = fork();
    if (pid == 0) {
        fprintf(stderr, "my pid -> %i parent's pid-> %i\n", getpid(), getppid());
        if (kill(mainpid, 0) == 0) { // signals are not queued not need loop
            sleep(1);
            kill(mainpid, testsig);
        }
        _exit(0);
    } else {

        wait(NULL); // play with this line to see what the difference is
        while ( signaled ) {
            printf("pid 0: %d, pid 1: %d, others: %d\n", histogram[0], histogram[1], histogram[2]);
            signaled = 0;
            sleep(1);
        }
        // wait(NULL); // play with this line to see what the difference is

    }
}


回答2:

It turns out that debugging via Xcode LLDB is the culprit. If I build and run the program normally it works fine. If I find out why I will update this answer.

I already have the "PASS" set for SIGTERM in lldb as noted in the question, so it seems like somehow there is a bug in the version of lldb shipped with Xcode 10.0 and it is "passing" the signal by creating a new struct and setting the signal number rather then the structure that would have normally been received. As I stated before this did used to work fine in whatever version of lldb shipped with macos 10.12

If somebody has a better explaination, please post an answer and I will accept and award bounty.