/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2019 Joyent, Inc. */ /* * The Sun Studio and GCC (patched for opensolaris/illumos) compilers * implement a argument saving scheme on amd64 via the -Wu,save-args or * options. When the option is specified, INTEGER type function arguments * passed via registers will be saved on the stack immediately after %rbp, and * will not be modified through out the life of the routine. * * +--------+ * %rbp --> | %rbp | * +--------+ * -0x8(%rbp) | %rdi | * +--------+ * -0x10(%rbp) | %rsi | * +--------+ * -0x18(%rbp) | %rdx | * +--------+ * -0x20(%rbp) | %rcx | * +--------+ * -0x28(%rbp) | %r8 | * +--------+ * -0x30(%rbp) | %r9 | * +--------+ * * * For example, for the following function, * * void * foo(int a1, int a2, int a3, int a4, int a5, int a6, int a7) * { * ... * } * * Disassembled code will look something like the following: * * pushq %rbp * movq %rsp, %rbp * subq $imm8, %rsp ** * movq %rdi, -0x8(%rbp) * movq %rsi, -0x10(%rbp) * movq %rdx, -0x18(%rbp) * movq %rcx, -0x20(%rbp) * movq %r8, -0x28(%rbp) * movq %r9, -0x30(%rbp) * ... * or * pushq %rbp * movq %rsp, %rbp * subq $imm8, %rsp ** * movq %r9, -0x30(%rbp) * movq %r8, -0x28(%rbp) * movq %rcx, -0x20(%rbp) * movq %rdx, -0x18(%rbp) * movq %rsi, -0x10(%rbp) * movq %rdi, -0x8(%rbp) * ... * or * pushq %rbp * movq %rsp, %rbp * pushq %rdi * pushq %rsi * pushq %rdx * pushq %rcx * pushq %r8 * pushq %r9 * * **: The space being reserved is in addition to what the current * function prolog already reserves. * * We loop through the first SAVEARGS_INSN_SEQ_LEN bytes of the function * looking for each argument saving instruction we would expect to see. * * If there are odd number of arguments to a function, additional space is * reserved on the stack to maintain 16-byte alignment. For example, * * argc == 0: no argument saving. * argc == 3: save 3, but space for 4 is reserved * argc == 7: save 6. */ #include #include #include #include #include /* * Size of the instruction sequence arrays. It should correspond to * the maximum number of arguments passed via registers. */ #define INSTR_ARRAY_SIZE 6 #define INSTR1(ins, off) (ins[(off)]) #define INSTR2(ins, off) (ins[(off)] + (ins[(off) + 1] << 8)) #define INSTR3(ins, off) \ (ins[(off)] + (ins[(off) + 1] << 8) + (ins[(off + 2)] << 16)) #define INSTR4(ins, off) \ (ins[(off)] + (ins[(off) + 1] << 8) + (ins[(off + 2)] << 16) + \ (ins[(off) + 3] << 24)) /* * Sun Studio 10 patch implementation saves %rdi first; * GCC 3.4.3 Sun branch implementation saves them in reverse order. */ static const uint32_t save_instr[INSTR_ARRAY_SIZE] = { 0xf87d8948, /* movq %rdi, -0x8(%rbp) */ 0xf0758948, /* movq %rsi, -0x10(%rbp) */ 0xe8558948, /* movq %rdx, -0x18(%rbp) */ 0xe04d8948, /* movq %rcx, -0x20(%rbp) */ 0xd845894c, /* movq %r8, -0x28(%rbp) */ 0xd04d894c /* movq %r9, -0x30(%rbp) */ }; static const uint16_t save_instr_push[] = { 0x57, /* pushq %rdi */ 0x56, /* pushq %rsi */ 0x52, /* pushq %rdx */ 0x51, /* pushq %rcx */ 0x5041, /* pushq %r8 */ 0x5141 /* pushq %r9 */ }; /* * If the return type of a function is a structure greater than 16 bytes in * size, %rdi will contain the address to which it should be stored, and * arguments will begin at %rsi. Studio will push all of the normal argument * registers anyway, GCC will start pushing at %rsi, so we need a separate * pattern. */ static const uint32_t save_instr_sr[INSTR_ARRAY_SIZE-1] = { 0xf8758948, /* movq %rsi,-0x8(%rbp) */ 0xf0558948, /* movq %rdx,-0x10(%rbp) */ 0xe84d8948, /* movq %rcx,-0x18(%rbp) */ 0xe045894c, /* movq %r8,-0x20(%rbp) */ 0xd84d894c /* movq %r9,-0x28(%rbp) */ }; static const uint8_t save_fp_pushes[] = { 0x55, /* pushq %rbp */ 0xcc /* int $0x3 */ }; #define NUM_FP_PUSHES (sizeof (save_fp_pushes) / sizeof (save_fp_pushes[0])) static const uint32_t save_fp_movs[] = { 0x00e58948, /* movq %rsp,%rbp, encoding 1 */ 0x00ec8b48, /* movq %rsp,%rbp, encoding 2 */ }; #define NUM_FP_MOVS (sizeof (save_fp_movs) / sizeof (save_fp_movs[0])) typedef struct { uint8_t *data; size_t size; } text_t; static int do_read(void *data, uint64_t addr, void *buf, size_t len) { text_t *t = data; if (addr >= t->size) return (-1); len = MIN(len, t->size - addr); (void) memcpy(buf, (char *)t->data + addr, len); return (len); } /* ARGSUSED */ int do_lookup(void *data, uint64_t addr, char *buf, size_t buflen, uint64_t *start, size_t *symlen) { /* We don't actually need lookup info */ return (-1); } static int instr_size(dis_handle_t *dhp, uint8_t *ins, unsigned int i, size_t size) { text_t t; t.data = ins; t.size = size; dis_set_data(dhp, &t); return (dis_instrlen(dhp, i)); } static boolean_t has_saved_fp(dis_handle_t *dhp, uint8_t *ins, int size) { int i, j; uint32_t n; boolean_t found_push = B_FALSE; ssize_t sz = 0; for (i = 0; i < size; i += sz) { if ((sz = instr_size(dhp, ins, i, size)) < 1) return (B_FALSE); if (found_push == B_FALSE) { if (sz != 1) continue; n = INSTR1(ins, i); for (j = 0; j < NUM_FP_PUSHES; j++) if (save_fp_pushes[j] == n) { found_push = B_TRUE; break; } } else { if (sz != 3) continue; n = INSTR3(ins, i); for (j = 0; j < NUM_FP_MOVS; j++) if (save_fp_movs[j] == n) return (B_TRUE); } } return (B_FALSE); } int saveargs_has_args(uint8_t *ins, size_t size, uint_t argc, int start_index) { int i, j; uint32_t n; uint8_t found = 0; ssize_t sz = 0; dis_handle_t *dhp = NULL; int ret = SAVEARGS_NO_ARGS; argc = MIN((start_index + argc), INSTR_ARRAY_SIZE); if ((dhp = dis_handle_create(DIS_X86_SIZE64, NULL, do_lookup, do_read)) == NULL) return (SAVEARGS_NO_ARGS); if (!has_saved_fp(dhp, ins, size)) { dis_handle_destroy(dhp); return (SAVEARGS_NO_ARGS); } /* * For each possible style of argument saving, walk the insn stream as * we've been given it, and set bit N in 'found' if we find the * instruction saving the Nth argument. */ /* * Compare against regular implementation */ found = 0; for (i = 0; i < size; i += sz) { sz = instr_size(dhp, ins, i, size); if (sz < 1) break; else if (sz != 4) continue; n = INSTR4(ins, i); for (j = 0; j < argc; j++) { if (n == save_instr[j]) { found |= (1 << j); if (found == ((1 << argc) - 1)) { ret = start_index ? SAVEARGS_STRUCT_ARGS : SAVEARGS_TRAD_ARGS; goto done; } break; } } } /* * Compare against GCC push-based implementation */ found = 0; for (i = 0; i < size; i += sz) { if ((sz = instr_size(dhp, ins, i, size)) < 1) break; for (j = start_index; j < argc; j++) { if (sz == 2) /* Two byte */ n = INSTR2(ins, i); else if (sz == 1) n = INSTR1(ins, i); else continue; if (n == save_instr_push[j]) { found |= (1 << (j - start_index)); if (found == ((1 << (argc - start_index)) - 1)) { ret = SAVEARGS_TRAD_ARGS; goto done; } break; } } } /* * Look for a GCC-style returned structure. */ found = 0; if (start_index != 0) { for (i = 0; i < size; i += sz) { sz = instr_size(dhp, ins, i, size); if (sz < 1) break; else if (sz != 4) continue; n = INSTR4(ins, i); /* argc is inclusive of start_index, allow for that */ for (j = 0; j < (argc - start_index); j++) { if (n == save_instr_sr[j]) { found |= (1 << j); if (found == ((1 << (argc - start_index)) - 1)) { ret = SAVEARGS_TRAD_ARGS; goto done; } break; } } } } done: dis_handle_destroy(dhp); return (ret); }