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 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 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 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 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 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 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 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 }