| 1 | /* Routines required for instrumenting a program. */ |
| 2 | /* Compile this one with gcc. */ |
| 3 | /* Copyright (C) 1989-2025 Free Software Foundation, Inc. |
| 4 | |
| 5 | This file is part of GCC. |
| 6 | |
| 7 | GCC is free software; you can redistribute it and/or modify it under |
| 8 | the terms of the GNU General Public License as published by the Free |
| 9 | Software Foundation; either version 3, or (at your option) any later |
| 10 | version. |
| 11 | |
| 12 | GCC is distributed in the hope that it will be useful, but WITHOUT ANY |
| 13 | WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 14 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 15 | for more details. |
| 16 | |
| 17 | Under Section 7 of GPL version 3, you are granted additional |
| 18 | permissions described in the GCC Runtime Library Exception, version |
| 19 | 3.1, as published by the Free Software Foundation. |
| 20 | |
| 21 | You should have received a copy of the GNU General Public License and |
| 22 | a copy of the GCC Runtime Library Exception along with this program; |
| 23 | see the files COPYING3 and COPYING.RUNTIME respectively. If not, see |
| 24 | <http://www.gnu.org/licenses/>. */ |
| 25 | |
| 26 | #if !IN_GCOV_TOOL |
| 27 | /* Configured via the GCOV_ERROR_FILE environment variable; |
| 28 | it will either be stderr, or a file of the user's choosing. |
| 29 | Non-static to prevent multiple gcov-aware shared objects from |
| 30 | instantiating their own copies. */ |
| 31 | FILE *__gcov_error_file = NULL; |
| 32 | #endif |
| 33 | |
| 34 | /* A utility function to populate the __gcov_error_file pointer. |
| 35 | This should NOT be called outside of the gcov system driver code. */ |
| 36 | |
| 37 | static FILE * |
| 38 | get_gcov_error_file (void) |
| 39 | { |
| 40 | #if IN_GCOV_TOOL |
| 41 | return stderr; |
| 42 | #else |
| 43 | if (!__gcov_error_file) |
| 44 | { |
| 45 | const char *gcov_error_filename = getenv ("GCOV_ERROR_FILE" ); |
| 46 | |
| 47 | if (gcov_error_filename) |
| 48 | __gcov_error_file = fopen (gcov_error_filename, "a" ); |
| 49 | if (!__gcov_error_file) |
| 50 | __gcov_error_file = stderr; |
| 51 | } |
| 52 | return __gcov_error_file; |
| 53 | #endif |
| 54 | } |
| 55 | |
| 56 | /* A utility function for outputting errors. */ |
| 57 | |
| 58 | static int __attribute__((format(printf, 1, 2))) |
| 59 | gcov_error (const char *fmt, ...) |
| 60 | { |
| 61 | int ret; |
| 62 | va_list argp; |
| 63 | |
| 64 | va_start (argp, fmt); |
| 65 | FILE *f = get_gcov_error_file (); |
| 66 | ret = vfprintf (s: f, format: fmt, arg: argp); |
| 67 | va_end (argp); |
| 68 | |
| 69 | if (getenv (name: "GCOV_EXIT_AT_ERROR" )) |
| 70 | { |
| 71 | fprintf (stream: f, format: "profiling:exiting after an error\n" ); |
| 72 | exit (status: 1); |
| 73 | } |
| 74 | |
| 75 | return ret; |
| 76 | } |
| 77 | |
| 78 | #if !IN_GCOV_TOOL |
| 79 | static void |
| 80 | gcov_error_exit (void) |
| 81 | { |
| 82 | if (__gcov_error_file && __gcov_error_file != stderr) |
| 83 | { |
| 84 | fclose (__gcov_error_file); |
| 85 | __gcov_error_file = NULL; |
| 86 | } |
| 87 | } |
| 88 | #endif |
| 89 | |
| 90 | /* Make sure path component of the given FILENAME exists, create |
| 91 | missing directories. FILENAME must be writable. |
| 92 | Returns zero on success, or -1 if an error occurred. */ |
| 93 | |
| 94 | static int |
| 95 | create_file_directory (char *filename) |
| 96 | { |
| 97 | #if !defined(TARGET_POSIX_IO) && !defined(_WIN32) |
| 98 | (void) filename; |
| 99 | return -1; |
| 100 | #else |
| 101 | char *s; |
| 102 | |
| 103 | s = filename; |
| 104 | |
| 105 | if (HAS_DRIVE_SPEC(s)) |
| 106 | s += 2; |
| 107 | if (IS_DIR_SEPARATOR(*s)) |
| 108 | ++s; |
| 109 | for (; *s != '\0'; s++) |
| 110 | if (IS_DIR_SEPARATOR(*s)) |
| 111 | { |
| 112 | char sep = *s; |
| 113 | *s = '\0'; |
| 114 | |
| 115 | /* Try to make directory if it doesn't already exist. */ |
| 116 | if (access (name: filename, F_OK) == -1 |
| 117 | #ifdef TARGET_POSIX_IO |
| 118 | && mkdir (path: filename, mode: 0777) == -1 |
| 119 | #else |
| 120 | #ifdef mkdir |
| 121 | #undef mkdir |
| 122 | #endif |
| 123 | && mkdir (filename) == -1 |
| 124 | #endif |
| 125 | /* The directory might have been made by another process. */ |
| 126 | && errno != EEXIST) |
| 127 | { |
| 128 | gcov_error (fmt: "profiling:%s:Cannot create directory\n" , filename); |
| 129 | *s = sep; |
| 130 | return -1; |
| 131 | }; |
| 132 | |
| 133 | *s = sep; |
| 134 | }; |
| 135 | return 0; |
| 136 | #endif |
| 137 | } |
| 138 | |
| 139 | /* Replace filename variables in FILENAME. We currently support expansion: |
| 140 | |
| 141 | %p - process ID |
| 142 | %q{ENV} - value of environment variable ENV |
| 143 | */ |
| 144 | |
| 145 | static char * |
| 146 | replace_filename_variables (char *filename) |
| 147 | { |
| 148 | char buffer[16]; |
| 149 | char empty[] = "" ; |
| 150 | for (char *p = filename; *p != '\0'; p++) |
| 151 | { |
| 152 | unsigned length = strlen (s: filename); |
| 153 | if (*p == '%' && *(p + 1) != '\0') |
| 154 | { |
| 155 | unsigned start = p - filename; |
| 156 | p++; |
| 157 | char *replacement = NULL; |
| 158 | switch (*p) |
| 159 | { |
| 160 | case 'p': |
| 161 | sprintf (s: buffer, format: "%d" , getpid ()); |
| 162 | replacement = buffer; |
| 163 | p++; |
| 164 | break; |
| 165 | case 'q': |
| 166 | if (*(p + 1) == '{') |
| 167 | { |
| 168 | p += 2; |
| 169 | char *e = strchr (s: p, c: '}'); |
| 170 | if (e) |
| 171 | { |
| 172 | *e = '\0'; |
| 173 | replacement = getenv (name: p); |
| 174 | if (replacement == NULL) |
| 175 | replacement = empty; |
| 176 | p = e + 1; |
| 177 | } |
| 178 | else |
| 179 | return filename; |
| 180 | } |
| 181 | break; |
| 182 | default: |
| 183 | return filename; |
| 184 | } |
| 185 | |
| 186 | /* Concat beginning of the path, replacement and |
| 187 | ending of the path. */ |
| 188 | unsigned end = length - (p - filename); |
| 189 | unsigned repl_length = replacement != NULL ? strlen (s: replacement) : 0; |
| 190 | |
| 191 | char *buffer = (char *)xmalloc (start + end + repl_length + 1); |
| 192 | char *buffer_ptr = buffer; |
| 193 | buffer_ptr = (char *)memcpy (dest: buffer_ptr, src: filename, n: start); |
| 194 | buffer_ptr += start; |
| 195 | if (replacement != NULL) |
| 196 | buffer_ptr = (char *)memcpy (dest: buffer_ptr, src: replacement, n: repl_length); |
| 197 | buffer_ptr += repl_length; |
| 198 | buffer_ptr = (char *)memcpy (dest: buffer_ptr, src: p, n: end); |
| 199 | buffer_ptr += end; |
| 200 | *buffer_ptr = '\0'; |
| 201 | |
| 202 | free (ptr: filename); |
| 203 | filename = buffer; |
| 204 | p = buffer + start + repl_length; |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | return filename; |
| 209 | } |
| 210 | |
| 211 | static void |
| 212 | allocate_filename_struct (struct gcov_filename *gf) |
| 213 | { |
| 214 | const char *gcov_prefix; |
| 215 | size_t prefix_length; |
| 216 | int strip = 0; |
| 217 | gf->filename = NULL; |
| 218 | |
| 219 | { |
| 220 | /* Check if the level of dirs to strip off specified. */ |
| 221 | char *tmp = getenv(name: "GCOV_PREFIX_STRIP" ); |
| 222 | if (tmp) |
| 223 | { |
| 224 | strip = atoi (nptr: tmp); |
| 225 | /* Do not consider negative values. */ |
| 226 | if (strip < 0) |
| 227 | strip = 0; |
| 228 | } |
| 229 | } |
| 230 | gf->strip = strip; |
| 231 | |
| 232 | /* Get file name relocation prefix. Non-absolute values are ignored. */ |
| 233 | gcov_prefix = getenv(name: "GCOV_PREFIX" ); |
| 234 | prefix_length = gcov_prefix ? strlen (s: gcov_prefix) : 0; |
| 235 | |
| 236 | /* Remove an unnecessary trailing '/' */ |
| 237 | if (prefix_length && IS_DIR_SEPARATOR (gcov_prefix[prefix_length - 1])) |
| 238 | prefix_length--; |
| 239 | |
| 240 | /* If no prefix was specified and a prefix stip, then we assume |
| 241 | relative. */ |
| 242 | if (!prefix_length && gf->strip) |
| 243 | { |
| 244 | gcov_prefix = "." ; |
| 245 | prefix_length = 1; |
| 246 | } |
| 247 | |
| 248 | /* Allocate and initialize the filename scratch space. */ |
| 249 | if (prefix_length) |
| 250 | { |
| 251 | gf->prefix = (char *) xmalloc (prefix_length + 1); |
| 252 | char *p = (char *) memcpy (dest: gf->prefix, src: gcov_prefix, n: prefix_length); |
| 253 | *(p + prefix_length) = '\0'; |
| 254 | } |
| 255 | else |
| 256 | gf->prefix = NULL; |
| 257 | } |
| 258 | |
| 259 | /* Open a gcda file specified by GI_FILENAME. |
| 260 | Return -1 on error. Return 0 on success. */ |
| 261 | |
| 262 | static int |
| 263 | gcov_exit_open_gcda_file (struct gcov_info *gi_ptr, |
| 264 | struct gcov_filename *gf, |
| 265 | int mode) |
| 266 | { |
| 267 | int append_slash = 0; |
| 268 | const char *fname = gi_ptr->filename; |
| 269 | |
| 270 | /* Build relocated filename, stripping off leading |
| 271 | directories from the initial filename if requested. */ |
| 272 | if (gf->strip > 0) |
| 273 | { |
| 274 | const char *probe = fname; |
| 275 | int level; |
| 276 | |
| 277 | /* Remove a leading separator, without counting it. */ |
| 278 | if (IS_DIR_SEPARATOR (*probe)) |
| 279 | probe++; |
| 280 | |
| 281 | /* Skip selected directory levels. If we fall off the end, we |
| 282 | keep the final part. */ |
| 283 | for (level = gf->strip; *probe && level; probe++) |
| 284 | if (IS_DIR_SEPARATOR (*probe)) |
| 285 | { |
| 286 | fname = probe; |
| 287 | level--; |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | /* Update complete filename with stripped original. */ |
| 292 | if (gf->prefix) |
| 293 | { |
| 294 | /* Avoid to add multiple drive letters into combined path. */ |
| 295 | if (HAS_DRIVE_SPEC(fname)) |
| 296 | fname += 2; |
| 297 | |
| 298 | if (!IS_DIR_SEPARATOR (*fname)) |
| 299 | append_slash = 1; |
| 300 | } |
| 301 | |
| 302 | size_t prefix_length = gf->prefix ? strlen (s: gf->prefix) : 0; |
| 303 | gf->filename = (char *) xmalloc (prefix_length + strlen (s: fname) + 2); |
| 304 | *gf->filename = '\0'; |
| 305 | if (prefix_length) |
| 306 | strcat (dest: gf->filename, src: gf->prefix); |
| 307 | if (append_slash) |
| 308 | *gf->filename++ = '/'; |
| 309 | strcat (dest: gf->filename, src: fname); |
| 310 | |
| 311 | gf->filename = replace_filename_variables (filename: gf->filename); |
| 312 | |
| 313 | if (!gcov_open (name: gf->filename, mode)) |
| 314 | { |
| 315 | /* Open failed likely due to missed directory. |
| 316 | Create directory and retry to open file. */ |
| 317 | if (create_file_directory (filename: gf->filename)) |
| 318 | { |
| 319 | fprintf (stderr, format: "profiling:%s:Skip\n" , gf->filename); |
| 320 | return -1; |
| 321 | } |
| 322 | if (!gcov_open (name: gf->filename, mode)) |
| 323 | { |
| 324 | fprintf (stderr, format: "profiling:%s:Cannot open\n" , gf->filename); |
| 325 | return -1; |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | return 0; |
| 330 | } |
| 331 | |