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