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 2006 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#pragma ident	"%Z%%M%	%I%	%E% SMI"
27
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <fcntl.h>
32#include <unistd.h>
33#include <string.h>
34#include <errno.h>
35#include <limits.h>
36
37#include "libproc.h"
38#include "Pcontrol.h"
39#include "Pisadep.h"
40#include "Putil.h"
41
42#define	BLKSIZE	(8 * 1024)
43
44/*
45 * Look for a SYSCALL instruction in the process's address space.
46 */
47int
48Pscantext(struct ps_prochandle *P)
49{
50	char mapfile[PATH_MAX];
51	int mapfd;
52	off_t offset;		/* offset in text section */
53	off_t endoff;		/* ending offset in text section */
54	uintptr_t sysaddr;	/* address of SYSCALL instruction */
55	int syspri;		/* priority of SYSCALL instruction */
56	int nbytes;		/* number of bytes in buffer */
57	int n2bytes;		/* number of bytes in second buffer */
58	int nmappings;		/* current number of mappings */
59	prmap_t *pdp;		/* pointer to map descriptor */
60	prmap_t *prbuf;		/* buffer for map descriptors */
61	unsigned nmap;		/* number of map descriptors */
62	uint32_t buf[2 * BLKSIZE / sizeof (uint32_t)];	/* text buffer */
63	uchar_t *p;
64
65	/* try the most recently-seen syscall address */
66	syspri = 0;
67	sysaddr = 0;
68	if (P->sysaddr != 0 &&
69	    (syspri = Pissyscall(P, P->sysaddr)))
70		sysaddr = P->sysaddr;
71
72	/* try the previous instruction */
73	if (sysaddr == 0 || syspri != 1)
74		syspri = Pissyscall_prev(P, P->status.pr_lwp.pr_reg[R_PC],
75		    &sysaddr);
76
77	if (sysaddr != 0 && syspri == 1) {
78		P->sysaddr = sysaddr;
79		return (0);
80	}
81
82	/* open the /proc/<pid>/map file */
83	(void) snprintf(mapfile, sizeof (mapfile), "%s/%d/map",
84	    procfs_path, (int)P->pid);
85	if ((mapfd = open(mapfile, O_RDONLY)) < 0) {
86		dprintf("failed to open %s: %s\n", mapfile, strerror(errno));
87		return (-1);
88	}
89
90	/* allocate a plausible initial buffer size */
91	nmap = 50;
92
93	/* read all the map structures, allocating more space as needed */
94	for (;;) {
95		prbuf = malloc(nmap * sizeof (prmap_t));
96		if (prbuf == NULL) {
97			dprintf("Pscantext: failed to allocate buffer\n");
98			(void) close(mapfd);
99			return (-1);
100		}
101		nmappings = pread(mapfd, prbuf, nmap * sizeof (prmap_t), 0L);
102		if (nmappings < 0) {
103			dprintf("Pscantext: failed to read map file: %s\n",
104			    strerror(errno));
105			free(prbuf);
106			(void) close(mapfd);
107			return (-1);
108		}
109		nmappings /= sizeof (prmap_t);
110		if (nmappings < nmap)	/* we read them all */
111			break;
112		/* allocate a bigger buffer */
113		free(prbuf);
114		nmap *= 2;
115	}
116	(void) close(mapfd);
117
118	/*
119	 * Scan each executable mapping looking for a syscall instruction.
120	 * In dynamically linked executables, syscall instructions are
121	 * typically only found in shared libraries.  Because shared libraries
122	 * are most often mapped at the top of the address space, we minimize
123	 * our expected search time by starting at the last mapping and working
124	 * our way down to the first mapping.
125	 */
126	for (pdp = &prbuf[nmappings - 1]; sysaddr == 0 && syspri != 1 &&
127	    pdp >= prbuf; pdp--) {
128
129		offset = (off_t)pdp->pr_vaddr;	/* beginning of text */
130		endoff = offset + pdp->pr_size;
131
132		/* avoid non-EXEC mappings; avoid the stack and heap */
133		if ((pdp->pr_mflags&MA_EXEC) == 0 ||
134		    (endoff > P->status.pr_stkbase &&
135		    offset < P->status.pr_stkbase + P->status.pr_stksize) ||
136		    (endoff > P->status.pr_brkbase &&
137		    offset < P->status.pr_brkbase + P->status.pr_brksize))
138			continue;
139
140		(void) lseek(P->asfd, (off_t)offset, 0);
141
142		if ((nbytes = read(P->asfd, buf, 2*BLKSIZE)) <= 0)
143			continue;
144
145		if (nbytes < BLKSIZE)
146			n2bytes = 0;
147		else {
148			n2bytes = nbytes - BLKSIZE;
149			nbytes  = BLKSIZE;
150		}
151
152		p = (uchar_t *)buf;
153
154		/* search text for a SYSCALL instruction */
155		while (sysaddr == 0 && syspri != 1 && offset < endoff) {
156			if (nbytes <= 0) {	/* shift buffers */
157				if ((nbytes = n2bytes) <= 0)
158					break;
159				(void) memcpy(buf,
160					&buf[BLKSIZE / sizeof (buf[0])],
161					nbytes);
162				n2bytes = 0;
163				p = (uchar_t *)buf;
164				if (nbytes == BLKSIZE &&
165				    offset + BLKSIZE < endoff)
166					n2bytes = read(P->asfd,
167						&buf[BLKSIZE / sizeof (buf[0])],
168						BLKSIZE);
169			}
170
171			if (syspri = Pissyscall_text(P, p, nbytes))
172				sysaddr = offset;
173
174			p += sizeof (instr_t);
175			offset += sizeof (instr_t);
176			nbytes -= sizeof (instr_t);
177		}
178	}
179
180	free(prbuf);
181
182	if ((P->sysaddr = sysaddr) != 0)
183		return (0);
184	else
185		return (-1);
186}
187