Trying to build my own non-GNU cross-platform C++ environment, I have faced the fact that I don't really understand the basics of the stack unwinding. The environment I build is as follows:
libc++
← libc++abi
← libunwind
(or some other unwinder).
I've found that libc++abi
already contains some kind of libunwind, but doesn't use it on Linux. From the comments I understood, that it's special libunwind: LLVM Stack Unwinder that supports only Darwin and ARM but not x86_64 - and it's confusing. How does it come that the CPU architecture affects the stack unwinding process?
Also I know about following stack unwinders:
I expect to get the answer that covers this topic as whole, not just separate points on each question.
When an exception is thrown and control passes from a try block to a handler, the C++ run time calls destructors for all automatic objects constructed since the beginning of the try block. This process is called stack unwinding. The automatic objects are destroyed in reverse order of their construction.
JSC's Calling Convention This convention is used by the interpreter, the baseline JIT and its optimizing compilers DFG and FTL, such that JS-functions emitted by different compilers are able to interoperate with each other. All arguments are passed on the stack, there are also some additional values passed as argument.
Fundamentally, the stack layout is up to the compiler. It can lay out the stack in almost whatever way it thinks is best. The language standard says nothing about how the stack is laid out.
In practice, different compilers lay the stack out differently and the same compiler can also lay it out differently when run with different options. The stack layout will depend on the size of types on the target platform (especially the size of pointer types), compiler options such as GCC's -fomit-frame-pointer
, ABI requirements of the platform (eg x64 has a defined ABI where x86 does not). How to interpret the stack will also depend on how the compiler stores the relevant information. This in turn partly depends on the executable format (probably either ELF or COFF these days, but actually, as long as the OS can load the executable and find the entry point, everything else is pretty much up for grabs) and partly on the debug information format - which is again specific to the compiler/debugger combination in use. In the end, it is perfectly possible to write inline assembler that manipulates the stack and program flow in a way that no unwinder will be able to follow. Some compilers also allow you to customise the function prologue and epilogue, giving you another opportunity to confuse any unwinding algorithm.
The net effect of all this is that it is not possible to write a single stack-unwinding algorithm that will work everywhere. The unwinding algorithm has to match the compiler, OS and, for more than the most basic information, the debugger. The best you can do is to write a simple stack-unwinding interface and implement it differently for each compiler/OS/debugger combination you support.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With