[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)