1702941cdSRichard Lowe /*
2702941cdSRichard Lowe * CDDL HEADER START
3702941cdSRichard Lowe *
4702941cdSRichard Lowe * The contents of this file are subject to the terms of the
5702941cdSRichard Lowe * Common Development and Distribution License (the "License").
6702941cdSRichard Lowe * You may not use this file except in compliance with the License.
7702941cdSRichard Lowe *
8702941cdSRichard Lowe * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9702941cdSRichard Lowe * or http://www.opensolaris.org/os/licensing.
10702941cdSRichard Lowe * See the License for the specific language governing permissions
11702941cdSRichard Lowe * and limitations under the License.
12702941cdSRichard Lowe *
13702941cdSRichard Lowe * When distributing Covered Code, include this CDDL HEADER in each
14702941cdSRichard Lowe * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15702941cdSRichard Lowe * If applicable, add the following below this CDDL HEADER, with the
16702941cdSRichard Lowe * fields enclosed by brackets "[]" replaced with your own identifying
17702941cdSRichard Lowe * information: Portions Copyright [yyyy] [name of copyright owner]
18702941cdSRichard Lowe *
19702941cdSRichard Lowe * CDDL HEADER END
20702941cdSRichard Lowe */
21702941cdSRichard Lowe /*
22702941cdSRichard Lowe * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
23702941cdSRichard Lowe * Use is subject to license terms.
24702941cdSRichard Lowe */
25702941cdSRichard Lowe
26*92c1a611SBryan Cantrill /*
27*92c1a611SBryan Cantrill * Copyright 2019 Joyent, Inc.
28*92c1a611SBryan Cantrill */
29702941cdSRichard Lowe
30702941cdSRichard Lowe /*
31702941cdSRichard Lowe * The Sun Studio and GCC (patched for opensolaris/illumos) compilers
32702941cdSRichard Lowe * implement a argument saving scheme on amd64 via the -Wu,save-args or
33702941cdSRichard Lowe * options. When the option is specified, INTEGER type function arguments
34702941cdSRichard Lowe * passed via registers will be saved on the stack immediately after %rbp, and
35702941cdSRichard Lowe * will not be modified through out the life of the routine.
36702941cdSRichard Lowe *
37702941cdSRichard Lowe * +--------+
38702941cdSRichard Lowe * %rbp --> | %rbp |
39702941cdSRichard Lowe * +--------+
40702941cdSRichard Lowe * -0x8(%rbp) | %rdi |
41702941cdSRichard Lowe * +--------+
42702941cdSRichard Lowe * -0x10(%rbp) | %rsi |
43702941cdSRichard Lowe * +--------+
44702941cdSRichard Lowe * -0x18(%rbp) | %rdx |
45702941cdSRichard Lowe * +--------+
46702941cdSRichard Lowe * -0x20(%rbp) | %rcx |
47702941cdSRichard Lowe * +--------+
48702941cdSRichard Lowe * -0x28(%rbp) | %r8 |
49702941cdSRichard Lowe * +--------+
50702941cdSRichard Lowe * -0x30(%rbp) | %r9 |
51702941cdSRichard Lowe * +--------+
52702941cdSRichard Lowe *
53702941cdSRichard Lowe *
54702941cdSRichard Lowe * For example, for the following function,
55702941cdSRichard Lowe *
56702941cdSRichard Lowe * void
57702941cdSRichard Lowe * foo(int a1, int a2, int a3, int a4, int a5, int a6, int a7)
58702941cdSRichard Lowe * {
59702941cdSRichard Lowe * ...
60702941cdSRichard Lowe * }
61702941cdSRichard Lowe *
62702941cdSRichard Lowe * Disassembled code will look something like the following:
63702941cdSRichard Lowe *
64702941cdSRichard Lowe * pushq %rbp
65702941cdSRichard Lowe * movq %rsp, %rbp
66702941cdSRichard Lowe * subq $imm8, %rsp **
67702941cdSRichard Lowe * movq %rdi, -0x8(%rbp)
68702941cdSRichard Lowe * movq %rsi, -0x10(%rbp)
69702941cdSRichard Lowe * movq %rdx, -0x18(%rbp)
70702941cdSRichard Lowe * movq %rcx, -0x20(%rbp)
71702941cdSRichard Lowe * movq %r8, -0x28(%rbp)
72702941cdSRichard Lowe * movq %r9, -0x30(%rbp)
73702941cdSRichard Lowe * ...
74702941cdSRichard Lowe * or
75702941cdSRichard Lowe * pushq %rbp
76702941cdSRichard Lowe * movq %rsp, %rbp
77702941cdSRichard Lowe * subq $imm8, %rsp **
78702941cdSRichard Lowe * movq %r9, -0x30(%rbp)
79702941cdSRichard Lowe * movq %r8, -0x28(%rbp)
80702941cdSRichard Lowe * movq %rcx, -0x20(%rbp)
81702941cdSRichard Lowe * movq %rdx, -0x18(%rbp)
82702941cdSRichard Lowe * movq %rsi, -0x10(%rbp)
83702941cdSRichard Lowe * movq %rdi, -0x8(%rbp)
84702941cdSRichard Lowe * ...
85702941cdSRichard Lowe * or
86702941cdSRichard Lowe * pushq %rbp
87702941cdSRichard Lowe * movq %rsp, %rbp
88702941cdSRichard Lowe * pushq %rdi
89702941cdSRichard Lowe * pushq %rsi
90702941cdSRichard Lowe * pushq %rdx
91702941cdSRichard Lowe * pushq %rcx
92702941cdSRichard Lowe * pushq %r8
93702941cdSRichard Lowe * pushq %r9
94702941cdSRichard Lowe *
95702941cdSRichard Lowe * **: The space being reserved is in addition to what the current
96702941cdSRichard Lowe * function prolog already reserves.
97702941cdSRichard Lowe *
98702941cdSRichard Lowe * We loop through the first SAVEARGS_INSN_SEQ_LEN bytes of the function
99702941cdSRichard Lowe * looking for each argument saving instruction we would expect to see.
100702941cdSRichard Lowe *
101702941cdSRichard Lowe * If there are odd number of arguments to a function, additional space is
102702941cdSRichard Lowe * reserved on the stack to maintain 16-byte alignment. For example,
103702941cdSRichard Lowe *
104702941cdSRichard Lowe * argc == 0: no argument saving.
105702941cdSRichard Lowe * argc == 3: save 3, but space for 4 is reserved
106702941cdSRichard Lowe * argc == 7: save 6.
107702941cdSRichard Lowe */
108702941cdSRichard Lowe
109702941cdSRichard Lowe #include <sys/sysmacros.h>
110702941cdSRichard Lowe #include <sys/types.h>
111702941cdSRichard Lowe #include <libdisasm.h>
112702941cdSRichard Lowe #include <string.h>
113702941cdSRichard Lowe
114702941cdSRichard Lowe #include <saveargs.h>
115702941cdSRichard Lowe
116702941cdSRichard Lowe /*
117702941cdSRichard Lowe * Size of the instruction sequence arrays. It should correspond to
118702941cdSRichard Lowe * the maximum number of arguments passed via registers.
119702941cdSRichard Lowe */
120702941cdSRichard Lowe #define INSTR_ARRAY_SIZE 6
121702941cdSRichard Lowe
122702941cdSRichard Lowe #define INSTR1(ins, off) (ins[(off)])
123702941cdSRichard Lowe #define INSTR2(ins, off) (ins[(off)] + (ins[(off) + 1] << 8))
124702941cdSRichard Lowe #define INSTR3(ins, off) \
125702941cdSRichard Lowe (ins[(off)] + (ins[(off) + 1] << 8) + (ins[(off + 2)] << 16))
126702941cdSRichard Lowe #define INSTR4(ins, off) \
127702941cdSRichard Lowe (ins[(off)] + (ins[(off) + 1] << 8) + (ins[(off + 2)] << 16) + \
128702941cdSRichard Lowe (ins[(off) + 3] << 24))
129702941cdSRichard Lowe
130702941cdSRichard Lowe /*
131702941cdSRichard Lowe * Sun Studio 10 patch implementation saves %rdi first;
132702941cdSRichard Lowe * GCC 3.4.3 Sun branch implementation saves them in reverse order.
133702941cdSRichard Lowe */
134702941cdSRichard Lowe static const uint32_t save_instr[INSTR_ARRAY_SIZE] = {
135702941cdSRichard Lowe 0xf87d8948, /* movq %rdi, -0x8(%rbp) */
136702941cdSRichard Lowe 0xf0758948, /* movq %rsi, -0x10(%rbp) */
137702941cdSRichard Lowe 0xe8558948, /* movq %rdx, -0x18(%rbp) */
138702941cdSRichard Lowe 0xe04d8948, /* movq %rcx, -0x20(%rbp) */
139702941cdSRichard Lowe 0xd845894c, /* movq %r8, -0x28(%rbp) */
140702941cdSRichard Lowe 0xd04d894c /* movq %r9, -0x30(%rbp) */
141702941cdSRichard Lowe };
142702941cdSRichard Lowe
143702941cdSRichard Lowe static const uint16_t save_instr_push[] = {
144702941cdSRichard Lowe 0x57, /* pushq %rdi */
145702941cdSRichard Lowe 0x56, /* pushq %rsi */
146702941cdSRichard Lowe 0x52, /* pushq %rdx */
147702941cdSRichard Lowe 0x51, /* pushq %rcx */
148702941cdSRichard Lowe 0x5041, /* pushq %r8 */
149702941cdSRichard Lowe 0x5141 /* pushq %r9 */
150702941cdSRichard Lowe };
151702941cdSRichard Lowe
152702941cdSRichard Lowe /*
153702941cdSRichard Lowe * If the return type of a function is a structure greater than 16 bytes in
154702941cdSRichard Lowe * size, %rdi will contain the address to which it should be stored, and
155702941cdSRichard Lowe * arguments will begin at %rsi. Studio will push all of the normal argument
156702941cdSRichard Lowe * registers anyway, GCC will start pushing at %rsi, so we need a separate
157702941cdSRichard Lowe * pattern.
158702941cdSRichard Lowe */
159702941cdSRichard Lowe static const uint32_t save_instr_sr[INSTR_ARRAY_SIZE-1] = {
160702941cdSRichard Lowe 0xf8758948, /* movq %rsi,-0x8(%rbp) */
161702941cdSRichard Lowe 0xf0558948, /* movq %rdx,-0x10(%rbp) */
162702941cdSRichard Lowe 0xe84d8948, /* movq %rcx,-0x18(%rbp) */
163702941cdSRichard Lowe 0xe045894c, /* movq %r8,-0x20(%rbp) */
164702941cdSRichard Lowe 0xd84d894c /* movq %r9,-0x28(%rbp) */
165702941cdSRichard Lowe };
166702941cdSRichard Lowe
167702941cdSRichard Lowe static const uint8_t save_fp_pushes[] = {
168702941cdSRichard Lowe 0x55, /* pushq %rbp */
169702941cdSRichard Lowe 0xcc /* int $0x3 */
170702941cdSRichard Lowe };
171702941cdSRichard Lowe #define NUM_FP_PUSHES (sizeof (save_fp_pushes) / sizeof (save_fp_pushes[0]))
172702941cdSRichard Lowe
173702941cdSRichard Lowe static const uint32_t save_fp_movs[] = {
174702941cdSRichard Lowe 0x00e58948, /* movq %rsp,%rbp, encoding 1 */
175702941cdSRichard Lowe 0x00ec8b48, /* movq %rsp,%rbp, encoding 2 */
176702941cdSRichard Lowe };
177702941cdSRichard Lowe #define NUM_FP_MOVS (sizeof (save_fp_movs) / sizeof (save_fp_movs[0]))
178702941cdSRichard Lowe
179702941cdSRichard Lowe typedef struct {
180702941cdSRichard Lowe uint8_t *data;
181702941cdSRichard Lowe size_t size;
182702941cdSRichard Lowe } text_t;
183702941cdSRichard Lowe
184702941cdSRichard Lowe static int
do_read(void * data,uint64_t addr,void * buf,size_t len)185702941cdSRichard Lowe do_read(void *data, uint64_t addr, void *buf, size_t len)
186702941cdSRichard Lowe {
187702941cdSRichard Lowe text_t *t = data;
188702941cdSRichard Lowe
189702941cdSRichard Lowe if (addr >= t->size)
190702941cdSRichard Lowe return (-1);
191702941cdSRichard Lowe
192702941cdSRichard Lowe len = MIN(len, t->size - addr);
193702941cdSRichard Lowe
194702941cdSRichard Lowe (void) memcpy(buf, (char *)t->data + addr, len);
195702941cdSRichard Lowe
196702941cdSRichard Lowe return (len);
197702941cdSRichard Lowe }
198702941cdSRichard Lowe
199702941cdSRichard Lowe /* ARGSUSED */
200702941cdSRichard Lowe int
do_lookup(void * data,uint64_t addr,char * buf,size_t buflen,uint64_t * start,size_t * symlen)201702941cdSRichard Lowe do_lookup(void *data, uint64_t addr, char *buf, size_t buflen, uint64_t *start,
202702941cdSRichard Lowe size_t *symlen)
203702941cdSRichard Lowe {
204702941cdSRichard Lowe /* We don't actually need lookup info */
205702941cdSRichard Lowe return (-1);
206702941cdSRichard Lowe }
207702941cdSRichard Lowe
208702941cdSRichard Lowe static int
instr_size(dis_handle_t * dhp,uint8_t * ins,unsigned int i,size_t size)209702941cdSRichard Lowe instr_size(dis_handle_t *dhp, uint8_t *ins, unsigned int i, size_t size)
210702941cdSRichard Lowe {
211702941cdSRichard Lowe text_t t;
212702941cdSRichard Lowe
213702941cdSRichard Lowe t.data = ins;
214702941cdSRichard Lowe t.size = size;
215702941cdSRichard Lowe
216702941cdSRichard Lowe dis_set_data(dhp, &t);
217702941cdSRichard Lowe return (dis_instrlen(dhp, i));
218702941cdSRichard Lowe }
219702941cdSRichard Lowe
220702941cdSRichard Lowe static boolean_t
has_saved_fp(dis_handle_t * dhp,uint8_t * ins,int size)221702941cdSRichard Lowe has_saved_fp(dis_handle_t *dhp, uint8_t *ins, int size)
222702941cdSRichard Lowe {
223702941cdSRichard Lowe int i, j;
224702941cdSRichard Lowe uint32_t n;
225702941cdSRichard Lowe boolean_t found_push = B_FALSE;
226702941cdSRichard Lowe ssize_t sz = 0;
227702941cdSRichard Lowe
228702941cdSRichard Lowe for (i = 0; i < size; i += sz) {
229702941cdSRichard Lowe if ((sz = instr_size(dhp, ins, i, size)) < 1)
230702941cdSRichard Lowe return (B_FALSE);
231702941cdSRichard Lowe
232702941cdSRichard Lowe if (found_push == B_FALSE) {
233702941cdSRichard Lowe if (sz != 1)
234702941cdSRichard Lowe continue;
235702941cdSRichard Lowe
236702941cdSRichard Lowe n = INSTR1(ins, i);
237*92c1a611SBryan Cantrill for (j = 0; j < NUM_FP_PUSHES; j++)
238702941cdSRichard Lowe if (save_fp_pushes[j] == n) {
239702941cdSRichard Lowe found_push = B_TRUE;
240702941cdSRichard Lowe break;
241702941cdSRichard Lowe }
242702941cdSRichard Lowe } else {
243702941cdSRichard Lowe if (sz != 3)
244702941cdSRichard Lowe continue;
245702941cdSRichard Lowe n = INSTR3(ins, i);
246*92c1a611SBryan Cantrill for (j = 0; j < NUM_FP_MOVS; j++)
247702941cdSRichard Lowe if (save_fp_movs[j] == n)
248702941cdSRichard Lowe return (B_TRUE);
249702941cdSRichard Lowe }
250702941cdSRichard Lowe }
251702941cdSRichard Lowe
252702941cdSRichard Lowe return (B_FALSE);
253702941cdSRichard Lowe }
254702941cdSRichard Lowe
255702941cdSRichard Lowe int
saveargs_has_args(uint8_t * ins,size_t size,uint_t argc,int start_index)256702941cdSRichard Lowe saveargs_has_args(uint8_t *ins, size_t size, uint_t argc, int start_index)
257702941cdSRichard Lowe {
258702941cdSRichard Lowe int i, j;
259702941cdSRichard Lowe uint32_t n;
260702941cdSRichard Lowe uint8_t found = 0;
261702941cdSRichard Lowe ssize_t sz = 0;
262702941cdSRichard Lowe dis_handle_t *dhp = NULL;
263702941cdSRichard Lowe int ret = SAVEARGS_NO_ARGS;
264702941cdSRichard Lowe
265702941cdSRichard Lowe argc = MIN((start_index + argc), INSTR_ARRAY_SIZE);
266702941cdSRichard Lowe
267702941cdSRichard Lowe if ((dhp = dis_handle_create(DIS_X86_SIZE64, NULL, do_lookup,
268702941cdSRichard Lowe do_read)) == NULL)
269702941cdSRichard Lowe return (SAVEARGS_NO_ARGS);
270702941cdSRichard Lowe
271702941cdSRichard Lowe if (!has_saved_fp(dhp, ins, size)) {
272702941cdSRichard Lowe dis_handle_destroy(dhp);
273702941cdSRichard Lowe return (SAVEARGS_NO_ARGS);
274702941cdSRichard Lowe }
275702941cdSRichard Lowe
276702941cdSRichard Lowe /*
277702941cdSRichard Lowe * For each possible style of argument saving, walk the insn stream as
278702941cdSRichard Lowe * we've been given it, and set bit N in 'found' if we find the
279702941cdSRichard Lowe * instruction saving the Nth argument.
280702941cdSRichard Lowe */
281702941cdSRichard Lowe
282702941cdSRichard Lowe /*
283702941cdSRichard Lowe * Compare against regular implementation
284702941cdSRichard Lowe */
285702941cdSRichard Lowe found = 0;
286702941cdSRichard Lowe for (i = 0; i < size; i += sz) {
287702941cdSRichard Lowe sz = instr_size(dhp, ins, i, size);
288702941cdSRichard Lowe
289702941cdSRichard Lowe if (sz < 1)
290702941cdSRichard Lowe break;
291702941cdSRichard Lowe else if (sz != 4)
292702941cdSRichard Lowe continue;
293702941cdSRichard Lowe
294702941cdSRichard Lowe n = INSTR4(ins, i);
295702941cdSRichard Lowe
296702941cdSRichard Lowe for (j = 0; j < argc; j++) {
297702941cdSRichard Lowe if (n == save_instr[j]) {
298702941cdSRichard Lowe found |= (1 << j);
299702941cdSRichard Lowe
300702941cdSRichard Lowe if (found == ((1 << argc) - 1)) {
301702941cdSRichard Lowe ret = start_index ?
302702941cdSRichard Lowe SAVEARGS_STRUCT_ARGS :
303702941cdSRichard Lowe SAVEARGS_TRAD_ARGS;
304702941cdSRichard Lowe goto done;
305702941cdSRichard Lowe }
306702941cdSRichard Lowe
307702941cdSRichard Lowe break;
308702941cdSRichard Lowe }
309702941cdSRichard Lowe }
310702941cdSRichard Lowe }
311702941cdSRichard Lowe
312702941cdSRichard Lowe /*
313702941cdSRichard Lowe * Compare against GCC push-based implementation
314702941cdSRichard Lowe */
315702941cdSRichard Lowe found = 0;
316702941cdSRichard Lowe for (i = 0; i < size; i += sz) {
317702941cdSRichard Lowe if ((sz = instr_size(dhp, ins, i, size)) < 1)
318702941cdSRichard Lowe break;
319702941cdSRichard Lowe
320702941cdSRichard Lowe for (j = start_index; j < argc; j++) {
321702941cdSRichard Lowe if (sz == 2) /* Two byte */
322702941cdSRichard Lowe n = INSTR2(ins, i);
323702941cdSRichard Lowe else if (sz == 1)
324702941cdSRichard Lowe n = INSTR1(ins, i);
325702941cdSRichard Lowe else
326702941cdSRichard Lowe continue;
327702941cdSRichard Lowe
328702941cdSRichard Lowe if (n == save_instr_push[j]) {
329702941cdSRichard Lowe found |= (1 << (j - start_index));
330702941cdSRichard Lowe
331702941cdSRichard Lowe if (found ==
332702941cdSRichard Lowe ((1 << (argc - start_index)) - 1)) {
333702941cdSRichard Lowe ret = SAVEARGS_TRAD_ARGS;
334702941cdSRichard Lowe goto done;
335702941cdSRichard Lowe }
336702941cdSRichard Lowe
337702941cdSRichard Lowe break;
338702941cdSRichard Lowe }
339702941cdSRichard Lowe }
340702941cdSRichard Lowe }
341702941cdSRichard Lowe
342702941cdSRichard Lowe /*
343702941cdSRichard Lowe * Look for a GCC-style returned structure.
344702941cdSRichard Lowe */
345702941cdSRichard Lowe found = 0;
346702941cdSRichard Lowe if (start_index != 0) {
347702941cdSRichard Lowe for (i = 0; i < size; i += sz) {
348702941cdSRichard Lowe sz = instr_size(dhp, ins, i, size);
349702941cdSRichard Lowe
350702941cdSRichard Lowe if (sz < 1)
351702941cdSRichard Lowe break;
352702941cdSRichard Lowe else if (sz != 4)
353702941cdSRichard Lowe continue;
354702941cdSRichard Lowe
355702941cdSRichard Lowe n = INSTR4(ins, i);
356702941cdSRichard Lowe
357702941cdSRichard Lowe /* argc is inclusive of start_index, allow for that */
358702941cdSRichard Lowe for (j = 0; j < (argc - start_index); j++) {
359702941cdSRichard Lowe if (n == save_instr_sr[j]) {
360702941cdSRichard Lowe found |= (1 << j);
361702941cdSRichard Lowe
362702941cdSRichard Lowe if (found ==
363702941cdSRichard Lowe ((1 << (argc - start_index)) - 1)) {
364702941cdSRichard Lowe ret = SAVEARGS_TRAD_ARGS;
365702941cdSRichard Lowe goto done;
366702941cdSRichard Lowe }
367702941cdSRichard Lowe
368702941cdSRichard Lowe break;
369702941cdSRichard Lowe }
370702941cdSRichard Lowe }
371702941cdSRichard Lowe }
372702941cdSRichard Lowe }
373702941cdSRichard Lowe
374702941cdSRichard Lowe done:
375702941cdSRichard Lowe dis_handle_destroy(dhp);
376702941cdSRichard Lowe return (ret);
377702941cdSRichard Lowe }
378