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