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
ksyms_bcopy(const void * srcptr,void * ptr,size_t rsize)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
ksyms_buflist_free(ksyms_buflist_hdr_t * hdr)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
ksyms_buflist_alloc(ksyms_buflist_hdr_t * hdr,size_t size)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 *
ksyms_asmap(struct as * as,size_t rlen)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 *
ksyms_mapin(ksyms_buflist_hdr_t * hdr,size_t size)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
ksyms_open(dev_t * devp,int flag,int otyp,struct cred * cred)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
ksyms_close(dev_t dev,int flag,int otyp,struct cred * cred)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
ksyms_symtbl_copy(ksyms_image_t * kip,struct uio * uio,size_t len)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
ksyms_read(dev_t dev,struct uio * uio,struct cred * cred)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
ksyms_segmap(dev_t dev,off_t off,struct as * as,caddr_t * addrp,off_t len,uint_t prot,uint_t maxprot,uint_t flags,struct cred * cred)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
ksyms_info(dev_info_t * dip,ddi_info_cmd_t infocmd,void * arg,void ** result)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
ksyms_attach(dev_info_t * devi,ddi_attach_cmd_t cmd)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
ksyms_detach(dev_info_t * devi,ddi_detach_cmd_t cmd)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
_init(void)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
_fini(void)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
_info(struct modinfo * modinfop)481 _info(struct modinfo *modinfop)
482 {
483 return (mod_info(&modlinkage, modinfop));
484 }
485