Index: lib/sanitizer_common/sanitizer_common.h =================================================================== --- lib/sanitizer_common/sanitizer_common.h +++ lib/sanitizer_common/sanitizer_common.h @@ -290,6 +290,9 @@ fd_t stderr_fd = kInvalidFd); // Checks if specified process is still running bool IsProcessRunning(pid_t pid); +// Waits for the process to finish and returns its exit code. +// Returns -1 in case of an error. +int WaitForProcess(pid_t pid); u32 GetUid(); void ReExec(); Index: lib/sanitizer_common/sanitizer_coverage_libcdep.cc =================================================================== --- lib/sanitizer_common/sanitizer_coverage_libcdep.cc +++ lib/sanitizer_common/sanitizer_coverage_libcdep.cc @@ -783,12 +783,45 @@ (*offsets)[i] = UnbundlePc((*offsets)[i]); } +static void GenerateHtmlReport(const InternalMmapVector &sancov_argv) { + if (!common_flags()->html_cov_report) { + return; + } + + 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); + } + } + } +} + void CoverageData::DumpOffsets() { auto sym = Symbolizer::GetOrInit(); if (!common_flags()->coverage_pcs) return; 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")); + auto argv_deleter = at_scope_exit([&] { + for (uptr i = 0; i < sancov_argv.size(); ++i) { + InternalFree(sancov_argv[i]); + } + }); + for (uptr m = 0; m < module_name_vec.size(); m++) { auto r = module_name_vec[m]; GetRangeOffsets(r, sym, &offsets); @@ -813,11 +846,15 @@ 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); + + sancov_argv.push_back(nullptr); + GenerateHtmlReport(sancov_argv); } 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_posix_libcdep.cc =================================================================== --- lib/sanitizer_common/sanitizer_posix_libcdep.cc +++ lib/sanitizer_common/sanitizer_posix_libcdep.cc @@ -383,6 +383,17 @@ return waitpid_status == 0; } +int WaitForProcess(pid_t 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_POSIX Index: lib/sanitizer_common/sanitizer_win.cc =================================================================== --- lib/sanitizer_common/sanitizer_win.cc +++ lib/sanitizer_common/sanitizer_win.cc @@ -789,6 +789,8 @@ return false; } +int WaitForProcess(pid_t pid) { return -1; } + } // namespace __sanitizer #endif // _WIN32 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":