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.
  97  *
  98  * If there are odd number of arguments to a function, additional space is
  99  * reserved on the stack to maintain 16-byte alignment.  For example,
 100  *
 101  *     argc == 0: no argument saving.
 102  *     argc == 3: save 3, but space for 4 is reserved
 103  *     argc == 7: save 6.
 104  */
 105 
 106 #include <sys/sysmacros.h>
 107 #include <sys/types.h>
 108 #include <libdisasm.h>
 109 #include <string.h>
 110 
 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 typedef struct {
 177         uint8_t *data;
 178         size_t size;
 179 } text_t;
 180 
 181 static int
 182 do_read(void *data, uint64_t addr, void *buf, size_t len)
 183 {
 184         text_t  *t = data;
 185 
 186         if (addr >= t->size)
 187                 return (-1);
 188 
 189         len = MIN(len, t->size - addr);
 190 
 191         (void) memcpy(buf, (char *)t->data + addr, len);
 192 
 193         return (len);
 194 }
 195 
 196 /* ARGSUSED */
 197 int
 198 do_lookup(void *data, uint64_t addr, char *buf, size_t buflen, uint64_t *start,
 199     size_t *symlen)
 200 {
 201         /* We don't actually need lookup info */
 202         return (-1);
 203 }
 204 
 205 static int
 206 instr_size(dis_handle_t *dhp, uint8_t *ins, unsigned int i, size_t size)
 207 {
 208         text_t  t;
 209 
 210         t.data = ins;
 211         t.size = size;
 212 
 213         dis_set_data(dhp, &t);
 214         return (dis_instrlen(dhp, i));
 215 }
 216 
 217 static boolean_t
 218 has_saved_fp(dis_handle_t *dhp, uint8_t *ins, int size)
 219 {
 220         int             i, j;
 221         uint32_t        n;
 222         boolean_t       found_push = B_FALSE;
 223         int             sz = 0;
 224 
 225         for (i = 0; i < size; i += sz) {
 226                 if ((sz = instr_size(dhp, ins, i, size)) < 1)
 227                         return (B_FALSE);
 228 
 229                 if (found_push == B_FALSE) {
 230                         if (sz != 1)
 231                                 continue;
 232 
 233                         n = INSTR1(ins, i);
 234                         for (j = 0; j <= NUM_FP_PUSHES; j++)
 235                                 if (save_fp_pushes[j] == n) {
 236                                         found_push = B_TRUE;
 237                                         break;
 238                                 }
 239                 } else {
 240                         if (sz != 3)
 241                                 continue;
 242                         n = INSTR3(ins, i);
 243                         for (j = 0; j <= NUM_FP_MOVS; j++)
 244                                 if (save_fp_movs[j] == n)
 245                                         return (B_TRUE);
 246                 }
 247         }
 248 
 249         return (B_FALSE);
 250 }
 251 
 252 int
 253 saveargs_has_args(uint8_t *ins, size_t size, uint_t argc, int start_index)
 254 {
 255         int             i, j;
 256         uint32_t        n;
 257         uint8_t         found = 0;
 258         size_t          sz = 0;
 259         dis_handle_t    *dhp = NULL;
 260         int             ret = SAVEARGS_NO_ARGS;
 261 
 262         argc = MIN((start_index + argc), INSTR_ARRAY_SIZE);
 263 
 264         if ((dhp = dis_handle_create(DIS_X86_SIZE64, NULL, do_lookup,
 265             do_read)) == NULL)
 266                 return (SAVEARGS_NO_ARGS);
 267 
 268         if (!has_saved_fp(dhp, ins, size)) {
 269                 dis_handle_destroy(dhp);
 270                 return (SAVEARGS_NO_ARGS);
 271         }
 272 
 273         /*
 274          * For each possible style of argument saving, walk the insn stream as
 275          * we've been given it, and set bit N in 'found' if we find the
 276          * instruction saving the Nth argument.
 277          */
 278 
 279         /*
 280          * Compare against regular implementation
 281          */
 282         found = 0;
 283         for (i = 0; i < size; i += sz) {
 284                 sz = instr_size(dhp, ins, i, size);
 285 
 286                 if (sz < 1)
 287                         break;
 288                 else if (sz != 4)
 289                         continue;
 290 
 291                 n = INSTR4(ins, i);
 292 
 293                 for (j = 0; j < argc; j++) {
 294                         if (n == save_instr[j]) {
 295                                 found |= (1 << j);
 296 
 297                                 if (found == ((1 << argc) - 1)) {
 298                                         ret = start_index ?
 299                                             SAVEARGS_STRUCT_ARGS :
 300                                             SAVEARGS_TRAD_ARGS;
 301                                         goto done;
 302                                 }
 303 
 304                                 break;
 305                         }
 306                 }
 307         }
 308 
 309         /*
 310          * Compare against GCC push-based implementation
 311          */
 312         found = 0;
 313         for (i = 0; i < size; i += sz) {
 314                 if ((sz = instr_size(dhp, ins, i, size)) < 1)
 315                         break;
 316 
 317                 for (j = start_index; j < argc; j++) {
 318                         if (sz == 2) /* Two byte */
 319                                 n = INSTR2(ins, i);
 320                         else if (sz == 1)
 321                                 n = INSTR1(ins, i);
 322                         else
 323                                 continue;
 324 
 325                         if (n == save_instr_push[j]) {
 326                                 found |= (1 << (j - start_index));
 327 
 328                                 if (found ==
 329                                     ((1 << (argc - start_index)) - 1)) {
 330                                         ret = SAVEARGS_TRAD_ARGS;
 331                                         goto done;
 332                                 }
 333 
 334                                 break;
 335                         }
 336                 }
 337         }
 338 
 339         /*
 340          * Look for a GCC-style returned structure.
 341          */
 342         found = 0;
 343         if (start_index != 0) {
 344                 for (i = 0; i < size; i += sz) {
 345                         sz = instr_size(dhp, ins, i, size);
 346 
 347                         if (sz < 1)
 348                                 break;
 349                         else if (sz != 4)
 350                                 continue;
 351 
 352                         n = INSTR4(ins, i);
 353 
 354                         /* argc is inclusive of start_index, allow for that */
 355                         for (j = 0; j < (argc - start_index); j++) {
 356                                 if (n == save_instr_sr[j]) {
 357                                         found |= (1 << j);
 358 
 359                                         if (found ==
 360                                             ((1 << (argc - start_index)) - 1)) {
 361                                                 ret = SAVEARGS_TRAD_ARGS;
 362                                                 goto done;
 363                                         }
 364 
 365                                         break;
 366                                 }
 367                         }
 368                 }
 369         }
 370 
 371 done:
 372         dis_handle_destroy(dhp);
 373         return (ret);
 374 }