1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  */
  25 
  26 
  27 /*
  28  * ksyms driver - exports a single symbol/string table for the kernel
  29  * by concatenating all the module symbol/string tables.
  30  */
  31 
  32 #include <sys/types.h>
  33 #include <sys/sysmacros.h>
  34 #include <sys/cmn_err.h>
  35 #include <sys/uio.h>
  36 #include <sys/kmem.h>
  37 #include <sys/cred.h>
  38 #include <sys/mman.h>
  39 #include <sys/errno.h>
  40 #include <sys/stat.h>
  41 #include <sys/conf.h>
  42 #include <sys/debug.h>
  43 #include <sys/kobj.h>
  44 #include <sys/ksyms.h>
  45 #include <sys/vmsystm.h>
  46 #include <vm/seg_vn.h>
  47 #include <sys/atomic.h>
  48 #include <sys/compress.h>
  49 #include <sys/ddi.h>
  50 #include <sys/sunddi.h>
  51 #include <sys/list.h>
  52 
  53 typedef struct ksyms_image {
  54         caddr_t ksyms_base;     /* base address of image */
  55         size_t  ksyms_size;     /* size of image */
  56 } ksyms_image_t;
  57 
  58 typedef struct ksyms_buflist {
  59         list_node_t     buflist_node;
  60         char buf[1];
  61 } ksyms_buflist_t;
  62 
  63 typedef struct ksyms_buflist_hdr {
  64         list_t  blist;
  65         int     nchunks;
  66         ksyms_buflist_t *cur;
  67         size_t  curbuf_off;
  68 } ksyms_buflist_hdr_t;
  69 
  70 #define BUF_SIZE        (PAGESIZE - (size_t)offsetof(ksyms_buflist_t, buf))
  71 
  72 int nksyms_clones;              /* tunable: max clones of this device */
  73 
  74 static ksyms_image_t *ksyms_clones;     /* clone device array */
  75 static dev_info_t *ksyms_devi;
  76 
  77 static void
  78 ksyms_bcopy(const void *srcptr, void *ptr, size_t rsize)
  79 {
  80 
  81         size_t sz;
  82         const char *src = (const char *)srcptr;
  83         ksyms_buflist_hdr_t *hptr = (ksyms_buflist_hdr_t *)ptr;
  84 
  85         if (hptr->cur == NULL)
  86                 return;
  87 
  88         while (rsize) {
  89                 sz = MIN(rsize, (BUF_SIZE - hptr->curbuf_off));
  90                 bcopy(src, (hptr->cur->buf + hptr->curbuf_off), sz);
  91 
  92                 hptr->curbuf_off += sz;
  93                 if (hptr->curbuf_off == BUF_SIZE) {
  94                         hptr->curbuf_off = 0;
  95                         hptr->cur = list_next(&hptr->blist, hptr->cur);
  96                         if (hptr->cur == NULL)
  97                                 break;
  98                 }
  99                 src += sz;
 100                 rsize -= sz;
 101         }
 102 }
 103 
 104 static void
 105 ksyms_buflist_free(ksyms_buflist_hdr_t *hdr)
 106 {
 107         ksyms_buflist_t *list;
 108 
 109         while (list = list_head(&hdr->blist)) {
 110                 list_remove(&hdr->blist, list);
 111                 kmem_free(list, PAGESIZE);
 112         }
 113         list_destroy(&hdr->blist);
 114         hdr->cur = NULL;
 115 }
 116 
 117 
 118 /*
 119  * Allocate 'size'(rounded to BUF_SIZE) bytes in chunks of BUF_SIZE, and
 120  * add it to the buf list.
 121  * Returns the total size rounded to BUF_SIZE.
 122  */
 123 static size_t
 124 ksyms_buflist_alloc(ksyms_buflist_hdr_t *hdr, size_t size)
 125 {
 126         int chunks, i;
 127         ksyms_buflist_t *list;
 128 
 129         chunks = howmany(size, BUF_SIZE);
 130 
 131         if (hdr->nchunks >= chunks)
 132                 return (hdr->nchunks * BUF_SIZE);
 133 
 134         /*
 135          * Allocate chunks - hdr->nchunks buffers and add them to
 136          * the list.
 137          */
 138         for (i = chunks - hdr->nchunks; i > 0; i--) {
 139 
 140                 if ((list = kmem_alloc(PAGESIZE, KM_NOSLEEP)) == NULL)
 141                         break;
 142 
 143                 list_insert_tail(&hdr->blist, list);
 144         }
 145 
 146         /*
 147          * If we are running short of memory, free memory allocated till now
 148          * and return.
 149          */
 150         if (i > 0) {
 151                 ksyms_buflist_free(hdr);
 152                 return (0);
 153         }
 154 
 155         hdr->nchunks = chunks;
 156         hdr->cur = list_head(&hdr->blist);
 157         hdr->curbuf_off = 0;
 158 
 159         return (chunks * BUF_SIZE);
 160 }
 161 
 162 /*
 163  * rlen is in multiples of PAGESIZE
 164  */
 165 static char *
 166 ksyms_asmap(struct as *as, size_t rlen)
 167 {
 168         char *addr = NULL;
 169 
 170         as_rangelock(as);
 171         map_addr(&addr, rlen, 0, 1, 0);
 172         if (addr == NULL || as_map(as, addr, rlen, segvn_create, zfod_argsp)) {
 173                 as_rangeunlock(as);
 174                 return (NULL);
 175         }
 176         as_rangeunlock(as);
 177         return (addr);
 178 }
 179 
 180 static char *
 181 ksyms_mapin(ksyms_buflist_hdr_t *hdr, size_t size)
 182 {
 183         size_t sz, rlen = roundup(size, PAGESIZE);
 184         struct as *as = curproc->p_as;
 185         char *addr, *raddr;
 186         ksyms_buflist_t *list = list_head(&hdr->blist);
 187 
 188         if ((addr = ksyms_asmap(as, rlen)) == NULL)
 189                 return (NULL);
 190 
 191         raddr = addr;
 192         while (size > 0 && list != NULL) {
 193                 sz = MIN(size, BUF_SIZE);
 194 
 195                 if (copyout(list->buf, raddr, sz)) {
 196                         (void) as_unmap(as, addr, rlen);
 197                         return (NULL);
 198                 }
 199                 list = list_next(&hdr->blist, list);
 200                 raddr += sz;
 201                 size -= sz;
 202         }
 203         return (addr);
 204 }
 205 
 206 /*
 207  * Copy a snapshot of the kernel symbol table into the user's address space.
 208  * The symbol table is copied in fragments so that we do not have to
 209  * do a large kmem_alloc() which could fail/block if the kernel memory is
 210  * fragmented.
 211  */
 212 /* ARGSUSED */
 213 static int
 214 ksyms_open(dev_t *devp, int flag, int otyp, struct cred *cred)
 215 {
 216         minor_t clone;
 217         size_t size = 0;
 218         size_t realsize;
 219         char *addr;
 220         void *hptr = NULL;
 221         ksyms_buflist_hdr_t hdr;
 222         bzero(&hdr, sizeof (struct ksyms_buflist_hdr));
 223         list_create(&hdr.blist, PAGESIZE,
 224             offsetof(ksyms_buflist_t, buflist_node));
 225 
 226         if (getminor(*devp) != 0)
 227                 return (ENXIO);
 228 
 229         for (;;) {
 230                 realsize = ksyms_snapshot(ksyms_bcopy, hptr, size);
 231                 if (realsize <= size)
 232                         break;
 233                 size = realsize;
 234                 size = ksyms_buflist_alloc(&hdr, size);
 235                 if (size == 0)
 236                         return (ENOMEM);
 237                 hptr = (void *)&hdr;
 238         }
 239 
 240         addr = ksyms_mapin(&hdr, realsize);
 241         ksyms_buflist_free(&hdr);
 242         if (addr == NULL)
 243                 return (EOVERFLOW);
 244 
 245         /*
 246          * Reserve a clone entry.  Note that we don't use clone 0
 247          * since that's the "real" minor number.
 248          */
 249         for (clone = 1; clone < nksyms_clones; clone++) {
 250                 if (atomic_cas_ptr(&ksyms_clones[clone].ksyms_base, 0, addr) ==
 251                     0) {
 252                         ksyms_clones[clone].ksyms_size = realsize;
 253                         *devp = makedevice(getemajor(*devp), clone);
 254                         (void) ddi_prop_update_int(*devp, ksyms_devi,
 255                             "size", realsize);
 256                         modunload_disable();
 257                         return (0);
 258                 }
 259         }
 260         cmn_err(CE_NOTE, "ksyms: too many open references");
 261         (void) as_unmap(curproc->p_as, addr, roundup(realsize, PAGESIZE));
 262         return (ENXIO);
 263 }
 264 
 265 /* ARGSUSED */
 266 static int
 267 ksyms_close(dev_t dev, int flag, int otyp, struct cred *cred)
 268 {
 269         minor_t clone = getminor(dev);
 270 
 271         (void) as_unmap(curproc->p_as, ksyms_clones[clone].ksyms_base,
 272             roundup(ksyms_clones[clone].ksyms_size, PAGESIZE));
 273         ksyms_clones[clone].ksyms_base = 0;
 274         modunload_enable();
 275         (void) ddi_prop_remove(dev, ksyms_devi, "size");
 276         return (0);
 277 }
 278 
 279 static int
 280 ksyms_symtbl_copy(ksyms_image_t *kip, struct uio *uio, size_t len)
 281 {
 282         char *buf;
 283         int error = 0;
 284         caddr_t base;
 285         off_t off = uio->uio_offset;
 286         size_t size;
 287 
 288         /*
 289          * The symbol table is stored in the user address space,
 290          * so we have to copy it into the kernel first,
 291          * then copy it back out to the specified user address.
 292          */
 293         buf = kmem_alloc(PAGESIZE, KM_SLEEP);
 294         base = kip->ksyms_base + off;
 295         while (len) {
 296                 size = MIN(PAGESIZE, len);
 297                 if (copyin(base, buf, size))
 298                         error = EFAULT;
 299                 else
 300                         error = uiomove(buf, size, UIO_READ, uio);
 301 
 302                 if (error)
 303                         break;
 304 
 305                 len -= size;
 306                 base += size;
 307         }
 308         kmem_free(buf, PAGESIZE);
 309         return (error);
 310 }
 311 
 312 /* ARGSUSED */
 313 static int
 314 ksyms_read(dev_t dev, struct uio *uio, struct cred *cred)
 315 {
 316         ksyms_image_t *kip = &ksyms_clones[getminor(dev)];
 317         off_t off = uio->uio_offset;
 318         size_t len = uio->uio_resid;
 319 
 320         if (off < 0 || off > kip->ksyms_size)
 321                 return (EFAULT);
 322 
 323         if (len > kip->ksyms_size - off)
 324                 len = kip->ksyms_size - off;
 325 
 326         if (len == 0)
 327                 return (0);
 328 
 329         return (ksyms_symtbl_copy(kip, uio, len));
 330 }
 331 
 332 /* ARGSUSED */
 333 static int
 334 ksyms_segmap(dev_t dev, off_t off, struct as *as, caddr_t *addrp, off_t len,
 335     uint_t prot, uint_t maxprot, uint_t flags, struct cred *cred)
 336 {
 337         ksyms_image_t *kip = &ksyms_clones[getminor(dev)];
 338         int error = 0;
 339         char *addr = NULL;
 340         size_t rlen = 0;
 341         struct iovec aiov;
 342         struct uio auio;
 343 
 344         if (flags & MAP_FIXED)
 345                 return (ENOTSUP);
 346 
 347         if (off < 0 || len <= 0 || off > kip->ksyms_size ||
 348             len > kip->ksyms_size - off)
 349                 return (EINVAL);
 350 
 351         rlen = roundup(len, PAGESIZE);
 352         if ((addr = ksyms_asmap(as, rlen)) == NULL)
 353                 return (EOVERFLOW);
 354 
 355         aiov.iov_base = addr;
 356         aiov.iov_len = len;
 357         auio.uio_offset = off;
 358         auio.uio_iov = &aiov;
 359         auio.uio_iovcnt = 1;
 360         auio.uio_resid = len;
 361         auio.uio_segflg = UIO_USERSPACE;
 362         auio.uio_llimit = MAXOFFSET_T;
 363         auio.uio_fmode = FREAD;
 364         auio.uio_extflg = UIO_COPY_CACHED;
 365 
 366         error = ksyms_symtbl_copy(kip, &auio, len);
 367 
 368         if (error)
 369                 (void) as_unmap(as, addr, rlen);
 370         else
 371                 *addrp = addr;
 372         return (error);
 373 }
 374 
 375 /* ARGSUSED */
 376 static int
 377 ksyms_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
 378 {
 379         switch (infocmd) {
 380         case DDI_INFO_DEVT2DEVINFO:
 381                 *result = ksyms_devi;
 382                 return (DDI_SUCCESS);
 383         case DDI_INFO_DEVT2INSTANCE:
 384                 *result = 0;
 385                 return (DDI_SUCCESS);
 386         }
 387         return (DDI_FAILURE);
 388 }
 389 
 390 static int
 391 ksyms_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
 392 {
 393         if (cmd != DDI_ATTACH)
 394                 return (DDI_FAILURE);
 395         if (ddi_create_minor_node(devi, "ksyms", S_IFCHR, 0, DDI_PSEUDO, NULL)
 396             == DDI_FAILURE) {
 397                 ddi_remove_minor_node(devi, NULL);
 398                 return (DDI_FAILURE);
 399         }
 400         ksyms_devi = devi;
 401         return (DDI_SUCCESS);
 402 }
 403 
 404 static int
 405 ksyms_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
 406 {
 407         if (cmd != DDI_DETACH)
 408                 return (DDI_FAILURE);
 409         ddi_remove_minor_node(devi, NULL);
 410         return (DDI_SUCCESS);
 411 }
 412 
 413 static struct cb_ops ksyms_cb_ops = {
 414         ksyms_open,             /* open */
 415         ksyms_close,            /* close */
 416         nodev,                  /* strategy */
 417         nodev,                  /* print */
 418         nodev,                  /* dump */
 419         ksyms_read,             /* read */
 420         nodev,                  /* write */
 421         nodev,                  /* ioctl */
 422         nodev,                  /* devmap */
 423         nodev,                  /* mmap */
 424         ksyms_segmap,           /* segmap */
 425         nochpoll,               /* poll */
 426         ddi_prop_op,            /* prop_op */
 427         0,                      /* streamtab  */
 428         D_NEW | D_MP            /* Driver compatibility flag */
 429 };
 430 
 431 static struct dev_ops ksyms_ops = {
 432         DEVO_REV,               /* devo_rev, */
 433         0,                      /* refcnt  */
 434         ksyms_info,             /* info */
 435         nulldev,                /* identify */
 436         nulldev,                /* probe */
 437         ksyms_attach,           /* attach */
 438         ksyms_detach,           /* detach */
 439         nodev,                  /* reset */
 440         &ksyms_cb_ops,              /* driver operations */
 441         (struct bus_ops *)0,    /* no bus operations */
 442         NULL,                   /* power */
 443         ddi_quiesce_not_needed,         /* quiesce */
 444 };
 445 
 446 static struct modldrv modldrv = {
 447         &mod_driverops, "kernel symbols driver", &ksyms_ops,
 448 };
 449 
 450 static struct modlinkage modlinkage = {
 451         MODREV_1, { (void *)&modldrv }
 452 };
 453 
 454 int
 455 _init(void)
 456 {
 457         int error;
 458 
 459         if (nksyms_clones == 0)
 460                 nksyms_clones = maxusers + 50;
 461 
 462         ksyms_clones = kmem_zalloc(nksyms_clones *
 463             sizeof (ksyms_image_t), KM_SLEEP);
 464 
 465         if ((error = mod_install(&modlinkage)) != 0)
 466                 kmem_free(ksyms_clones, nksyms_clones * sizeof (ksyms_image_t));
 467 
 468         return (error);
 469 }
 470 
 471 int
 472 _fini(void)
 473 {
 474         int error;
 475 
 476         if ((error = mod_remove(&modlinkage)) == 0)
 477                 kmem_free(ksyms_clones, nksyms_clones * sizeof (ksyms_image_t));
 478         return (error);
 479 }
 480 
 481 int
 482 _info(struct modinfo *modinfop)
 483 {
 484         return (mod_info(&modlinkage, modinfop));
 485 }