Hacker News new | past | comments | ask | show | jobs | submit login
Why you cannot have C++ exceptions with a stack trace (unipi.it)
58 points by ingve on Jan 18, 2016 | hide | past | favorite | 24 comments



You can have C++ exceptions with a stack trace; you just need to read the relevant debug information. (e.g. DWARF-2 frames). There are libc specific functions that do this for you. see https://www.gnu.org/software/libc/manual/html_node/Backtrace...

Some ffis do this to present C++ stack traces to their host languages.

If variables/frames are optimized out, then yes, you can't display their contents. But anyone that has used gdb already knows this...


Libunwind also works great in this context. For some cases I even found it to work much better than backtrace. It also has a signal-safe functions that facilitate implementation of stack printing on crash.

I work with projects where stack traces are added automatically to exceptions during throw. It works quite well, and in fact I would disagree with author on some of his points:

- stack trace not representative of source code, due to optimizations - unwinding information is consistent with what you have in a source code, and compiler optimization don't change that (i.e., it would be a compiler bug if it did, see any design document about debugging information, for example in LLVM / Clang).

- throw expression is not a place where the problem is - that is of course true in general, but how much more useful is stack trace indicating code path where exception have been thrown, than merely a type of exception. Name of function which have thrown an exception is also a good start, but of limited value for some common utility functions.

- memory allocation - dynamic memory allocation could be problematic, but storing stack trace in exception object itself is quite simple and avoid this problem altogether.


This sort of thing drives me crazy sometimes. Why can't we have a high level assembly language which can represent the code that's executing. Right now I have the choice of either looking at the code, which can be wrong, or looking at assembly, which is hard to associate with the code. Why can't I look at annotated assembly which tells me exactly which part of the code it corresponds to?


> Why can't I look at annotated assembly which tells me exactly which part of the code it corresponds to?

Because modern optimizations make that impossible. (How do you represent a statement that GVN eliminated, for example?) Not doing those optimizations would make code unacceptably slow.


Use Visual Studio with optimisation off. Its disassembly view will then line up fairly well with the C++ source.

(and with "structured exception handling" and a bit of hacking, you can get backtraces for machine exceptions as well)


But an issue is still that in the handler (catch clause), the stack has unwound, so if you want to access the stack trace "after the fact", you will have to do some work when throwing the exception.


You'd normally use a library that hooks the exception throwing mechanism in the runtime library in a toolset-specific way.


Yes, but I mean that every exception will have an additional cost then, because you will have to copy the stack trace (implicitly or explicitly).


For many common ABIs, unwinding the stack is relatively cheap compared to decoding the debug info to figure out how to unwind the stack. If you have frame pointers, it's especially cheap -- you're walking a 20 element linked list.


To recap, if you want to implement your own exceptions in C++ with the stack trace in the same way as Java or C# do, that's good fun.

Just use Google Breakpad. For Objective-C, you'll probably do better with PLCrashReporter (or possibly KSCrash) which can reliably unwind at the time of the crash because of certain assumptions it can make about ObjC binaries that the more general Breakpad does not make. (Breakpad just captures the stacks to a minidump, which you then unwind later using symbol files generated from your binaries.)

you usually need to allocate a piece of memory in the heap

Breakpad preallocates. The ObjC (Mac, iOS) handler included with Breakpad (should you go that route) goes so far as to mprotect the preallocated memory to help protect it from memory smashers.

None of this is perfect, but I'd rather have stack traces than not. It's also useful to have a breadcrumb mechanism in the app to include with the stack trace.


Back in 2001, when VS6 couldn't even parse templates correctly, I was making games on Linux but wanted them to run on Windows and Mac too.

I also wanted stack traces in exceptions (or being able to get them at any time, really) and I wanted to make them encrypted, to avoid giving away the internal structure of our game and engine code.

The solution I came up with was to have a global array representing the stack trace, a macro that would create a small scoped object which pushed a new "stack trace" entry on creation and popped it on destruction, and a script that would add a call to this macro to the beginning of each method with any payload I wanted -usually the encrypted filename, line and signature of the method, but it was possible to append e.g. parameter values as well.

