summaryrefslogtreecommitdiff
path: root/usr/src/cmd/ctfconvert/ctfconvert.c
blob: bae355be22c9b26f260671ddadb592afbe83e43f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2019 Joyent, Inc.
 */

/*
 * Create CTF from extant debugging information
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <libelf.h>
#include <libctf.h>
#include <string.h>
#include <libgen.h>
#include <limits.h>
#include <strings.h>
#include <sys/debug.h>

#define	CTFCONVERT_OK		0
#define	CTFCONVERT_FATAL	1
#define	CTFCONVERT_USAGE	2

#define	CTFCONVERT_DEFAULT_BATCHSIZE	256
#define	CTFCONVERT_DEFAULT_NTHREADS	4

static char *ctfconvert_progname;

static void
ctfconvert_fatal(const char *fmt, ...)
{
	va_list ap;

	(void) fprintf(stderr, "%s: ", ctfconvert_progname);
	va_start(ap, fmt);
	(void) vfprintf(stderr, fmt, ap);
	va_end(ap);

	exit(CTFCONVERT_FATAL);
}


static void
ctfconvert_usage(const char *fmt, ...)
{
	if (fmt != NULL) {
		va_list ap;

		(void) fprintf(stderr, "%s: ", ctfconvert_progname);
		va_start(ap, fmt);
		(void) vfprintf(stderr, fmt, ap);
		va_end(ap);
	}

	(void) fprintf(stderr, "Usage: %s [-ikm] [-j nthrs] [-l label | "
	    "-L labelenv] [-b batchsize]\n"
	    "                  [-o outfile] input\n"
	    "\n"
	    "\t-b  batch process this many dies at a time (default %d)\n"
	    "\t-i  ignore files not built partially from C sources\n"
	    "\t-j  use nthrs threads to perform the merge (default %d)\n"
	    "\t-k  keep around original input file on failure\n"
	    "\t-l  set output container's label to specified value\n"
	    "\t-L  set output container's label to value from environment\n"
	    "\t-m  allow input to have missing debug info\n"
	    "\t-o  copy input to outfile and add CTF\n",
	    ctfconvert_progname,
	    CTFCONVERT_DEFAULT_BATCHSIZE,
	    CTFCONVERT_DEFAULT_NTHREADS);
}

/*
 * This is a bit unfortunate. Traditionally we do type uniquification across all
 * modules in the kernel, including ip and unix against genunix. However, when
 * _MACHDEP is defined, then the cpu_t ends up having an additional member
 * (cpu_m), thus changing the ability for us to uniquify against it. This in
 * turn causes a lot of type sprawl, as there's a lot of things that end up
 * referring to the cpu_t and it chains out from there.
 *
 * So, if we find that a cpu_t has been defined and it has a couple of useful
 * sentinel members and it does *not* have the cpu_m member, then we will try
 * and lookup or create a forward declaration to the machcpu, append it to the
 * end, and update the file.
 *
 * This currently is only invoked if an undocumented option -X is passed. This
 * value is private to illumos and it can be changed at any time inside of it,
 * so if -X wants to be used for something, it should be. The ability to rely on
 * -X for others is strictly not an interface in any way, shape, or form.
 *
 * The following struct contains most of the information that we care about and
 * that we want to validate exists before we decide what to do.
 */

typedef struct ctfconvert_fixup {
	boolean_t	cf_cyclic;	/* Do we have a cpu_cyclic member */
	boolean_t	cf_mcpu;	/* We have a cpu_m member */
	boolean_t	cf_lastpad;	/* Is the pad member the last entry */
	ulong_t		cf_padoff;	/* offset of the pad */
} ctfconvert_fixup_t;

/* ARGSUSED */
static int
ctfconvert_fixup_genunix_cb(const char *name, ctf_id_t tid, ulong_t off,
    void *arg)
{
	ctfconvert_fixup_t *cfp = arg;

	cfp->cf_lastpad = B_FALSE;
	if (strcmp(name, "cpu_cyclic") == 0) {
		cfp->cf_cyclic = B_TRUE;
		return (0);
	}

	if (strcmp(name, "cpu_m") == 0) {
		cfp->cf_mcpu = B_TRUE;
		return (0);
	}

	if (strcmp(name, "cpu_m_pad") == 0) {
		cfp->cf_lastpad = B_TRUE;
		cfp->cf_padoff = off;
		return (0);
	}

	return (0);
}

