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 } 485