diff --git a/llvm/lib/Support/Signals.cpp b/llvm/lib/Support/Signals.cpp --- a/llvm/lib/Support/Signals.cpp +++ b/llvm/lib/Support/Signals.cpp @@ -72,6 +72,7 @@ constexpr char DisableSymbolizationEnv[] = "LLVM_DISABLE_SYMBOLIZATION"; constexpr char LLVMSymbolizerPathEnv[] = "LLVM_SYMBOLIZER_PATH"; +constexpr char EnableSymbolizerMarkupEnv[] = "LLVM_ENABLE_SYMBOLIZER_MARKUP"; // Callbacks to run in signal handler must be lock-free because a signal handler // could be running as we add new callbacks. We don't add unbounded numbers of @@ -252,6 +253,25 @@ return true; } +static bool printMarkupContext(raw_ostream &OS, const char *MainExecutableName); + +LLVM_ATTRIBUTE_USED +static bool printMarkupStackTrace(StringRef Argv0, void **StackTrace, int Depth, + raw_ostream &OS) { + const char *Env = getenv(EnableSymbolizerMarkupEnv); + if (!Env || !*Env) + return false; + + std::string MainExecutableName = + sys::fs::exists(Argv0) ? std::string(Argv0) + : sys::fs::getMainExecutable(nullptr, nullptr); + if (!printMarkupContext(OS, MainExecutableName.c_str())) + return false; + for (int I = 0; I < Depth; I++) + OS << format("{{{bt:%d:%#016x}}}\n", I, StackTrace[I]); + return true; +} + // Include the platform-specific parts of this class. #ifdef LLVM_ON_UNIX #include "Unix/Signals.inc" diff --git a/llvm/lib/Support/Unix/Signals.inc b/llvm/lib/Support/Unix/Signals.inc --- a/llvm/lib/Support/Unix/Signals.inc +++ b/llvm/lib/Support/Unix/Signals.inc @@ -509,6 +509,115 @@ dl_iterate_phdr(dl_iterate_phdr_cb, &data); return true; } + +class DSOMarkupPrinter { +public: + DSOMarkupPrinter(llvm::raw_ostream &OS, const char *MainExecutableName) : + OS(OS), MainExecutableName(MainExecutableName) {} + + /// Print llvm-symbolizer markup describing the layout of the given DSO. + void printDSOMarkup(dl_phdr_info *Info) { + ArrayRef BuildID = findBuildID(Info); + if (BuildID.empty()) + return; + OS << format("{{{module:%d:%s:elf:", ModuleCount, + IsFirst ? MainExecutableName : Info->dlpi_name); + for (uint8_t X : BuildID) + OS << format("%02x", X); + OS << "}}}\n"; + + for (int I = 0; I < Info->dlpi_phnum; I++) { + const auto *Phdr = &Info->dlpi_phdr[I]; + if (Phdr->p_type != PT_LOAD) + continue; + uintptr_t StartAddress = Info->dlpi_addr + Phdr->p_vaddr; + uintptr_t ModuleRelativeAddress = Phdr->p_vaddr; + char ModeStr[4]; + fillModeStr(Phdr->p_flags, ModeStr); + OS << format("{{{mmap:%#016x:%#x:load:%d:%s:%#016x}}}\n", + StartAddress, Phdr->p_memsz, ModuleCount, (char*)ModeStr, + ModuleRelativeAddress); + } + IsFirst = false; + ModuleCount++; + } + + /// Callback for use with dl_iterate_phdr. The last dl_iterate_phdr argument + /// must be a pointer to an instance of this class. + static int printDSOMarkup(dl_phdr_info *Info, size_t Size, void *Arg) { + static_cast(Arg)->printDSOMarkup(Info); + return 0; + } + + // Returns the build ID for the given DSO as an array of bytes. Returns an + // empty array if none could be found. + ArrayRef findBuildID(dl_phdr_info *Info) { + for (int I = 0; I < Info->dlpi_phnum; I++) { + const auto *Phdr = &Info->dlpi_phdr[I]; + if (Phdr->p_type != PT_NOTE) + continue; + + ArrayRef Notes( + (const uint8_t *)(Info->dlpi_addr + Phdr->p_vaddr), + Phdr->p_memsz); + while (Notes.size() > 12) { + uint32_t NameSize = *(const uint32_t *)(Notes.data()); + Notes = Notes.drop_front(4); + uint32_t DescSize = *(const uint32_t *)(Notes.data()); + Notes = Notes.drop_front(4); + uint32_t Type = *(const uint32_t *)(Notes.data()); + Notes = Notes.drop_front(4); + + ArrayRef Name = Notes.take_front(NameSize); + uint32_t AmtToSkip = + alignToPowerOf2((uintptr_t)Notes.data() + NameSize, 4) - + (uintptr_t)Notes.data(); + if (AmtToSkip >= Notes.size()) + break; + Notes = Notes.drop_front(AmtToSkip); + + ArrayRef Desc = Notes.take_front(DescSize); + AmtToSkip = alignToPowerOf2((uintptr_t)Notes.data() + DescSize, 4) - + (uintptr_t)Notes.data(); + if (AmtToSkip > Notes.size()) + break; + Notes = Notes.drop_front(AmtToSkip); + + if (Type == 3/*NT_GNU_BUILD_ID*/ && Name.size() >= 3 && + Name[0] == 'G' && Name[1] == 'N' && Name[2] == 'U') + return Desc; + } + } + return {}; + } + + // Produces a symbolizer markup string describing the permissions on a DSO + // with the given p_flags. The incoming pointer must point to an object of at + // least 4 bytes. + void fillModeStr(uint32_t Flags, char *Str) { + if (Flags & PF_R) + *Str++ = 'r'; + if (Flags & PF_W) + *Str++ = 'w'; + if (Flags & PF_X) + *Str++ = 'x'; + *Str = '\0'; + } + +private: + llvm::raw_ostream &OS; + const char *MainExecutableName; + size_t ModuleCount = 0; + bool IsFirst = true; +}; + +static bool printMarkupContext(llvm::raw_ostream &OS, + const char *MainExecutableName) { + OS << "{{{reset}}}\n"; + DSOMarkupPrinter MP(OS, MainExecutableName); + dl_iterate_phdr(DSOMarkupPrinter::printDSOMarkup, &MP); + return true; +} #else /// This platform does not have dl_iterate_phdr, so we do not yet know how to /// find all loaded DSOs. @@ -518,6 +627,11 @@ StringSaver &StrPool) { return false; } + +static bool printMarkupContext(llvm::raw_ostream &OS, + const char *MainExecutableName) { + return false; +} #endif // defined(HAVE_BACKTRACE) && ENABLE_BACKTRACES && ... #if ENABLE_BACKTRACES && defined(HAVE__UNWIND_BACKTRACE) @@ -578,6 +692,8 @@ // backtrace() for printing a symbolized stack trace. if (!Depth) Depth = depth; + if (printMarkupStackTrace(Argv0, StackTrace, Depth, OS)) + return; if (printSymbolizedStackTrace(Argv0, StackTrace, Depth, OS)) return; OS << "Stack dump without symbol names (ensure you have llvm-symbolizer in " diff --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt --- a/llvm/unittests/Support/CMakeLists.txt +++ b/llvm/unittests/Support/CMakeLists.txt @@ -74,6 +74,7 @@ ScaledNumberTest.cpp ScopedPrinterTest.cpp SHA256.cpp + SignalsTest.cpp SourceMgrTest.cpp SpecialCaseListTest.cpp SuffixTreeTest.cpp diff --git a/llvm/unittests/Support/SignalsTest.cpp b/llvm/unittests/Support/SignalsTest.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/Support/SignalsTest.cpp @@ -0,0 +1,65 @@ +//===-- llvm/unittest/Support/SignalsTest.cpp - Signals unit tests --------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains unit tests for Signals.cpp and Signals.inc. +/// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/Signals.h" +#include "llvm/ADT/ScopeExit.h" +#include "llvm/Config/config.h" +#include "gmock/gmock.h" +#include "gtest/gtest-matchers.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::sys; +using testing::MatchesRegex; +using testing::Not; + +#define TAG_BEGIN "\\{\\{\\{" +#define TAG_END "\\}\\}\\}" + +#if defined(HAVE_BACKTRACE) && ENABLE_BACKTRACES && HAVE_LINK_H && \ + (defined(__linux__) || defined(__FreeBSD__) || \ + defined(__FreeBSD_kernel__) || defined(__NetBSD__)) +TEST(SignalsTest, PrintsSymbolizerMarkup) { + auto Exit = + make_scope_exit([]() { unsetenv("LLVM_ENABLE_SYMBOLIZER_MARKUP"); }); + setenv("LLVM_ENABLE_SYMBOLIZER_MARKUP", "1", 1); + std::string Res; + raw_string_ostream RawStream(Res); + PrintStackTrace(RawStream); + EXPECT_THAT(Res, MatchesRegex(TAG_BEGIN "reset" TAG_END ".*")); + // Module line for main binary + EXPECT_THAT(Res, + MatchesRegex(".*" TAG_BEGIN + "module:0:[^:]*SupportTests:elf:[0-9a-f]+" TAG_END + ".*")); + // Text segment for main binary + EXPECT_THAT( + Res, + MatchesRegex(".*" TAG_BEGIN + "mmap:0x[0-9a-f]+:0x[0-9a-f]+:load:0:rx:0x[0-9a-f]+" TAG_END + ".*")); + // Backtrace line + EXPECT_THAT(Res, MatchesRegex(".*" TAG_BEGIN "bt:0:0x[0-9a-f]+" + ".*")); +} + +TEST(SignalsTest, SymbolizerMarkupDisabled) { + auto Exit = make_scope_exit([]() { unsetenv("LLVM_DISABLE_SYMBOLIZATION"); }); + setenv("LLVM_DISABLE_SYMBOLIZATION", "1", 1); + std::string Res; + raw_string_ostream RawStream(Res); + PrintStackTrace(RawStream); + EXPECT_THAT(Res, Not(MatchesRegex(TAG_BEGIN "reset" TAG_END ".*"))); +} + +#endif // defined(HAVE_BACKTRACE) && ...