Index: lib/sanitizer_common/sanitizer_common.h =================================================================== --- lib/sanitizer_common/sanitizer_common.h +++ lib/sanitizer_common/sanitizer_common.h @@ -279,6 +279,15 @@ 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); +// Wait for process to terminate and returns its exit code. +int WaitForProcess(int pid); 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_common_nolibc.cc =================================================================== --- lib/sanitizer_common/sanitizer_common_nolibc.cc +++ lib/sanitizer_common/sanitizer_common_nolibc.cc @@ -24,4 +24,15 @@ void WriteToSyslog(const char *buffer) {} void Abort() { internal__exit(1); } +#if !SANITIZER_LINUX +int StartSubprocess(const char *program, char *const argv[], + fd_t std_in_fd, fd_t std_out_fd, + fd_t std_err_fd) { + return -1; +} +int WaitForProcess(int pid) { + return -1; +} +#endif // !SANITIZER_LINUX + } // namespace __sanitizer 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,13 @@ CHECK_NE(sym, nullptr); InternalMmapVector offsets(0); InternalScopedString path(kMaxPathLength); + + InternalMmapVector sancov_argv(module_name_vec.size() + 2); + sancov_argv.push_back(FindPathToBinary(common_flags()->sancov)); + sancov_argv.push_back(internal_strdup("-obj")); + sancov_argv.push_back(internal_strdup(GetArgv()[0])); + sancov_argv.push_back(internal_strdup("-html-report")); + for (uptr m = 0; m < module_name_vec.size(); m++) { auto r = module_name_vec[m]; GetRangeOffsets(r, sym, &offsets); @@ -813,11 +820,34 @@ if (fd == kInvalidFd) continue; WriteToFile(fd, offsets.data(), offsets.size() * sizeof(offsets[0])); CloseFile(fd); + sancov_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); + + if (common_flags()->html_cov_report) { + sancov_argv.push_back(nullptr); + if (sancov_argv[0]) { + InternalScopedString report_path(kMaxPathLength); + fd_t report_fd = CovOpenFile(&report_path, false /* packed */, + GetProcessName(), "html"); + int pid = + StartSubprocess(sancov_argv[0], sancov_argv.data(), + kInvalidFd /* stdin */, report_fd /* std_out */); + if (pid > 0) { + int result = WaitForProcess(pid); + if (result == 0) { + VReport(1, " CovDump: html report generated to %s (%d)\n", + report_path.data(), result); + } + } + } + } + for (uptr i = 0; i < sancov_argv.size(); ++i) { + InternalFree(sancov_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,66 @@ // 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) { + int pid = -1; + + if (!FileExists(program)) { + Report("WARNING: Program %s not found!\n", program); + } else { + 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; +} + +int WaitForProcess(int pid) { + int process_status; + uptr waitpid_status = internal_waitpid(pid, &process_status, 0); + int local_errno; + if (internal_iserror(waitpid_status, &local_errno)) { + VReport(1, "Waiting on the process failed (errno %d).\n", local_errno); + return -1; + } + return process_status; +} + } // 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,24 @@ +// REQUIRES: has_sancovcc +// RUN: %clangxx_asan -fsanitize-coverage=func %s -o %t +// RUN: rm -rf %T/coverage_html_report +// RUN: mkdir -p %T/coverage_html_report +// RUN: 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: ls *.html | FileCheck %s --check-prefix=CHECK-ls +// RUN: rm -r %T/coverage_html_report + +#include +#include + +void bar() { printf("bar\n"); } + +int main(int argc, char **argv) { + fprintf(stderr, "PID: %d\n", getpid()); + bar(); + return 0; +} + +// CHECK-main: PID: [[PID:[0-9]+]] +// CHECK-main: [[PID]].sancov: 2 PCs written +// CHECK-main: html report generated to ./coverage_html_report.cc.tmp.[[PID]].html +// CHECK-ls: coverage_html_report.cc.tmp.{{[0-9]+}}.html Index: test/asan/lit.cfg =================================================================== --- test/asan/lit.cfg +++ test/asan/lit.cfg @@ -138,7 +138,13 @@ 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 + " ") ) + +sancovcc_path = os.path.join( + get_required_attr(config, "llvm_obj_root"), "bin/sancov") +if os.path.exists(sancovcc_path): + config.available_features.add("has_sancovcc") + config.substitutions.append( ("%sancovcc ", sancovcc_path) ) # Determine kernel bitness if config.host_arch.find('64') != -1 and config.android != "1":