22//
33// The embedded sidecar calls this module explicitly from its entrypoint. It
44// reinitializes the LLVM profiling runtime to use the correct output path.
5+ // Every other process that loads the library is left pointing at the path the
6+ // harness passes in LLVM_PROFILE_FILE, which is /dev/null: none of them runs
7+ // helper-rust, so their profiles cover nothing that is reported on.
58//
69// Coverage data is flushed when the process receives SIGUSR1, as well as
710// at normal process exit via atexit handler.
1518#include <signal.h>
1619#include <fcntl.h>
1720#include <stdarg.h>
21+ #include <limits.h>
22+ #include <sys/stat.h>
1823#include <time.h>
1924#include <errno.h>
2025
2126// LLVM profiling runtime API (from compiler-rt/lib/profile/)
2227extern void __llvm_profile_initialize_file (void );
2328extern void __llvm_profile_set_filename (const char * );
2429extern int __llvm_profile_write_file (void );
30+ extern void __llvm_profile_reset_counters (void );
2531extern const char * __llvm_profile_get_filename (void );
2632
2733static int log_fd = -1 ;
@@ -55,10 +61,17 @@ static void coverage_log(const char *fmt, ...) {
5561 write (log_fd , buf , offset );
5662}
5763
64+ // Writing in merge mode adds the file's counters back into the in-process
65+ // ones, so a process that writes more than once counts everything it has
66+ // already written a second time and the totals double per flush. This process
67+ // flushes on SIGUSR1 as well as at exit, and the runtime registers a writer of
68+ // its own on top of that, so the counters have to be dropped once they are on
69+ // disk.
5870static void coverage_flush (void ) {
5971 coverage_log ("coverage_flush() called, flushing coverage data" );
6072
6173 int ret = __llvm_profile_write_file ();
74+ __llvm_profile_reset_counters ();
6275 coverage_log ("__llvm_profile_write_file() returned %d (errno=%d: %s)" ,
6376 ret , errno , strerror (errno ));
6477
@@ -79,16 +92,67 @@ static void coverage_signal_handler(int signo) {
7992 (void )signo ;
8093 coverage_log ("SIGUSR1 received, flushing coverage data" );
8194 int ret = __llvm_profile_write_file ();
95+ __llvm_profile_reset_counters ();
8296 coverage_log ("__llvm_profile_write_file() returned %d" , ret );
8397}
8498
99+ // %m selects merge mode, where the runtime serializes on an fcntl write lock
100+ // and accumulates into one shared file per instrumented binary instead of
101+ // writing one file per process. The runtime creates that file itself, at the
102+ // first write, with open(..., 0666); the container's 022 umask turns it into
103+ // 0644. Every container in a run shares the file, and a sidecar inherits the
104+ // user of the SAPI that spawned it, so whoever writes first would otherwise
105+ // end up owning a file the other users cannot open. Get there first and create
106+ // it with the bits the umask would have dropped, after which the runtime only
107+ // ever opens a file that everyone can already write. A zero-length file is
108+ // what merge mode expects to find when there is nothing to merge yet.
109+ //
110+ // Only the runtime can expand the name, since %m stands for a hash of the
111+ // instrumented binary, hence __llvm_profile_get_filename() rather than a path
112+ // built by the caller.
113+ //
114+ // Created under a private name and hard-linked into place, so the file is
115+ // never reachable under its final name while it is still 0644; link() fails
116+ // with EEXIST if another process got there first, which is the wanted
117+ // create-only-if-absent. The staging name deliberately does not end in
118+ // .profraw, so a leftover cannot reach the coverage report.
119+ static void coverage_precreate_profile_file (void ) {
120+ const char * filename = __llvm_profile_get_filename ();
121+ if (!filename || !filename [0 ]) {
122+ return ;
123+ }
124+
125+ char staging [PATH_MAX ];
126+ int len = snprintf (staging , sizeof (staging ), "%s.new.%d" , filename ,
127+ (int )getpid ());
128+ if (len < 0 || len >= (int )sizeof (staging )) {
129+ return ;
130+ }
131+
132+ int fd = open (staging , O_RDWR | O_CREAT | O_EXCL , 0666 );
133+ if (fd < 0 ) {
134+ return ;
135+ }
136+ if (fchmod (fd , 0666 ) != 0 ) {
137+ coverage_log ("Failed to make %s group/other-writable: %s" , staging ,
138+ strerror (errno ));
139+ } else if (link (staging , filename ) != 0 && errno != EEXIST ) {
140+ coverage_log ("Failed to create %s: %s" , filename , strerror (errno ));
141+ }
142+ close (fd );
143+ unlink (staging );
144+ }
145+
85146void ddappsec_helper_coverage_init (void ) {
86147 coverage_log ("ddappsec_helper_coverage_init() called" );
87148
88- const char * profile_path = getenv ("_DD_HELPER_RUST_COVERAGE_FILE" );
89- if (!profile_path ) {
90- profile_path = "/helper-rust/coverage/helper-%h-%p-%m.profraw" ;
91- }
149+ // Hardcoded rather than read from the environment on purpose. Only about
150+ // a third of the sidecars in a run inherit the ambient environment at all,
151+ // so a variable naming this path would reach some of them and not others,
152+ // and pointing it anywhere else would split the merge pool in two instead
153+ // of moving it. %m rather than %p so that repeatedly respawned sidecars
154+ // keep merging into the one file.
155+ const char * profile_path = "/helper-rust/helper-%m.profraw" ;
92156 coverage_log ("Helper coverage profile = %s" , profile_path );
93157
94158 const char * filename_before = __llvm_profile_get_filename ();
@@ -103,6 +167,8 @@ void ddappsec_helper_coverage_init(void) {
103167 coverage_log ("Profile filename AFTER reinit: %s" ,
104168 filename_after ? filename_after : "(null)" );
105169
170+ coverage_precreate_profile_file ();
171+
106172 signal (SIGUSR1 , coverage_signal_handler );
107173 coverage_log ("Signal handler installed for SIGUSR1" );
108174
0 commit comments