Index: include/sanitizer/common_interface_defs.h =================================================================== --- include/sanitizer/common_interface_defs.h +++ include/sanitizer/common_interface_defs.h @@ -24,13 +24,28 @@ #ifdef __cplusplus extern "C" { #endif + // Arguments for __sanitizer_sandbox_on_notify() below. + typedef struct { + // Enable sandbox support in sanitizer coverage. + int coverage_sandboxed; + // File descriptor to write coverage data to. If -1 is passed, a file will + // be pre-opened by __sanitizer_sandobx_on_notify(). This field has no + // effect if coverage_sandboxed == 0. + int coverage_fd; + // If non-zero, split the coverage data into well-formed blocks. This is + // useful when coverage_fd is a socket descriptor. Each block will contain + // a header, allowing data from multiple processes to be sent over the same + // socket. + size_t coverage_max_block_size; + } __sanitizer_sandbox_arguments; + // Tell the tools to write their reports to "path." instead of stderr. void __sanitizer_set_report_path(const char *path); // Notify the tools that the sandbox is going to be turned on. The reserved // parameter will be used in the future to hold a structure with functions // that the tools may call to bypass the sandbox. - void __sanitizer_sandbox_on_notify(void *reserved); + void __sanitizer_sandbox_on_notify(__sanitizer_sandbox_arguments *args); // This function is called by the tool when it has just finished reporting // an error. 'error_summary' is a one-line string that summarizes Index: lib/sanitizer_common/sanitizer_common.h =================================================================== --- lib/sanitizer_common/sanitizer_common.h +++ lib/sanitizer_common/sanitizer_common.h @@ -183,7 +183,8 @@ bool StackSizeIsUnlimited(); void SetStackSizeLimitInBytes(uptr limit); void AdjustStackSize(void *attr); -void PrepareForSandboxing(); +void PrepareForSandboxing(__sanitizer_sandbox_arguments *args); +void CovPrepareForSandboxing(__sanitizer_sandbox_arguments *args); void SetSandboxingCallback(void (*f)()); void InitTlsSize(); Index: lib/sanitizer_common/sanitizer_common.cc =================================================================== --- lib/sanitizer_common/sanitizer_common.cc +++ lib/sanitizer_common/sanitizer_common.cc @@ -300,9 +300,9 @@ } } -void NOINLINE __sanitizer_sandbox_on_notify(void *reserved) { - (void)reserved; - PrepareForSandboxing(); +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 @@ -43,7 +43,7 @@ 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 AS- safe it has to be large enough. +// 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. @@ -51,6 +51,10 @@ static uptr *pc_array; static atomic_uintptr_t pc_array_index; +static bool cov_sandboxed = false; +static int cov_fd = kInvalidFd; +static uptr cov_max_block_size = 0; + namespace __sanitizer { // Simply add the pc into the vector under lock. If the function is called more @@ -71,8 +75,58 @@ 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; + uptr data_length; +}; + +static void CovWritePacked(int pid, const char *module, const void *blob, + uptr blob_size) { + CHECK_GE(cov_fd, 0); + unsigned module_name_length = internal_strlen(module); + CovHeader header; + header.pid = pid; + header.module_name_length = module_name_length; + header.data_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); + uptr 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) { + uptr 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); + } + } +} + // Dump the coverage on disk. -void CovDump() { +static void CovDump() { + if (!common_flags()->coverage) return; #if !SANITIZER_WINDOWS if (atomic_fetch_add(&dump_once_guard, 1, memory_order_relaxed)) return; @@ -81,7 +135,7 @@ InternalMmapVector offsets(size); const uptr *vb = pc_array; const uptr *ve = vb + size; - MemoryMappingLayout proc_maps(/*cache_enabled*/false); + MemoryMappingLayout proc_maps(/*cache_enabled*/true); uptr mb, me, off, prot; InternalScopedBuffer module(4096); InternalScopedBuffer path(4096 * 2); @@ -102,22 +156,57 @@ offsets.push_back(static_cast(diff)); } char *module_name = StripModuleName(module.data()); - internal_snprintf((char *)path.data(), path.size(), "%s.%zd.sancov", - module_name, internal_getpid()); - InternalFree(module_name); - uptr fd = OpenFile(path.data(), true); - if (internal_iserror(fd)) { - Report(" CovDump: failed to open %s for writing\n", path.data()); + if (cov_sandboxed) { + 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 { - 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); + // One file per module per process. + internal_snprintf((char *)path.data(), path.size(), "%s.%zd.sancov", + module_name, internal_getpid()); + uptr fd = OpenFile(path.data(), true); + if (internal_iserror(fd)) { + Report(" CovDump: failed to open %s for writing\n", path.data()); + } else { + 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 } +static void OpenPackedFileForWriting() { + CHECK(cov_fd == kInvalidFd); + InternalScopedBuffer path(1024); + internal_snprintf((char *)path.data(), path.size(), "%zd.sancov.packed", + internal_getpid()); + uptr fd = OpenFile(path.data(), true); + if (internal_iserror(fd)) { + Report(" Coverage: failed to open %s for writing\n", path.data()); + Die(); + } + cov_fd = fd; +} + +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. + OpenPackedFileForWriting(); +} + } // namespace __sanitizer extern "C" { Index: lib/sanitizer_common/sanitizer_internal_defs.h =================================================================== --- lib/sanitizer_common/sanitizer_internal_defs.h +++ lib/sanitizer_common/sanitizer_internal_defs.h @@ -91,11 +91,15 @@ SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_set_report_path(const char *path); - // Notify the tools that the sandbox is going to be turned on. The reserved - // parameter will be used in the future to hold a structure with functions - // that the tools may call to bypass the sandbox. - SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE - void __sanitizer_sandbox_on_notify(void *reserved); + typedef struct { + int coverage_sandboxed; + int coverage_fd; + __sanitizer::uptr coverage_max_block_size; + } __sanitizer_sandbox_arguments; + + // Notify the tools that the sandbox is going to be turned on. + SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE void + __sanitizer_sandbox_on_notify(__sanitizer_sandbox_arguments *args); // This function is called by the tool when it has just finished reporting // an error. 'error_summary' is a one-line string that summarizes Index: lib/sanitizer_common/sanitizer_linux.cc =================================================================== --- lib/sanitizer_common/sanitizer_linux.cc +++ lib/sanitizer_common/sanitizer_linux.cc @@ -390,7 +390,7 @@ } #endif // SANITIZER_GO -void PrepareForSandboxing() { +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 @@ -401,6 +401,7 @@ if (Symbolizer *sym = Symbolizer::GetOrNull()) sym->PrepareForSandboxing(); #endif + CovPrepareForSandboxing(args); } enum MutexState { Index: lib/sanitizer_common/sanitizer_mac.cc =================================================================== --- lib/sanitizer_common/sanitizer_mac.cc +++ lib/sanitizer_common/sanitizer_mac.cc @@ -191,7 +191,8 @@ UNIMPLEMENTED(); } -void PrepareForSandboxing() { +void PrepareForSandboxing(__sanitizer_sandbox_arguments *args) { + (void)args; // Nothing here for now. } Index: lib/sanitizer_common/sanitizer_win.cc =================================================================== --- lib/sanitizer_common/sanitizer_win.cc +++ lib/sanitizer_common/sanitizer_win.cc @@ -192,7 +192,8 @@ UNIMPLEMENTED(); } -void PrepareForSandboxing() { +void PrepareForSandboxing(__sanitizer_sandbox_arguments *args) { + (void)args; // Nothing here for now. } Index: lib/sanitizer_common/scripts/sancov.py =================================================================== --- lib/sanitizer_common/scripts/sancov.py +++ lib/sanitizer_common/scripts/sancov.py @@ -4,6 +4,7 @@ # We need to merge these integers into a set and then # either print them (as hex) or dump them into another file. import array +import struct import sys prog_name = ""; @@ -11,16 +12,16 @@ def Usage(): print >> sys.stderr, "Usage: \n" + \ " " + prog_name + " merge file1 [file2 ...] > output\n" \ - " " + prog_name + " print file1 [file2 ...]\n" + " " + prog_name + " print file1 [file2 ...]\n" \ + " " + prog_name + " unpack file1 [file2 ...]\n" exit(1) def ReadOneFile(path): - f = open(path, mode="rb") - f.seek(0, 2) - size = f.tell() - f.seek(0, 0) - s = set(array.array('I', f.read(size))) - f.close() + with open(path, mode="rb") as f: + f.seek(0, 2) + size = f.tell() + f.seek(0, 0) + s = set(array.array('I', f.read(size))) print >>sys.stderr, "%s: read %d PCs from %s" % (prog_name, size / 4, path) return s @@ -44,6 +45,37 @@ a = array.array('I', s) a.tofile(sys.stdout) + +def UnpackOneFile(path): + with open(path, mode="rb") as f: + print >> sys.stderr, "%s: unpacking %s" % (prog_name, path) + while True: + header = f.read(16) + if not header: return + if len(header) < 16: + break + pid, module_length, blob_size = struct.unpack('iIL', header) + module = f.read(module_length) + blob = f.read(blob_size) + assert(len(module) == module_length) + assert(len(blob) == blob_size) + extracted_file = "%s.%d.sancov" % (module, pid) + print >> sys.stderr, "%s: extracting %s" % \ + (prog_name, extracted_file) + with open(extracted_file, 'wb') as f2: + # The packed file may contain multiple blobs for the same pid/module + # pair. Append to the end of the file instead of overwriting. + f2.seek(0, 2) + f2.write(blob) + # fail + raise Exception('Error reading file %s' % path) + + +def Unpack(files): + for f in files: + UnpackOneFile(f) + + if __name__ == '__main__': prog_name = sys.argv[0] if len(sys.argv) <= 2: @@ -52,5 +84,7 @@ PrintFiles(sys.argv[2:]) elif sys.argv[1] == "merge": MergeAndPrint(sys.argv[2:]) + elif sys.argv[1] == "unpack": + Unpack(sys.argv[2:]) else: Usage()