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 /*
  28  * The Sun Studio and GCC (patched for opensolaris/illumos) compilers
  29  * implement a argument saving scheme on amd64 via the -Wu,save-args or
  30  * options.  When the option is specified, INTEGER type function arguments
  31  * passed via registers will be saved on the stack immediately after %rbp, and
  32  * will not be modified through out the life of the routine.
  33  *
  34  *                              +--------+
  35  *              %rbp    -->     |  %rbp  |
  36  *                              +--------+
  37  *              -0x8(%rbp)      |  %rdi  |
  38  *                              +--------+
  39  *              -0x10(%rbp)     |  %rsi  |
  40  *                              +--------+
  41  *              -0x18(%rbp)     |  %rdx  |
  42  *                              +--------+
  43  *              -0x20(%rbp)     |  %rcx  |
  44  *                              +--------+
  45  *              -0x28(%rbp)     |  %r8   |
  46  *                              +--------+
  47  *              -0x30(%rbp)     |  %r9   |
  48  *                              +--------+
  49  *
  50  *
  51  * For example, for the following function,
  52  *
  53  * void
  54  * foo(int a1, int a2, int a3, int a4, int a5, int a6, int a7)
  55  * {
  56  * ...
  57  * }
  58  *
  59  * Disassembled code will look something like the following:
  60  *
  61  *     pushq    %rbp
  62  *     movq     %rsp, %rbp
  63  *     subq     $imm8, %rsp                     **
  64  *     movq     %rdi, -0x8(%rbp)
  65  *     movq     %rsi, -0x10(%rbp)
  66  *     movq     %rdx, -0x18(%rbp)
  67  *     movq     %rcx, -0x20(%rbp)
  68  *     movq     %r8, -0x28(%rbp)
  69  *     movq     %r9, -0x30(%rbp)
  70  *     ...
  71  * or
  72  *     pushq    %rbp
  73  *     movq     %rsp, %rbp
  74  *     subq     $imm8, %rsp                     **
  75  *     movq     %r9, -0x30(%rbp)
  76  *     movq     %r8, -0x28(%rbp)
  77  *     movq     %rcx, -0x20(%rbp)
  78  *     movq     %rdx, -0x18(%rbp)
  79  *     movq     %rsi, -0x10(%rbp)
  80  *     movq     %rdi, -0x8(%rbp)
  81  *     ...
  82  * or
  83  *     pushq    %rbp
  84  *     movq     %rsp, %rbp
  85  *     pushq    %rdi
  86  *     pushq    %rsi
  87  *     pushq    %rdx
  88  *     pushq    %rcx
  89  *     pushq    %r8
  90  *     pushq    %r9
  91  *
  92  * **: The space being reserved is in addition to what the current
  93  *     function prolog already reserves.
  94  *
  95  * We loop through the first SAVEARGS_INSN_SEQ_LEN bytes of the function
  96  * looking for each argument saving instruction we would expect to see.  We
  97  * loop byte-by-byte, rather than doing anything smart about insn lengths,
  98  * only deviating from this when we know we have our insn, and can skip the
  99  * rest of it.
 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 <saveargs.h>
 112 
 113 /*
 114  * Size of the instruction sequence arrays.  It should correspond to
 115  * the maximum number of arguments passed via registers.
 116  */
 117 #define INSTR_ARRAY_SIZE        6
 118 
 119 #define INSTR1(ins, off) (ins[(off)])
 120 #define INSTR2(ins, off) (ins[(off)] + (ins[(off) + 1] << 8))
 121 #define INSTR3(ins, off)        \
 122         (ins[(off)] + (ins[(off) + 1] << 8) + (ins[(off + 2)] << 16))
 123 #define INSTR4(ins, off)        \
 124         (ins[(off)] + (ins[(off) + 1] << 8) + (ins[(off + 2)] << 16) + \
 125         (ins[(off) + 3] << 24))
 126 
 127 /*
 128  * Sun Studio 10 patch implementation saves %rdi first;
 129  * GCC 3.4.3 Sun branch implementation saves them in reverse order.
 130  */
 131 static const uint32_t save_instr[INSTR_ARRAY_SIZE] = {
 132         0xf87d8948,     /* movq %rdi, -0x8(%rbp) */
 133         0xf0758948,     /* movq %rsi, -0x10(%rbp) */
 134         0xe8558948,     /* movq %rdx, -0x18(%rbp) */
 135         0xe04d8948,     /* movq %rcx, -0x20(%rbp) */
 136         0xd845894c,     /* movq %r8, -0x28(%rbp) */
 137         0xd04d894c      /* movq %r9, -0x30(%rbp) */
 138 };
 139 
 140 static const uint16_t save_instr_push[] = {
 141         0x57,   /* pushq %rdi */
 142         0x56,   /* pushq %rsi */
 143         0x52,   /* pushq %rdx */
 144         0x51,   /* pushq %rcx */
 145         0x5041, /* pushq %r8 */
 146         0x5141  /* pushq %r9 */
 147 };
 148 
 149 /*
 150  * If the return type of a function is a structure greater than 16 bytes in
 151  * size, %rdi will contain the address to which it should be stored, and
 152  * arguments will begin at %rsi.  Studio will push all of the normal argument
 153  * registers anyway, GCC will start pushing at %rsi, so we need a separate
 154  * pattern.
 155  */
 156 static const uint32_t save_instr_sr[INSTR_ARRAY_SIZE-1] = {
 157         0xf8758948,     /* movq %rsi,-0x8(%rbp) */
 158         0xf0558948,     /* movq %rdx,-0x10(%rbp) */
 159         0xe84d8948,     /* movq %rcx,-0x18(%rbp) */
 160         0xe045894c,     /* movq %r8,-0x20(%rbp) */
 161         0xd84d894c      /* movq %r9,-0x28(%rbp) */
 162 };
 163 
 164 static const uint8_t save_fp_pushes[] = {
 165     0x55,       /* pushq %rbp */
 166     0xcc        /* int $0x3 */
 167 };
 168 #define NUM_FP_PUSHES (sizeof (save_fp_pushes) / sizeof (save_fp_pushes[0]))
 169 
 170 static const uint32_t save_fp_movs[] = {
 171         0x00e58948,     /* movq %rsp,%rbp, encoding 1 */
 172         0x00ec8b48,     /* movq %rsp,%rbp, encoding 2 */
 173 };
 174 #define NUM_FP_MOVS (sizeof (save_fp_movs) / sizeof (save_fp_movs[0]))
 175 
 176 static int
 177 has_saved_fp(uint8_t *ins, int size)
 178 {
 179         int i, j;
 180         uint32_t n;
 181         int found_push = 0;
 182 
 183         for (i = 0; i < size; i++) {
 184                 if (found_push == 0) {
 185                         n = INSTR1(ins, i);
 186                         for (j = 0; j <= NUM_FP_PUSHES; j++)
 187                                 if (save_fp_pushes[j] == n) {
 188                                         found_push = 1;
 189                                         break;
 190                                 }
 191                 } else {
 192                         n = INSTR3(ins, i);
 193                         for (j = 0; j <= NUM_FP_MOVS; j++)
 194                                 if (save_fp_movs[j] == n)
 195                                         return (1);
 196                 }
 197         }
 198 
 199         return (0);
 200 }
 201 
 202 int
 203 saveargs_has_args(uint8_t *ins, size_t size, uint_t argc, int start_index)
 204 {
 205         int             i, j;
 206         uint32_t        n;
 207 
 208         argc = MIN((start_index + argc), INSTR_ARRAY_SIZE);
 209 
 210         if (!has_saved_fp(ins, size))
 211                 return (SAVEARGS_NO_ARGS);
 212 
 213         /*
 214          * Compare against Sun Studio implementation
 215          */
 216         for (i = 4, j = 0; i <= size - 4; i++) {
 217                 n = INSTR4(ins, i);
 218 
 219                 if (n == save_instr[j]) {
 220                         i += 3;
 221                         if (++j >= argc)
 222                                 return (start_index ? SAVEARGS_STRUCT_ARGS :
 223                                     SAVEARGS_TRAD_ARGS);
 224                 }
 225         }
 226 
 227         /*
 228          * Compare against GCC implementation
 229          */
 230         for (i = 4, j = argc - 1; i <= size - 4; i++) {
 231                 n = INSTR4(ins, i);
 232 
 233                 if (n == save_instr[j]) {
 234                         i += 3;
 235                         if (--j < start_index)
 236                                 return (SAVEARGS_TRAD_ARGS);
 237                 }
 238         }
 239 
 240         /*
 241          * Compare against GCC push-based implementation
 242          */
 243         for (i = 4, j = start_index; i <= size - 2; i += 1) {
 244                 n = (i >= (8 - start_index)) ? INSTR2(ins, i) : INSTR1(ins, i);
 245 
 246                 if (n == save_instr_push[j]) {
 247                         if (i >= (8 - start_index))
 248                                 i += 1;
 249                         if (++j >= argc)
 250                                 return (SAVEARGS_TRAD_ARGS);
 251                 }
 252         }
 253 
 254         /* Look for a GCC-style returned structure */
 255         if (start_index != 0) {
 256                 for (i = 4, j = argc - 2; i <= size - 4; i++) {
 257                         n = INSTR4(ins, i);
 258 
 259                         if (n == save_instr_sr[j]) {
 260                                 i += 3;
 261                                 if (--j >= (argc - 1))
 262                                         return (SAVEARGS_TRAD_ARGS);
 263                         }
 264                 }
 265         }
 266 
 267         return (SAVEARGS_NO_ARGS);
 268 }