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 }