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").