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