1/*-
2 * Copyright (c) 2002, 2003 Greg Lehey
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * This software is provided by the author ``as is'' and any express
15 * or implied warranties, including, but not limited to, the implied
16 * warranties of merchantability and fitness for a particular purpose
17 * are disclaimed.  In no event shall the author be liable for any
18 * direct, indirect, incidental, special, exemplary, or consequential
19 * damages (including, but not limited to, procurement of substitute
20 * goods or services; loss of use, data, or profits; or business
21 * interruption) however caused and on any theory of liability,
22 * whether in contract, strict liability, or tort (including
23 * negligence or otherwise) arising in any way out of the use of this
24 * software, even if advised of the possibility of such damage.
25 */
26/* $Id: asf.c,v 1.4 2003/05/04 02:55:20 grog Exp grog $ */
27
28#include <sys/cdefs.h>
29__FBSDID("$FreeBSD$");
30
31#include <sys/types.h>
32#include <sys/queue.h>
33#include <sys/stat.h>
34#include <ctype.h>
35#include <err.h>
36#include <errno.h>
37#include <fts.h>
38#include <inttypes.h>
39#include <limits.h>
40#include <stdio.h>
41#include <stdlib.h>
42#include <string.h>
43#include <unistd.h>
44
45#include "asf.h"
46
47struct kfile {
48    char	       *name;
49    caddr_t		addr;
50    int			seen;
51    STAILQ_ENTRY(kfile)	link;
52};
53
54static STAILQ_HEAD(,kfile) kfile_head = STAILQ_HEAD_INITIALIZER(kfile_head);
55
56void
57kfile_add(const char *name, caddr_t addr)
58{
59    struct kfile *kfp;
60
61    if ((kfp = malloc(sizeof(*kfp))) == NULL ||
62	(kfp->name = strdup(name)) == NULL)
63	    errx(2, "out of memory");
64    kfp->addr = addr;
65    kfp->seen = 0;
66    STAILQ_INSERT_TAIL(&kfile_head, kfp, link);
67}
68
69static struct kfile *
70kfile_find(const char *name)
71{
72    struct kfile *kfp;
73
74    STAILQ_FOREACH(kfp, &kfile_head, link)
75	if (strcmp(kfp->name, name) == 0)
76	    return (kfp);	/* found */
77
78    return (NULL);		/* not found */
79}
80
81static int
82kfile_allseen(void)
83{
84    struct kfile *kfp;
85
86    STAILQ_FOREACH(kfp, &kfile_head, link)
87	if (!kfp->seen)
88	    return (0);	/* at least one unseen */
89
90    return (1);		/* all seen */
91}
92
93static int
94kfile_empty(void)
95{
96    return (STAILQ_EMPTY(&kfile_head));
97}
98
99/*
100 * Take a blank separated list of tokens and turn it into a list of
101 * individual nul-delimited strings.  Build a list of pointers at
102 * token, which must have enough space for the tokens.  Return the
103 * number of tokens, or -1 on error (typically a missing string
104 * delimiter).
105 */
106int
107tokenize(char *cptr, char *token[], int maxtoken)
108{
109    char delim;				/* delimiter to search for */
110    int tokennr;			/* index of this token */
111
112    for (tokennr = 0; tokennr < maxtoken;) {
113	while (isspace(*cptr))
114	    cptr++;			/* skip initial white space */
115	if ((*cptr == '\0') || (*cptr == '\n')
116	    || (*cptr == '#'))		/* end of line */
117	    return tokennr;		/* return number of tokens found */
118	delim = *cptr;
119	token[tokennr] = cptr;		/* point to it */
120	tokennr++;			/* one more */
121	if (tokennr == maxtoken)	/* run off the end? */
122	    return tokennr;
123	if ((delim == '\'') || (delim == '"')) { /* delimitered */
124	    for (;;) {
125		cptr++;
126		if ((*cptr == delim)
127		    && (cptr[-1] != '\\')) { /* found the partner */
128		    cptr++;		/* move on past */
129		    if (!isspace(*cptr)) /* no space after closing quote */
130			return -1;
131		    *cptr++ = '\0';	/* delimit */
132		} else if ((*cptr == '\0')
133		    || (*cptr == '\n'))	/* end of line */
134		    return -1;
135	    }
136	} else {			/* not quoted */
137	    while ((*cptr != '\0') && (!isspace(*cptr)) && (*cptr != '\n'))
138		cptr++;
139	    if (*cptr != '\0')		/* not end of the line, */
140		*cptr++ = '\0';		/* delimit and move to the next */
141	}
142    }
143    return maxtoken;			/* can't get here */
144}
145
146static void
147doobj(const char *path, caddr_t addr, FILE *out)
148{
149    uintmax_t	base = (uintptr_t)addr;
150    uintmax_t	textaddr = 0;
151    uintmax_t	dataaddr = 0;
152    uintmax_t	bssaddr = 0;
153    uintmax_t  *up;
154    int		octokens;
155    char       *octoken[MAXTOKEN];
156    char	ocbuf[LINE_MAX + PATH_MAX];
157    FILE       *objcopy;
158
159    snprintf(ocbuf, sizeof(ocbuf),
160	     "/usr/bin/objdump --section-headers %s", path);
161    if ((objcopy = popen(ocbuf, "r")) == NULL)
162	err(2, "can't start %s", ocbuf);
163    while (fgets(ocbuf, sizeof(ocbuf), objcopy)) {
164	octokens = tokenize(ocbuf, octoken, MAXTOKEN);
165	if (octokens <= 1)
166	    continue;
167	up = NULL;
168	if (strcmp(octoken[1], ".text") == 0)
169	    up = &textaddr;
170	else if (strcmp(octoken[1], ".data") == 0)
171	    up = &dataaddr;
172	else if (strcmp(octoken[1], ".bss") == 0)
173	    up = &bssaddr;
174	if (up == NULL)
175	    continue;
176	*up = strtoumax(octoken[3], NULL, 16) + base;
177    }
178    if (textaddr) {	/* we must have a text address */
179	fprintf(out, "add-symbol-file %s 0x%jx", path, textaddr);
180	if (dataaddr)
181	    fprintf(out, " -s .data 0x%jx", dataaddr);
182	if (bssaddr)
183	    fprintf(out, " -s .bss 0x%jx", bssaddr);
184	fprintf(out, "\n");
185    }
186}
187
188static void
189findmodules(char *path_argv[], const char *sfx[], FILE *out)
190{
191    char	       *p;
192    FTS		       *fts;
193    FTSENT	       *ftsent;
194    struct kfile       *kfp;
195    int			i;
196    int			sl;
197
198    /* Have to fts once per suffix to find preferred suffixes first */
199    do {
200	sl = *sfx ? strlen(*sfx) : 0;	/* current suffix length */
201	fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL);
202	if (fts == NULL)
203	    err(2, "can't begin traversing module path");
204	while ((ftsent = fts_read(fts)) != NULL) {
205	    if (ftsent->fts_info == FTS_DNR ||
206		ftsent->fts_info == FTS_ERR ||
207		ftsent->fts_info == FTS_NS) {
208		    errno = ftsent->fts_errno;
209		    err(2, "error while traversing path %s", ftsent->fts_path);
210	    }
211	    if (ftsent->fts_info != FTS_F)
212		continue;			/* not a plain file */
213
214	    if (sl > 0) {
215		/* non-blank suffix; see if file name has it */
216		i = ftsent->fts_namelen - sl;
217		if (i <= 0 || strcmp(ftsent->fts_name + i, *sfx) != 0)
218		    continue;		/* no such suffix */
219		if ((p = strdup(ftsent->fts_name)) == NULL)
220		    errx(2, "out of memory");
221		p[i] = '\0';		/* remove suffix in the copy */
222		kfp = kfile_find(p);
223		free(p);
224	    } else
225		kfp = kfile_find(ftsent->fts_name);
226
227	    if (kfp && !kfp->seen) {
228		doobj(ftsent->fts_path, kfp->addr, out);
229		kfp->seen = 1;
230		/* Optimization: stop fts as soon as seen all loaded modules */
231		if (kfile_allseen()) {
232		    fts_close(fts);
233		    return;
234		}
235	    }
236	}
237	if (ftsent == NULL && errno != 0)
238	    err(2, "couldn't complete traversing module path");
239	fts_close(fts);
240    } while (*sfx++);
241}
242
243static void
244usage(const char *myname)
245{
246    fprintf(stderr,
247	"Usage:\n"
248	"%s [-afKksVx] [-M core] [-N system] [-o outfile] [-X suffix]\n"
249	"%*s [modules-path [outfile]]\n\n"
250	"\t-a\tappend to outfile\n"
251	"\t-f\tfind the module in any subdirectory of modules-path\n"
252	"\t-K\tuse kld(2) to get the list of modules\n"
253	"\t-k\ttake input from kldstat(8)\n"
254	"\t-M\tspecify core name for kvm(3)\n"
255	"\t-N\tspecify system name for kvm(3)\n"
256	"\t-o\tuse outfile instead of \".asf\"\n"
257	"\t-s\tdon't prepend subdir for module path\n"
258	"\t-V\tuse kvm(3) to get the list of modules\n"
259	"\t-X\tappend suffix to list of possible module file name suffixes\n"
260	"\t-x\tclear list of possible module file name suffixes\n",
261	myname, (int)strlen(myname), "");
262    exit(2);
263}
264
265#define	MAXPATHS	15
266#define	MAXSUFFIXES	15
267
268/* KLD file names end in this */
269static int	   nsuffixes = 2;
270static const char *suffixes[MAXSUFFIXES + 1] = {
271    ".debug",
272    ".symbols",
273    NULL
274};
275
276int
277main(int argc, char *argv[])
278{
279    char basename[PATH_MAX];
280    char path[PATH_MAX];
281    char *modules_argv[MAXPATHS + 1];
282    char *copy, *p;
283    char **ap;
284    const char *filemode = "w";		/* mode for outfile */
285    const char *modules_path = "modules"; /* path to kernel build directory */
286    const char *outfile = ".asf";	/* and where to write the output */
287    const char *corefile = NULL;	/* for kvm(3) */
288    const char *sysfile = NULL;		/* for kvm(3) */
289    const char **sfx;
290    struct kfile *kfp;
291    struct stat st;
292    FILE *out;				/* output file */
293    int dofind = 0;
294    int dokld = 0;
295    int dokvm = 0;
296    int nosubdir = 0;
297    int runprog = 0;
298    int i;
299    const int sl = strlen(KLDSUFFIX);
300
301    while ((i = getopt(argc, argv, "afKkM:N:o:sVX:x")) != -1)
302	switch (i) {
303	case 'a':
304	    filemode = "a";	/* append to outfile */
305	    break;
306	case 'f':
307	    dofind = 1;		/* find .ko (recursively) */
308	    break;
309	case 'K':
310	    dokld = 1;		/* use kld(2) interface */
311	    break;
312	case 'k':
313	    runprog = 1;	/* get input from kldstat(8) */
314	    break;
315	case 'M':
316	    corefile = optarg;	/* core file for kvm(3) */
317	    break;
318	case 'N':
319	    sysfile = optarg;	/* system file (kernel) for kvm(3) */
320	    break;
321	case 'o':
322	    outfile = optarg;	/* output file name */
323	    break;
324	case 's':
325	    nosubdir = 1;	/* don't descend into subdirs */
326	    break;
327	case 'V':
328	    dokvm = 1;		/* use kvm(3) interface */
329	    break;
330	case 'X':
331	    if (nsuffixes >= MAXSUFFIXES)
332		errx(2, "only %d suffixes can be specified", MAXSUFFIXES);
333	    suffixes[nsuffixes++] = optarg;
334	    suffixes[nsuffixes] = NULL;
335	    break;
336	case 'x':
337	    nsuffixes = 0;
338	    suffixes[0] = NULL;
339	    break;
340	default:
341	    usage(argv[0]);
342	}
343
344    argc -= optind;
345    argv += optind;
346
347    if (argc > 0) {
348	modules_path = argv[0];
349	argc--, argv++;
350    }
351    if (argc > 0) {
352	outfile = argv[0];
353	argc--, argv++;
354    }
355    if (argc > 0)
356	usage(argv[0]);
357
358    if (strcmp(outfile, "-") == 0)
359	out = stdout;
360    else
361	if ((out = fopen(outfile, filemode)) == NULL)
362	    err(2, "can't open output file %s", outfile);
363
364    if (dokvm || corefile || sysfile) {
365	if (dokld || runprog)
366	    warnx("using kvm(3) instead");
367	asf_kvm(sysfile, corefile);
368    } else if (dokld) {
369	if (runprog)
370	    warnx("using kld(2) instead");
371	asf_kld();
372    } else
373	asf_prog(runprog);
374
375    /* Avoid long operations like module tree traversal when nothing to do */
376    if (kfile_empty()) {
377	warnx("no kernel modules loaded");
378	return (0);
379    }
380
381    if ((copy = strdup(modules_path)) == NULL)
382	errx(2, "out of memory");
383    for (
384	ap = modules_argv, p = copy;
385	(*ap = strsep(&p, ";")) != NULL && ap < &modules_argv[MAXPATHS];
386	ap++
387    );
388    if (*ap)
389	errx(2, "only %d module path elements can be specified", MAXPATHS);
390
391    if (!dofind)
392	STAILQ_FOREACH(kfp, &kfile_head, link) {
393	    for (ap = modules_argv; *ap; ap++) {
394		if (!nosubdir) {
395		    /* prepare basename of KLD, w/o suffix */
396		    strlcpy(basename, kfp->name, sizeof(basename) - 1);
397		    i = strlen(basename);
398		    if (i > sl && strcmp(basename + i - sl, KLDSUFFIX) == 0)
399			i -= sl;
400		    basename[i] = '/';
401		    basename[i + 1] = '\0';
402		}
403		for (sfx = suffixes;; sfx++) {
404		    snprintf(path, sizeof(path),
405			     "%s/%s%s%s",
406			     *ap,
407			     nosubdir ? "" : basename,
408			     kfp->name,
409			     *sfx ? *sfx : "");
410		    if (stat(path, &st) == 0) {
411			doobj(path, kfp->addr, out);
412			goto found;
413		    }
414		    if (*sfx == NULL)
415			break;
416		}
417	    }
418	    warnx("module %s not found in search path", kfp->name);
419found:
420	    ;
421	}
422    else
423    	findmodules(modules_argv, suffixes, out);
424
425    free(copy);
426    return (0);
427}
428