Index: lib/sanitizer_common/sanitizer_common.h =================================================================== --- lib/sanitizer_common/sanitizer_common.h +++ lib/sanitizer_common/sanitizer_common.h @@ -279,6 +279,13 @@ char *FindPathToBinary(const char *name); bool IsPathSeparator(const char c); bool IsAbsolutePath(const char *path); +// Starts a subprocess and returs its pid. +// If *_fd parameters are >=0 their corresponding input/output +// streams would be redirect to the file. The files would always be closed +// in parent process even in case of an error. +int StartSubprocess(const char *program, char *const argv[], + fd_t std_in_fd = kInvalidFd, fd_t std_out_fd = kInvalidFd, + fd_t std_err_fd = kInvalidFd); u32 GetUid(); void ReExec(); Index: lib/sanitizer_common/sanitizer_common.cc =================================================================== --- lib/sanitizer_common/sanitizer_common.cc +++ lib/sanitizer_common/sanitizer_common.cc @@ -423,6 +423,10 @@ static const char kPathSeparator = SANITIZER_WINDOWS ? ';' : ':'; char *FindPathToBinary(const char *name) { + if (FileExists(name)) { + return internal_strdup(name); + } + const char *path = GetEnv("PATH"); if (!path) return nullptr; Index: lib/sanitizer_common/sanitizer_coverage_libcdep.cc =================================================================== --- lib/sanitizer_common/sanitizer_coverage_libcdep.cc +++ lib/sanitizer_common/sanitizer_coverage_libcdep.cc @@ -789,6 +789,10 @@ CHECK_NE(sym, nullptr); InternalMmapVector offsets(0); InternalScopedString path(kMaxPathLength); + + InternalMmapVector argv(module_name_vec.size() + 2); + argv.push_back(internal_strdup(common_flags()->sancov)); + for (uptr m = 0; m < module_name_vec.size(); m++) { auto r = module_name_vec[m]; GetRangeOffsets(r, sym, &offsets); @@ -813,11 +817,28 @@ if (fd == kInvalidFd) continue; WriteToFile(fd, offsets.data(), offsets.size() * sizeof(offsets[0])); CloseFile(fd); + argv.push_back(internal_strdup(path.data())); VReport(1, " CovDump: %s: %zd PCs written\n", path.data(), num_offsets); } } if (cov_fd != kInvalidFd) CloseFile(cov_fd); + + argv.push_back(nullptr); + if (common_flags()->html_cov_report) { + char *sancov = FindPathToBinary(common_flags()->sancov); + if (sancov) { + InternalScopedString report_path(kMaxPathLength); + fd_t report_fd = + CovOpenFile(&report_path, false /* packed */, "report", "html"); + StartSubprocess(sancov, argv.data(), kInvalidFd /* stdin */, + report_fd /* std_out */); + InternalFree(sancov); + } + } + for (uptr i = 0; i < argv.size(); ++i) { + InternalFree(argv[i]); + } } void CoverageData::DumpAll() { Index: lib/sanitizer_common/sanitizer_flags.inc =================================================================== --- lib/sanitizer_common/sanitizer_flags.inc +++ lib/sanitizer_common/sanitizer_flags.inc @@ -202,3 +202,5 @@ "halt_on_error=false mode (asan only).") COMMON_FLAG(bool, print_cmdline, false, "Print command line on crash " "(asan only).") +COMMON_FLAG(bool, html_cov_report, false, "Generate html coverage report.") +COMMON_FLAG(const char *, sancov, "sancov", "Sancov tool location.") Index: lib/sanitizer_common/sanitizer_linux.cc =================================================================== --- lib/sanitizer_common/sanitizer_linux.cc +++ lib/sanitizer_common/sanitizer_linux.cc @@ -1233,6 +1233,55 @@ // No need to re-exec on Linux. } +int StartSubprocess(const char *program, char *const argv[], fd_t std_in_fd, + fd_t std_out_fd, fd_t std_err_fd) { + if (!FileExists(program)) { + Report("WARNING: Program %s not found!\n", program); + return -1; + } + + int pid = internal_fork(); + + int rverrno; + if (internal_iserror(pid, &rverrno)) { + Report("WARNING: failed to fork (errno %d)\n", rverrno); + } else if (pid == 0) { + // Child subprocess + + if (std_in_fd >= 0) { + internal_close(STDIN_FILENO); + internal_dup2(std_in_fd, STDIN_FILENO); + internal_close(std_in_fd); + } + if (std_out_fd >= 0) { + internal_close(STDOUT_FILENO); + internal_dup2(std_out_fd, STDOUT_FILENO); + internal_close(std_out_fd); + } + if (std_err_fd >= 0) { + internal_close(STDERR_FILENO); + internal_dup2(std_err_fd, STDERR_FILENO); + internal_close(std_err_fd); + } + + internal_execve(program, argv, nullptr); + internal__exit(1); + } + + // Parent process + if (std_in_fd >= 0) { + internal_close(std_in_fd); + } + if (std_out_fd >= 0) { + internal_close(std_out_fd); + } + if (std_err_fd >= 0) { + internal_close(std_err_fd); + } + + return pid; +} + } // namespace __sanitizer #endif // SANITIZER_FREEBSD || SANITIZER_LINUX Index: test/asan/TestCases/Posix/coverage_html_report.cc =================================================================== --- /dev/null +++ test/asan/TestCases/Posix/coverage_html_report.cc @@ -0,0 +1,70 @@ +// RUN: %clangxx_asan -fsanitize-coverage=func -DSHARED %s -shared -o +// %dynamiclib -fPIC %ld_flags_rpath_so +// RUN: %clangxx_asan -fsanitize-coverage=func %s %ld_flags_rpath_exe -o %t +// RUN: rm -rf %T/coverage_html_report && mkdir -p %T/coverage_html_report && cd +// %T/coverage_html_report +// RUN: %env_asan_opts=coverage=1:verbosity=1:html_cov_report=1:sancov=%sancovcc +// %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-main +// RUN: rm -r %T/coverage_html_report +// +// https://code.google.com/p/address-sanitizer/issues/detail?id=263 +// XFAIL: android + +#include +#include +#include +#include +#include + +#ifdef SHARED +void bar() { printf("bar\n"); } +#else +__attribute__((noinline)) void foo() { printf("foo\n"); } +extern void bar(); + +int G[4]; + +int main(int argc, char **argv) { + fprintf(stderr, "PID: %d\n", getpid()); + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "foo")) { + uintptr_t old_coverage = __sanitizer_get_total_unique_coverage(); + foo(); + uintptr_t new_coverage = __sanitizer_get_total_unique_coverage(); + assert(new_coverage > old_coverage); + } + if (!strcmp(argv[i], "bar")) + bar(); + } + if (argc == 5) { + static volatile char *zero = 0; + *zero = 0; // SEGV if argc == 5. + } + return G[argc]; // Buffer overflow if argc >= 4. +} +#endif + +// CHECK-main: PID: [[PID:[0-9]+]] +// CHECK-main: [[PID]].sancov: 1 PCs written +// CHECK-main-NOT: .so.[[PID]] +// +// CHECK-foo: PID: [[PID:[0-9]+]] +// CHECK-foo: [[PID]].sancov: 2 PCs written +// CHECK-foo-NOT: .so.[[PID]] +// +// CHECK-bar: PID: [[PID:[0-9]+]] +// CHECK-bar: .so.[[PID]].sancov: 1 PCs written +// CHECK-bar: [[PID]].sancov: 1 PCs written +// +// CHECK-foo-bar: PID: [[PID:[0-9]+]] +// CHECK-foo-bar: so.[[PID]].sancov: 1 PCs written +// CHECK-foo-bar: [[PID]].sancov: 2 PCs written +// +// CHECK-report: AddressSanitizer: global-buffer-overflow +// CHECK-report: PCs written +// +// CHECK-segv: AddressSanitizer: SEGV +// CHECK-segv: PCs written +// +// CHECK-SANCOV1: 1 PCs total +// CHECK-SANCOV2: 2 PCs total Index: test/asan/lit.cfg =================================================================== --- test/asan/lit.cfg +++ test/asan/lit.cfg @@ -138,7 +138,9 @@ if not os.path.exists(sancov): lit_config.fatal("Can't find script on path %r" % sancov) python_exec = get_required_attr(config, "python_executable") -config.substitutions.append( ("%sancov", python_exec + " " + sancov + " ") ) +config.substitutions.append( ("%sancov ", python_exec + " " + sancov + " ") ) +config.substitutions.append( ("%sancovcc ", os.path.join( + get_required_attr(config, "llvm_obj_root"), "bin/sancov")) ) # Determine kernel bitness if config.host_arch.find('64') != -1 and config.android != "1":