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
ctfmerge_fatal(const char * fmt,...)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
ctfmerge_expect_ctf(const char * name,Elf * elf)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
ctfmerge_elfopen(const char * name,Elf * elf,ctf_merge_t * cmh)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
ctfmerge_read_archive(const char * name,int fd,Elf * elf,ctf_merge_t * cmh)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
ctfmerge_usage(const char * fmt,...)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
ctfmerge_altexec(char ** argv)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
main(int argc,char * argv[])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