1

I was looking through the MINIX 3 headers and in include/signal.h there was some seemingly unusual definitions:

/* Macros used as function pointers */
#define SIG_ERR ((sig_handler_t) -1)    /* error return */
#define SIG_DFL ((sig_handler_t) 0)     /* default signal handling */
#define SIG_IGN ((sig_handler_t) 1)     /* ignore signal */
#define SIG_HOLD ((sig_handler_t) 2)    /* block signal */ 
#define SIG_CATCH ((sig_handler_t) 3)   /* catch signal */

First of all, how is SIG_ERR valid? Second of all (and for my main question), there have been other pointers in the source code that map to some of those addresses (ex. NULL). What would happen if you dereference one of these pointers? Is the data in those addresses valid?

nanoman
  • 341
  • 4
  • 11
  • Same as if you try to dereference NULL pointer. – purec Jul 15 '18 at 17:03
  • @purec: An integer value of `0` is not necessarily a null-pointer. The code is apparently beyond the standard, alone for casting `int` constants to pointers. It can only be explained with platform details. – too honest for this site Jul 15 '18 at 22:28
  • @Olaf, the code has nothing to do with the standard.... it is implementation of the special values for signal handling for the MINIX implementation. The special values to be used for indicating default behaviour, ignoring a signal or a sistem error (signal returns a pointer to the old signal handler installed or `SIG_ERR` (`-1` cast to `(sig_handler_t)`, as common for system calls) – Luis Colorado Jul 18 '18 at 19:47
  • @LuisColorado: Would you please read my comment **carefully** again? I wrote exactly it's implementation specific. Nevertheless I'd expect to caode cause problems on 64 bit platforms (just in case Minix has been ported to one). Neverthless it's still problematic. It would have been better to use `uintptr_t` or maybe `intptr_t` as base-type. But, well, Minix is a legacy itself, much older than modern C. – too honest for this site Jul 18 '18 at 22:40
  • @Olaf, sorry you misinterpreted me... you comment "The code is apparently... " and I say the code has **nothing to do**. I'm in agreement with you, except being more agressive in my statements.... that's what I tried to show, not a discrepancy between you an I. – Luis Colorado Jul 20 '18 at 12:27

2 Answers2

1

Casting like this is not defined by the C standard, however your particular implementation allows it in order to check for various errors or return codes.

These values can’t be defeferenced, but they can be compared against values returned for various signal handling functions.

dbush
  • 205,898
  • 23
  • 218
  • 273
1

These are not memory addresses and dereferencing them is not meaningful. They are “magic” values that indicate that this is not the address of a handler function, but an instruction to set the signal handling state to something other than “run this function”.

The values are chosen to be distinct from the address of any valid function, because otherwise there'd be no way to tell whether the caller of the signal function passed the address of a function or the magic value. Virtually all systems that have an MMU arrange to map nothing in the first page, so addresses below the page size cannot be the address of a variable of function. This enables NULL to be the address 0, for example.

The value -1 is typically mapped to the highest possible address (all-bits-one), just like (unsigned)(-1) is all-bits-one. But that's an implementation choice (unlike (unsigned)(-1), which is perfectly well-defined since unsigned integers are defined modulo 2N where N is the bit size). For example, on some implementations where int is a 32-bit type but addresses have 64 bits, ((sig_handler_t) -1) would map to the address 0xffffffff, which is a plausible address for a function.

Note that these are things that the operating system implementer can do, because they know how pointers are represented on a particular platform. The representation of pointers is not specified by the C standard (specifically, the effect of converting an integer to a pointer is implementation-defined) and constraints vary from system to system. As a C programmer, you can't do this (more precisely: you can, but unless you know exactly what you're doing, it's likely to go wrong). Not only would you have to know how a particular platform represents pointers and how it converts integers to pointers, but you'd also have to know what assumptions the compiler makes on your code. OS code may need to be compiled with a specific compiler or with specific compiler flags to enable the necessary implementation-specific behavior.

The signal system call uses them in a way like the following (vastly simplified, but you get the idea):

enum signal_disposition {
    SIGNAL_DISPOSITION_IGNORE,
    SIGNAL_DISPOSITION_KILL,
    SIGNAL_DISPOSITION_RUN_HANDLER,
    SIGNAL_DISPOSITION_STOP,
};

sighandler_t sys_signal(struct task *calling_task, int signum, sighandler_t handler)
{
    if (signum > SIGMAX || signum == SIGKILL) return SIG_ERR;
    sighandler_t previous_handler =
        calling_task->signal_disposition == SIGNAL_DISPOSITION_IGNORE ? SIG_IGN :
        calling_task->signal_disposition == SIGNAL_DISPOSITION_RUN_HANDLER ?
        calling_task->signal_handler[signum] :
        SIG_DFL;
    if (handler == SIG_DFL) {
        calling_task->signal_disposition[signum] =
            signum == SIGTSTP ? SIGNAL_DISPOSITION_STOP :
            signum == SIGALRM ? SIGNAL_DISPOSITION_IGNORE :
            SIGNAL_DISPOTITION_KILL;
        calling_task->signal_handler[signum] = NULL;
    } else if (handler == SIG_IGN) {
        calling_task->signal_disposition[signum] = SIGNAL_DISPOSITION_IGNORE;
        calling_task->signal_handler[signum] = NULL;
    } else {
        calling_task->signal_disposition[signum] = SIGNAL_DISPOSITION_RUN_HANDLER;
        calling_task->signal_handler[signum] = handler;
    }
    return previous_handler;
}

And this is the corresponding code run in the kernel to trigger a signal in a process (again, vastly simplified):

void handle_signal(struct task *calling_task, int signum) {
    switch (calling_task->signal_disposition[signum]) {
    case SIGNAL_DISPOSITION_IGNORE:
        break;
    case SIGNAL_DISPOSITION_KILL:
        kill_task(task, signum);
        break;
    case SIGNAL_DISPOSITION_RUN_HANDLER:
        task->registers->r0 = signum;
        task->registers->pc = calling_task->signal_handler;
        wake_up(task);
        break;
    case SIGNAL_DISPOSITION_STOP:
        stop_task(task);
        break;
    }
}
Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254