xref: /illumos-gate/usr/src/uts/common/io/ksyms.c (revision edb348833aaacfa1176e502ad38875fd0b2717ab)
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