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 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 /* 29 * ksyms driver - exports a single symbol/string table for the kernel 30 * by concatenating all the module symbol/string tables. 31 */ 32 33 #include <sys/types.h> 34 #include <sys/sysmacros.h> 35 #include <sys/cmn_err.h> 36 #include <sys/uio.h> 37 #include <sys/kmem.h> 38 #include <sys/cred.h> 39 #include <sys/mman.h> 40 #include <sys/errno.h> 41 #include <sys/stat.h> 42 #include <sys/conf.h> 43 #include <sys/debug.h> 44 #include <sys/kobj.h> 45 #include <sys/ksyms.h> 46 #include <sys/vmsystm.h> 47 #include <vm/seg_vn.h> 48 #include <sys/atomic.h> 49 #include <sys/compress.h> 50 #include <sys/ddi.h> 51 #include <sys/sunddi.h> 52 #include <sys/list.h> 53 54 typedef struct ksyms_image { 55 caddr_t ksyms_base; /* base address of image */ 56 size_t ksyms_size; /* size of image */ 57 } ksyms_image_t; 58 59 typedef struct ksyms_buflist { 60 list_node_t buflist_node; 61 char buf[1]; 62 } ksyms_buflist_t; 63 64 typedef struct ksyms_buflist_hdr { 65 list_t blist; 66 int nchunks; 67 ksyms_buflist_t *cur; 68 size_t curbuf_off; 69 } ksyms_buflist_hdr_t; 70 71 #define BUF_SIZE (PAGESIZE - (size_t)offsetof(ksyms_buflist_t, buf)) 72 73 int nksyms_clones; /* tunable: max clones of this device */ 74 75 static ksyms_image_t *ksyms_clones; /* clone device array */ 76 static dev_info_t *ksyms_devi; 77 78 static void 79 ksyms_bcopy(const void *srcptr, void *ptr, size_t rsize) 80 { 81 82 size_t sz; 83 const char *src = (const char *)srcptr; 84 ksyms_buflist_hdr_t *hptr = (ksyms_buflist_hdr_t *)ptr; 85 86 if (hptr->cur == NULL) 87 return; 88 89 while (rsize) { 90 sz = MIN(rsize, (BUF_SIZE - hptr->curbuf_off)); 91 bcopy(src, (hptr->cur->buf + hptr->curbuf_off), sz); 92 93 hptr->curbuf_off += sz; 94 if (hptr->curbuf_off == BUF_SIZE) { 95 hptr->curbuf_off = 0; 96 hptr->cur = list_next(&hptr->blist, hptr->cur); 97 if (hptr->cur == NULL) 98 break; 99 } 100 src += sz; 101 rsize -= sz; 102 } 103 } 104 105 static void 106 ksyms_buflist_free(ksyms_buflist_hdr_t *hdr) 107 { 108 ksyms_buflist_t *list; 109 110 while (list = list_head(&hdr->blist)) { 111 list_remove(&hdr->blist, list); 112 kmem_free(list, PAGESIZE); 113 } 114 list_destroy(&hdr->blist); 115 hdr->cur = NULL; 116 } 117 118 119 /* 120 * Allocate 'size'(rounded to BUF_SIZE) bytes in chunks of BUF_SIZE, and 121 * add it to the buf list. 122 * Returns the total size rounded to BUF_SIZE. 123 */ 124 static size_t 125 ksyms_buflist_alloc(ksyms_buflist_hdr_t *hdr, size_t size) 126 { 127 int chunks, i; 128 ksyms_buflist_t *list; 129 130 chunks = howmany(size, BUF_SIZE); 131 132 if (hdr->nchunks >= chunks) 133 return (hdr->nchunks * BUF_SIZE); 134 135 /* 136 * Allocate chunks - hdr->nchunks buffers and add them to 137 * the list. 138 */ 139 for (i = chunks - hdr->nchunks; i > 0; i--) { 140 141 if ((list = kmem_alloc(PAGESIZE, KM_NOSLEEP)) == NULL) 142 break; 143 144 list_insert_tail(&hdr->blist, list); 145 } 146 147 /* 148 * If we are running short of memory, free memory allocated till now 149 * and return. 150 */ 151 if (i > 0) { 152 ksyms_buflist_free(hdr); 153 return (0); 154 } 155 156 hdr->nchunks = chunks; 157 hdr->cur = list_head(&hdr->blist); 158 hdr->curbuf_off = 0; 159 160 return (chunks * BUF_SIZE); 161 } 162 163 /* 164 * rlen is in multiples of PAGESIZE 165 */ 166 static char * 167 ksyms_asmap(struct as *as, size_t rlen) 168 { 169 char *addr = NULL; 170 171 as_rangelock(as); 172 map_addr(&addr, rlen, 0, 1, 0); 173 if (addr == NULL || as_map(as, addr, rlen, segvn_create, zfod_argsp)) { 174 as_rangeunlock(as); 175 return (NULL); 176 } 177 as_rangeunlock(as); 178 return (addr); 179 } 180 181 static char * 182 ksyms_mapin(ksyms_buflist_hdr_t *hdr, size_t size) 183 { 184 size_t sz, rlen = roundup(size, PAGESIZE); 185 struct as *as = curproc->p_as; 186 char *addr, *raddr; 187 ksyms_buflist_t *list = list_head(&hdr->blist); 188 189 if ((addr = ksyms_asmap(as, rlen)) == NULL) 190 return (NULL); 191 192 raddr = addr; 193 while (size > 0 && list != NULL) { 194 sz = MIN(size, BUF_SIZE); 195 196 if (copyout(list->buf, raddr, sz)) { 197 (void) as_unmap(as, addr, rlen); 198 return (NULL); 199 } 200 list = list_next(&hdr->blist, list); 201 raddr += sz; 202 size -= sz; 203 } 204 return (addr); 205 } 206 207 /* 208 * Copy a snapshot of the kernel symbol table into the user's address space. 209 * The symbol table is copied in fragments so that we do not have to 210 * do a large kmem_alloc() which could fail/block if the kernel memory is 211 * fragmented. 212 */ 213 /* ARGSUSED */ 214 static int 215 ksyms_open(dev_t *devp, int flag, int otyp, struct cred *cred) 216 { 217 minor_t clone; 218 size_t size = 0; 219 size_t realsize; 220 char *addr; 221 void *hptr = NULL; 222 ksyms_buflist_hdr_t hdr; 223 bzero(&hdr, sizeof (struct ksyms_buflist_hdr)); 224 list_create(&hdr.blist, PAGESIZE, 225 offsetof(ksyms_buflist_t, buflist_node)); 226 227 ASSERT(getminor(*devp) == 0); 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 }; 442 443 static struct modldrv modldrv = { 444 &mod_driverops, "kernel symbols driver %I%", &ksyms_ops, 445 }; 446 447 static struct modlinkage modlinkage = { 448 MODREV_1, { (void *)&modldrv } 449 }; 450 451 int 452 _init(void) 453 { 454 int error; 455 456 if (nksyms_clones == 0) 457 nksyms_clones = maxusers + 50; 458 459 ksyms_clones = kmem_zalloc(nksyms_clones * 460 sizeof (ksyms_image_t), KM_SLEEP); 461 462 if ((error = mod_install(&modlinkage)) != 0) 463 kmem_free(ksyms_clones, nksyms_clones * sizeof (ksyms_image_t)); 464 465 return (error); 466 } 467 468 int 469 _fini(void) 470 { 471 int error; 472 473 if ((error = mod_remove(&modlinkage)) == 0) 474 kmem_free(ksyms_clones, nksyms_clones * sizeof (ksyms_image_t)); 475 return (error); 476 } 477 478 int 479 _info(struct modinfo *modinfop) 480 { 481 return (mod_info(&modlinkage, modinfop)); 482 } 483