Skip to content

Commit d7361d9

Browse files
authored
Install an crash handler showing a stacktrace and useful information. (#890)
1 parent f034fed commit d7361d9

2 files changed

Lines changed: 103 additions & 5 deletions

File tree

lib/CppInterOp/CppInterOp.cpp

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@
6464
#include "llvm/Support/FileSystem.h"
6565
#include "llvm/Support/ManagedStatic.h"
6666
#include "llvm/Support/Path.h"
67+
#include "llvm/Support/Process.h"
68+
#include "llvm/Support/Signals.h"
6769
#include "llvm/Support/TargetSelect.h"
6870
#include "llvm/Support/raw_ostream.h"
6971
#include "llvm/TargetParser/Host.h"
@@ -79,6 +81,7 @@
7981
#include <iterator>
8082
#include <map>
8183
#include <memory>
84+
#include <mutex>
8285
#include <set>
8386
#include <sstream>
8487
#include <stack>
@@ -163,24 +166,67 @@ struct InterpreterInfo {
163166
InterpreterInfo& operator=(const InterpreterInfo&) = delete;
164167
};
165168

169+
static void DefaultProcessCrashHandler(void*);
166170
// Function-static storage for interpreters
167171
static std::deque<InterpreterInfo>& GetInterpreters() {
172+
// static int FakeArgc = 1;
173+
// static const std::string VersionStr = GetVersion();
174+
// static const char* ArgvBuffer[] = {VersionStr.c_str(), nullptr};
175+
// static const char** FakeArgv = ArgvBuffer;
176+
// static llvm::InitLLVM X(FakeArgc, FakeArgv);
177+
// Cannot be a llvm::ManagedStatic because X will call shutdown which will
178+
// trigger destruction on llvm::ManagedStatics and the destruction of the
179+
// InterpreterInfos require to have llvm around.
180+
// FIXME: Currently we never call llvm::llvm_shutdown and sInterpreters leaks.
168181
static llvm::ManagedStatic<std::deque<InterpreterInfo>> sInterpreters;
169-
static bool ProcessInitialized = false;
182+
static std::once_flag ProcessInitialized;
183+
std::call_once(ProcessInitialized, []() {
184+
llvm::sys::PrintStackTraceOnErrorSignal("CppInterOp");
170185

171-
if (!ProcessInitialized) {
172186
// Initialize all targets (required for device offloading)
173187
llvm::InitializeAllTargetInfos();
174188
llvm::InitializeAllTargets();
175189
llvm::InitializeAllTargetMCs();
176190
llvm::InitializeAllAsmParsers();
177191
llvm::InitializeAllAsmPrinters();
178-
ProcessInitialized = true;
179-
}
192+
193+
llvm::sys::AddSignalHandler(DefaultProcessCrashHandler, /*Cookie=*/nullptr);
194+
// std::atexit(llvm::llvm_shutdown);
195+
});
180196

181197
return *sInterpreters;
182198
}
183199

200+
// Global crash handler for the entire process
201+
static void DefaultProcessCrashHandler(void*) {
202+
// Access the static deque via the getter
203+
std::deque<InterpreterInfo>& Interps = GetInterpreters();
204+
205+
llvm::errs() << "\n**************************************************\n";
206+
llvm::errs() << " CppInterOp CRASH DETECTED\n";
207+
208+
if (!Interps.empty()) {
209+
llvm::errs() << " Active Interpreters:\n";
210+
for (const auto& Info : Interps) {
211+
if (Info.Interpreter)
212+
llvm::errs() << " - " << Info.Interpreter << "\n";
213+
}
214+
}
215+
216+
llvm::errs() << "**************************************************\n";
217+
llvm::errs().flush();
218+
219+
// Print backtrace (includes JIT symbols if registered)
220+
llvm::sys::PrintStackTrace(llvm::errs());
221+
222+
llvm::errs() << "**************************************************\n";
223+
llvm::errs().flush();
224+
225+
// The process must actually terminate for EXPECT_DEATH to pass.
226+
// We use _exit to avoid calling atexit() handlers which might be corrupted.
227+
llvm::sys::Process::Exit(/*RetCode=*/1, /*NoCleanup=*/false);
228+
}
229+
184230
static void RegisterInterpreter(compat::Interpreter* I, bool Owned) {
185231
std::deque<InterpreterInfo>& Interps = GetInterpreters();
186232
Interps.emplace_back(I, Owned);

unittests/CppInterOp/InterpreterTest.cpp

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "gtest/gtest.h"
2626

2727
#include <algorithm>
28+
#include <csignal>
2829

2930
using ::testing::StartsWith;
3031

@@ -108,7 +109,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_DeleteInterpreter) {
108109

109110
EXPECT_EQ(I3, Cpp::GetInterpreter()) << "I3 is not active";
110111

111-
EXPECT_TRUE(Cpp::DeleteInterpreter(nullptr));
112+
EXPECT_TRUE(Cpp::DeleteInterpreter(/*I=*/nullptr));
112113
EXPECT_EQ(I2, Cpp::GetInterpreter());
113114

114115
auto* I4 = reinterpret_cast<void*>(static_cast<std::uintptr_t>(~0U));
@@ -456,3 +457,54 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_ExternalInterpreter) {
456457
delete ExtInterp;
457458
#endif
458459
}
460+
461+
// Verify the basic crash banner and Active Interpreter reporting
462+
#ifdef GTEST_HAS_DEATH_TEST
463+
TYPED_TEST(CPPINTEROP_TEST_MODE, SignalHandler_BasicBanner) {
464+
// Ensure a clean registry for each JIT configuration
465+
466+
// FIXME: Uncomment after resolving compiler-research/CppInterOp#887
467+
468+
// while (Cpp::GetInterpreter())
469+
// Cpp::DeleteInterpreter(/*I=*/nullptr);
470+
471+
// EXPECT_FALSE(Cpp::GetInterpreter()) << "Failed to delete all interpreters";
472+
473+
// // Create an interpreter (this calls RegisterInterpreter internally)
474+
// TInterp_t I = TestFixture::CreateInterpreter();
475+
// ASSERT_NE(I, nullptr);
476+
477+
// We expect the banner to appear in stderr when the process dies
478+
std::string ExpectedMsg = "CppInterOp CRASH DETECTED";
479+
#ifdef _WIN32
480+
// FIXME: Windows says 'Actual msg:' without maybe capturing the message.
481+
ExpectedMsg = "";
482+
#endif //_WIN32
483+
484+
EXPECT_DEATH(
485+
{
486+
// Trigger a synchronous signal
487+
raise(SIGABRT);
488+
},
489+
ExpectedMsg);
490+
}
491+
#endif // GTEST_HAS_DEATH_TEST
492+
493+
// Verify that the handler correctly lists multiple interpreters
494+
#ifdef GTEST_HAS_DEATH_TEST
495+
TYPED_TEST(CPPINTEROP_TEST_MODE, SignalHandler_MultipleInterpreters) {
496+
ASSERT_NE(TestFixture::CreateInterpreter(), nullptr);
497+
ASSERT_NE(TestFixture::CreateInterpreter(), nullptr);
498+
499+
// The handler iterates through the deque and prints the pointers
500+
501+
// We check for the "Active Interpreters:" header and the list format
502+
std::string ExpectedMsg = "Active Interpreters:.*- 0x";
503+
#ifdef _WIN32
504+
// FIXME: Windows says 'Actual msg:' without maybe capturing the message.
505+
ExpectedMsg = "";
506+
#endif //_WIN32
507+
508+
EXPECT_DEATH({ raise(SIGSEGV); }, ExpectedMsg);
509+
}
510+
#endif // GTEST_HAS_DEATH_TEST

0 commit comments

Comments
 (0)