diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace.h b/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace.h --- a/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace.h @@ -56,6 +56,12 @@ // Prints a symbolized stacktrace, followed by an empty line. void Print() 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 --- a/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace_libcdep.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_stacktrace_libcdep.cpp @@ -18,46 +18,210 @@ namespace __sanitizer { +namespace { + +template +class StackTraceTextProcessor { + public: + void InitFromFlags(const CommonFlags *flags) { + SetDedupTokenLength(flags->dedup_token_length); + SetStackTraceFormat(flags->stack_trace_format); + } + + void SetDedupTokenLength(int dedup_token_length) { + dedup_frames_ = dedup_token_length; + } + + void SetStackTraceFormat(const char *fmt) { + stack_trace_fmt_ = fmt; + symbolize_ = RenderNeedsSymbolization(fmt); + } + + // Returns a short string token to simplify deduplication of reports. + const char *GetDedupToken() const { return dedup_token_.data(); } + + uptr GetDedupTokenLength() const { return dedup_token_.length(); } + + bool ProcessAddressFrames(uptr pc) { + SymbolizedStack *frames = Frames(pc); + if (!frames) + return false; + + for (SymbolizedStack *cur = frames; cur; cur = cur->next) { + frame_desc_.clear(); + RenderFrame(&frame_desc_, stack_trace_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; + + ProcessText(frame_desc_.data(), frame_desc_.length()); + ExtendDedupToken(cur); + } + frames->ClearAll(); + return true; + } + + protected: + void ProcessText(const char *text, uptr length) { + static_cast(*this).ProcessText(text, length); + } + + // Returns a list of symbolized frames for the given address. + SymbolizedStack *Frames(uptr pc) const { + return symbolize_ ? Symbolizer::GetOrInit()->SymbolizePC(pc) + : SymbolizedStack::New(pc); + } + + // Extend the dedup token by appending a new frame. + void ExtendDedupToken(SymbolizedStack *stack) { + if (dedup_frames_-- > 0) { + if (dedup_token_.length()) + dedup_token_.append("--"); + if (stack->info.function != nullptr) + dedup_token_.append(stack->info.function); + } + } + + private: + const char *stack_trace_fmt_; + bool symbolize_ = false; + int dedup_frames_ = 0; + uptr frame_num_ = 0; + InternalScopedString frame_desc_; + InternalScopedString dedup_token_; +}; + +class StackTraceTextPrinter + : public StackTraceTextProcessor { + protected: + friend class StackTraceTextProcessor; + + void ProcessText(const char *text, uptr) { Printf("%s\n", text); } +}; + +class StackTraceTextCopier + : public StackTraceTextProcessor { + public: + StackTraceTextCopier(char *out_buf, uptr out_buf_size) + : StackTraceTextProcessor(), + out_buf_(out_buf), + out_end_(out_buf + out_buf_size - 1) { + *out_end_ = 0; + } + + void SetFrameDelimiter(char delim) { frame_delimiter_ = delim; } + + uptr GetLength() const { return length_; } + + void Copy(const char *text) { + length_ += internal_strlen(text); + + if (out_buf_ == out_end_) + return; + + uptr n = out_end_ - out_buf_; + internal_strncpy(out_buf_, text, n); + + while (*out_buf_++) { + } + + CHECK(out_buf_ <= out_end_); + CHECK(!*out_end_); + } + + protected: + friend class StackTraceTextProcessor; + + void ProcessText(const char *text, uptr length) { + length_ += length + 1; + + if (!length) + return; + if (out_buf_ == out_end_) + return; + + // Reserve one byte for the frame delimiter. + uptr n = out_end_ - out_buf_ - 1; + internal_strncpy(out_buf_, text, n); + out_buf_ += __sanitizer::Min(n, length); + *out_buf_++ = frame_delimiter_; + + CHECK(out_buf_ <= out_end_); + CHECK(!*out_end_); + } + + private: + // Holds the actual length of the contents, even if it doesn't fit in the + // output buffer. + uptr length_ = 0; + + char *out_buf_; + char *const out_end_; + char frame_delimiter_ = '\0'; +}; + +} // namespace + +uptr StackTrace::PrintTo(char *out_buf, uptr out_buf_size) const { + CHECK(out_buf); + CHECK_GT(out_buf_size, 0); + + StackTraceTextCopier copier(out_buf, out_buf_size); + + if (trace == nullptr || size == 0) { + copier.Copy(" \n\n"); + return copier.GetLength(); + } + + copier.InitFromFlags(common_flags()); + copier.SetFrameDelimiter('\n'); + + 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]); + CHECK(copier.ProcessAddressFrames(pc)); + } + + // Always add a trailing empty line after stack trace. + copier.Copy("\n"); + + // Append deduplication token, if non-empty. + if (copier.GetDedupTokenLength()) { + copier.Copy("DEDUP_TOKEN: "); + copier.Copy(copier.GetDedupToken()); + copier.Copy("\n"); + } + + return copier.GetLength(); +} + void StackTrace::Print() const { if (trace == nullptr || size == 0) { Printf(" \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; + + StackTraceTextPrinter printer; + printer.InitFromFlags(common_flags()); + 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"); - if (dedup_token.length()) - Printf("DEDUP_TOKEN: %s\n", dedup_token.data()); + + // Print deduplication token, if non-empty. + if (printer.GetDedupTokenLength()) { + Printf("DEDUP_TOKEN: %s\n", printer.GetDedupToken()); + } } void BufferedStackTrace::Unwind(u32 max_depth, uptr pc, uptr bp, void *context, @@ -116,40 +280,16 @@ 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; + + StackTraceTextCopier copier(out_buf, out_buf_size); + copier.SetStackTraceFormat(fmt); + + if (!copier.ProcessAddressFrames(pc)) { + copier.Copy(""); return; } - 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(); } 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 --- 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,11 @@ // //===----------------------------------------------------------------------===// -#include "sanitizer_common/sanitizer_common.h" #include "sanitizer_common/sanitizer_stacktrace.h" + #include "gtest/gtest.h" +#include "sanitizer_common/sanitizer_common.h" +#include "string.h" namespace __sanitizer { @@ -160,6 +162,107 @@ } } +using StackPrintTest = FastUnwindTest; + +TEST_F(StackPrintTest, SKIP_ON_SPARC(StackPrintToContainsFullTrace)) { + // 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. + 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(StackPrintToTruncatesContents)) { + 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) - 2; + EXPECT_EQ(strncmp(buf, tinybuf, lastpos), 0); + + // Truncated contents end with newline. + EXPECT_EQ(tinybuf[lastpos], '\n'); + EXPECT_EQ(tinybuf[lastpos + 1], '\0'); + + // Full bufffer has more contents... + EXPECT_NE(buf[lastpos], '\0'); + EXPECT_NE(buf[lastpos], '\n'); +} + +TEST_F(StackPrintTest, SKIP_ON_SPARC(StackPrintToWorksWithEmptyStack)) { + char buf[3000]; + trace.PrintTo(buf, sizeof(buf)); + EXPECT_NE(strstr(buf, ""), nullptr); +} + +TEST_F(StackPrintTest, SKIP_ON_SPARC(StackPrintToReturnsCorrectLength)) { + 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); +} + +using StackPrintDeathTest = StackPrintTest; + +TEST_F(StackPrintDeathTest, + SKIP_ON_SPARC(StackPrintRequiresNonNullBufferAndPosSize)) { + UnwindFast(); + char buf[100]; + EXPECT_DEATH(trace.PrintTo(buf, 0), ""); + EXPECT_DEATH(trace.PrintTo(NULL, 100), ""); +} + #endif // SANITIZER_CAN_FAST_UNWIND TEST(SlowUnwindTest, ShortStackTrace) {