static void
ctfconvert_fixup_genunix(ctf_file_t *fp)
{
	ctf_id_t cpuid, mcpu;
	ssize_t sz;
	ctfconvert_fixup_t cf;
	int model, ptrsz;

	cpuid = ctf_lookup_by_name(fp, "struct cpu");
	if (cpuid == CTF_ERR)
		return;

	if (ctf_type_kind(fp, cpuid) != CTF_K_STRUCT)
		return;

	if ((sz = ctf_type_size(fp, cpuid)) == CTF_ERR)
		return;

	model = ctf_getmodel(fp);
	VERIFY(model == CTF_MODEL_ILP32 || model == CTF_MODEL_LP64);
	ptrsz = model == CTF_MODEL_ILP32 ? 4 : 8;

	bzero(&cf, sizeof (ctfconvert_fixup_t));
	if (ctf_member_iter(fp, cpuid, ctfconvert_fixup_genunix_cb, &cf) ==
	    CTF_ERR)
		return;

	/*
	 * Finally, we want to verify that the cpu_m is actually the last member
	 * that we have here.
	 */
	if (cf.cf_cyclic == B_FALSE || cf.cf_mcpu == B_TRUE ||
	    cf.cf_lastpad == B_FALSE) {
		return;
	}

	if (cf.cf_padoff + ptrsz * NBBY != sz * NBBY) {
		return;
	}

	/*
	 * Okay, we're going to do this, try to find a struct machcpu. We either
	 * want a forward or a struct. If we find something else, error. If we
	 * find nothing, add a forward and then add the member.
	 */
	mcpu = ctf_lookup_by_name(fp, "struct machcpu");
	if (mcpu == CTF_ERR) {
		mcpu = ctf_add_forward(fp, CTF_ADD_NONROOT, "machcpu",
		    CTF_K_STRUCT);
		if (mcpu == CTF_ERR) {
			ctfconvert_fatal("failed to add 'struct machcpu' "
			    "forward: %s", ctf_errmsg(ctf_errno(fp)));
		}
	} else {
		int kind;
		if ((kind = ctf_type_kind(fp, mcpu)) == CTF_ERR) {
			ctfconvert_fatal("failed to get the type kind for "
			    "the struct machcpu: %s",
			    ctf_errmsg(ctf_errno(fp)));
		}

		if (kind != CTF_K_STRUCT && kind != CTF_K_FORWARD)
			ctfconvert_fatal("encountered a struct machcpu of the "
			    "wrong type, found type kind %d\n", kind);
	}

	if (ctf_update(fp) == CTF_ERR) {
		ctfconvert_fatal("failed to update output file: %s\n",
		    ctf_errmsg(ctf_errno(fp)));
	}

	if (ctf_add_member(fp, cpuid, "cpu_m", mcpu, sz * NBBY) == CTF_ERR) {
		ctfconvert_fatal("failed to add the m_cpu member: %s\n",
		    ctf_errmsg(ctf_errno(fp)));
	}

	if (ctf_update(fp) == CTF_ERR) {
		ctfconvert_fatal("failed to update output file: %s\n",
		    ctf_errmsg(ctf_errno(fp)));
	}

	VERIFY(ctf_type_size(fp, cpuid) == sz);
}

