forked from lix-project/lix
Merge "main: log stack traces for std::terminate" into main
This commit is contained in:
commit
b6038e988d
9 changed files with 164 additions and 7 deletions
26
doc/manual/rl-next/stack-traces.md
Normal file
26
doc/manual/rl-next/stack-traces.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
synopsis: "Some Lix crashes now produce reporting instructions and a stack trace, then abort"
|
||||
cls: [1854]
|
||||
category: Improvements
|
||||
credits: jade
|
||||
---
|
||||
|
||||
Lix, being a C++ program, can crash in a few kinds of ways.
|
||||
It can obviously do a memory access violation, which will generate a core dump and thus be relatively debuggable.
|
||||
But, worse, it could throw an unhandled exception, and, in the past, we would just show the message but not where it comes from, in spite of this always being a bug, since we expect all such errors to be translated to a Lix specific error.
|
||||
Now the latter kind of bug should print reporting instructions, a rudimentary stack trace and (depending on system configuration) generate a core dump.
|
||||
|
||||
Sample output:
|
||||
|
||||
```
|
||||
Lix crashed. This is a bug. We would appreciate if you report it along with what caused it at https://git.lix.systems/lix-project/lix/issues with the following information included:
|
||||
|
||||
Exception: std::runtime_error: test exception
|
||||
Stack trace:
|
||||
0# nix::printStackTrace() in /home/jade/lix/lix3/build/src/nix/../libutil/liblixutil.so
|
||||
1# 0x000073C9862331F2 in /home/jade/lix/lix3/build/src/nix/../libmain/liblixmain.so
|
||||
2# 0x000073C985F2E21A in /nix/store/p44qan69linp3ii0xrviypsw2j4qdcp2-gcc-13.2.0-lib/lib/libstdc++.so.6
|
||||
3# 0x000073C985F2E285 in /nix/store/p44qan69linp3ii0xrviypsw2j4qdcp2-gcc-13.2.0-lib/lib/libstdc++.so.6
|
||||
4# nix::handleExceptions(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::function<void ()>) in /home/jade/lix/lix3/build/src/nix/../libmain/liblixmain.so
|
||||
...
|
||||
```
|
41
src/libmain/crash-handler.cc
Normal file
41
src/libmain/crash-handler.cc
Normal file
|
@ -0,0 +1,41 @@
|
|||
#include "crash-handler.hh"
|
||||
#include "fmt.hh"
|
||||
|
||||
#include <boost/core/demangle.hpp>
|
||||
#include <exception>
|
||||
|
||||
namespace nix {
|
||||
|
||||
namespace {
|
||||
void onTerminate()
|
||||
{
|
||||
std::cerr << "Lix crashed. This is a bug. We would appreciate if you report it along with what caused it at https://git.lix.systems/lix-project/lix/issues with the following information included:\n\n";
|
||||
try {
|
||||
std::exception_ptr eptr = std::current_exception();
|
||||
if (eptr) {
|
||||
std::rethrow_exception(eptr);
|
||||
} else {
|
||||
std::cerr << "std::terminate() called without exception\n";
|
||||
}
|
||||
} catch (const std::exception & ex) {
|
||||
std::cerr << "Exception: " << boost::core::demangle(typeid(ex).name()) << ": " << ex.what() << "\n";
|
||||
} catch (...) {
|
||||
std::cerr << "Unknown exception! Spooky.\n";
|
||||
}
|
||||
|
||||
std::cerr << "Stack trace:\n";
|
||||
nix::printStackTrace();
|
||||
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
void registerCrashHandler()
|
||||
{
|
||||
// DO NOT use this for signals. Boost stacktrace is very much not
|
||||
// async-signal-safe, and in a world with ASLR, addr2line is pointless.
|
||||
//
|
||||
// If you want signals, set up a minidump system and do it out-of-process.
|
||||
std::set_terminate(onTerminate);
|
||||
}
|
||||
}
|
21
src/libmain/crash-handler.hh
Normal file
21
src/libmain/crash-handler.hh
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
/// @file Crash handler for Lix that prints back traces (hopefully in instances where it is not just going to crash the process itself).
|
||||
/*
|
||||
* Author's note: This will probably be partially/fully supplanted by a
|
||||
* minidump writer like the following once we get our act together on crashes a
|
||||
* little bit more:
|
||||
* https://github.com/rust-minidump/minidump-writer
|
||||
* https://github.com/EmbarkStudios/crash-handling
|
||||
* (out of process implementation *should* be able to be done on-demand)
|
||||
*
|
||||
* Such an out-of-process implementation could then both make minidumps and
|
||||
* print stack traces for arbitrarily messed-up process states such that we can
|
||||
* safely give out backtraces for SIGSEGV and other deadly signals.
|
||||
*/
|
||||
|
||||
namespace nix {
|
||||
|
||||
/** Registers the Lix crash handler for std::terminate (currently; will support more crashes later). See also detectStackOverflow(). */
|
||||
void registerCrashHandler();
|
||||
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
libmain_sources = files(
|
||||
'common-args.cc',
|
||||
'crash-handler.cc',
|
||||
'loggers.cc',
|
||||
'progress-bar.cc',
|
||||
'shared.cc',
|
||||
|
@ -8,6 +9,7 @@ libmain_sources = files(
|
|||
|
||||
libmain_headers = files(
|
||||
'common-args.hh',
|
||||
'crash-handler.hh',
|
||||
'loggers.hh',
|
||||
'progress-bar.hh',
|
||||
'shared.hh',
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "crash-handler.hh"
|
||||
#include "globals.hh"
|
||||
#include "shared.hh"
|
||||
#include "store-api.hh"
|
||||
|
@ -118,6 +119,8 @@ static void sigHandler(int signo) { }
|
|||
|
||||
void initNix()
|
||||
{
|
||||
registerCrashHandler();
|
||||
|
||||
/* Turn on buffering for cerr. */
|
||||
static char buf[1024];
|
||||
std::cerr.rdbuf()->pubsetbuf(buf, sizeof(buf));
|
||||
|
@ -335,12 +338,15 @@ int handleExceptions(const std::string & programName, std::function<void()> fun)
|
|||
} catch (BaseError & e) {
|
||||
logError(e.info());
|
||||
return e.info().status;
|
||||
} catch (std::bad_alloc & e) {
|
||||
} catch (const std::bad_alloc & e) {
|
||||
printError(error + "out of memory");
|
||||
return 1;
|
||||
} catch (std::exception & e) {
|
||||
printError(error + e.what());
|
||||
return 1;
|
||||
} catch (const std::exception & e) {
|
||||
// Random exceptions bubbling into main are cause for bug reports, crash
|
||||
std::terminate();
|
||||
} catch (...) {
|
||||
// Explicitly do not tolerate non-std exceptions escaping.
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -111,7 +111,7 @@ struct PrintFreed
|
|||
|
||||
|
||||
/**
|
||||
* Install a SIGSEGV handler to detect stack overflows.
|
||||
* Install a SIGSEGV handler to detect stack overflows. See also registerCrashHandler().
|
||||
*/
|
||||
void detectStackOverflow();
|
||||
|
||||
|
|
|
@ -443,7 +443,7 @@ static bool initLibStoreDone = false;
|
|||
void assertLibStoreInitialized() {
|
||||
if (!initLibStoreDone) {
|
||||
printError("The program must call nix::initNix() before calling any libstore library functions.");
|
||||
abort();
|
||||
std::terminate();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
56
tests/unit/libmain/crash.cc
Normal file
56
tests/unit/libmain/crash.cc
Normal file
|
@ -0,0 +1,56 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include "crash-handler.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
class OopsException : public std::exception
|
||||
{
|
||||
const char * msg;
|
||||
|
||||
public:
|
||||
OopsException(const char * msg) : msg(msg) {}
|
||||
const char * what() const noexcept override
|
||||
{
|
||||
return msg;
|
||||
}
|
||||
};
|
||||
|
||||
void causeCrashForTesting(std::function<void()> fixture)
|
||||
{
|
||||
registerCrashHandler();
|
||||
std::cerr << "time to crash\n";
|
||||
try {
|
||||
fixture();
|
||||
} catch (...) {
|
||||
std::terminate();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CrashHandler, exceptionName)
|
||||
{
|
||||
ASSERT_DEATH(
|
||||
causeCrashForTesting([]() { throw OopsException{"lol oops"}; }),
|
||||
"time to crash\nLix crashed.*OopsException: lol oops"
|
||||
);
|
||||
}
|
||||
|
||||
TEST(CrashHandler, unknownTerminate)
|
||||
{
|
||||
ASSERT_DEATH(
|
||||
causeCrashForTesting([]() { std::terminate(); }),
|
||||
"time to crash\nLix crashed.*std::terminate\\(\\) called without exception"
|
||||
);
|
||||
}
|
||||
|
||||
TEST(CrashHandler, nonStdException)
|
||||
{
|
||||
ASSERT_DEATH(
|
||||
causeCrashForTesting([]() {
|
||||
// NOLINTNEXTLINE(hicpp-exception-baseclass): intentional
|
||||
throw 4;
|
||||
}),
|
||||
"time to crash\nLix crashed.*Unknown exception! Spooky\\."
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -264,9 +264,14 @@ test(
|
|||
protocol : 'gtest',
|
||||
)
|
||||
|
||||
libmain_tests_sources = files(
|
||||
'libmain/crash.cc',
|
||||
'libmain/progress-bar.cc',
|
||||
)
|
||||
|
||||
libmain_tester = executable(
|
||||
'liblixmain-tests',
|
||||
files('libmain/progress-bar.cc'),
|
||||
libmain_tests_sources,
|
||||
dependencies : [
|
||||
liblixmain,
|
||||
liblixexpr,
|
||||
|
|
Loading…
Reference in a new issue