Search code examples
cunixsignalsubuntu-14.04macos-sierra

Why does printf break signal handlers on macOS but not on ubuntu?


I was performing a simple demonstration of signal handlers as an exercise when I noticed some odd behaviour. The following code works unchanged on ubuntu 14.04 LTS but not on macOS Sierra 10.12.6. That is on macOS the program hangs forever with no output. However, it works on both platforms with printf commented out in the signal handlers or with an extra printf added in the main function anywhere before the posix_memalign call. I don't understand this behaviour, what's going on?

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <signal.h>


//Has to be global for signal handler
char* mem_spot = NULL;
const char* copy_from = "hello";
const int mem_req = 5;

//signal handler prototype
void handle_seg(int fault);

int
main(int argc, char const *argv[])
{
    //On macOS this must be placed uncommented somewhere before
    //posix_memalign or the signal handler printing removed:

    //printf("Strange fixing code!");

    //setup signal handlers
    signal(SIGSEGV,handle_seg);
    signal(SIGBUS,handle_seg);
    //get memory aligned to the page and protect it
    posix_memalign((void**) &mem_spot, getpagesize(), mem_req);
    mprotect(mem_spot,mem_req,PROT_READ);
    //Copying into the memory slot (it's protected so should break!)
    strcpy(mem_spot,copy_from);
    //Output my results
    printf("Write of string '%s' to memory giving '%s' complete!\n",copy_from, mem_spot);
    return 0;
}

void handle_seg(int fault)
{
    if (fault == SIGSEGV){
        printf(" (Handled a segmentation fault!) ");
        mprotect(mem_spot,mem_req,PROT_READ | PROT_WRITE);
    }
    else if (fault == SIGBUS){
        printf(" (Handled a bus fault!) ");
        mprotect(mem_spot,mem_req,PROT_READ | PROT_WRITE);
    }
}

Solution

  • So, to give an actual answer: Your program triggers undefined behavior in a number of ways, which means it can do anything. Printing out something, or hanging, or crashing, are all equally valid results of running it.

    Your first problem: Calling a non-async-signal-safe function from a signal handler. There are a limited number of functions that can safely be called from handlers. You can see the list here, or on linux, with man signal-safety.

    Your second problem: Returning from a signal handler triggered by a SIGSEGV that wasn't triggered by a function like kill() that explicitly raises a given signal. To quote from the above documentation:

    The behavior of a process is undefined after it returns normally from a signal-catching function for a SIGBUS, SIGFPE, SIGILL, or SIGSEGV signal that was not generated by kill(), sigqueue(), or raise().

    To summarize: What you're doing is inherently broken and not something that can be predicted or reasoned about or understood. Don't do it.