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 (casptr(&ksyms_clones[clone].ksyms_base, 0, addr) == 0) {
 251                         ksyms_clones[clone].ksyms_size = realsize;
 252                         *devp = makedevice(getemajor(*devp), clone);
 253                         (void) ddi_prop_update_int(*devp, ksyms_devi,
 254                             "size", realsize);
 255                         modunload_disable();
 256                         return (0);
 257                 }
 258         }
 259         cmn_err(CE_NOTE, "ksyms: too many open references");
 260         (void) as_unmap(curproc->p_as, addr, roundup(realsize, PAGESIZE));
 261         return (ENXIO);
 262 }
 263 
 264 /* ARGSUSED */
 265 static int
 266 ksyms_close(dev_t dev, int flag, int otyp, struct cred *cred)
 267 {
 268         minor_t clone = getminor(dev);
 269 
 270         (void) as_unmap(curproc->p_as, ksyms_clones[clone].ksyms_base,
 271             roundup(ksyms_clones[clone].ksyms_size, PAGESIZE));
 272         ksyms_clones[clone].ksyms_base = 0;
 273         modunload_enable();
 274         (void) ddi_prop_remove(dev, ksyms_devi, "size");
 275         return (0);
 276 }
 277 
 278 static int
 279 ksyms_symtbl_copy(ksyms_image_t *kip, struct uio *uio, size_t len)
 280 {
 281         char *buf;
 282         int error = 0;
 283         caddr_t base;
 284         off_t off = uio->uio_offset;
 285         size_t size;
 286 
 287         /*
 288          * The symbol table is stored in the user address space,
 289          * so we have to copy it into the kernel first,
 290          * then copy it back out to the specified user address.
 291          */
 292         buf = kmem_alloc(PAGESIZE, KM_SLEEP);
 293         base = kip->ksyms_base + off;
 294         while (len) {
 295                 size = MIN(PAGESIZE, len);
 296                 if (copyin(base, buf, size))
 297                         error = EFAULT;
 298                 else
 299                         error = uiomove(buf, size, UIO_READ, uio);
 300 
 301                 if (error)
 302                         break;
 303 
 304                 len -= size;
 305                 base += size;
 306         }
 307         kmem_free(buf, PAGESIZE);
 308         return (error);
 309 }
 310 
 311 /* ARGSUSED */
 312 static int
 313 ksyms_read(dev_t dev, struct uio *uio, struct cred *cred)
 314 {
 315         ksyms_image_t *kip = &ksyms_clones[getminor(dev)];
 316         off_t off = uio->uio_offset;
 317         size_t len = uio->uio_resid;
 318 
 319         if (off < 0 || off > kip->ksyms_size)
 320                 return (EFAULT);
 321 
 322         if (len > kip->ksyms_size - off)
 323                 len = kip->ksyms_size - off;
 324 
 325         if (len == 0)
 326                 return (0);
 327 
 328         return (ksyms_symtbl_copy(kip, uio, len));
 329 }
 330 
 331 /* ARGSUSED */
 332 static int
 333 ksyms_segmap(dev_t dev, off_t off, struct as *as, caddr_t *addrp, off_t len,
 334     uint_t prot, uint_t maxprot, uint_t flags, struct cred *cred)
 335 {
 336         ksyms_image_t *kip = &ksyms_clones[getminor(dev)];
 337         int error = 0;
 338         char *addr = NULL;
 339         size_t rlen = 0;
 340         struct iovec aiov;
 341         struct uio auio;
 342 
 343         if (flags & MAP_FIXED)
 344                 return (ENOTSUP);
 345 
 346         if (off < 0 || len <= 0 || off > kip->ksyms_size ||
 347             len > kip->ksyms_size - off)
 348                 return (EINVAL);
 349 
 350         rlen = roundup(len, PAGESIZE);
 351         if ((addr = ksyms_asmap(as, rlen)) == NULL)
 352                 return (EOVERFLOW);
 353 
 354         aiov.iov_base = addr;
 355         aiov.iov_len = len;
 356         auio.uio_offset = off;
 357         auio.uio_iov = &aiov;
 358         auio.uio_iovcnt = 1;
 359         auio.uio_resid = len;
 360         auio.uio_segflg = UIO_USERSPACE;
 361         auio.uio_llimit = MAXOFFSET_T;
 362         auio.uio_fmode = FREAD;
 363         auio.uio_extflg = UIO_COPY_CACHED;
 364 
 365         error = ksyms_symtbl_copy(kip, &auio, len);
 366 
 367         if (error)
 368                 (void) as_unmap(as, addr, rlen);
 369         else
 370                 *addrp = addr;
 371         return (error);
 372 }
 373 
 374 /* ARGSUSED */
 375 static int
 376 ksyms_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
 377 {
 378         switch (infocmd) {
 379         case DDI_INFO_DEVT2DEVINFO:
 380                 *result = ksyms_devi;
 381                 return (DDI_SUCCESS);
 382         case DDI_INFO_DEVT2INSTANCE:
 383                 *result = 0;
 384                 return (DDI_SUCCESS);
 385         }
 386         return (DDI_FAILURE);
 387 }
 388 
 389 static int
 390 ksyms_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
 391 {
 392         if (cmd != DDI_ATTACH)
 393                 return (DDI_FAILURE);
 394         if (ddi_create_minor_node(devi, "ksyms", S_IFCHR, 0, DDI_PSEUDO, NULL)
 395             == DDI_FAILURE) {
 396                 ddi_remove_minor_node(devi, NULL);
 397                 return (DDI_FAILURE);
 398         }
 399         ksyms_devi = devi;
 400         return (DDI_SUCCESS);
 401 }
 402 
 403 static int
 404 ksyms_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
 405 {
 406         if (cmd != DDI_DETACH)
 407                 return (DDI_FAILURE);
 408         ddi_remove_minor_node(devi, NULL);
 409         return (DDI_SUCCESS);
 410 }
 411 
 412 static struct cb_ops ksyms_cb_ops = {
 413         ksyms_open,             /* open */
 414         ksyms_close,            /* close */
 415         nodev,                  /* strategy */
 416         nodev,                  /* print */
 417         nodev,                  /* dump */
 418         ksyms_read,             /* read */
 419         nodev,                  /* write */
 420         nodev,                  /* ioctl */
 421         nodev,                  /* devmap */
 422         nodev,                  /* mmap */
 423         ksyms_segmap,           /* segmap */
 424         nochpoll,               /* poll */
 425         ddi_prop_op,            /* prop_op */
 426         0,                      /* streamtab  */
 427         D_NEW | D_MP            /* Driver compatibility flag */
 428 };
 429 
 430 static struct dev_ops ksyms_ops = {
 431         DEVO_REV,               /* devo_rev, */
 432         0,                      /* refcnt  */
 433         ksyms_info,             /* info */
 434         nulldev,                /* identify */
 435         nulldev,                /* probe */
 436         ksyms_attach,           /* attach */
 437         ksyms_detach,           /* detach */
 438         nodev,                  /* reset */
 439         &ksyms_cb_ops,              /* driver operations */
 440         (struct bus_ops *)0,    /* no bus operations */
 441         NULL,                   /* power */
 442         ddi_quiesce_not_needed,         /* quiesce */
 443 };
 444 
 445 static struct modldrv modldrv = {
 446         &mod_driverops, "kernel symbols driver", &ksyms_ops,
 447 };
 448 
 449 static struct modlinkage modlinkage = {
 450         MODREV_1, { (void *)&modldrv }
 451 };
 452 
 453 int
 454 _init(void)
 455 {
 456         int error;
 457 
 458         if (nksyms_clones == 0)
 459                 nksyms_clones = maxusers + 50;
 460 
 461         ksyms_clones = kmem_zalloc(nksyms_clones *
 462             sizeof (ksyms_image_t), KM_SLEEP);
 463 
 464         if ((error = mod_install(&modlinkage)) != 0)
 465                 kmem_free(ksyms_clones, nksyms_clones * sizeof (ksyms_image_t));
 466 
 467         return (error);
 468 }
 469 
 470 int
 471 _fini(void)
 472 {
 473         int error;
 474 
 475         if ((error = mod_remove(&modlinkage)) == 0)
 476                 kmem_free(ksyms_clones, nksyms_clones * sizeof (ksyms_image_t));
 477         return (error);
 478 }
 479 
 480 int
 481 _info(struct modinfo *modinfop)
 482 {
 483         return (mod_info(&modlinkage, modinfop));
 484 }