xref: /illumos-gate/usr/src/cmd/ctfconvert/ctfconvert.c (revision 6fa29843813e354e472ca1ef80590ab80e2362b7)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2019 Joyent, Inc.
14  * Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
15  */
16 
17 /*
18  * Create CTF from extant debugging information
19  */
20 
21 #include <stdio.h>
22 #include <unistd.h>
23 #include <stdlib.h>
24 #include <stdarg.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <fcntl.h>
28 #include <errno.h>
29 #include <libelf.h>
30 #include <libctf.h>
31 #include <string.h>
32 #include <libgen.h>
33 #include <limits.h>
34 #include <strings.h>
35 #include <sys/debug.h>
36 
37 #define	CTFCONVERT_OK		0
38 #define	CTFCONVERT_FATAL	1
39 #define	CTFCONVERT_USAGE	2
40 
41 static char *ctfconvert_progname;
42 
43 static void
44 ctfconvert_fatal(const char *fmt, ...)
45 {
46 	va_list ap;
47 
48 	(void) fprintf(stderr, "%s: ", ctfconvert_progname);
49 	va_start(ap, fmt);
50 	(void) vfprintf(stderr, fmt, ap);
51 	va_end(ap);
52 
53 	exit(CTFCONVERT_FATAL);
54 }
55 
56 static void
57 ctfconvert_warning(void *arg, const char *fmt, ...)
58 {
59 	va_list ap;
60 	char *buf;
61 
62 	va_start(ap, fmt);
63 	if (vasprintf(&buf, fmt, ap) != -1) {
64 		(void) fprintf(stderr, "%s: WARNING: %s", ctfconvert_progname,
65 		    buf);
66 		free(buf);
67 	}
68 	va_end(ap);
69 }
70 
71 static void
72 ctfconvert_usage(const char *fmt, ...)
73 {
74 	if (fmt != NULL) {
75 		va_list ap;
76 
77 		(void) fprintf(stderr, "%s: ", ctfconvert_progname);
78 		va_start(ap, fmt);
79 		(void) vfprintf(stderr, fmt, ap);
80 		va_end(ap);
81 	}
82 
83 	(void) fprintf(stderr, "Usage: %s [-fikms] [-j nthrs] [-l label | "
84 	    "-L labelenv] [-b batchsize]\n"
85 	    "                  [-o outfile] [-M ignorefile] input\n"
86 	    "\n"
87 	    "\t-b  batch process this many dies at a time (default %d)\n"
88 	    "\t-f  always attempt to convert files\n"
89 	    "\t-i  ignore files not built partially from C sources\n"
90 	    "\t-j  use nthrs threads to perform the merge (default %d)\n"
91 	    "\t-k  keep around original input file on failure\n"
92 	    "\t-l  set output container's label to specified value\n"
93 	    "\t-L  set output container's label to value from environment\n"
94 	    "\t-m  allow input to have missing debug info\n"
95 	    "\t-M  allow files listed in ignorefile to have missing debug\n"
96 	    "\t-o  copy input to outfile and add CTF\n"
97 	    "\t-s  allow truncation of data that cannot be fully converted\n",
98 	    ctfconvert_progname,
99 	    CTF_CONVERT_DEFAULT_BATCHSIZE,
100 	    CTF_CONVERT_DEFAULT_NTHREADS);
101 }
102 
103 /*
104  * This is a bit unfortunate. Traditionally we do type uniquification across all
105  * modules in the kernel, including ip and unix against genunix. However, when
106  * _MACHDEP is defined, then the cpu_t ends up having an additional member
107  * (cpu_m), thus changing the ability for us to uniquify against it. This in
108  * turn causes a lot of type sprawl, as there's a lot of things that end up
109  * referring to the cpu_t and it chains out from there.
110  *
111  * So, if we find that a cpu_t has been defined and it has a couple of useful
112  * sentinel members and it does *not* have the cpu_m member, then we will try
113  * and lookup or create a forward declaration to the machcpu, append it to the
114  * end, and update the file.
115  *
116  * This currently is only invoked if an undocumented option -X is passed. This
117  * value is private to illumos and it can be changed at any time inside of it,
118  * so if -X wants to be used for something, it should be. The ability to rely on
119  * -X for others is strictly not an interface in any way, shape, or form.
120  *
121  * The following struct contains most of the information that we care about and
122  * that we want to validate exists before we decide what to do.
123  */
124 
125 typedef struct ctfconvert_fixup {
126 	boolean_t	cf_cyclic;	/* Do we have a cpu_cyclic member */
127 	boolean_t	cf_mcpu;	/* We have a cpu_m member */
128 	boolean_t	cf_lastpad;	/* Is the pad member the last entry */
129 	ulong_t		cf_padoff;	/* offset of the pad */
130 } ctfconvert_fixup_t;
131 
132 /* ARGSUSED */
133 static int
134 ctfconvert_fixup_genunix_cb(const char *name, ctf_id_t tid, ulong_t off,
135     void *arg)
136 {
137 	ctfconvert_fixup_t *cfp = arg;
138 
139 	cfp->cf_lastpad = B_FALSE;
140 	if (strcmp(name, "cpu_cyclic") == 0) {
141 		cfp->cf_cyclic = B_TRUE;
142 		return (0);
143 	}
144 
145 	if (strcmp(name, "cpu_m") == 0) {
146 		cfp->cf_mcpu = B_TRUE;
147 		return (0);
148 	}
149 
150 	if (strcmp(name, "cpu_m_pad") == 0) {
151 		cfp->cf_lastpad = B_TRUE;
152 		cfp->cf_padoff = off;
153 		return (0);
154 	}
155 
156 	return (0);
157 }
158 
159 static void
160 ctfconvert_fixup_genunix(ctf_file_t *fp)
161 {
162 	ctf_id_t cpuid, mcpu;
163 	ssize_t sz;
164 	ctfconvert_fixup_t cf;
165 	int model, ptrsz;
166 
167 	cpuid = ctf_lookup_by_name(fp, "struct cpu");
168 	if (cpuid == CTF_ERR)
169 		return;
170 
171 	if (ctf_type_kind(fp, cpuid) != CTF_K_STRUCT)
172 		return;
173 
174 	if ((sz = ctf_type_size(fp, cpuid)) == CTF_ERR)
175 		return;
176 
177 	model = ctf_getmodel(fp);
178 	VERIFY(model == CTF_MODEL_ILP32 || model == CTF_MODEL_LP64);
179 	ptrsz = model == CTF_MODEL_ILP32 ? 4 : 8;
180 
181 	bzero(&cf, sizeof (ctfconvert_fixup_t));
182 	if (ctf_member_iter(fp, cpuid, ctfconvert_fixup_genunix_cb, &cf) ==
183 	    CTF_ERR)
184 		return;
185 
186 	/*
187 	 * Finally, we want to verify that the cpu_m is actually the last member
188 	 * that we have here.
189 	 */
190 	if (cf.cf_cyclic == B_FALSE || cf.cf_mcpu == B_TRUE ||
191 	    cf.cf_lastpad == B_FALSE) {
192 		return;
193 	}
194 
195 	if (cf.cf_padoff + ptrsz * NBBY != sz * NBBY) {
196 		return;
197 	}
198 
199 	/*
200 	 * Okay, we're going to do this, try to find a struct machcpu. We either
201 	 * want a forward or a struct. If we find something else, error. If we
202 	 * find nothing, add a forward and then add the member.
203 	 */
204 	mcpu = ctf_lookup_by_name(fp, "struct machcpu");
205 	if (mcpu == CTF_ERR) {
206 		mcpu = ctf_add_forward(fp, CTF_ADD_NONROOT, "machcpu",
207 		    CTF_K_STRUCT);
208 		if (mcpu == CTF_ERR) {
209 			ctfconvert_fatal("failed to add 'struct machcpu' "
210 			    "forward: %s\n", ctf_errmsg(ctf_errno(fp)));
211 		}
212 	} else {
213 		int kind;
214 		if ((kind = ctf_type_kind(fp, mcpu)) == CTF_ERR) {
215 			ctfconvert_fatal("failed to get the type kind for "
216 			    "the struct machcpu: %s\n",
217 			    ctf_errmsg(ctf_errno(fp)));
218 		}
219 
220 		if (kind != CTF_K_STRUCT && kind != CTF_K_FORWARD)
221 			ctfconvert_fatal("encountered a struct machcpu of the "
222 			    "wrong type, found type kind %d\n", kind);
223 	}
224 
225 	if (ctf_update(fp) == CTF_ERR) {
226 		ctfconvert_fatal("failed to update output file: %s\n",
227 		    ctf_errmsg(ctf_errno(fp)));
228 	}
229 
230 	if (ctf_add_member(fp, cpuid, "cpu_m", mcpu, sz * NBBY) == CTF_ERR) {
231 		ctfconvert_fatal("failed to add the m_cpu member: %s\n",
232 		    ctf_errmsg(ctf_errno(fp)));
233 	}
234 
235 	if (ctf_update(fp) == CTF_ERR) {
236 		ctfconvert_fatal("failed to update output file: %s\n",
237 		    ctf_errmsg(ctf_errno(fp)));
238 	}
239 
240 	VERIFY(ctf_type_size(fp, cpuid) == sz);
241 }
242 
243 int
244 main(int argc, char *argv[])
245 {
246 	int c, ifd, err;
247 	boolean_t keep = B_FALSE;
248 	ctf_convert_flag_t flags = 0;
249 	uint_t bsize = CTF_CONVERT_DEFAULT_BATCHSIZE;
250 	uint_t nthreads = CTF_CONVERT_DEFAULT_NTHREADS;
251 	const char *outfile = NULL;
252 	const char *label = NULL;
253 	const char *infile = NULL;
254 	const char *ignorefile = NULL;
255 	char *tmpfile;
256 	ctf_file_t *ofp;
257 	char buf[4096] = "";
258 	boolean_t optx = B_FALSE;
259 	boolean_t ignore_non_c = B_FALSE;
260 	ctf_convert_t *cch;
261 
262 	ctfconvert_progname = basename(argv[0]);
263 
264 	while ((c = getopt(argc, argv, ":b:fij:kl:L:mM:o:sX")) != -1) {
265 		switch (c) {
266 		case 'b': {
267 			long argno;
268 			const char *errstr;
269 
270 			argno = strtonum(optarg, 1, UINT_MAX, &errstr);
271 			if (errstr != NULL) {
272 				ctfconvert_fatal("invalid argument for -b: "
273 				    "%s - %s\n", optarg, errstr);
274 			}
275 			bsize = (uint_t)argno;
276 			break;
277 		}
278 		case 'f':
279 			flags |= CTF_FORCE_CONVERSION;
280 			break;
281 		case 'i':
282 			ignore_non_c = B_TRUE;
283 			break;
284 		case 'j': {
285 			long argno;
286 			const char *errstr;
287 
288 			argno = strtonum(optarg, 1, 1024, &errstr);
289 			if (errstr != NULL) {
290 				ctfconvert_fatal("invalid argument for -j: "
291 				    "%s - %s\n", optarg, errstr);
292 			}
293 			nthreads = (uint_t)argno;
294 			break;
295 		}
296 		case 'k':
297 			keep = B_TRUE;
298 			break;
299 		case 'l':
300 			label = optarg;
301 			break;
302 		case 'L':
303 			label = getenv(optarg);
304 			break;
305 		case 'm':
306 			flags |= CTF_ALLOW_MISSING_DEBUG;
307 			break;
308 		case 'M':
309 			ignorefile = optarg;
310 			break;
311 		case 'o':
312 			outfile = optarg;
313 			break;
314 		case 's':
315 			flags |= CTF_ALLOW_TRUNCATION;
316 			break;
317 		case 'X':
318 			optx = B_TRUE;
319 			break;
320 		case ':':
321 			ctfconvert_usage("Option -%c requires an operand\n",
322 			    optopt);
323 			return (CTFCONVERT_USAGE);
324 		case '?':
325 			ctfconvert_usage("Unknown option: -%c\n", optopt);
326 			return (CTFCONVERT_USAGE);
327 		}
328 	}
329 
330 	argv += optind;
331 	argc -= optind;
332 
333 	if (argc != 1) {
334 		ctfconvert_usage("Exactly one input file is required\n");
335 		return (CTFCONVERT_USAGE);
336 	}
337 	infile = argv[0];
338 
339 	if (elf_version(EV_CURRENT) == EV_NONE)
340 		ctfconvert_fatal("failed to initialize libelf: library is "
341 		    "out of date\n");
342 
343 	ifd = open(infile, O_RDONLY);
344 	if (ifd < 0) {
345 		ctfconvert_fatal("failed to open input file %s: %s\n", infile,
346 		    strerror(errno));
347 	}
348 
349 	/*
350 	 * By default we remove the input file on failure unless we've been
351 	 * given an output file or -k has been specified.
352 	 */
353 	if (outfile != NULL && strcmp(infile, outfile) != 0)
354 		keep = B_TRUE;
355 
356 	cch = ctf_convert_init(&err);
357 	if (cch == NULL) {
358 		ctfconvert_fatal(
359 		    "failed to create libctf conversion handle: %s\n",
360 		    strerror(err));
361 	}
362 	if ((err = ctf_convert_set_nthreads(cch, nthreads)) != 0)
363 		ctfconvert_fatal("Could not set number of threads: %s\n",
364 		    strerror(err));
365 	if ((err = ctf_convert_set_batchsize(cch, bsize)) != 0)
366 		ctfconvert_fatal("Could not set batch size: %s\n",
367 		    strerror(err));
368 	if ((err = ctf_convert_set_flags(cch, flags)) != 0)
369 		ctfconvert_fatal("Could not set conversion flags: %s\n",
370 		    strerror(err));
371 	if (label != NULL && (err = ctf_convert_set_label(cch, label)) != 0)
372 		ctfconvert_fatal("Could not set label: %s\n",
373 		    strerror(err));
374 	if ((err = ctf_convert_set_warncb(cch, ctfconvert_warning, NULL)) != 0)
375 		ctfconvert_fatal("Could not set warning callback: %s\n",
376 		    strerror(err));
377 
378 	if (ignorefile != NULL) {
379 		char *buf = NULL;
380 		ssize_t cnt;
381 		size_t len = 0;
382 		FILE *fp;
383 
384 		if ((fp = fopen(ignorefile, "r")) == NULL) {
385 			ctfconvert_fatal("Could not open ignorefile '%s': %s\n",
386 			    ignorefile, strerror(errno));
387 		}
388 
389 		while ((cnt = getline(&buf, &len, fp)) != -1) {
390 			char *p = buf;
391 
392 			if (cnt == 0 || *p == '#')
393 				continue;
394 
395 			(void) strsep(&p, "\n");
396 			if ((err = ctf_convert_add_ignore(cch, buf)) != 0) {
397 				ctfconvert_fatal(
398 				    "Failed to add '%s' to ignore list: %s\n",
399 				    buf, strerror(err));
400 			}
401 		}
402 		free(buf);
403 		if (cnt == -1 && ferror(fp) != 0) {
404 			ctfconvert_fatal(
405 			    "Error reading from ignorefile '%s': %s\n",
406 			    ignorefile, strerror(errno));
407 		}
408 
409 		(void) fclose(fp);
410 	}
411 
412 	ofp = ctf_fdconvert(cch, ifd, &err, buf, sizeof (buf));
413 
414 	ctf_convert_fini(cch);
415 
416 	if (ofp == NULL) {
417 		/*
418 		 * Normally, ctfconvert requires that its input file has at
419 		 * least one C-source compilation unit, and that every C-source
420 		 * compilation unit has DWARF. This is to avoid accidentally
421 		 * leaving out useful CTF.
422 		 *
423 		 * However, for the benefit of intransigent build environments,
424 		 * the -i and -m options can be used to relax this.
425 		 */
426 		if (err == ECTF_CONVNOCSRC && ignore_non_c) {
427 			exit(CTFCONVERT_OK);
428 		}
429 
430 		if (err == ECTF_CONVNODEBUG &&
431 		    (flags & CTF_ALLOW_MISSING_DEBUG) != 0) {
432 			exit(CTFCONVERT_OK);
433 		}
434 
435 		if (keep == B_FALSE)
436 			(void) unlink(infile);
437 
438 		/*
439 		 * Note, we expect libctf to include a newline it all of its
440 		 * error messages right now (though it perhaps shouldn't).
441 		 */
442 		switch (err) {
443 		case ECTF_CONVBKERR:
444 			ctfconvert_fatal("CTF conversion failed: %s", buf);
445 			break;
446 		case ECTF_CONVNODEBUG:
447 			ctfconvert_fatal("CTF conversion failed due to "
448 			    "missing debug data; use -m to override\n");
449 			break;
450 		default:
451 			if (*buf != '\0') {
452 				(void) fprintf(stderr, "%s: %s",
453 				    ctfconvert_progname, buf);
454 			}
455 			ctfconvert_fatal("CTF conversion failed: %s\n",
456 			    ctf_errmsg(err));
457 		}
458 	}
459 
460 	if (optx == B_TRUE)
461 		ctfconvert_fixup_genunix(ofp);
462 
463 	tmpfile = NULL;
464 	if (outfile == NULL || strcmp(infile, outfile) == 0) {
465 		if (asprintf(&tmpfile, "%s.ctf", infile) == -1) {
466 			if (keep == B_FALSE)
467 				(void) unlink(infile);
468 			ctfconvert_fatal("failed to allocate memory for "
469 			    "temporary file: %s\n", strerror(errno));
470 		}
471 		outfile = tmpfile;
472 	}
473 	err = ctf_elfwrite(ofp, infile, outfile, CTF_ELFWRITE_F_COMPRESS);
474 	if (err == CTF_ERR) {
475 		(void) unlink(outfile);
476 		if (keep == B_FALSE)
477 			(void) unlink(infile);
478 		ctfconvert_fatal("failed to write CTF section to output file: "
479 		    "%s\n", ctf_errmsg(ctf_errno(ofp)));
480 	}
481 	ctf_close(ofp);
482 
483 	if (tmpfile != NULL) {
484 		if (rename(tmpfile, infile) != 0) {
485 			int e = errno;
486 			(void) unlink(outfile);
487 			if (keep == B_FALSE)
488 				(void) unlink(infile);
489 			ctfconvert_fatal("failed to rename temporary file: "
490 			    "%s\n", strerror(e));
491 		}
492 	}
493 	free(tmpfile);
494 
495 	return (CTFCONVERT_OK);
496 }
497