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 */
134static 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
143static 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 */
159static 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
167static 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
173static 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
179typedef struct {
180	uint8_t *data;
181	size_t size;
182} text_t;
183
184static int
185do_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 */
200int
201do_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
208static int
209instr_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
220static boolean_t
221has_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
255int
256saveargs_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
374done:
375	dis_handle_destroy(dhp);
376	return (ret);
377}
378