1

I know that:

  • When installing a SIGSEGV signal handler with sigaction and a sa_sigaction (rather than sa_handler), the signal handler receives a siginfo_t*, of which the si_addr is the address at which the fault occurred.

  • Using the ucontext_t we can inspect the values of registers, for example the instruction pointer, albeit not in a platform-independent way (Linux signal handling. How to get address of interrupted instruction?).

My question: can we also know which register caused the fault? Given that we don't have memory-to-memory moves, this should be only one register (after all, there is also only a single si_addr). Of course I could inspect all registers and search for si_addr, but there may be more than one match.

I would be perfectly happy with solutions that are not platform-independent.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • 2
    "can we also know which register caused the fault?" - Neither arch I program for on segfault does store the register which causes it. So you have no other way than decode the instruction which caused the segfault. – Tsyvarev Jul 02 '20 at 17:06
  • @Tsyvarev yep, that's what I was afraid of, and that's not going to be neat especially for a large instruction set like that of x86... –  Jul 02 '20 at 17:14
  • 1
    The address could also be in no register at all, but encoded in the instruction; e.g. `movl %eax, var`. And just to nitpick, there are some instructions that do memory-to-memory moves: `movs*` and `push/pop mem`, for instance. – Nate Eldredge Jul 02 '20 at 21:43
  • But I'm having trouble thinking of a situation where it would be necessary or useful to do what you describe, even if it were well-defined. Maybe if you were to explain your problem more fully, someone will know of a higher-level solution. – Nate Eldredge Jul 02 '20 at 21:48
  • 1
    @NateEldredge thanks for that. Those two situations you describe (variables and mem-to-mem) don't happen in my case, but they do show together with Peter's comment why it is not possible what I wanted to do. I was wondering whether it would be possible to create a garbage collection scheme like a copying collector, which does not copy anything but protects the old semispace, and on segfaults copies the node pointed at over. The advantage would be to slice GC time so that there are no noticeable pauses. (I know there are other GC schemes that do this as well, it was just an idea.) –  Jul 03 '20 at 07:28
  • @NateEldredge: Possible, but I left out absolute / RIP-relative addressing because you have to be doing something really wrong for static data to segfault (absolute or RIP-rel), in compiler-generated code. Thread-local storage (with FS base that's not easily readable) is also possible but it's not usually dynamically allocated. – Peter Cordes Jul 03 '20 at 08:00
  • @user13853391: I'm not sure why you need the register. You have the pointer into the faulting node. Were you hoping to find the object base address in a register, with the addressing mode being `[reg + member_offset]`? That could work if access to dynamic objects is always doing that way, i.e. you control code-gen. Otherwise a loop accessing an array of objects might do something else, like start with a pointer `&foo[0].y`, and increment it by the object size to get `&foo[1].y` so actual access could use `[reg]`. Or with SIMD or loop unrolling, there's lots of real-world-plausible code-gen – Peter Cordes Jul 03 '20 at 08:06
  • @PeterCordes the idea was to copy the node to the new semispace, then update the register to point to the new node and continue running. I didn't think of the problem of array offsets, I didn't realize that. Anyway, it was just a fun idea, and probably the overhead of the signal handler would make it unusable even if it could be implemented... –  Jul 03 '20 at 09:00
  • 1
    Ah, so that's why you wanted the reg. Makes some sense. But yes, the cost of an exception / entering the kernel alone would be a showstopper, especially with Meltdown + Spectre mitigation slowing that down by an order of magnitude or so. You'd be paying more than the cost of a soft page-fault on a per-object basis, not per page, so yes performance would be impractical. – Peter Cordes Jul 03 '20 at 09:07

1 Answers1

2

The load/store address might not be in any single register; it could the result of an addressing mode like [rdi + rax*4 + 100] or something.

There is no easy solution to print what a full debugger would, other than running your program under a debugger to catch the fault in the first place, like a normal person. Or let it generate a coredump for you to analyze offline, if you need to debug crashes that happened on someone else's system.

The Linux kernel chooses to dump instruction bytes starting at the code address of the fault (or actually somewhat before it for context), and the contents of all registers. Disassembly to see the faulting instruction can be done after the fact, from the crashlog, along with seeing register contents, without needing to include a disassembler in the kernel itself. See What is "Code" in Linux Kernel crash messages? for an example of what Linux does, and of manually picking it apart instead of using decodecode.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Thanks for explaining why this is not possible. What you mention about debugging is less useful for me (see my use case in a comment above), but it may be helpful to others in the future. –  Jul 03 '20 at 07:30