[libc-commits] [libc] [libc] Enabling code coverage via Linux syscalls (PR #213271)
Jeff Bailey via libc-commits
libc-commits at lists.llvm.org
Tue Aug 4 00:42:08 PDT 2026
================
@@ -43,8 +43,151 @@ TestOptions parseOptions(int argc, char **argv) {
} // anonymous namespace
-// The C++ standard forbids declaring the main function with a linkage specifier
-// outisde of 'freestanding' mode, only define the linkage for hermetic tests.
+#if defined(__linux__)
+#include "hdr/errno_macros.h"
+#include "hdr/fcntl_macros.h"
+#include "hdr/sys_mman_macros.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/close.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/mmap.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/munmap.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/open.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/write.h"
+#include "src/__support/OSUtil/syscall.h"
+#include "src/__support/integer_to_string.h"
+#include "src/string/memory_utils/inline_memcpy.h"
+#include <sys/syscall.h>
+
+//===----------------------------------------------------------------------===//
+// Freestanding Linux Code Coverage Profile Writer
+//
+// Freestanding (-nostdlib) libc binaries cannot link standard compiler-rt
+// file I/O (fopen/fwrite). Here we override compiler-rt's default filename
+// to "/dev/null" and register write_raw_profile() via atexit() to dump
+// raw coverage counters (libc_cov_<pid>.profraw) using direct Linux
+// system calls (SYS_mmap, SYS_openat, SYS_write, SYS_close, SYS_munmap).
+//===----------------------------------------------------------------------===//
+extern "C" {
+__attribute__((weak)) uint64_t __llvm_profile_get_size_for_buffer();
+__attribute__((weak)) int __llvm_profile_write_buffer(char *buffer);
+__attribute__((weak)) void
+__llvm_profile_set_filename(const char *filename_pat);
+
+// Override compiler-rt's weak filename symbol. This redirects the default
+// filename to /dev/null to silence the default dumper by default.
+char __llvm_profile_filename[] = "/dev/null";
+}
+
+namespace {
+struct FixedSizeBuffer {
+ char data[64];
+ size_t idx = 0;
+
+ FixedSizeBuffer() { data[0] = '\0'; }
+
+ bool append(string_view str) {
+ if (idx + str.size() >= sizeof(data))
+ return false;
+ LIBC_NAMESPACE::inline_memcpy(data + idx, str.data(), str.size());
+ idx += str.size();
+ data[idx] = '\0';
+ return true;
+ }
+};
+
+LIBC_INLINE void report_error(string_view msg) {
+ LIBC_NAMESPACE::syscall_impl<long>(SYS_write, 2, msg.data(), msg.size());
+}
+
+void write_raw_profile() {
+ if (!__llvm_profile_get_size_for_buffer || !__llvm_profile_write_buffer)
+ return;
+
+ size_t required_size =
+ static_cast<size_t>(__llvm_profile_get_size_for_buffer());
+ if (required_size == 0)
+ return;
+
+ auto mmap_or_error = LIBC_NAMESPACE::linux_syscalls::mmap(
+ nullptr, required_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (!mmap_or_error)
+ return report_error("error: libc coverage failed to mmap buffer\n");
+ char *profile_buffer = reinterpret_cast<char *>(mmap_or_error.value());
+
+ if (__llvm_profile_write_buffer(profile_buffer) != 0) {
+ LIBC_NAMESPACE::linux_syscalls::munmap(profile_buffer, required_size);
+ return report_error(
+ "error: libc coverage failed to write profile buffer\n");
+ }
+
+ // Create a minimal filename: libc_cov_<pid>.profraw
+ long pid = LIBC_NAMESPACE::syscall_impl<long>(SYS_getpid);
+ if (pid <= 0)
+ pid = 1;
+
+ FixedSizeBuffer filename;
+ LIBC_NAMESPACE::IntegerToString<long> pid_str(pid);
+ if (!filename.append("libc_cov_") || !filename.append(pid_str.view()) ||
+ !filename.append(".profraw")) {
+ LIBC_NAMESPACE::linux_syscalls::munmap(profile_buffer, required_size);
+ return report_error("error: libc coverage filename buffer overflow\n");
+ }
+
+ auto fd_or_error = LIBC_NAMESPACE::linux_syscalls::open(
+ filename.data, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (!fd_or_error) {
+ LIBC_NAMESPACE::linux_syscalls::munmap(profile_buffer, required_size);
+ return report_error("error: libc coverage failed to open output file\n");
+ }
+ int fd = fd_or_error.value();
+
+ size_t bytes_written = 0;
+ bool write_error_occurred = false;
+ while (bytes_written < required_size) {
+ auto write_or_error = LIBC_NAMESPACE::linux_syscalls::write(
+ fd, profile_buffer + bytes_written, required_size - bytes_written);
+ if (!write_or_error) {
+ if (write_or_error.error() == EINTR)
+ continue;
+ write_error_occurred = true;
+ break;
+ }
+ ssize_t ret = write_or_error.value();
+ if (ret == 0) {
+ write_error_occurred = true;
+ break;
+ }
+ bytes_written += ret;
+ }
+
+ LIBC_NAMESPACE::linux_syscalls::close(fd);
+ LIBC_NAMESPACE::linux_syscalls::munmap(profile_buffer, required_size);
+
+ if (write_error_occurred || bytes_written < required_size)
+ return report_error(
+ "error: libc coverage failed to write all data to file\n");
+
+ // Clear the filename pattern to prevent compiler-rt from writing at exit.
+ if (__llvm_profile_set_filename)
+ __llvm_profile_set_filename("/dev/null");
+}
+} // anonymous namespace
+#else
+namespace {
+void write_raw_profile() {}
+} // anonymous namespace
+#endif
+
+extern "C" int atexit(void (*func)());
+
+// We must use a global constructor to register the atexit hook instead of
+// calling write_raw_profile() at the end of TEST_MAIN. This is because the
+// LLVM libc test framework spawns child processes for Death Tests which
+// terminate via exit(). If we don't intercept exit(), we lose their coverage.
+__attribute__((constructor)) void __register_libc_coverage() {
----------------
kaladron wrote:
```suggestion
__attribute__((constructor)) static void __register_libc_coverage() {
```
https://github.com/llvm/llvm-project/pull/213271
More information about the libc-commits
mailing list