Index: lib/asan/asan_interceptors.cc =================================================================== --- lib/asan/asan_interceptors.cc +++ lib/asan/asan_interceptors.cc @@ -146,6 +146,8 @@ } while (false) #define COMMON_INTERCEPTOR_BLOCK_REAL(name) REAL(name) #define COMMON_INTERCEPTOR_ON_EXIT(ctx) OnExit() +#define COMMON_INTERCEPTOR_LIBRARY_LOADED(filename, res) CovUpdateMapping() +#define COMMON_INTERCEPTOR_LIBRARY_UNLOADED() CovUpdateMapping() #include "sanitizer_common/sanitizer_common_interceptors.inc" #define COMMON_SYSCALL_PRE_READ_RANGE(p, s) ASAN_READ_RANGE(p, s) Index: lib/msan/msan_interceptors.cc =================================================================== --- lib/msan/msan_interceptors.cc +++ lib/msan/msan_interceptors.cc @@ -886,25 +886,6 @@ return res; } -// dlopen() ultimately calls mmap() down inside the loader, which generally -// doesn't participate in dynamic symbol resolution. Therefore we won't -// intercept its calls to mmap, and we have to hook it here. The loader -// initializes the module before returning, so without the dynamic component, we -// won't be able to clear the shadow before the initializers. Fixing this would -// require putting our own initializer first to clear the shadow. -INTERCEPTOR(void *, dlopen, const char *filename, int flag) { - ENSURE_MSAN_INITED(); - EnterLoader(); - link_map *map = (link_map *)REAL(dlopen)(filename, flag); - ExitLoader(); - if (!__msan_has_dynamic_component() && map) { - // If msandr didn't clear the shadow before the initializers ran, we do it - // ourselves afterwards. - ForEachMappedRegion(map, __msan_unpoison); - } - return (void *)map; -} - typedef int (*dl_iterate_phdr_cb)(__sanitizer_dl_phdr_info *info, SIZE_T size, void *data); struct dl_iterate_phdr_data { @@ -1234,6 +1215,13 @@ } while (false) // FIXME #define COMMON_INTERCEPTOR_BLOCK_REAL(name) REAL(name) #define COMMON_INTERCEPTOR_ON_EXIT(ctx) OnExit() +#define COMMON_INTERCEPTOR_LIBRARY_LOADED(filename, map) \ + if (!__msan_has_dynamic_component() && map) { \ + /* If msandr didn't clear the shadow before the initializers ran, we do */ \ + /* it ourselves afterwards. */ \ + ForEachMappedRegion((link_map *)map, __msan_unpoison); \ + } + #include "sanitizer_common/sanitizer_common_interceptors.inc" #define COMMON_SYSCALL_PRE_READ_RANGE(p, s) CHECK_UNPOISONED(p, s) @@ -1559,7 +1547,6 @@ INTERCEPT_FUNCTION(recvfrom); INTERCEPT_FUNCTION(dladdr); INTERCEPT_FUNCTION(dlerror); - INTERCEPT_FUNCTION(dlopen); INTERCEPT_FUNCTION(dl_iterate_phdr); INTERCEPT_FUNCTION(getrusage); INTERCEPT_FUNCTION(sigaction); Index: lib/sanitizer_common/CMakeLists.txt =================================================================== --- lib/sanitizer_common/CMakeLists.txt +++ lib/sanitizer_common/CMakeLists.txt @@ -4,7 +4,6 @@ set(SANITIZER_SOURCES sanitizer_allocator.cc sanitizer_common.cc - sanitizer_coverage.cc sanitizer_deadlock_detector1.cc sanitizer_deadlock_detector2.cc sanitizer_flags.cc @@ -31,6 +30,8 @@ set(SANITIZER_LIBCDEP_SOURCES sanitizer_common_libcdep.cc + sanitizer_coverage_libcdep.cc + sanitizer_coverage_mapping_libcdep.cc sanitizer_linux_libcdep.cc sanitizer_posix_libcdep.cc sanitizer_stacktrace_libcdep.cc Index: lib/sanitizer_common/sanitizer_common.h =================================================================== --- lib/sanitizer_common/sanitizer_common.h +++ lib/sanitizer_common/sanitizer_common.h @@ -161,6 +161,7 @@ // (or NULL if the mapping failes). Stores the size of mmaped region // in '*buff_size'. void *MapFileToMemory(const char *file_name, uptr *buff_size); +void *MapWritableFileToMemory(void *addr, uptr size, uptr fd, uptr offset); // Error report formatting. const char *StripPathPrefix(const char *filepath, @@ -187,6 +188,8 @@ void CovPrepareForSandboxing(__sanitizer_sandbox_arguments *args); void SetSandboxingCallback(void (*f)()); +void CovUpdateMapping(); + void InitTlsSize(); uptr GetTlsSize(); @@ -479,6 +482,10 @@ const char *full_name() const { return full_name_; } uptr base_address() const { return base_address_; } + uptr n_ranges() const { return n_ranges_; } + uptr address_range_start(int i) const { return ranges_[i].beg; } + uptr address_range_end(int i) const { return ranges_[i].end; } + private: struct AddressRange { uptr beg; Index: lib/sanitizer_common/sanitizer_common.cc =================================================================== --- lib/sanitizer_common/sanitizer_common.cc +++ lib/sanitizer_common/sanitizer_common.cc @@ -263,11 +263,6 @@ atomic_fetch_sub(&g_total_mmaped, size, memory_order_relaxed); } -static void (*sandboxing_callback)(); -void SetSandboxingCallback(void (*f)()) { - sandboxing_callback = f; -} - } // namespace __sanitizer using namespace __sanitizer; // NOLINT @@ -300,13 +295,6 @@ } } -void NOINLINE -__sanitizer_sandbox_on_notify(__sanitizer_sandbox_arguments *args) { - PrepareForSandboxing(args); - if (sandboxing_callback) - sandboxing_callback(); -} - void __sanitizer_report_error_summary(const char *error_summary) { Printf("%s\n", error_summary); } Index: lib/sanitizer_common/sanitizer_common_interceptors.inc =================================================================== --- lib/sanitizer_common/sanitizer_common_interceptors.inc +++ lib/sanitizer_common/sanitizer_common_interceptors.inc @@ -75,6 +75,19 @@ #define COMMON_INTERCEPTOR_FILE_CLOSE(ctx, file) {} #endif +#ifndef COMMON_INTERCEPTOR_LIBRARY_LOADED +#define COMMON_INTERCEPTOR_LIBRARY_LOADED(filename, map) {} +#endif + +#ifndef COMMON_INTERCEPTOR_LIBRARY_UNLOADED +#define COMMON_INTERCEPTOR_LIBRARY_UNLOADED() {} +#endif + +#ifndef COMMON_INTERCEPTOR_ENTER_NOIGNORE +#define COMMON_INTERCEPTOR_ENTER_NOIGNORE(ctx, ...) \ + COMMON_INTERCEPTOR_ENTER(ctx, __VA_ARGS__) +#endif + struct FileMetadata { // For open_memstream(). char **addr; @@ -4036,6 +4049,29 @@ #define INIT_FCLOSE #endif +#if SANITIZER_INTERCEPT_DLOPEN_DLCLOSE +INTERCEPTOR(void*, dlopen, const char *filename, int flag) { + void *ctx; + COMMON_INTERCEPTOR_ENTER_NOIGNORE(ctx, dlopen, filename, flag); + void *res = REAL(dlopen)(filename, flag); + COMMON_INTERCEPTOR_LIBRARY_LOADED(filename, res); + return res; +} + +INTERCEPTOR(int, dlclose, void *handle) { + void *ctx; + COMMON_INTERCEPTOR_ENTER_NOIGNORE(ctx, dlclose, handle); + int res = REAL(dlclose)(handle); + COMMON_INTERCEPTOR_LIBRARY_UNLOADED(); + return res; +} +#define INIT_DLOPEN_DLCLOSE \ + COMMON_INTERCEPT_FUNCTION(dlopen); \ + COMMON_INTERCEPT_FUNCTION(dlclose); +#else +#define INIT_DLOPEN_DLCLOSE +#endif + static void InitializeCommonInterceptors() { static u64 metadata_mem[sizeof(MetadataHashMap) / sizeof(u64) + 1]; interceptor_metadata_map = new((void *)&metadata_mem) MetadataHashMap(); @@ -4177,4 +4213,5 @@ INIT_OBSTACK; INIT_FFLUSH; INIT_FCLOSE; + INIT_DLOPEN_DLCLOSE; } Index: lib/sanitizer_common/sanitizer_common_libcdep.cc =================================================================== --- lib/sanitizer_common/sanitizer_common_libcdep.cc +++ lib/sanitizer_common/sanitizer_common_libcdep.cc @@ -41,4 +41,17 @@ return internal_strcmp(flag, "always") == 0 || (internal_strcmp(flag, "auto") == 0 && PrintsToTtyCached()); } + +static void (*sandboxing_callback)(); +void SetSandboxingCallback(void (*f)()) { + sandboxing_callback = f; +} + } // namespace __sanitizer + +void NOINLINE +__sanitizer_sandbox_on_notify(__sanitizer_sandbox_arguments *args) { + PrepareForSandboxing(args); + if (sandboxing_callback) + sandboxing_callback(); +} Index: lib/sanitizer_common/sanitizer_coverage.cc =================================================================== --- lib/sanitizer_common/sanitizer_coverage.cc +++ lib/sanitizer_common/sanitizer_coverage.cc @@ -1,238 +0,0 @@ -//===-- sanitizer_coverage.cc ---------------------------------------------===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -// -// Sanitizer Coverage. -// This file implements run-time support for a poor man's coverage tool. -// -// Compiler instrumentation: -// For every interesting basic block the compiler injects the following code: -// if (*Guard) { -// __sanitizer_cov(); -// *Guard = 1; -// } -// It's fine to call __sanitizer_cov more than once for a given block. -// -// Run-time: -// - __sanitizer_cov(): record that we've executed the PC (GET_CALLER_PC). -// - __sanitizer_cov_dump: dump the coverage data to disk. -// For every module of the current process that has coverage data -// this will create a file module_name.PID.sancov. The file format is simple: -// it's just a sorted sequence of 4-byte offsets in the module. -// -// Eventually, this coverage implementation should be obsoleted by a more -// powerful general purpose Clang/LLVM coverage instrumentation. -// Consider this implementation as prototype. -// -// FIXME: support (or at least test with) dlclose. -//===----------------------------------------------------------------------===// - -#include "sanitizer_allocator_internal.h" -#include "sanitizer_common.h" -#include "sanitizer_libc.h" -#include "sanitizer_mutex.h" -#include "sanitizer_procmaps.h" -#include "sanitizer_stacktrace.h" -#include "sanitizer_flags.h" - -atomic_uint32_t dump_once_guard; // Ensure that CovDump runs only once. - -// pc_array is the array containing the covered PCs. -// To make the pc_array thread- and async-signal-safe it has to be large enough. -// 128M counters "ought to be enough for anybody" (4M on 32-bit). -// pc_array is allocated with MmapNoReserveOrDie and so it uses only as -// much RAM as it really needs. -static const uptr kPcArraySize = FIRST_32_SECOND_64(1 << 22, 1 << 27); -static uptr *pc_array; -static atomic_uintptr_t pc_array_index; - -static bool cov_sandboxed = false; -static int cov_fd = kInvalidFd; -static unsigned int cov_max_block_size = 0; - -namespace __sanitizer { - -// Simply add the pc into the vector under lock. If the function is called more -// than once for a given PC it will be inserted multiple times, which is fine. -static void CovAdd(uptr pc) { - if (!pc_array) return; - uptr idx = atomic_fetch_add(&pc_array_index, 1, memory_order_relaxed); - CHECK_LT(idx, kPcArraySize); - pc_array[idx] = pc; -} - -void CovInit() { - pc_array = reinterpret_cast( - MmapNoReserveOrDie(sizeof(uptr) * kPcArraySize, "CovInit")); -} - -static inline bool CompareLess(const uptr &a, const uptr &b) { - return a < b; -} - -// Block layout for packed file format: header, followed by module name (no -// trailing zero), followed by data blob. -struct CovHeader { - int pid; - unsigned int module_name_length; - unsigned int data_length; -}; - -static void CovWritePacked(int pid, const char *module, const void *blob, - unsigned int blob_size) { - if (cov_fd < 0) return; - unsigned module_name_length = internal_strlen(module); - CovHeader header = {pid, module_name_length, blob_size}; - - if (cov_max_block_size == 0) { - // Writing to a file. Just go ahead. - internal_write(cov_fd, &header, sizeof(header)); - internal_write(cov_fd, module, module_name_length); - internal_write(cov_fd, blob, blob_size); - } else { - // Writing to a socket. We want to split the data into appropriately sized - // blocks. - InternalScopedBuffer block(cov_max_block_size); - CHECK_EQ((uptr)block.data(), (uptr)(CovHeader *)block.data()); - uptr header_size_with_module = sizeof(header) + module_name_length; - CHECK_LT(header_size_with_module, cov_max_block_size); - unsigned int max_payload_size = - cov_max_block_size - header_size_with_module; - char *block_pos = block.data(); - internal_memcpy(block_pos, &header, sizeof(header)); - block_pos += sizeof(header); - internal_memcpy(block_pos, module, module_name_length); - block_pos += module_name_length; - char *block_data_begin = block_pos; - char *blob_pos = (char *)blob; - while (blob_size > 0) { - unsigned int payload_size = Min(blob_size, max_payload_size); - blob_size -= payload_size; - internal_memcpy(block_data_begin, blob_pos, payload_size); - blob_pos += payload_size; - ((CovHeader *)block.data())->data_length = payload_size; - internal_write(cov_fd, block.data(), - header_size_with_module + payload_size); - } - } -} - -// If packed = false: .. (name = module name). -// If packed = true and name == 0: ... -// If packed = true and name != 0: .. (name is -// user-supplied). -static int CovOpenFile(bool packed, const char* name) { - InternalScopedBuffer path(1024); - if (!packed) { - CHECK(name); - internal_snprintf((char *)path.data(), path.size(), "%s.%zd.sancov", - name, internal_getpid()); - } else { - if (!name) - internal_snprintf((char *)path.data(), path.size(), "%zd.sancov.packed", - internal_getpid()); - else - internal_snprintf((char *)path.data(), path.size(), "%s.sancov.packed", - name); - } - uptr fd = OpenFile(path.data(), true); - if (internal_iserror(fd)) { - Report(" SanitizerCoverage: failed to open %s for writing\n", path.data()); - return -1; - } - return fd; -} - -// Dump the coverage on disk. -static void CovDump() { - if (!common_flags()->coverage) return; -#if !SANITIZER_WINDOWS - if (atomic_fetch_add(&dump_once_guard, 1, memory_order_relaxed)) - return; - uptr size = atomic_load(&pc_array_index, memory_order_relaxed); - InternalSort(&pc_array, size, CompareLess); - InternalMmapVector offsets(size); - const uptr *vb = pc_array; - const uptr *ve = vb + size; - MemoryMappingLayout proc_maps(/*cache_enabled*/true); - uptr mb, me, off, prot; - InternalScopedBuffer module(4096); - InternalScopedBuffer path(4096 * 2); - for (int i = 0; - proc_maps.Next(&mb, &me, &off, module.data(), module.size(), &prot); - i++) { - if ((prot & MemoryMappingLayout::kProtectionExecute) == 0) - continue; - while (vb < ve && *vb < mb) vb++; - if (vb >= ve) break; - if (*vb < me) { - offsets.clear(); - const uptr *old_vb = vb; - CHECK_LE(off, *vb); - for (; vb < ve && *vb < me; vb++) { - uptr diff = *vb - (i ? mb : 0) + off; - CHECK_LE(diff, 0xffffffffU); - offsets.push_back(static_cast(diff)); - } - char *module_name = StripModuleName(module.data()); - if (cov_sandboxed) { - if (cov_fd >= 0) { - CovWritePacked(internal_getpid(), module_name, offsets.data(), - offsets.size() * sizeof(u32)); - VReport(1, " CovDump: %zd PCs written to packed file\n", vb - old_vb); - } - } else { - // One file per module per process. - internal_snprintf((char *)path.data(), path.size(), "%s.%zd.sancov", - module_name, internal_getpid()); - int fd = CovOpenFile(false /* packed */, module_name); - if (fd > 0) { - internal_write(fd, offsets.data(), offsets.size() * sizeof(u32)); - internal_close(fd); - VReport(1, " CovDump: %s: %zd PCs written\n", path.data(), - vb - old_vb); - } - } - InternalFree(module_name); - } - } - if (cov_fd >= 0) - internal_close(cov_fd); -#endif // !SANITIZER_WINDOWS -} - -void CovPrepareForSandboxing(__sanitizer_sandbox_arguments *args) { - if (!args) return; - if (!common_flags()->coverage) return; - cov_sandboxed = args->coverage_sandboxed; - if (!cov_sandboxed) return; - cov_fd = args->coverage_fd; - cov_max_block_size = args->coverage_max_block_size; - if (cov_fd < 0) - // Pre-open the file now. The sandbox won't allow us to do it later. - cov_fd = CovOpenFile(true /* packed */, 0); -} - -int MaybeOpenCovFile(const char *name) { - CHECK(name); - if (!common_flags()->coverage) return -1; - return CovOpenFile(true /* packed */, name); -} -} // namespace __sanitizer - -extern "C" { -SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_cov() { - CovAdd(StackTrace::GetPreviousInstructionPc(GET_CALLER_PC())); -} -SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_cov_dump() { CovDump(); } -SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_cov_init() { CovInit(); } -SANITIZER_INTERFACE_ATTRIBUTE -sptr __sanitizer_maybe_open_cov_file(const char *name) { - return MaybeOpenCovFile(name); -} -} // extern "C" Index: lib/sanitizer_common/sanitizer_coverage_libcdep.cc =================================================================== --- lib/sanitizer_common/sanitizer_coverage_libcdep.cc +++ lib/sanitizer_common/sanitizer_coverage_libcdep.cc @@ -45,11 +45,10 @@ // pc_array is the array containing the covered PCs. // To make the pc_array thread- and async-signal-safe it has to be large enough. // 128M counters "ought to be enough for anybody" (4M on 32-bit). -// pc_array is allocated with MmapNoReserveOrDie and so it uses only as -// much RAM as it really needs. -static const uptr kPcArraySize = FIRST_32_SECOND_64(1 << 22, 1 << 27); -static uptr *pc_array; -static atomic_uintptr_t pc_array_index; + +// With coverage_direct=1 in ASAN_OPTIONS, pc_array memory is mapped to a file. +// In this mode, __sanitizer_cov_dump does nothing, and CovUpdateMapping() +// dump current memory layout to another file. static bool cov_sandboxed = false; static int cov_fd = kInvalidFd; @@ -57,22 +56,114 @@ namespace __sanitizer { +class CoverageData { + public: + void Init(); + void Extend(uptr npcs); + void Add(uptr pc); + + uptr *data(); + uptr size(); + + private: + // Maximal size pc array may ever grow. + // We MmapNoReserve this space to ensure that the array is contiguous. + static const uptr kPcArrayMaxSize = FIRST_32_SECOND_64(1 << 22, 1 << 27); + // The amount file mapping for the pc array is grown by. + static const uptr kPcArrayMmapSize = 64 * 1024; + + // pc_array is allocated with MmapNoReserveOrDie and so it uses only as + // much RAM as it really needs. + uptr *pc_array; + // Index of the first available pc_array slot. + atomic_uintptr_t pc_array_index; + // Array size. + atomic_uintptr_t pc_array_size; + // Current file mapped size of the pc array. + uptr pc_array_mapped_size; + // Descriptor of the file mapped pc array. + int pc_fd; + StaticSpinMutex mu; + + void DirectInit(); +}; + +static CoverageData coverage_data; + +void CoverageData::DirectInit() { + InternalScopedString path(64); + internal_snprintf((char *)path.data(), path.size(), "%zd.sancov.raw", + internal_getpid()); + pc_fd = OpenFile(path.data(), true); + if (internal_iserror(pc_fd)) { + Report(" Coverage: failed to open %s for writing\n", path.data()); + Die(); + } + + atomic_store(&pc_array_size, 0, memory_order_relaxed); + pc_array_mapped_size = 0; + + CovUpdateMapping(); +} + +void CoverageData::Init() { + pc_array = reinterpret_cast( + MmapNoReserveOrDie(sizeof(uptr) * kPcArrayMaxSize, "CovInit")); + if (common_flags()->coverage_direct) { + DirectInit(); + } else { + pc_fd = 0; + atomic_store(&pc_array_size, kPcArrayMaxSize, memory_order_relaxed); + } +} + +// Extend coverage PC array to fit additional npcs elements. +void CoverageData::Extend(uptr npcs) { + // If pc_fd=0, pc array is a huge anonymous mapping that does not need to be + // resized. + if (!pc_fd) return; + SpinMutexLock l(&mu); + + uptr size = atomic_load(&pc_array_size, memory_order_relaxed); + size += npcs * sizeof(uptr); + + if (size > pc_array_mapped_size) { + uptr new_mapped_size = pc_array_mapped_size; + while (size > new_mapped_size) new_mapped_size += kPcArrayMmapSize; + + // Extend the file and map the new space at the end of pc_array. + uptr res = internal_ftruncate(pc_fd, new_mapped_size); + int err; + if (internal_iserror(res, &err)) { + Printf("failed to extend raw coverage file: %d\n", err); + Die(); + } + void *p = MapWritableFileToMemory(pc_array + pc_array_mapped_size, + new_mapped_size - pc_array_mapped_size, + pc_fd, pc_array_mapped_size); + CHECK_EQ(p, pc_array + pc_array_mapped_size); + pc_array_mapped_size = new_mapped_size; + } + + atomic_store(&pc_array_size, size, memory_order_release); +} + // Simply add the pc into the vector under lock. If the function is called more // than once for a given PC it will be inserted multiple times, which is fine. -static void CovAdd(uptr pc) { +void CoverageData::Add(uptr pc) { if (!pc_array) return; uptr idx = atomic_fetch_add(&pc_array_index, 1, memory_order_relaxed); - CHECK_LT(idx, kPcArraySize); + CHECK_LT(idx * sizeof(uptr), + atomic_load(&pc_array_size, memory_order_acquire)); pc_array[idx] = pc; } -void CovInit() { - pc_array = reinterpret_cast( - MmapNoReserveOrDie(sizeof(uptr) * kPcArraySize, "CovInit")); +uptr *CoverageData::data() { + return pc_array; } -static inline bool CompareLess(const uptr &a, const uptr &b) { - return a < b; +uptr CoverageData::size() { + return atomic_load(&pc_array_index, memory_order_relaxed); } // Block layout for packed file format: header, followed by module name (no @@ -150,15 +241,15 @@ // Dump the coverage on disk. static void CovDump() { - if (!common_flags()->coverage) return; + if (!common_flags()->coverage || common_flags()->coverage_direct) return; #if !SANITIZER_WINDOWS if (atomic_fetch_add(&dump_once_guard, 1, memory_order_relaxed)) return; - uptr size = atomic_load(&pc_array_index, memory_order_relaxed); - InternalSort(&pc_array, size, CompareLess); + uptr size = coverage_data.size(); InternalMmapVector offsets(size); - const uptr *vb = pc_array; - const uptr *ve = vb + size; + uptr *vb = coverage_data.data(); + uptr *ve = vb + size; + SortArray(vb, size); MemoryMappingLayout proc_maps(/*cache_enabled*/true); uptr mb, me, off, prot; InternalScopedBuffer module(4096); @@ -227,10 +318,15 @@ extern "C" { SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_cov() { - CovAdd(StackTrace::GetPreviousInstructionPc(GET_CALLER_PC())); + coverage_data.Add(StackTrace::GetPreviousInstructionPc(GET_CALLER_PC())); } SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_cov_dump() { CovDump(); } -SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_cov_init() { CovInit(); } +SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_cov_init() { + coverage_data.Init(); +} +SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_cov_module_init(uptr npcs) { + coverage_data.Extend(npcs); +} SANITIZER_INTERFACE_ATTRIBUTE sptr __sanitizer_maybe_open_cov_file(const char *name) { return MaybeOpenCovFile(name); Index: lib/sanitizer_common/sanitizer_coverage_mapping_libcdep.cc =================================================================== --- lib/sanitizer_common/sanitizer_coverage_mapping_libcdep.cc +++ lib/sanitizer_common/sanitizer_coverage_mapping_libcdep.cc @@ -0,0 +1,97 @@ +//===-- sanitizer_coverage_mapping.cc -------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Mmap-based implementation of sanitizer coverage. +// +// This is part of the implementation of code coverage that does not require +// __sanitizer_cov_dump() call. Data is stored in 2 files per process. +// +// $pid.sancov.map describes process memory layout in the following text-based +// format: +// // 1 line, 32 or 64 +// // repeated +// ... +// Mapping lines are NOT sorted. This file is updated every time memory layout +// is changed (i.e. in dlopen() and dlclose() interceptors). +// +// $pid.sancov.raw is a binary dump of PC values, sizeof(uptr) each. Again, not +// sorted. This file is extended by 64Kb at a time and mapped into memory. It +// contains one or more 0 words at the end, up to the next 64Kb aligned offset. +// +// To convert these 2 files to the usual .sancov format, run sancov.py rawunpack +// $pid.sancov.raw. +// +//===----------------------------------------------------------------------===// + +#include "sanitizer_allocator_internal.h" +#include "sanitizer_libc.h" +#include "sanitizer_procmaps.h" + +namespace __sanitizer { + +static const uptr kMaxNumberOfModules = 1 << 14; + +void CovUpdateMapping() { + if (!common_flags()->coverage || !common_flags()->coverage_direct) return; + + int err; + InternalScopedString tmp_path(64); + internal_snprintf((char *)tmp_path.data(), tmp_path.size(), + "%zd.sancov.map.tmp", internal_getpid()); + uptr map_fd = OpenFile(tmp_path.data(), true); + if (internal_iserror(map_fd)) { + Report(" Coverage: failed to open %s for writing\n", tmp_path.data()); + Die(); + } + + InternalScopedBuffer modules_data(kMaxNumberOfModules * + sizeof(LoadedModule)); + LoadedModule *modules = (LoadedModule *)modules_data.data(); + CHECK(modules); + int n_modules = GetListOfModules(modules, kMaxNumberOfModules, + /* filter */ 0); + + InternalScopedString line(4096); + line.append("%d\n", sizeof(uptr) * 8); + uptr res = internal_write(map_fd, line.data(), line.length()); + if (internal_iserror(res, &err)) { + Printf("sancov.map write failed: %d\n", err); + Die(); + } + line.clear(); + + for (int i = 0; i < n_modules; ++i) { + char *module_name = StripModuleName(modules[i].full_name()); + for (unsigned j = 0; j < modules[i].n_ranges(); ++j) { + line.append("%zx %zx %zx %s\n", modules[i].address_range_start(j), + modules[i].address_range_end(j), modules[i].base_address(), + module_name); + res = internal_write(map_fd, line.data(), line.length()); + if (internal_iserror(res, &err)) { + Printf("sancov.map write failed: %d\n", err); + Die(); + } + line.clear(); + } + InternalFree(module_name); + } + + internal_close(map_fd); + + InternalScopedString path(64); + internal_snprintf((char *)path.data(), path.size(), "%zd.sancov.map", + internal_getpid()); + res = internal_rename(tmp_path.data(), path.data()); + if (internal_iserror(res, &err)) { + Printf("sancov.map rename failed: %d\n", err); + Die(); + } +} + +} // namespace __sanitizer Index: lib/sanitizer_common/sanitizer_flags.h =================================================================== --- lib/sanitizer_common/sanitizer_flags.h +++ lib/sanitizer_common/sanitizer_flags.h @@ -54,6 +54,7 @@ bool help; uptr mmap_limit_mb; bool coverage; + bool coverage_direct; bool full_address_space; }; Index: lib/sanitizer_common/sanitizer_flags.cc =================================================================== --- lib/sanitizer_common/sanitizer_flags.cc +++ lib/sanitizer_common/sanitizer_flags.cc @@ -55,6 +55,7 @@ f->legacy_pthread_cond = false; f->intercept_tls_get_addr = false; f->coverage = false; + f->coverage_direct = false; f->full_address_space = false; } @@ -127,6 +128,10 @@ ParseFlag(str, &f->coverage, "coverage", "If set, coverage information will be dumped at program shutdown (if the " "coverage instrumentation was enabled at compile time)."); + ParseFlag(str, &f->coverage_direct, "coverage_direct", + "If set, coverage information will be dumped directly to a memory " + "mapped file. This way data is not lost even if the process is " + "suddenly killed."); ParseFlag(str, &f->full_address_space, "full_address_space", "Sanitize complete address space; " "by default kernel area on 32-bit platforms will not be sanitized"); Index: lib/sanitizer_common/sanitizer_libc.h =================================================================== --- lib/sanitizer_common/sanitizer_libc.h +++ lib/sanitizer_common/sanitizer_libc.h @@ -74,6 +74,7 @@ uptr internal_read(fd_t fd, void *buf, uptr count); uptr internal_write(fd_t fd, const void *buf, uptr count); +uptr internal_ftruncate(fd_t fd, uptr size); // OS uptr internal_filesize(fd_t fd); // -1 on error. @@ -83,6 +84,7 @@ uptr internal_dup2(int oldfd, int newfd); uptr internal_readlink(const char *path, char *buf, uptr bufsize); uptr internal_unlink(const char *path); +uptr internal_rename(const char *oldpath, const char *newpath); void NORETURN internal__exit(int exitcode); uptr internal_lseek(fd_t fd, OFF_T offset, int whence); Index: lib/sanitizer_common/sanitizer_linux.cc =================================================================== --- lib/sanitizer_common/sanitizer_linux.cc +++ lib/sanitizer_common/sanitizer_linux.cc @@ -134,7 +134,7 @@ uptr OpenFile(const char *filename, bool write) { return internal_open(filename, - write ? O_WRONLY | O_CREAT /*| O_CLOEXEC*/ : O_RDONLY, 0660); + write ? O_RDWR | O_CREAT /*| O_CLOEXEC*/ : O_RDONLY, 0660); } uptr internal_read(fd_t fd, void *buf, uptr count) { @@ -151,6 +151,12 @@ return res; } +uptr internal_ftruncate(fd_t fd, uptr size) { + sptr res; + HANDLE_EINTR(res, (sptr)internal_syscall(SYSCALL(ftruncate), fd, size)); + return res; +} + #if !SANITIZER_LINUX_USES_64BIT_SYSCALLS && !SANITIZER_FREEBSD static void stat64_to_stat(struct stat64 *in, struct stat *out) { internal_memset(out, 0, sizeof(*out)); @@ -246,6 +252,15 @@ #endif } +uptr internal_rename(const char *oldpath, const char *newpath) { +#if SANITIZER_USES_CANONICAL_LINUX_SYSCALLS + return internal_syscall(SYSCALL(renameat), AT_FDCWD, (uptr)oldpath, AT_FDCWD, + (uptr)newpath); +#else + return internal_syscall(SYSCALL(rename), (uptr)oldpath, (uptr)newpath); +#endif +} + uptr internal_sched_yield() { return internal_syscall(SYSCALL(sched_yield)); } @@ -390,20 +405,6 @@ } #endif // SANITIZER_GO -void PrepareForSandboxing(__sanitizer_sandbox_arguments *args) { - // Some kinds of sandboxes may forbid filesystem access, so we won't be able - // to read the file mappings from /proc/self/maps. Luckily, neither the - // process will be able to load additional libraries, so it's fine to use the - // cached mappings. - MemoryMappingLayout::CacheMemoryMappings(); - // Same for /proc/self/exe in the symbolizer. -#if !SANITIZER_GO - if (Symbolizer *sym = Symbolizer::GetOrNull()) - sym->PrepareForSandboxing(); - CovPrepareForSandboxing(args); -#endif -} - enum MutexState { MtxUnlocked = 0, MtxLocked = 1, Index: lib/sanitizer_common/sanitizer_linux_libcdep.cc =================================================================== --- lib/sanitizer_common/sanitizer_linux_libcdep.cc +++ lib/sanitizer_common/sanitizer_linux_libcdep.cc @@ -22,6 +22,7 @@ #include "sanitizer_procmaps.h" #include "sanitizer_stacktrace.h" #include "sanitizer_atomic.h" +#include "sanitizer_symbolizer.h" #include #include @@ -529,6 +530,20 @@ indirect_call_wrapper = wrapper; } +void PrepareForSandboxing(__sanitizer_sandbox_arguments *args) { + // Some kinds of sandboxes may forbid filesystem access, so we won't be able + // to read the file mappings from /proc/self/maps. Luckily, neither the + // process will be able to load additional libraries, so it's fine to use the + // cached mappings. + MemoryMappingLayout::CacheMemoryMappings(); + // Same for /proc/self/exe in the symbolizer. +#if !SANITIZER_GO + if (Symbolizer *sym = Symbolizer::GetOrNull()) + sym->PrepareForSandboxing(); + CovPrepareForSandboxing(args); +#endif +} + } // namespace __sanitizer #endif // SANITIZER_FREEBSD || SANITIZER_LINUX Index: lib/sanitizer_common/sanitizer_mac.cc =================================================================== --- lib/sanitizer_common/sanitizer_mac.cc +++ lib/sanitizer_common/sanitizer_mac.cc @@ -131,6 +131,14 @@ return fork(); } +uptr internal_rename(const char *oldpath, const char *newpath) { + return rename(oldpath, newpath); +} + +uptr internal_ftruncate(fd_t fd, uptr size) { + return ftruncate(fd, size); +} + // ----------------- sanitizer_common.h bool FileExists(const char *filename) { struct stat st; Index: lib/sanitizer_common/sanitizer_platform_interceptors.h =================================================================== --- lib/sanitizer_common/sanitizer_platform_interceptors.h +++ lib/sanitizer_common/sanitizer_platform_interceptors.h @@ -203,5 +203,6 @@ #define SANITIZER_INTERCEPT_OBSTACK SI_LINUX_NOT_ANDROID #define SANITIZER_INTERCEPT_FFLUSH SI_NOT_WINDOWS #define SANITIZER_INTERCEPT_FCLOSE SI_NOT_WINDOWS +#define SANITIZER_INTERCEPT_DLOPEN_DLCLOSE SI_NOT_WINDOWS #endif // #ifndef SANITIZER_PLATFORM_INTERCEPTORS_H Index: lib/sanitizer_common/sanitizer_posix.cc =================================================================== --- lib/sanitizer_common/sanitizer_posix.cc +++ lib/sanitizer_common/sanitizer_posix.cc @@ -206,6 +206,17 @@ return internal_iserror(map) ? 0 : (void *)map; } +void *MapWritableFileToMemory(void *addr, uptr size, uptr fd, uptr offset) { + uptr flags = MAP_SHARED; + if (addr) flags |= MAP_FIXED; + uptr p = internal_mmap(addr, size, PROT_READ | PROT_WRITE, flags, fd, offset); + if (internal_iserror(p)) { + Printf("could not map writable file (%zd, %zu, %zu): %zd\n", fd, offset, + size, p); + return 0; + } + return (void *)p; +} static inline bool IntervalsAreSeparate(uptr start1, uptr end1, uptr start2, uptr end2) { Index: lib/sanitizer_common/scripts/sancov.py =================================================================== --- lib/sanitizer_common/scripts/sancov.py +++ lib/sanitizer_common/scripts/sancov.py @@ -6,6 +6,8 @@ import array import struct import sys +import bisect +import os.path prog_name = ""; @@ -74,6 +76,63 @@ for f in files: UnpackOneFile(f) +def UnpackOneRawFile(path, map_path): + mem_map = [] + with open(map_path, mode="rt") as f_map: + print >> sys.stderr, "%s: reading map %s" % (prog_name, map_path) + bits = int(f_map.readline()) + for line in f_map: + parts = line.rstrip().split() + assert len(parts) == 4 + mem_map.append((int(parts[0], 16), + int(parts[1], 16), + int(parts[2], 16), + parts[3])) + mem_map.sort(key=lambda m : m[0]) + mem_map_keys = [m[0] for m in mem_map] + + print mem_map + with open(path, mode="rb") as f: + print >> sys.stderr, "%s: unpacking %s" % (prog_name, path) + + f.seek(0, 2) + size = f.tell() + f.seek(0, 0) + if bits == 64: + typecode = 'L' + else: + typecode = 'I' + pcs = array.array(typecode, f.read(size)) + mem_map_pcs = [[] for i in range(0, len(mem_map))] + + for pc in pcs: + if pc == 0: continue + map_idx = bisect.bisect(mem_map_keys, pc) - 1 + (start, end, base, module_path) = mem_map[map_idx] + print pc + print start, end, base, module_path + assert pc >= start + if pc >= end: + print >> sys.stderr, "warning: %s: pc %x outside of any known mapping" % (prog_name, pc) + continue + mem_map_pcs[map_idx].append(pc - base) + + for ((start, end, base, module_path), pc_list) in zip(mem_map, mem_map_pcs): + if len(pc_list) == 0: continue + assert path.endswith('.sancov.raw') + dst_path = module_path + '.' + os.path.basename(path)[:-4] + print "writing %d PCs to %s" % (len(pc_list), dst_path) + arr = array.array('I') + arr.fromlist(sorted(pc_list)) + with open(dst_path, 'ab') as f2: + arr.tofile(f2) + +def RawUnpack(files): + for f in files: + if not f.endswith('.sancov.raw'): + raise Exception('Unexpected raw file name %s' % f) + f_map = f[:-3] + 'map' + UnpackOneRawFile(f, f_map) if __name__ == '__main__': prog_name = sys.argv[0] @@ -85,5 +144,7 @@ MergeAndPrint(sys.argv[2:]) elif sys.argv[1] == "unpack": Unpack(sys.argv[2:]) + elif sys.argv[1] == "rawunpack": + RawUnpack(sys.argv[2:]) else: Usage() Index: lib/tsan/rtl/tsan_interceptors.cc =================================================================== --- lib/tsan/rtl/tsan_interceptors.cc +++ lib/tsan/rtl/tsan_interceptors.cc @@ -207,7 +207,7 @@ if (REAL(func) == 0) { \ Report("FATAL: ThreadSanitizer: failed to intercept %s\n", #func); \ Die(); \ - } \ + } \ if (thr->ignore_interceptors || thr->in_ignored_lib) \ return REAL(func)(__VA_ARGS__); \ /**/ @@ -259,20 +259,6 @@ return res; } -TSAN_INTERCEPTOR(void*, dlopen, const char *filename, int flag) { - SCOPED_INTERCEPTOR_RAW(dlopen, filename, flag); - void *res = REAL(dlopen)(filename, flag); - libignore()->OnLibraryLoaded(filename); - return res; -} - -TSAN_INTERCEPTOR(int, dlclose, void *handle) { - SCOPED_INTERCEPTOR_RAW(dlclose, handle); - int res = REAL(dlclose)(handle); - libignore()->OnLibraryUnloaded(); - return res; -} - class AtExitContext { public: AtExitContext() @@ -2021,6 +2007,12 @@ ctx = (void *)&_ctx; \ (void) ctx; +#define COMMON_INTERCEPTOR_ENTER_NOIGNORE(ctx, func, ...) \ + SCOPED_INTERCEPTOR_RAW(func, __VA_ARGS__); \ + TsanInterceptorContext _ctx = {thr, caller_pc, pc}; \ + ctx = (void *)&_ctx; \ + (void) ctx; + #define COMMON_INTERCEPTOR_FILE_OPEN(ctx, file, path) \ Acquire(thr, pc, File2addr(path)); \ if (file) { \ @@ -2034,6 +2026,12 @@ if (fd >= 0) FdClose(thr, pc, fd); \ } +#define COMMON_INTERCEPTOR_LIBRARY_LOADED(filename, res) \ + libignore()->OnLibraryLoaded(filename) + +#define COMMON_INTERCEPTOR_LIBRARY_UNLOADED() \ + libignore()->OnLibraryUnloaded() + #define COMMON_INTERCEPTOR_FD_ACQUIRE(ctx, fd) \ FdAcquire(((TsanInterceptorContext *) ctx)->thr, pc, fd) @@ -2376,8 +2374,6 @@ TSAN_INTERCEPT(fork); TSAN_INTERCEPT(vfork); - TSAN_INTERCEPT(dlopen); - TSAN_INTERCEPT(dlclose); TSAN_INTERCEPT(on_exit); TSAN_INTERCEPT(__cxa_atexit); TSAN_INTERCEPT(_exit); Index: test/asan/TestCases/Linux/coverage-direct-large.cc =================================================================== --- test/asan/TestCases/Linux/coverage-direct-large.cc +++ test/asan/TestCases/Linux/coverage-direct-large.cc @@ -0,0 +1,45 @@ +// Test for direct coverage writing with lots of data. +// Current implementation maps output file in chunks of 64K. This test overflows +// 1 chunk. +// RUN: %clangxx_asan -mllvm -asan-coverage=1 -O0 %s -o %t + +// RUN: rm -rf %T/coverage-direct-large + +// RUN: mkdir -p %T/coverage-direct-large/normal && cd %T/coverage-direct-large/normal +// RUN: ASAN_OPTIONS=coverage=1:coverage_direct=0:verbosity=1 %run %t +// RUN: %sancov print *.sancov >out.txt +// RUN: cd ../.. + +// RUN: mkdir -p %T/coverage-direct-large/direct && cd %T/coverage-direct-large/direct +// RUN: ASAN_OPTIONS=coverage=1:coverage_direct=1:verbosity=1 %run %t +// RUN: %sancov rawunpack *.sancov.raw +// RUN: %sancov print *.sancov >out.txt +// RUN: cd ../.. + +// RUN: diff -u coverage-direct-large/normal/out.txt coverage-direct-large/direct/out.txt +// +// XFAIL: android + +#define F0(Q, x) Q(x) +#define F1(Q, x) \ + F0(Q, x##0) F0(Q, x##1) F0(Q, x##2) F0(Q, x##3) F0(Q, x##4) F0(Q, x##5) \ + F0(Q, x##6) F0(Q, x##7) F0(Q, x##8) F0(Q, x##9) +#define F2(Q, x) \ + F1(Q, x##0) F1(Q, x##1) F1(Q, x##2) F1(Q, x##3) F1(Q, x##4) F1(Q, x##5) \ + F1(Q, x##6) F1(Q, x##7) F1(Q, x##8) F1(Q, x##9) +#define F3(Q, x) \ + F2(Q, x##0) F2(Q, x##1) F2(Q, x##2) F2(Q, x##3) F2(Q, x##4) F2(Q, x##5) \ + F2(Q, x##6) F2(Q, x##7) F2(Q, x##8) F2(Q, x##9) +#define F4(Q, x) \ + F3(Q, x##0) F3(Q, x##1) F3(Q, x##2) F3(Q, x##3) F3(Q, x##4) F3(Q, x##5) \ + F3(Q, x##6) F3(Q, x##7) F3(Q, x##8) F3(Q, x##9) + +#define DECL(x) __attribute__((noinline)) void x() {} +#define CALL(x) x(); + +F4(DECL, f) + +int main(void) { + F4(CALL, f) + return 0; +} Index: test/asan/TestCases/Linux/coverage-direct.cc =================================================================== --- test/asan/TestCases/Linux/coverage-direct.cc +++ test/asan/TestCases/Linux/coverage-direct.cc @@ -0,0 +1,44 @@ +// Test for direct coverage writing with dlopen. +// RUN: %clangxx_asan -mllvm -asan-coverage=1 -DSHARED %s -shared -o %T/libcoverage_direct_test_1.so -fPIC +// RUN: %clangxx_asan -mllvm -asan-coverage=1 -DSO_DIR=\"%T\" %s -o %t + +// RUN: rm -rf %T/coverage-direct + +// RUN: mkdir -p %T/coverage-direct/normal && cd %T/coverage-direct/normal +// RUN: ASAN_OPTIONS=coverage=1:coverage_direct=0:verbosity=1 %run %t +// RUN: %sancov print *.sancov >out.txt +// RUN: cd ../.. + +// RUN: mkdir -p %T/coverage-direct/direct && cd %T/coverage-direct/direct +// RUN: ASAN_OPTIONS=coverage=1:coverage_direct=1:verbosity=1 %run %t +// RUN: %sancov rawunpack *.sancov.raw +// RUN: %sancov print *.sancov >out.txt +// RUN: cd ../.. + +// RUN: diff -u coverage-direct/normal/out.txt coverage-direct/direct/out.txt +// +// XFAIL: android + +#include +#include +#include +#include + +#ifdef SHARED +extern "C" { +void bar() { printf("bar\n"); } +} +#else + +int main(int argc, char **argv) { + fprintf(stderr, "PID: %d\n", getpid()); + void *handle1 = + dlopen(SO_DIR "/libcoverage_direct_test_1.so", RTLD_LAZY); + assert(handle1); + void (*bar1)() = (void (*)())dlsym(handle1, "bar"); + assert(bar1); + bar1(); + + return 0; +} +#endif