Add kldxref(8), for maintaining the linker.hints file for translating

module->pathname.ko.  It supports only ELF for now.

Submitted by:   bp  (with some minor tweaks)
This commit is contained in:
Peter Wemm 2001-09-11 01:13:15 +00:00
parent 505222d35f
commit 9c6f92408c
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=83322
7 changed files with 838 additions and 0 deletions

View File

@ -46,6 +46,7 @@ SUBDIR= IPXrouted \
kbdcontrol \
kbdmap \
kernbb \
kldxref \
lastlogin \
mailwrapper \
manctl \

View File

@ -0,0 +1,7 @@
# $FreeBSD$
PROG= kldxref
SRCS= kldxref.c ef.c
NOMAN=
.include <bsd.prog.mk>

376
usr.sbin/kldxref/ef.c Normal file
View File

@ -0,0 +1,376 @@
/*
* Copyright (c) 2000, Boris Popov
* 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Boris Popov.
* 4. Neither the name of the author nor the names of any co-contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
*
* $FreeBSD$
*/
#include <sys/param.h>
#include <sys/exec.h>
#include <sys/queue.h>
#include <sys/kernel.h>
#include <sys/reboot.h>
#include <sys/linker.h>
#include <string.h>
#include <machine/bootinfo.h>
#include <machine/elf.h>
#include <stand.h>
#define FREEBSD_ELF
#include <link.h>
#include <err.h>
#include "ef.h"
void
ef_print_phdr(Elf_Phdr *phdr)
{
if ((phdr->p_flags & PF_W) == 0) {
printf("text=0x%lx ", (long)phdr->p_filesz);
} else {
printf("data=0x%lx", (long)phdr->p_filesz);
if (phdr->p_filesz < phdr->p_memsz)
printf("+0x%lx", (long)(phdr->p_memsz - phdr->p_filesz));
printf(" ");
}
}
u_long
ef_get_offset(elf_file_t ef, Elf_Off off)
{
Elf_Phdr *ph;
int i;
for (i = 0; i < ef->ef_nsegs; i++) {
ph = ef->ef_segs[i];
if (off >= ph->p_vaddr && off < ph->p_vaddr + ph->p_memsz) {
return ph->p_offset + (off - ph->p_vaddr);
}
}
return 0;
}
/*
* next three functions copied from link_elf.c
*/
static unsigned long
elf_hash(const char *name)
{
const unsigned char *p = (const unsigned char *) name;
unsigned long h = 0;
unsigned long g;
while (*p != '\0') {
h = (h << 4) + *p++;
if ((g = h & 0xf0000000) != 0)
h ^= g >> 24;
h &= ~g;
}
return h;
}
int
ef_lookup_symbol(elf_file_t ef, const char* name, Elf_Sym** sym)
{
unsigned long symnum;
Elf_Sym* symp;
char *strp;
unsigned long hash;
/* First, search hashed global symbols */
hash = elf_hash(name);
symnum = ef->ef_buckets[hash % ef->ef_nbuckets];
while (symnum != STN_UNDEF) {
if (symnum >= ef->ef_nchains) {
warnx("ef_lookup_symbol: file %s have corrupted symbol table\n",
ef->ef_name);
return ENOENT;
}
symp = ef->ef_symtab + symnum;
if (symp->st_name == 0) {
warnx("ef_lookup_symbol: file %s have corrupted symbol table\n",
ef->ef_name);
return ENOENT;
}
strp = ef->ef_strtab + symp->st_name;
if (strcmp(name, strp) == 0) {
if (symp->st_shndx != SHN_UNDEF ||
(symp->st_value != 0 &&
ELF_ST_TYPE(symp->st_info) == STT_FUNC)) {
*sym = symp;
return 0;
} else
return ENOENT;
}
symnum = ef->ef_chains[symnum];
}
return ENOENT;
}
int
ef_parse_dynamic(elf_file_t ef)
{
Elf_Dyn *dp;
Elf_Off hashhdr[2];
/* int plttype = DT_REL;*/
int error;
for (dp = ef->ef_dyn; dp->d_tag != DT_NULL; dp++) {
switch (dp->d_tag) {
case DT_HASH:
error = ef_read(ef, ef_get_offset(ef, dp->d_un.d_ptr),
sizeof(hashhdr), hashhdr);
if (error) {
warnx("can't read hash header (%lx)",
ef_get_offset(ef, dp->d_un.d_ptr));
return error;
}
ef->ef_nbuckets = hashhdr[0];
ef->ef_nchains = hashhdr[1];
error = ef_read_entry(ef, -1,
(hashhdr[0] + hashhdr[1]) * sizeof(Elf_Off),
(void**)&ef->ef_hashtab);
if (error) {
warnx("can't read hash table");
return error;
}
ef->ef_buckets = ef->ef_hashtab;
ef->ef_chains = ef->ef_buckets + ef->ef_nbuckets;
break;
case DT_STRTAB:
ef->ef_stroff = dp->d_un.d_ptr;
break;
case DT_STRSZ:
ef->ef_strsz = dp->d_un.d_val;
break;
case DT_SYMTAB:
ef->ef_symoff = dp->d_un.d_ptr;
break;
case DT_SYMENT:
if (dp->d_un.d_val != sizeof(Elf_Sym))
return EFTYPE;
break;
}
}
if (ef->ef_symoff == 0) {
warnx("%s: no .dynsym section found\n", ef->ef_name);
return EFTYPE;
}
if (ef->ef_stroff == 0) {
warnx("%s: no .dynstr section found\n", ef->ef_name);
return EFTYPE;
}
if (ef_read_entry(ef, ef_get_offset(ef, ef->ef_symoff),
ef->ef_nchains * sizeof(Elf_Sym),
(void**)&ef->ef_symtab) != 0) {
if (ef->ef_verbose)
warnx("%s: can't load .dynsym section (0x%lx)",
ef->ef_name, (long)ef->ef_symoff);
return EIO;
}
if (ef_read_entry(ef, ef_get_offset(ef, ef->ef_stroff), ef->ef_strsz,
(void**)&ef->ef_strtab) != 0) {
warnx("can't load .dynstr section");
return EIO;
}
return 0;
}
int
ef_read(elf_file_t ef, Elf_Off offset, size_t len, void*dest)
{
if (offset != -1) {
if (lseek(ef->ef_fd, offset, SEEK_SET) == -1)
return EIO;
}
return read(ef->ef_fd, dest, len) == len ? 0 : EIO;
}
int
ef_read_entry(elf_file_t ef, Elf_Off offset, size_t len, void**ptr)
{
int error;
*ptr = malloc(len);
if (*ptr == NULL)
return ENOMEM;
error = ef_read(ef, offset, len, *ptr);
if (error)
free(*ptr);
return error;
}
int
ef_seg_read(elf_file_t ef, Elf_Off offset, size_t len, void*dest)
{
u_long ofs = ef_get_offset(ef, offset);
if (ofs == 0) {
if (ef->ef_verbose)
warnx("ef_seg_read(%s): zero offset (%lx:%ld)",
ef->ef_name, (long)offset, ofs);
return EFAULT;
}
return ef_read(ef, ofs, len, dest);
}
int
ef_seg_read_entry(elf_file_t ef, Elf_Off offset, size_t len, void**ptr)
{
int error;
*ptr = malloc(len);
if (*ptr == NULL)
return ENOMEM;
error = ef_seg_read(ef, offset, len, *ptr);
if (error)
free(*ptr);
return error;
}
int
ef_open(const char *filename, elf_file_t ef, int verbose)
{
Elf_Ehdr *hdr;
int fd;
int error;
int phlen, res;
int nsegs;
Elf_Phdr *phdr, *phdyn, *phphdr, *phlimit;
bzero(ef, sizeof(*ef));
if (filename == NULL)
return EFTYPE;
ef->ef_verbose = verbose;
if ((fd = open(filename, O_RDONLY)) == -1)
return errno;
ef->ef_fd = fd;
ef->ef_name = strdup(filename);
hdr = (Elf_Ehdr *)&ef->ef_hdr;
do {
res = read(fd, hdr, sizeof(*hdr));
error = EFTYPE;
if (res != sizeof(*hdr))
break;
if (!IS_ELF(*hdr))
break;
if (hdr->e_ident[EI_CLASS] != ELF_TARG_CLASS ||
hdr->e_ident[EI_DATA] != ELF_TARG_DATA ||
hdr->e_ident[EI_VERSION] != EV_CURRENT ||
hdr->e_version != EV_CURRENT ||
hdr->e_machine != ELF_TARG_MACH ||
hdr->e_phentsize != sizeof(Elf_Phdr))
break;
phlen = hdr->e_phnum * sizeof(Elf_Phdr);
if (ef_read_entry(ef, hdr->e_phoff, phlen,
(void**)&ef->ef_ph) != 0)
break;
phdr = ef->ef_ph;
phlimit = phdr + hdr->e_phnum;
nsegs = 0;
phdyn = NULL;
phphdr = NULL;
while (phdr < phlimit) {
if (verbose > 1)
ef_print_phdr(phdr);
switch (phdr->p_type) {
case PT_LOAD:
if (nsegs == 2) {
warnx("%s: too many sections",
filename);
break;
}
ef->ef_segs[nsegs++] = phdr;
break;
case PT_PHDR:
phphdr = phdr;
break;
case PT_DYNAMIC:
phdyn = phdr;
break;
}
phdr++;
}
if (verbose > 1)
printf("\n");
ef->ef_nsegs = nsegs;
if (phdyn == NULL) {
warnx("file isn't dynamically-linked");
break;
}
if (ef_read_entry(ef, phdyn->p_offset,
phdyn->p_filesz, (void**)&ef->ef_dyn) != 0) {
printf("ef_read_entry failed\n");
break;
}
error = ef_parse_dynamic(ef);
if (error)
break;
if (hdr->e_type == ET_DYN) {
ef->ef_type = EFT_KLD;
/* pad = (u_int)dest & PAGE_MASK;
if (pad)
dest += PAGE_SIZE - pad;*/
error = 0;
} else if (hdr->e_type == ET_EXEC) {
/* dest = hdr->e_entry;
if (dest == 0)
break;*/
ef->ef_type = EFT_KERNEL;
error = 0;
} else
break;
} while(0);
if (error) {
ef_close(ef);
if (ef->ef_verbose)
warnc(error, "elf_open(%s)", filename);
}
return error;
}
int
ef_close(elf_file_t ef)
{
close(ef->ef_fd);
/* if (ef->ef_fpage)
free(ef->ef_fpage);*/
if (ef->ef_name)
free(ef->ef_name);
return 0;
}

