[HN Gopher] Show HN: Using C++23 <stacktrace> to get proper cras...
___________________________________________________________________
Show HN: Using C++23 <stacktrace> to get proper crash logs in C++
programs
Author : TylerGlaiel
Score : 55 points
Date : 2023-07-03 18:50 UTC (4 hours ago)
(HTM) web link (github.com)
(TXT) w3m dump (github.com)
| zX41ZdbW wrote:
| There are parts of C++ standard library that no one should ever
| use.
|
| The examples are: regex, iostreams, locale...
|
| My main concern - this can also become such a dead weight.
| the_svd_doctor wrote:
| Can you expand on the problems with regex?
| dkersten wrote:
| From what I've heard, std::regex is notoriously inefficient
| (lots of memory allocations, no allocator support). But I've
| never used it myself.
| TylerGlaiel wrote:
| oh yeah C++ regex is stupidly inefficient, like "python is
| faster" inefficient. I tried to use it for text
| replacements and pretty much immediately abandoned it
| gpderetta wrote:
| Apparently typical implementations of std::regex are
| inefficient like "'popen("perl..")' is faster" is
| inefficient!
|
| I thing boost::regex is significantly faster although not
| particularly fast
| mike_hock wrote:
| The implementations are bad and implementers are refusing to
| fix their own bad implementations so as to not break their
| own ABI, but that has nothing to do with C++ the standard.
| verall wrote:
| What's wrong with std::regex? Seems to work fine for me.
|
| And iostreams - they're not great. Bad programming UI, poor
| performance, etc. Issues abound. But should you never use them?
| What do you use instead? *printf methods have lots of issues
| too. And so does depending on Boost::format. And so does
| writing your own Logger/wrapping code (which is what everyone
| does AFAICT).
|
| locale is bad though
| gpderetta wrote:
| iostreams are not great but are nothing compared to the
| awkwardness and lack of extensibility of printf style format
| strings.
| plq wrote:
| > What's wrong with std::regex?
|
| In my experience, stdlibc++ <regex> is VERY slow, especially
| on debug builds. We are using g_regex instead, which in turn
| uses pcre2.
|
| > And iostreams
|
| <iostream> achieves too little with too much code. We instead
| use: std::cout << fmt::format(...);
|
| for simple output, loguru[1] for everything else. I feel like
| the fmt grammar/mini-language is both nicely extensible and
| has hit the expressiveness sweet spot -- not too verbose
| (iostreams) nor too terse (printf).
|
| I also like that fmt has helpers for pointers (fmt::ptr),
| enums (fmt::underlying) and arrays (fmt::join). It's both
| easy on the eyes and feels consistent.
|
| [1]: https://github.com/emilk/loguru
| einpoklum wrote:
| 1. Historically, std::regex was offered in GCC before it was
| actually fully implemented. Much hilarity ensued...
|
| 2. Some existing implementations have efficiency issues, e.g.
| performing many allocations.
|
| 3. It is claimed (e.g. by Titus Winters) that the ABI of
| std::regex is problematic, and without breaking it, the
| implementations cannot be good enough
|
| See these points and others at:
|
| https://www.reddit.com/r/cpp/comments/e16s1m/what_is_wrong_w.
| ..
| scatters wrote:
| iostream is replaced by format.
| npsimons wrote:
| > What do you use instead [of std::iostream]?
|
| This is what I want to know. Having come from C to C++,
| iostreams were a big improvement over the "strings" and print
| functions of C. I even extended a base iostream class to have
| a "teebuf" logger, that could output to multiple streams and
| had the standard logging levels.
|
| It's been a while since I last had mastery of C++, but I'd
| like to hear what is as portable and better than iostreams.
| mlhpdx wrote:
| I may be wrong, but as I recall it is good form to chain
| exception filter calls by making note of the return from
| `SetUnhandledExceptionFilter`. For example, if I want to use
| `<stacktrace>` and do copy-on-write using memory protection in
| the same program.
| [deleted]
| catiopatio wrote:
| Reliable in-process crash reporting is exceptionally difficult.
|
| The code must be fully async-safe, which means you cannot use
| <stacktrace>. You also cannot acquire mutexes, use any of the
| standard allocators, etc etc etc.
| hoten wrote:
| What's the benefit of in-process crash reporting compared to
| just using something like crashpad/breakpad?
|
| To the extent that in-process crash reporting is even
| possible... seems the most common class of crashes would be
| entirely unrecoverable.
| kevin_thibedeau wrote:
| It has value in embedded code where you can stop the world to
| handle or log fault conditions.
| aseipp wrote:
| Ease of integration, because having literally _anything_ is
| typically better than nothing. Honestly Crashpad isn 't fun
| to integrate unless you use a fork like backtrace's (which
| adds CMake support), which I think doesn't help. I don't know
| of any alternatives.
|
| A version of Crashpad or something like it with a single
| turnkey server for database dumps, a one-line "defaults are
| good enough" integration, would be a real great thing to see.
| hoten wrote:
| I found Sentry's crash reporting (which uses crashpad)
| simple enough to configure into an existing CMake build
| within an afternoon.
|
| Building Sentry/crashpad from source in a few lines of
| CMake: https://github.com/ArmageddonGames/ZQuestClassic/com
| mit/3471...
|
| And a few lines in the main function: https://github.com/Ar
| mageddonGames/ZQuestClassic/commit/3471...
| aseipp wrote:
| Neat, I didn't know Sentry also had a good fork, I
| haven't tried it! But in contrast, here's an in-process
| fault library that I whipped up (from forking Phusion
| Passenger) about 10 years ago that I still reach for
| sometimes, which is surprisingly robust to most of the
| original complaints about async safety, but still not
| perfect: https://github.com/thoughtpolice/libfault
|
| You add one C file and 6 lines of code in `main()`, and
| you can do this in pretty much any programming language
| with a tiny extra bit of glue. It takes 3 minutes to do
| this in any C/C++ codebase of mine. It is build system
| agnostic and works immediately, with zero outside deps.
| It's _something_ , and that's better than nothing, in
| practice. So people reach for that. I reach for it. And
| not just because I wrote it.
|
| I want to be clear: Crashpad is 10000x better than mine
| in every way, except this _one_ way. And I really wish it
| wasn 't. To add onto this, I really don't like CMake for
| example, so this problem isn't just a "well I like my
| thing." I want something that will also work in my Java
| programs, or Rust programs, for instance! Sometimes they
| crash too. I don't need to add any dependencies except
| like 2 or 3 C function calls, which almost every langauge
| supports with a native FFI out of the box. The friction
| is extremely low.
|
| I'm reminded of something Yann Collet once said about the
| design of zstd, and getting people to adopt new
| compression technology. If you make a compressor and it's
| better than an alternative in one or more dimensions, but
| worse in another (size, decompressor speed), then
| friction is actually _significantly_ increased by that
| one failure. But if you make it better in _every_
| dimension -- so it gives an equal ratio and compression
| and decompression are always better than alternatives --
| the friction is eliminated and people will just reach for
| it. Even though you only did worse in _one_ spot, people
| find ways to make it matter. It really makes people think
| twice. But if it 's always better, in every way, then
| using and reaching for it is just instinctive -- it
| replaces the old thing entirely.
|
| So that's what I really wish we had here. I think that's
| what you would need to see a lot better crash handling
| and reporting become more widely used. There needs to be
| a version of Crashpad, or any robust out of process crash
| collector, that you can just drop into any language and
| any build system with a little C glue (or Rust! Sure!
| Whatever!) in 5 minutes and it should have a crash
| database server and crash handler process _which should
| instantly work_ for most uses.
| hoten wrote:
| Thanks for sharing, I'm sure that will come in handy for
| me some day!
|
| This all feels like a failure of our modern OSes - why
| must the application layer know how to report on when it
| crashes? It seems like functionality that the OS should
| provide! Instead, we're stuck reaching for these random
| extensions solving the same problem in the same way
| everywhere - or, if you're lucky, this gets provided by
| the language framework for "free" to application
| developers (but not the language developers).
| [deleted]
| rightbyte wrote:
| I guess it is easier to print out some interesting process
| variables, compared to trying to save and then make sense of
| dumps etc.
| zX41ZdbW wrote:
| The best way I've found is - patching LLVM's libunwind to make
| it fully async-signal safe, and sending the stack trace to
| another thread for symbolization. This is implemented in
| ClickHouse.
| einpoklum wrote:
| 1. Can you link to that?
|
| 2. Have these changes been offered as patch for libunwind or
| boost::stacktrace?
| einpoklum wrote:
| What is async-unsafe in using `<stacktrace>`?
|
| As for not using standard allocators - not a problem, just have
| a fixed area set aside as a buffer for crash reporting. Yes, it
| might not fit an extremely long report, but it's not that much
| of an issue.
| catiopatio wrote:
| It's not guaranteed to be async-safe. From the C++ proposal
| (P0881R7):
|
| > Note about signal safety: this proposal does not attempt to
| provide a signal-safe solution for capturing and decoding
| stacktraces. Such functionality currently is not
| implementable on some of the popular platforms.
|
| https://www.open-
| std.org/jtc1/sc22/wg21/docs/papers/2020/p08...
|
| [edit] Replying here, because HN is doing its occasional
| obnoxious rate-limiting of replies:
|
| Signal-safe and async-safe _are_ effectively the same thing,
| and "async-safe" _absolutely isn't_ the same thing as
| "thread-safe".
|
| A code path that acquires a mutex can be thread-safe; that's
| absolutely not async-safe.
|
| If boost implemented a fully async-safe stack unwinder,
| complete with DWARF expression support, Apple compact unwind
| encoding support, and all the other features required across
| platforms, then good for them -- but that's not what
| <stacktrace> is guaranteed to provide, and such a thing is
| _still_ not sufficient to implement anything but the most
| barebones portion of a real crash reporter.
| einpoklum wrote:
| 1. signal-safe and async-safe/thread-safe is not quite the
| same thing, but fair enough.
|
| 2. The boost::stacktrace library (on which the
| standardization was mostly based IIANM) has a
| `safe_dump_to()` function for these cases.
|
| See here: https://github.com/boostorg/stacktrace/blob/devel
| op/include/...
| evmar wrote:
| The code: //a decent amount of this was
| copied/modified from backward.cpp
| (https://github.com/bombela/backward-cpp)
|
| The license on the other side of that link: The
| above copyright notice and this permission notice shall be
| included in all copies or substantial portions of the
| Software.
| londons_explore wrote:
| This is only an issue if the original author chooses to
| enforce. You don't know - this code may have been given special
| permission to omit the notice by the original author. It isn't
| up to random joe to find copyrights they think have been
| violated.
| elsamuko wrote:
| A question: Would it be possible to pass the stacktrace of the
| current thread to another, so that the stacktrace would be
| traceable across threadpools or worker threads?
| soulbadguy wrote:
| I am not sure if i understand the question correctly. But once
| collected, stack traces are just regular object that can be
| passed around thread as other object. It's possible that some
| implementation have references to some stack addresses (like
| for example the address of a function parameter), in which case
| you would need to serialize the stack trace before storing
| them/ moving then another thread.
| mike_hock wrote:
| > once collected, stack traces are just regular object that
| can be passed around thread as other object.
|
| > It's possible that some implementation have references to
| some stack addresses (like for example the address of a
| function parameter), in which case you would need to
| serialize the stack trace before storing them/ moving then
| another thread.
|
| So which of these two mutually exclusive options is it? As I
| understand it, that _was_ the question.
| soulbadguy wrote:
| > So which of these two mutually exclusive options is it?
| As I understand it, that was the question.
|
| Well the stack_entry/stack_trace object can be moved around
| between thread, as in the object itself is copyable and
| movable. However, the handle_type is implementation
| defined, so it might be the case that extracting the
| information out of the object only works on the producing
| thread.
| elsamuko wrote:
| When I debug multithreaded programs, the stacktrace of a
| breakpoint usually ends somewhere in a worker thread. What
| I want is that the worker thread's stacktrace part is
| replaced by the one who put the work into it. Kinda like
| the program wasn't multithreaded at all.
| TylerGlaiel wrote:
| if you actually wanted to you could probably wrap thread
| to pass the stacktrace of the spawning thread into the
| worker thread whenever you spawn a thread and then output
| that upon a crash as well. the library seems pretty
| simple and flexible.
| gpderetta wrote:
| Ah. I guess you can capture the stack trace at task
| creation point, then stitch together a new stack trace by
| replacing the generic common prefix of your worker thread
| trace with the task creation one.
|
| But you can't use the basic_stacktrace container itself
| as it is immutable and not constructibe from a range, so
| you have to roll your own. You should be able to use the
| stacktrace_entries though.
|
| Most importantly, I expect that capturing a stacktrace is
| quite expensive, so you might not be able to do it at
| task creation time, and it is too late to do it later.
| Maybe you want this only in debug mode.
|
| Note I haven't actually tried any if this, it is just
| guesswork.
| elsamuko wrote:
| Exactly this. I didn't try this, and I suppose that some
| low level pointer rewriting would be necessary to do
| this. I'm not sure if it's expensive though, maybe you
| can replace the pointers without resolving the
| stacktrace.
| gpderetta wrote:
| The problem is that to get the stacktrace of the task
| creation you have to traverse the stack at that point in
| time. You can't really do it later. And stack traversal
| using DWARF unwind info, for example, is neither cheap
| nor simple. You might have better luck if you compile
| with frame pointer though.
| soulbadguy wrote:
| I think what you looking for is "task tracing" not really
| stack tracing. The relationship between the task (like
| where was a task added in the thread pool) are not
| reflected in the stacktrace the way you want them. To
| address those, you need to have special handshake between
| the debugger and your task api. You can also instrument
| the "add_task" function call to log every time a task is
| added to you queue and do some some offline stack
| stiching.
| gpderetta wrote:
| As far as I understand, basic_stack trace is just a
| container of stacktrace_entries, which are Regular types.
| So the default distinct-objects type safety rules apply.
|
| You should be able to, for example, collect the stacktace
| on one thread, transport it to another and print it.
| rightbyte wrote:
| This is my favorite macro: "#define WIN32_LEAN_AND_MEAN"
|
| Why is it that even though Github or what ever has cutsie
| unicorns (or whatever it is) as error messages it feels fake and
| contrived while this define just feels like some random dude at
| MS naming it before going off to write Solitaire?
| hoten wrote:
| Don't forget `WIN32_EXTRA_LEAN`! Still no idea what that
| does/did.
|
| For those curious about WIN32_LEAN_AND_MEAN - it reduces
| compile time by not auto-including a number of windows headers:
| https://devblogs.microsoft.com/oldnewthing/20091130-00/?p=15...
| dataflow wrote:
| Do you mean VC_EXTRALEAN? Or is WIN32_EXTRA_LEAN also a
| thing?
| TeMPOraL wrote:
| First time I hear of VC_EXTRALEAN, always used
| WIN32_EXTRA_LEAN.
| [deleted]
| Night_Thastus wrote:
| We've had to use this at my work a couple times. I forget the
| exact reasoning, but IIRC if you're using including parts of
| the Win32 API, you get some things that would stomp on C or C++
| names, which is bad. Macros like that one prevent loading
| things you don't want.
|
| One example was min and max - Win32 includes those which messes
| with trying to use std::min and std::max.
| dataflow wrote:
| There's NOMINMAX, NOGDI, etc. for that.
| LexiMax wrote:
| I've always been a fan of _CRT_SECURE_NO_WARNINGS, personally.
| ghosty141 wrote:
| As always, Raymond Chen wrote about it!
| https://devblogs.microsoft.com/oldnewthing/20091130-00/?p=15...
|
| Its also my favorite btw.
___________________________________________________________________
(page generated 2023-07-03 23:00 UTC)