xref: /illumos-gate/usr/src/cmd/ctfmerge/ctfmerge.c (revision 856f710c9dc323b39da5935194d7928ffb99b67f)
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) 2017, 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 boolean_t g_req;
42 static uint_t g_nctf;
43 
44 #define	CTFMERGE_OK	0
45 #define	CTFMERGE_FATAL	1
46 #define	CTFMERGE_USAGE	2
47 
48 #define	CTFMERGE_DEFAULT_NTHREADS	8
49 #define	CTFMERGE_ALTEXEC	"CTFMERGE_ALTEXEC"
50 
51 static void
52 ctfmerge_fatal(const char *fmt, ...)
53 {
54 	va_list ap;
55 
56 	(void) fprintf(stderr, "%s: ", g_progname);
57 	va_start(ap, fmt);
58 	(void) vfprintf(stderr, fmt, ap);
59 	va_end(ap);
60 
61 	if (g_outfile != NULL)
62 		(void) unlink(g_outfile);
63 
64 	exit(CTFMERGE_FATAL);
65 }
66 
67 static boolean_t
68 ctfmerge_expect_ctf(const char *name, Elf *elf)
69 {
70 	Elf_Scn *scn, *strscn;
71 	Elf_Data *data, *strdata;
72 	GElf_Shdr shdr;
73 	ulong_t i;
74 
75 	if (g_req == B_FALSE)
76 		return (B_FALSE);
77 
78 	scn = NULL;
79 	while ((scn = elf_nextscn(elf, scn)) != NULL) {
80 		if (gelf_getshdr(scn, &shdr) == NULL) {
81 			ctfmerge_fatal("failed to get section header for file "
82 			    "%s: %s\n", name, elf_errmsg(elf_errno()));
83 		}
84 
85 		if (shdr.sh_type == SHT_SYMTAB)
86 			break;
87 	}
88 
89 	if (scn == NULL)
90 		return (B_FALSE);
91 
92 	if ((strscn = elf_getscn(elf, shdr.sh_link)) == NULL)
93 		ctfmerge_fatal("failed to get section header for file %s: %s\n",
94 		    name, elf_errmsg(elf_errno()));
95 
96 	if ((data = elf_getdata(scn, NULL)) == NULL)
97 		ctfmerge_fatal("failed to read symbol table for %s: %s\n",
98 		    name, elf_errmsg(elf_errno()));
99 
100 	if ((strdata = elf_getdata(strscn, NULL)) == NULL)
101 		ctfmerge_fatal("failed to read string table for %s: %s\n",
102 		    name, elf_errmsg(elf_errno()));
103 
104 	for (i = 0; i < shdr.sh_size / shdr.sh_entsize; i++) {
105 		GElf_Sym sym;
106 		const char *file;
107 		size_t len;
108 
109 		if (gelf_getsym(data, i, &sym) == NULL)
110 			ctfmerge_fatal("failed to read symbol table entry %lu "
111 			    "for %s: %s\n", i, name, elf_errmsg(elf_errno()));
112 
113 		if (GELF_ST_TYPE(sym.st_info) != STT_FILE)
114 			continue;
115 
116 		file = (const char *)((uintptr_t)strdata->d_buf + sym.st_name);
117 		len = strlen(file);
118 		if (len < 2 || name[len - 2] != '.')
119 			continue;
120 
121 		if (name[len - 1] == 'c')
122 			return (B_TRUE);
123 	}
124 
125 	return (B_FALSE);
126 }
127 
128 /*
129  * Go through and construct enough information for this Elf Object to try and do
130  * a ctf_bufopen().
131  */
132 static void
133 ctfmerge_elfopen(const char *name, Elf *elf, ctf_merge_t *cmh)
134 {
135 	GElf_Ehdr ehdr;
136 	GElf_Shdr shdr;
137 	Elf_Scn *scn;
138 	Elf_Data *ctf_data, *str_data, *sym_data;
139 	ctf_sect_t ctfsect, symsect, strsect;
140 	ctf_file_t *fp;
141 	int err;
142 
143 	if (gelf_getehdr(elf, &ehdr) == NULL)
144 		ctfmerge_fatal("failed to get ELF header for %s: %s\n",
145 		    name, elf_errmsg(elf_errno()));
146 
147 	bzero(&ctfsect, sizeof (ctf_sect_t));
148 	bzero(&symsect, sizeof (ctf_sect_t));
149 	bzero(&strsect, sizeof (ctf_sect_t));
150 
151 	scn = NULL;
152 	while ((scn = elf_nextscn(elf, scn)) != NULL) {
153 		const char *sname;
154 
155 		if (gelf_getshdr(scn, &shdr) == NULL)
156 			ctfmerge_fatal("failed to get section header for "
157 			    "file %s: %s\n", name, elf_errmsg(elf_errno()));
158 
159 		sname = elf_strptr(elf, ehdr.e_shstrndx, shdr.sh_name);
160 		if (shdr.sh_type == SHT_PROGBITS &&
161 		    strcmp(sname, ".SUNW_ctf") == 0) {
162 			ctfsect.cts_name = sname;
163 			ctfsect.cts_type = shdr.sh_type;
164 			ctfsect.cts_flags = shdr.sh_flags;
165 			ctfsect.cts_size = shdr.sh_size;
166 			ctfsect.cts_entsize = shdr.sh_entsize;
167 			ctfsect.cts_offset = (off64_t)shdr.sh_offset;
168 
169 			ctf_data = elf_getdata(scn, NULL);
170 			if (ctf_data == NULL)
171 				ctfmerge_fatal("failed to get ELF CTF "
172 				    "data section for %s: %s\n", name,
173 				    elf_errmsg(elf_errno()));
174 			ctfsect.cts_data = ctf_data->d_buf;
175 		} else if (shdr.sh_type == SHT_SYMTAB) {
176 			Elf_Scn *strscn;
177 			GElf_Shdr strhdr;
178 
179 			symsect.cts_name = sname;
180 			symsect.cts_type = shdr.sh_type;
181 			symsect.cts_flags = shdr.sh_flags;
182 			symsect.cts_size = shdr.sh_size;
183 			symsect.cts_entsize = shdr.sh_entsize;
184 			symsect.cts_offset = (off64_t)shdr.sh_offset;
185 
186 			if ((strscn = elf_getscn(elf, shdr.sh_link)) == NULL ||
187 			    gelf_getshdr(strscn, &strhdr) == NULL)
188 				ctfmerge_fatal("failed to get "
189 				    "string table for file %s: %s\n", name,
190 				    elf_errmsg(elf_errno()));
191 
192 			strsect.cts_name = elf_strptr(elf, ehdr.e_shstrndx,
193 			    strhdr.sh_name);
194 			strsect.cts_type = strhdr.sh_type;
195 			strsect.cts_flags = strhdr.sh_flags;
196 			strsect.cts_size = strhdr.sh_size;
197 			strsect.cts_entsize = strhdr.sh_entsize;
198 			strsect.cts_offset = (off64_t)strhdr.sh_offset;
199 
200 			sym_data = elf_getdata(scn, NULL);
201 			if (sym_data == NULL)
202 				ctfmerge_fatal("failed to get ELF CTF "
203 				    "data section for %s: %s\n", name,
204 				    elf_errmsg(elf_errno()));
205 			symsect.cts_data = sym_data->d_buf;
206 
207 			str_data = elf_getdata(strscn, NULL);
208 			if (str_data == NULL)
209 				ctfmerge_fatal("failed to get ELF CTF "
210 				    "data section for %s: %s\n", name,
211 				    elf_errmsg(elf_errno()));
212 			strsect.cts_data = str_data->d_buf;
213 		}
214 	}
215 
216 	if (ctfsect.cts_type == SHT_NULL) {
217 		if (ctfmerge_expect_ctf(name, elf) == B_FALSE)
218 			return;
219 		ctfmerge_fatal("failed to open %s: %s\n", name,
220 		    ctf_errmsg(ECTF_NOCTFDATA));
221 	}
222 
223 	if (symsect.cts_type != SHT_NULL && strsect.cts_type != SHT_NULL) {
224 		fp = ctf_bufopen(&ctfsect, &symsect, &strsect, &err);
225 	} else {
226 		fp = ctf_bufopen(&ctfsect, NULL, NULL, &err);
227 	}
228 
229 	if (fp == NULL) {
230 		if (ctfmerge_expect_ctf(name, elf) == B_TRUE) {
231 			ctfmerge_fatal("failed to open file %s: %s\n",
232 			    name, ctf_errmsg(err));
233 		}
234 	} else {
235 		if ((err = ctf_merge_add(cmh, fp)) != 0) {
236 			ctfmerge_fatal("failed to add input %s: %s\n",
237 			    name, ctf_errmsg(err));
238 		}
239 		g_nctf++;
240 	}
241 }
242 
243 static void
244 ctfmerge_read_archive(const char *name, int fd, Elf *elf,
245     ctf_merge_t *cmh)
246 {
247 	Elf *aelf;
248 	Elf_Cmd cmd = ELF_C_READ;
249 	int cursec = 1;
250 	char *nname;
251 
252 	while ((aelf = elf_begin(fd, cmd, elf)) != NULL) {
253 		Elf_Arhdr *arhdr;
254 		boolean_t leakelf = B_FALSE;
255 
256 		if ((arhdr = elf_getarhdr(aelf)) == NULL)
257 			ctfmerge_fatal("failed to get archive header %d for "
258 			    "%s: %s\n", cursec, name, elf_errmsg(elf_errno()));
259 
260 		if (*(arhdr->ar_name) == '/')
261 			goto next;
262 
263 		if (asprintf(&nname, "%s.%s.%d", name, arhdr->ar_name,
264 		    cursec) < 0)
265 			ctfmerge_fatal("failed to allocate memory for archive "
266 			    "%d of file %s\n", cursec, name);
267 
268 		switch (elf_kind(aelf)) {
269 		case ELF_K_AR:
270 			ctfmerge_read_archive(nname, fd, aelf, cmh);
271 			free(nname);
272 			break;
273 		case ELF_K_ELF:
274 			ctfmerge_elfopen(nname, aelf, cmh);
275 			free(nname);
276 			leakelf = B_TRUE;
277 			break;
278 		default:
279 			ctfmerge_fatal("unknown elf kind (%d) in archive %d "
280 			    "for %s\n", elf_kind(aelf), cursec, name);
281 		}
282 
283 next:
284 		cmd = elf_next(aelf);
285 		if (leakelf == B_FALSE)
286 			(void) elf_end(aelf);
287 		cursec++;
288 	}
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 [-t] [-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-o  file to add CTF data to\n"
311 	    "\t-t  require CTF data from all inputs built from C sources\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 	ctf_file_t *ofp;
345 	ctf_merge_t *cmh;
346 	long argj;
347 	char *eptr;
348 
349 	g_progname = basename(argv[0]);
350 
351 	ctfmerge_altexec(argv);
352 
353 	/*
354 	 * We support a subset of the old CTF merge flags, mostly for
355 	 * compatability.
356 	 */
357 	while ((c = getopt(argc, argv, ":d:fgj:l:L:o:t")) != -1) {
358 		switch (c) {
359 		case 'd':
360 			g_unique = optarg;
361 			break;
362 		case 'f':
363 			/* Silently ignored for compatibility */
364 			break;
365 		case 'g':
366 			/* Silently ignored for compatibility */
367 			break;
368 		case 'j':
369 			errno = 0;
370 			argj = strtol(optarg, &eptr, 10);
371 			if (errno != 0 || argj == LONG_MAX ||
372 			    argj > 1024 || *eptr != '\0') {
373 				ctfmerge_fatal("invalid argument for -j: %s\n",
374 				    optarg);
375 			}
376 			nthreads = (uint_t)argj;
377 			break;
378 		case 'l':
379 			label = optarg;
380 			break;
381 		case 'L':
382 			label = getenv(optarg);
383 			break;
384 		case 'o':
385 			g_outfile = optarg;
386 			break;
387 		case 't':
388 			g_req = B_TRUE;
389 			break;
390 		case ':':
391 			ctfmerge_usage("Option -%c requires an operand\n",
392 			    optopt);
393 			return (CTFMERGE_USAGE);
394 		case '?':
395 			ctfmerge_usage("Unknown option: -%c\n", optopt);
396 			return (CTFMERGE_USAGE);
397 		}
398 	}
399 
400 	if (g_outfile == NULL) {
401 		ctfmerge_usage("missing required -o output file\n");
402 		return (CTFMERGE_USAGE);
403 	}
404 
405 	(void) elf_version(EV_CURRENT);
406 
407 	/*
408 	 * Obviously this isn't atomic, but at least gives us a good starting
409 	 * point.
410 	 */
411 	if ((ofd = open(g_outfile, O_RDWR)) < 0)
412 		ctfmerge_fatal("cannot open output file %s: %s\n", g_outfile,
413 		    strerror(errno));
414 
415 	argc -= optind;
416 	argv += optind;
417 
418 	if (argc < 1) {
419 		ctfmerge_usage("no input files specified");
420 		return (CTFMERGE_USAGE);
421 	}
422 
423 	cmh = ctf_merge_init(ofd, &err);
424 	if (cmh == NULL)
425 		ctfmerge_fatal("failed to create merge handle: %s\n",
426 		    ctf_errmsg(err));
427 
428 	if ((err = ctf_merge_set_nthreads(cmh, nthreads)) != 0)
429 		ctfmerge_fatal("failed to set parallelism to %u: %s\n",
430 		    nthreads, ctf_errmsg(err));
431 
432 	for (i = 0; i < argc; i++) {
433 		ctf_file_t *ifp;
434 		int fd;
435 
436 		if ((fd = open(argv[i], O_RDONLY)) < 0)
437 			ctfmerge_fatal("failed to open file %s: %s\n",
438 			    argv[i], strerror(errno));
439 		ifp = ctf_fdopen(fd, &err);
440 		if (ifp == NULL) {
441 			Elf *e;
442 
443 			if ((e = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
444 				(void) close(fd);
445 				ctfmerge_fatal("failed to open %s: %s\n",
446 				    argv[i], ctf_errmsg(err));
447 			}
448 
449 			/*
450 			 * It's an ELF file, check if we have an archive or if
451 			 * we're expecting CTF here.
452 			 */
453 			switch (elf_kind(e)) {
454 			case ELF_K_AR:
455 				break;
456 			case ELF_K_ELF:
457 				if (ctfmerge_expect_ctf(argv[i], e) == B_TRUE) {
458 					(void) elf_end(e);
459 					(void) close(fd);
460 					ctfmerge_fatal("failed to "
461 					    "open %s: file was built from C "
462 					    "sources, but missing CTF\n",
463 					    argv[i]);
464 				}
465 				(void) elf_end(e);
466 				(void) close(fd);
467 				continue;
468 			default:
469 				(void) elf_end(e);
470 				(void) close(fd);
471 				ctfmerge_fatal("failed to open %s: "
472 				    "unsupported ELF file type", argv[i]);
473 			}
474 
475 			ctfmerge_read_archive(argv[i], fd, e, cmh);
476 			(void) elf_end(e);
477 			(void) close(fd);
478 			continue;
479 		}
480 		(void) close(fd);
481 		if ((err = ctf_merge_add(cmh, ifp)) != 0)
482 			ctfmerge_fatal("failed to add input %s: %s\n",
483 			    argv[i], ctf_errmsg(err));
484 		g_nctf++;
485 	}
486 
487 	if (g_nctf == 0) {
488 		ctf_merge_fini(cmh);
489 		return (0);
490 	}
491 
492 	if (g_unique != NULL) {
493 		ctf_file_t *ufp;
494 		char *base;
495 
496 		ufp = ctf_open(g_unique, &err);
497 		if (ufp == NULL) {
498 			ctfmerge_fatal("failed to open uniquify file %s: %s\n",
499 			    g_unique, ctf_errmsg(err));
500 		}
501 
502 		base = basename(g_unique);
503 		(void) ctf_merge_uniquify(cmh, ufp, base);
504 	}
505 
506 	if (label != NULL) {
507 		if ((err = ctf_merge_label(cmh, label)) != 0)
508 			ctfmerge_fatal("failed to add label %s: %s\n", label,
509 			    ctf_errmsg(err));
510 	}
511 
512 	err = ctf_merge_merge(cmh, &ofp);
513 	if (err != 0)
514 		ctfmerge_fatal("failed to merge types: %s\n", ctf_errmsg(err));
515 	ctf_merge_fini(cmh);
516 
517 	if (asprintf(&tmpfile, "%s.ctf", g_outfile) == -1)
518 		ctfmerge_fatal("ran out of memory for temporary file name\n");
519 	err = ctf_elfwrite(ofp, g_outfile, tmpfile, wflags);
520 	if (err == CTF_ERR) {
521 		(void) unlink(tmpfile);
522 		free(tmpfile);
523 		ctfmerge_fatal("encountered a libctf error: %s!\n",
524 		    ctf_errmsg(ctf_errno(ofp)));
525 	}
526 
527 	if (rename(tmpfile, g_outfile) != 0) {
528 		(void) unlink(tmpfile);
529 		free(tmpfile);
530 		ctfmerge_fatal("failed to rename temporary file: %s\n",
531 		    strerror(errno));
532 	}
533 	free(tmpfile);
534 
535 	return (CTFMERGE_OK);
536 }
537