llvm-project/compiler-rt/lib/sanitizer_common/sanitizer_symbolizer_mac.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

257 lines
8.8 KiB
C++
Raw Normal View History

//===-- sanitizer_symbolizer_mac.cpp --------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file is shared between various sanitizers' runtime libraries.
//
// Implementation of Mac-specific "atos" symbolizer.
//===----------------------------------------------------------------------===//
#include "sanitizer_platform.h"
#if SANITIZER_MAC
#include "sanitizer_allocator_internal.h"
#include "sanitizer_mac.h"
#include "sanitizer_symbolizer_mac.h"
#include <dlfcn.h>
#include <errno.h>
[Darwin] Fix symbolization for recent simulator runtimes. Summary: Due to sandbox restrictions in the recent versions of the simulator runtime the atos program is no longer able to access the task port of a parent process without additional help. This patch fixes this by registering a task port for the parent process before spawning atos and also tells atos to look for this by setting a special environment variable. This patch is based on an Apple internal fix (rdar://problem/43693565) that unfortunately contained a bug (rdar://problem/58789439) because it used setenv() to set the special environment variable. This is not safe because in certain circumstances this can trigger a call to realloc() which can fail during symbolization leading to deadlock. A test case is included that captures this problem. The approach used to set the necessary environment variable is as follows: 1. Calling `putenv()` early during process init (but late enough that malloc/realloc works) to set a dummy value for the environment variable. 2. Just before `atos` is spawned the storage for the environment variable is modified to contain the correct PID. A flaw with this approach is that if the application messes with the atos environment variable (i.e. unsets it or changes it) between the time its set and the time we need it then symbolization will fail. We will ignore this issue for now but a `DCHECK()` is included in the patch that documents this assumption but doesn't check it at runtime to avoid calling `getenv()`. The issue reported in rdar://problem/58789439 manifested as a deadlock during symbolization in the following situation: 1. Before TSan detects an issue something outside of the runtime calls setenv() that sets a new environment variable that wasn't previously set. This triggers a call to malloc() to allocate a new environment array. This uses TSan's normal user-facing allocator. LibC stores this pointer for future use later. 2. TSan detects an issue and tries to launch the symbolizer. When we are in the symbolizer we switch to a different (internal allocator) and then we call setenv() to set a new environment variable. When this happen setenv() sees that it needs to make the environment array larger and calls realloc() on the existing enviroment array because it remembers that it previously allocated memory for it. Calling realloc() fails here because it is being called on a pointer its never seen before. The included test case closely reproduces the originally reported problem but it doesn't replicate the `((kBlockMagic)) == ((((u64*)addr)[0])` assertion failure exactly. This is due to the way TSan's normal allocator allocates the environment array the first time it is allocated. In the test program addr[0] accesses an inaccessible page and raises SIGBUS. If TSan's SIGBUS signal handler is active, the signal is caught and symbolication is attempted again which results in deadlock. In the originally reported problem the pointer is successfully derefenced but then the assert fails due to the provided pointer not coming from the active allocator. When the assert fails TSan tries to symbolicate the stacktrace while already being in the middle of symbolication which results in deadlock. rdar://problem/58789439 Reviewers: kubamracek, yln Subscribers: jfb, #sanitizers, llvm-commits Tags: #sanitizers Differential Revision: https://reviews.llvm.org/D78179
2020-04-15 13:27:49 +08:00
#include <mach/mach.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <util.h>
namespace __sanitizer {
bool DlAddrSymbolizer::SymbolizePC(uptr addr, SymbolizedStack *stack) {
Dl_info info;
int result = dladdr((const void *)addr, &info);
if (!result) return false;
Avoid failing a CHECK in `DlAddrSymbolizer::SymbolizePC`. Summary: It turns out the `CHECK(addr >= reinterpret_cast<upt>(info.dli_saddr)` can fail because on armv7s on iOS 9.3 `dladdr()` returns `info.dli_saddr` with an address larger than the address we provided. We should avoid crashing here because crashing in the middle of reporting an issue is very unhelpful. Instead we now try to compute a function offset if the value we get back from `dladdr()` looks sane, otherwise we don't set the function offset. A test case is included. It's basically a slightly modified version of the existing `test/sanitizer_common/TestCases/Darwin/symbolizer-function-offset-dladdr.cpp` test case that doesn't run on iOS devices right now. More details: In the concrete scenario on armv7s `addr` is `0x2195c870` and the returned `info.dli_saddr` is `0x2195c871`. This what LLDB says when disassembling the code. ``` (lldb) dis -a 0x2195c870 libdyld.dylib`<redacted>: 0x2195c870 <+0>: nop 0x2195c872 <+2>: blx 0x2195c91c ; symbol stub for: exit 0x2195c876 <+6>: trap ``` The value returned by `dladdr()` doesn't make sense because it points into the middle of a instruction. There might also be other bugs lurking here because I noticed that the PCs we gather during stackunwinding (before changing them with `StackTrace::GetPreviousInstructionPc()`) look a little suspicious (e.g. the PC stored for the frame with fail to symbolicate is 0x2195c873) as they don't look properly aligned. This probably warrants further investigation in the future. rdar://problem/65621511 Reviewers: kubamracek, yln Subscribers: kristof.beyls, llvm-commits, #sanitizers Tags: #sanitizers Differential Revision: https://reviews.llvm.org/D84262
2020-07-22 02:54:51 +08:00
// Compute offset if possible. `dladdr()` doesn't always ensure that `addr >=
// sym_addr` so only compute the offset when this holds. Failure to find the
// function offset is not treated as a failure because it might still be
// possible to get the symbol name.
uptr sym_addr = reinterpret_cast<uptr>(info.dli_saddr);
if (addr >= sym_addr) {
stack->info.function_offset = addr - sym_addr;
}
const char *demangled = DemangleSwiftAndCXX(info.dli_sname);
if (!demangled) return false;
stack->info.function = internal_strdup(demangled);
return true;
}
bool DlAddrSymbolizer::SymbolizeData(uptr addr, DataInfo *datainfo) {
Dl_info info;
int result = dladdr((const void *)addr, &info);
if (!result) return false;
const char *demangled = DemangleSwiftAndCXX(info.dli_sname);
datainfo->name = internal_strdup(demangled);
datainfo->start = (uptr)info.dli_saddr;
return true;
}
[Darwin] Fix symbolization for recent simulator runtimes. Summary: Due to sandbox restrictions in the recent versions of the simulator runtime the atos program is no longer able to access the task port of a parent process without additional help. This patch fixes this by registering a task port for the parent process before spawning atos and also tells atos to look for this by setting a special environment variable. This patch is based on an Apple internal fix (rdar://problem/43693565) that unfortunately contained a bug (rdar://problem/58789439) because it used setenv() to set the special environment variable. This is not safe because in certain circumstances this can trigger a call to realloc() which can fail during symbolization leading to deadlock. A test case is included that captures this problem. The approach used to set the necessary environment variable is as follows: 1. Calling `putenv()` early during process init (but late enough that malloc/realloc works) to set a dummy value for the environment variable. 2. Just before `atos` is spawned the storage for the environment variable is modified to contain the correct PID. A flaw with this approach is that if the application messes with the atos environment variable (i.e. unsets it or changes it) between the time its set and the time we need it then symbolization will fail. We will ignore this issue for now but a `DCHECK()` is included in the patch that documents this assumption but doesn't check it at runtime to avoid calling `getenv()`. The issue reported in rdar://problem/58789439 manifested as a deadlock during symbolization in the following situation: 1. Before TSan detects an issue something outside of the runtime calls setenv() that sets a new environment variable that wasn't previously set. This triggers a call to malloc() to allocate a new environment array. This uses TSan's normal user-facing allocator. LibC stores this pointer for future use later. 2. TSan detects an issue and tries to launch the symbolizer. When we are in the symbolizer we switch to a different (internal allocator) and then we call setenv() to set a new environment variable. When this happen setenv() sees that it needs to make the environment array larger and calls realloc() on the existing enviroment array because it remembers that it previously allocated memory for it. Calling realloc() fails here because it is being called on a pointer its never seen before. The included test case closely reproduces the originally reported problem but it doesn't replicate the `((kBlockMagic)) == ((((u64*)addr)[0])` assertion failure exactly. This is due to the way TSan's normal allocator allocates the environment array the first time it is allocated. In the test program addr[0] accesses an inaccessible page and raises SIGBUS. If TSan's SIGBUS signal handler is active, the signal is caught and symbolication is attempted again which results in deadlock. In the originally reported problem the pointer is successfully derefenced but then the assert fails due to the provided pointer not coming from the active allocator. When the assert fails TSan tries to symbolicate the stacktrace while already being in the middle of symbolication which results in deadlock. rdar://problem/58789439 Reviewers: kubamracek, yln Subscribers: jfb, #sanitizers, llvm-commits Tags: #sanitizers Differential Revision: https://reviews.llvm.org/D78179
2020-04-15 13:27:49 +08:00
#define K_ATOS_ENV_VAR "__check_mach_ports_lookup"
// This cannot live in `AtosSymbolizerProcess` because instances of that object
// are allocated by the internal allocator which under ASan is poisoned with
// kAsanInternalHeapMagic.
static char kAtosMachPortEnvEntry[] = K_ATOS_ENV_VAR "=000000000000000";
class AtosSymbolizerProcess final : public SymbolizerProcess {
public:
explicit AtosSymbolizerProcess(const char *path)
: SymbolizerProcess(path, /*use_posix_spawn*/ true) {
pid_str_[0] = '\0';
}
[Darwin] Fix symbolization for recent simulator runtimes. Summary: Due to sandbox restrictions in the recent versions of the simulator runtime the atos program is no longer able to access the task port of a parent process without additional help. This patch fixes this by registering a task port for the parent process before spawning atos and also tells atos to look for this by setting a special environment variable. This patch is based on an Apple internal fix (rdar://problem/43693565) that unfortunately contained a bug (rdar://problem/58789439) because it used setenv() to set the special environment variable. This is not safe because in certain circumstances this can trigger a call to realloc() which can fail during symbolization leading to deadlock. A test case is included that captures this problem. The approach used to set the necessary environment variable is as follows: 1. Calling `putenv()` early during process init (but late enough that malloc/realloc works) to set a dummy value for the environment variable. 2. Just before `atos` is spawned the storage for the environment variable is modified to contain the correct PID. A flaw with this approach is that if the application messes with the atos environment variable (i.e. unsets it or changes it) between the time its set and the time we need it then symbolization will fail. We will ignore this issue for now but a `DCHECK()` is included in the patch that documents this assumption but doesn't check it at runtime to avoid calling `getenv()`. The issue reported in rdar://problem/58789439 manifested as a deadlock during symbolization in the following situation: 1. Before TSan detects an issue something outside of the runtime calls setenv() that sets a new environment variable that wasn't previously set. This triggers a call to malloc() to allocate a new environment array. This uses TSan's normal user-facing allocator. LibC stores this pointer for future use later. 2. TSan detects an issue and tries to launch the symbolizer. When we are in the symbolizer we switch to a different (internal allocator) and then we call setenv() to set a new environment variable. When this happen setenv() sees that it needs to make the environment array larger and calls realloc() on the existing enviroment array because it remembers that it previously allocated memory for it. Calling realloc() fails here because it is being called on a pointer its never seen before. The included test case closely reproduces the originally reported problem but it doesn't replicate the `((kBlockMagic)) == ((((u64*)addr)[0])` assertion failure exactly. This is due to the way TSan's normal allocator allocates the environment array the first time it is allocated. In the test program addr[0] accesses an inaccessible page and raises SIGBUS. If TSan's SIGBUS signal handler is active, the signal is caught and symbolication is attempted again which results in deadlock. In the originally reported problem the pointer is successfully derefenced but then the assert fails due to the provided pointer not coming from the active allocator. When the assert fails TSan tries to symbolicate the stacktrace while already being in the middle of symbolication which results in deadlock. rdar://problem/58789439 Reviewers: kubamracek, yln Subscribers: jfb, #sanitizers, llvm-commits Tags: #sanitizers Differential Revision: https://reviews.llvm.org/D78179
2020-04-15 13:27:49 +08:00
void LateInitialize() {
if (SANITIZER_IOSSIM) {
// `putenv()` may call malloc/realloc so it is only safe to do this
// during LateInitialize() or later (i.e. we can't do this in the
// constructor). We also can't do this in `StartSymbolizerSubprocess()`
// because in TSan we switch allocators when we're symbolizing.
// We use `putenv()` rather than `setenv()` so that we can later directly
// write into the storage without LibC getting involved to change what the
// variable is set to
int result = putenv(kAtosMachPortEnvEntry);
[Darwin] Fix symbolization for recent simulator runtimes. Summary: Due to sandbox restrictions in the recent versions of the simulator runtime the atos program is no longer able to access the task port of a parent process without additional help. This patch fixes this by registering a task port for the parent process before spawning atos and also tells atos to look for this by setting a special environment variable. This patch is based on an Apple internal fix (rdar://problem/43693565) that unfortunately contained a bug (rdar://problem/58789439) because it used setenv() to set the special environment variable. This is not safe because in certain circumstances this can trigger a call to realloc() which can fail during symbolization leading to deadlock. A test case is included that captures this problem. The approach used to set the necessary environment variable is as follows: 1. Calling `putenv()` early during process init (but late enough that malloc/realloc works) to set a dummy value for the environment variable. 2. Just before `atos` is spawned the storage for the environment variable is modified to contain the correct PID. A flaw with this approach is that if the application messes with the atos environment variable (i.e. unsets it or changes it) between the time its set and the time we need it then symbolization will fail. We will ignore this issue for now but a `DCHECK()` is included in the patch that documents this assumption but doesn't check it at runtime to avoid calling `getenv()`. The issue reported in rdar://problem/58789439 manifested as a deadlock during symbolization in the following situation: 1. Before TSan detects an issue something outside of the runtime calls setenv() that sets a new environment variable that wasn't previously set. This triggers a call to malloc() to allocate a new environment array. This uses TSan's normal user-facing allocator. LibC stores this pointer for future use later. 2. TSan detects an issue and tries to launch the symbolizer. When we are in the symbolizer we switch to a different (internal allocator) and then we call setenv() to set a new environment variable. When this happen setenv() sees that it needs to make the environment array larger and calls realloc() on the existing enviroment array because it remembers that it previously allocated memory for it. Calling realloc() fails here because it is being called on a pointer its never seen before. The included test case closely reproduces the originally reported problem but it doesn't replicate the `((kBlockMagic)) == ((((u64*)addr)[0])` assertion failure exactly. This is due to the way TSan's normal allocator allocates the environment array the first time it is allocated. In the test program addr[0] accesses an inaccessible page and raises SIGBUS. If TSan's SIGBUS signal handler is active, the signal is caught and symbolication is attempted again which results in deadlock. In the originally reported problem the pointer is successfully derefenced but then the assert fails due to the provided pointer not coming from the active allocator. When the assert fails TSan tries to symbolicate the stacktrace while already being in the middle of symbolication which results in deadlock. rdar://problem/58789439 Reviewers: kubamracek, yln Subscribers: jfb, #sanitizers, llvm-commits Tags: #sanitizers Differential Revision: https://reviews.llvm.org/D78179
2020-04-15 13:27:49 +08:00
CHECK_EQ(result, 0);
}
}
private:
bool StartSymbolizerSubprocess() override {
// Configure sandbox before starting atos process.
// Put the string command line argument in the object so that it outlives
// the call to GetArgV.
internal_snprintf(pid_str_, sizeof(pid_str_), "%d", internal_getpid());
[Darwin] Fix symbolization for recent simulator runtimes. Summary: Due to sandbox restrictions in the recent versions of the simulator runtime the atos program is no longer able to access the task port of a parent process without additional help. This patch fixes this by registering a task port for the parent process before spawning atos and also tells atos to look for this by setting a special environment variable. This patch is based on an Apple internal fix (rdar://problem/43693565) that unfortunately contained a bug (rdar://problem/58789439) because it used setenv() to set the special environment variable. This is not safe because in certain circumstances this can trigger a call to realloc() which can fail during symbolization leading to deadlock. A test case is included that captures this problem. The approach used to set the necessary environment variable is as follows: 1. Calling `putenv()` early during process init (but late enough that malloc/realloc works) to set a dummy value for the environment variable. 2. Just before `atos` is spawned the storage for the environment variable is modified to contain the correct PID. A flaw with this approach is that if the application messes with the atos environment variable (i.e. unsets it or changes it) between the time its set and the time we need it then symbolization will fail. We will ignore this issue for now but a `DCHECK()` is included in the patch that documents this assumption but doesn't check it at runtime to avoid calling `getenv()`. The issue reported in rdar://problem/58789439 manifested as a deadlock during symbolization in the following situation: 1. Before TSan detects an issue something outside of the runtime calls setenv() that sets a new environment variable that wasn't previously set. This triggers a call to malloc() to allocate a new environment array. This uses TSan's normal user-facing allocator. LibC stores this pointer for future use later. 2. TSan detects an issue and tries to launch the symbolizer. When we are in the symbolizer we switch to a different (internal allocator) and then we call setenv() to set a new environment variable. When this happen setenv() sees that it needs to make the environment array larger and calls realloc() on the existing enviroment array because it remembers that it previously allocated memory for it. Calling realloc() fails here because it is being called on a pointer its never seen before. The included test case closely reproduces the originally reported problem but it doesn't replicate the `((kBlockMagic)) == ((((u64*)addr)[0])` assertion failure exactly. This is due to the way TSan's normal allocator allocates the environment array the first time it is allocated. In the test program addr[0] accesses an inaccessible page and raises SIGBUS. If TSan's SIGBUS signal handler is active, the signal is caught and symbolication is attempted again which results in deadlock. In the originally reported problem the pointer is successfully derefenced but then the assert fails due to the provided pointer not coming from the active allocator. When the assert fails TSan tries to symbolicate the stacktrace while already being in the middle of symbolication which results in deadlock. rdar://problem/58789439 Reviewers: kubamracek, yln Subscribers: jfb, #sanitizers, llvm-commits Tags: #sanitizers Differential Revision: https://reviews.llvm.org/D78179
2020-04-15 13:27:49 +08:00
if (SANITIZER_IOSSIM) {
// `atos` in the simulator is restricted in its ability to retrieve the
// task port for the target process (us) so we need to do extra work
// to pass our task port to it.
mach_port_t ports[]{mach_task_self()};
kern_return_t ret =
mach_ports_register(mach_task_self(), ports, /*count=*/1);
CHECK_EQ(ret, KERN_SUCCESS);
// Set environment variable that signals to `atos` that it should look
// for our task port. We can't call `setenv()` here because it might call
// malloc/realloc. To avoid that we instead update the
// `mach_port_env_var_entry_` variable with our current PID.
uptr count = internal_snprintf(kAtosMachPortEnvEntry,
sizeof(kAtosMachPortEnvEntry),
[Darwin] Fix symbolization for recent simulator runtimes. Summary: Due to sandbox restrictions in the recent versions of the simulator runtime the atos program is no longer able to access the task port of a parent process without additional help. This patch fixes this by registering a task port for the parent process before spawning atos and also tells atos to look for this by setting a special environment variable. This patch is based on an Apple internal fix (rdar://problem/43693565) that unfortunately contained a bug (rdar://problem/58789439) because it used setenv() to set the special environment variable. This is not safe because in certain circumstances this can trigger a call to realloc() which can fail during symbolization leading to deadlock. A test case is included that captures this problem. The approach used to set the necessary environment variable is as follows: 1. Calling `putenv()` early during process init (but late enough that malloc/realloc works) to set a dummy value for the environment variable. 2. Just before `atos` is spawned the storage for the environment variable is modified to contain the correct PID. A flaw with this approach is that if the application messes with the atos environment variable (i.e. unsets it or changes it) between the time its set and the time we need it then symbolization will fail. We will ignore this issue for now but a `DCHECK()` is included in the patch that documents this assumption but doesn't check it at runtime to avoid calling `getenv()`. The issue reported in rdar://problem/58789439 manifested as a deadlock during symbolization in the following situation: 1. Before TSan detects an issue something outside of the runtime calls setenv() that sets a new environment variable that wasn't previously set. This triggers a call to malloc() to allocate a new environment array. This uses TSan's normal user-facing allocator. LibC stores this pointer for future use later. 2. TSan detects an issue and tries to launch the symbolizer. When we are in the symbolizer we switch to a different (internal allocator) and then we call setenv() to set a new environment variable. When this happen setenv() sees that it needs to make the environment array larger and calls realloc() on the existing enviroment array because it remembers that it previously allocated memory for it. Calling realloc() fails here because it is being called on a pointer its never seen before. The included test case closely reproduces the originally reported problem but it doesn't replicate the `((kBlockMagic)) == ((((u64*)addr)[0])` assertion failure exactly. This is due to the way TSan's normal allocator allocates the environment array the first time it is allocated. In the test program addr[0] accesses an inaccessible page and raises SIGBUS. If TSan's SIGBUS signal handler is active, the signal is caught and symbolication is attempted again which results in deadlock. In the originally reported problem the pointer is successfully derefenced but then the assert fails due to the provided pointer not coming from the active allocator. When the assert fails TSan tries to symbolicate the stacktrace while already being in the middle of symbolication which results in deadlock. rdar://problem/58789439 Reviewers: kubamracek, yln Subscribers: jfb, #sanitizers, llvm-commits Tags: #sanitizers Differential Revision: https://reviews.llvm.org/D78179
2020-04-15 13:27:49 +08:00
K_ATOS_ENV_VAR "=%s", pid_str_);
CHECK_GE(count, sizeof(K_ATOS_ENV_VAR) + internal_strlen(pid_str_));
// Document our assumption but without calling `getenv()` in normal
// builds.
DCHECK(getenv(K_ATOS_ENV_VAR));
[Darwin] Fix symbolization for recent simulator runtimes. Summary: Due to sandbox restrictions in the recent versions of the simulator runtime the atos program is no longer able to access the task port of a parent process without additional help. This patch fixes this by registering a task port for the parent process before spawning atos and also tells atos to look for this by setting a special environment variable. This patch is based on an Apple internal fix (rdar://problem/43693565) that unfortunately contained a bug (rdar://problem/58789439) because it used setenv() to set the special environment variable. This is not safe because in certain circumstances this can trigger a call to realloc() which can fail during symbolization leading to deadlock. A test case is included that captures this problem. The approach used to set the necessary environment variable is as follows: 1. Calling `putenv()` early during process init (but late enough that malloc/realloc works) to set a dummy value for the environment variable. 2. Just before `atos` is spawned the storage for the environment variable is modified to contain the correct PID. A flaw with this approach is that if the application messes with the atos environment variable (i.e. unsets it or changes it) between the time its set and the time we need it then symbolization will fail. We will ignore this issue for now but a `DCHECK()` is included in the patch that documents this assumption but doesn't check it at runtime to avoid calling `getenv()`. The issue reported in rdar://problem/58789439 manifested as a deadlock during symbolization in the following situation: 1. Before TSan detects an issue something outside of the runtime calls setenv() that sets a new environment variable that wasn't previously set. This triggers a call to malloc() to allocate a new environment array. This uses TSan's normal user-facing allocator. LibC stores this pointer for future use later. 2. TSan detects an issue and tries to launch the symbolizer. When we are in the symbolizer we switch to a different (internal allocator) and then we call setenv() to set a new environment variable. When this happen setenv() sees that it needs to make the environment array larger and calls realloc() on the existing enviroment array because it remembers that it previously allocated memory for it. Calling realloc() fails here because it is being called on a pointer its never seen before. The included test case closely reproduces the originally reported problem but it doesn't replicate the `((kBlockMagic)) == ((((u64*)addr)[0])` assertion failure exactly. This is due to the way TSan's normal allocator allocates the environment array the first time it is allocated. In the test program addr[0] accesses an inaccessible page and raises SIGBUS. If TSan's SIGBUS signal handler is active, the signal is caught and symbolication is attempted again which results in deadlock. In the originally reported problem the pointer is successfully derefenced but then the assert fails due to the provided pointer not coming from the active allocator. When the assert fails TSan tries to symbolicate the stacktrace while already being in the middle of symbolication which results in deadlock. rdar://problem/58789439 Reviewers: kubamracek, yln Subscribers: jfb, #sanitizers, llvm-commits Tags: #sanitizers Differential Revision: https://reviews.llvm.org/D78179
2020-04-15 13:27:49 +08:00
DCHECK_EQ(internal_strcmp(getenv(K_ATOS_ENV_VAR), pid_str_), 0);
}
return SymbolizerProcess::StartSymbolizerSubprocess();
}
bool ReachedEndOfOutput(const char *buffer, uptr length) const override {
return (length >= 1 && buffer[length - 1] == '\n');
}
void GetArgV(const char *path_to_binary,
const char *(&argv)[kArgVMax]) const override {
int i = 0;
argv[i++] = path_to_binary;
argv[i++] = "-p";
argv[i++] = &pid_str_[0];
if (GetMacosAlignedVersion() == MacosVersion(10, 9)) {
// On Mavericks atos prints a deprecation warning which we suppress by
// passing -d. The warning isn't present on other OSX versions, even the
// newer ones.
argv[i++] = "-d";
}
argv[i++] = nullptr;
}
char pid_str_[16];
// Space for `\0` in `K_ATOS_ENV_VAR` is reused for `=`.
static_assert(sizeof(kAtosMachPortEnvEntry) ==
(sizeof(K_ATOS_ENV_VAR) + sizeof(pid_str_)),
"sizes should match");
};
[Darwin] Fix symbolization for recent simulator runtimes. Summary: Due to sandbox restrictions in the recent versions of the simulator runtime the atos program is no longer able to access the task port of a parent process without additional help. This patch fixes this by registering a task port for the parent process before spawning atos and also tells atos to look for this by setting a special environment variable. This patch is based on an Apple internal fix (rdar://problem/43693565) that unfortunately contained a bug (rdar://problem/58789439) because it used setenv() to set the special environment variable. This is not safe because in certain circumstances this can trigger a call to realloc() which can fail during symbolization leading to deadlock. A test case is included that captures this problem. The approach used to set the necessary environment variable is as follows: 1. Calling `putenv()` early during process init (but late enough that malloc/realloc works) to set a dummy value for the environment variable. 2. Just before `atos` is spawned the storage for the environment variable is modified to contain the correct PID. A flaw with this approach is that if the application messes with the atos environment variable (i.e. unsets it or changes it) between the time its set and the time we need it then symbolization will fail. We will ignore this issue for now but a `DCHECK()` is included in the patch that documents this assumption but doesn't check it at runtime to avoid calling `getenv()`. The issue reported in rdar://problem/58789439 manifested as a deadlock during symbolization in the following situation: 1. Before TSan detects an issue something outside of the runtime calls setenv() that sets a new environment variable that wasn't previously set. This triggers a call to malloc() to allocate a new environment array. This uses TSan's normal user-facing allocator. LibC stores this pointer for future use later. 2. TSan detects an issue and tries to launch the symbolizer. When we are in the symbolizer we switch to a different (internal allocator) and then we call setenv() to set a new environment variable. When this happen setenv() sees that it needs to make the environment array larger and calls realloc() on the existing enviroment array because it remembers that it previously allocated memory for it. Calling realloc() fails here because it is being called on a pointer its never seen before. The included test case closely reproduces the originally reported problem but it doesn't replicate the `((kBlockMagic)) == ((((u64*)addr)[0])` assertion failure exactly. This is due to the way TSan's normal allocator allocates the environment array the first time it is allocated. In the test program addr[0] accesses an inaccessible page and raises SIGBUS. If TSan's SIGBUS signal handler is active, the signal is caught and symbolication is attempted again which results in deadlock. In the originally reported problem the pointer is successfully derefenced but then the assert fails due to the provided pointer not coming from the active allocator. When the assert fails TSan tries to symbolicate the stacktrace while already being in the middle of symbolication which results in deadlock. rdar://problem/58789439 Reviewers: kubamracek, yln Subscribers: jfb, #sanitizers, llvm-commits Tags: #sanitizers Differential Revision: https://reviews.llvm.org/D78179
2020-04-15 13:27:49 +08:00
#undef K_ATOS_ENV_VAR
static bool ParseCommandOutput(const char *str, uptr addr, char **out_name,
char **out_module, char **out_file, uptr *line,
uptr *start_address) {
// Trim ending newlines.
char *trim;
ExtractTokenUpToDelimiter(str, "\n", &trim);
// The line from `atos` is in one of these formats:
// myfunction (in library.dylib) (sourcefile.c:17)
// myfunction (in library.dylib) + 0x1fe
// myfunction (in library.dylib) + 15
// 0xdeadbeef (in library.dylib) + 0x1fe
// 0xdeadbeef (in library.dylib) + 15
// 0xdeadbeef (in library.dylib)
// 0xdeadbeef
const char *rest = trim;
char *symbol_name;
rest = ExtractTokenUpToDelimiter(rest, " (in ", &symbol_name);
if (rest[0] == '\0') {
InternalFree(symbol_name);
InternalFree(trim);
return false;
}
if (internal_strncmp(symbol_name, "0x", 2) != 0)
*out_name = symbol_name;
else
InternalFree(symbol_name);
rest = ExtractTokenUpToDelimiter(rest, ") ", out_module);
if (rest[0] == '(') {
if (out_file) {
rest++;
rest = ExtractTokenUpToDelimiter(rest, ":", out_file);
char *extracted_line_number;
rest = ExtractTokenUpToDelimiter(rest, ")", &extracted_line_number);
if (line) *line = (uptr)internal_atoll(extracted_line_number);
InternalFree(extracted_line_number);
}
} else if (rest[0] == '+') {
rest += 2;
uptr offset = internal_atoll(rest);
if (start_address) *start_address = addr - offset;
}
InternalFree(trim);
return true;
}
AtosSymbolizer::AtosSymbolizer(const char *path, LowLevelAllocator *allocator)
: process_(new (*allocator) AtosSymbolizerProcess(path)) {}
bool AtosSymbolizer::SymbolizePC(uptr addr, SymbolizedStack *stack) {
if (!process_) return false;
if (addr == 0) return false;
char command[32];
internal_snprintf(command, sizeof(command), "0x%zx\n", addr);
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, &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);
}
Avoid failing a CHECK in `DlAddrSymbolizer::SymbolizePC`. Summary: It turns out the `CHECK(addr >= reinterpret_cast<upt>(info.dli_saddr)` can fail because on armv7s on iOS 9.3 `dladdr()` returns `info.dli_saddr` with an address larger than the address we provided. We should avoid crashing here because crashing in the middle of reporting an issue is very unhelpful. Instead we now try to compute a function offset if the value we get back from `dladdr()` looks sane, otherwise we don't set the function offset. A test case is included. It's basically a slightly modified version of the existing `test/sanitizer_common/TestCases/Darwin/symbolizer-function-offset-dladdr.cpp` test case that doesn't run on iOS devices right now. More details: In the concrete scenario on armv7s `addr` is `0x2195c870` and the returned `info.dli_saddr` is `0x2195c871`. This what LLDB says when disassembling the code. ``` (lldb) dis -a 0x2195c870 libdyld.dylib`<redacted>: 0x2195c870 <+0>: nop 0x2195c872 <+2>: blx 0x2195c91c ; symbol stub for: exit 0x2195c876 <+6>: trap ``` The value returned by `dladdr()` doesn't make sense because it points into the middle of a instruction. There might also be other bugs lurking here because I noticed that the PCs we gather during stackunwinding (before changing them with `StackTrace::GetPreviousInstructionPc()`) look a little suspicious (e.g. the PC stored for the frame with fail to symbolicate is 0x2195c873) as they don't look properly aligned. This probably warrants further investigation in the future. rdar://problem/65621511 Reviewers: kubamracek, yln Subscribers: kristof.beyls, llvm-commits, #sanitizers Tags: #sanitizers Differential Revision: https://reviews.llvm.org/D84262
2020-07-22 02:54:51 +08:00
// Only assign to `function_offset` if we were able to get the function's
// start address and we got a sensible `start_address` (dladdr doesn't always
// ensure that `addr >= sym_addr`).
if (start_address != AddressInfo::kUnknown && addr >= start_address) {
stack->info.function_offset = addr - start_address;
}
return true;
}
bool AtosSymbolizer::SymbolizeData(uptr addr, DataInfo *info) {
if (!process_) return false;
char command[32];
internal_snprintf(command, sizeof(command), "0x%zx\n", addr);
const char *buf = process_->SendCommand(command);
if (!buf) return false;
if (!ParseCommandOutput(buf, addr, &info->name, &info->module, nullptr,
nullptr, &info->start)) {
process_ = nullptr;
return false;
}
return true;
}
[Darwin] Fix symbolization for recent simulator runtimes. Summary: Due to sandbox restrictions in the recent versions of the simulator runtime the atos program is no longer able to access the task port of a parent process without additional help. This patch fixes this by registering a task port for the parent process before spawning atos and also tells atos to look for this by setting a special environment variable. This patch is based on an Apple internal fix (rdar://problem/43693565) that unfortunately contained a bug (rdar://problem/58789439) because it used setenv() to set the special environment variable. This is not safe because in certain circumstances this can trigger a call to realloc() which can fail during symbolization leading to deadlock. A test case is included that captures this problem. The approach used to set the necessary environment variable is as follows: 1. Calling `putenv()` early during process init (but late enough that malloc/realloc works) to set a dummy value for the environment variable. 2. Just before `atos` is spawned the storage for the environment variable is modified to contain the correct PID. A flaw with this approach is that if the application messes with the atos environment variable (i.e. unsets it or changes it) between the time its set and the time we need it then symbolization will fail. We will ignore this issue for now but a `DCHECK()` is included in the patch that documents this assumption but doesn't check it at runtime to avoid calling `getenv()`. The issue reported in rdar://problem/58789439 manifested as a deadlock during symbolization in the following situation: 1. Before TSan detects an issue something outside of the runtime calls setenv() that sets a new environment variable that wasn't previously set. This triggers a call to malloc() to allocate a new environment array. This uses TSan's normal user-facing allocator. LibC stores this pointer for future use later. 2. TSan detects an issue and tries to launch the symbolizer. When we are in the symbolizer we switch to a different (internal allocator) and then we call setenv() to set a new environment variable. When this happen setenv() sees that it needs to make the environment array larger and calls realloc() on the existing enviroment array because it remembers that it previously allocated memory for it. Calling realloc() fails here because it is being called on a pointer its never seen before. The included test case closely reproduces the originally reported problem but it doesn't replicate the `((kBlockMagic)) == ((((u64*)addr)[0])` assertion failure exactly. This is due to the way TSan's normal allocator allocates the environment array the first time it is allocated. In the test program addr[0] accesses an inaccessible page and raises SIGBUS. If TSan's SIGBUS signal handler is active, the signal is caught and symbolication is attempted again which results in deadlock. In the originally reported problem the pointer is successfully derefenced but then the assert fails due to the provided pointer not coming from the active allocator. When the assert fails TSan tries to symbolicate the stacktrace while already being in the middle of symbolication which results in deadlock. rdar://problem/58789439 Reviewers: kubamracek, yln Subscribers: jfb, #sanitizers, llvm-commits Tags: #sanitizers Differential Revision: https://reviews.llvm.org/D78179
2020-04-15 13:27:49 +08:00
void AtosSymbolizer::LateInitialize() { process_->LateInitialize(); }
} // namespace __sanitizer
#endif // SANITIZER_MAC