diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace.h b/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace.h index 15616f899d01..a53d8b8899c9 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace.h @@ -12,6 +12,7 @@ #ifndef SANITIZER_STACKTRACE_H #define SANITIZER_STACKTRACE_H +#include "sanitizer_common.h" #include "sanitizer_internal_defs.h" #include "sanitizer_platform.h" @@ -56,6 +57,16 @@ struct StackTrace { // Prints a symbolized stacktrace, followed by an empty line. void Print() const; + // Prints a symbolized stacktrace to the output string, followed by an empty + // line. + void PrintTo(InternalScopedString *output) const; + + // Prints a symbolized stacktrace to the output buffer, followed by an empty + // line. Returns the number of symbols that should have been written to buffer + // (not including trailing '\0'). Thus, the string is truncated iff return + // value is not less than "out_buf_size". + uptr PrintTo(char *out_buf, uptr out_buf_size) const; + static bool WillUseFastUnwind(bool request_fast_unwind) { if (!SANITIZER_CAN_FAST_UNWIND) return false; diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace_libcdep.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace_libcdep.cpp index b1f537c0238f..f60ea7731748 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace_libcdep.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace_libcdep.cpp @@ -18,46 +18,119 @@ namespace __sanitizer { -void StackTrace::Print() const { +namespace { + +class StackTraceTextPrinter { + public: + StackTraceTextPrinter(const char *stack_trace_fmt, char frame_delimiter, + InternalScopedString *output, + InternalScopedString *dedup_token) + : stack_trace_fmt_(stack_trace_fmt), + frame_delimiter_(frame_delimiter), + output_(output), + dedup_token_(dedup_token), + symbolize_(RenderNeedsSymbolization(stack_trace_fmt)) {} + + bool ProcessAddressFrames(uptr pc) { + SymbolizedStack *frames = symbolize_ + ? Symbolizer::GetOrInit()->SymbolizePC(pc) + : SymbolizedStack::New(pc); + if (!frames) + return false; + + for (SymbolizedStack *cur = frames; cur; cur = cur->next) { + uptr prev_len = output_->length(); + RenderFrame(output_, stack_trace_fmt_, frame_num_++, cur->info.address, + symbolize_ ? &cur->info : nullptr, + common_flags()->symbolize_vs_style, + common_flags()->strip_path_prefix); + + if (prev_len != output_->length()) + output_->append("%c", frame_delimiter_); + + ExtendDedupToken(cur); + } + frames->ClearAll(); + return true; + } + + private: + // Extend the dedup token by appending a new frame. + void ExtendDedupToken(SymbolizedStack *stack) { + if (!dedup_token_) + return; + + if (dedup_frames_-- > 0) { + if (dedup_token_->length()) + dedup_token_->append("--"); + if (stack->info.function != nullptr) + dedup_token_->append(stack->info.function); + } + } + + const char *stack_trace_fmt_; + const char frame_delimiter_; + int dedup_frames_ = common_flags()->dedup_token_length; + uptr frame_num_ = 0; + InternalScopedString *output_; + InternalScopedString *dedup_token_; + const bool symbolize_ = false; +}; + +static void CopyStringToBuffer(const InternalScopedString &str, char *out_buf, + uptr out_buf_size) { + if (!out_buf_size) + return; + + CHECK_GT(out_buf_size, 0); + uptr copy_size = Min(str.length(), out_buf_size - 1); + internal_memcpy(out_buf, str.data(), copy_size); + out_buf[copy_size] = '\0'; +} + +} // namespace + +void StackTrace::PrintTo(InternalScopedString *output) const { + CHECK(output); + + InternalScopedString dedup_token; + StackTraceTextPrinter printer(common_flags()->stack_trace_format, '\n', + output, &dedup_token); + if (trace == nullptr || size == 0) { - Printf(" \n\n"); + output->append(" \n\n"); return; } - InternalScopedString frame_desc; - InternalScopedString dedup_token; - int dedup_frames = common_flags()->dedup_token_length; - bool symbolize = RenderNeedsSymbolization(common_flags()->stack_trace_format); - uptr frame_num = 0; + for (uptr i = 0; i < size && trace[i]; i++) { // PCs in stack traces are actually the return addresses, that is, // addresses of the next instructions after the call. uptr pc = GetPreviousInstructionPc(trace[i]); - SymbolizedStack *frames; - if (symbolize) - frames = Symbolizer::GetOrInit()->SymbolizePC(pc); - else - frames = SymbolizedStack::New(pc); - CHECK(frames); - for (SymbolizedStack *cur = frames; cur; cur = cur->next) { - frame_desc.clear(); - RenderFrame(&frame_desc, common_flags()->stack_trace_format, frame_num++, - cur->info.address, symbolize ? &cur->info : nullptr, - common_flags()->symbolize_vs_style, - common_flags()->strip_path_prefix); - Printf("%s\n", frame_desc.data()); - if (dedup_frames-- > 0) { - if (dedup_token.length()) - dedup_token.append("--"); - if (cur->info.function != nullptr) - dedup_token.append(cur->info.function); - } - } - frames->ClearAll(); + CHECK(printer.ProcessAddressFrames(pc)); } - // Always print a trailing empty line after stack trace. - Printf("\n"); + + // Always add a trailing empty line after stack trace. + output->append("\n"); + + // Append deduplication token, if non-empty. if (dedup_token.length()) - Printf("DEDUP_TOKEN: %s\n", dedup_token.data()); + output->append("DEDUP_TOKEN: %s\n", dedup_token.data()); +} + +uptr StackTrace::PrintTo(char *out_buf, uptr out_buf_size) const { + CHECK(out_buf); + + InternalScopedString output; + PrintTo(&output); + CopyStringToBuffer(output, out_buf, out_buf_size); + + return output.length(); +} + +void StackTrace::Print() const { + InternalScopedString output; + PrintTo(&output); + Printf("%s", output.data()); } void BufferedStackTrace::Unwind(u32 max_depth, uptr pc, uptr bp, void *context, @@ -115,41 +188,18 @@ extern "C" { SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_symbolize_pc(uptr pc, const char *fmt, char *out_buf, uptr out_buf_size) { - if (!out_buf_size) return; - pc = StackTrace::GetPreviousInstructionPc(pc); - SymbolizedStack *frame; - bool symbolize = RenderNeedsSymbolization(fmt); - if (symbolize) - frame = Symbolizer::GetOrInit()->SymbolizePC(pc); - else - frame = SymbolizedStack::New(pc); - if (!frame) { - internal_strncpy(out_buf, "", out_buf_size); - out_buf[out_buf_size - 1] = 0; + if (!out_buf_size) return; + + pc = StackTrace::GetPreviousInstructionPc(pc); + + InternalScopedString output; + StackTraceTextPrinter printer(fmt, '\0', &output, nullptr); + if (!printer.ProcessAddressFrames(pc)) { + output.clear(); + output.append(""); } - InternalScopedString frame_desc; - uptr frame_num = 0; - // Reserve one byte for the final 0. - char *out_end = out_buf + out_buf_size - 1; - for (SymbolizedStack *cur = frame; cur && out_buf < out_end; - cur = cur->next) { - frame_desc.clear(); - RenderFrame(&frame_desc, fmt, frame_num++, cur->info.address, - symbolize ? &cur->info : nullptr, - common_flags()->symbolize_vs_style, - common_flags()->strip_path_prefix); - if (!frame_desc.length()) - continue; - // Reserve one byte for the terminating 0. - uptr n = out_end - out_buf - 1; - internal_strncpy(out_buf, frame_desc.data(), n); - out_buf += __sanitizer::Min(n, frame_desc.length()); - *out_buf++ = 0; - } - CHECK(out_buf <= out_end); - *out_buf = 0; - frame->ClearAll(); + CopyStringToBuffer(output, out_buf, out_buf_size); } SANITIZER_INTERFACE_ATTRIBUTE diff --git a/compiler-rt/lib/sanitizer_common/tests/sanitizer_stacktrace_test.cpp b/compiler-rt/lib/sanitizer_common/tests/sanitizer_stacktrace_test.cpp index 58f92fcf9eea..504fa1e51457 100644 --- a/compiler-rt/lib/sanitizer_common/tests/sanitizer_stacktrace_test.cpp +++ b/compiler-rt/lib/sanitizer_common/tests/sanitizer_stacktrace_test.cpp @@ -10,9 +10,12 @@ // //===----------------------------------------------------------------------===// -#include "sanitizer_common/sanitizer_common.h" #include "sanitizer_common/sanitizer_stacktrace.h" + +#include + #include "gtest/gtest.h" +#include "sanitizer_common/sanitizer_common.h" namespace __sanitizer { @@ -40,6 +43,9 @@ class FastUnwindTest : public ::testing::Test { const uptr kFpOffset = 2; const uptr kBpOffset = 0; #endif + + private: + CommonFlags tmp_flags_; }; static uptr PC(uptr idx) { @@ -69,11 +75,16 @@ void FastUnwindTest::SetUp() { fake_bottom = (uhwptr)mapping; fake_bp = (uptr)&fake_stack[kBpOffset]; start_pc = PC(0); + + tmp_flags_.CopyFrom(*common_flags()); } void FastUnwindTest::TearDown() { size_t ps = GetPageSize(); UnmapOrDie(mapping, 2 * ps); + + // Restore default flags. + OverrideCommonFlags(tmp_flags_); } #if SANITIZER_CAN_FAST_UNWIND @@ -160,6 +171,107 @@ TEST_F(FastUnwindTest, SKIP_ON_SPARC(CloseToZeroFrame)) { } } +using StackPrintTest = FastUnwindTest; + +TEST_F(StackPrintTest, SKIP_ON_SPARC(ContainsFullTrace)) { + // Override stack trace format to make testing code independent of default + // flag values. + CommonFlags flags; + flags.CopyFrom(*common_flags()); + flags.stack_trace_format = "#%n %p"; + OverrideCommonFlags(flags); + + UnwindFast(); + + char buf[3000]; + uptr len = trace.PrintTo(buf, sizeof(buf)); + + // This is the no-truncation case. + ASSERT_LT(len, sizeof(buf)); + + // Printed contents should always end with an empty line, unless truncated. + EXPECT_EQ(buf[len - 2], '\n'); + EXPECT_EQ(buf[len - 1], '\n'); + EXPECT_EQ(buf[len], '\0'); + + // Buffer contents are delimited by newlines, by default. + char *saveptr; + char *line = strtok_r(buf, "\n", &saveptr); + + // Checks buffer contents line-by-line. + for (u32 i = 0; i < trace.size; ++i) { + char traceline[100]; + + // Should be synced with the stack trace format, set above. + snprintf(traceline, sizeof(traceline) - 1, "#%u 0x%lx", i, + trace.trace[i] - 1); + + EXPECT_STREQ(line, traceline); + line = strtok_r(NULL, "\n", &saveptr); + } + + EXPECT_EQ(line, nullptr); +} + +TEST_F(StackPrintTest, SKIP_ON_SPARC(TruncatesContents)) { + UnwindFast(); + + char buf[3000]; + uptr actual_len = trace.PrintTo(buf, sizeof(buf)); + ASSERT_LT(actual_len, sizeof(buf)); + + char tinybuf[10]; + trace.PrintTo(tinybuf, sizeof(tinybuf)); + + // This the the truncation case. + ASSERT_GT(actual_len, sizeof(tinybuf)); + + // The truncated contents should be a prefix of the full contents. + size_t lastpos = sizeof(tinybuf) - 1; + EXPECT_EQ(strncmp(buf, tinybuf, lastpos), 0); + EXPECT_EQ(tinybuf[lastpos], '\0'); + + // Full bufffer has more contents... + EXPECT_NE(buf[lastpos], '\0'); +} + +TEST_F(StackPrintTest, SKIP_ON_SPARC(WorksWithEmptyStack)) { + char buf[3000]; + trace.PrintTo(buf, sizeof(buf)); + EXPECT_NE(strstr(buf, ""), nullptr); +} + +TEST_F(StackPrintTest, SKIP_ON_SPARC(ReturnsCorrectLength)) { + UnwindFast(); + + char buf[3000]; + uptr len = trace.PrintTo(buf, sizeof(buf)); + size_t actual_len = strlen(buf); + ASSERT_LT(len, sizeof(buf)); + EXPECT_EQ(len, actual_len); + + char tinybuf[5]; + len = trace.PrintTo(tinybuf, sizeof(tinybuf)); + size_t truncated_len = strlen(tinybuf); + ASSERT_GE(len, sizeof(tinybuf)); + EXPECT_EQ(len, actual_len); + EXPECT_EQ(truncated_len, sizeof(tinybuf) - 1); +} + +TEST_F(StackPrintTest, SKIP_ON_SPARC(AcceptsZeroSize)) { + UnwindFast(); + char buf[1]; + EXPECT_GT(trace.PrintTo(buf, 0), 0u); +} + +using StackPrintDeathTest = StackPrintTest; + +TEST_F(StackPrintDeathTest, SKIP_ON_SPARC(RequiresNonNullBuffer)) { + UnwindFast(); + char buf[100]; + EXPECT_DEATH(trace.PrintTo(NULL, 100), ""); +} + #endif // SANITIZER_CAN_FAST_UNWIND TEST(SlowUnwindTest, ShortStackTrace) {