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  * Copyright 2019 Joyent, Inc.
  28  */
  29 
  30 /*
  31  * The Sun Studio and GCC (patched for opensolaris/illumos) compilers
  32  * implement a argument saving scheme on amd64 via the -Wu,save-args or
  33  * options.  When the option is specified, INTEGER type function arguments
  34  * passed via registers will be saved on the stack immediately after %rbp, and
  35  * will not be modified through out the life of the routine.
  36  *
  37  *                              +--------+
  38  *              %rbp    -->     |  %rbp  |
  39  *                              +--------+
  40  *              -0x8(%rbp)      |  %rdi  |
  41  *                              +--------+
  42  *              -0x10(%rbp)     |  %rsi  |
  43  *                              +--------+
  44  *              -0x18(%rbp)     |  %rdx  |
  45  *                              +--------+
  46  *              -0x20(%rbp)     |  %rcx  |
  47  *                              +--------+
  48  *              -0x28(%rbp)     |  %r8   |
  49  *                              +--------+
  50  *              -0x30(%rbp)     |  %r9   |
  51  *                              +--------+
  52  *
  53  *
  54  * For example, for the following function,
  55  *
  56  * void
  57  * foo(int a1, int a2, int a3, int a4, int a5, int a6, int a7)
  58  * {
  59  * ...
  60  * }
  61  *
  62  * Disassembled code will look something like the following:
  63  *
  64  *     pushq    %rbp
  65  *     movq     %rsp, %rbp
  66  *     subq     $imm8, %rsp                     **
  67  *     movq     %rdi, -0x8(%rbp)
  68  *     movq     %rsi, -0x10(%rbp)
  69  *     movq     %rdx, -0x18(%rbp)
  70  *     movq     %rcx, -0x20(%rbp)
  71  *     movq     %r8, -0x28(%rbp)
  72  *     movq     %r9, -0x30(%rbp)
  73  *     ...
  74  * or
  75  *     pushq    %rbp
  76  *     movq     %rsp, %rbp
  77  *     subq     $imm8, %rsp                     **
  78  *     movq     %r9, -0x30(%rbp)
  79  *     movq     %r8, -0x28(%rbp)
  80  *     movq     %rcx, -0x20(%rbp)
  81  *     movq     %rdx, -0x18(%rbp)
  82  *     movq     %rsi, -0x10(%rbp)
  83  *     movq     %rdi, -0x8(%rbp)
  84  *     ...
  85  * or
  86  *     pushq    %rbp
  87  *     movq     %rsp, %rbp
  88  *     pushq    %rdi
  89  *     pushq    %rsi
  90  *     pushq    %rdx
  91  *     pushq    %rcx
  92  *     pushq    %r8
  93  *     pushq    %r9
  94  *
  95  * **: The space being reserved is in addition to what the current
  96  *     function prolog already reserves.
  97  *
  98  * We loop through the first SAVEARGS_INSN_SEQ_LEN bytes of the function
  99  * looking for each argument saving instruction we would expect to see.
 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 <libdisasm.h>
 112 #include <string.h>
 113 
 114 #include <saveargs.h>
 115 
 116 /*
 117  * Size of the instruction sequence arrays.  It should correspond to
 118  * the maximum number of arguments passed via registers.
 119  */
 120 #define INSTR_ARRAY_SIZE        6
 121 
 122 #define INSTR1(ins, off) (ins[(off)])
 123 #define INSTR2(ins, off) (ins[(off)] + (ins[(off) + 1] << 8))
 124 #define INSTR3(ins, off)        \
 125         (ins[(off)] + (ins[(off) + 1] << 8) + (ins[(off + 2)] << 16))
 126 #define INSTR4(ins, off)        \
 127         (ins[(off)] + (ins[(off) + 1] << 8) + (ins[(off + 2)] << 16) + \
 128         (ins[(off) + 3] << 24))
 129 
 130 /*
 131  * Sun Studio 10 patch implementation saves %rdi first;
 132  * GCC 3.4.3 Sun branch implementation saves them in reverse order.
 133  */
 134 static const uint32_t save_instr[INSTR_ARRAY_SIZE] = {
 135         0xf87d8948,     /* movq %rdi, -0x8(%rbp) */
 136         0xf0758948,     /* movq %rsi, -0x10(%rbp) */
 137         0xe8558948,     /* movq %rdx, -0x18(%rbp) */
 138         0xe04d8948,     /* movq %rcx, -0x20(%rbp) */
 139         0xd845894c,     /* movq %r8, -0x28(%rbp) */
 140         0xd04d894c      /* movq %r9, -0x30(%rbp) */
 141 };
 142 
 143 static const uint16_t save_instr_push[] = {
 144         0x57,   /* pushq %rdi */
 145         0x56,   /* pushq %rsi */
 146         0x52,   /* pushq %rdx */
 147         0x51,   /* pushq %rcx */
 148         0x5041, /* pushq %r8 */
 149         0x5141  /* pushq %r9 */
 150 };
 151 
 152 /*
 153  * If the return type of a function is a structure greater than 16 bytes in
 154  * size, %rdi will contain the address to which it should be stored, and
 155  * arguments will begin at %rsi.  Studio will push all of the normal argument
 156  * registers anyway, GCC will start pushing at %rsi, so we need a separate
 157  * pattern.
 158  */
 159 static const uint32_t save_instr_sr[INSTR_ARRAY_SIZE-1] = {
 160         0xf8758948,     /* movq %rsi,-0x8(%rbp) */
 161         0xf0558948,     /* movq %rdx,-0x10(%rbp) */
 162         0xe84d8948,     /* movq %rcx,-0x18(%rbp) */
 163         0xe045894c,     /* movq %r8,-0x20(%rbp) */
 164         0xd84d894c      /* movq %r9,-0x28(%rbp) */
 165 };
 166 
 167 static const uint8_t save_fp_pushes[] = {
 168         0x55,   /* pushq %rbp */
 169         0xcc    /* int $0x3 */
 170 };
 171 #define NUM_FP_PUSHES (sizeof (save_fp_pushes) / sizeof (save_fp_pushes[0]))
 172 
 173 static const uint32_t save_fp_movs[] = {
 174         0x00e58948,     /* movq %rsp,%rbp, encoding 1 */
 175         0x00ec8b48,     /* movq %rsp,%rbp, encoding 2 */
 176 };
 177 #define NUM_FP_MOVS (sizeof (save_fp_movs) / sizeof (save_fp_movs[0]))
 178 
 179 typedef struct {
 180         uint8_t *data;
 181         size_t size;
 182 } text_t;
 183 
 184 static int
 185 do_read(void *data, uint64_t addr, void *buf, size_t len)
 186 {
 187         text_t  *t = data;
 188 
 189         if (addr >= t->size)
 190                 return (-1);
 191 
 192         len = MIN(len, t->size - addr);
 193 
 194         (void) memcpy(buf, (char *)t->data + addr, len);
 195 
 196         return (len);
 197 }
 198 
 199 /* ARGSUSED */
 200 int
 201 do_lookup(void *data, uint64_t addr, char *buf, size_t buflen, uint64_t *start,
 202     size_t *symlen)
 203 {
 204         /* We don't actually need lookup info */
 205         return (-1);
 206 }
 207 
 208 static int
 209 instr_size(dis_handle_t *dhp, uint8_t *ins, unsigned int i, size_t size)
 210 {
 211         text_t  t;
 212 
 213         t.data = ins;
 214         t.size = size;
 215 
 216         dis_set_data(dhp, &t);
 217         return (dis_instrlen(dhp, i));
 218 }
 219 
 220 static boolean_t
 221 has_saved_fp(dis_handle_t *dhp, uint8_t *ins, int size)
 222 {
 223         int             i, j;
 224         uint32_t        n;
 225         boolean_t       found_push = B_FALSE;
 226         ssize_t         sz = 0;
 227 
 228         for (i = 0; i < size; i += sz) {
 229                 if ((sz = instr_size(dhp, ins, i, size)) < 1)
 230                         return (B_FALSE);
 231 
 232                 if (found_push == B_FALSE) {
 233                         if (sz != 1)
 234                                 continue;
 235 
 236                         n = INSTR1(ins, i);
 237                         for (j = 0; j < NUM_FP_PUSHES; j++)
 238                                 if (save_fp_pushes[j] == n) {
 239                                         found_push = B_TRUE;
 240                                         break;
 241                                 }
 242                 } else {
 243                         if (sz != 3)
 244                                 continue;
 245                         n = INSTR3(ins, i);
 246                         for (j = 0; j < NUM_FP_MOVS; j++)
 247                                 if (save_fp_movs[j] == n)
 248                                         return (B_TRUE);
 249                 }
 250         }
 251 
 252         return (B_FALSE);
 253 }
 254 
 255 int
 256 saveargs_has_args(uint8_t *ins, size_t size, uint_t argc, int start_index)
 257 {
 258         int             i, j;
 259         uint32_t        n;
 260         uint8_t         found = 0;
 261         ssize_t         sz = 0;
 262         dis_handle_t    *dhp = NULL;
 263         int             ret = SAVEARGS_NO_ARGS;
 264 
 265         argc = MIN((start_index + argc), INSTR_ARRAY_SIZE);
 266 
 267         if ((dhp = dis_handle_create(DIS_X86_SIZE64, NULL, do_lookup,
 268             do_read)) == NULL)
 269                 return (SAVEARGS_NO_ARGS);
 270 
 271         if (!has_saved_fp(dhp, ins, size)) {
 272                 dis_handle_destroy(dhp);
 273                 return (SAVEARGS_NO_ARGS);
 274         }
 275 
 276         /*
 277          * For each possible style of argument saving, walk the insn stream as
 278          * we've been given it, and set bit N in 'found' if we find the
 279          * instruction saving the Nth argument.
 280          */
 281 
 282         /*
 283          * Compare against regular implementation
 284          */
 285         found = 0;
 286         for (i = 0; i < size; i += sz) {
 287                 sz = instr_size(dhp, ins, i, size);
 288 
 289                 if (sz < 1)
 290                         break;
 291                 else if (sz != 4)
 292                         continue;
 293 
 294                 n = INSTR4(ins, i);
 295 
 296                 for (j = 0; j < argc; j++) {
 297                         if (n == save_instr[j]) {
 298                                 found |= (1 << j);
 299 
 300                                 if (found == ((1 << argc) - 1)) {
 301                                         ret = start_index ?
 302                                             SAVEARGS_STRUCT_ARGS :
 303                                             SAVEARGS_TRAD_ARGS;
 304                                         goto done;
 305                                 }
 306 
 307                                 break;
 308                         }
 309                 }
 310         }
 311 
 312         /*
 313          * Compare against GCC push-based implementation
 314          */
 315         found = 0;
 316         for (i = 0; i < size; i += sz) {
 317                 if ((sz = instr_size(dhp, ins, i, size)) < 1)
 318                         break;
 319 
 320                 for (j = start_index; j < argc; j++) {
 321                         if (sz == 2) /* Two byte */
 322                                 n = INSTR2(ins, i);
 323                         else if (sz == 1)
 324                                 n = INSTR1(ins, i);
 325                         else
 326                                 continue;
 327 
 328                         if (n == save_instr_push[j]) {
 329                                 found |= (1 << (j - start_index));
 330 
 331                                 if (found ==
 332                                     ((1 << (argc - start_index)) - 1)) {
 333                                         ret = SAVEARGS_TRAD_ARGS;
 334                                         goto done;
 335                                 }
 336 
 337                                 break;
 338                         }
 339                 }
 340         }
 341 
 342         /*
 343          * Look for a GCC-style returned structure.
 344          */
 345         found = 0;
 346         if (start_index != 0) {
 347                 for (i = 0; i < size; i += sz) {
 348                         sz = instr_size(dhp, ins, i, size);
 349 
 350                         if (sz < 1)
 351                                 break;
 352                         else if (sz != 4)
 353                                 continue;
 354 
 355                         n = INSTR4(ins, i);
 356 
 357                         /* argc is inclusive of start_index, allow for that */
 358                         for (j = 0; j < (argc - start_index); j++) {
 359                                 if (n == save_instr_sr[j]) {
 360                                         found |= (1 << j);
 361 
 362                                         if (found ==
 363                                             ((1 << (argc - start_index)) - 1)) {
 364                                                 ret = SAVEARGS_TRAD_ARGS;
 365                                                 goto done;
 366                                         }
 367 
 368                                         break;
 369                                 }
 370                         }
 371                 }
 372         }
 373 
 374 done:
 375         dis_handle_destroy(dhp);
 376         return (ret);
 377 }