summaryrefslogtreecommitdiff
path: root/usr/src/common/saveargs/saveargs.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/common/saveargs/saveargs.c')
-rw-r--r--usr/src/common/saveargs/saveargs.c268
1 files changed, 268 insertions, 0 deletions
diff --git a/usr/src/common/saveargs/saveargs.c b/usr/src/common/saveargs/saveargs.c
new file mode 100644
index 0000000000..c75d07e5b9
--- /dev/null
+++ b/usr/src/common/saveargs/saveargs.c
@@ -0,0 +1,268 @@
+/*
+ * 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.
+ */
+
+
+/*
+ * 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. We
+ * loop byte-by-byte, rather than doing anything smart about insn lengths,
+ * only deviating from this when we know we have our insn, and can skip the
+ * rest of it.
+ *
+ * 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 <sys/sysmacros.h>
+#include <sys/types.h>
+#include <saveargs.h>
+
+/*
+ * 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]))
+
+static int
+has_saved_fp(uint8_t *ins, int size)
+{
+ int i, j;
+ uint32_t n;
+ int found_push = 0;
+
+ for (i = 0; i < size; i++) {
+ if (found_push == 0) {
+ n = INSTR1(ins, i);
+ for (j = 0; j <= NUM_FP_PUSHES; j++)
+ if (save_fp_pushes[j] == n) {
+ found_push = 1;
+ break;
+ }
+ } else {
+ n = INSTR3(ins, i);
+ for (j = 0; j <= NUM_FP_MOVS; j++)
+ if (save_fp_movs[j] == n)
+ return (1);
+ }
+ }
+
+ return (0);
+}
+
+int
+saveargs_has_args(uint8_t *ins, size_t size, uint_t argc, int start_index)
+{
+ int i, j;
+ uint32_t n;
+
+ argc = MIN((start_index + argc), INSTR_ARRAY_SIZE);
+
+ if (!has_saved_fp(ins, size))
+ return (SAVEARGS_NO_ARGS);
+
+ /*
+ * Compare against Sun Studio implementation
+ */
+ for (i = 4, j = 0; i <= size - 4; i++) {
+ n = INSTR4(ins, i);
+
+ if (n == save_instr[j]) {
+ i += 3;
+ if (++j >= argc)
+ return (start_index ? SAVEARGS_STRUCT_ARGS :
+ SAVEARGS_TRAD_ARGS);
+ }
+ }
+
+ /*
+ * Compare against GCC implementation
+ */
+ for (i = 4, j = argc - 1; i <= size - 4; i++) {
+ n = INSTR4(ins, i);
+
+ if (n == save_instr[j]) {
+ i += 3;
+ if (--j < start_index)
+ return (SAVEARGS_TRAD_ARGS);
+ }
+ }
+
+ /*
+ * Compare against GCC push-based implementation
+ */
+ for (i = 4, j = start_index; i <= size - 2; i += 1) {
+ n = (i >= (8 - start_index)) ? INSTR2(ins, i) : INSTR1(ins, i);
+
+ if (n == save_instr_push[j]) {
+ if (i >= (8 - start_index))
+ i += 1;
+ if (++j >= argc)
+ return (SAVEARGS_TRAD_ARGS);
+ }
+ }
+
+ /* Look for a GCC-style returned structure */
+ if (start_index != 0) {
+ for (i = 4, j = argc - 2; i <= size - 4; i++) {
+ n = INSTR4(ins, i);
+
+ if (n == save_instr_sr[j]) {
+ i += 3;
+ if (--j >= (argc - 1))
+ return (SAVEARGS_TRAD_ARGS);
+ }
+ }
+ }
+
+ return (SAVEARGS_NO_ARGS);
+}