xref: /illumos-gate/usr/src/cmd/ctfmerge/ctfmerge.c (revision 16d40492)
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__))
ctfmerge_fatal(const char * fmt,...)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
ctfmerge_check_for_c(const char * name,Elf * elf,uint_t flags)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
ctfmerge_elfopen(const char * name,Elf * elf,ctf_merge_t * cmh,uint_t flags)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
ctfmerge_read_archive(const char * name,int fd,Elf * elf,ctf_merge_t * cmh,uint_t flags)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)", name, arhdr->ar_name) < 0)
227 			ctfmerge_fatal("failed to allocate memory for archive "
228 			    "%d of file %s\n", cursec, name);
229 
230 		switch (elf_kind(aelf)) {
231 		case ELF_K_AR:
232 			ctfmerge_read_archive(nname, fd, aelf, cmh, flags);
233 			break;
234 		case ELF_K_ELF:
235 			/* ctfmerge_elfopen() takes ownership of aelf. */
236 			if (ctfmerge_elfopen(nname, aelf, cmh, flags) == 0)
237 				aelf = NULL;
238 			break;
239 		default:
240 			ctfmerge_fatal("unknown elf kind (%d) in archive %d "
241 			    "for %s\n", elf_kind(aelf), cursec, name);
242 			break;
243 		}
244 
245 next:
246 		(void) elf_end(aelf);
247 		free(nname);
248 		cursec++;
249 	}
250 }
251 
252 static void
ctfmerge_file_add(ctf_merge_t * cmh,const char * file,uint_t flags)253 ctfmerge_file_add(ctf_merge_t *cmh, const char *file, uint_t flags)
254 {
255 	Elf *e;
256 	int fd;
257 
258 	if ((fd = open(file, O_RDONLY)) < 0) {
259 		ctfmerge_fatal("failed to open file %s: %s\n",
260 		    file, strerror(errno));
261 	}
262 
263 	if ((e = elf_begin(fd, ELF_C_READ, NULL)) == NULL) {
264 		(void) close(fd);
265 		ctfmerge_fatal("failed to open %s: %s\n",
266 		    file, elf_errmsg(elf_errno()));
267 	}
268 
269 	switch (elf_kind(e)) {
270 	case ELF_K_AR:
271 		ctfmerge_read_archive(file, fd, e, cmh, flags);
272 		break;
273 
274 	case ELF_K_ELF:
275 		/* ctfmerge_elfopen() takes ownership of e. */
276 		if (ctfmerge_elfopen(file, e, cmh, flags) == 0)
277 			e = NULL;
278 		break;
279 
280 	default:
281 		ctfmerge_fatal("unknown elf kind (%d) for %s\n",
282 		    elf_kind(e), file);
283 	}
284 
285 	(void) elf_end(e);
286 	(void) close(fd);
287 }
288 
289 static void
ctfmerge_usage(const char * fmt,...)290 ctfmerge_usage(const char *fmt, ...)
291 {
292 	if (fmt != NULL) {
293 		va_list ap;
294 
295 		(void) fprintf(stderr, "%s: ", g_progname);
296 		va_start(ap, fmt);
297 		(void) vfprintf(stderr, fmt, ap);
298 		va_end(ap);
299 	}
300 
301 	(void) fprintf(stderr, "Usage: %s [-m] [-d uniqfile] [-l label] "
302 	    "[-L labelenv] [-j nthrs] -o outfile file ...\n"
303 	    "\n"
304 	    "\t-d  uniquify merged output against uniqfile\n"
305 	    "\t-j  use nthrs threads to perform the merge\n"
306 	    "\t-l  set output container's label to specified value\n"
307 	    "\t-L  set output container's label to value from environment\n"
308 	    "\t-m  allow C-based input files to not have CTF\n"
309 	    "\t-o  file to add CTF data to\n",
310 	    g_progname);
311 }
312 
313 int
main(int argc,char * argv[])314 main(int argc, char *argv[])
315 {
316 	int err, i, c, ofd;
317 	uint_t nthreads = CTFMERGE_DEFAULT_NTHREADS;
318 	char *tmpfile = NULL, *label = NULL;
319 	int wflags = CTF_ELFWRITE_F_COMPRESS;
320 	uint_t flags = 0;
321 	ctf_merge_t *cmh;
322 	ctf_file_t *ofp;
323 	long argj;
324 	char *eptr;
325 
326 	g_progname = basename(argv[0]);
327 
328 	/*
329 	 * We support a subset of the old CTF merge flags, mostly for
330 	 * compatibility.
331 	 */
332 	while ((c = getopt(argc, argv, ":d:fgj:l:L:mo:t")) != -1) {
333 		switch (c) {
334 		case 'd':
335 			g_unique = optarg;
336 			break;
337 		case 'f':
338 			/* Silently ignored for compatibility */
339 			break;
340 		case 'g':
341 			/* Silently ignored for compatibility */
342 			break;
343 		case 'j':
344 			errno = 0;
345 			argj = strtol(optarg, &eptr, 10);
346 			if (errno != 0 || argj == LONG_MAX ||
347 			    argj > 1024 || *eptr != '\0') {
348 				ctfmerge_fatal("invalid argument for -j: %s\n",
349 				    optarg);
350 			}
351 			nthreads = (uint_t)argj;
352 			break;
353 		case 'l':
354 			label = optarg;
355 			break;
356 		case 'L':
357 			label = getenv(optarg);
358 			break;
359 		case 'm':
360 			flags |= CTF_ALLOW_MISSING_DEBUG;
361 			break;
362 		case 'o':
363 			g_outfile = optarg;
364 			break;
365 		case 't':
366 			/* Silently ignored for compatibility */
367 			break;
368 		case ':':
369 			ctfmerge_usage("Option -%c requires an operand\n",
370 			    optopt);
371 			return (CTFMERGE_USAGE);
372 		case '?':
373 			ctfmerge_usage("Unknown option: -%c\n", optopt);
374 			return (CTFMERGE_USAGE);
375 		}
376 	}
377 
378 	if (g_outfile == NULL) {
379 		ctfmerge_usage("missing required -o output file\n");
380 		return (CTFMERGE_USAGE);
381 	}
382 
383 	(void) elf_version(EV_CURRENT);
384 
385 	/*
386 	 * Obviously this isn't atomic, but at least gives us a good starting
387 	 * point.
388 	 */
389 	if ((ofd = open(g_outfile, O_RDWR)) < 0)
390 		ctfmerge_fatal("cannot open output file %s: %s\n", g_outfile,
391 		    strerror(errno));
392 
393 	argc -= optind;
394 	argv += optind;
395 
396 	if (argc < 1) {
397 		ctfmerge_usage("no input files specified\n");
398 		return (CTFMERGE_USAGE);
399 	}
400 
401 	cmh = ctf_merge_init(ofd, &err);
402 	if (cmh == NULL)
403 		ctfmerge_fatal("failed to create merge handle: %s\n",
404 		    ctf_errmsg(err));
405 
406 	if ((err = ctf_merge_set_nthreads(cmh, nthreads)) != 0)
407 		ctfmerge_fatal("failed to set parallelism to %u: %s\n",
408 		    nthreads, ctf_errmsg(err));
409 
410 	for (i = 0; i < argc; i++) {
411 		ctfmerge_file_add(cmh, argv[i], flags);
412 	}
413 
414 	if (g_nctf == 0) {
415 		ctf_merge_fini(cmh);
416 		return (0);
417 	}
418 
419 	if (g_unique != NULL) {
420 		ctf_file_t *ufp;
421 		char *base;
422 
423 		ufp = ctf_open(g_unique, &err);
424 		if (ufp == NULL) {
425 			ctfmerge_fatal("failed to open uniquify file %s: %s\n",
426 			    g_unique, ctf_errmsg(err));
427 		}
428 
429 		base = basename(g_unique);
430 		(void) ctf_merge_uniquify(cmh, ufp, base);
431 	}
432 
433 	if (label != NULL) {
434 		if ((err = ctf_merge_label(cmh, label)) != 0)
435 			ctfmerge_fatal("failed to add label %s: %s\n", label,
436 			    ctf_errmsg(err));
437 	}
438 
439 	err = ctf_merge_merge(cmh, &ofp);
440 	if (err != 0)
441 		ctfmerge_fatal("failed to merge types: %s\n", ctf_errmsg(err));
442 	ctf_merge_fini(cmh);
443 
444 	if (asprintf(&tmpfile, "%s.ctf", g_outfile) == -1)
445 		ctfmerge_fatal("ran out of memory for temporary file name\n");
446 	err = ctf_elfwrite(ofp, g_outfile, tmpfile, wflags);
447 	if (err == CTF_ERR) {
448 		(void) unlink(tmpfile);
449 		free(tmpfile);
450 		ctfmerge_fatal("encountered a libctf error: %s!\n",
451 		    ctf_errmsg(ctf_errno(ofp)));
452 	}
453 
454 	if (rename(tmpfile, g_outfile) != 0) {
455 		(void) unlink(tmpfile);
456 		free(tmpfile);
457 		ctfmerge_fatal("failed to rename temporary file: %s\n",
458 		    strerror(errno));
459 	}
460 	free(tmpfile);
461 
462 	return (CTFMERGE_OK);
463 }
464