//===-- sanitizer_symbolizer_posix_libcdep.cc -----------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file is shared between AddressSanitizer and ThreadSanitizer
// run-time libraries.
// POSIX-specific implementation of symbolizer parts.
//===----------------------------------------------------------------------===//
#include "sanitizer_platform.h"
#if SANITIZER_POSIX
#include "sanitizer_allocator_internal.h"
#include "sanitizer_common.h"
#include "sanitizer_flags.h"
#include "sanitizer_internal_defs.h"
#include "sanitizer_linux.h"
#include "sanitizer_placement_new.h"
#include "sanitizer_procmaps.h"
#include "sanitizer_symbolizer.h"
#include "sanitizer_symbolizer_libbacktrace.h"
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
// C++ demangling function, as required by Itanium C++ ABI. This is weak,
// because we do not require a C++ ABI library to be linked to a program
// using sanitizers; if it's not present, we'll just use the mangled name.
namespace __cxxabiv1 {
extern "C" SANITIZER_WEAK_ATTRIBUTE
char *__cxa_demangle(const char *mangled, char *buffer,
size_t *length, int *status);
}
namespace __sanitizer {
// Attempts to demangle the name via __cxa_demangle from __cxxabiv1.
static const char *DemangleCXXABI(const char *name) {
// FIXME: __cxa_demangle aggressively insists on allocating memory.
// There's not much we can do about that, short of providing our
// own demangler (libc++abi's implementation could be adapted so that
// it does not allocate). For now, we just call it anyway, and we leak
// the returned value.
if (__cxxabiv1::__cxa_demangle)
if (const char *demangled_name =
__cxxabiv1::__cxa_demangle(name, 0, 0, 0))
return demangled_name;
return name;
}
// Extracts the prefix of "str" that consists of any characters not
// present in "delims" string, and copies this prefix to "result", allocating
// space for it.
// Returns a pointer to "str" after skipping extracted prefix and first
// delimiter char.
static const char *ExtractToken(const char *str, const char *delims,
char **result) {
uptr prefix_len = internal_strcspn(str, delims);
*result = (char*)InternalAlloc(prefix_len + 1);
internal_memcpy(*result, str, prefix_len);
(*result)[prefix_len] = '\0';
const char *prefix_end = str + prefix_len;
if (*prefix_end != '\0') prefix_end++;
return prefix_end;
}
// Same as ExtractToken, but converts extracted token to integer.
static const char *ExtractInt(const char *str, const char *delims,
int *result) {
char *buff;
const char *ret = ExtractToken(str, delims, &buff);
if (buff != 0) {
*result = (int)internal_atoll(buff);
}
InternalFree(buff);
return ret;
}
static const char *ExtractUptr(const char *str, const char *delims,
uptr *result) {
char *buff;
const char *ret = ExtractToken(str, delims, &buff);
if (buff != 0) {
*result = (uptr)internal_atoll(buff);
}
InternalFree(buff);
return ret;
}
class ExternalSymbolizerInterface {
public:
// Can't declare pure virtual functions in sanitizer runtimes:
// __cxa_pure_virtual might be unavailable.
virtual char *SendCommand(bool is_data, const char *module_name,
uptr module_offset) {
UNIMPLEMENTED();
}
};
// SymbolizerProcess encapsulates communication between the tool and
// external symbolizer program, running in a different subprocess.
// SymbolizerProcess may not be used from two threads simultaneously.
class SymbolizerProcess : public ExternalSymbolizerInterface {
public:
explicit SymbolizerProcess(const char *path)
: path_(path),
input_fd_(kInvalidFd),
output_fd_(kInvalidFd),
times_restarted_(0),
failed_to_start_(false),
reported_invalid_path_(false) {
CHECK(path_);
CHECK_NE(path_[0], '\0');
}
char *SendCommand(bool is_data, const char *module_name, uptr module_offset) {
for (; times_restarted_ < kMaxTimesRestarted; times_restarted_++) {
// Start or restart symbolizer if we failed to send command to it.
if (char *res = SendCommandImpl(is_data, module_name, module_offset))
return res;
Restart();
}
if (!failed_to_start_) {
Report("WARNING: Failed to use and restart external symbolizer!\n");
failed_to_start_ = true;
}
return 0;
}
private:
bool Restart() {
if (input_fd_ != kInvalidFd)
internal_close(input_fd_);
if (output_fd_ != kInvalidFd)
internal_close(output_fd_);
return StartSymbolizerSubprocess();
}
char *SendCommandImpl(bool is_data, const char *module_name,
uptr module_offset) {
if (input_fd_ == kInvalidFd || output_fd_ == kInvalidFd)
return 0;
CHECK(module_name);
if (!RenderInputCommand(buffer_, kBufferSize, is_data, module_name,
module_offset))
return 0;
if (!writeToSymbolizer(buffer_, internal_strlen(buffer_)))
return 0;
if (!readFromSymbolizer(buffer_, kBufferSize))
return 0;
return buffer_;
}
bool readFromSymbolizer(char *buffer, uptr max_length) {
if (max_length == 0)
return true;
uptr read_len = 0;
while (true) {
uptr just_read = internal_read(input_fd_, buffer + read_len,
max_length - read_len - 1);
// We can't read 0 bytes, as we don't expect external symbolizer to close
// its stdout.
if (just_read == 0 || just_read == (uptr)-1) {
Report("WARNING: Can't read from symbolizer at fd %d\n", input_fd_);
return false;
}
read_len += just_read;
if (ReachedEndOfOutput(buffer, read_len))
break;
}
buffer[read_len] = '\0';
return true;
}
bool writeToSymbolizer(const char *buffer, uptr length) {
if (length == 0)
return true;
uptr write_len = internal_write(output_fd_, buffer, length);
if (write_len == 0 || write_len == (uptr)-1) {
Report("WARNING: Can't write to symbolizer at fd %d\n", output_fd_);
return false;
}
return true;
}
bool StartSymbolizerSubprocess() {
if (!FileExists(path_)) {
if (!reported_invalid_path_) {
Report("WARNING: invalid path to external symbolizer!\n");
reported_invalid_path_ = true;
}
return false;
}
int *infd = NULL;
int *outfd = NULL;
// The client program may close its stdin and/or stdout and/or stderr
// thus allowing socketpair to reuse file descriptors 0, 1 or 2.
// In this case the communication between the forked processes may be
// broken if either the parent or the child tries to close or duplicate
// these descriptors. The loop below produces two pairs of file
// descriptors, each greater than 2 (stderr).
int sock_pair[5][2];
for (int i = 0; i < 5; i++) {
if (pipe(sock_pair[i]) == -1) {
for (int j = 0; j < i; j++) {
internal_close(sock_pair[j][0]);
internal_close(sock_pair[j][1]);
}
Report("WARNING: Can't create a socket pair to start "
"external symbolizer (errno: %d)\n", errno);
return false;
} else if (sock_pair[i][0] > 2 && sock_pair[i][1] > 2) {
if (infd == NULL) {
infd = sock_pair[i];
} else {
outfd = sock_pair[i];
for (int j = 0; j < i; j++) {
if (sock_pair[j] == infd) continue;
internal_close(sock_pair[j][0]);
internal_close(sock_pair[j][1]);
}
break;
}
}
}
CHECK(infd);
CHECK(outfd);
// Real fork() may call user callbacks registered with pthread_atfork().
int pid = internal_fork();
if (pid == -1) {
// Fork() failed.
internal_close(infd[0]);
internal_close(infd[1]);
internal_close(outfd[0]);
internal_close(outfd[1]);
Report("WARNING: failed to fork external symbolizer "
" (errno: %d)\n", errno);
return false;
} else if (pid == 0) {
// Child subprocess.
internal_close(STDOUT_FILENO);
internal_close(STDIN_FILENO);
internal_dup2(outfd[0], STDIN_FILENO);
internal_dup2(infd[1], STDOUT_FILENO);
internal_close(outfd[0]);
internal_close(outfd[1]);
internal_close(infd[0]);
internal_close(infd[1]);
for (int fd = sysconf(_SC_OPEN_MAX); fd > 2; fd--)
internal_close(fd);
ExecuteWithDefaultArgs(path_);
internal__exit(1);
}
// Continue execution in parent process.
internal_close(outfd[0]);
internal_close(infd[1]);
input_fd_ = infd[0];
output_fd_ = outfd[1];
// Check that symbolizer subprocess started successfully.
int pid_status;
SleepForMillis(kSymbolizerStartupTimeMillis);
int exited_pid = waitpid(pid, &pid_status, WNOHANG);
if (exited_pid != 0) {
// Either waitpid failed, or child has already exited.
Report("WARNING: external symbolizer didn't start up correctly!\n");
return false;
}
return true;
}
virtual bool RenderInputCommand(char *buffer, uptr max_length, bool is_data,
const char *module_name,
uptr module_offset) const {
UNIMPLEMENTED();
}
virtual bool ReachedEndOfOutput(const char *buffer, uptr length) const {
UNIMPLEMENTED();
}
virtual void ExecuteWithDefaultArgs(const char *path_to_binary) const {
UNIMPLEMENTED();
}
const char *path_;
int input_fd_;
int output_fd_;
static const uptr kBufferSize = 16 * 1024;
char buffer_[kBufferSize];
static const uptr kMaxTimesRestarted = 5;
static const int kSymbolizerStartupTimeMillis = 10;
uptr times_restarted_;
bool failed_to_start_;
bool reported_invalid_path_;
};
// For now we assume the following protocol:
// For each request of the form
// <module_name> <module_offset>
// passed to STDIN, external symbolizer prints to STDOUT response:
// <function_name>
// <file_name>:<line_number>:<column_number>
// <function_name>
// <file_name>:<line_number>:<column_number>
// ...
// <empty line>
class LLVMSymbolizerProcess : public SymbolizerProcess {
public:
explicit LLVMSymbolizerProcess(const char *path) : SymbolizerProcess(path) {}
private:
bool RenderInputCommand(char *buffer, uptr max_length, bool is_data,
const char *module_name, uptr module_offset) const {
internal_snprintf(buffer, max_length, "%s\"%s\" 0x%zx\n",
is_data ? "DATA " : "", module_name, module_offset);
return true;
}
bool ReachedEndOfOutput(const char *buffer, uptr length) const {
// Empty line marks the end of llvm-symbolizer output.
return length >= 2 && buffer[length - 1] == '\n' &&
buffer[length - 2] == '\n';
}
void ExecuteWithDefaultArgs(const char *path_to_binary) const {
#if defined(__x86_64__)
const char* const kSymbolizerArch = "--default-arch=x86_64";
#elif defined(__i386__)
const char* const kSymbolizerArch = "--default-arch=i386";
#elif defined(__powerpc64__)
const char* const kSymbolizerArch = "--default-arch=powerpc64";
#else
const char* const kSymbolizerArch = "--default-arch=unknown";
#endif
execl(path_to_binary, path_to_binary, kSymbolizerArch, (char *)0);
}
};
class Addr2LineProcess : public SymbolizerProcess {
public:
Addr2LineProcess(const char *path, const char *module_name)
: SymbolizerProcess(path), module_name_(internal_strdup(module_name)) {}
const char *module_name() const { return module_name_; }
private:
bool RenderInputCommand(char *buffer, uptr max_length, bool is_data,
const char *module_name, uptr module_offset) const {
if (is_data)
return false;
CHECK_EQ(0, internal_strcmp(module_name, module_name_));
internal_snprintf(buffer, max_length, "0x%zx\n", module_offset);
return true;
}
bool ReachedEndOfOutput(const char *buffer, uptr length) const {
// Output should consist of two lines.
int num_lines = 0;
for (uptr i = 0; i < length; ++i) {
if (buffer[i] == '\n')
num_lines++;
if (num_lines >= 2)
return true;
}
return false;
}
void ExecuteWithDefaultArgs(const char *path_to_binary) const {
execl(path_to_binary, path_to_binary, "-Cfe", module_name_, (char *)0);
}
const char *module_name_; // Owned, leaked.
};
class Addr2LinePool : public ExternalSymbolizerInterface {
public:
explicit Addr2LinePool(const char *addr2line_path,
LowLevelAllocator *allocator)
: addr2line_path_(addr2line_path), allocator_(allocator),
addr2line_pool_(16) {}
char *SendCommand(bool is_data, const char *module_name, uptr module_offset) {
if (is_data)
return 0;
Addr2LineProcess *addr2line = 0;
for (uptr i = 0; i < addr2line_pool_.size(); ++i) {
if (0 ==
internal_strcmp(module_name, addr2line_pool_[i]->module_name())) {
addr2line = addr2line_pool_[i];
break;
}
}
if (!addr2line) {
addr2line =
new(*allocator_) Addr2LineProcess(addr2line_path_, module_name);
addr2line_pool_.push_back(addr2line);
}
return addr2line->SendCommand(is_data, module_name, module_offset);
}
private:
const char *addr2line_path_;
LowLevelAllocator *allocator_;
InternalMmapVector<Addr2LineProcess*> addr2line_pool_;
};
#if SANITIZER_SUPPORTS_WEAK_HOOKS
extern "C" {
SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE
bool __sanitizer_symbolize_code(const char *ModuleName, u64 ModuleOffset,
char *Buffer, int MaxLength);
SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE
bool __sanitizer_symbolize_data(const char *ModuleName, u64 ModuleOffset,
char *Buffer, int MaxLength);
SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE
void __sanitizer_symbolize_flush();
SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE
int __sanitizer_symbolize_demangle(const char *Name, char *Buffer,
int MaxLength);
} // extern "C"
class InternalSymbolizer {
public:
typedef bool (*SanitizerSymbolizeFn)(const char*, u64, char*, int);
static InternalSymbolizer *get(LowLevelAllocator *alloc) {
if (__sanitizer_symbolize_code != 0 &&
__sanitizer_symbolize_data != 0) {
return new(*alloc) InternalSymbolizer();
}
return 0;
}
char *SendCommand(bool is_data, const char *module_name, uptr module_offset) {
SanitizerSymbolizeFn symbolize_fn = is_data ? __sanitizer_symbolize_data
: __sanitizer_symbolize_code;
if (symbolize_fn(module_name, module_offset, buffer_, kBufferSize))
return buffer_;
return 0;
}
void Flush() {
if (__sanitizer_symbolize_flush)
__sanitizer_symbolize_flush();
}
const char *Demangle(const char *name) {
if (__sanitizer_symbolize_demangle) {
for (uptr res_length = 1024;
res_length <= InternalSizeClassMap::kMaxSize;) {
char *res_buff = static_cast<char*>(InternalAlloc(res_length));
uptr req_length =
__sanitizer_symbolize_demangle(name, res_buff, res_length);
if (req_length > res_length) {
res_length = req_length + 1;
InternalFree(res_buff);
continue;
}
return res_buff;
}
}
return name;
}
private:
InternalSymbolizer() { }
static const int kBufferSize = 16 * 1024;
static const int kMaxDemangledNameSize = 1024;
char buffer_[kBufferSize];
};
#else // SANITIZER_SUPPORTS_WEAK_HOOKS
class InternalSymbolizer {
public:
static InternalSymbolizer *get(LowLevelAllocator *alloc) { return 0; }
char *SendCommand(bool is_data, const char *module_name, uptr module_offset) {
return 0;
}
void Flush() { }
const char *Demangle(const char *name) { return name; }
};
#endif // SANITIZER_SUPPORTS_WEAK_HOOKS
class POSIXSymbolizer : public Symbolizer {
public:
POSIXSymbolizer(ExternalSymbolizerInterface *external_symbolizer,
InternalSymbolizer *internal_symbolizer,
LibbacktraceSymbolizer *libbacktrace_symbolizer)
: Symbolizer(),
external_symbolizer_(external_symbolizer),
internal_symbolizer_(internal_symbolizer),
libbacktrace_symbolizer_(libbacktrace_symbolizer) {}
uptr SymbolizePC(uptr addr, AddressInfo *frames, uptr max_frames) {
BlockingMutexLock l(&mu_);
if (max_frames == 0)
return 0;
const char *module_name;
uptr module_offset;
if (!FindModuleNameAndOffsetForAddress(addr, &module_name, &module_offset))
return 0;
// First, try to use libbacktrace symbolizer (if it's available).
if (libbacktrace_symbolizer_ != 0) {
mu_.CheckLocked();
uptr res = libbacktrace_symbolizer_->SymbolizeCode(
addr, frames, max_frames, module_name, module_offset);
if (res > 0)
return res;
}
const char *str = SendCommand(false, module_name, module_offset);
if (str == 0) {
// Symbolizer was not initialized or failed. Fill only data
// about module name and offset.
AddressInfo *info = &frames[0];
info->Clear();
info->FillAddressAndModuleInfo(addr, module_name, module_offset);
return 1;
}
uptr frame_id = 0;
for (frame_id = 0; frame_id < max_frames; frame_id++) {
AddressInfo *info = &frames[frame_id];
char *function_name = 0;
str = ExtractToken(str, "\n", &function_name);
CHECK(function_name);
if (function_name[0] == '\0') {
// There are no more frames.
break;
}
info->Clear();
info->FillAddressAndModuleInfo(addr, module_name, module_offset);
info->function = function_name;
// Parse <file>:<line>:<column> buffer.
char *file_line_info = 0;
str = ExtractToken(str, "\n", &file_line_info);
CHECK(file_line_info);
const char *line_info = ExtractToken(file_line_info, ":", &info->file);
line_info = ExtractInt(line_info, ":", &info->line);
line_info = ExtractInt(line_info, "", &info->column);
InternalFree(file_line_info);
// Functions and filenames can be "??", in which case we write 0
// to address info to mark that names are unknown.
if (0 == internal_strcmp(info->function, "??")) {
InternalFree(info->function);
info->function = 0;
}
if (0 == internal_strcmp(info->file, "??")) {
InternalFree(info->file);
info->file = 0;
}
}
if (frame_id == 0) {
// Make sure we return at least one frame.
AddressInfo *info = &frames[0];
info->Clear();
info->FillAddressAndModuleInfo(addr, module_name, module_offset);
frame_id = 1;
}
return frame_id;
}
bool SymbolizeData(uptr addr, DataInfo *info) {
BlockingMutexLock l(&mu_);
LoadedModule *module = FindModuleForAddress(addr);
if (module == 0)
return false;
const char *module_name = module->full_name();
uptr module_offset = addr - module->base_address();
internal_memset(info, 0, sizeof(*info));
info->address = addr;
info->module = internal_strdup(module_name);
info->module_offset = module_offset;
// First, try to use libbacktrace symbolizer (if it's available).
if (libbacktrace_symbolizer_ != 0) {
mu_.CheckLocked();
if (libbacktrace_symbolizer_->SymbolizeData(info))
return true;
}
const char *str = SendCommand(true, module_name, module_offset);
if (str == 0)
return true;
str = ExtractToken(str, "\n", &info->name);
str = ExtractUptr(str, " ", &info->start);
str = ExtractUptr(str, "\n", &info->size);
info->start += module->base_address();
return true;
}
bool GetModuleNameAndOffsetForPC(uptr pc, const char **module_name,
uptr *module_address) {
BlockingMutexLock l(&mu_);
return FindModuleNameAndOffsetForAddress(pc, module_name, module_address);
}
bool CanReturnFileLineInfo() {
return internal_symbolizer_ != 0 || external_symbolizer_ != 0 ||
libbacktrace_symbolizer_ != 0;
}
void Flush() {
BlockingMutexLock l(&mu_);
if (internal_symbolizer_ != 0) {
SymbolizerScope sym_scope(this);
internal_symbolizer_->Flush();
}
}
const char *Demangle(const char *name) {
BlockingMutexLock l(&mu_);
// Run hooks even if we don't use internal symbolizer, as cxxabi
// demangle may call system functions.
SymbolizerScope sym_scope(this);
// Try to use libbacktrace demangler (if available).
if (libbacktrace_symbolizer_ != 0) {
if (const char *demangled = libbacktrace_symbolizer_->Demangle(name))
return demangled;
}
if (internal_symbolizer_ != 0)
return internal_symbolizer_->Demangle(name);
return DemangleCXXABI(name);
}
void PrepareForSandboxing() {
#if SANITIZER_LINUX && !SANITIZER_ANDROID
BlockingMutexLock l(&mu_);
// Cache /proc/self/exe on Linux.
CacheBinaryName();
#endif
}
private:
char *SendCommand(bool is_data, const char *module_name, uptr module_offset) {
mu_.CheckLocked();
// First, try to use internal symbolizer.
if (internal_symbolizer_) {
SymbolizerScope sym_scope(this);
return internal_symbolizer_->SendCommand(is_data, module_name,
module_offset);
}
// Otherwise, fall back to external symbolizer.
if (external_symbolizer_) {
SymbolizerScope sym_scope(this);
return external_symbolizer_->SendCommand(is_data, module_name,
module_offset);
}
return 0;
}
LoadedModule *FindModuleForAddress(uptr address) {
mu_.CheckLocked();
bool modules_were_reloaded = false;
if (modules_ == 0 || !modules_fresh_) {
modules_ = (LoadedModule*)(symbolizer_allocator_.Allocate(
kMaxNumberOfModuleContexts * sizeof(LoadedModule)));
CHECK(modules_);
n_modules_ = GetListOfModules(modules_, kMaxNumberOfModuleContexts,
/* filter */ 0);
CHECK_GT(n_modules_, 0);
CHECK_LT(n_modules_, kMaxNumberOfModuleContexts);
modules_fresh_ = true;
modules_were_reloaded = true;
}
for (uptr i = 0; i < n_modules_; i++) {
if (modules_[i].containsAddress(address)) {
return &modules_[i];
}
}
// Reload the modules and look up again, if we haven't tried it yet.
if (!modules_were_reloaded) {
// FIXME: set modules_fresh_ from dlopen()/dlclose() interceptors.
// It's too aggressive to reload the list of modules each time we fail
// to find a module for a given address.
modules_fresh_ = false;
return FindModuleForAddress(address);
}
return 0;
}
bool FindModuleNameAndOffsetForAddress(uptr address, const char **module_name,
uptr *module_offset) {
mu_.CheckLocked();
LoadedModule *module = FindModuleForAddress(address);
if (module == 0)
return false;
*module_name = module->full_name();
*module_offset = address - module->base_address();
return true;
}
// 16K loaded modules should be enough for everyone.
static const uptr kMaxNumberOfModuleContexts = 1 << 14;
LoadedModule *modules_; // Array of module descriptions is leaked.
uptr n_modules_;
// If stale, need to reload the modules before looking up addresses.
bool modules_fresh_;
BlockingMutex mu_;
ExternalSymbolizerInterface *external_symbolizer_; // Leaked.
InternalSymbolizer *const internal_symbolizer_; // Leaked.
LibbacktraceSymbolizer *libbacktrace_symbolizer_; // Leaked.
};
Symbolizer *Symbolizer::PlatformInit(const char *path_to_external) {
if (!common_flags()->symbolize) {
return new(symbolizer_allocator_) POSIXSymbolizer(0, 0, 0);
}
InternalSymbolizer* internal_symbolizer =
InternalSymbolizer::get(&symbolizer_allocator_);
ExternalSymbolizerInterface *external_symbolizer = 0;
LibbacktraceSymbolizer *libbacktrace_symbolizer = 0;
if (!internal_symbolizer) {
libbacktrace_symbolizer =
LibbacktraceSymbolizer::get(&symbolizer_allocator_);
if (!libbacktrace_symbolizer) {
if (path_to_external && path_to_external[0] == '\0') {
// External symbolizer is explicitly disabled. Do nothing.
} else {
// Find path to llvm-symbolizer if it's not provided.
if (!path_to_external)
path_to_external = FindPathToBinary("llvm-symbolizer");
if (path_to_external) {
external_symbolizer = new(symbolizer_allocator_)
LLVMSymbolizerProcess(path_to_external);
} else if (common_flags()->allow_addr2line) {
// If llvm-symbolizer is not found, try to use addr2line.
if (const char *addr2line_path = FindPathToBinary("addr2line")) {
external_symbolizer = new(symbolizer_allocator_)
Addr2LinePool(addr2line_path, &symbolizer_allocator_);
}
}
}
}
}
return new(symbolizer_allocator_) POSIXSymbolizer(
external_symbolizer, internal_symbolizer, libbacktrace_symbolizer);
}
} // namespace __sanitizer
#endif // SANITIZER_POSIX