/*- * Copyright (c) 2002, 2003 Greg Lehey * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * This software is provided by the author ``as is'' and any express * or implied warranties, including, but not limited to, the implied * warranties of merchantability and fitness for a particular purpose * are disclaimed. In no event shall the author be liable for any * direct, indirect, incidental, special, exemplary, or consequential * damages (including, but not limited to, procurement of substitute * goods or services; loss of use, data, or profits; or business * interruption) however caused and on any theory of liability, * whether in contract, strict liability, or tort (including * negligence or otherwise) arising in any way out of the use of this * software, even if advised of the possibility of such damage. */ /* $Id: asf.c,v 1.4 2003/05/04 02:55:20 grog Exp grog $ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include "asf.h" struct kfile { char *name; caddr_t addr; int seen; STAILQ_ENTRY(kfile) link; }; static STAILQ_HEAD(,kfile) kfile_head = STAILQ_HEAD_INITIALIZER(kfile_head); void kfile_add(const char *name, caddr_t addr) { struct kfile *kfp; if ((kfp = malloc(sizeof(*kfp))) == NULL || (kfp->name = strdup(name)) == NULL) errx(2, "out of memory"); kfp->addr = addr; kfp->seen = 0; STAILQ_INSERT_TAIL(&kfile_head, kfp, link); } static struct kfile * kfile_find(const char *name) { struct kfile *kfp; STAILQ_FOREACH(kfp, &kfile_head, link) if (strcmp(kfp->name, name) == 0) return (kfp); /* found */ return (NULL); /* not found */ } static int kfile_allseen(void) { struct kfile *kfp; STAILQ_FOREACH(kfp, &kfile_head, link) if (!kfp->seen) return (0); /* at least one unseen */ return (1); /* all seen */ } static int kfile_empty(void) { return (STAILQ_EMPTY(&kfile_head)); } /* * Take a blank separated list of tokens and turn it into a list of * individual nul-delimited strings. Build a list of pointers at * token, which must have enough space for the tokens. Return the * number of tokens, or -1 on error (typically a missing string * delimiter). */ int tokenize(char *cptr, char *token[], int maxtoken) { char delim; /* delimiter to search for */ int tokennr; /* index of this token */ for (tokennr = 0; tokennr < maxtoken;) { while (isspace(*cptr)) cptr++; /* skip initial white space */ if ((*cptr == '\0') || (*cptr == '\n') || (*cptr == '#')) /* end of line */ return tokennr; /* return number of tokens found */ delim = *cptr; token[tokennr] = cptr; /* point to it */ tokennr++; /* one more */ if (tokennr == maxtoken) /* run off the end? */ return tokennr; if ((delim == '\'') || (delim == '"')) { /* delimitered */ for (;;) { cptr++; if ((*cptr == delim) && (cptr[-1] != '\\')) { /* found the partner */ cptr++; /* move on past */ if (!isspace(*cptr)) /* no space after closing quote */ return -1; *cptr++ = '\0'; /* delimit */ } else if ((*cptr == '\0') || (*cptr == '\n')) /* end of line */ return -1; } } else { /* not quoted */ while ((*cptr != '\0') && (!isspace(*cptr)) && (*cptr != '\n')) cptr++; if (*cptr != '\0') /* not end of the line, */ *cptr++ = '\0'; /* delimit and move to the next */ } } return maxtoken; /* can't get here */ } static void doobj(const char *path, caddr_t addr, FILE *out) { uintmax_t base = (uintptr_t)addr; uintmax_t textaddr = 0; uintmax_t dataaddr = 0; uintmax_t bssaddr = 0; uintmax_t *up; int octokens; char *octoken[MAXTOKEN]; char ocbuf[LINE_MAX + PATH_MAX]; FILE *objcopy; snprintf(ocbuf, sizeof(ocbuf), "/usr/bin/objdump --section-headers %s", path); if ((objcopy = popen(ocbuf, "r")) == NULL) err(2, "can't start %s", ocbuf); while (fgets(ocbuf, sizeof(ocbuf), objcopy)) { octokens = tokenize(ocbuf, octoken, MAXTOKEN); if (octokens <= 1) continue; up = NULL; if (strcmp(octoken[1], ".text") == 0) up = &textaddr; else if (strcmp(octoken[1], ".data") == 0) up = &dataaddr; else if (strcmp(octoken[1], ".bss") == 0) up = &bssaddr; if (up == NULL) continue; *up = strtoumax(octoken[3], NULL, 16) + base; } if (textaddr) { /* we must have a text address */ fprintf(out, "add-symbol-file %s 0x%jx", path, textaddr); if (dataaddr) fprintf(out, " -s .data 0x%jx", dataaddr); if (bssaddr) fprintf(out, " -s .bss 0x%jx", bssaddr); fprintf(out, "\n"); } } static void findmodules(char *path_argv[], const char *sfx[], FILE *out) { char *p; FTS *fts; FTSENT *ftsent; struct kfile *kfp; int i; int sl; /* Have to fts once per suffix to find preferred suffixes first */ do { sl = *sfx ? strlen(*sfx) : 0; /* current suffix length */ fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL); if (fts == NULL) err(2, "can't begin traversing module path"); while ((ftsent = fts_read(fts)) != NULL) { if (ftsent->fts_info == FTS_DNR || ftsent->fts_info == FTS_ERR || ftsent->fts_info == FTS_NS) { errno = ftsent->fts_errno; err(2, "error while traversing path %s", ftsent->fts_path); } if (ftsent->fts_info != FTS_F) continue; /* not a plain file */ if (sl > 0) { /* non-blank suffix; see if file name has it */ i = ftsent->fts_namelen - sl; if (i <= 0 || strcmp(ftsent->fts_name + i, *sfx) != 0) continue; /* no such suffix */ if ((p = strdup(ftsent->fts_name)) == NULL) errx(2, "out of memory"); p[i] = '\0'; /* remove suffix in the copy */ kfp = kfile_find(p); free(p); } else kfp = kfile_find(ftsent->fts_name); if (kfp && !kfp->seen) { doobj(ftsent->fts_path, kfp->addr, out); kfp->seen = 1; /* Optimization: stop fts as soon as seen all loaded modules */ if (kfile_allseen()) { fts_close(fts); return; } } } if (ftsent == NULL && errno != 0) err(2, "couldn't complete traversing module path"); fts_close(fts); } while (*sfx++); } static void usage(const char *myname) { fprintf(stderr, "Usage:\n" "%s [-afKksVx] [-M core] [-N system] [-o outfile] [-X suffix]\n" "%*s [modules-path [outfile]]\n\n" "\t-a\tappend to outfile\n" "\t-f\tfind the module in any subdirectory of modules-path\n" "\t-K\tuse kld(2) to get the list of modules\n" "\t-k\ttake input from kldstat(8)\n" "\t-M\tspecify core name for kvm(3)\n" "\t-N\tspecify system name for kvm(3)\n" "\t-o\tuse outfile instead of \".asf\"\n" "\t-s\tdon't prepend subdir for module path\n" "\t-V\tuse kvm(3) to get the list of modules\n" "\t-X\tappend suffix to list of possible module file name suffixes\n" "\t-x\tclear list of possible module file name suffixes\n", myname, (int)strlen(myname), ""); exit(2); } #define MAXPATHS 15 #define MAXSUFFIXES 15 /* KLD file names end in this */ static int nsuffixes = 2; static const char *suffixes[MAXSUFFIXES + 1] = { ".debug", ".symbols", NULL }; int main(int argc, char *argv[]) { char basename[PATH_MAX]; char path[PATH_MAX]; char *modules_argv[MAXPATHS + 1]; char *copy, *p; char **ap; const char *filemode = "w"; /* mode for outfile */ const char *modules_path = "modules"; /* path to kernel build directory */ const char *outfile = ".asf"; /* and where to write the output */ const char *corefile = NULL; /* for kvm(3) */ const char *sysfile = NULL; /* for kvm(3) */ const char **sfx; struct kfile *kfp; struct stat st; FILE *out; /* output file */ int dofind = 0; int dokld = 0; int dokvm = 0; int nosubdir = 0; int runprog = 0; int i; const int sl = strlen(KLDSUFFIX); while ((i = getopt(argc, argv, "afKkM:N:o:sVX:x")) != -1) switch (i) { case 'a': filemode = "a"; /* append to outfile */ break; case 'f': dofind = 1; /* find .ko (recursively) */ break; case 'K': dokld = 1; /* use kld(2) interface */ break; case 'k': runprog = 1; /* get input from kldstat(8) */ break; case 'M': corefile = optarg; /* core file for kvm(3) */ break; case 'N': sysfile = optarg; /* system file (kernel) for kvm(3) */ break; case 'o': outfile = optarg; /* output file name */ break; case 's': nosubdir = 1; /* don't descend into subdirs */ break; case 'V': dokvm = 1; /* use kvm(3) interface */ break; case 'X': if (nsuffixes >= MAXSUFFIXES) errx(2, "only %d suffixes can be specified", MAXSUFFIXES); suffixes[nsuffixes++] = optarg; suffixes[nsuffixes] = NULL; break; case 'x': nsuffixes = 0; suffixes[0] = NULL; break; default: usage(argv[0]); } argc -= optind; argv += optind; if (argc > 0) { modules_path = argv[0]; argc--, argv++; } if (argc > 0) { outfile = argv[0]; argc--, argv++; } if (argc > 0) usage(argv[0]); if (strcmp(outfile, "-") == 0) out = stdout; else if ((out = fopen(outfile, filemode)) == NULL) err(2, "can't open output file %s", outfile); if (dokvm || corefile || sysfile) { if (dokld || runprog) warnx("using kvm(3) instead"); asf_kvm(sysfile, corefile); } else if (dokld) { if (runprog) warnx("using kld(2) instead"); asf_kld(); } else asf_prog(runprog); /* Avoid long operations like module tree traversal when nothing to do */ if (kfile_empty()) { warnx("no kernel modules loaded"); return (0); } if ((copy = strdup(modules_path)) == NULL) errx(2, "out of memory"); for ( ap = modules_argv, p = copy; (*ap = strsep(&p, ";")) != NULL && ap < &modules_argv[MAXPATHS]; ap++ ); if (*ap) errx(2, "only %d module path elements can be specified", MAXPATHS); if (!dofind) STAILQ_FOREACH(kfp, &kfile_head, link) { for (ap = modules_argv; *ap; ap++) { if (!nosubdir) { /* prepare basename of KLD, w/o suffix */ strlcpy(basename, kfp->name, sizeof(basename) - 1); i = strlen(basename); if (i > sl && strcmp(basename + i - sl, KLDSUFFIX) == 0) i -= sl; basename[i] = '/'; basename[i + 1] = '\0'; } for (sfx = suffixes;; sfx++) { snprintf(path, sizeof(path), "%s/%s%s%s", *ap, nosubdir ? "" : basename, kfp->name, *sfx ? *sfx : ""); if (stat(path, &st) == 0) { doobj(path, kfp->addr, out); goto found; } if (*sfx == NULL) break; } } warnx("module %s not found in search path", kfp->name); found: ; } else findmodules(modules_argv, suffixes, out); free(copy); return (0); }