[libc-commits] [libc] Code Coverage Prototype - Linux Extraction Hook (PR #210062)

Tapiwa Gonga via libc-commits libc-commits at lists.llvm.org
Fri Jul 17 06:46:50 PDT 2026


https://github.com/tapiwagonga updated https://github.com/llvm/llvm-project/pull/210062

>From f4f50f1df5d1b8e8faf74583cacfc13bd3e3bcf1 Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Thu, 16 Jul 2026 13:26:20 +0000
Subject: [PATCH 1/2] [libc][WIP] Implement freestanding coverage extraction
 prototype using raw syscalls

---
 libc/test/UnitTest/LibcTestMain.cpp | 91 ++++++++++++++++++++++++++++-
 1 file changed, 88 insertions(+), 3 deletions(-)

diff --git a/libc/test/UnitTest/LibcTestMain.cpp b/libc/test/UnitTest/LibcTestMain.cpp
index c348d5ef1aa1b..00f747d1f6f1e 100644
--- a/libc/test/UnitTest/LibcTestMain.cpp
+++ b/libc/test/UnitTest/LibcTestMain.cpp
@@ -43,8 +43,90 @@ 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 "src/__support/OSUtil/syscall.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <time.h>
+
+extern "C" {
+__attribute__((weak)) uint64_t __llvm_profile_get_size_for_buffer(void);
+__attribute__((weak)) int __llvm_profile_write_buffer(char *Buffer);
+__attribute__((weak)) void __llvm_profile_set_filename(const char *FilenamePat);
+
+// 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 {
+void write_raw_profile() {
+  if (!__llvm_profile_get_size_for_buffer || !__llvm_profile_write_buffer)
+    return;
+
+  uint64_t required_size = __llvm_profile_get_size_for_buffer();
+  if (required_size == 0)
+    return;
+
+  long mmap_ret = LIBC_NAMESPACE::syscall_impl<long>(
+      SYS_mmap, nullptr, required_size, PROT_READ | PROT_WRITE,
+      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  if (mmap_ret < 0 && mmap_ret > -4096)
+    return;
+  char *profile_buffer = reinterpret_cast<char *>(mmap_ret);
+
+  if (__llvm_profile_write_buffer(profile_buffer) != 0) {
+    LIBC_NAMESPACE::syscall_impl<long>(SYS_munmap, profile_buffer, required_size);
+    return;
+  }
+
+  // Create a minimal filename: libc_cov_<pid>.profraw
+  long pid = LIBC_NAMESPACE::syscall_impl<long>(SYS_getpid);
+  if (pid <= 0) pid = 1;
+
+  char filename[64] = "libc_cov_";
+  int idx = 9;
+  
+  long temp = pid;
+  char digits[32];
+  int d_len = 0;
+  while (temp > 0) { digits[d_len++] = static_cast<char>('0' + (temp % 10)); temp /= 10; }
+  if (d_len == 0) digits[d_len++] = '0';
+  for (int i = d_len - 1; i >= 0; --i) filename[idx++] = digits[i];
+  
+  const char *suffix = ".profraw";
+  for (int i = 0; suffix[i] != '\0'; ++i) filename[idx++] = suffix[i];
+  filename[idx] = '\0';
+
+  long fd = LIBC_NAMESPACE::syscall_impl<long>(
+      SYS_openat, AT_FDCWD, filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+      
+  if (fd >= 0) {
+    uint64_t bytes_written = 0;
+    while (bytes_written < required_size) {
+      long ret = LIBC_NAMESPACE::syscall_impl<long>(
+          SYS_write, fd, profile_buffer + bytes_written, required_size - bytes_written);
+      if (ret <= 0 && ret != -EINTR) break;
+      if (ret > 0) bytes_written += ret;
+    }
+    LIBC_NAMESPACE::syscall_impl<long>(SYS_close, fd);
+  }
+
+  LIBC_NAMESPACE::syscall_impl<long>(SYS_munmap, profile_buffer, required_size);
+
+  // 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
+
 #if __STDC_HOSTED__
 #define TEST_MAIN int main
 #else
@@ -56,5 +138,8 @@ TEST_MAIN(int argc, char **argv, char **envp) {
   LIBC_NAMESPACE::testing::argv = argv;
   LIBC_NAMESPACE::testing::envp = envp;
 
-  return LIBC_NAMESPACE::testing::Test::runTests(parseOptions(argc, argv));
+  int result =
+      LIBC_NAMESPACE::testing::Test::runTests(parseOptions(argc, argv));
+  write_raw_profile();
+  return result;
 }

>From 6dccbeeddbed1442917885a1a91398c4e5e521b3 Mon Sep 17 00:00:00 2001
From: Tapiwa Gonga <tapiwagonga at google.com>
Date: Fri, 17 Jul 2026 13:45:22 +0000
Subject: [PATCH 2/2] [libc] Integrate robust error handling and linux_syscall
 wrappers for coverage

---
 libc/test/UnitTest/CMakeLists.txt   |  11 +++
 libc/test/UnitTest/LibcTestMain.cpp | 123 ++++++++++++++++++----------
 2 files changed, 93 insertions(+), 41 deletions(-)

diff --git a/libc/test/UnitTest/CMakeLists.txt b/libc/test/UnitTest/CMakeLists.txt
index 4a47597ae3a45..b8142f48ef08a 100644
--- a/libc/test/UnitTest/CMakeLists.txt
+++ b/libc/test/UnitTest/CMakeLists.txt
@@ -69,6 +69,16 @@ else()
   set(test_logger_osutil "libc.src.__support.OSUtil.osutil")
 endif()
 
+if(LIBC_TARGET_OS STREQUAL "linux")
+  set(libctest_syscall_wrappers
+    libc.src.__support.OSUtil.linux.syscall_wrappers.close
+    libc.src.__support.OSUtil.linux.syscall_wrappers.mmap
+    libc.src.__support.OSUtil.linux.syscall_wrappers.munmap
+    libc.src.__support.OSUtil.linux.syscall_wrappers.open
+    libc.src.__support.OSUtil.linux.syscall_wrappers.write
+  )
+endif()
+
 add_unittest_framework_library(
   LibcTest
   SRCS
@@ -94,6 +104,7 @@ add_unittest_framework_library(
     libc.src.__support.macros.properties.types
     libc.src.__support.uint128
     ${test_logger_osutil}
+    ${libctest_syscall_wrappers}
 )
 
 set(libc_death_test_srcs LibcDeathTestExecutors.cpp)
diff --git a/libc/test/UnitTest/LibcTestMain.cpp b/libc/test/UnitTest/LibcTestMain.cpp
index 00f747d1f6f1e..8ddb1349d17d5 100644
--- a/libc/test/UnitTest/LibcTestMain.cpp
+++ b/libc/test/UnitTest/LibcTestMain.cpp
@@ -44,17 +44,24 @@ TestOptions parseOptions(int argc, char **argv) {
 } // anonymous namespace
 
 #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 <errno.h>
-#include <fcntl.h>
-#include <sys/mman.h>
+#include "src/__support/integer_to_string.h"
+#include "src/string/memory_utils/inline_memcpy.h"
 #include <sys/syscall.h>
-#include <time.h>
 
 extern "C" {
 __attribute__((weak)) uint64_t __llvm_profile_get_size_for_buffer(void);
-__attribute__((weak)) int __llvm_profile_write_buffer(char *Buffer);
-__attribute__((weak)) void __llvm_profile_set_filename(const char *FilenamePat);
+__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.
@@ -62,6 +69,26 @@ 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;
@@ -70,51 +97,65 @@ void write_raw_profile() {
   if (required_size == 0)
     return;
 
-  long mmap_ret = LIBC_NAMESPACE::syscall_impl<long>(
-      SYS_mmap, nullptr, required_size, PROT_READ | PROT_WRITE,
+  auto mmap_or_error = LIBC_NAMESPACE::linux_syscalls::mmap(
+      nullptr, required_size, PROT_READ | PROT_WRITE,
       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
-  if (mmap_ret < 0 && mmap_ret > -4096)
-    return;
-  char *profile_buffer = reinterpret_cast<char *>(mmap_ret);
+  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::syscall_impl<long>(SYS_munmap, profile_buffer, required_size);
-    return;
+    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;
-
-  char filename[64] = "libc_cov_";
-  int idx = 9;
-  
-  long temp = pid;
-  char digits[32];
-  int d_len = 0;
-  while (temp > 0) { digits[d_len++] = static_cast<char>('0' + (temp % 10)); temp /= 10; }
-  if (d_len == 0) digits[d_len++] = '0';
-  for (int i = d_len - 1; i >= 0; --i) filename[idx++] = digits[i];
-  
-  const char *suffix = ".profraw";
-  for (int i = 0; suffix[i] != '\0'; ++i) filename[idx++] = suffix[i];
-  filename[idx] = '\0';
-
-  long fd = LIBC_NAMESPACE::syscall_impl<long>(
-      SYS_openat, AT_FDCWD, filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
-      
-  if (fd >= 0) {
-    uint64_t bytes_written = 0;
-    while (bytes_written < required_size) {
-      long ret = LIBC_NAMESPACE::syscall_impl<long>(
-          SYS_write, fd, profile_buffer + bytes_written, required_size - bytes_written);
-      if (ret <= 0 && ret != -EINTR) break;
-      if (ret > 0) bytes_written += ret;
+  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();
+
+  uint64_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;
     }
-    LIBC_NAMESPACE::syscall_impl<long>(SYS_close, fd);
+    ssize_t ret = write_or_error.value();
+    if (ret == 0) {
+      write_error_occurred = true;
+      break;
+    }
+    bytes_written += ret;
   }
 
-  LIBC_NAMESPACE::syscall_impl<long>(SYS_munmap, profile_buffer, required_size);
+  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)



More information about the libc-commits mailing list