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