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 * Copyright 2020 OmniOS Community Edition (OmniOSce) Association. 15 */ 16 17 /* 18 * Create CTF from extant debugging information 19 */ 20 21 #include <stdio.h> 22 #include <unistd.h> 23 #include <stdlib.h> 24 #include <stdarg.h> 25 #include <sys/types.h> 26 #include <sys/stat.h> 27 #include <fcntl.h> 28 #include <errno.h> 29 #include <libelf.h> 30 #include <libctf.h> 31 #include <string.h> 32 #include <libgen.h> 33 #include <limits.h> 34 #include <strings.h> 35 #include <sys/debug.h> 36 37 #define CTFCONVERT_OK 0 38 #define CTFCONVERT_FATAL 1 39 #define CTFCONVERT_USAGE 2 40 41 static char *ctfconvert_progname; 42 43 static void 44 ctfconvert_fatal(const char *fmt, ...) 45 { 46 va_list ap; 47 48 (void) fprintf(stderr, "%s: ", ctfconvert_progname); 49 va_start(ap, fmt); 50 (void) vfprintf(stderr, fmt, ap); 51 va_end(ap); 52 53 exit(CTFCONVERT_FATAL); 54 } 55 56 static void 57 ctfconvert_warning(void *arg, const char *fmt, ...) 58 { 59 va_list ap; 60 char *buf; 61 62 va_start(ap, fmt); 63 if (vasprintf(&buf, fmt, ap) != -1) { 64 (void) fprintf(stderr, "%s: WARNING: %s", ctfconvert_progname, 65 buf); 66 free(buf); 67 } 68 va_end(ap); 69 } 70 71 static void 72 ctfconvert_usage(const char *fmt, ...) 73 { 74 if (fmt != NULL) { 75 va_list ap; 76 77 (void) fprintf(stderr, "%s: ", ctfconvert_progname); 78 va_start(ap, fmt); 79 (void) vfprintf(stderr, fmt, ap); 80 va_end(ap); 81 } 82 83 (void) fprintf(stderr, "Usage: %s [-fikms] [-j nthrs] [-l label | " 84 "-L labelenv] [-b batchsize]\n" 85 " [-o outfile] [-M ignorefile] input\n" 86 "\n" 87 "\t-b batch process this many dies at a time (default %d)\n" 88 "\t-f always attempt to convert files\n" 89 "\t-i ignore files not built partially from C sources\n" 90 "\t-j use nthrs threads to perform the merge (default %d)\n" 91 "\t-k keep around original input file on failure\n" 92 "\t-l set output container's label to specified value\n" 93 "\t-L set output container's label to value from environment\n" 94 "\t-m allow input to have missing debug info\n" 95 "\t-M allow files listed in ignorefile to have missing debug\n" 96 "\t-o copy input to outfile and add CTF\n" 97 "\t-s allow truncation of data that cannot be fully converted\n", 98 ctfconvert_progname, 99 CTF_CONVERT_DEFAULT_BATCHSIZE, 100 CTF_CONVERT_DEFAULT_NTHREADS); 101 } 102 103 /* 104 * This is a bit unfortunate. Traditionally we do type uniquification across all 105 * modules in the kernel, including ip and unix against genunix. However, when 106 * _MACHDEP is defined, then the cpu_t ends up having an additional member 107 * (cpu_m), thus changing the ability for us to uniquify against it. This in 108 * turn causes a lot of type sprawl, as there's a lot of things that end up 109 * referring to the cpu_t and it chains out from there. 110 * 111 * So, if we find that a cpu_t has been defined and it has a couple of useful 112 * sentinel members and it does *not* have the cpu_m member, then we will try 113 * and lookup or create a forward declaration to the machcpu, append it to the 114 * end, and update the file. 115 * 116 * This currently is only invoked if an undocumented option -X is passed. This 117 * value is private to illumos and it can be changed at any time inside of it, 118 * so if -X wants to be used for something, it should be. The ability to rely on 119 * -X for others is strictly not an interface in any way, shape, or form. 120 * 121 * The following struct contains most of the information that we care about and 122 * that we want to validate exists before we decide what to do. 123 */ 124 125 typedef struct ctfconvert_fixup { 126 boolean_t cf_cyclic; /* Do we have a cpu_cyclic member */ 127 boolean_t cf_mcpu; /* We have a cpu_m member */ 128 boolean_t cf_lastpad; /* Is the pad member the last entry */ 129 ulong_t cf_padoff; /* offset of the pad */ 130 } ctfconvert_fixup_t; 131 132 /* ARGSUSED */ 133 static int 134 ctfconvert_fixup_genunix_cb(const char *name, ctf_id_t tid, ulong_t off, 135 void *arg) 136 { 137 ctfconvert_fixup_t *cfp = arg; 138 139 cfp->cf_lastpad = B_FALSE; 140 if (strcmp(name, "cpu_cyclic") == 0) { 141 cfp->cf_cyclic = B_TRUE; 142 return (0); 143 } 144 145 if (strcmp(name, "cpu_m") == 0) { 146 cfp->cf_mcpu = B_TRUE; 147 return (0); 148 } 149 150 if (strcmp(name, "cpu_m_pad") == 0) { 151 cfp->cf_lastpad = B_TRUE; 152 cfp->cf_padoff = off; 153 return (0); 154 } 155 156 return (0); 157 } 158 159 static void 160 ctfconvert_fixup_genunix(ctf_file_t *fp) 161 { 162 ctf_id_t cpuid, mcpu; 163 ssize_t sz; 164 ctfconvert_fixup_t cf; 165 int model, ptrsz; 166 167 cpuid = ctf_lookup_by_name(fp, "struct cpu"); 168 if (cpuid == CTF_ERR) 169 return; 170 171 if (ctf_type_kind(fp, cpuid) != CTF_K_STRUCT) 172 return; 173 174 if ((sz = ctf_type_size(fp, cpuid)) == CTF_ERR) 175 return; 176 177 model = ctf_getmodel(fp); 178 VERIFY(model == CTF_MODEL_ILP32 || model == CTF_MODEL_LP64); 179 ptrsz = model == CTF_MODEL_ILP32 ? 4 : 8; 180 181 bzero(&cf, sizeof (ctfconvert_fixup_t)); 182 if (ctf_member_iter(fp, cpuid, ctfconvert_fixup_genunix_cb, &cf) == 183 CTF_ERR) 184 return; 185 186 /* 187 * Finally, we want to verify that the cpu_m is actually the last member 188 * that we have here. 189 */ 190 if (cf.cf_cyclic == B_FALSE || cf.cf_mcpu == B_TRUE || 191 cf.cf_lastpad == B_FALSE) { 192 return; 193 } 194 195 if (cf.cf_padoff + ptrsz * NBBY != sz * NBBY) { 196 return; 197 } 198 199 /* 200 * Okay, we're going to do this, try to find a struct machcpu. We either 201 * want a forward or a struct. If we find something else, error. If we 202 * find nothing, add a forward and then add the member. 203 */ 204 mcpu = ctf_lookup_by_name(fp, "struct machcpu"); 205 if (mcpu == CTF_ERR) { 206 mcpu = ctf_add_forward(fp, CTF_ADD_NONROOT, "machcpu", 207 CTF_K_STRUCT); 208 if (mcpu == CTF_ERR) { 209 ctfconvert_fatal("failed to add 'struct machcpu' " 210 "forward: %s\n", ctf_errmsg(ctf_errno(fp))); 211 } 212 } else { 213 int kind; 214 if ((kind = ctf_type_kind(fp, mcpu)) == CTF_ERR) { 215 ctfconvert_fatal("failed to get the type kind for " 216 "the struct machcpu: %s\n", 217 ctf_errmsg(ctf_errno(fp))); 218 } 219 220 if (kind != CTF_K_STRUCT && kind != CTF_K_FORWARD) 221 ctfconvert_fatal("encountered a struct machcpu of the " 222 "wrong type, found type kind %d\n", kind); 223 } 224 225 if (ctf_update(fp) == CTF_ERR) { 226 ctfconvert_fatal("failed to update output file: %s\n", 227 ctf_errmsg(ctf_errno(fp))); 228 } 229 230 if (ctf_add_member(fp, cpuid, "cpu_m", mcpu, sz * NBBY) == CTF_ERR) { 231 ctfconvert_fatal("failed to add the m_cpu member: %s\n", 232 ctf_errmsg(ctf_errno(fp))); 233 } 234 235 if (ctf_update(fp) == CTF_ERR) { 236 ctfconvert_fatal("failed to update output file: %s\n", 237 ctf_errmsg(ctf_errno(fp))); 238 } 239 240 VERIFY(ctf_type_size(fp, cpuid) == sz); 241 } 242 243 int 244 main(int argc, char *argv[]) 245 { 246 int c, ifd, err; 247 boolean_t keep = B_FALSE; 248 ctf_convert_flag_t flags = 0; 249 uint_t bsize = CTF_CONVERT_DEFAULT_BATCHSIZE; 250 uint_t nthreads = CTF_CONVERT_DEFAULT_NTHREADS; 251 const char *outfile = NULL; 252 const char *label = NULL; 253 const char *infile = NULL; 254 const char *ignorefile = NULL; 255 char *tmpfile; 256 ctf_file_t *ofp; 257 char buf[4096] = ""; 258 boolean_t optx = B_FALSE; 259 boolean_t ignore_non_c = B_FALSE; 260 ctf_convert_t *cch; 261 262 ctfconvert_progname = basename(argv[0]); 263 264 while ((c = getopt(argc, argv, ":b:fij:kl:L:mM:o:sX")) != -1) { 265 switch (c) { 266 case 'b': { 267 long argno; 268 const char *errstr; 269 270 argno = strtonum(optarg, 1, UINT_MAX, &errstr); 271 if (errstr != NULL) { 272 ctfconvert_fatal("invalid argument for -b: " 273 "%s - %s\n", optarg, errstr); 274 } 275 bsize = (uint_t)argno; 276 break; 277 } 278 case 'f': 279 flags |= CTF_FORCE_CONVERSION; 280 break; 281 case 'i': 282 ignore_non_c = B_TRUE; 283 break; 284 case 'j': { 285 long argno; 286 const char *errstr; 287 288 argno = strtonum(optarg, 1, 1024, &errstr); 289 if (errstr != NULL) { 290 ctfconvert_fatal("invalid argument for -j: " 291 "%s - %s\n", optarg, errstr); 292 } 293 nthreads = (uint_t)argno; 294 break; 295 } 296 case 'k': 297 keep = B_TRUE; 298 break; 299 case 'l': 300 label = optarg; 301 break; 302 case 'L': 303 label = getenv(optarg); 304 break; 305 case 'm': 306 flags |= CTF_ALLOW_MISSING_DEBUG; 307 break; 308 case 'M': 309 ignorefile = optarg; 310 break; 311 case 'o': 312 outfile = optarg; 313 break; 314 case 's': 315 flags |= CTF_ALLOW_TRUNCATION; 316 break; 317 case 'X': 318 optx = B_TRUE; 319 break; 320 case ':': 321 ctfconvert_usage("Option -%c requires an operand\n", 322 optopt); 323 return (CTFCONVERT_USAGE); 324 case '?': 325 ctfconvert_usage("Unknown option: -%c\n", optopt); 326 return (CTFCONVERT_USAGE); 327 } 328 } 329 330 argv += optind; 331 argc -= optind; 332 333 if (argc != 1) { 334 ctfconvert_usage("Exactly one input file is required\n"); 335 return (CTFCONVERT_USAGE); 336 } 337 infile = argv[0]; 338 339 if (elf_version(EV_CURRENT) == EV_NONE) 340 ctfconvert_fatal("failed to initialize libelf: library is " 341 "out of date\n"); 342 343 ifd = open(infile, O_RDONLY); 344 if (ifd < 0) { 345 ctfconvert_fatal("failed to open input file %s: %s\n", infile, 346 strerror(errno)); 347 } 348 349 /* 350 * By default we remove the input file on failure unless we've been 351 * given an output file or -k has been specified. 352 */ 353 if (outfile != NULL && strcmp(infile, outfile) != 0) 354 keep = B_TRUE; 355 356 cch = ctf_convert_init(&err); 357 if (cch == NULL) { 358 ctfconvert_fatal( 359 "failed to create libctf conversion handle: %s\n", 360 strerror(err)); 361 } 362 if ((err = ctf_convert_set_nthreads(cch, nthreads)) != 0) 363 ctfconvert_fatal("Could not set number of threads: %s\n", 364 strerror(err)); 365 if ((err = ctf_convert_set_batchsize(cch, bsize)) != 0) 366 ctfconvert_fatal("Could not set batch size: %s\n", 367 strerror(err)); 368 if ((err = ctf_convert_set_flags(cch, flags)) != 0) 369 ctfconvert_fatal("Could not set conversion flags: %s\n", 370 strerror(err)); 371 if (label != NULL && (err = ctf_convert_set_label(cch, label)) != 0) 372 ctfconvert_fatal("Could not set label: %s\n", 373 strerror(err)); 374 if ((err = ctf_convert_set_warncb(cch, ctfconvert_warning, NULL)) != 0) 375 ctfconvert_fatal("Could not set warning callback: %s\n", 376 strerror(err)); 377 378 if (ignorefile != NULL) { 379 char *buf = NULL; 380 ssize_t cnt; 381 size_t len = 0; 382 FILE *fp; 383 384 if ((fp = fopen(ignorefile, "r")) == NULL) { 385 ctfconvert_fatal("Could not open ignorefile '%s': %s\n", 386 ignorefile, strerror(errno)); 387 } 388 389 while ((cnt = getline(&buf, &len, fp)) != -1) { 390 char *p = buf; 391 392 if (cnt == 0 || *p == '#') 393 continue; 394 395 (void) strsep(&p, "\n"); 396 if ((err = ctf_convert_add_ignore(cch, buf)) != 0) { 397 ctfconvert_fatal( 398 "Failed to add '%s' to ignore list: %s\n", 399 buf, strerror(err)); 400 } 401 } 402 free(buf); 403 if (cnt == -1 && ferror(fp) != 0) { 404 ctfconvert_fatal( 405 "Error reading from ignorefile '%s': %s\n", 406 ignorefile, strerror(errno)); 407 } 408 409 (void) fclose(fp); 410 } 411 412 ofp = ctf_fdconvert(cch, ifd, &err, buf, sizeof (buf)); 413 414 ctf_convert_fini(cch); 415 416 if (ofp == NULL) { 417 /* 418 * Normally, ctfconvert requires that its input file has at 419 * least one C-source compilation unit, and that every C-source 420 * compilation unit has DWARF. This is to avoid accidentally 421 * leaving out useful CTF. 422 * 423 * However, for the benefit of intransigent build environments, 424 * the -i and -m options can be used to relax this. 425 */ 426 if (err == ECTF_CONVNOCSRC && ignore_non_c) { 427 exit(CTFCONVERT_OK); 428 } 429 430 if (err == ECTF_CONVNODEBUG && 431 (flags & CTF_ALLOW_MISSING_DEBUG) != 0) { 432 exit(CTFCONVERT_OK); 433 } 434 435 if (keep == B_FALSE) 436 (void) unlink(infile); 437 438 /* 439 * Note, we expect libctf to include a newline it all of its 440 * error messages right now (though it perhaps shouldn't). 441 */ 442 switch (err) { 443 case ECTF_CONVBKERR: 444 ctfconvert_fatal("CTF conversion failed: %s", buf); 445 break; 446 case ECTF_CONVNODEBUG: 447 ctfconvert_fatal("CTF conversion failed due to " 448 "missing debug data; use -m to override\n"); 449 break; 450 default: 451 if (*buf != '\0') { 452 (void) fprintf(stderr, "%s: %s", 453 ctfconvert_progname, buf); 454 } 455 ctfconvert_fatal("CTF conversion failed: %s\n", 456 ctf_errmsg(err)); 457 } 458 } 459 460 if (optx == B_TRUE) 461 ctfconvert_fixup_genunix(ofp); 462 463 tmpfile = NULL; 464 if (outfile == NULL || strcmp(infile, outfile) == 0) { 465 if (asprintf(&tmpfile, "%s.ctf", infile) == -1) { 466 if (keep == B_FALSE) 467 (void) unlink(infile); 468 ctfconvert_fatal("failed to allocate memory for " 469 "temporary file: %s\n", strerror(errno)); 470 } 471 outfile = tmpfile; 472 } 473 err = ctf_elfwrite(ofp, infile, outfile, CTF_ELFWRITE_F_COMPRESS); 474 if (err == CTF_ERR) { 475 (void) unlink(outfile); 476 if (keep == B_FALSE) 477 (void) unlink(infile); 478 ctfconvert_fatal("failed to write CTF section to output file: " 479 "%s\n", ctf_errmsg(ctf_errno(ofp))); 480 } 481 ctf_close(ofp); 482 483 if (tmpfile != NULL) { 484 if (rename(tmpfile, infile) != 0) { 485 int e = errno; 486 (void) unlink(outfile); 487 if (keep == B_FALSE) 488 (void) unlink(infile); 489 ctfconvert_fatal("failed to rename temporary file: " 490 "%s\n", strerror(e)); 491 } 492 } 493 free(tmpfile); 494 495 return (CTFCONVERT_OK); 496 } 497