Home | History | Annotate | Line # | Download | only in libgcc
      1 /* Routines required for instrumenting a program.  */
      2 /* Compile this one with gcc.  */
      3 /* Copyright (C) 1989-2024 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 (f, fmt, argp);
     67   va_end (argp);
     68 
     69   if (getenv ("GCOV_EXIT_AT_ERROR"))
     70     {
     71       fprintf (f, "profiling:exiting after an error\n");
     72       exit (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 (filename, F_OK) == -1
    117 #ifdef TARGET_POSIX_IO
    118 	    && mkdir (filename, 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 ("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 (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 (buffer, "%d", getpid ());
    162 	      replacement = buffer;
    163 	      p++;
    164 	      break;
    165 	    case 'q':
    166 	      if (*(p + 1) == '{')
    167 		{
    168 		  p += 2;
    169 		  char *e = strchr (p, '}');
    170 		  if (e)
    171 		    {
    172 		      *e = '\0';
    173 		      replacement = getenv (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 (replacement) : 0;
    190 
    191 	  char *buffer = (char *)xmalloc (start + end + repl_length + 1);
    192 	  char *buffer_ptr = buffer;
    193 	  buffer_ptr = (char *)memcpy (buffer_ptr, filename, start);
    194 	  buffer_ptr += start;
    195 	  if (replacement != NULL)
    196 	    buffer_ptr = (char *)memcpy (buffer_ptr, replacement, repl_length);
    197 	  buffer_ptr += repl_length;
    198 	  buffer_ptr = (char *)memcpy (buffer_ptr, p, end);
    199 	  buffer_ptr += end;
    200 	  *buffer_ptr = '\0';
    201 
    202 	  free (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("GCOV_PREFIX_STRIP");
    222     if (tmp)
    223       {
    224         strip = atoi (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("GCOV_PREFIX");
    234   prefix_length = gcov_prefix ? strlen (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 (gf->prefix, gcov_prefix, 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 (gf->prefix) : 0;
    303   gf->filename = (char *) xmalloc (prefix_length + strlen (fname) + 2);
    304   *gf->filename = '\0';
    305   if (prefix_length)
    306     strcat (gf->filename, gf->prefix);
    307   if (append_slash)
    308     *gf->filename++ = '/';
    309   strcat (gf->filename, fname);
    310 
    311   gf->filename = replace_filename_variables (gf->filename);
    312 
    313   if (!gcov_open (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 (gf->filename))
    318         {
    319           fprintf (stderr, "profiling:%s:Skip\n", gf->filename);
    320           return -1;
    321         }
    322       if (!gcov_open (gf->filename, mode))
    323         {
    324           fprintf (stderr, "profiling:%s:Cannot open\n", gf->filename);
    325           return -1;
    326         }
    327     }
    328 
    329   return 0;
    330 }
    331