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