xref: /illumos-gate/usr/src/lib/libsaveargs/amd64/saveargs.c (revision 3ba944265c4ae1fcf23ef758537c2e4f4feec16e)
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 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 
27 /*
28  * The Sun Studio and GCC (patched for opensolaris/illumos) compilers
29  * implement a argument saving scheme on amd64 via the -Wu,save-args or
30  * options.  When the option is specified, INTEGER type function arguments
31  * passed via registers will be saved on the stack immediately after %rbp, and
32  * will not be modified through out the life of the routine.
33  *
34  *				+--------+
35  *		%rbp	-->     |  %rbp  |
36  *				+--------+
37  *		-0x8(%rbp)	|  %rdi  |
38  *				+--------+
39  *		-0x10(%rbp)	|  %rsi  |
40  *				+--------+
41  *		-0x18(%rbp)	|  %rdx  |
42  *				+--------+
43  *		-0x20(%rbp)	|  %rcx  |
44  *				+--------+
45  *		-0x28(%rbp)	|  %r8   |
46  *				+--------+
47  *		-0x30(%rbp)	|  %r9   |
48  *				+--------+
49  *
50  *
51  * For example, for the following function,
52  *
53  * void
54  * foo(int a1, int a2, int a3, int a4, int a5, int a6, int a7)
55  * {
56  * ...
57  * }
58  *
59  * Disassembled code will look something like the following:
60  *
61  *     pushq	%rbp
62  *     movq	%rsp, %rbp
63  *     subq	$imm8, %rsp			**
64  *     movq	%rdi, -0x8(%rbp)
65  *     movq	%rsi, -0x10(%rbp)
66  *     movq	%rdx, -0x18(%rbp)
67  *     movq	%rcx, -0x20(%rbp)
68  *     movq	%r8, -0x28(%rbp)
69  *     movq	%r9, -0x30(%rbp)
70  *     ...
71  * or
72  *     pushq	%rbp
73  *     movq	%rsp, %rbp
74  *     subq	$imm8, %rsp			**
75  *     movq	%r9, -0x30(%rbp)
76  *     movq	%r8, -0x28(%rbp)
77  *     movq	%rcx, -0x20(%rbp)
78  *     movq	%rdx, -0x18(%rbp)
79  *     movq	%rsi, -0x10(%rbp)
80  *     movq	%rdi, -0x8(%rbp)
81  *     ...
82  * or
83  *     pushq	%rbp
84  *     movq	%rsp, %rbp
85  *     pushq	%rdi
86  *     pushq	%rsi
87  *     pushq	%rdx
88  *     pushq	%rcx
89  *     pushq	%r8
90  *     pushq	%r9
91  *
92  * **: The space being reserved is in addition to what the current
93  *     function prolog already reserves.
94  *
95  * We loop through the first SAVEARGS_INSN_SEQ_LEN bytes of the function
96  * looking for each argument saving instruction we would expect to see.
97  *
98  * If there are odd number of arguments to a function, additional space is
99  * reserved on the stack to maintain 16-byte alignment.  For example,
100  *
101  *     argc == 0: no argument saving.
102  *     argc == 3: save 3, but space for 4 is reserved
103  *     argc == 7: save 6.
104  */
105 
106 #include <sys/sysmacros.h>
107 #include <sys/types.h>
108 #include <libdisasm.h>
109 #include <string.h>
110 
111 #include <saveargs.h>
112 
113 /*
114  * Size of the instruction sequence arrays.  It should correspond to
115  * the maximum number of arguments passed via registers.
116  */
117 #define	INSTR_ARRAY_SIZE	6
118 
119 #define	INSTR1(ins, off) (ins[(off)])
120 #define	INSTR2(ins, off) (ins[(off)] + (ins[(off) + 1] << 8))
121 #define	INSTR3(ins, off)	\
122 	(ins[(off)] + (ins[(off) + 1] << 8) + (ins[(off + 2)] << 16))
123 #define	INSTR4(ins, off)	\
124 	(ins[(off)] + (ins[(off) + 1] << 8) + (ins[(off + 2)] << 16) + \
125 	(ins[(off) + 3] << 24))
126 
127 /*
128  * Sun Studio 10 patch implementation saves %rdi first;
129  * GCC 3.4.3 Sun branch implementation saves them in reverse order.
130  */
131 static const uint32_t save_instr[INSTR_ARRAY_SIZE] = {
132 	0xf87d8948,	/* movq %rdi, -0x8(%rbp) */
133 	0xf0758948,	/* movq %rsi, -0x10(%rbp) */
134 	0xe8558948,	/* movq %rdx, -0x18(%rbp) */
135 	0xe04d8948,	/* movq %rcx, -0x20(%rbp) */
136 	0xd845894c,	/* movq %r8, -0x28(%rbp) */
137 	0xd04d894c	/* movq %r9, -0x30(%rbp) */
138 };
139 
140 static const uint16_t save_instr_push[] = {
141 	0x57,	/* pushq %rdi */
142 	0x56,	/* pushq %rsi */
143 	0x52,	/* pushq %rdx */
144 	0x51,	/* pushq %rcx */
145 	0x5041,	/* pushq %r8 */
146 	0x5141	/* pushq %r9 */
147 };
148 
149 /*
150  * If the return type of a function is a structure greater than 16 bytes in
151  * size, %rdi will contain the address to which it should be stored, and
152  * arguments will begin at %rsi.  Studio will push all of the normal argument
153  * registers anyway, GCC will start pushing at %rsi, so we need a separate
154  * pattern.
155  */
156 static const uint32_t save_instr_sr[INSTR_ARRAY_SIZE-1] = {
157 	0xf8758948,	/* movq %rsi,-0x8(%rbp) */
158 	0xf0558948,	/* movq %rdx,-0x10(%rbp) */
159 	0xe84d8948,	/* movq %rcx,-0x18(%rbp) */
160 	0xe045894c,	/* movq %r8,-0x20(%rbp) */
161 	0xd84d894c	/* movq %r9,-0x28(%rbp) */
162 };
163 
164 static const uint8_t save_fp_pushes[] = {
165 	0x55,	/* pushq %rbp */
166 	0xcc	/* int $0x3 */
167 };
168 #define	NUM_FP_PUSHES (sizeof (save_fp_pushes) / sizeof (save_fp_pushes[0]))
169 
170 static const uint32_t save_fp_movs[] = {
171 	0x00e58948,	/* movq %rsp,%rbp, encoding 1 */
172 	0x00ec8b48,	/* movq %rsp,%rbp, encoding 2 */
173 };
174 #define	NUM_FP_MOVS (sizeof (save_fp_movs) / sizeof (save_fp_movs[0]))
175 
176 typedef struct {
177 	uint8_t *data;
178 	size_t size;
179 } text_t;
180 
181 static int
182 do_read(void *data, uint64_t addr, void *buf, size_t len)
183 {
184 	text_t	*t = data;
185 
186 	if (addr >= t->size)
187 		return (-1);
188 
189 	len = MIN(len, t->size - addr);
190 
191 	(void) memcpy(buf, (char *)t->data + addr, len);
192 
193 	return (len);
194 }
195 
196 /* ARGSUSED */
197 int
198 do_lookup(void *data, uint64_t addr, char *buf, size_t buflen, uint64_t *start,
199     size_t *symlen)
200 {
201 	/* We don't actually need lookup info */
202 	return (-1);
203 }
204 
205 static int
206 instr_size(dis_handle_t *dhp, uint8_t *ins, unsigned int i, size_t size)
207 {
208 	text_t	t;
209 
210 	t.data = ins;
211 	t.size = size;
212 
213 	dis_set_data(dhp, &t);
214 	return (dis_instrlen(dhp, i));
215 }
216 
217 static boolean_t
218 has_saved_fp(dis_handle_t *dhp, uint8_t *ins, int size)
219 {
220 	int 		i, j;
221 	uint32_t	n;
222 	boolean_t	found_push = B_FALSE;
223 	ssize_t		sz = 0;
224 
225 	for (i = 0; i < size; i += sz) {
226 		if ((sz = instr_size(dhp, ins, i, size)) < 1)
227 			return (B_FALSE);
228 
229 		if (found_push == B_FALSE) {
230 			if (sz != 1)
231 				continue;
232 
233 			n = INSTR1(ins, i);
234 			for (j = 0; j <= NUM_FP_PUSHES; j++)
235 				if (save_fp_pushes[j] == n) {
236 					found_push = B_TRUE;
237 					break;
238 				}
239 		} else {
240 			if (sz != 3)
241 				continue;
242 			n = INSTR3(ins, i);
243 			for (j = 0; j <= NUM_FP_MOVS; j++)
244 				if (save_fp_movs[j] == n)
245 					return (B_TRUE);
246 		}
247 	}
248 
249 	return (B_FALSE);
250 }
251 
252 int
253 saveargs_has_args(uint8_t *ins, size_t size, uint_t argc, int start_index)
254 {
255 	int		i, j;
256 	uint32_t	n;
257 	uint8_t		found = 0;
258 	ssize_t		sz = 0;
259 	dis_handle_t	*dhp = NULL;
260 	int		ret = SAVEARGS_NO_ARGS;
261 
262 	argc = MIN((start_index + argc), INSTR_ARRAY_SIZE);
263 
264 	if ((dhp = dis_handle_create(DIS_X86_SIZE64, NULL, do_lookup,
265 	    do_read)) == NULL)
266 		return (SAVEARGS_NO_ARGS);
267 
268 	if (!has_saved_fp(dhp, ins, size)) {
269 		dis_handle_destroy(dhp);
270 		return (SAVEARGS_NO_ARGS);
271 	}
272 
273 	/*
274 	 * For each possible style of argument saving, walk the insn stream as
275 	 * we've been given it, and set bit N in 'found' if we find the
276 	 * instruction saving the Nth argument.
277 	 */
278 
279 	/*
280 	 * Compare against regular implementation
281 	 */
282 	found = 0;
283 	for (i = 0; i < size; i += sz) {
284 		sz = instr_size(dhp, ins, i, size);
285 
286 		if (sz < 1)
287 			break;
288 		else if (sz != 4)
289 			continue;
290 
291 		n = INSTR4(ins, i);
292 
293 		for (j = 0; j < argc; j++) {
294 			if (n == save_instr[j]) {
295 				found |= (1 << j);
296 
297 				if (found == ((1 << argc) - 1)) {
298 					ret = start_index ?
299 					    SAVEARGS_STRUCT_ARGS :
300 					    SAVEARGS_TRAD_ARGS;
301 					goto done;
302 				}
303 
304 				break;
305 			}
306 		}
307 	}
308 
309 	/*
310 	 * Compare against GCC push-based implementation
311 	 */
312 	found = 0;
313 	for (i = 0; i < size; i += sz) {
314 		if ((sz = instr_size(dhp, ins, i, size)) < 1)
315 			break;
316 
317 		for (j = start_index; j < argc; j++) {
318 			if (sz == 2) /* Two byte */
319 				n = INSTR2(ins, i);
320 			else if (sz == 1)
321 				n = INSTR1(ins, i);
322 			else
323 				continue;
324 
325 			if (n == save_instr_push[j]) {
326 				found |= (1 << (j - start_index));
327 
328 				if (found ==
329 				    ((1 << (argc - start_index)) - 1)) {
330 					ret = SAVEARGS_TRAD_ARGS;
331 					goto done;
332 				}
333 
334 				break;
335 			}
336 		}
337 	}
338 
339 	/*
340 	 * Look for a GCC-style returned structure.
341 	 */
342 	found = 0;
343 	if (start_index != 0) {
344 		for (i = 0; i < size; i += sz) {
345 			sz = instr_size(dhp, ins, i, size);
346 
347 			if (sz < 1)
348 				break;
349 			else if (sz != 4)
350 				continue;
351 
352 			n = INSTR4(ins, i);
353 
354 			/* argc is inclusive of start_index, allow for that */
355 			for (j = 0; j < (argc - start_index); j++) {
356 				if (n == save_instr_sr[j]) {
357 					found |= (1 << j);
358 
359 					if (found ==
360 					    ((1 << (argc - start_index)) - 1)) {
361 						ret = SAVEARGS_TRAD_ARGS;
362 						goto done;
363 					}
364 
365 					break;
366 				}
367 			}
368 		}
369 	}
370 
371 done:
372 	dis_handle_destroy(dhp);
373 	return (ret);
374 }
375