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