4

i am currently writing a small VM in C/C++. Obviously i can't let the whole VM crash if the user dereferences a null pointer so i have to check every access which is becoming cumbersome as the VM grows and more systems are implemented.

So i had an idea: write a signal handler for sigsegv and let the OS do its thing but instead of closing the program call the VM exception handler.

It seems to work (with my very simple test cases), but i didn't find anything guaranteeing a Sigsegv being thrown on null-derefs nor the handler being called for OS generated signals.

So my question is: Can i count on signal.h on modern destkop OSes (i don't really care if it's not standard on doesn't work on something other than linux/win: it's a pet project). Are there any non trivial stuff i should be aware of (obscure limitations of signal(...) or longjmp(...) ?)

Thank you !

Here is the pseudo implementation:

/* ... */

jmp_buf env;

/* ... */

void handler(int) {
    longjmp(env, VM_NULLPTR);
}

/* ... */

if(setjmp(env)) {
    return vm_throw("NullPtrException");
} 
switch(opcode) {

    /* instructions */

    case INVOKE:
        *stack_top = vm_call(stack_top->obj); // don't check anything in the case where stack_top or stack_top->obj is null handler() will be called an a "NullPtrException" will be thrown
    break;

    /* more instructions */

}

/* ... */

Note : i only need to check nulls, garbage (dangling) pointers are handled by the GC and should not happen.

gan_
  • 656
  • 5
  • 15
  • In addition to dereferencing a null pointer, code can also dereference a garbage pointer, or any other kind of invalid pointer. Which will not necessarily generate a signal. As such, you have no alternative to validating every pointer access. – Sam Varshavchik Jul 10 '16 at 12:40
  • It's not a VM if you're passing pointer dereference operations to the host OS. At best it's an emulation wrapper (though don't tell WINE that that's what it is!). – Lightness Races in Orbit Jul 10 '16 at 12:47
  • @Sam Varshavchik : that's why the last note is there : garbage should not happen : ever. – gan_ Jul 10 '16 at 12:50
  • @OP: No, but your worry seems to be handling segmentation faults in general, not only NULL pointers. For example: `*(int *)(-5) = 1` is likely to generate segfault, even if it doesn't contain any NULL-pointer. – Zsigmond Lőrinczy Jul 11 '16 at 04:16

2 Answers2

3

Calling longjmp() from a signal handler is only safe if the signal handler is never invoked from async signal unsafe code. So for example if you might receive SIGSEGV by passing a bad pointer to any of the printf() family of functions, you cannot longjmp() from your signal handler.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • I read that the handler function should not return (as it has undef-behaviour). Does this mean i have to sanitize call to printf's by hand ? Is it also the case for C++ IO ? – gan_ Jul 10 '16 at 12:52
  • It's not just printf, it's any standard library function not explicitly listed here: http://man7.org/linux/man-pages/man7/signal.7.html . Yes, it includes C++ I/O, which is implemented using the C functions, and it includes more than I/O as well (for one more example, `mktime()` cannot be used). – John Zwinck Jul 10 '16 at 13:01
1

Can i count on signal.h on modern destkop OSes

You can count on it in the sense that the header, and the functions within will be available on all standard compliant systems. However, exactly what what signals are thrown and when is not consistent across OSes.

On windows, you may need to compile your program with cygwin or similar environment to get the system to raise a segmentation fault. Programs compiled with visual studio use "structured exceptions" to handle invalid storage access.


Is signal.h a reliable way to catch null pointers?

There are situations where null pointer dereference does not result in the raising a segmentation fault signal, even on a POSIX system.

  • One case might be that the compiler has optimized the operation away, which is typical for example in the case of dereferencing a null pointer to call a member function that does not access any data members. When there is no invalid memory access, there is no signal either. Of course, in that case there won't be a crash either.
  • Another case might be that address 0 is in fact valid. That's the case on AIX, which you don't care about. It is also the case on Linux, which you do care about, but not by default and you might choose to not care about the case where it is. See this answer for more details.

Then there is your implementation of the signal handler. longjmp is not async signal safe, so if the signal was raised while another non-safe operation was being performed, the interrupted operation may have left your program in an inconsistent state. See the answer by John Zwinck and the libc documentation for details.

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326