diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -158,6 +158,7 @@ MAN= aac.4 \ kbdmux.4 \ keyboard.4 \ kld.4 \ + ksyms.4 \ ktr.4 \ kue.4 \ lagg.4 \ diff --git a/share/man/man4/ksyms.4 b/share/man/man4/ksyms.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/ksyms.4 @@ -0,0 +1,158 @@ +.\" Copyright (c) 2008-2009 Stacey Son +.\" The Regents of the University of California. 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 the University of +.\" California, Berkeley and its contributors. +.\" 4. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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 +.\" +.Dd April 5, 2009 +.Dt KSYMS 4 +.Os +.Sh NAME +.Nm ksyms +.Nd kernel symbol table interface +.Sh SYNOPSIS +.Cd "device ksyms" +.Sh DESCRIPTION +The +.Pa /dev/ksyms +character device provides a read-only interface to a snapshot of the kernel +symbol table. The in-kernel symbol manager is designed to be able to handle +many types of symbols tables, however, only +.Xr elf 5 +symbol tables are supported by this device. The ELF format image contains two +sections: a symbol table and a corresponding string table. +.Bl -tag -width indent -offset indent +.It Dv Symbol Table +The SYMTAB section contains the symbol table entries present in the current +running kernel, including the symbol table entries of any loaded modules. The +symbols are ordered by the kernel module load time starting with kernel file +symbols first, followed by the first loaded module's symbols and so on. +.It Dv String Table +The STRTAB section contains the symbol name strings from the kernel and any +loaded modules that the symbol table entries reference. +.El +.Pp +Elf formated symbol table data read from the +.Pa /dev/ksyms +file represents the state of the kernel at the time when the device is opened. +Since +.Pa /dev/ksyms +has no text or data, most of the fields are initialized to NULL. +The +.Nm +driver does not block the loading or unloading of modules into the kernel +while the +.Pa /dev/ksyms +file is open but may contain stale data. +.Sh IOCTLS +The +.Xr ioctl 2 +command codes below are defined in +.Aq Pa sys/ksyms.h . +.Pp +The (third) argument to the +.Xr ioctl 2 +should be a pointer to the type indicated. +.Bl -tag -width indent -offset indent +.It Dv KIOCGSIZE (size_t) +Returns the total size of the current symbol table. +This can be used when allocating a buffer to make a copy of +the kernel symbol table. +.It Dv KIOCGADDR (void *) +Returns the address of the kernel symbol table mapped in +the process memory. +.El +.Sh FILES +.Bl -tag -width /dev/ksymsX +.It Pa /dev/ksyms +.El +.Sh ERRORS +An +.Xr open 2 +of +.Pa /dev/ksyms +will fail if: +.Bl -tag -width Er +.It Bq Er EBUSY +The device is already open. A process must close +.Pa /dev/ksyms +before it can be opened again. +.It Bq Er ENOMEM +There is a resource shortage in the kernel. +.It Bq Er ENXIO +The driver was unsuccessful in creating a snapshot of the kernel symbol +table. This may occur if the kernel was in the process of loading or +unloading a module. +.El +.Sh SEE ALSO +.Xr ioctl 2 , +.Xr nlist 3 , +.Xr elf 5 , +.Xr kldload 8 +.Sh HISTORY +A +.Nm +device exists in many different operating systems. +This implementation is similar in function to the Solaris and NetBSD +.Nm +driver. +.Pp +The +.Nm +driver first appeared in +.Fx 8.0 +to support +.Xr lockstat 1 . +.Sh BUGS +Because files can be dynamically linked into the kernel at any time the symbol +information can vary. When you open the +.Pa /dev/ksyms +file, you have access to an ELF image which represents a snapshot of the state of the kernel symbol information at that instant in time. Keeping the device open does not block the loading or unloading of kernel modules. To get a new snapshot you must close and re-open the device. +.Pp +A process is only allowed to open the +.Pa /dev/ksyms +file once at a time. The process must close the +.Pa /dev/ksyms +before it is allowed to open it again. +.Pp +The +.Nm +driver uses the calling process' memory address space to store the snapshot. +.Xr ioctl 2 +can be used to get the memory address where the symbol table is stored to +save kernel memory. +.Xr mmap 2 +may also be used but it will map it to another address. +.Sh AUTHORS +The +.Nm +driver was written by +.An Stacey Son +.Aq sson@freebsd.org . diff --git a/sys/conf/NOTES b/sys/conf/NOTES --- a/sys/conf/NOTES +++ b/sys/conf/NOTES @@ -1079,6 +1079,9 @@ device random # The system memory devices; /dev/mem, /dev/kmem device mem +# The kernel symbol table device; /dev/ksyms +device ksyms + # Optional character code conversion support with LIBICONV. # Each option requires their base file system and LIBICONV. options CD9660_ICONV diff --git a/sys/conf/files b/sys/conf/files --- a/sys/conf/files +++ b/sys/conf/files @@ -1085,6 +1085,7 @@ dev/joy/joy.c optional joy dev/joy/joy_isa.c optional joy isa dev/joy/joy_pccard.c optional joy pccard dev/kbdmux/kbdmux.c optional kbdmux +dev/ksyms/ksyms.c optional ksyms dev/le/am7990.c optional le dev/le/am79900.c optional le dev/le/if_le_pci.c optional le pci diff --git a/sys/dev/ksyms/ksyms.c b/sys/dev/ksyms/ksyms.c new file mode 100644 --- /dev/null +++ b/sys/dev/ksyms/ksyms.c @@ -0,0 +1,677 @@ +/*- + * Copyright (c) 2008-2009, Stacey Son + * 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 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. + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "linker_if.h" + +#define SHDR_NULL 0 +#define SHDR_SYMTAB 1 +#define SHDR_STRTAB 2 +#define SHDR_SHSTRTAB 3 + +#define SHDR_NUM 4 + +#define STR_SYMTAB ".symtab" +#define STR_STRTAB ".strtab" +#define STR_SHSTRTAB ".shstrtab" + +#define KSYMS_DNAME "ksyms" + +static d_open_t ksyms_open; +static d_read_t ksyms_read; +static d_close_t ksyms_close; +static d_ioctl_t ksyms_ioctl; +static d_mmap_t ksyms_mmap; + +static struct cdevsw ksyms_cdevsw = { + .d_version = D_VERSION, + .d_flags = D_PSEUDO | D_TRACKCLOSE, + .d_open = ksyms_open, + .d_close = ksyms_close, + .d_read = ksyms_read, + .d_ioctl = ksyms_ioctl, + .d_mmap = ksyms_mmap, + .d_name = KSYMS_DNAME +}; + +struct ksyms_softc { + LIST_ENTRY(ksyms_softc) sc_list; + vm_offset_t sc_uaddr; + size_t sc_usize; + pmap_t sc_pmap; + struct proc *sc_proc; +}; + +static struct mtx ksyms_mtx; +static struct cdev *ksyms_dev; +static LIST_HEAD(, ksyms_softc) ksyms_list = + LIST_HEAD_INITIALIZER(&ksyms_list); + +static const char ksyms_shstrtab[] = + "\0" STR_SYMTAB "\0" STR_STRTAB "\0" STR_SHSTRTAB "\0"; + +struct ksyms_hdr { + Elf_Ehdr kh_ehdr; + Elf_Phdr kh_txtphdr; + Elf_Phdr kh_datphdr; + Elf_Shdr kh_shdr[SHDR_NUM]; + char kh_shstrtab[sizeof(ksyms_shstrtab)]; +}; + +struct tsizes { + size_t ts_symsz; + size_t ts_strsz; +}; + +struct toffsets { + vm_offset_t to_symoff; + vm_offset_t to_stroff; + unsigned to_stridx; + size_t to_resid; +}; + +static MALLOC_DEFINE(M_KSYMS, "KSYMS", "Kernel Symbol Table"); + +/* + * Get the symbol and string table sizes for a kernel module. Add it to the + * running total. + */ +static int +ksyms_size_permod(linker_file_t lf, void *arg) +{ + struct tsizes *ts; + Elf_Sym *symtab; + caddr_t strtab; + long syms; + + ts = arg; + + syms = LINKER_SYMTAB_GET(lf, &symtab); + ts->ts_symsz += syms * sizeof(Elf_Sym); + ts->ts_strsz += LINKER_STRTAB_GET(lf, &strtab); + + return (0); +} + +/* + * For kernel module get the symbol and string table sizes, returning the + * totals in *ts. + */ +static void +ksyms_size_calc(struct tsizes *ts) +{ + ts->ts_symsz = 0; + ts->ts_strsz = 0; + + (void) linker_file_foreach(ksyms_size_permod, ts); +} + +#define KSYMS_EMIT(src, des, sz) do { \ + copyout(src, (void *)des, sz); \ + des += sz; \ + } while (0) + +#define SYMBLKSZ 256 * sizeof (Elf_Sym) + +/* + * For a kernel module, add the symbol and string tables into the + * snapshot buffer. Fix up the offsets in the tables. + */ +static int +ksyms_add(linker_file_t lf, void *arg) +{ + struct toffsets *to; + Elf_Sym *symtab, *symp; + caddr_t strtab; + long symsz; + size_t strsz, numsyms; + linker_symval_t symval; + char *buf; + int i, nsyms, len; + + to = arg; + + MOD_SLOCK; + numsyms = LINKER_SYMTAB_GET(lf, &symtab); + strsz = LINKER_STRTAB_GET(lf, &strtab); + symsz = numsyms * sizeof(Elf_Sym); + + buf = malloc(SYMBLKSZ, M_KSYMS, M_WAITOK); + + while (symsz > 0) { + len = min(SYMBLKSZ, symsz); + bcopy(symtab, buf, len); + + /* + * Fix up symbol table for kernel modules: + * string offsets need adjusted + * symbol values made absolute + */ + symp = (Elf_Sym *) buf; + nsyms = len / sizeof (Elf_Sym); + for (i = 0; i < nsyms; i++) { + symp[i].st_name += to->to_stridx; + if (lf->id > 1 && LINKER_SYMBOL_VALUES(lf, + (c_linker_sym_t) &symtab[i], &symval) == 0) { + symp[i].st_value = (uintptr_t) symval.value; + } + } + + if (len > to->to_resid) { + MOD_SUNLOCK; + free(buf, M_KSYMS); + return (ENXIO); + } else + to->to_resid -= len; + KSYMS_EMIT(buf, to->to_symoff, len); + + symtab += nsyms; + symsz -= len; + } + free(buf, M_KSYMS); + MOD_SUNLOCK; + + if (strsz > to->to_resid) + return (ENXIO); + else + to->to_resid -= strsz; + KSYMS_EMIT(strtab, to->to_stroff, strsz); + to->to_stridx += strsz; + + return (0); +} + +/* + * Create a single ELF symbol table for the kernel and kernel modules loaded + * at this time. Write this snapshot out in the process address space. Return + * 0 on success, otherwise error. + */ +static int +ksyms_snapshot(struct tsizes *ts, vm_offset_t uaddr, size_t resid) +{ + + struct ksyms_hdr *hdr; + struct toffsets to; + int error = 0; + + /* Be kernel stack friendly */ + hdr = malloc(sizeof (*hdr), M_KSYMS, M_WAITOK|M_ZERO); + + /* + * Create the ELF header. + */ + hdr->kh_ehdr.e_ident[EI_PAD] = 0; + hdr->kh_ehdr.e_ident[EI_MAG0] = ELFMAG0; + hdr->kh_ehdr.e_ident[EI_MAG1] = ELFMAG1; + hdr->kh_ehdr.e_ident[EI_MAG2] = ELFMAG2; + hdr->kh_ehdr.e_ident[EI_MAG3] = ELFMAG3; + hdr->kh_ehdr.e_ident[EI_DATA] = ELF_DATA; + hdr->kh_ehdr.e_ident[EI_OSABI] = ELFOSABI_FREEBSD; + hdr->kh_ehdr.e_ident[EI_CLASS] = ELF_CLASS; + hdr->kh_ehdr.e_ident[EI_VERSION] = EV_CURRENT; + hdr->kh_ehdr.e_ident[EI_ABIVERSION] = 0; + hdr->kh_ehdr.e_type = ET_EXEC; + hdr->kh_ehdr.e_machine = ELF_ARCH; + hdr->kh_ehdr.e_version = EV_CURRENT; + hdr->kh_ehdr.e_entry = 0; + hdr->kh_ehdr.e_phoff = offsetof(struct ksyms_hdr, kh_txtphdr); + hdr->kh_ehdr.e_shoff = offsetof(struct ksyms_hdr, kh_shdr); + hdr->kh_ehdr.e_flags = 0; + hdr->kh_ehdr.e_ehsize = sizeof(Elf_Ehdr); + hdr->kh_ehdr.e_phentsize = sizeof(Elf_Phdr); + hdr->kh_ehdr.e_phnum = 2; /* Text and Data */ + hdr->kh_ehdr.e_shentsize = sizeof(Elf_Shdr); + hdr->kh_ehdr.e_shnum = SHDR_NUM; + hdr->kh_ehdr.e_shstrndx = SHDR_SHSTRTAB; + + /* + * Add both the text and data Program headers. + */ + hdr->kh_txtphdr.p_type = PT_LOAD; + /* XXX - is there a way to put the actual .text addr/size here? */ + hdr->kh_txtphdr.p_vaddr = 0; + hdr->kh_txtphdr.p_memsz = 0; + hdr->kh_txtphdr.p_flags = PF_R | PF_X; + + hdr->kh_datphdr.p_type = PT_LOAD; + /* XXX - is there a way to put the actual .data addr/size here? */ + hdr->kh_datphdr.p_vaddr = 0; + hdr->kh_datphdr.p_memsz = 0; + hdr->kh_datphdr.p_flags = PF_R | PF_W | PF_X; + + /* + * Add the Section headers: null, symtab, strtab, shstrtab, + */ + + /* First section header - null */ + + /* Second section header - symtab */ + hdr->kh_shdr[SHDR_SYMTAB].sh_name = 1; /* String offset (skip null) */ + hdr->kh_shdr[SHDR_SYMTAB].sh_type = SHT_SYMTAB; + hdr->kh_shdr[SHDR_SYMTAB].sh_flags = 0; + hdr->kh_shdr[SHDR_SYMTAB].sh_addr = 0; + hdr->kh_shdr[SHDR_SYMTAB].sh_offset = sizeof(*hdr); + hdr->kh_shdr[SHDR_SYMTAB].sh_size = ts->ts_symsz; + hdr->kh_shdr[SHDR_SYMTAB].sh_link = SHDR_STRTAB; + hdr->kh_shdr[SHDR_SYMTAB].sh_info = ts->ts_symsz / sizeof(Elf_Sym); + hdr->kh_shdr[SHDR_SYMTAB].sh_addralign = sizeof(long); + hdr->kh_shdr[SHDR_SYMTAB].sh_entsize = sizeof(Elf_Sym); + + /* Third section header - strtab */ + hdr->kh_shdr[SHDR_STRTAB].sh_name = 1 + sizeof(STR_SYMTAB); + hdr->kh_shdr[SHDR_STRTAB].sh_type = SHT_STRTAB; + hdr->kh_shdr[SHDR_STRTAB].sh_flags = 0; + hdr->kh_shdr[SHDR_STRTAB].sh_addr = 0; + hdr->kh_shdr[SHDR_STRTAB].sh_offset = + hdr->kh_shdr[SHDR_SYMTAB].sh_offset + ts->ts_symsz; + hdr->kh_shdr[SHDR_STRTAB].sh_size = ts->ts_strsz; + hdr->kh_shdr[SHDR_STRTAB].sh_link = 0; + hdr->kh_shdr[SHDR_STRTAB].sh_info = 0; + hdr->kh_shdr[SHDR_STRTAB].sh_addralign = sizeof(char); + hdr->kh_shdr[SHDR_STRTAB].sh_entsize = 0; + + /* Fourth section - shstrtab */ + hdr->kh_shdr[SHDR_SHSTRTAB].sh_name = 1 + sizeof(STR_SYMTAB) + + sizeof(STR_STRTAB); + hdr->kh_shdr[SHDR_SHSTRTAB].sh_type = SHT_STRTAB; + hdr->kh_shdr[SHDR_SHSTRTAB].sh_flags = 0; + hdr->kh_shdr[SHDR_SHSTRTAB].sh_addr = 0; + hdr->kh_shdr[SHDR_SHSTRTAB].sh_offset = + offsetof(struct ksyms_hdr, kh_shstrtab); + hdr->kh_shdr[SHDR_SHSTRTAB].sh_size = sizeof(ksyms_shstrtab); + hdr->kh_shdr[SHDR_SHSTRTAB].sh_link = 0; + hdr->kh_shdr[SHDR_SHSTRTAB].sh_info = 0; + hdr->kh_shdr[SHDR_SHSTRTAB].sh_addralign = 0 /* sizeof(char) */; + hdr->kh_shdr[SHDR_SHSTRTAB].sh_entsize = 0; + + /* Copy shstrtab into the header */ + bcopy(ksyms_shstrtab, hdr->kh_shstrtab, sizeof(ksyms_shstrtab)); + + to.to_symoff = uaddr + hdr->kh_shdr[SHDR_SYMTAB].sh_offset; + to.to_stroff = uaddr + hdr->kh_shdr[SHDR_STRTAB].sh_offset; + to.to_stridx = 0; + if (sizeof(struct ksyms_hdr) > resid) { + free(hdr, M_KSYMS); + return (ENXIO); + } + to.to_resid = resid - sizeof(struct ksyms_hdr); + + /* Emit Header */ + copyout(hdr, (void *)uaddr, sizeof(struct ksyms_hdr)); + + free(hdr, M_KSYMS); + + /* Add symbol and string tables for each kernelmodule */ + error = linker_file_foreach(ksyms_add, &to); + + if (to.to_resid != 0) + return (ENXIO); + + return (error); +} + +/* + * Map some anonymous memory in user space of size sz, rounded up to the page + * boundary. + */ +static int +ksyms_map(struct thread *td, vm_offset_t *addr, size_t sz) +{ + struct vmspace *vms = td->td_proc->p_vmspace; + int error; + vm_size_t size; + + + /* + * Map somewhere after heap in process memory. + */ + PROC_LOCK(td->td_proc); + *addr = round_page((vm_offset_t)vms->vm_daddr + + lim_max(td->td_proc, RLIMIT_DATA)); + PROC_UNLOCK(td->td_proc); + + /* round size up to page boundry */ + size = (vm_size_t) round_page(sz); + + error = vm_mmap(&vms->vm_map, addr, size, PROT_READ | PROT_WRITE, + VM_PROT_ALL, MAP_PRIVATE | MAP_ANON, OBJT_DEFAULT, NULL, 0); + + return (error); +} + +/* + * Unmap memory in user space. + */ +static int +ksyms_unmap(struct thread *td, vm_offset_t addr, size_t sz) +{ + vm_map_t map; + int error; + vm_size_t size; + + map = &td->td_proc->p_vmspace->vm_map; + + size = (vm_size_t) round_page(sz); + + /* check for address wrap-around */ + if (addr + size < addr || addr < vm_map_min(map) || + addr + size > vm_map_max(map)) + return (EINVAL); + + vm_map_lock(map); + /* make sure the pages are mapped */ + if (!vm_map_check_protection(map, addr, addr + size, VM_PROT_NONE)) { + vm_map_unlock(map); + return (EINVAL); + } + + error = vm_map_delete(map, addr, addr + size); + vm_map_unlock(map); + + return (error); +} + +static void +ksyms_cdevpriv_dtr(void *data) +{ + struct ksyms_softc *sc; + + sc = (struct ksyms_softc *)data; + + mtx_lock(&ksyms_mtx); + LIST_REMOVE(sc, sc_list); + mtx_unlock(&ksyms_mtx); + free(sc, M_KSYMS); +} + +/* ARGSUSED */ +static int +ksyms_open(struct cdev *dev, int flags, int fmt __unused, struct thread *td) +{ + struct tsizes ts; + size_t total_elf_sz; + int error, try; + struct ksyms_softc *sc; + + /* + * Limit one open() per process. The process must close() + * before open()'ing again. + */ + mtx_lock(&ksyms_mtx); + LIST_FOREACH(sc, &ksyms_list, sc_list) { + if (sc->sc_proc == td->td_proc) { + mtx_unlock(&ksyms_mtx); + return (EBUSY); + } + } + + sc = (struct ksyms_softc *) malloc(sizeof (*sc), M_KSYMS, + M_NOWAIT|M_ZERO); + + if (sc == NULL) { + mtx_unlock(&ksyms_mtx); + return (ENOMEM); + } + sc->sc_proc = td->td_proc; + sc->sc_pmap = &td->td_proc->p_vmspace->vm_pmap; + LIST_INSERT_HEAD(&ksyms_list, sc, sc_list); + mtx_unlock(&ksyms_mtx); + + error = devfs_set_cdevpriv(sc, ksyms_cdevpriv_dtr); + if (error) + goto failed; + + /* + * MOD_SLOCK doesn't work here (because of a lock reversal with + * KLD_SLOCK). Therefore, simply try upto 3 times to get a "clean" + * snapshot of the kernel symbol table. This should work fine in the + * rare case of a kernel module being loaded/unloaded at the same + * time. + */ + for(try = 0; try < 3; try++) { + /* + * Map a buffer in the calling process memory space and + * create a snapshot of the kernel symbol table in it. + */ + + /* Compute the size of buffer needed. */ + ksyms_size_calc(&ts); + total_elf_sz = sizeof(struct ksyms_hdr) + ts.ts_symsz + + ts.ts_strsz; + + error = ksyms_map(td, &(sc->sc_uaddr), + (vm_size_t) total_elf_sz); + if (error) + break; + sc->sc_usize = total_elf_sz; + + error = ksyms_snapshot(&ts, sc->sc_uaddr, total_elf_sz); + if (!error) { + /* Successful Snapshot */ + return (0); + } + + /* Snapshot failed, unmap the memory and try again */ + (void) ksyms_unmap(td, sc->sc_uaddr, sc->sc_usize); + } + +failed: + ksyms_cdevpriv_dtr(sc); + return (error); +} + +/* ARGSUSED */ +static int +ksyms_read(struct cdev *dev, struct uio *uio, int flags __unused) +{ + int error; + size_t len, sz; + struct ksyms_softc *sc; + off_t off; + char *buf; + vm_size_t ubase; + + error = devfs_get_cdevpriv((void **)&sc); + if (error) + return (error); + + off = uio->uio_offset; + len = uio->uio_resid; + + if (off < 0 || off > sc->sc_usize) + return (EFAULT); + + if (len > (sc->sc_usize - off)) + len = sc->sc_usize - off; + + if (len == 0) + return (0); + + /* + * Since the snapshot buffer is in the user space we have to copy it + * in to the kernel and then back out. The extra copy saves valuable + * kernel memory. + */ + buf = malloc(PAGE_SIZE, M_KSYMS, M_WAITOK); + ubase = sc->sc_uaddr + off; + + while (len) { + + sz = min(PAGE_SIZE, len); + if (copyin((void *)ubase, buf, sz)) + error = EFAULT; + else + error = uiomove(buf, sz, uio); + + if (error) + break; + + len -= sz; + ubase += sz; + } + free(buf, M_KSYMS); + + return (error); +} + +/* ARGSUSED */ +static int +ksyms_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int32_t flag __unused, + d_thread_t *td __unused) +{ + int error = 0; + struct ksyms_softc *sc; + + error = devfs_get_cdevpriv((void **)&sc); + if (error) + return (error); + + switch (cmd) { + case KIOCGSIZE: + /* + * Return the size (in bytes) of the symbol table + * snapshot. + */ + *(size_t *)data = sc->sc_usize; + break; + + case KIOCGADDR: + /* + * Return the address of the symbol table snapshot. + * XXX - compat32 version of this? + */ + *(void **)data = (void *)sc->sc_uaddr; + break; + + default: + error = ENOTTY; + break; + } + + return (error); +} + +/* ARGUSED */ +static int +ksyms_mmap(struct cdev *dev, vm_offset_t offset, vm_paddr_t *paddr, + int prot __unused) +{ + struct ksyms_softc *sc; + int error; + + error = devfs_get_cdevpriv((void **)&sc); + if (error) + return (error); + + /* + * XXX mmap() will actually map the symbol table into the process + * address space again. + */ + if (offset > round_page(sc->sc_usize) || + (*paddr = pmap_extract(sc->sc_pmap, + (vm_offset_t)sc->sc_uaddr + offset)) == 0) + return (-1); + + return (0); +} + +/* ARGUSED */ +static int +ksyms_close(struct cdev *dev, int flags __unused, int fmt __unused, + struct thread *td) +{ + int error = 0; + struct ksyms_softc *sc; + + error = devfs_get_cdevpriv((void **)&sc); + if (error) + return (error); + + /* Unmap the buffer from the process address space. */ + error = ksyms_unmap(td, sc->sc_uaddr, sc->sc_usize); + + devfs_clear_cdevpriv(); + + return (error); +} + +/* ARGSUSED */ +static int +ksyms_modevent(module_t mod __unused, int type, void *data __unused) +{ + int error = 0; + + switch (type) { + case MOD_LOAD: + mtx_init(&ksyms_mtx, "KSyms mtx", NULL, MTX_DEF); + ksyms_dev = make_dev(&ksyms_cdevsw, 0, UID_ROOT, GID_WHEEL, + 0444, KSYMS_DNAME); + break; + + case MOD_UNLOAD: + if (!LIST_EMPTY(&ksyms_list)) + return (EBUSY); + destroy_dev(ksyms_dev); + mtx_destroy(&ksyms_mtx); + break; + + case MOD_SHUTDOWN: + break; + + default: + error = EOPNOTSUPP; + break; + } + return (error); +} + +DEV_MODULE(ksyms, ksyms_modevent, NULL); +MODULE_VERSION(ksyms, 1); diff --git a/sys/kern/link_elf.c b/sys/kern/link_elf.c --- a/sys/kern/link_elf.c +++ b/sys/kern/link_elf.c @@ -137,6 +137,8 @@ static int link_elf_each_function_nameva linker_function_nameval_callback_t, void *); static void link_elf_reloc_local(linker_file_t); +static long link_elf_symtab_get(linker_file_t, const Elf_Sym **); +static long link_elf_strtab_get(linker_file_t, caddr_t *); static Elf_Addr elf_lookup(linker_file_t lf, Elf_Size symidx, int deps); static kobj_method_t link_elf_methods[] = { @@ -151,6 +153,8 @@ static kobj_method_t link_elf_methods[] KOBJMETHOD(linker_each_function_name, link_elf_each_function_name), KOBJMETHOD(linker_each_function_nameval, link_elf_each_function_nameval), KOBJMETHOD(linker_ctf_get, link_elf_ctf_get), + KOBJMETHOD(linker_symtab_get, link_elf_symtab_get), + KOBJMETHOD(linker_strtab_get, link_elf_strtab_get), { 0, 0 } }; @@ -1390,3 +1394,29 @@ link_elf_reloc_local(linker_file_t lf) } } } + +static long +link_elf_symtab_get(linker_file_t lf, const Elf_Sym **symtab) +{ + elf_file_t ef = (elf_file_t)lf; + + *symtab = ef->ddbsymtab; + + if (*symtab == NULL) + return (0); + + return (ef->ddbsymcnt); +} + +static long +link_elf_strtab_get(linker_file_t lf, caddr_t *strtab) +{ + elf_file_t ef = (elf_file_t)lf; + + *strtab = ef->ddbstrtab; + + if (*strtab == NULL) + return (0); + + return (ef->ddbstrcnt); +} diff --git a/sys/kern/link_elf_obj.c b/sys/kern/link_elf_obj.c --- a/sys/kern/link_elf_obj.c +++ b/sys/kern/link_elf_obj.c @@ -140,6 +140,8 @@ static int link_elf_each_function_nameva linker_function_nameval_callback_t, void *); static void link_elf_reloc_local(linker_file_t); +static long link_elf_symtab_get(linker_file_t, Elf_Sym **); +static long link_elf_strtab_get(linker_file_t, caddr_t *); static Elf_Addr elf_obj_lookup(linker_file_t lf, Elf_Size symidx, int deps); @@ -155,6 +157,8 @@ static kobj_method_t link_elf_methods[] KOBJMETHOD(linker_each_function_name, link_elf_each_function_name), KOBJMETHOD(linker_each_function_nameval, link_elf_each_function_nameval), KOBJMETHOD(linker_ctf_get, link_elf_ctf_get), + KOBJMETHOD(linker_symtab_get, link_elf_symtab_get), + KOBJMETHOD(linker_strtab_get, link_elf_strtab_get), { 0, 0 } }; @@ -1286,3 +1290,29 @@ link_elf_reloc_local(linker_file_t lf) } } } + +static long +link_elf_symtab_get(linker_file_t lf, Elf_Sym **symtab) +{ + elf_file_t ef = (elf_file_t)lf; + + *symtab = ef->ddbsymtab; + + if (*symtab == NULL) + return (0); + + return (ef->ddbsymcnt); +} + +static long +link_elf_strtab_get(linker_file_t lf, caddr_t *strtab) +{ + elf_file_t ef = (elf_file_t)lf; + + *strtab = ef->ddbstrtab; + + if (*strtab == NULL) + return (0); + + return (ef->ddbstrcnt); +} diff --git a/sys/kern/linker_if.m b/sys/kern/linker_if.m --- a/sys/kern/linker_if.m +++ b/sys/kern/linker_if.m @@ -105,6 +105,24 @@ METHOD int ctf_get { }; # +# Get the symbol table, returning it in **symtab. Return the +# number of symbols, otherwise zero. +# +METHOD long symtab_get { + linker_file_t file; + Elf_Sym **symtab; +}; + +# +# Get the string table, returning it in *strtab. Return the +# size (in bytes) of the string table, otherwise zero. +# +METHOD long strtab_get { + linker_file_t file; + caddr_t *strtab; +}; + +# # Load a file, returning the new linker_file_t in *result. If # the class does not recognise the file type, zero should be # returned, without modifying *result. If the file is diff --git a/sys/modules/Makefile b/sys/modules/Makefile --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -139,6 +139,7 @@ SUBDIR= ${_3dfx} \ joy \ kbdmux \ krpc \ + ksyms \ le \ lge \ libalias \ diff --git a/sys/modules/ksyms/Makefile b/sys/modules/ksyms/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/ksyms/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD:$ + +.PATH: ${.CURDIR}/../../dev/ksyms + +KMOD= ksyms +SRCS= ksyms.c linker_if.h + +MFILES= kern/linker_if.m + +.include diff --git a/sys/sys/ksyms.h b/sys/sys/ksyms.h new file mode 100644 --- /dev/null +++ b/sys/sys/ksyms.h @@ -0,0 +1,36 @@ +/*- + * Copyright (c) 2008-2009, Stacey Son + * 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 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. + * + */ + +#ifndef _SYS_KSYMS_H_ +#define _SYS_KSYMS_H_ + +#include + +#define KIOCGSIZE _IOR('l', 1, size_t) +#define KIOCGADDR _IOR('l', 2, void *) + +#endif /* _SYS_KSYMS_H_ */