Since these strings were compile time constants, this whole thing was surprisingly cheap, and worked great for over a decade.

With C++ there always is a way :)


Most of the reasoning here seems to be based on 1) that "out of memory" is the only exception worth talking about, and 2) the only thing you want to do is abort the program at that point.

I don't think either of these are true. Some libraries make liberal use of exceptions, as a design decision. If your text editor tries to open an enormous file, you don't want it to crash, you want it to deallocate all of the memory it consumed for that file and then fall back with an error message.

I don't think the point about inlining is reasonable either: I wouldn't expect a fully optimised build to give me a full stack trace, that is what debug builds are for.

>If the exception is meant to signal the fact that the memory is finished, where are we allocating the stack trace?

If this is really a concern, allocate some memory at the start of execution that you can use in the event of a crash.


Unless you have carefully configured your OS, 'out of memory' exceptions are essentially worthless. This is because most systems work using 'optimistic' memory management, where it is not known (or checked) whether there is enough free memory available at the time of the malloc() call. The problem comes later, at any time when a random instruction tries to access the memory and the OS finds that it has no RAM available.


Of course we should eat the poison and then go to the hospital.

The naive texteditor should just check the size of the file before loading it into memory instead of trying to recover from a crash.


Agreed; in my line of work (mobile games) an out of memory exception is the least useful, because the place that threw is not necessarily in the offending system that ate all of the memory.

Getting stack traces for other exceptions IS useful - our code is full of assertions that throw when invariant assumptions are violated, and knowing the stack trace is useful for tracking down what system is misbehaving.


On Windows, C++ exceptions have a stack trace.

It's not perfect because all the destructors for your stack allocated objects have run. So for example, any std::string on the stack will be gone, but any pointers to structures on the heap are still intact.

In addition, just knowing what the stack was is invaluable in debugging even if you don't have the values of every variable.

It's a huge pain that you can't get that under linux.



Like the article, but I'm really not digging that style used for inline code snippets. Just changing font face and color a bit seems like a proven solution to me. Doing that but also adding ⌈characters⌋ around it makes it hard to read. At least for me.


Same feelings here: nice post but bad typography. To the author and anybody writing their own CSS: please use line spacing to make it easier to get the starting point of the next long line of text. See https://www.w3.org/TR/WCAG20-TECHS/C21.html and, believe me, it doesn't impact only "people with cognitive disabilities".


There are functions that can capture or print stack traces, such as the backtrace() family on OS X (which has a "man" page) or just the "pstack" command on Linux. It is very useful to have a function in your code base that invokes one of these on demand.

Then you can do interesting things like print a stack trace at a "throw" point if you suspect a problem, or capture a trace into an exception base class in debug modes for printing at catch points, or even as a form of non-crashing assertion to log traces whenever unexpected conditions arise.


It's true that exceptions in C++ are more expensive than in other languages, often even in absolute terms, but that has nothing to do with problem at hand.

In GC'd language, implementing exceptions by simple non-local exit to catch/finally block is perfectly viable implementation strategy. It does not work so well in C++, where are implicit finally blocks that call destructors of local variables almost everywhere. To some extent this feature could be abused to produce stack-trace during the unwinding (but that necessarily involves injecting code to all functions in the program).

On the other hand, CL/Smalltalk-style condition systems sidestep this issue completely, by running the handler code before unwinding the stack. Windows' SEH allows you to do the same thing. And IIRC even in plain C++, handler for completely unhandled exception (that by default involves call to abort()) runs without stack unwinding (thus something along the lines of std::set_terminate (__gnu_cxx::__verbose_terminate_handler) gives you stacktraces for unhandled exceptions).


I'd rather have a core dump than a stack trace. There's so much more information in a core dump. The stack is there as well, but having all live variables in the program, all loaded code, and all process state makes it so much easier to debug what's going wrong.


I rather have both.


And what's wrong with using libunwind?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: