C++ Logo

std-proposals

Advanced search

Re: Adding stacktrace to std::exception

From: Andrey Semashev <andrey.semashev_at_[hidden]>
Date: Thu, 12 Nov 2020 23:06:03 +0300
On 11/12/20 10:22 PM, Antony Polukhin wrote:
> чт, 12 нояб. 2020 г. в 19:03, Andrey Semashev via Std-Proposals
> <std-proposals_at_[hidden]>:
>> I'm not sure I understand how that would work. Saving a frame pointer is
>> not enough since stack frames would be naturally destroyed during
>> unwinding, so at the point where you catch the exception and about to
>> get the stacktrace the pointer is useless.
>
> We're saving not the frame, but the frame pinter - a return address to
> the caller. That address is a pointer to the code, to the actual
> instructions. When the frame is destroyed the pointer to the
> instructions is still valid (there are nuances with DSO unloading,
> let's leave those out of scope). Later, that pointer could be used to
> get the human readable name of the caller.

A sole pointer to the code is not enough to restore the stacktrace. You
need to walk up the stack to collect the addresses of the calls that led
to the point of throwing the exception. This is a linear operation.

>> Generating stacktraces at compile time also doesn't seem feasible, since
>> there may be a lot ways the control could get to a particular throw
>> statement. So it seems some state capturing should happen at run time,
>> and that work could have a linear complexity.
>
> When you call std::stacktrace::current() it iterates through the
> linked list of frames, storing return addresses. Operation takes
> linear time from the frames count.
>
> When stack unwinding is triggered by the exception, it does pretty the
> same things: goes through the frames and calls destructors for
> constructed objects. The operation takes linear time from count of
> objects to destroy and count of frames.
>
> You can embed the functionality of std::stacktrace::current() into the
> stack unwinding to iterate only once through a list of frames. That's
> a micro optimization, stack unwinding does an insane amount of
> additional work (like locking mutex multiple times).

When an exception is thrown, stack unwinding is only performed up to the
first relevant catch() block, while the stacktrace needs to present
frames beyond that.

Also, I'm not sure the amount of work needed for calling destructors is
the same as the one to collect the stacktrace. I.e. there may be frames
where no destructors need to be called, but which must still be present
in the stacktrace.

Lastly, calling destructors presumably doesn't need to allocate anything
(beyond the destructor logic). The contents of the stack should be
enough to perform the unwind. But collecting the stacktrace means the
addresses of the functions need to be stored somewhere, which means the
storage needs to be allocated. This is overhead and a potential point of
failure.

Received on 2020-11-12 14:06:08