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