xref: /illumos-gate/usr/src/cmd/ctfmerge/ctfmerge.c (revision 44bf619d328827ce5eca6833fcd5c69f1592e578)
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  * merge CTF containers
18  */
19 
20 #include <stdio.h>
21 #include <libctf.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 #include <fcntl.h>
25 #include <errno.h>
26 #include <strings.h>
27 #include <assert.h>
28 #include <unistd.h>
29 #include <sys/fcntl.h>
30 #include <stdlib.h>
31 #include <libelf.h>
32 #include <gelf.h>
33 #include <sys/mman.h>
34 #include <libgen.h>
35 #include <stdarg.h>
36 #include <limits.h>
37 
38 static char *g_progname;
39 static char *g_unique;
40 static char *g_outfile;
41 static uint_t g_nctf;
42 
43 #define	CTFMERGE_OK	0
44 #define	CTFMERGE_FATAL	1
45 #define	CTFMERGE_USAGE	2
46 
47 #define	CTFMERGE_DEFAULT_NTHREADS	8
48 #define	CTFMERGE_ALTEXEC	"CTFMERGE_ALTEXEC"
49 
50 static void __attribute__((__noreturn__))
51 ctfmerge_fatal(const char *fmt, ...)
52 {
53 	va_list ap;
54 
55 	(void) fprintf(stderr, "%s: ", g_progname);
56 	va_start(ap, fmt);
57 	(void) vfprintf(stderr, fmt, ap);
58 	va_end(ap);
59 
60 	if (g_outfile != NULL)
61 		(void) unlink(g_outfile);
62 
63 	exit(CTFMERGE_FATAL);
64 }
65 
66 /*
67  * We failed to find CTF for this file, check if it's OK. If we're not derived
68  * from C, or we have the -m option, we let missing CTF pass.
69  */
70 static void
71 ctfmerge_check_for_c(const char *name, Elf *elf, uint_t flags)
72 {
73 	char errmsg[1024];
74 
75 	if (flags & CTF_ALLOW_MISSING_DEBUG)
76 		return;
77 
78 	switch (ctf_has_c_source(elf, errmsg, sizeof (errmsg))) {
79 	case CHR_ERROR:
80 		ctfmerge_fatal("failed to open %s: %s\n", name, errmsg);
81 		break;
82 
83 	case CHR_NO_C_SOURCE:
84 		return;
85 
86 	default:
87 		ctfmerge_fatal("failed to open %s: %s\n", name,
88 		    ctf_errmsg(ECTF_NOCTFDATA));
89 		break;
90 	}
91 }
92 
93 /*
94  * Go through and construct enough information for this Elf Object to try and do
95  * a ctf_bufopen().
96  */
97 static int
98 ctfmerge_elfopen(const char *name, Elf *elf, ctf_merge_t *cmh, uint_t flags)
99 {
100 	GElf_Ehdr ehdr;
101 	GElf_Shdr shdr;
102 	Elf_Scn *scn;
103 	Elf_Data *ctf_data, *str_data, *sym_data;
104 	ctf_sect_t ctfsect, symsect, strsect;
105 	ctf_file_t *fp;
106 	int err;
107 
108 	if (gelf_getehdr(elf, &ehdr) == NULL)
109 		ctfmerge_fatal("failed to get ELF header for %s: %s\n",
110 		    name, elf_errmsg(elf_errno()));
111 
112 	bzero(&ctfsect, sizeof (ctf_sect_t));
113 	bzero(&symsect, sizeof (ctf_sect_t));
114 	bzero(&strsect, sizeof (ctf_sect_t));
115 
116 	scn = NULL;
117 	while ((scn = elf_nextscn(elf, scn)) != NULL) {
118 		const char *sname;
119 
120 		if (gelf_getshdr(scn, &shdr) == NULL)
121 			ctfmerge_fatal("failed to get section header for "
122 			    "file %s: %s\n", name, elf_errmsg(elf_errno()));
123 
124 		sname = elf_strptr(elf, ehdr.e_shstrndx, shdr.sh_name);
125 		if (shdr.sh_type == SHT_PROGBITS &&
126 		    strcmp(sname, ".SUNW_ctf") == 0) {
127 			ctfsect.cts_name = sname;
128 			ctfsect.cts_type = shdr.sh_type;
129 			ctfsect.cts_flags = shdr.sh_flags;
130 			ctfsect.cts_size = shdr.sh_size;
131 			ctfsect.cts_entsize = shdr.sh_entsize;
132 			ctfsect.cts_offset = (off64_t)shdr.sh_offset;
133 
134 			ctf_data = elf_getdata(scn, NULL);
135 			if (ctf_data == NULL)
136 				ctfmerge_fatal("failed to get ELF CTF "
137 				    "data section for %s: %s\n", name,
138 				    elf_errmsg(elf_errno()));
139 			ctfsect.cts_data = ctf_data->d_buf;
140 		} else if (shdr.sh_type == SHT_SYMTAB) {
141 			Elf_Scn *strscn;
142 			GElf_Shdr strhdr;
143 
144 			symsect.cts_name = sname;
145 			symsect.cts_type = shdr.sh_type;
146 			symsect.cts_flags = shdr.sh_flags;
147 			symsect.cts_size = shdr.sh_size;
148 			symsect.cts_entsize = shdr.sh_entsize;
149 			symsect.cts_offset = (off64_t)shdr.sh_offset;
150 
151 			if ((strscn = elf_getscn(elf, shdr.sh_link)) == NULL ||
152 			    gelf_getshdr(strscn, &strhdr) == NULL)
153 				ctfmerge_fatal("failed to get "
154 				    "string table for file %s: %s\n", name,
155 				    elf_errmsg(elf_errno()));
156 
157 			strsect.cts_name = elf_strptr(elf, ehdr.e_shstrndx,
158 			    strhdr.sh_name);
159 			strsect.cts_type = strhdr.sh_type;
160 			strsect.cts_flags = strhdr.sh_flags;
161 			strsect.cts_size = strhdr.sh_size;
162 			strsect.cts_entsize = strhdr.sh_entsize;
163 			strsect.cts_offset = (off64_t)strhdr.sh_offset;
164 
165 			sym_data = elf_getdata(scn, NULL);
166 			if (sym_data == NULL)
167 				ctfmerge_fatal("failed to get ELF CTF "
168 				    "data section for %s: %s\n", name,
169 				    elf_errmsg(elf_errno()));
170 			symsect.cts_data = sym_data->d_buf;
171 
172 			str_data = elf_getdata(strscn, NULL);
173 			if (str_data == NULL)
174 				ctfmerge_fatal("failed to get ELF CTF "
175 				    "data section for %s: %s\n", name,
176 				    elf_errmsg(elf_errno()));
177 			strsect.cts_data = str_data->d_buf;
178 		}
179 	}
180 
181 	if (ctfsect.cts_type == SHT_NULL) {
182 		ctfmerge_check_for_c(name, elf, flags);
183 		return (ENOENT);
184 	}
185 
186 	if (symsect.cts_type != SHT_NULL && strsect.cts_type != SHT_NULL) {
187 		fp = ctf_bufopen(&ctfsect, &symsect, &strsect, &err);
188 	} else {
189 		fp = ctf_bufopen(&ctfsect, NULL, NULL, &err);
190 	}
191 
192 	if (fp == NULL) {
193 		ctfmerge_fatal("failed to open file %s: %s\n",
194 		    name, ctf_errmsg(err));
195 	}
196 
197 	if ((err = ctf_merge_add(cmh, fp)) != 0) {
198 		ctfmerge_fatal("failed to add input %s: %s\n",
199 		    name, ctf_errmsg(err));
200 	}
201 
202 	g_nctf++;
203 	return (0);
204 }
205 
206 static void
207 ctfmerge_read_archive(const char *name, int fd, Elf *elf,
208     ctf_merge_t *cmh, uint_t flags)
209 {
210 	Elf_Cmd cmd = ELF_C_READ;
211 	int cursec = 1;
212 	Elf *aelf;
213 
214 	while ((aelf = elf_begin(fd, cmd, elf)) != NULL) {
215 		char *nname = NULL;
216 		Elf_Arhdr *arhdr;
217 
218 		if ((arhdr = elf_getarhdr(aelf)) == NULL)
219 			ctfmerge_fatal("failed to get archive header %d for "
220 			    "%s: %s\n", cursec, name, elf_errmsg(elf_errno()));
221 
222 		cmd = elf_next(aelf);
223 
224 		if (*(arhdr->ar_name) == '/')
225 			goto next;
226 
227 		if (asprintf(&nname, "%s.%s.%d", name, arhdr->ar_name,
228 		    cursec) < 0)
229 			ctfmerge_fatal("failed to allocate memory for archive "
230 			    "%d of file %s\n", cursec, name);
231 
232 		switch (elf_kind(aelf)) {
233 		case ELF_K_AR:
234 			ctfmerge_read_archive(nname, fd, aelf, cmh, flags);
235 			break;
236 		case ELF_K_ELF:
237 			/* ctfmerge_elfopen() takes ownership of aelf. */
238 			if (ctfmerge_elfopen(nname, aelf, cmh, flags) == 0)
239 				aelf = NULL;
240 			break;
241 		default:
242 			ctfmerge_fatal("unknown elf kind (%d) in archive %d "
243 			    "for %s\n", elf_kind(aelf), cursec, name);
244 			break;
245 		}
246 
247 next:
248 		(void) elf_end(aelf);
249 		free(nname);
250 		cursec++;
251 	}
252 }
253 
254 static void
255 ctfmerge_file_add(ctf_merge_t *cmh, const char *file, uint_t flags)
256 {
257 	Elf *e;
258 	int fd;
259 
260 	if ((fd = open(file, O_RDONLY)) < 0) {
261 		ctfmerge_fatal("failed to open file %s: %s\n",
262 		    file, strerror(errno));
263 	}
264 
265 	if ((e = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
266 		(void) close(fd);
267 		ctfmerge_fatal("failed to open %s: %s\n",
268 		    file, elf_errmsg(elf_errno()));
269 	}
270 
271 	switch (elf_kind(e)) {
272 	case ELF_K_AR:
273 		ctfmerge_read_archive(file, fd, e, cmh, flags);
274 		break;
275 
276 	case ELF_K_ELF:
277 		/* ctfmerge_elfopen() takes ownership of e. */
278 		if (ctfmerge_elfopen(file, e, cmh, flags) == 0)
279 			e = NULL;
280 		break;
281 
282 	default:
283 		ctfmerge_fatal("unknown elf kind (%d) for %s\n",
284 		    elf_kind(e), file);
285 	}
286 
287 	(void) elf_end(e);
288 	(void) close(fd);
289 }
290 
291 static void
292 ctfmerge_usage(const char *fmt, ...)
293 {
294 	if (fmt != NULL) {
295 		va_list ap;
296 
297 		(void) fprintf(stderr, "%s: ", g_progname);
298 		va_start(ap, fmt);
299 		(void) vfprintf(stderr, fmt, ap);
300 		va_end(ap);
301 	}
302 
303 	(void) fprintf(stderr, "Usage: %s [-m] [-d uniqfile] [-l label] "
304 	    "[-L labelenv] [-j nthrs] -o outfile file ...\n"
305 	    "\n"
306 	    "\t-d  uniquify merged output against uniqfile\n"
307 	    "\t-j  use nthrs threads to perform the merge\n"
308 	    "\t-l  set output container's label to specified value\n"
309 	    "\t-L  set output container's label to value from environment\n"
310 	    "\t-m  allow C-based input files to not have CTF\n"
311 	    "\t-o  file to add CTF data to\n",
312 	    g_progname);
313 }
314 
315 static void
316 ctfmerge_altexec(char **argv)
317 {
318 	const char *alt;
319 	char *altexec;
320 
321 	alt = getenv(CTFMERGE_ALTEXEC);
322 	if (alt == NULL || *alt == '\0')
323 		return;
324 
325 	altexec = strdup(alt);
326 	if (altexec == NULL)
327 		ctfmerge_fatal("failed to allocate memory for altexec\n");
328 	if (unsetenv(CTFMERGE_ALTEXEC) != 0)
329 		ctfmerge_fatal("failed to unset %s from environment: %s\n",
330 		    CTFMERGE_ALTEXEC, strerror(errno));
331 
332 	(void) execv(altexec, argv);
333 	ctfmerge_fatal("failed to execute alternate program %s: %s",
334 	    altexec, strerror(errno));
335 }
336 
337 int
338 main(int argc, char *argv[])
339 {
340 	int err, i, c, ofd;
341 	uint_t nthreads = CTFMERGE_DEFAULT_NTHREADS;
342 	char *tmpfile = NULL, *label = NULL;
343 	int wflags = CTF_ELFWRITE_F_COMPRESS;
344 	uint_t flags = 0;
345 	ctf_merge_t *cmh;
346 	ctf_file_t *ofp;
347 	long argj;
348 	char *eptr;
349 
350 	g_progname = basename(argv[0]);
351 
352 	ctfmerge_altexec(argv);
353 
354 	/*
355 	 * We support a subset of the old CTF merge flags, mostly for
356 	 * compatibility.
357 	 */
358 	while ((c = getopt(argc, argv, ":d:fgj:l:L:mo:t")) != -1) {
359 		switch (c) {
360 		case 'd':
361 			g_unique = optarg;
362 			break;
363 		case 'f':
364 			/* Silently ignored for compatibility */
365 			break;
366 		case 'g':
367 			/* Silently ignored for compatibility */
368 			break;
369 		case 'j':
370 			errno = 0;
371 			argj = strtol(optarg, &eptr, 10);
372 			if (errno != 0 || argj == LONG_MAX ||
373 			    argj > 1024 || *eptr != '\0') {
374 				ctfmerge_fatal("invalid argument for -j: %s\n",
375 				    optarg);
376 			}
377 			nthreads = (uint_t)argj;
378 			break;
379 		case 'l':
380 			label = optarg;
381 			break;
382 		case 'L':
383 			label = getenv(optarg);
384 			break;
385 		case 'm':
386 			flags |= CTF_ALLOW_MISSING_DEBUG;
387 			break;
388 		case 'o':
389 			g_outfile = optarg;
390 			break;
391 		case 't':
392 			/* Silently ignored for compatibility */
393 			break;
394 		case ':':
395 			ctfmerge_usage("Option -%c requires an operand\n",
396 			    optopt);
397 			return (CTFMERGE_USAGE);
398 		case '?':
399 			ctfmerge_usage("Unknown option: -%c\n", optopt);
400 			return (CTFMERGE_USAGE);
401 		}
402 	}
403 
404 	if (g_outfile == NULL) {
405 		ctfmerge_usage("missing required -o output file\n");
406 		return (CTFMERGE_USAGE);
407 	}
408 
409 	(void) elf_version(EV_CURRENT);
410 
411 	/*
412 	 * Obviously this isn't atomic, but at least gives us a good starting
413 	 * point.
414 	 */
415 	if ((ofd = open(g_outfile, O_RDWR)) < 0)
416 		ctfmerge_fatal("cannot open output file %s: %s\n", g_outfile,
417 		    strerror(errno));
418 
419 	argc -= optind;
420 	argv += optind;
421 
422 	if (argc < 1) {
423 		ctfmerge_usage("no input files specified");
424 		return (CTFMERGE_USAGE);
425 	}
426 
427 	cmh = ctf_merge_init(ofd, &err);
428 	if (cmh == NULL)
429 		ctfmerge_fatal("failed to create merge handle: %s\n",
430 		    ctf_errmsg(err));
431 
432 	if ((err = ctf_merge_set_nthreads(cmh, nthreads)) != 0)
433 		ctfmerge_fatal("failed to set parallelism to %u: %s\n",
434 		    nthreads, ctf_errmsg(err));
435 
436 	for (i = 0; i < argc; i++) {
437 		ctfmerge_file_add(cmh, argv[i], flags);
438 	}
439 
440 	if (g_nctf == 0) {
441 		ctf_merge_fini(cmh);
442 		return (0);
443 	}
444 
445 	if (g_unique != NULL) {
446 		ctf_file_t *ufp;
447 		char *base;
448 
449 		ufp = ctf_open(g_unique, &err);
450 		if (ufp == NULL) {
451 			ctfmerge_fatal("failed to open uniquify file %s: %s\n",
452 			    g_unique, ctf_errmsg(err));
453 		}
454 
455 		base = basename(g_unique);
456 		(void) ctf_merge_uniquify(cmh, ufp, base);
457 	}
458 
459 	if (label != NULL) {
460 		if ((err = ctf_merge_label(cmh, label)) != 0)
461 			ctfmerge_fatal("failed to add label %s: %s\n", label,
462 			    ctf_errmsg(err));
463 	}
464 
465 	err = ctf_merge_merge(cmh, &ofp);
466 	if (err != 0)
467 		ctfmerge_fatal("failed to merge types: %s\n", ctf_errmsg(err));
468 	ctf_merge_fini(cmh);
469 
470 	if (asprintf(&tmpfile, "%s.ctf", g_outfile) == -1)
471 		ctfmerge_fatal("ran out of memory for temporary file name\n");
472 	err = ctf_elfwrite(ofp, g_outfile, tmpfile, wflags);
473 	if (err == CTF_ERR) {
474 		(void) unlink(tmpfile);
475 		free(tmpfile);
476 		ctfmerge_fatal("encountered a libctf error: %s!\n",
477 		    ctf_errmsg(ctf_errno(ofp)));
478 	}
479 
480 	if (rename(tmpfile, g_outfile) != 0) {
481 		(void) unlink(tmpfile);
482 		free(tmpfile);
483 		ctfmerge_fatal("failed to rename temporary file: %s\n",
484 		    strerror(errno));
485 	}
486 	free(tmpfile);
487 
488 	return (CTFMERGE_OK);
489 }
490