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