Skip to content

Commit 22b9404

Browse files
committed
Optionally print symbolizer markup backtraces.
When the environment LLVM_ENABLE_SYMBOLIZER_MARKUP is set, if llvm-symbolizer fails or is disabled, this change will print a backtrace in llvm-symbolizer markup instead of falling back to in-process symbolization mechanisms. This allows llvm-symbolizer to be run on the output later to produce a high quality backtrace, even for fully-stripped LLVM utilities. Reviewed By: mcgrathr Differential Revision: https://reviews.llvm.org/D139750
1 parent 3415798 commit 22b9404

File tree

6 files changed

+213
-1
lines changed

6 files changed

+213
-1
lines changed

llvm/docs/SymbolizerMarkupFormat.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ text. It's specifically intended not to require sanitizing plain text, such as
3333
the HTML/XML requirement to replace ``<`` with ``&lt;`` and the like.
3434

3535
:doc:`llvm-symbolizer <CommandGuide/llvm-symbolizer>` includes a symbolizing
36-
filter via its ``--filter-markup`` option.
36+
filter via its ``--filter-markup`` option. Also, LLVM utilites emit stack
37+
traces as markup when the ``LLVM_ENABLE_SYMBOLIZER_MARKUP`` environment
38+
variable is set.
3739

3840
Scope and assumptions
3941
=====================