43
usr.sbin/kldxref/ef.h Normal file
View File

@ -0,0 +1,43 @@
/* $FreeBSD$ */
#ifndef _EF_H_
#define _EF_H_
#define EFT_KLD 1
#define EFT_KERNEL 2
typedef struct elf_file {
char* ef_name;
Elf_Phdr * ef_ph;
int ef_fd;
int ef_type;
Elf_Ehdr ef_hdr;
void* ef_fpage; /* First block of the file */
int ef_fplen; /* length of first block */
Elf_Dyn* ef_dyn; /* Symbol table etc. */
Elf_Off ef_nbuckets;
Elf_Off ef_nchains;
Elf_Off* ef_buckets;
Elf_Off* ef_chains;
Elf_Off* ef_hashtab;
Elf_Off ef_stroff;
caddr_t ef_strtab;
int ef_strsz;
Elf_Off ef_symoff;
Elf_Sym* ef_symtab;
int ef_nsegs;
Elf_Phdr * ef_segs[2];
int ef_verbose;
} *elf_file_t;
__BEGIN_DECLS
int ef_open(const char *, elf_file_t, int);
int ef_close(elf_file_t ef);
int ef_read(elf_file_t ef, Elf_Off offset, size_t len, void* dest);
int ef_read_entry(elf_file_t ef, Elf_Off offset, size_t len, void **ptr);
int ef_seg_read(elf_file_t ef, Elf_Off offset, size_t len, void *dest);
int ef_seg_read_entry(elf_file_t ef, Elf_Off offset, size_t len, void**ptr);
int ef_lookup_symbol(elf_file_t ef, const char* name, Elf_Sym** sym);
__END_DECLS
#endif /* _EF_H_*/

