Merge "main: log stack traces for std::terminate" into main
This commit is contained in:
commit
b6038e988d
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(
|
libmain_sources = files(
|
||||||
'common-args.cc',
|
'common-args.cc',
|
||||||
|
'crash-handler.cc',
|
||||||
'loggers.cc',
|
'loggers.cc',
|
||||||
'progress-bar.cc',
|
'progress-bar.cc',
|
||||||
'shared.cc',
|
'shared.cc',
|
||||||
|
@ -8,6 +9,7 @@ libmain_sources = files(
|
||||||
|
|
||||||
libmain_headers = files(
|
libmain_headers = files(
|
||||||
'common-args.hh',
|
'common-args.hh',
|
||||||
|
'crash-handler.hh',
|
||||||
'loggers.hh',
|
'loggers.hh',
|
||||||
'progress-bar.hh',
|
'progress-bar.hh',
|
||||||
'shared.hh',
|
'shared.hh',
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#include "crash-handler.hh"
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
#include "shared.hh"
|
#include "shared.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
@ -118,6 +119,8 @@ static void sigHandler(int signo) { }
|
||||||
|
|
||||||
void initNix()
|
void initNix()
|
||||||
{
|
{
|
||||||
|
registerCrashHandler();
|
||||||
|
|
||||||
/* Turn on buffering for cerr. */
|
/* Turn on buffering for cerr. */
|
||||||
static char buf[1024];
|
static char buf[1024];
|
||||||
std::cerr.rdbuf()->pubsetbuf(buf, sizeof(buf));
|
std::cerr.rdbuf()->pubsetbuf(buf, sizeof(buf));
|
||||||
|
@ -335,12 +338,15 @@ int handleExceptions(const std::string & programName, std::function<void()> fun)
|
||||||
} catch (BaseError & e) {
|
} catch (BaseError & e) {
|
||||||
logError(e.info());
|
logError(e.info());
|
||||||
return e.info().status;
|
return e.info().status;
|
||||||
} catch (std::bad_alloc & e) {
|
} catch (const std::bad_alloc & e) {
|
||||||
printError(error + "out of memory");
|
printError(error + "out of memory");
|
||||||
return 1;
|
return 1;
|
||||||
} catch (std::exception & e) {
|
} catch (const std::exception & e) {
|
||||||
printError(error + e.what());
|
// Random exceptions bubbling into main are cause for bug reports, crash
|
||||||
return 1;
|
std::terminate();
|
||||||
|
} catch (...) {
|
||||||
|
// Explicitly do not tolerate non-std exceptions escaping.
|
||||||
|
std::terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
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();
|
void detectStackOverflow();
|
||||||
|
|
||||||
|
|
|
@ -443,7 +443,7 @@ static bool initLibStoreDone = false;
|
||||||
void assertLibStoreInitialized() {
|
void assertLibStoreInitialized() {
|
||||||
if (!initLibStoreDone) {
|
if (!initLibStoreDone) {
|
||||||
printError("The program must call nix::initNix() before calling any libstore library functions.");
|
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',
|
protocol : 'gtest',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
libmain_tests_sources = files(
|
||||||
|
'libmain/crash.cc',
|
||||||
|
'libmain/progress-bar.cc',
|
||||||
|
)
|
||||||
|
|
||||||
libmain_tester = executable(
|
libmain_tester = executable(
|
||||||
'liblixmain-tests',
|
'liblixmain-tests',
|
||||||
files('libmain/progress-bar.cc'),
|
libmain_tests_sources,
|
||||||
dependencies : [
|
dependencies : [
|
||||||
liblixmain,
|
liblixmain,
|
||||||
liblixexpr,
|
liblixexpr,
|
||||||
|
|
Loading…
Reference in a new issue