xref: /illumos-gate/usr/src/cmd/ctfconvert/ctfconvert.c (revision a99cb9618990662acbd3bab1b4a5b05a6ca62556)
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 (c) 2019, Joyent, Inc.
14  */
15 
16 /*
17  * Create CTF from extant debugging information
18  */
19 
20 #include <stdio.h>
21 #include <unistd.h>
22 #include <stdlib.h>
23 #include <stdarg.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <fcntl.h>
27 #include <errno.h>
28 #include <libelf.h>
29 #include <libctf.h>
30 #include <string.h>
31 #include <libgen.h>
32 #include <limits.h>
33 #include <strings.h>
34 #include <sys/debug.h>
35 
36 #define	CTFCONVERT_OK		0
37 #define	CTFCONVERT_FATAL	1
38 #define	CTFCONVERT_USAGE	2
39 
40 #define	CTFCONVERT_DEFAULT_NTHREADS	4
41 
42 #define	CTFCONVERT_ALTEXEC	"CTFCONVERT_ALTEXEC"
43 
44 static char *ctfconvert_progname;
45 
46 static void
47 ctfconvert_fatal(const char *fmt, ...)
48 {
49 	va_list ap;
50 
51 	(void) fprintf(stderr, "%s: ", ctfconvert_progname);
52 	va_start(ap, fmt);
53 	(void) vfprintf(stderr, fmt, ap);
54 	va_end(ap);
55 
56 	exit(CTFCONVERT_FATAL);
57 }
58 
59 
60 static void
61 ctfconvert_usage(const char *fmt, ...)
62 {
63 	if (fmt != NULL) {
64 		va_list ap;
65 
66 		(void) fprintf(stderr, "%s: ", ctfconvert_progname);
67 		va_start(ap, fmt);
68 		(void) vfprintf(stderr, fmt, ap);
69 		va_end(ap);
70 	}
71 
72 	(void) fprintf(stderr, "Usage: %s [-ims] [-j nthrs] [-l label | "
73 	    "-L labelenv] [-o outfile] input\n"
74 	    "\n"
75 	    "\t-i  ignore files not built partially from C sources\n"
76 	    "\t-j  use nthrs threads to perform the merge\n"
77 	    "\t-k  keep around original input file on failure\n"
78 	    "\t-m  allow input to have missing debug info\n"
79 	    "\t-o  copy input to outfile and add CTF\n"
80 	    "\t-l  set output container's label to specified value\n"
81 	    "\t-L  set output container's label to value from environment\n",
82 	    ctfconvert_progname);
83 }
84 
85 /*
86  * This is a bit unfortunate. Traditionally we do type uniquification across all
87  * modules in the kernel, including ip and unix against genunix. However, when
88  * _MACHDEP is defined, then the cpu_t ends up having an additional member
89  * (cpu_m), thus changing the ability for us to uniquify against it. This in
90  * turn causes a lot of type sprawl, as there's a lot of things that end up
91  * referring to the cpu_t and it chains out from there.
92  *
93  * So, if we find that a cpu_t has been defined and it has a couple of useful
94  * sentinel members and it does *not* have the cpu_m member, then we will try
95  * and lookup or create a forward declaration to the machcpu, append it to the
96  * end, and update the file.
97  *
98  * This currently is only invoked if an undocumented option -X is passed. This
99  * value is private to illumos and it can be changed at any time inside of it,
100  * so if -X wants to be used for something, it should be. The ability to rely on
101  * -X for others is strictly not an interface in any way, shape, or form.
102  *
103  * The following struct contains most of the information that we care about and
104  * that we want to validate exists before we decide what to do.
105  */
106 
107 typedef struct ctfconvert_fixup {
108 	boolean_t	cf_cyclic;	/* Do we have a cpu_cyclic member */
109 	boolean_t	cf_mcpu;	/* We have a cpu_m member */
110 	boolean_t	cf_lastpad;	/* Is the pad member the last entry */
111 	ulong_t		cf_padoff;	/* offset of the pad */
112 } ctfconvert_fixup_t;
113 
114 /* ARGSUSED */
115 static int
116 ctfconvert_fixup_genunix_cb(const char *name, ctf_id_t tid, ulong_t off,
117     void *arg)
118 {
119 	ctfconvert_fixup_t *cfp = arg;
120 
121 	cfp->cf_lastpad = B_FALSE;
122 	if (strcmp(name, "cpu_cyclic") == 0) {
123 		cfp->cf_cyclic = B_TRUE;
124 		return (0);
125 	}
126 
127 	if (strcmp(name, "cpu_m") == 0) {
128 		cfp->cf_mcpu = B_TRUE;
129 		return (0);
130 	}
131 
132 	if (strcmp(name, "cpu_m_pad") == 0) {
133 		cfp->cf_lastpad = B_TRUE;
134 		cfp->cf_padoff = off;
135 		return (0);
136 	}
137 
138 	return (0);
139 }
140 
141 static void
142 ctfconvert_fixup_genunix(ctf_file_t *fp)
143 {
144 	ctf_id_t cpuid, mcpu;
145 	ssize_t sz;
146 	ctfconvert_fixup_t cf;
147 	int model, ptrsz;
148 
149 	cpuid = ctf_lookup_by_name(fp, "struct cpu");
150 	if (cpuid == CTF_ERR)
151 		return;
152 
153 	if (ctf_type_kind(fp, cpuid) != CTF_K_STRUCT)
154 		return;
155 
156 	if ((sz = ctf_type_size(fp, cpuid)) == CTF_ERR)
157 		return;
158 
159 	model = ctf_getmodel(fp);
160 	VERIFY(model == CTF_MODEL_ILP32 || model == CTF_MODEL_LP64);
161 	ptrsz = model == CTF_MODEL_ILP32 ? 4 : 8;
162 
163 	bzero(&cf, sizeof (ctfconvert_fixup_t));
164 	if (ctf_member_iter(fp, cpuid, ctfconvert_fixup_genunix_cb, &cf) ==
165 	    CTF_ERR)
166 		return;
167 
168 	/*
169 	 * Finally, we want to verify that the cpu_m is actually the last member
170 	 * that we have here.
171 	 */
172 	if (cf.cf_cyclic == B_FALSE || cf.cf_mcpu == B_TRUE ||
173 	    cf.cf_lastpad == B_FALSE) {
174 		return;
175 	}
176 
177 	if (cf.cf_padoff + ptrsz * NBBY != sz * NBBY) {
178 		return;
179 	}
180 
181 	/*
182 	 * Okay, we're going to do this, try to find a struct machcpu. We either
183 	 * want a forward or a struct. If we find something else, error. If we
184 	 * find nothing, add a forward and then add the member.
185 	 */
186 	mcpu = ctf_lookup_by_name(fp, "struct machcpu");
187 	if (mcpu == CTF_ERR) {
188 		mcpu = ctf_add_forward(fp, CTF_ADD_NONROOT, "machcpu",
189 		    CTF_K_STRUCT);
190 		if (mcpu == CTF_ERR) {
191 			ctfconvert_fatal("failed to add 'struct machcpu' "
192 			    "forward: %s", ctf_errmsg(ctf_errno(fp)));
193 		}
194 	} else {
195 		int kind;
196 		if ((kind = ctf_type_kind(fp, mcpu)) == CTF_ERR) {
197 			ctfconvert_fatal("failed to get the type kind for "
198 			    "the struct machcpu: %s",
199 			    ctf_errmsg(ctf_errno(fp)));
200 		}
201 
202 		if (kind != CTF_K_STRUCT && kind != CTF_K_FORWARD)
203 			ctfconvert_fatal("encountered a struct machcpu of the "
204 			    "wrong type, found type kind %d\n", kind);
205 	}
206 
207 	if (ctf_update(fp) == CTF_ERR) {
208 		ctfconvert_fatal("failed to update output file: %s\n",
209 		    ctf_errmsg(ctf_errno(fp)));
210 	}
211 
212 	if (ctf_add_member(fp, cpuid, "cpu_m", mcpu, sz * NBBY) == CTF_ERR) {
213 		ctfconvert_fatal("failed to add the m_cpu member: %s\n",
214 		    ctf_errmsg(ctf_errno(fp)));
215 	}
216 
217 	if (ctf_update(fp) == CTF_ERR) {
218 		ctfconvert_fatal("failed to update output file: %s\n",
219 		    ctf_errmsg(ctf_errno(fp)));
220 	}
221 
222 	VERIFY(ctf_type_size(fp, cpuid) == sz);
223 }
224 
225 static void
226 ctfconvert_altexec(char **argv)
227 {
228 	const char *alt;
229 	char *altexec;
230 
231 	alt = getenv(CTFCONVERT_ALTEXEC);
232 	if (alt == NULL || *alt == '\0')
233 		return;
234 
235 	altexec = strdup(alt);
236 	if (altexec == NULL)
237 		ctfconvert_fatal("failed to allocate memory for altexec\n");
238 	if (unsetenv(CTFCONVERT_ALTEXEC) != 0)
239 		ctfconvert_fatal("failed to unset %s from environment: %s\n",
240 		    CTFCONVERT_ALTEXEC, strerror(errno));
241 
242 	(void) execv(altexec, argv);
243 	ctfconvert_fatal("failed to execute alternate program %s: %s",
244 	    altexec, strerror(errno));
245 }
246 
247 int
248 main(int argc, char *argv[])
249 {
250 	int c, ifd, err;
251 	boolean_t keep = B_FALSE;
252 	uint_t flags = 0;
253 	uint_t nthreads = CTFCONVERT_DEFAULT_NTHREADS;
254 	const char *outfile = NULL;
255 	const char *label = NULL;
256 	const char *infile = NULL;
257 	char *tmpfile;
258 	ctf_file_t *ofp;
259 	long argj;
260 	char *eptr;
261 	char buf[4096];
262 	boolean_t optx = B_FALSE;
263 	boolean_t ignore_non_c = B_FALSE;
264 
265 	ctfconvert_progname = basename(argv[0]);
266 
267 	ctfconvert_altexec(argv);
268 
269 	while ((c = getopt(argc, argv, ":ij:kl:L:mo:X")) != -1) {
270 		switch (c) {
271 		case 'i':
272 			ignore_non_c = B_TRUE;
273 			break;
274 		case 'j':
275 			errno = 0;
276 			argj = strtol(optarg, &eptr, 10);
277 			if (errno != 0 || argj == LONG_MAX ||
278 			    argj > 1024 || *eptr != '\0') {
279 				ctfconvert_fatal("invalid argument for -j: "
280 				    "%s\n", optarg);
281 			}
282 			nthreads = (uint_t)argj;
283 			break;
284 		case 'k':
285 			keep = B_TRUE;
286 			break;
287 		case 'l':
288 			label = optarg;
289 			break;
290 		case 'L':
291 			label = getenv(optarg);
292 			break;
293 		case 'm':
294 			flags |= CTF_ALLOW_MISSING_DEBUG;
295 			break;
296 		case 'o':
297 			outfile = optarg;
298 			break;
299 		case 'X':
300 			optx = B_TRUE;
301 			break;
302 		case ':':
303 			ctfconvert_usage("Option -%c requires an operand\n",
304 			    optopt);
305 			return (CTFCONVERT_USAGE);
306 		case '?':
307 			ctfconvert_usage("Unknown option: -%c\n", optopt);
308 			return (CTFCONVERT_USAGE);
309 		}
310 	}
311 
312 	argv += optind;
313 	argc -= optind;
314 
315 	if (argc != 1) {
316 		ctfconvert_usage("Exactly one input file is required\n");
317 		return (CTFCONVERT_USAGE);
318 	}
319 	infile = argv[0];
320 
321 	if (elf_version(EV_CURRENT) == EV_NONE)
322 		ctfconvert_fatal("failed to initialize libelf: library is "
323 		    "out of date\n");
324 
325 	ifd = open(infile, O_RDONLY);
326 	if (ifd < 0) {
327 		ctfconvert_fatal("failed to open input file %s: %s\n", infile,
328 		    strerror(errno));
329 	}
330 
331 	/*
332 	 * By default we remove the input file on failure unless we've been
333 	 * given an output file or -k has been specified.
334 	 */
335 	if (outfile != NULL && strcmp(infile, outfile) != 0)
336 		keep = B_TRUE;
337 
338 	ofp = ctf_fdconvert(ifd, label, nthreads, flags, &err, buf,
339 	    sizeof (buf));
340 	if (ofp == NULL) {
341 		/*
342 		 * Normally, ctfconvert requires that its input file has at
343 		 * least one C-source compilation unit, and that every C-source
344 		 * compilation unit has DWARF. This is to avoid accidentally
345 		 * leaving out useful CTF.
346 		 *
347 		 * However, for the benefit of intransigent build environments,
348 		 * the -i and -m options can be used to relax this.
349 		 */
350 		if (err == ECTF_CONVNOCSRC && ignore_non_c) {
351 			exit(CTFCONVERT_OK);
352 		}
353 
354 		if (err == ECTF_CONVNODEBUG &&
355 		    (flags & CTF_ALLOW_MISSING_DEBUG) != 0) {
356 			exit(CTFCONVERT_OK);
357 		}
358 
359 		if (keep == B_FALSE)
360 			(void) unlink(infile);
361 
362 		if (err == ECTF_CONVBKERR || err == ECTF_CONVNODEBUG) {
363 			ctfconvert_fatal("%s", buf);
364 		} else {
365 			ctfconvert_fatal("CTF conversion failed: %s\n",
366 			    ctf_errmsg(err));
367 		}
368 	}
369 
370 	if (optx == B_TRUE)
371 		ctfconvert_fixup_genunix(ofp);
372 
373 	tmpfile = NULL;
374 	if (outfile == NULL || strcmp(infile, outfile) == 0) {
375 		if (asprintf(&tmpfile, "%s.ctf", infile) == -1) {
376 			if (keep == B_FALSE)
377 				(void) unlink(infile);
378 			ctfconvert_fatal("failed to allocate memory for "
379 			    "temporary file: %s\n", strerror(errno));
380 		}
381 		outfile = tmpfile;
382 	}
383 	err = ctf_elfwrite(ofp, infile, outfile, CTF_ELFWRITE_F_COMPRESS);
384 	if (err == CTF_ERR) {
385 		(void) unlink(outfile);
386 		if (keep == B_FALSE)
387 			(void) unlink(infile);
388 		ctfconvert_fatal("failed to write CTF section to output file: "
389 		    "%s", ctf_errmsg(ctf_errno(ofp)));
390 	}
391 	ctf_close(ofp);
392 
393 	if (tmpfile != NULL) {
394 		if (rename(tmpfile, infile) != 0) {
395 			int e = errno;
396 			(void) unlink(outfile);
397 			if (keep == B_FALSE)
398 				(void) unlink(infile);
399 			ctfconvert_fatal("failed to rename temporary file: "
400 			    "%s\n", strerror(e));
401 		}
402 	}
403 	free(tmpfile);
404 
405 	return (CTFCONVERT_OK);
406 }
407