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