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 }