4 ms·
You can distinguish where a NULL pointer exception occurred by the program counter/instruction pointer. JIT'd code lives in particular memory regions (e.g. thos
by wahern 4y ago
You can distinguish where a NULL pointer exception occurred by the program counter/instruction pointer. JIT'd code lives in particular memory regions (e.g. those explicitly mmap'd with PROT_EXEC) dynamically managed by the application. From there you may or may not care to examine the stack(s) for more specific details.
Alternatively, you could simply only enable NULL pointer handling around specific blocks of code where you're prepared to handle it, and disable it everywhere else (causing the kernel to simply kill the program). I've used this technique to elide explicit array overflow checks in some very performance critical code, dramatically increasing performance (there would have been at least as much overflow checks as semantic operations). None of the critical code used malloc/free or called into any libraries, so on SIGSEGV I would simply longjmp back to a safe place and either grow the data structures and reset the state, or return failed, depending on how large things had grown.[1]
The hard part of all of this isn't detecting where or even why a fault occurred--at least presuming you've architected things deliberately--but rather handling asynchronicity. If you have access to mechanisms like userfaultfd it's much easier, but it's doable on most systems. On most Unix systems threading primitives are NOT async-signal safe, but there are plenty of syscalls that are, and at least historically the semantics of Unix signal handlers were carefully defined to permit these sorts of tricks. It's a fine needle that needs to be threaded, but one that can done correctly nonetheless.
[1] Note that this wasn't accidental. I wrote the code very carefully this way. I had also worked on at least two projects before where someone tried to get clever and add SIGSEGV handlers to recover from code that was never designed to be recoverable, with predictably horrendous results (e.g. endless, time-wasting bug reports and "fixes").
- gpderetta 4y agoAFAIK, GNAT and GCJ (GCC front ends for Ada And Java respectively, the latter now dead) implement null pointer exceptions by simply raising a language level exception from the SIGSEGV handler (after I assume checking it happened in user code instead of the runtime). Completely non portable of course, but with the help of glibc, the Itanium ABI exception support and non-call-exceptions it can evidently be made to work reliably, at least on Linux.
- evmar 4y agoThank you for explaining! I think the piece I was missing is that the sigint handler is passed the instruction pointer of the faulting instruction.