View File

@ -0,0 +1,40 @@
$FreeBSD$
linker.hints file consists from the one or more records. First record of
file is special and determines its version:
int version;
All subsequent records have following format:
struct record {
int length; /* length of following data */
char data[length];
};
Each record is aligned on sizeof(int) boundary. First integer of the field
'data' determines its type:
struct data {
int type; /* type of data. currently MTD_* values */
};
The rest of record depends on the type.
struct string {
int length; /* length of string */
char val[]; /* string itself (no terminating zero) */
};
struct data_mdt_version {
int type = MDT_VERSION;
struct string modname;
int version;
struct string kldname;
};
struct data_mdt_module {
int type = MDT_VERSION;
struct string modname;
struct string kldname;
};

View File

@ -0,0 +1,25 @@
$FreeBSD$
[DRAFT]
kldxref(8) used to generate linker.hints file which contains list of
modules, their version numbers and container KLDs. This file used by loader
and kernel linker.
Each directory with KLDs should have its own linker.hints file.
Typical invocation of kldxref utility may look like this:
kldxref /boot/kernel /modules
which will build hints file in both directories.
A recursive behaviour can be specified with -R option:
kldxref /boot
If no hint records written, hints file will not be created and old file
will be removed.
If -d flag specified then no files generated and program prints metadata
records to stdout.

