0

The goal here was to catch SIGINT to close the server socket on a little socket server. I've tried to use a nested functions to keep the code clean. But...

When I do Ctrl-C (SIGINT, right?), I get Illegal instruction: 4. After reading this post, I've tried adding -mmacosx-version-min=10.8 to the compile flags since I'm on 10.8. Same error when doing Ctrl-C.

Two questions here: Why do I get `Illegal instruction 4"? How can I close the server socket without using a global variable?

My software:

Mac OSX 10.8.4
GCC 4.2.1

Here's how I'm compiling:

gcc -fnested-functions main.c

Here's the code:

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

void register_sigint_handler(int *serverSocket)
{
    void sigint_handler(int signal) {
        printf("Shutting down...\n");
        printf("Server socket was %d\n", *serverSocket);
        close(*serverSocket);
        exit(0);
    }
    signal(SIGINT, &sigint_handler);
}

int main(void) {
    int serverSocket = 0, guestSocket = 0;

    register_sigint_handler(&serverSocket);

    serverSocket = socket(PF_INET, SOCK_STREAM, 0);

    while (1) {}

    close(serverSocket);
    return 0;
}
Community
  • 1
  • 1
conradkleinespel
  • 6,560
  • 10
  • 51
  • 87
  • Did you try using a debugger (gdb/lldb)? – Kevin Aug 27 '13 at 21:50
  • 1
    This isn't causing your problem, but `printf` is not safe to call from a signal handler. Signal handlers have very limited things you can do in them -- the safest thing to do is to set a flag (of type `volatile sigatomic_t`), and then check that flag periodically during your main loop, and watch out for the `EINTR` error code from system calls like `accept(2)` and `select(2)`. – Adam Rosenfield Aug 27 '13 at 21:52
  • @AdamRosenfield: Since the code is using sockets, it can probably use the less stringent rules that [POSIX](http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04) provides for what can safely be called from a signal handler. The safe list still doesn't include `printf()`, but it does allow a lot of other functions to be used, including `write()`. However, the main thrusts of your comment remain accurate: (1) don't call `printf()` et al, and (2) be circumspect in what you do in a signal handler. – Jonathan Leffler Aug 27 '13 at 22:04

2 Answers2

6

While I can't tell you specifically what happens, the gcc docs have a generalization:

If you try to call the nested function through its address after the containing function exits, all hell breaks loose.

Passing the function pointer to signal() will do exactly that, call your local function after the containing function has exited. So you shouldn't pass a nested function pointer to signal()

You should probably just use a normal function for the handler, which sets a flag.

static volatile int do_exit;
void sigint_handler(int sig)
{
   do_exit = 1;
}

In a server one usually have main loop of some sort, around e.g. select or poll, your main loop, the empty while loop can now become

while (!do_exit) { 
  pause(); 
}

(Note that sockets are automatically closed by the operating system when the process exits;)

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
nos
  • 223,662
  • 58
  • 417
  • 506
  • Thanks for the thorough answer. Just for others wondering why `volatile` is needed here, this Q/A helped me understand that: http://stackoverflow.com/questions/246127/why-is-volatile-needed-in-c – conradkleinespel Aug 27 '13 at 22:03
5

"Don't do that, then."

GCC's nested-functions-in-C extension does not provide true closures. When you take the address of sigint_handler, a "trampoline" (a small piece of self-modifying code) is written to the stack; as soon as register_sigint_handler exits, the trampoline is destroyed; a subsequent attempt to invoke the trampoline (by the kernel, in order to dispatch the signal) causes undefined behavior.

Signal handlers are process-global by definition. Therefore, it is incorrect in principle to attempt to avoid using global variables in signal handlers. Imagine what you'd do to make this code cope with two server sockets: you can only have one SIGINT handler registered, so somehow it has to close both sockets.

All open file descriptors are automatically closed when your process terminates. Therefore, it is not necessary to close them by hand first. Furthermore, it is a breach of convention to exit successfully on ^C. If this program were being driven by a supervisory process, that process would want to know (via waitpid's status code) that it exited because of a SIGINT. Putting those two things together, you should not have this signal handler at all.

(That stops being true if you need to do something to your active sockets on exit. For instance, if you wanted to write something to every active client connection on exit, you'd want a signal handler. But at that point you want to have the signal handler alert the main event loop, and do the work there.)

(Consider using libevent for this sort of thing rather than doing all the low-level goo yourself.)

zwol
  • 135,547
  • 38
  • 252
  • 361