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 if (getminor(*devp) != 0) 228 return (ENXIO); 229 230 for (;;) { 231 realsize = ksyms_snapshot(ksyms_bcopy, hptr, size); 232 if (realsize <= size) 233 break; 234 size = realsize; 235 size = ksyms_buflist_alloc(&hdr, size); 236 if (size == 0) 237 return (ENOMEM); 238 hptr = (void *)&hdr; 239 } 240 241 addr = ksyms_mapin(&hdr, realsize); 242 ksyms_buflist_free(&hdr); 243 if (addr == NULL) 244 return (EOVERFLOW); 245 246 /* 247 * Reserve a clone entry. Note that we don't use clone 0 248 * since that's the "real" minor number. 249 */ 250 for (clone = 1; clone < nksyms_clones; clone++) { 251 if (casptr(&ksyms_clones[clone].ksyms_base, 0, addr) == 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 }; 443 444 static struct modldrv modldrv = { 445 &mod_driverops, "kernel symbols driver %I%", &ksyms_ops, 446 }; 447 448 static struct modlinkage modlinkage = { 449 MODREV_1, { (void *)&modldrv } 450 }; 451 452 int 453 _init(void) 454 { 455 int error; 456 457 if (nksyms_clones == 0) 458 nksyms_clones = maxusers + 50; 459 460 ksyms_clones = kmem_zalloc(nksyms_clones * 461 sizeof (ksyms_image_t), KM_SLEEP); 462 463 if ((error = mod_install(&modlinkage)) != 0) 464 kmem_free(ksyms_clones, nksyms_clones * sizeof (ksyms_image_t)); 465 466 return (error); 467 } 468 469 int 470 _fini(void) 471 { 472 int error; 473 474 if ((error = mod_remove(&modlinkage)) == 0) 475 kmem_free(ksyms_clones, nksyms_clones * sizeof (ksyms_image_t)); 476 return (error); 477 } 478 479 int 480 _info(struct modinfo *modinfop) 481 { 482 return (mod_info(&modlinkage, modinfop)); 483 } 484