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 }