int
main(int argc, char *argv[])
{
	int c, ifd, err;
	boolean_t keep = B_FALSE;
	uint_t flags = 0;
	uint_t bsize = CTFCONVERT_DEFAULT_BATCHSIZE;
	uint_t nthreads = CTFCONVERT_DEFAULT_NTHREADS;
	const char *outfile = NULL;
	const char *label = NULL;
	const char *infile = NULL;
	char *tmpfile;
	ctf_file_t *ofp;
	char buf[4096];
	boolean_t optx = B_FALSE;
	boolean_t ignore_non_c = B_FALSE;

	ctfconvert_progname = basename(argv[0]);

	while ((c = getopt(argc, argv, ":b:ij:kl:L:mo:X")) != -1) {
		switch (c) {
		case 'b': {
			long argno;
			const char *errstr;

			argno = strtonum(optarg, 1, UINT_MAX, &errstr);
			if (errstr != NULL) {
				ctfconvert_fatal("invalid argument for -b: "
				    "%s - %s\n", optarg, errstr);
			}
			bsize = (uint_t)argno;
			break;
		}
		case 'i':
			ignore_non_c = B_TRUE;
			break;
		case 'j': {
			long argno;
			const char *errstr;

			argno = strtonum(optarg, 1, 1024, &errstr);
			if (errstr != NULL) {
				ctfconvert_fatal("invalid argument for -j: "
				    "%s - %s\n", optarg, errstr);
			}
			nthreads = (uint_t)argno;
			break;
		}
		case 'k':
			keep = B_TRUE;
			break;
		case 'l':
			label = optarg;
			break;
		case 'L':
			label = getenv(optarg);
			break;
		case 'm':
			flags |= CTF_ALLOW_MISSING_DEBUG;
			break;
		case 'o':
			outfile = optarg;
			break;
		case 'X':
			optx = B_TRUE;
			break;
		case ':':
			ctfconvert_usage("Option -%c requires an operand\n",
			    optopt);
			return (CTFCONVERT_USAGE);
		case '?':
			ctfconvert_usage("Unknown option: -%c\n", optopt);
			return (CTFCONVERT_USAGE);
		}
	}

	argv += optind;
	argc -= optind;

	if (argc != 1) {
		ctfconvert_usage("Exactly one input file is required\n");
		return (CTFCONVERT_USAGE);
	}
	infile = argv[0];

	if (elf_version(EV_CURRENT) == EV_NONE)
		ctfconvert_fatal("failed to initialize libelf: library is "
		    "out of date\n");

	ifd = open(infile, O_RDONLY);
	if (ifd < 0) {
		ctfconvert_fatal("failed to open input file %s: %s\n", infile,
		    strerror(errno));
	}

	/*
	 * By default we remove the input file on failure unless we've been
	 * given an output file or -k has been specified.
	 */
	if (outfile != NULL && strcmp(infile, outfile) != 0)
		keep = B_TRUE;

	ofp = ctf_fdconvert(ifd, label, bsize, nthreads, flags, &err, buf,
	    sizeof (buf));
	if (ofp == NULL) {
		/*
		 * Normally, ctfconvert requires that its input file has at
		 * least one C-source compilation unit, and that every C-source
		 * compilation unit has DWARF. This is to avoid accidentally
		 * leaving out useful CTF.
		 *
		 * However, for the benefit of intransigent build environments,
		 * the -i and -m options can be used to relax this.
		 */
		if (err == ECTF_CONVNOCSRC && ignore_non_c) {
			exit(CTFCONVERT_OK);
		}

		if (err == ECTF_CONVNODEBUG &&
		    (flags & CTF_ALLOW_MISSING_DEBUG) != 0) {
			exit(CTFCONVERT_OK);
		}

		if (keep == B_FALSE)
			(void) unlink(infile);

		if (err == ECTF_CONVBKERR || err == ECTF_CONVNODEBUG) {
			ctfconvert_fatal("%s\n", buf);
		} else {
			ctfconvert_fatal("CTF conversion failed: %s\n",
			    ctf_errmsg(err));
		}
	}

	if (optx == B_TRUE)
		ctfconvert_fixup_genunix(ofp);

	tmpfile = NULL;
	if (outfile == NULL || strcmp(infile, outfile) == 0) {
		if (asprintf(&tmpfile, "%s.ctf", infile) == -1) {
			if (keep == B_FALSE)
				(void) unlink(infile);
			ctfconvert_fatal("failed to allocate memory for "
			    "temporary file: %s\n", strerror(errno));
		}
		outfile = tmpfile;
	}
	err = ctf_elfwrite(ofp, infile, outfile, CTF_ELFWRITE_F_COMPRESS);
	if (err == CTF_ERR) {
		(void) unlink(outfile);
		if (keep == B_FALSE)
			(void) unlink(infile);
		ctfconvert_fatal("failed to write CTF section to output file: "
		    "%s", ctf_errmsg(ctf_errno(ofp)));
	}
	ctf_close(ofp);

	if (tmpfile != NULL) {
		if (rename(tmpfile, infile) != 0) {
			int e = errno;
			(void) unlink(outfile);
			if (keep == B_FALSE)
				(void) unlink(infile);
			ctfconvert_fatal("failed to rename temporary file: "
			    "%s\n", strerror(e));
		}
	}
	free(tmpfile);

	return (CTFCONVERT_OK);
}