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 (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 | |