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