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 }