llvm/lib/Support/Signals.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ void llvm::initSignalsOptions() {
7272

7373
constexpr char DisableSymbolizationEnv[] = "LLVM_DISABLE_SYMBOLIZATION";
7474
constexpr char LLVMSymbolizerPathEnv[] = "LLVM_SYMBOLIZER_PATH";
75+
constexpr char EnableSymbolizerMarkupEnv[] = "LLVM_ENABLE_SYMBOLIZER_MARKUP";
7576

7677
// Callbacks to run in signal handler must be lock-free because a signal handler
7778
// could be running as we add new callbacks. We don't add unbounded numbers of
@@ -252,6 +253,25 @@ static bool printSymbolizedStackTrace(StringRef Argv0, void **StackTrace,
252253
return true;
253254
}
254255

256+
static bool printMarkupContext(raw_ostream &OS, const char *MainExecutableName);
257+
258+
LLVM_ATTRIBUTE_USED
259+
static bool printMarkupStackTrace(StringRef Argv0, void **StackTrace, int Depth,
260+
raw_ostream &OS) {
261+
const char *Env = getenv(EnableSymbolizerMarkupEnv);
262+
if (!Env || !*Env)
263+
return false;
264+
265+
std::string MainExecutableName =
266+
sys::fs::exists(Argv0) ? std::string(Argv0)
267+
: sys::fs::getMainExecutable(nullptr, nullptr);
268+
if (!printMarkupContext(OS, MainExecutableName.c_str()))
269+
return false;
270+
for (int I = 0; I < Depth; I++)
271+
OS << format("{{{bt:%d:%#016x}}}\n", I, StackTrace[I]);
272+
return true;
273+
}
274+
255275
// Include the platform-specific parts of this class.
256276
#ifdef LLVM_ON_UNIX
257277
#include "Unix/Signals.inc"

llvm/lib/Support/Unix/Signals.inc

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,117 @@ static bool findModulesAndOffsets(void **StackTrace, int Depth,
510510
dl_iterate_phdr(dl_iterate_phdr_cb, &data);
511511
return true;
512512
}
513+
514+
class DSOMarkupPrinter {
515+
llvm::raw_ostream &OS;
516+
const char *MainExecutableName;
517+
size_t ModuleCount = 0;
518+
bool IsFirst = true;
519+
520+
public:
521+
DSOMarkupPrinter(llvm::raw_ostream &OS, const char *MainExecutableName)
522+
: OS(OS), MainExecutableName(MainExecutableName) {}
523+
524+
/// Print llvm-symbolizer markup describing the layout of the given DSO.
525+
void printDSOMarkup(dl_phdr_info *Info) {
526+
ArrayRef<uint8_t> BuildID = findBuildID(Info);
527+
if (BuildID.empty())
528+
return;
529+
OS << format("{{{module:%d:%s:elf:", ModuleCount,
530+
IsFirst ? MainExecutableName : Info->dlpi_name);
531+
for (uint8_t X : BuildID)
532+
OS << format("%02x", X);
533+
OS << "}}}\n";
534+
535+
for (int I = 0; I < Info->dlpi_phnum; I++) {
536+
const auto *Phdr = &Info->dlpi_phdr[I];
537+
if (Phdr->p_type != PT_LOAD)
538+
continue;
539+
uintptr_t StartAddress = Info->dlpi_addr + Phdr->p_vaddr;
540+
uintptr_t ModuleRelativeAddress = Phdr->p_vaddr;
541+
std::array<char, 4> ModeStr = modeStrFromFlags(Phdr->p_flags);
542+
OS << format("{{{mmap:%#016x:%#x:load:%d:%s:%#016x}}}\n", StartAddress,
543+
Phdr->p_memsz, ModuleCount, &ModeStr[0],
544+
ModuleRelativeAddress);
545+
}
546+
IsFirst = false;
547+
ModuleCount++;
548+
}
549+
550+
/// Callback for use with dl_iterate_phdr. The last dl_iterate_phdr argument
551+
/// must be a pointer to an instance of this class.
552+
static int printDSOMarkup(dl_phdr_info *Info, size_t Size, void *Arg) {
553+
static_cast<DSOMarkupPrinter *>(Arg)->printDSOMarkup(Info);
554+
return 0;
555+
}
556+
557+
// Returns the build ID for the given DSO as an array of bytes. Returns an
558+
// empty array if none could be found.
559+
ArrayRef<uint8_t> findBuildID(dl_phdr_info *Info) {
560+
for (int I = 0; I < Info->dlpi_phnum; I++) {
561+
const auto *Phdr = &Info->dlpi_phdr[I];
562+
if (Phdr->p_type != PT_NOTE)
563+
continue;
564+
565+
ArrayRef<uint8_t> Notes(
566+
reinterpret_cast<const uint8_t *>(Info->dlpi_addr + Phdr->p_vaddr),
567+
Phdr->p_memsz);
568+
while (Notes.size() > 12) {
569+
uint32_t NameSize = *reinterpret_cast<const uint32_t *>(Notes.data());
570+
Notes = Notes.drop_front(4);
571+
uint32_t DescSize = *reinterpret_cast<const uint32_t *>(Notes.data());
572+
Notes = Notes.drop_front(4);
573+
uint32_t Type = *reinterpret_cast<const uint32_t *>(Notes.data());
574+
Notes = Notes.drop_front(4);
575+
576+
ArrayRef<uint8_t> Name = Notes.take_front(NameSize);
577+
auto CurPos = reinterpret_cast<uintptr_t>(Notes.data());
578+
uint32_t BytesUntilDesc =
579+
alignToPowerOf2(CurPos + NameSize, 4) - CurPos;
580+
if (BytesUntilDesc >= Notes.size())
581+
break;
582+
Notes = Notes.drop_front(BytesUntilDesc);
583+
584+
ArrayRef<uint8_t> Desc = Notes.take_front(DescSize);
585+
CurPos = reinterpret_cast<uintptr_t>(Notes.data());
586+
uint32_t BytesUntilNextNote =
587+
alignToPowerOf2(CurPos + DescSize, 4) - CurPos;
588+
if (BytesUntilNextNote > Notes.size())
589+
break;
590+
Notes = Notes.drop_front(BytesUntilNextNote);
591+
592+
if (Type == 3 /*NT_GNU_BUILD_ID*/ && Name.size() >= 3 &&
593+
Name[0] == 'G' && Name[1] == 'N' && Name[2] == 'U')
594+
return Desc;
595+
}
596+
}
597+
return {};
598+
}
599+
600+
// Returns a symbolizer markup string describing the permissions on a DSO
601+
// with the given p_flags.
602+
std::array<char, 4> modeStrFromFlags(uint32_t Flags) {
603+
std::array<char, 4> Mode;
604+
char *Cur = &Mode[0];
605+
if (Flags & PF_R)
606+
*Cur++ = 'r';
607+
if (Flags & PF_W)
608+
*Cur++ = 'w';
609+
if (Flags & PF_X)
610+
*Cur++ = 'x';
611+
*Cur = '\0';
612+
return Mode;
613+
}
614+
};
615+
616+
static bool printMarkupContext(llvm::raw_ostream &OS,
617+
const char *MainExecutableName) {
618+
OS << "{{{reset}}}\n";
619+
DSOMarkupPrinter MP(OS, MainExecutableName);
620+
dl_iterate_phdr(DSOMarkupPrinter::printDSOMarkup, &MP);
621+
return true;
622+
}
623+
513624
#elif ENABLE_BACKTRACES && defined(__APPLE__) && defined(__LP64__)
514625
static bool findModulesAndOffsets(void **StackTrace, int Depth,
515626
const char **Modules, intptr_t *Offsets,
@@ -544,6 +655,7 @@ static bool findModulesAndOffsets(void **StackTrace, int Depth,
544655
}
545656
return true;
546657
}
658+
547659
#else
548660
/// Backtraces are not enabled or we don't yet know how to find all loaded DSOs
549661
/// on this platform.
@@ -553,6 +665,11 @@ static bool findModulesAndOffsets(void **StackTrace, int Depth,
553665
StringSaver &StrPool) {
554666
return false;
555667
}
668+
669+
static bool printMarkupContext(llvm::raw_ostream &OS,
670+
const char *MainExecutableName) {
671+
return false;
672+
}
556673
#endif // ENABLE_BACKTRACES && ... (findModulesAndOffsets variants)
557674

558675
#if ENABLE_BACKTRACES && defined(HAVE__UNWIND_BACKTRACE)
@@ -613,6 +730,8 @@ void llvm::sys::PrintStackTrace(raw_ostream &OS, int Depth) {
613730
// backtrace() for printing a symbolized stack trace.
614731
if (!Depth)
615732
Depth = depth;
733+
if (printMarkupStackTrace(Argv0, StackTrace, Depth, OS))
734+
return;
616735
if (printSymbolizedStackTrace(Argv0, StackTrace, Depth, OS))
617736
return;
618737
OS << "Stack dump without symbol names (ensure you have llvm-symbolizer in "

llvm/lib/Support/Windows/Signals.inc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,11 @@ static bool findModulesAndOffsets(void **StackTrace, int Depth,
302302
return true;
303303
}
304304

305+
static bool printMarkupContext(llvm::raw_ostream &OS,
306+
const char *MainExecutableName) {
307+
return false;
308+
}
309+
305310
static void PrintStackTraceForThread(llvm::raw_ostream &OS, HANDLE hProcess,
306311
HANDLE hThread, STACKFRAME64 &StackFrame,
307312
CONTEXT *Context) {

llvm/unittests/Support/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ add_llvm_unittest(SupportTests
7474
ScaledNumberTest.cpp
7575
ScopedPrinterTest.cpp
7676
SHA256.cpp
77+
SignalsTest.cpp
7778
SourceMgrTest.cpp
7879
SpecialCaseListTest.cpp
7980
SuffixTreeTest.cpp
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//===-- llvm/unittest/Support/SignalsTest.cpp - Signals unit tests --------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
///
9+
/// \file
10+
/// This file contains unit tests for Signals.cpp and Signals.inc.
11+
///
12+
//===----------------------------------------------------------------------===//
13+
14+
#include "llvm/Support/Signals.h"
15+
#include "llvm/ADT/ScopeExit.h"
16+
#include "llvm/Config/config.h"
17+
#include "gmock/gmock.h"
18+
#include "gtest/gtest-matchers.h"
19+
#include "gtest/gtest.h"
20+
21+
using namespace llvm;
22+
using namespace llvm::sys;
23+
using testing::MatchesRegex;
24+
using testing::Not;
25+
26+
#define TAG_BEGIN "\\{\\{\\{"
27+
#define TAG_END "\\}\\}\\}"
28+
29+
#if defined(HAVE_BACKTRACE) && ENABLE_BACKTRACES && HAVE_LINK_H && \
30+
(defined(__linux__) || defined(__FreeBSD__) || \
31+
defined(__FreeBSD_kernel__) || defined(__NetBSD__))
32+
TEST(SignalsTest, PrintsSymbolizerMarkup) {
33+
auto Exit =
34+
make_scope_exit([]() { unsetenv("LLVM_ENABLE_SYMBOLIZER_MARKUP"); });
35+
setenv("LLVM_ENABLE_SYMBOLIZER_MARKUP", "1", 1);
36+
std::string Res;
37+
raw_string_ostream RawStream(Res);
38+
PrintStackTrace(RawStream);
39+
EXPECT_THAT(Res, MatchesRegex(TAG_BEGIN "reset" TAG_END ".*"));
40+
// Module line for main binary
41+
EXPECT_THAT(Res,
42+
MatchesRegex(".*" TAG_BEGIN
43+
"module:0:[^:]*SupportTests:elf:[0-9a-f]+" TAG_END
44+
".*"));
45+
// Text segment for main binary
46+
EXPECT_THAT(
47+
Res,
48+
MatchesRegex(".*" TAG_BEGIN
49+
"mmap:0x[0-9a-f]+:0x[0-9a-f]+:load:0:rx:0x[0-9a-f]+" TAG_END
50+
".*"));
51+
// Backtrace line
52+
EXPECT_THAT(Res, MatchesRegex(".*" TAG_BEGIN "bt:0:0x[0-9a-f]+"
53+
".*"));
54+
}
55+
56+
TEST(SignalsTest, SymbolizerMarkupDisabled) {
57+
auto Exit = make_scope_exit([]() { unsetenv("LLVM_DISABLE_SYMBOLIZATION"); });
58+
setenv("LLVM_DISABLE_SYMBOLIZATION", "1", 1);
59+
std::string Res;
60+
raw_string_ostream RawStream(Res);
61+
PrintStackTrace(RawStream);
62+
EXPECT_THAT(Res, Not(MatchesRegex(TAG_BEGIN "reset" TAG_END ".*")));
63+
}
64+
65+
#endif // defined(HAVE_BACKTRACE) && ...

0 commit comments

Comments
 (0)