b8dd56e74a
The improvements are: - can get the kld info from core files via kvm(3); - can use kldstat(2) directly, which is a piece of cake; - can use .symbols or whatever, which allows for use by non-developers when reporting system crashes -- now asf(8) can be mentioned in the handbook at last; - speed (no more double fts(3) per loaded module); - various bugs fixed. At the same time, the new asf(8) should stay compatible with the old one, bar bugs. Perhaps some defaults may be changed later to match today's state of affairs. Reviewed by: grog MFC after: 1 month Sponsored by: RiNet (Cronyx Plus LLC)
414 lines
11 KiB
C
414 lines
11 KiB
C
/*-
|
|
* 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 <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/stat.h>
|
|
#include <ctype.h>
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <fts.h>
|
|
#include <inttypes.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#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(const char *modules_path, const char *sfx[], FILE *out)
|
|
{
|
|
char *path_argv[2];
|
|
char *p;
|
|
FTS *fts;
|
|
FTSENT *ftsent;
|
|
struct kfile *kfp;
|
|
int i;
|
|
int sl;
|
|
|
|
/* Have to copy modules_path here because it's const */
|
|
if ((path_argv[0] = strdup(modules_path)) == NULL)
|
|
errx(2, "out of memory");
|
|
path_argv[1] = NULL;
|
|
|
|
/* 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 path %s", modules_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);
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
if (ftsent == NULL && errno != 0)
|
|
err(2, "couldn't complete traversing path %s", modules_path);
|
|
fts_close(fts);
|
|
} while (*sfx++);
|
|
done:
|
|
free(path_argv[0]);
|
|
}
|
|
|
|
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, strlen(myname), "");
|
|
exit(2);
|
|
}
|
|
|
|
#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];
|
|
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 (!dofind)
|
|
STAILQ_FOREACH(kfp, &kfile_head, link) {
|
|
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",
|
|
modules_path,
|
|
nosubdir ? "" : basename,
|
|
kfp->name,
|
|
*sfx ? *sfx : "");
|
|
if (*sfx == NULL || stat(path, &st) == 0) {
|
|
doobj(path, kfp->addr, out);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
findmodules(modules_path, suffixes, out);
|
|
|
|
return (0);
|
|
}
|