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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 /* 30 * ksyms driver - exports a single symbol/string table for the kernel 31 * by concatenating all the module symbol/string tables. 32 */ 33 34 #include <sys/types.h> 35 #include <sys/sysmacros.h> 36 #include <sys/cmn_err.h> 37 #include <sys/uio.h> 38 #include <sys/kmem.h> 39 #include <sys/cred.h> 40 #include <sys/mman.h> 41 #include <sys/errno.h> 42 #include <sys/stat.h> 43 #include <sys/conf.h> 44 #include <sys/debug.h> 45 #include <sys/kobj.h> 46 #include <sys/ksyms.h> 47 #include <sys/vmsystm.h> 48 #include <vm/seg_vn.h> 49 #include <sys/atomic.h> 50 #include <sys/compress.h> 51 #include <sys/ddi.h> 52 #include <sys/sunddi.h> 53 #include <sys/list.h> 54 55 typedef struct ksyms_image { 56 caddr_t ksyms_base; /* base address of image */ 57 size_t ksyms_size; /* size of image */ 58 } ksyms_image_t; 59 60 typedef struct ksyms_buflist { 61 list_node_t buflist_node; 62 char buf[1]; 63 } ksyms_buflist_t; 64 65 typedef struct ksyms_buflist_hdr { 66 list_t blist; 67 int nchunks; 68 ksyms_buflist_t *cur; 69 size_t curbuf_off; 70 } ksyms_buflist_hdr_t; 71 72 #define BUF_SIZE (PAGESIZE - (size_t)offsetof(ksyms_buflist_t, buf)) 73 74 int nksyms_clones; /* tunable: max clones of this device */ 75 76 static ksyms_image_t *ksyms_clones; /* clone device array */ 77 static dev_info_t *ksyms_devi; 78 79 static void 80 ksyms_bcopy(const void *srcptr, void *ptr, size_t rsize) 81 { 82 83 size_t sz; 84 const char *src = (const char *)srcptr; 85 ksyms_buflist_hdr_t *hptr = (ksyms_buflist_hdr_t *)ptr; 86 87 if (hptr->cur == NULL) 88 return; 89 90 while (rsize) { 91 sz = MIN(rsize, (BUF_SIZE - hptr->curbuf_off)); 92 bcopy(src, (hptr->cur->buf + hptr->curbuf_off), sz); 93 94 hptr->curbuf_off += sz; 95 if (hptr->curbuf_off == BUF_SIZE) { 96 hptr->curbuf_off = 0; 97 hptr->cur = list_next(&hptr->blist, hptr->cur); 98 if (hptr->cur == NULL) 99 break; 100 } 101 src += sz; 102 rsize -= sz; 103 } 104 } 105 106 static void 107 ksyms_buflist_free(ksyms_buflist_hdr_t *hdr) 108 { 109 ksyms_buflist_t *list; 110 111 while (list = list_head(&hdr->blist)) { 112 list_remove(&hdr->blist, list); 113 kmem_free(list, PAGESIZE); 114 } 115 list_destroy(&hdr->blist); 116 hdr->cur = NULL; 117 } 118 119 120 /* 121 * Allocate 'size'(rounded to BUF_SIZE) bytes in chunks of BUF_SIZE, and 122 * add it to the buf list. 123 * Returns the total size rounded to BUF_SIZE. 124 */ 125 static size_t 126 ksyms_buflist_alloc(ksyms_buflist_hdr_t *hdr, size_t size) 127 { 128 int chunks, i; 129 ksyms_buflist_t *list; 130 131 chunks = howmany(size, BUF_SIZE); 132 133 if (hdr->nchunks >= chunks) 134 return (hdr->nchunks * BUF_SIZE); 135 136 /* 137 * Allocate chunks - hdr->nchunks buffers and add them to 138 * the list. 139 */ 140 for (i = chunks - hdr->nchunks; i > 0; i--) { 141 142 if ((list = kmem_alloc(PAGESIZE, KM_NOSLEEP)) == NULL) 143 break; 144 145 list_insert_tail(&hdr->blist, list); 146 } 147 148 /* 149 * If we are running short of memory, free memory allocated till now 150 * and return. 151 */ 152 if (i > 0) { 153 ksyms_buflist_free(hdr); 154 return (0); 155 } 156 157 hdr->nchunks = chunks; 158 hdr->cur = list_head(&hdr->blist); 159 hdr->curbuf_off = 0; 160 161 return (chunks * BUF_SIZE); 162 } 163 164 /* 165 * rlen is in multiples of PAGESIZE 166 */ 167 static char * 168 ksyms_asmap(struct as *as, size_t rlen) 169 { 170 char *addr; 171 172 as_rangelock(as); 173 map_addr(&addr, rlen, 0, 1, 0); 174 if (addr == NULL || as_map(as, addr, rlen, segvn_create, zfod_argsp)) { 175 as_rangeunlock(as); 176 return (NULL); 177 } 178 as_rangeunlock(as); 179 return (addr); 180 } 181 182 static char * 183 ksyms_mapin(ksyms_buflist_hdr_t *hdr, size_t size) 184 { 185 size_t sz, rlen = roundup(size, PAGESIZE); 186 struct as *as = curproc->p_as; 187 char *addr, *raddr; 188 ksyms_buflist_t *list = list_head(&hdr->blist); 189 190 if ((addr = ksyms_asmap(as, rlen)) == NULL) 191 return (NULL); 192 193 raddr = addr; 194 while (size > 0 && list != NULL) { 195 sz = MIN(size, BUF_SIZE); 196 197 if (copyout(list->buf, raddr, sz)) { 198 (void) as_unmap(as, addr, rlen); 199 return (NULL); 200 } 201 list = list_next(&hdr->blist, list); 202 raddr += sz; 203 size -= sz; 204 } 205 return (addr); 206 } 207 208 /* 209 * Copy a snapshot of the kernel symbol table into the user's address space. 210 * The symbol table is copied in fragments so that we do not have to 211 * do a large kmem_alloc() which could fail/block if the kernel memory is 212 * fragmented. 213 */ 214 /* ARGSUSED */ 215 static int 216 ksyms_open(dev_t *devp, int flag, int otyp, struct cred *cred) 217 { 218 minor_t clone; 219 size_t size = 0; 220 size_t realsize; 221 char *addr; 222 void *hptr = NULL; 223 ksyms_buflist_hdr_t hdr; 224 bzero(&hdr, sizeof (struct ksyms_buflist_hdr)); 225 list_create(&hdr.blist, PAGESIZE, 226 offsetof(ksyms_buflist_t, buflist_node)); 227 228 ASSERT(getminor(*devp) == 0); 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