1

I'm trying to understand the machinery behind stack unwinding in C++. In other words I'm interested in how this feature is implemented (and whether that is a part of the standard).

So, the thread executes some code until an exception is thrown. When the exception is thrown, what are the threads/interrupt handlers used to record the state and unwind the stack? What is guaranteed by the standard and what is implementation specific?

Dmitry Kuzminov
  • 6,180
  • 6
  • 18
  • 40
  • 6
    No, there are no interrupts. I think you're confusing C++ exceptions with lower-level exceptions. – molbdnilo Nov 11 '22 at 08:34
  • 1
    C++ standard gives only general requirements so it can differ per operating system and even choice of runtime on same operating system. That causes such questions: https://stackoverflow.com/questions/15670169/what-is-difference-between-sjlj-vs-dwarf-vs-seh – Öö Tiib Nov 11 '22 at 08:41
  • 1
    There are parts of this question that _can_ be answered - which is why StackOverflow recommends to ask one question per question. Stack unwinding _is_ done on the thread which threw the exception, for instance. – MSalters Nov 11 '22 at 09:40

1 Answers1

1

The thread executes some code until the exception is thrown, and it continues to do so. Exception handling still is C++ code.

The throw expression creates a C++ object, running in the context of the throwing function. While the constructor of the exception object is running, all objects in the scope of the throwing function are still alive.

Directly after, however, the stack unwind happens. The C++ compiler will have arranged for a return path that does not require a return object, but which does allow passing of the exception object. Just like a normal return, objects that are local to a function are being destroyed when the function returns. At binary level, this is pretty straightforward: it's just a bunch of destructor calls, and typically a stack pointer is also adjusted.

What the standard also does not specify, is the mechanism used to determine how many scopes need to be exited. The standard describes in terms of catch, but a typical CPU has no direct equivalent. Hence, this is commonly part of the C++ ABI for a given platform so that compilers sharing a ABI agree. ABI compatibility requires that a caller must catch the exceptions from a callee, even if they've been compiled with different compilers. And obviously, destructors have to be called, so the ABI also needs to arrange that mechanism. The intermediate functions could even have been compiled by a third compiler - as long as they share an ABI, it's all supposed to work.

As noted in the comments, C++ has no notion of interrupts. If the OS needs something to happen with interrupts, the compiler needs to take care of that. It matters little what exactly the C++ code is doing at that time.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • I think there is a slightly inaccurate statement in the text: It is not exactly a return path that allows passing of the exception object; this makes it sound as if the function is capable of alternatively returning the exception object, while I do not think that this is in fact what happens. The cleanup code of each function in the stack gets invoked by the stack-unwinding code, but it does not receive, nor return, the exception object. `catch` clauses receive it, but again, they do not return it. – Mike Nakis Nov 20 '22 at 15:33
  • A couple of examples could be given of how `catch` and `finally` clauses can be detected by the stack-unwinding code. One way is to always push a special sentinel value into the stack on `try`, and pop it at the end of the `try-catch-finally` clause. The stack-unwinding code then only needs to look at the value right before the return address in the stack. – Mike Nakis Nov 20 '22 at 15:38
  • Another way is to always follow every `call` instruction with a relative jump a few bytes further down, and store the sentinel value in those bytes. This performs slightly better under normal circumstances, but requires more work during stack unwinding, because the stack-unwinder now needs to look at the code at the return address, but this is generally okay. – Mike Nakis Nov 20 '22 at 15:38