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
do_read(void * data,uint64_t addr,void * buf,size_t len)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
do_lookup(void * data,uint64_t addr,char * buf,size_t buflen,uint64_t * start,size_t * symlen)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
instr_size(dis_handle_t * dhp,uint8_t * ins,unsigned int i,size_t size)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
has_saved_fp(dis_handle_t * dhp,uint8_t * ins,int size)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
saveargs_has_args(uint8_t * ins,size_t size,uint_t argc,int start_index)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