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