Commit b9a92206 authored by Dan Liew's avatar Dan Liew
Browse files

[Symbolizers] On Darwin compute function offset when possible.

Summary:
The sanitizer symbolizers support printing the function offset
(difference between pc and function start) of a stackframe using the
`%q` format specifier.

Unfortunately this didn't actually work because neither the atos
or dladdr symbolizer set the `AddressInfo::function_offset` field.

This patch teaches both symbolizers to try to compute the function
offset. In the case of the atos symbolizer, atos might not report the
function offset (e.g. it reports a source location instead) so in this
case it fallsback to using `dladdr()` to compute the function offset.

Two test cases are included.

rdar://problem/56695185

Reviewers: kubamracek, yln

Subscribers: #sanitizers, llvm-commits

Tags: #sanitizers, #llvm

Differential Revision: https://reviews.llvm.org/D69549
parent e531750c
Loading
Loading
Loading
Loading
+21 −1
Original line number Diff line number Diff line
@@ -31,6 +31,9 @@ bool DlAddrSymbolizer::SymbolizePC(uptr addr, SymbolizedStack *stack) {
  Dl_info info;
  int result = dladdr((const void *)addr, &info);
  if (!result) return false;

  CHECK(addr >= reinterpret_cast<uptr>(info.dli_saddr));
  stack->info.function_offset = addr - reinterpret_cast<uptr>(info.dli_saddr);
  const char *demangled = DemangleSwiftAndCXX(info.dli_sname);
  if (!demangled) return false;
  stack->info.function = internal_strdup(demangled);
@@ -145,12 +148,29 @@ bool AtosSymbolizer::SymbolizePC(uptr addr, SymbolizedStack *stack) {
  const char *buf = process_->SendCommand(command);
  if (!buf) return false;
  uptr line;
  uptr start_address = AddressInfo::kUnknown;
  if (!ParseCommandOutput(buf, addr, &stack->info.function, &stack->info.module,
                          &stack->info.file, &line, nullptr)) {
                          &stack->info.file, &line, &start_address)) {
    process_ = nullptr;
    return false;
  }
  stack->info.line = (int)line;

  if (start_address == AddressInfo::kUnknown) {
    // Fallback to dladdr() to get function start address if atos doesn't report
    // it.
    Dl_info info;
    int result = dladdr((const void *)addr, &info);
    if (result)
      start_address = reinterpret_cast<uptr>(info.dli_saddr);
  }

  // Only assig to `function_offset` if we were able to get the function's
  // start address.
  if (start_address != AddressInfo::kUnknown) {
    CHECK(addr >= start_address);
    stack->info.function_offset = addr - start_address;
  }
  return true;
}

+2 −2
Original line number Diff line number Diff line
@@ -17,8 +17,8 @@
// source location is not and instead module name and offset are
// printed.
// CHECK-PS: WRITE of size 4
// CHECK-PS: #0 0x{{.+}} in foo ({{.+}}.executable:{{.+}}+0x{{.+}})
// CHECK-PS: #1 0x{{.+}} in main ({{.+}}.executable:{{.+}}+0x{{.+}})
// CHECK-PS: #0 0x{{.+}} in foo{{(\+0x[0-9a-f]+)?}} ({{.+}}.executable:{{.+}}+0x{{.+}})
// CHECK-PS: #1 0x{{.+}} in main{{(\+0x[0-9a-f]+)?}} ({{.+}}.executable:{{.+}}+0x{{.+}})

// CHECK-FS: WRITE of size 4

+43 −0
Original line number Diff line number Diff line
// RUN: %clangxx %s -g -O0 -o %t-with-debug

// With debug info atos reports the source location, but no function offset. We fallback to dladdr() to retrieve the function offset.
// RUN: %env_tool_opts=verbosity=2,stack_trace_format='"function_name:%f function_offset:%q"' %run %t-with-debug > %t-with-debug.output 2>&1
// RUN: FileCheck -input-file=%t-with-debug.output %s

// Without debug info atos reports the function offset and so dladdr() fallback is not used.
// RUN: rm -rf %t-with-debug.dSYM
// RUN: %env_tool_opts=verbosity=2,stack_trace_format='"function_name:%f function_offset:%q"' %run %t-with-debug > %t-no-debug.output 2>&1
// RUN: FileCheck -input-file=%t-no-debug.output %s

#include <sanitizer/common_interface_defs.h>
#include <stdio.h>

void baz() {
  printf("Do stuff in baz\n");
  __sanitizer_print_stack_trace();
}

void bar() {
  printf("Do stuff in bar\n");
  baz();
}

void foo() {
  printf("Do stuff in foo\n");
  bar();
}

int main() {
  printf("Do stuff in main\n");
  foo();
  return 0;
}

// CHECK: Using atos found at:

// These `function_offset` patterns are designed to disallow `0x0` which is the
// value printed for `kUnknown`.
// CHECK: function_name:baz{{(\(\))?}} function_offset:0x{{0*[1-9a-f][0-9a-f]*$}}
// CHECK: function_name:bar{{(\(\))?}} function_offset:0x{{0*[1-9a-f][0-9a-f]*$}}
// CHECK: function_name:foo{{(\(\))?}} function_offset:0x{{0*[1-9a-f][0-9a-f]*$}}
// CHECK: function_name:main{{(\(\))?}} function_offset:0x{{0*[1-9a-f][0-9a-f]*$}}
+41 −0
Original line number Diff line number Diff line
// UNSUPPORTED: lsan
// This test fails with LSan enabled because the dladdr symbolizer actually leaks
// memory because the call to `__sanitizer::DemangleCXXABI` leaks memory which LSan
// detects (rdar://problem/42868950).

// RUN: %clangxx %s -O0 -o %t
// RUN: %env_tool_opts=verbosity=2,external_symbolizer_path=,stack_trace_format='"function_name:%f function_offset:%q"' %run %t > %t.output 2>&1
// RUN: FileCheck -input-file=%t.output %s
#include <sanitizer/common_interface_defs.h>
#include <stdio.h>

void baz() {
  printf("Do stuff in baz\n");
  __sanitizer_print_stack_trace();
}

void bar() {
  printf("Do stuff in bar\n");
  baz();
}

void foo() {
  printf("Do stuff in foo\n");
  bar();
}

int main() {
  printf("Do stuff in main\n");
  foo();
  return 0;
}

// CHECK: External symbolizer is explicitly disabled
// CHECK: Using dladdr symbolizer

// These `function_offset` patterns are designed to disallow `0x0` which is the
// value printed for `kUnknown`.
// CHECK: function_name:baz{{(\(\))?}} function_offset:0x{{0*[1-9a-f][0-9a-f]*$}}
// CHECK: function_name:bar{{(\(\))?}} function_offset:0x{{0*[1-9a-f][0-9a-f]*$}}
// CHECK: function_name:foo{{(\(\))?}} function_offset:0x{{0*[1-9a-f][0-9a-f]*$}}
// CHECK: function_name:main{{(\(\))?}} function_offset:0x{{0*[1-9a-f][0-9a-f]*$}}