Here are even more interfaces than the three the author listed (libunwind, __register_frame, GDB):
- oprofile has a JIT interface: http://oprofile.sourceforge.net/doc/devel/jit-interface.html
- perf has a JIT interface: https://github.com/torvalds/linux/blob/master/tools/perf/Doc...
I wrote a blog article about the craziness around unwinding a few years back: http://blog.reverberate.org/2013/05/deep-wizardry-stack-unwi...
One of the questions I answer in my article which isn't addressed as much here is: why do we care about generating backtraces in the first place? It might seem obvious, but I identified four separate reasons which are all important: to support runtime exceptions (a la C++), to offer backtraces in debuggers, to sample the stack for profilers, and to print a backtrace from the program itself. So if you have a JIT and you don't offer proper backtraces, your users are going to be annoyed when they use any of these tools.
I found out the hard way that Microsoft has two sets of ARM compilers for WinCE7, and only one of them generates code that's compatible with the SEH unwinder.
I don't think SEH actually calls destructors when unwinding C++, unlike C++ exceptions. This makes it leaky to use for anything other than fatal exceptions.
How do you come to the conclusion that SEH is a leaky abstraction instead of the behavior of C++ destructors?
I went and looked this up to refresh my memory: https://msdn.microsoft.com/en-us/library/swezty51.aspx
Microsoft recommend you don't mix the use of SEH exceptions with C++, or that you use the flag which turns SEH into C++ exceptions (and therefore calls destructors). In our case we didn't want to do that, because we're doing our own crash reporting and all we want is to log a backtrace while exiting the program, and do not want to call destructors which may themselves crash because the program is in an invalid state.
This is my general impression on Win vs Linux: Windows offers more fundamental services as a core OS component, whereas Linux delegates this to a hodge-podge of competing and mutually incompatible user-space components. (Examples: tracing (ETW), transactional database (BlueJet/ESE), etc).
At that time, I blamed myself for not having been able to understand libunwind's code. I confess that this post has changed a bit my perspective.
The "unwinding" that libunwind refers to is merely the restoration of the procedure state (stack pointer and callee-saved registers). It doesn't know the ad hoc schemes for cleaning up the resources associated with a frame.
So that is to say, we can't take this library into the implementation of some language, and gain the ability to unwind through the frames of a foreign library which we called (and which called back into us) for doing things like safely performing a non-local return across that library. It's not going to deal with its resources, even if that library has some sort of unwind-protect scheme.
Basically, it's hard to understand what libunwind is for; what do I gain by integrating libunwind, beyond dumping back traces? Maybe I can integrate an interactive mini-debugger into the program where you can step through frames and examine state?
In "the old days" some systems had system-wide calling conventions. For example VMS had a standard calling convention all languages were supposed to use. Sounds good -- tools and subroutines can interoperate, right? Actually not really: object representation differed, for example. Plus new features (such as exceptions) that hadn't been thought up when the calling convention was developed couldn't be supported.
SVR4 attempted to make a common set of calling conventions, and to some degree succeeded, but really didn't help that much for the same reasons above.
As with so many problems in computing, the problem can possibly be solved by adding a layer of indirection: add a special ELF section with special entry points that support unwinding and examining frames. As long as you compile all your code with one compiler you should be OK...but what happens when new language features are added?
I like UNIX, don't get me wrong, but some of the API design is truly appalling.
> I like UNIX, don't get me wrong, but some of the API design is truly appalling.
I guess this is time for my annual post on Plan 9, which is basically Unix Done Right™. It really does feel like a coherent operating system, where all the parts fit together, feel well-thought-out and are well-finished. Every time I play with it (and really, at this point that's most of what it's good for) I want to cry at the lost potential. In that way, it's like SmallTalk or Lisp: something better, ignored by the world.
Sadly, while it offered built-in stack unwinding, it looks like the functions involved were different depending on RISC vs. CISC, and they could unwind at most 40 levels deep at a time (!): http://plan9.bell-labs.com/magic/man2html/2/debugger