diff --git a/compiler-rt/lib/sanitizer_common/CMakeLists.txt b/compiler-rt/lib/sanitizer_common/CMakeLists.txt index 61e832a30eb37..fb7584c298a1c 100644 --- a/compiler-rt/lib/sanitizer_common/CMakeLists.txt +++ b/compiler-rt/lib/sanitizer_common/CMakeLists.txt @@ -88,6 +88,7 @@ set(SANITIZER_SYMBOLIZER_SOURCES sanitizer_symbolizer_libcdep.cpp sanitizer_symbolizer_mac.cpp sanitizer_symbolizer_markup.cpp + sanitizer_symbolizer_markup_fuchsia.cpp sanitizer_symbolizer_posix_libcdep.cpp sanitizer_symbolizer_report.cpp sanitizer_symbolizer_report_fuchsia.cpp @@ -195,6 +196,7 @@ set(SANITIZER_IMPL_HEADERS sanitizer_symbolizer_internal.h sanitizer_symbolizer_libbacktrace.h sanitizer_symbolizer_mac.h + sanitizer_symbolizer_markup.h sanitizer_syscall_generic.inc sanitizer_syscall_linux_aarch64.inc sanitizer_syscall_linux_arm.inc diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_flags.inc b/compiler-rt/lib/sanitizer_common/sanitizer_flags.inc index 949bdbd148b6b..7d0c7c4b63c91 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_flags.inc +++ b/compiler-rt/lib/sanitizer_common/sanitizer_flags.inc @@ -275,3 +275,7 @@ COMMON_FLAG(bool, test_only_emulate_no_memorymap, false, // program. COMMON_FLAG(bool, test_only_replace_dlopen_main_program, false, "TEST ONLY replace dlopen(
,...) with dlopen(NULL)") + +COMMON_FLAG(bool, enable_symbolizer_markup, SANITIZER_FUCHSIA, + "Use sanitizer symbolizer markup, available on Linux " + "and always set true for fuchsia.") diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace_printer.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace_printer.cpp index 88f186b9c20c1..748d832ccc211 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace_printer.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace_printer.cpp @@ -16,6 +16,7 @@ #include "sanitizer_file.h" #include "sanitizer_flags.h" #include "sanitizer_fuchsia.h" +#include "sanitizer_symbolizer_markup.h" namespace __sanitizer { @@ -62,6 +63,9 @@ const char *StackTracePrinter::StripFunctionName(const char *function) { #if !SANITIZER_SYMBOLIZER_MARKUP StackTracePrinter *StackTracePrinter::NewStackTracePrinter() { + if (common_flags()->enable_symbolizer_markup) + return new (GetGlobalLowLevelAllocator()) MarkupStackTracePrinter(); + return new (GetGlobalLowLevelAllocator()) FormattedStackTracePrinter(); } diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer.h b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer.h index 7fb7928dce0d8..82cd9bc227916 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer.h @@ -154,6 +154,8 @@ class Symbolizer final { void InvalidateModuleList(); + const ListOfModules &GetRefreshedListOfModules(); + private: // GetModuleNameAndOffsetForPC has to return a string to the caller. // Since the corresponding module might get unloaded later, we should create diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_libcdep.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_libcdep.cpp index 81141023386ea..74458028ae8f5 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_libcdep.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_libcdep.cpp @@ -191,6 +191,13 @@ void Symbolizer::RefreshModules() { modules_fresh_ = true; } +const ListOfModules &Symbolizer::GetRefreshedListOfModules() { + if (!modules_fresh_) + RefreshModules(); + + return modules_; +} + static const LoadedModule *SearchForModule(const ListOfModules &modules, uptr address) { for (uptr i = 0; i < modules.size(); i++) { diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_markup.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_markup.cpp index c7332af7d9efd..fe9933e942eba 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_markup.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_markup.cpp @@ -8,105 +8,169 @@ // // This file is shared between various sanitizers' runtime libraries. // -// Implementation of offline markup symbolizer. +// This generic support for offline symbolizing is based on the +// Fuchsia port. We don't do any actual symbolization per se. +// Instead, we emit text containing raw addresses and raw linkage +// symbol names, embedded in Fuchsia's symbolization markup format. +// See the spec at: +// https://llvm.org/docs/SymbolizerMarkupFormat.html //===----------------------------------------------------------------------===// -#include "sanitizer_platform.h" +#include "sanitizer_symbolizer_markup.h" -#if SANITIZER_SYMBOLIZER_MARKUP - -# include "sanitizer_common.h" -# include "sanitizer_stacktrace_printer.h" -# include "sanitizer_symbolizer.h" -# include "sanitizer_symbolizer_markup_constants.h" +#include "sanitizer_common.h" +#include "sanitizer_stacktrace_printer.h" +#include "sanitizer_symbolizer.h" +#include "sanitizer_symbolizer_markup_constants.h" namespace __sanitizer { -// This generic support for offline symbolizing is based on the -// Fuchsia port. We don't do any actual symbolization per se. -// Instead, we emit text containing raw addresses and raw linkage -// symbol names, embedded in Fuchsia's symbolization markup format. -// Fuchsia's logging infrastructure emits enough information about -// process memory layout that a post-processing filter can do the -// symbolization and pretty-print the markup. See the spec at: -// https://fuchsia.googlesource.com/zircon/+/master/docs/symbolizer_markup.md - -// This is used by UBSan for type names, and by ASan for global variable names. -// It's expected to return a static buffer that will be reused on each call. -const char *Symbolizer::Demangle(const char *name) { - static char buffer[kFormatDemangleMax]; - internal_snprintf(buffer, sizeof(buffer), kFormatDemangle, name); - return buffer; +void MarkupStackTracePrinter::RenderData(InternalScopedString *buffer, + const char *format, const DataInfo *DI, + const char *strip_path_prefix) { + RenderContext(buffer); + buffer->AppendF(kFormatData, DI->start); } -// This is used mostly for suppression matching. Making it work -// would enable "interceptor_via_lib" suppressions. It's also used -// once in UBSan to say "in module ..." in a message that also -// includes an address in the module, so post-processing can already -// pretty-print that so as to indicate the module. -bool Symbolizer::GetModuleNameAndOffsetForPC(uptr pc, const char **module_name, - uptr *module_address) { +bool MarkupStackTracePrinter::RenderNeedsSymbolization(const char *format) { return false; } -// This is mainly used by hwasan for online symbolization. This isn't needed -// since hwasan can always just dump stack frames for offline symbolization. -bool Symbolizer::SymbolizeFrame(uptr addr, FrameInfo *info) { return false; } - -// This is used in some places for suppression checking, which we -// don't really support for Fuchsia. It's also used in UBSan to -// identify a PC location to a function name, so we always fill in -// the function member with a string containing markup around the PC -// value. -// TODO(mcgrathr): Under SANITIZER_GO, it's currently used by TSan -// to render stack frames, but that should be changed to use -// RenderStackFrame. -SymbolizedStack *Symbolizer::SymbolizePC(uptr addr) { - SymbolizedStack *s = SymbolizedStack::New(addr); +// We don't support the stack_trace_format flag at all. +void MarkupStackTracePrinter::RenderFrame(InternalScopedString *buffer, + const char *format, int frame_no, + uptr address, const AddressInfo *info, + bool vs_style, + const char *strip_path_prefix) { + CHECK(!RenderNeedsSymbolization(format)); + RenderContext(buffer); + buffer->AppendF(kFormatFrame, frame_no, address); +} + +bool MarkupSymbolizerTool::SymbolizePC(uptr addr, SymbolizedStack *stack) { char buffer[kFormatFunctionMax]; internal_snprintf(buffer, sizeof(buffer), kFormatFunction, addr); - s->info.function = internal_strdup(buffer); - return s; + stack->info.function = internal_strdup(buffer); + return true; } -// Always claim we succeeded, so that RenderDataInfo will be called. -bool Symbolizer::SymbolizeData(uptr addr, DataInfo *info) { +bool MarkupSymbolizerTool::SymbolizeData(uptr addr, DataInfo *info) { info->Clear(); info->start = addr; return true; } -class MarkupStackTracePrinter : public StackTracePrinter { - // We ignore the format argument to __sanitizer_symbolize_global. - void RenderData(InternalScopedString *buffer, const char *format, - const DataInfo *DI, const char *strip_path_prefix) override { - buffer->AppendF(kFormatData, DI->start); - } +const char *MarkupSymbolizerTool::Demangle(const char *name) { + static char buffer[kFormatDemangleMax]; + internal_snprintf(buffer, sizeof(buffer), kFormatDemangle, name); + return buffer; +} - bool RenderNeedsSymbolization(const char *format) override { return false; } +// Fuchsia's implementation of symbolizer markup doesn't need to emit contextual +// elements at this point. +// Fuchsia's logging infrastructure emits enough information about +// process memory layout that a post-processing filter can do the +// symbolization and pretty-print the markup. +#if !SANITIZER_FUCHSIA + +// Simplier view of a LoadedModule. It only holds information necessary to +// identify unique modules. +struct RenderedModule { + char *full_name; + uptr base_address; + u8 uuid[kModuleUUIDSize]; // BuildId +}; - // We don't support the stack_trace_format flag at all. - void RenderFrame(InternalScopedString *buffer, const char *format, - int frame_no, uptr address, const AddressInfo *info, - bool vs_style, const char *strip_path_prefix) override { - CHECK(!RenderNeedsSymbolization(format)); - buffer->AppendF(kFormatFrame, frame_no, address); - } +static bool ModulesEq(const LoadedModule &module, + const RenderedModule &renderedModule) { + return module.base_address() == renderedModule.base_address && + internal_memcmp(module.uuid(), renderedModule.uuid, + module.uuid_size()) == 0 && + internal_strcmp(module.full_name(), renderedModule.full_name) == 0; +} - protected: - ~MarkupStackTracePrinter(); -}; +static bool ModuleHasBeenRendered( + const LoadedModule &module, + const InternalMmapVectorNoCtor &renderedModules) { + for (const auto &renderedModule : renderedModules) + if (ModulesEq(module, renderedModule)) + return true; -StackTracePrinter *StackTracePrinter::NewStackTracePrinter() { - return new (GetGlobalLowLevelAllocator()) MarkupStackTracePrinter(); + return false; } -Symbolizer *Symbolizer::PlatformInit() { - return new (symbolizer_allocator_) Symbolizer({}); +static void RenderModule(InternalScopedString *buffer, + const LoadedModule &module, uptr moduleId) { + InternalScopedString buildIdBuffer; + for (uptr i = 0; i < module.uuid_size(); i++) + buildIdBuffer.AppendF("%02x", module.uuid()[i]); + + buffer->AppendF(kFormatModule, moduleId, module.full_name(), + buildIdBuffer.data()); + buffer->Append("\n"); } -void Symbolizer::LateInitialize() { Symbolizer::GetOrInit(); } +static void RenderMmaps(InternalScopedString *buffer, + const LoadedModule &module, uptr moduleId) { + InternalScopedString accessBuffer; -} // namespace __sanitizer + // All module mmaps are readable at least + for (const auto &range : module.ranges()) { + accessBuffer.Append("r"); + if (range.writable) + accessBuffer.Append("w"); + if (range.executable) + accessBuffer.Append("x"); + + //{{{mmap:%starting_addr:%size_in_hex:load:%moduleId:r%(w|x):%relative_addr}}} + + // module.base_address == dlpi_addr + // range.beg == dlpi_addr + p_vaddr + // relative address == p_vaddr == range.beg - module.base_address + buffer->AppendF(kFormatMmap, range.beg, range.end - range.beg, moduleId, + accessBuffer.data(), range.beg - module.base_address()); + + buffer->Append("\n"); + accessBuffer.clear(); + } +} -#endif // SANITIZER_SYMBOLIZER_MARKUP +void MarkupStackTracePrinter::RenderContext(InternalScopedString *buffer) { + // Keeps track of the modules that have been rendered. + static bool initialized = false; + static InternalMmapVectorNoCtor renderedModules; + if (!initialized) { + // arbitrary initial size, counting the main module plus some important libs + // like libc. + renderedModules.Initialize(3); + initialized = true; + } + + if (renderedModules.size() == 0) + buffer->Append("{{{reset}}}\n"); + + const auto &modules = Symbolizer::GetOrInit()->GetRefreshedListOfModules(); + + for (const auto &module : modules) { + if (ModuleHasBeenRendered(module, renderedModules)) + continue; + + // symbolizer markup id, used to refer to this modules from other contextual + // elements + uptr moduleId = renderedModules.size(); + + RenderModule(buffer, module, moduleId); + RenderMmaps(buffer, module, moduleId); + + RenderedModule renderedModule{ + internal_strdup(module.full_name()), module.base_address(), {}}; + + // kModuleUUIDSize is the size of curModule.uuid + CHECK_GE(kModuleUUIDSize, module.uuid_size()); + internal_memcpy(renderedModule.uuid, module.uuid(), module.uuid_size()); + renderedModules.push_back(renderedModule); + } +} +#endif // !SANITIZER_FUCHSIA + +} // namespace __sanitizer diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_markup.h b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_markup.h new file mode 100644 index 0000000000000..07630d0b3bdf4 --- /dev/null +++ b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_markup.h @@ -0,0 +1,68 @@ +//===-- sanitizer_symbolizer_markup.h -----------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file is shared between various sanitizers' runtime libraries. +// +// Header for the offline markup symbolizer. +//===----------------------------------------------------------------------===// +#ifndef SANITIZER_SYMBOLIZER_MARKUP_H +#define SANITIZER_SYMBOLIZER_MARKUP_H + +#include "sanitizer_common.h" +#include "sanitizer_stacktrace_printer.h" +#include "sanitizer_symbolizer.h" +#include "sanitizer_symbolizer_internal.h" + +namespace __sanitizer { + +class MarkupStackTracePrinter : public StackTracePrinter { + public: + // We don't support the stack_trace_format flag at all. + void RenderFrame(InternalScopedString *buffer, const char *format, + int frame_no, uptr address, const AddressInfo *info, + bool vs_style, const char *strip_path_prefix = "") override; + + bool RenderNeedsSymbolization(const char *format) override; + + // We ignore the format argument to __sanitizer_symbolize_global. + void RenderData(InternalScopedString *buffer, const char *format, + const DataInfo *DI, + const char *strip_path_prefix = "") override; + + private: + void RenderContext(InternalScopedString *buffer); + + protected: + ~MarkupStackTracePrinter() {} +}; + +class MarkupSymbolizerTool final : public SymbolizerTool { + public: + // This is used in some places for suppression checking, which we + // don't really support for Fuchsia. It's also used in UBSan to + // identify a PC location to a function name, so we always fill in + // the function member with a string containing markup around the PC + // value. + // TODO(mcgrathr): Under SANITIZER_GO, it's currently used by TSan + // to render stack frames, but that should be changed to use + // RenderStackFrame. + bool SymbolizePC(uptr addr, SymbolizedStack *stack) override; + + // Always claim we succeeded, so that RenderDataInfo will be called. + bool SymbolizeData(uptr addr, DataInfo *info) override; + + // May return NULL if demangling failed. + // This is used by UBSan for type names, and by ASan for global variable + // names. It's expected to return a static buffer that will be reused on each + // call. + const char *Demangle(const char *name) override; +}; + +} // namespace __sanitizer + +#endif // SANITIZER_SYMBOLIZER_MARKUP_H diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_markup_constants.h b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_markup_constants.h index 0f96ca78600a5..83643504e1289 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_markup_constants.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_markup_constants.h @@ -35,6 +35,12 @@ constexpr const char *kFormatData = "{{{data:%p}}}"; // One frame in a backtrace (printed on a line by itself). constexpr const char *kFormatFrame = "{{{bt:%u:%p}}}"; +// Module contextual element. +constexpr const char *kFormatModule = "{{{module:%d:%s:elf:%s}}}"; + +// mmap for a module segment. +constexpr const char *kFormatMmap = "{{{mmap:%p:0x%x:load:%d:%s:0x%x}}}"; + // Dump trigger element. #define FORMAT_DUMPFILE "{{{dumpfile:%s:%s}}}" diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_markup_fuchsia.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_markup_fuchsia.cpp new file mode 100644 index 0000000000000..08b06c2faf30d --- /dev/null +++ b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_markup_fuchsia.cpp @@ -0,0 +1,85 @@ +//===-- sanitizer_symbolizer_markup_fuchsia.cpp ---------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file is shared between various sanitizers' runtime libraries. +// +// Fuchsia specific implementation of offline markup symbolizer. +//===----------------------------------------------------------------------===// +#include "sanitizer_platform.h" + +#if SANITIZER_SYMBOLIZER_MARKUP + +# include "sanitizer_common.h" +# include "sanitizer_stacktrace_printer.h" +# include "sanitizer_symbolizer.h" +# include "sanitizer_symbolizer_markup.h" +# include "sanitizer_symbolizer_markup_constants.h" + +namespace __sanitizer { + +// This is used by UBSan for type names, and by ASan for global variable names. +// It's expected to return a static buffer that will be reused on each call. +const char *Symbolizer::Demangle(const char *name) { + static char buffer[kFormatDemangleMax]; + internal_snprintf(buffer, sizeof(buffer), kFormatDemangle, name); + return buffer; +} + +// This is used mostly for suppression matching. Making it work +// would enable "interceptor_via_lib" suppressions. It's also used +// once in UBSan to say "in module ..." in a message that also +// includes an address in the module, so post-processing can already +// pretty-print that so as to indicate the module. +bool Symbolizer::GetModuleNameAndOffsetForPC(uptr pc, const char **module_name, + uptr *module_address) { + return false; +} + +// This is mainly used by hwasan for online symbolization. This isn't needed +// since hwasan can always just dump stack frames for offline symbolization. +bool Symbolizer::SymbolizeFrame(uptr addr, FrameInfo *info) { return false; } + +// This is used in some places for suppression checking, which we +// don't really support for Fuchsia. It's also used in UBSan to +// identify a PC location to a function name, so we always fill in +// the function member with a string containing markup around the PC +// value. +// TODO(mcgrathr): Under SANITIZER_GO, it's currently used by TSan +// to render stack frames, but that should be changed to use +// RenderStackFrame. +SymbolizedStack *Symbolizer::SymbolizePC(uptr addr) { + SymbolizedStack *s = SymbolizedStack::New(addr); + char buffer[kFormatFunctionMax]; + internal_snprintf(buffer, sizeof(buffer), kFormatFunction, addr); + s->info.function = internal_strdup(buffer); + return s; +} + +// Always claim we succeeded, so that RenderDataInfo will be called. +bool Symbolizer::SymbolizeData(uptr addr, DataInfo *info) { + info->Clear(); + info->start = addr; + return true; +} + +// Fuchsia only uses MarkupStackTracePrinter +StackTracePrinter *StackTracePrinter::NewStackTracePrinter() { + return new (GetGlobalLowLevelAllocator()) MarkupStackTracePrinter(); +} + +void MarkupStackTracePrinter::RenderContext(InternalScopedString *) {} + +Symbolizer *Symbolizer::PlatformInit() { + return new (symbolizer_allocator_) Symbolizer({}); +} + +void Symbolizer::LateInitialize() { Symbolizer::GetOrInit(); } + +} // namespace __sanitizer + +#endif // SANITIZER_SYMBOLIZER_MARKUP diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cpp index d92349c04fffa..28f11352a6b5b 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "sanitizer_platform.h" +#include "sanitizer_symbolizer_markup.h" #if SANITIZER_POSIX # include // for dlsym() # include @@ -475,6 +476,12 @@ static void ChooseSymbolizerTools(IntrusiveList *list, VReport(2, "Symbolizer is disabled.\n"); return; } + if (common_flags()->enable_symbolizer_markup) { + VReport(2, "Using symbolizer markup"); + SymbolizerTool *tool = new (*allocator) MarkupSymbolizerTool(); + CHECK(tool); + list->push_back(tool); + } if (IsAllocatorOutOfMemory()) { VReport(2, "Cannot use internal symbolizer: out of memory\n"); } else if (SymbolizerTool *tool = InternalSymbolizer::get(allocator)) { diff --git a/compiler-rt/test/sanitizer_common/TestCases/print-stack-trace-markup.cpp b/compiler-rt/test/sanitizer_common/TestCases/print-stack-trace-markup.cpp new file mode 100644 index 0000000000000..15d89d091a99e --- /dev/null +++ b/compiler-rt/test/sanitizer_common/TestCases/print-stack-trace-markup.cpp @@ -0,0 +1,38 @@ +// RUN: %clangxx %s -o %t +// RUN: %env_tool_opts=enable_symbolizer_markup=1 %run %t 2>&1 | FileCheck %s + +// REQUIRES: linux +#include + +void Bar() { __sanitizer_print_stack_trace(); } + +void Foo() { + Bar(); + return; +} + +void Baz() { __sanitizer_print_stack_trace(); } + +int main() { + Foo(); + Baz(); + return 0; +} + +// COM: For element syntax see: https://llvm.org/docs/SymbolizerMarkupFormat.html +// COM: OPEN is {{{ and CLOSE is }}} + +// CHECK: [[OPEN:{{{]]reset[[CLOSE:}}}]] +// CHECK: [[OPEN]]module:[[MOD_ID:[0-9]+]]:{{.+}}:elf:{{[0-9a-fA-F]+}}[[CLOSE]] +// CHECK: [[OPEN]]mmap:{{0x[0-9a-fA-F]+:0x[0-9a-fA-F]+}}:load:[[MOD_ID]]:{{r[wx]{0,2}:0x[0-9a-fA-F]+}}[[CLOSE]] +// CHECK: [[OPEN]]bt:0:0x{{[0-9a-fA-F]+}}[[CLOSE]] +// CHECK-NEXT: [[OPEN]]bt:1:0x{{[0-9a-fA-F]+}}[[CLOSE]] +// CHECK-NEXT: [[OPEN]]bt:2:0x{{[0-9a-fA-F]+}}[[CLOSE]] + +// COM: Emitting a second backtrace should not emit contextual elements in this case. +// CHECK-NOT: [[OPEN:{{{]]reset[[CLOSE:}}}]] +// CHECK-NOT: [[OPEN]]module:[[MOD_ID:[0-9]+]]:{{.+}}:elf:{{[0-9a-fA-F]+}}[[CLOSE]] +// CHECK-NOT: [[OPEN]]mmap:{{0x[0-9a-fA-F]+:0x[0-9a-fA-F]+}}:load:[[MOD_ID]]:{{r[wx]{0,2}:0x[0-9a-fA-F]+}}[[CLOSE]] + +// CHECK: [[OPEN]]bt:0:0x{{[0-9a-fA-F]+}}[[CLOSE]] +// CHECK-NEXT: [[OPEN]]bt:1:0x{{[0-9a-fA-F]+}}[[CLOSE]]