346
usr.sbin/kldxref/kldxref.c Normal file
View File

@ -0,0 +1,346 @@
/*
* Copyright (c) 2000, Boris Popov
* 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Boris Popov.
* 4. Neither the name of the author nor the names of any co-contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
*
* $FreeBSD$
*/
#include <sys/param.h>
#include <sys/exec.h>
#include <sys/queue.h>
#include <sys/kernel.h>
#include <sys/reboot.h>
#include <sys/linker.h>
#include <sys/stat.h>
#include <sys/module.h>
#define FREEBSD_ELF
#include <link.h>
#include <err.h>
#include <fts.h>
#include <string.h>
#include <machine/bootinfo.h>
#include <machine/elf.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include "ef.h"
#define MAXRECSIZE 1024
#define check(val) if ((error = (val)) != 0) break
#ifndef min
#define min(a,b) (((a)<(b)) ? (a) : (b))
#endif
struct mod_info {
char* mi_name;
int mi_ver;
SLIST_ENTRY(mod_info) mi_next;
};
#ifdef notnow
struct kld_info {
char* k_filename;
SLIST_HEAD(mod_list_head, mod_info) k_modules;
SLIST_ENTRY(kld_info) k_next;
};
SLIST_HEAD(kld_list_head, kld_info) kldlist;
#endif
static int dflag, verbose;
FILE *fxref;
static char *xref_file = "linker.hints";
static char recbuf[MAXRECSIZE];
static int recpos, reccnt;
static void usage(void);
static void
intalign(void)
{
recpos = (recpos + sizeof(int) - 1) & ~(sizeof(int) - 1);
}
static void
record_start(void)
{
recpos = 0;
memset(recbuf, 0, MAXRECSIZE);
}
static int
record_end(void)
{
if (dflag || recpos == 0)
return 0;
reccnt++;
intalign();
fwrite(&recpos, sizeof(recpos), 1, fxref);
return fwrite(recbuf, recpos, 1, fxref) != 1 ? errno : 0;
}
static int
record_buf(const void *buf, int size)
{
if (MAXRECSIZE - recpos < size)
errx(1, "record buffer overflow");
memcpy(recbuf + recpos, buf, size);
recpos += size;
return 0;
}
static int
record_int(int val)
{
intalign();
return record_buf(&val, sizeof(val));
}
static int
record_byte(u_char val)
{
return record_buf(&val, sizeof(val));
}
static int
record_string(const char *str)
{
int len = strlen(str);
int error;
if (dflag)
return 0;
error = record_byte(len);
if (error)
return error;
return record_buf(str, len);
}
static int
parse_entry(struct mod_metadata *md, const char *cval,
struct elf_file *ef, const char *kldname)
{
struct mod_depend mdp;
struct mod_version mdv;
Elf_Off data = (Elf_Off)md->md_data;
int error = 0;
record_start();
switch (md->md_type) {
case MDT_DEPEND:
if (!dflag)
break;
check(ef_seg_read(ef, data, sizeof(mdp), (void**)&mdp));
printf(" depends on %s.%d (%d,%d)\n", cval,
mdp.md_ver_preferred, mdp.md_ver_minimum, mdp.md_ver_maximum);
break;
case MDT_VERSION:
check(ef_seg_read(ef, data, sizeof(mdv), (void**)&mdv));
record_int(MDT_VERSION);
record_string(cval);
record_int(mdv.mv_version);
record_string(kldname);
if (!dflag)
break;
printf(" interface %s.%d\n", cval, mdv.mv_version);
break;
case MDT_MODULE:
record_int(MDT_MODULE);
record_string(cval);
record_string(kldname);
if (!dflag)
break;
printf(" module %s\n", cval);
break;
default:
warnx("unknown metdata record %d in file %s", md->md_type, kldname);
}
if (!error)
record_end();
return error;
}
static int
read_kld(char *filename, char *kldname)
{
struct mod_metadata md;
struct elf_file ef;
/* struct kld_info *kip;
struct mod_info *mip;*/
void **p, **orgp;
int error, nmlen;
long start, finish, entries;
Elf_Sym *sym;
char kldmodname[MAXMODNAME + 1], cval[MAXMODNAME + 1], *cp;
if (verbose || dflag)
printf("%s\n", filename);
error = ef_open(filename, &ef, verbose);
if (error)
return error;
if (ef.ef_type != EFT_KLD && ef.ef_type != EFT_KERNEL) {
ef_close(&ef);
return 0;
}
if (!dflag) {
cp = strrchr(kldname, '.');
nmlen = cp ? min(MAXMODNAME, cp - kldname) :
min(MAXMODNAME, strlen(kldname));
strncpy(kldmodname, kldname, nmlen);
kldmodname[nmlen] = '\0';
/* fprintf(fxref, "%s:%s:%d\n", kldmodname, kldname, 0);*/
}
do {
check(ef_lookup_symbol(&ef, "__start_set_" MDT_SETNAME, &sym));
start = sym->st_value;
check(ef_lookup_symbol(&ef, "__stop_set_" MDT_SETNAME, &sym));
finish = sym->st_value;
entries = (finish - start) / sizeof(void *);
check(ef_seg_read_entry(&ef, start, sizeof(*p) * entries, (void**)&p));
orgp = p;
while(entries--) {
check(ef_seg_read(&ef, (Elf_Off)*p, sizeof(md), &md));
p++;
check(ef_seg_read(&ef, (Elf_Off)md.md_cval, sizeof(cval), cval));
cval[MAXMODNAME] = '\0';
parse_entry(&md, cval, &ef, kldname);
}
if (error)
warnc(error, "error while reading %s", filename);
free(orgp);
} while(0);
ef_close(&ef);
return error;
}
void
maketempfile(char *dest, const char *root)
{
char *p;
strncpy(dest, root, MAXPATHLEN - 1);
dest[MAXPATHLEN] = '\0';
if ((p = strrchr(dest, '/')) != 0)
p++;
else
p = dest;
strcpy(p, "lhint.XXXXXX");
if (mkstemp(dest) == -1)
err(1, "%s", dest);
}
static char xrefname[MAXPATHLEN], tempname[MAXPATHLEN];
int
main(int argc, char *argv[])
{
FTS *ftsp;
FTSENT *p;
int opt, fts_options, ival;
fts_options = FTS_PHYSICAL;
/* SLIST_INIT(&kldlist);*/
while ((opt = getopt(argc, argv, "Rdf:v")) != -1) {
switch (opt) {
case 'd':
dflag = 1;
break;
case 'f':
xref_file = optarg;
break;
case 'v':
verbose++;
break;
case 'R':
fts_options |= FTS_COMFOLLOW;
break;
default:
usage();
/* NOTREACHED */
}
}
if (argc - optind < 1)
usage();
argc -= optind;
argv += optind;
ftsp = fts_open(argv, fts_options, 0);
if (ftsp == NULL)
exit(1);
for (;;) {
p = fts_read(ftsp);
if ((p == NULL || p->fts_info == FTS_D) && !dflag && fxref) {
fclose(fxref);
if (reccnt) {
rename(tempname, xrefname);
} else {
unlink(tempname);
unlink(xrefname);
}
}
if (p == NULL)
break;
if (p && p->fts_info == FTS_D && !dflag) {
snprintf(xrefname, sizeof(xrefname), "%s/%s",
ftsp->fts_path, xref_file);
maketempfile(tempname, ftsp->fts_path);
fxref = fopen(tempname, "w+t");
if (fxref == NULL)
err(1, "can't create %s", tempname);
ival = 1;
fwrite(&ival, sizeof(ival), 1, fxref);
reccnt = 0;
}
if (p->fts_info != FTS_F)
continue;
read_kld(p->fts_path, p->fts_name);
}
fts_close(ftsp);
return 0;
}
static void
usage(void)
{
fprintf(stderr, "%s\n",
"Usage: kldxref [-Rdv] [-f hintfile] path [path..]"
);
exit(1);
}