Index: lib/sanitizer_common/sanitizer_stacktrace.h =================================================================== --- lib/sanitizer_common/sanitizer_stacktrace.h +++ lib/sanitizer_common/sanitizer_stacktrace.h @@ -43,15 +43,20 @@ u32 size; u32 tag; + // Nonzero if the stack trace was generated in response to a signal. + bool signaled; + static const int TAG_UNKNOWN = 0; static const int TAG_ALLOC = 1; static const int TAG_DEALLOC = 2; static const int TAG_CUSTOM = 100; // Tool specific tags start here. - StackTrace() : trace(nullptr), size(0), tag(0) {} - StackTrace(const uptr *trace, u32 size) : trace(trace), size(size), tag(0) {} + StackTrace() : trace(nullptr), size(0), signaled(false) {} + StackTrace(const uptr *trace, uptr size) + : trace(trace), size(size), signaled(false) {} + StackTrace(const uptr *trace, u32 size, u32 tag) - : trace(trace), size(size), tag(tag) {} + : trace(trace), size(size), tag(tag), signaled(false) {} // Prints a symbolized stacktrace, followed by an empty line. void Print() const; @@ -76,7 +81,7 @@ uptr StackTrace::GetPreviousInstructionPc(uptr pc) { #if defined(__arm__) // Cancel Thumb bit. - pc = pc & (~1); + return pc & (~1); #endif #if defined(__powerpc__) || defined(__powerpc64__) // PCs are always 4 byte aligned. Index: lib/sanitizer_common/sanitizer_stacktrace_libcdep.cc =================================================================== --- lib/sanitizer_common/sanitizer_stacktrace_libcdep.cc +++ lib/sanitizer_common/sanitizer_stacktrace_libcdep.cc @@ -27,12 +27,19 @@ InternalScopedString frame_desc(GetPageSizeCached() * 2); 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]); + // Except when the stack trace was generated in response to a signal + // and the frame is #0, decrementing the PC in the stack trace by 1 + // helps find the expected corresponding line number in the debug + // info. This is because with the exception of frame #0 the PC saved + // on the stack is the return address, that is, the address of the + // next instruction after a branch and link or call instruction. + const int adjust = (0 == i) && signaled ? 0 : 1; + const uptr pc = trace[i] - adjust; SymbolizedStack *frames = Symbolizer::GetOrInit()->SymbolizePC(pc); CHECK(frames); for (SymbolizedStack *cur = frames; cur; cur = cur->next) { + // Restore the PC to match the stack trace. + cur->info.address += adjust; frame_desc.clear(); RenderFrame(&frame_desc, common_flags()->stack_trace_format, frame_num++, cur->info, common_flags()->strip_path_prefix); @@ -47,6 +54,8 @@ void BufferedStackTrace::Unwind(u32 max_depth, uptr pc, uptr bp, void *context, uptr stack_top, uptr stack_bottom, bool request_fast_unwind) { + // Record that the stack trace was generated as a result of a signal. + signaled = 0 != context; top_frame_bp = (max_depth > 0) ? bp : 0; // Avoid doing any work for small max_depth. if (max_depth == 0) { Index: test/sanitizer_common/TestCases/print-stack-trace-pc.cc =================================================================== --- test/sanitizer_common/TestCases/print-stack-trace-pc.cc +++ test/sanitizer_common/TestCases/print-stack-trace-pc.cc @@ -0,0 +1,71 @@ +// Run each sanitizer test with XSAN_SYMBOLIZER_PATH pointing at +// bin/llvm-symbolizer and with XSAN_OPTIONS set to a stack trace +// format necessary for the expected stack trace output to easily +// compare to the output of the test. + +// RUN: %clangxx -O0 %s -o %t && %tool_symbolizer_path=%llvm_obj_root/bin/llvm-symbolizer %tool_options='stack_trace_format="#%%n %%p %%f:%%l"' %run %t 2>&1 | FileCheck %s +// RUN: %clangxx -O3 %s -o %t && %tool_symbolizer_path=%llvm_obj_root/bin/llvm-symbolizer %tool_options='stack_trace_format="#%%n %%p %%f:%%l"' %run %t 2>&1 | FileCheck %s + +#include + +#include +#include + +static struct StackFrame { + const void *pc; + const char *func; + int line; +} stack[4], *pfrm = stack; + +// Line numbers to include in stack trace. +#define FOO_LINE 101 +#define BAR_LINE 201 +#define MAIN_LINE 301 + +void __attribute__((weak)) foo(void) { + *pfrm++ = (struct StackFrame) { + __builtin_return_address(0), __func__, FOO_LINE + }; + for (int i = 0; pfrm-- != stack; ++i) { + const size_t pc = i ? reinterpret_cast(pfrm[1].pc) : 0; + fprintf(stderr, ">%i %#zx %s:%i\n", i, pc, pfrm->func, pfrm->line); + } + fputc('\n', stderr); +#line FOO_LINE + __sanitizer_print_stack_trace(); + + // Avoid printing the same stack trace more than once. + exit(0); +} + +void __attribute__((weak)) bar(void) { + *pfrm++ = (struct StackFrame) { + __builtin_return_address(0), __func__, BAR_LINE + }; +#line BAR_LINE + foo(); + + // Call foo again to prevent tail call optimization from eliminating + // bar's stack frame from the trace. + foo(); +} + +int main(void) { + *pfrm++ = (struct StackFrame) { + __builtin_return_address(0), __func__, MAIN_LINE + }; +#line MAIN_LINE + bar(); +} + +// Check the stack trace generated by __builtin_return_address... +// CHECK: {{>0 0 foo:101}} +// CHECK-NEXT: >1 [[PCBAR:0x[0-9a-f]*]] bar:201 +// CHECK-NEXT: >2 [[PCMAIN:0x[0-9a-f]*]] main:301 + +// ...against stack trace generated by __sanitizer_print_stack_trace, +// verifying that PC values and line numbers printed by both match. +// CHECK: {{#0 0x[0-9a-f]+ __sanitizer_print_stack_trace:[1-9][0-9]*}} +// CHECK-NEXT: {{#1 0x[0-9a-f]+ foo\(\):101}} +// CHECK-NEXT: #2 [[PCBAR]] bar():201 +// CHECK-NEXT: #3 [[PCMAIN]] main:301 Index: test/sanitizer_common/lit.common.cfg =================================================================== --- test/sanitizer_common/lit.common.cfg +++ test/sanitizer_common/lit.common.cfg @@ -6,17 +6,17 @@ config.name = "SanitizerCommon-" + config.tool_name if config.tool_name == "asan": + tool_prefix = "ASAN" tool_cflags = ["-fsanitize=address"] - tool_options = "ASAN_OPTIONS" elif config.tool_name == "tsan": + tool_prefix = "TSAN" tool_cflags = ["-fsanitize=thread"] - tool_options = "TSAN_OPTIONS" elif config.tool_name == "msan": + tool_prefix = "MSAN" tool_cflags = ["-fsanitize=memory"] - tool_options = "MSAN_OPTIONS" elif config.tool_name == "lsan": + tool_prefix = "LSAN" tool_cflags = ["-fsanitize=leak"] - tool_options = "LSAN_OPTIONS" else: lit_config.fatal("Unknown tool for sanitizer_common tests: %r" % config.tool_name) @@ -28,9 +28,15 @@ def build_invocation(compile_flags): return " " + " ".join([config.clang] + compile_flags) + " " +tool_symbolizer_path = tool_prefix + "_SYMBOLIZER_PATH" +tool_options = tool_prefix + "_OPTIONS" + +# Set up variables recognized in RUN directives in sanitizer tests. config.substitutions.append( ("%clang ", build_invocation(clang_cflags)) ) config.substitutions.append( ("%clangxx ", build_invocation(clang_cxxflags)) ) config.substitutions.append( ("%tool_options", tool_options) ) +config.substitutions.append( ("%tool_symbolizer_path", tool_symbolizer_path) ) +config.substitutions.append( ("%llvm_obj_root", config.llvm_obj_root) ) config.suffixes = ['.c', '.cc', '.cpp']