Index: lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cc =================================================================== --- lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cc +++ lib/sanitizer_common/sanitizer_symbolizer_posix_libcdep.cc @@ -23,6 +23,7 @@ #include "sanitizer_procmaps.h" #include "sanitizer_symbolizer_internal.h" #include "sanitizer_symbolizer_libbacktrace.h" +#include "sanitizer_symbolizer_mac.h" #include @@ -413,48 +414,75 @@ bool modules_fresh_; }; -static SymbolizerTool *ChooseSymbolizer(LowLevelAllocator *allocator) { - if (!common_flags()->symbolize) { - VReport(2, "Symbolizer is disabled.\n"); - return nullptr; - } +static void ChooseMainSymbolizerTools(IntrusiveList *list, + LowLevelAllocator *allocator) { if (SymbolizerTool *tool = InternalSymbolizer::get(allocator)) { VReport(2, "Using internal symbolizer.\n"); - return tool; + list->push_back(tool); + return; } if (SymbolizerTool *tool = LibbacktraceSymbolizer::get(allocator)) { VReport(2, "Using libbacktrace symbolizer.\n"); - return tool; + list->push_back(tool); + return; } const char *path_to_external = common_flags()->external_symbolizer_path; if (path_to_external && path_to_external[0] == '\0') { // External symbolizer is explicitly disabled. Do nothing. - return nullptr; + return; } // Find path to llvm-symbolizer if it's not provided. if (!path_to_external) { path_to_external = FindPathToBinary("llvm-symbolizer"); } +#if SANITIZER_MAC + // On Darwin, if we can't find llvm-symbolizer, try atos. + if (!path_to_external) { + path_to_external = FindPathToBinary("atos"); + } + if (internal_strcmp(StripModuleName(path_to_external), "atos") == 0) { + VReport(2, "Using atos at path: %s\n", path_to_external); + list->push_back(new(*allocator) + AtosSymbolizer(path_to_external, allocator)); + return; + } +#endif if (path_to_external) { VReport(2, "Using llvm-symbolizer at path: %s\n", path_to_external); - return new(*allocator) LLVMSymbolizer(path_to_external, allocator); + list->push_back(new(*allocator) + LLVMSymbolizer(path_to_external, allocator)); +#if SANITIZER_MAC + if (const char *atos_path = FindPathToBinary("atos")) { + VReport(2, "Using atos as fallback at path: %s\n", atos_path); + list->push_back(new(*allocator) + AtosSymbolizer(atos_path, allocator)); + } +#endif + return; } if (common_flags()->allow_addr2line) { // If llvm-symbolizer is not found, try to use addr2line. if (const char *addr2line_path = FindPathToBinary("addr2line")) { VReport(2, "Using addr2line at path: %s\n", addr2line_path); - return new(*allocator) Addr2LinePool(addr2line_path, allocator); + list->push_back(new(*allocator) Addr2LinePool(addr2line_path, allocator)); + return; } } - VReport(2, "No internal or external symbolizer found.\n"); - return nullptr; + VReport(2, "No main internal or external symbolizer found.\n"); + return; } Symbolizer *Symbolizer::PlatformInit() { IntrusiveList list; list.clear(); - if (SymbolizerTool *tool = ChooseSymbolizer(&symbolizer_allocator_)) { - list.push_back(tool); + if (!common_flags()->symbolize) { + VReport(2, "Symbolizer is disabled.\n"); + } else { + ChooseMainSymbolizerTools(&list, &symbolizer_allocator_); +#if SANITIZER_MAC + VReport(2, "Using dladdr symbolizer as fallback.\n"); + list.push_back(new(symbolizer_allocator_) DlAddrSymbolizer()); +#endif // SANITIZER_MAC } return new(symbolizer_allocator_) POSIXSymbolizer(list); } Index: test/asan/TestCases/Darwin/atos-symbolizer.cc =================================================================== --- test/asan/TestCases/Darwin/atos-symbolizer.cc +++ test/asan/TestCases/Darwin/atos-symbolizer.cc @@ -0,0 +1,24 @@ +// Check that the `atos` symbolizer works. + +// RUN: %clangxx_asan -O0 %s -o %t +// RUN: ASAN_OPTIONS=verbosity=2 ASAN_SYMBOLIZER_PATH=$(which atos) not %run %t 2>&1 | FileCheck %s + +#include +#include +int main(int argc, char **argv) { + char *x = (char*)malloc(10 * sizeof(char)); + memset(x, 0, 10); + int res = x[argc]; + free(x); + free(x + argc - 1); // BOOM + // CHECK: AddressSanitizer: attempting double-free{{.*}}in thread T0 + // CHECK-DLADDR: Using atos at path + // CHECK: #0 0x{{.*}} in {{.*}}free + // CHECK: #1 0x{{.*}} in main {{.*}}atos-symbolizer.cc:[[@LINE-4]] + // CHECK: freed by thread T0 here: + // CHECK: #0 0x{{.*}} in {{.*}}free + // CHECK: #1 0x{{.*}} in main {{.*}}atos-symbolizer.cc:[[@LINE-8]] + // CHECK: allocated by thread T0 here: + // CHECK: atos-symbolizer.cc:[[@LINE-13]] + return res; +} Index: test/asan/TestCases/Darwin/dyld-root-path.cc =================================================================== --- test/asan/TestCases/Darwin/dyld-root-path.cc +++ test/asan/TestCases/Darwin/dyld-root-path.cc @@ -0,0 +1,18 @@ +// Check that when having a DYLD_ROOT_PATH set, the symbolizer still works. +// RUN: %clangxx_asan -O0 %s -o %t -framework Foundation +// RUN: DYLD_ROOT_PATH="/" ASAN_OPTIONS=verbosity=2 ASAN_SYMBOLIZER_PATH=$(which atos) \ +// RUN: not %run %t 2>&1 | FileCheck %s + +#include + +// CHECK: Using atos at path: + +int main() { + char *x = (char*)malloc(10 * sizeof(char)); + free(x); + return x[5]; + // CHECK: {{AddressSanitizer: heap-use-after-free on address}} + // CHECK: {{READ of size 1 at 0x.* thread T0}} + // CHECK-NOT: atos returned an error + // CHECK: {{#0 0x.* in main .*dyld-root-path.cc:}}[[@LINE-4]] +} Index: test/asan/TestCases/Darwin/sandbox-symbolizer.cc =================================================================== --- test/asan/TestCases/Darwin/sandbox-symbolizer.cc +++ test/asan/TestCases/Darwin/sandbox-symbolizer.cc @@ -0,0 +1,31 @@ +// In a non-forking sandbox, we can't spawn an external symbolizer, but dladdr() +// should still work and provide function names. No line numbers though. + +// RUN: %clangxx_asan -O0 %s -o %t +// RUN: not %run sandbox-exec -p '(version 1)(allow default)(deny process-fork)' %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -O3 %s -o %t +// RUN: not %run sandbox-exec -p '(version 1)(allow default)(deny process-fork)' %t 2>&1 | FileCheck %s + +// `atos` symbolizer can't inspect a process that has an inaccessible task port, +// in which case we should again fallback to dladdr gracefully. + +// RUN: %clangxx_asan -O0 %s -o %t +// RUN: not %run sandbox-exec -p '(version 1)(allow default)(deny mach-priv-task-port)' %t 2>&1 | FileCheck %s +// RUN: %clangxx_asan -O3 %s -o %t +// RUN: not %run sandbox-exec -p '(version 1)(allow default)(deny mach-priv-task-port)' %t 2>&1 | FileCheck %s + +#include +int main() { + char *x = (char*)malloc(10 * sizeof(char)); + free(x); + return x[5]; + // CHECK: {{.*ERROR: AddressSanitizer: heap-use-after-free on address}} + // CHECK: {{READ of size 1 at 0x.* thread T0}} + // CHECK: {{ #0 0x.* in main}} + // CHECK: {{freed by thread T0 here:}} + // CHECK: {{ #0 0x.* in wrap_free}} + // CHECK: {{ #1 0x.* in main}} + // CHECK: {{previously allocated by thread T0 here:}} + // CHECK: {{ #0 0x.* in wrap_malloc}} + // CHECK: {{ #1 0x.* in main}} +} Index: test/asan/TestCases/Darwin/suppressions-sandbox.cc =================================================================== --- test/asan/TestCases/Darwin/suppressions-sandbox.cc +++ test/asan/TestCases/Darwin/suppressions-sandbox.cc @@ -0,0 +1,26 @@ +// Check that without suppressions, we catch the issue. +// RUN: %clangxx_asan -O0 %s -o %t -framework Foundation +// RUN: not %run %t 2>&1 | FileCheck --check-prefix=CHECK-CRASH %s + +// Check that suppressing a function name works within a no-fork sandbox +// RUN: echo "interceptor_via_fun:CFStringCreateWithBytes" > %t.supp +// RUN: ASAN_OPTIONS=suppressions=%t.supp \ +// RUN: sandbox-exec -p '(version 1)(allow default)(deny process-fork)' \ +// RUN: %run %t 2>&1 | FileCheck --check-prefix=CHECK-IGNORE %s + +#include + +int main() { + char *a = (char *)malloc(6); + strcpy(a, "hello"); + CFStringRef str = + CFStringCreateWithBytes(kCFAllocatorDefault, (unsigned char *)a, 10, + kCFStringEncodingUTF8, FALSE); // BOOM + fprintf(stderr, "Ignored.\n"); + free(a); +} + +// CHECK-CRASH: AddressSanitizer: heap-buffer-overflow +// CHECK-CRASH-NOT: Ignored. +// CHECK-IGNORE-NOT: AddressSanitizer: heap-buffer-overflow +// CHECK-IGNORE: Ignored.