/* First tried something like this, but we hit too many issues in decode and encode */ bool compare_pages(void *drcontext, byte *start1, byte *start2) { byte *p1 = start1, *p2 = start2; int skipped_bytes = 0, identical_skipped_bytes = 0; while (p1 < start1 + PAGE_SIZE) { int instr_size = decode_sizeof(drcontext, p1, NULL _IF_X64(NULL)); if (p1 + instr_size > start1 + PAGE_SIZE) { /* We're overlapping the end of the page, skip these. */ int end_skip = start1 + PAGE_SIZE - p1; VVERBOSE_PRINT("Passing PAGE_END %d bytes", end_skip); skipped_bytes += end_skip; if (memcmp(p1, p2, end_skip) == 0) identical_skipped_bytes += end_skip; break; } if (decode_sizeof(drcontext, p2, NULL _IF_X64(NULL)) != instr_size) { VVERBOSE_PRINT("Instruction alignment mismatch\n"); return false; } /* assumption - instructions <= 4 bytes in size won't have relocations */ if (instr_size < 5) { if (memcmp(p1, p2, instr_size) != 0) { VVERBOSE_PRINT("Difference found in small instr\n"); return false; } p1 += size; p2 += size; } else { /* guess if there could be a relocation */ instr_t *instr1 = instr_create(drcontext); instr_t *instr2 = instr_create(drcontext); p1 = decode(drcontext, p1, instr1); p2 = decode(drcontext, p2, instr2); if (p1 - start1 != p2 - start2) { VVERBOSE_PRINT("Instruction alignment mismatch on full decode\n"); /* Fixme - free instr, don't expect this to happen */ return false; } if (instr_get_num_srcs(instr1) != instr_get_num_srcs(instr2) || instr_get_num_dsts(instr1) != instr_get_num_dsts(instr2)) { VVERBOSE_PRINT("Full decode operand mismatch"); return false; } for (i = instr_get_num_srcs(instr1); i > 0; i--) { opnd_t opnd = instr_get_src(instr1, i); if (opnd_is_immed_int(opnd) && opnd_get_immed_int(opnd) > 0x10000) { instr_set_src(instr1, i, opnd_create_immed_int(opnd_get_immed_int(opnd), opnd_get_size(opnd))); } } } } }
/* PR 332254: test xchg vs nop */ static void test_nop_xchg(void *dc) { /* 0x0000000000671460 87 c0 xchg %eax %eax -> %eax %eax * 0x0000000000671460 48 87 c0 xchg %rax %rax -> %rax %rax * 0x0000000000671460 41 87 c0 xchg %r8d %eax -> %r8d %eax * 0x0000000000671460 46 90 nop * 0x0000000000671460 4e 90 nop * 0x0000000000671460 41 90 xchg %r8d %eax -> %r8d %eax */ instr_t *instr; instr = INSTR_CREATE_xchg(dc, opnd_create_reg(REG_EAX), opnd_create_reg(REG_EAX)); test_instr_encode(dc, instr, 2); #ifdef X64 /* we don't do the optimal "48 90" instead of "48 87 c0" */ instr = INSTR_CREATE_xchg(dc, opnd_create_reg(REG_RAX), opnd_create_reg(REG_RAX)); test_instr_encode(dc, instr, 3); /* we don't do the optimal "41 90" instead of "41 87 c0" */ instr = INSTR_CREATE_xchg(dc, opnd_create_reg(REG_R8D), opnd_create_reg(REG_EAX)); test_instr_encode(dc, instr, 3); /* ensure we treat as nop and NOT xchg if doesn't have rex.b */ buf[0] = 0x46; buf[1] = 0x90; instr = instr_create(dc); # if VERBOSE disassemble_with_info(dc, buf, STDOUT, true, true); # endif decode(dc, buf, instr); ASSERT(instr_get_opcode(instr) == OP_nop); instr_destroy(dc, instr); buf[0] = 0x4e; buf[1] = 0x90; instr = instr_create(dc); # if VERBOSE disassemble_with_info(dc, buf, STDOUT, true, true); # endif decode(dc, buf, instr); ASSERT(instr_get_opcode(instr) == OP_nop); instr_destroy(dc, instr); buf[0] = 0x41; buf[1] = 0x90; instr = instr_create(dc); # if VERBOSE disassemble_with_info(dc, buf, STDOUT, true, true); # endif decode(dc, buf, instr); ASSERT(instr_get_opcode(instr) == OP_xchg); instr_destroy(dc, instr); #endif }
void memory_write(void *pc, void *prev_pc) { void *drcontext = dr_get_current_drcontext(); instr_t *instr = instr_create(drcontext); dr_mcontext_t mctx; opnd_t dst; ctx_t ctx; pc = dr_app_pc_for_decoding(pc); mctx.flags = DR_MC_CONTROL|DR_MC_INTEGER; mctx.size = sizeof(mctx); dr_get_mcontext(drcontext, &mctx); instr_init(drcontext, instr); if (!decode(drcontext, pc, instr)) { dr_printf("Decode of instruction at %p failed\n", pc); return; } ctx.addr = prev_pc; ctx.dr_addr = prev_pc; for (int i = 0; i < instr_num_dsts(instr); i++) { dst = instr_get_dst(instr, i); check_opnd(dst, pc, 0, drcontext, &mctx, &ctx); } instr_destroy(drcontext, instr); }
/* emits the instruction to buf (for tests that wish to do additional checks on * the output) */ static void test_instr_encode_and_decode(void *dc, instr_t *instr, uint len_expect, /* also checks one operand's size */ bool src, uint opnum, opnd_size_t sz, uint bytes) { opnd_t op; opnd_size_t opsz; instr_t *decin; uint len; byte *pc = instr_encode(dc, instr, buf); len = (int) (pc - (byte *)buf); #if VERBOSE disassemble_with_info(dc, buf, STDOUT, true, true); #endif ASSERT(len == len_expect); decin = instr_create(dc); decode(dc, buf, decin); ASSERT(instr_same(instr, decin)); /* PR 245805: variable sizes should be resolved on decode */ if (src) op = instr_get_src(decin, opnum); else op = instr_get_dst(decin, opnum); opsz = opnd_get_size(op); ASSERT(opsz == sz && opnd_size_in_bytes(opsz) == bytes); instr_destroy(dc, instr); instr_destroy(dc, decin); }
/* returns false on failure */ static bool decode_function(void *dcontext, byte *entry) { byte *pc, *pre_pc; int num_instr = 0; bool found_ret = false; instr_t *instr; if (entry == NULL) return false; instr = instr_create(dcontext); pc = entry; while (true) { instr_reset(dcontext, instr); pre_pc = pc; pc = decode(dcontext, pc, instr); instr_set_translation(instr, pre_pc); dr_print_instr(dcontext, STDOUT, instr, ""); if (instr_is_return(instr)) { found_ret = true; break; } num_instr++; if (num_instr > MAX_INSTRS_IN_FUNCTION) { print("ERROR: hit max instr limit %d\n", MAX_INSTRS_IN_FUNCTION); break; } } instr_destroy(dcontext, instr); return found_ret; }
/* Decode instruction from callee and return the next_pc to be decoded. */ static app_pc decode_callee_instr(dcontext_t *dcontext, callee_info_t *ci, app_pc instr_pc) { instrlist_t *ilist = ci->ilist; instr_t *instr; app_pc next_pc = NULL; instr = instr_create(GLOBAL_DCONTEXT); instrlist_append(ilist, instr); ci->num_instrs++; TRY_EXCEPT(dcontext, { next_pc = decode(GLOBAL_DCONTEXT, instr_pc, instr); }, { /* EXCEPT */
/* emits the instruction to buf (for tests that wish to do additional checks on * the output) */ static void test_instr_encode(void *dc, instr_t *instr, uint len_expect) { instr_t *decin; uint len; byte *pc = instr_encode(dc, instr, buf); len = (int) (pc - (byte *)buf); #if VERBOSE disassemble_with_info(dc, buf, STDOUT, true, true); #endif ASSERT(len == len_expect); decin = instr_create(dc); decode(dc, buf, decin); ASSERT(instr_same(instr, decin)); instr_destroy(dc, instr); instr_destroy(dc, decin); }
static void look_for_usercall(void *dcontext, byte *entry, const char *sym, LOADED_IMAGE *img, const char *modpath) { bool found_push_imm = false; int imm = 0; app_pc pc, pre_pc; instr_t *instr; if (entry == NULL) return; instr = instr_create(dcontext); pc = entry; while (true) { instr_reset(dcontext, instr); pre_pc = pc; pc = decode(dcontext, pc, instr); if (verbose) { instr_set_translation(instr, pre_pc); dr_print_instr(dcontext, STDOUT, instr, ""); } if (pc == NULL || !instr_valid(instr)) break; if (instr_get_opcode(instr) == OP_push_imm) { found_push_imm = true; imm = (int) opnd_get_immed_int(instr_get_src(instr, 0)); } else if (instr_is_call_direct(instr) && found_push_imm) { app_pc tgt = opnd_get_pc(instr_get_target(instr)); bool found = false; int i; for (i = 0; i < NUM_USERCALL; i++) { if (tgt == usercall_addr[i]) { dr_printf("Call #0x%02x to %s at %s+0x%x\n", imm, usercall_names[i], sym, pre_pc - entry); found = true; break; } } if (found) break; } else if (instr_is_return(instr)) break; if (pc - entry > MAX_BYTES_BEFORE_USERCALL) break; } instr_destroy(dcontext, instr); }
// return the addr of the previous instructions // a bad addr can be return in some specific case but generally work good void *get_prev_instr_pc(void *pc, void *drc) { instr_t *instr = instr_create(drc); void *tmp_pc; for (int ct = 1; ; ct++) { tmp_pc = dr_app_pc_for_decoding(pc - ct); if (decode(drc, tmp_pc, instr)) { if (instr_is_call(instr) && decode_next_pc(drc, tmp_pc) == pc) break; } instr_reuse(drc, instr); } instr_destroy(drc, instr); return tmp_pc; }
static void test_instr_opnds(void *dc) { /* Verbose disasm looks like this: * 32-bit: * 0x080f1ae0 ff 25 e7 1a 0f 08 jmp 0x080f1ae7 * 0x080f1ae6 b8 ef be ad de mov $0xdeadbeef -> %eax * 0x080f1ae0 a0 e6 1a 0f 08 mov 0x080f1ae6 -> %al * 0x080f1ae5 b8 ef be ad de mov $0xdeadbeef -> %eax * 64-bit: * 0x00000000006b8de0 ff 25 02 00 00 00 jmp <rel> 0x00000000006b8de8 * 0x00000000006b8de6 48 b8 ef be ad de 00 mov $0x00000000deadbeef -> %rax * 00 00 00 * 0x00000000006b8de0 8a 05 02 00 00 00 mov <rel> 0x00000000006b8de8 -> %al * 0x00000000006b8de6 48 b8 ef be ad de 00 mov $0x00000000deadbeef -> %rax * 00 00 00 */ instrlist_t *ilist; instr_t *tgt, *instr; byte *pc; short disp; ilist = instrlist_create(dc); /* test mem instr as ind jmp target */ tgt = INSTR_CREATE_mov_imm(dc, opnd_create_reg(DR_REG_XAX), opnd_create_immed_int(0xdeadbeef, OPSZ_PTR)); /* skip rex+opcode */ disp = IF_X64_ELSE(2,1); instrlist_append(ilist, INSTR_CREATE_jmp_ind (dc, opnd_create_mem_instr(tgt, disp, OPSZ_PTR))); instrlist_append(ilist, tgt); pc = instrlist_encode(dc, ilist, buf, true/*instr targets*/); ASSERT(pc != NULL); instrlist_clear(dc, ilist); #if VERBOSE pc = disassemble_with_info(dc, buf, STDOUT, true, true); pc = disassemble_with_info(dc, pc, STDOUT, true, true); #endif pc = buf; instr = instr_create(dc); pc = decode(dc, pc, instr); ASSERT(pc != NULL); ASSERT(instr_get_opcode(instr) == OP_jmp_ind); #ifdef X64 ASSERT(opnd_is_rel_addr(instr_get_src(instr, 0))); ASSERT(opnd_get_addr(instr_get_src(instr, 0)) == pc + disp); #else ASSERT(opnd_is_base_disp(instr_get_src(instr, 0))); ASSERT(opnd_get_base(instr_get_src(instr, 0)) == REG_NULL); ASSERT(opnd_get_index(instr_get_src(instr, 0)) == REG_NULL); ASSERT(opnd_get_disp(instr_get_src(instr, 0)) == (ptr_int_t)pc + disp); #endif /* test mem instr as TYPE_O */ tgt = INSTR_CREATE_mov_imm(dc, opnd_create_reg(DR_REG_XAX), opnd_create_immed_int(0xdeadbeef, OPSZ_PTR)); /* skip rex+opcode */ disp = IF_X64_ELSE(2,1); instrlist_append(ilist, INSTR_CREATE_mov_ld (dc, opnd_create_reg(DR_REG_AL), opnd_create_mem_instr(tgt, disp, OPSZ_1))); instrlist_append(ilist, tgt); pc = instrlist_encode(dc, ilist, buf, true/*instr targets*/); ASSERT(pc != NULL); instrlist_clear(dc, ilist); #if VERBOSE pc = disassemble_with_info(dc, buf, STDOUT, true, true); pc = disassemble_with_info(dc, pc, STDOUT, true, true); #endif pc = buf; instr_reset(dc, instr); pc = decode(dc, pc, instr); ASSERT(pc != NULL); ASSERT(instr_get_opcode(instr) == OP_mov_ld); #ifdef X64 ASSERT(opnd_is_rel_addr(instr_get_src(instr, 0))); ASSERT(opnd_get_addr(instr_get_src(instr, 0)) == pc + disp); #else ASSERT(opnd_is_base_disp(instr_get_src(instr, 0))); ASSERT(opnd_get_base(instr_get_src(instr, 0)) == REG_NULL); ASSERT(opnd_get_index(instr_get_src(instr, 0)) == REG_NULL); ASSERT(opnd_get_disp(instr_get_src(instr, 0)) == (ptr_int_t)pc + disp); #endif instr_free(dc, instr); instrlist_destroy(dc, ilist); }
/* make sure the following are consistent (though they could still all be wrong :)) * with respect to instr length and opcode: * - decode_fast * - decode * - INSTR_CREATE_ * - encode */ static void test_all_opcodes(void *dc) { byte *pc, *next_pc; byte *end; instrlist_t *ilist = instrlist_create(dc); instr_t *instr; /* we cannot pass on variadic args as separate args to another * macro, so we must split ours by # args (xref PR 208603) */ # define MEMARG(sz) (opnd_create_base_disp(REG_XCX, REG_NULL, 0, 0x37, sz)) # define IMMARG(sz) opnd_create_immed_int(37, sz) # define TGTARG opnd_create_instr(instrlist_last(ilist)) # define REGARG(reg) opnd_create_reg(REG_##reg) # define X86_ONLY 1 # define X64_ONLY 2 # define OPCODE(opc, icnm, ...) \ int len_##icnm; # include "ir_0args.h" # include "ir_1args.h" # include "ir_2args.h" # include "ir_3args.h" # include "ir_4args.h" # undef OPCODE /* we can encode+fast-decode some instrs cross-platform but we * leave that testing to the regression run on that platform */ # define OPCODE(opc, icnm, flags) do { \ if ((flags & IF_X64_ELSE(X86_ONLY, X64_ONLY)) == 0) { \ instrlist_append(ilist, INSTR_CREATE_##icnm(dc)); \ len_##icnm = instr_length(dc, instrlist_last(ilist)); \ } } while (0); # include "ir_0args.h" # undef OPCODE # define OPCODE(opc, icnm, flags, arg1) do { \ if ((flags & IF_X64_ELSE(X86_ONLY, X64_ONLY)) == 0) { \ instrlist_append(ilist, INSTR_CREATE_##icnm(dc, arg1)); \ len_##icnm = instr_length(dc, instrlist_last(ilist)); \ } } while (0); # include "ir_1args.h" # undef OPCODE # define OPCODE(opc, icnm, flags, arg1, arg2) do { \ if ((flags & IF_X64_ELSE(X86_ONLY, X64_ONLY)) == 0) { \ instrlist_append(ilist, INSTR_CREATE_##icnm(dc, arg1, arg2)); \ len_##icnm = instr_length(dc, instrlist_last(ilist)); \ } } while (0); # include "ir_2args.h" # undef OPCODE # define OPCODE(opc, icnm, flags, arg1, arg2, arg3) do { \ if ((flags & IF_X64_ELSE(X86_ONLY, X64_ONLY)) == 0) { \ instrlist_append(ilist, INSTR_CREATE_##icnm(dc, arg1, arg2, arg3)); \ len_##icnm = instr_length(dc, instrlist_last(ilist)); \ } } while (0); # include "ir_3args.h" # undef OPCODE # define OPCODE(opc, icnm, flags, arg1, arg2, arg3, arg4) do { \ if ((flags & IF_X64_ELSE(X86_ONLY, X64_ONLY)) == 0) { \ instrlist_append(ilist, INSTR_CREATE_##icnm(dc, arg1, arg2, arg3, arg4)); \ len_##icnm = instr_length(dc, instrlist_last(ilist)); \ } } while (0); # include "ir_4args.h" # undef OPCODE end = instrlist_encode(dc, ilist, buf, false); instr = instr_create(dc); pc = buf; # define OPCODE(opc, icnm, flags, ...) do { \ if ((flags & IF_X64_ELSE(X86_ONLY, X64_ONLY)) == 0 && len_##icnm != 0) { \ instr_reset(dc, instr); \ next_pc = decode(dc, pc, instr); \ ASSERT((next_pc - pc) == decode_sizeof(dc, pc, NULL _IF_X64(NULL))); \ ASSERT((next_pc - pc) == len_##icnm); \ ASSERT(instr_get_opcode(instr) == OP_##opc); \ pc = next_pc; \ } } while (0); # include "ir_0args.h" # include "ir_1args.h" # include "ir_2args.h" # include "ir_3args.h" # include "ir_4args.h" # undef OPCODE #if VERBOSE for (pc = buf; pc < end; ) pc = disassemble_with_info(dc, pc, STDOUT, true, true); #endif instr_destroy(dc, instr); instrlist_clear_and_destroy(dc, ilist); }
void Shade::detour(void *address, void *target, void *&trampoline) { const size_t instr_max = 17; auto list = instrlist_create(dr); byte instr_data[instr_max]; byte *current = (byte *)address; byte *min_pos = (byte *)address + 5; size_t size = 0; while(current < min_pos) { read(current, instr_data, instr_max); auto instr = instr_create(dr); byte *decoded = decode_from_copy(dr, instr_data, current, instr); if(!decoded) error("Unknown instruction"); instrlist_append(list, instr); instr_make_persistent(dr, instr); current += (size_t)(decoded - instr_data); size += instr_length(dr, instr); } auto instr = INSTR_CREATE_jmp(dr, opnd_create_pc(current)); size += instr_length(dr, instr); instrlist_append(list, instr); auto local_trampoline = alloca(size); if(!local_trampoline) error("Out of memory"); void *remote = code_section.allocate(size, 4); if(!instrlist_encode_to_copy(dr, list, (byte *)local_trampoline, (byte *)remote, 0, true)) error("Unable to encode instructions"); instrlist_clear_and_destroy(dr, list); write(remote, local_trampoline, size); trampoline = remote; char code[5]; DWORD offset = (size_t)target - (size_t)address - 5; code[0] = 0xE9; *(DWORD *)(code + 1) = offset; access(address, 5, [&] { write(address, code, 5); }); }
/* returns false on failure */ static bool decode_syscall_num(void *dcontext, byte *entry, syscall_info_t *info, LOADED_IMAGE *img) { /* FIXME: would like to fail gracefully rather than have a DR assertion * on non-code! => use DEBUG=0 INTERNAL=1 DR build! */ bool found_syscall = false, found_eax = false, found_edx = false, found_ecx = false; bool found_ret = false; byte *pc, *pre_pc; int num_instr = 0; instr_t *instr; byte *preferred = get_preferred_base(img); if (entry == NULL) return false; info->num_args = -1; /* if find sysnum but not args */ info->sysnum = -1; info->fixup_index = -1; instr = instr_create(dcontext); pc = entry; /* FIXME - we don't support decoding 64bit instructions in 32bit mode, but I want * this to work on 32bit machines. Hack fix based on the wrapper pattern, we skip * the first instruction (mov r10, rcx) here, the rest should decode ok. * Xref PR 236203. */ if (expect_x64 && *pc == 0x4c && *(pc+1) == 0x8b && *(pc+2) == 0xd1) pc += 3; while (true) { instr_reset(dcontext, instr); pre_pc = pc; pc = decode(dcontext, pc, instr); if (verbose) { instr_set_translation(instr, pre_pc); dr_print_instr(dcontext, STDOUT, instr, ""); } if (pc == NULL || !instr_valid(instr)) break; if (instr_is_syscall(instr) || instr_is_call_indirect(instr)) { /* If we see a syscall instr or an indirect call which is not syscall, * we assume this is not a syscall wrapper. */ found_syscall = process_syscall_instr(dcontext, instr, found_eax, found_edx); if (!found_syscall) break; /* assume not a syscall wrapper, give up gracefully */ } else if (instr_is_return(instr)) { /* we must break on return to avoid case like win8 x86 * which has sysenter callee adjacent-"inlined" * ntdll!NtYieldExecution: * 77d7422c b801000000 mov eax,1 * 77d74231 e801000000 call ntdll!NtYieldExecution+0xb (77d74237) * 77d74236 c3 ret * 77d74237 8bd4 mov edx,esp * 77d74239 0f34 sysenter * 77d7423b c3 ret */ if (!found_ret) { process_ret(instr, info); found_ret = true; } break; } else if (instr_get_opcode(instr) == OP_call) { found_syscall = process_syscall_call(dcontext, pc, instr, found_eax, found_edx); /* If we see a call and it is not a sysenter callee, * we assume this is not a syscall wrapper. */ if (!found_syscall) break; /* assume not a syscall wrapper, give up gracefully */ } else if (instr_is_cti(instr)) { /* We expect only ctis like ret or ret imm, syscall, and call, which are * handled above. Give up gracefully if we hit any other cti. * XXX: what about jmp to shared ret (seen in the past on some syscalls)? */ /* Update: win10 TH2 1511 x64 has a cti: * ntdll!NtContinue: * 00007ff9`13185630 4c8bd1 mov r10,rcx * 00007ff9`13185633 b843000000 mov eax,43h * 00007ff9`13185638 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1 * 00007ff9`13185640 7503 jne ntdll!NtContinue+0x15 (00007ff9`13185645) * 00007ff9`13185642 0f05 syscall * 00007ff9`13185644 c3 ret * 00007ff9`13185645 cd2e int 2Eh * 00007ff9`13185647 c3 ret */ if (expect_x64 && instr_is_cbr(instr) && opnd_get_pc(instr_get_target(instr)) == pc + 3/*syscall;ret*/) { /* keep going */ } else break; } else if ((!found_eax || !found_edx || !found_ecx) && instr_get_opcode(instr) == OP_mov_imm && opnd_is_reg(instr_get_dst(instr, 0))) { if (!found_eax && opnd_get_reg(instr_get_dst(instr, 0)) == REG_EAX) { info->sysnum = (int) opnd_get_immed_int(instr_get_src(instr, 0)); found_eax = true; } else if (!found_edx && opnd_get_reg(instr_get_dst(instr, 0)) == REG_EDX) { uint imm = (uint) opnd_get_immed_int(instr_get_src(instr, 0)); if (imm == 0x7ffe0300 || /* On Win10 the immed is ntdll!Wow64SystemServiceCall */ (expect_wow && imm > (ptr_uint_t)preferred && imm < (ptr_uint_t)preferred + img->SizeOfImage)) found_edx = true; } else if (!found_ecx && opnd_get_reg(instr_get_dst(instr, 0)) == REG_ECX) { found_ecx = true; info->fixup_index = (int) opnd_get_immed_int(instr_get_src(instr, 0)); } } else if (instr_get_opcode(instr) == OP_xor && opnd_is_reg(instr_get_src(instr, 0)) && opnd_get_reg(instr_get_src(instr, 0)) == REG_ECX && opnd_is_reg(instr_get_dst(instr, 0)) && opnd_get_reg(instr_get_dst(instr, 0)) == REG_ECX) { /* xor to 0 */ found_ecx = true; info->fixup_index = 0; } num_instr++; if (num_instr > MAX_INSTRS_BEFORE_SYSCALL) /* wrappers should be short! */ break; /* avoid weird cases like NPXEMULATORTABLE */ } instr_destroy(dcontext, instr); return found_syscall; }
/* returns false on failure */ static bool decode_syscall_num(void *dcontext, byte *entry, syscall_info_t *info) { /* FIXME: would like to fail gracefully rather than have a DR assertion * on non-code! => use DEBUG=0 INTERNAL=1 DR build! */ bool found_syscall = false, found_eax = false, found_edx = false, found_ecx = false; bool found_ret = false; byte *pc; int num_instr = 0; instr_t *instr; if (entry == NULL) return false; info->num_args = -1; /* if find sysnum but not args */ info->sysnum = -1; info->fixup_index = -1; instr = instr_create(dcontext); pc = entry; /* FIXME - we don't support decoding 64bit instructions in 32bit mode, but I want * this to work on 32bit machines. Hack fix based on the wrapper pattern, we skip * the first instruction (mov r10, rcx) here, the rest should decode ok. * Xref PR 236203. */ if (expect_x64 && *pc == 0x4c && *(pc+1) == 0x8b && *(pc+2) == 0xd1) pc += 3; while (true) { instr_reset(dcontext, instr); pc = decode(dcontext, pc, instr); if (verbose) dr_print_instr(dcontext, STDOUT, instr, ""); if (pc == NULL || !instr_valid(instr)) break; /* ASSUMPTION: a mov imm of 0x7ffe0300 into edx followed by an * indirect call via edx is a system call on XP and later * On XP SP1 it's call *edx, while on XP SP2 it's call *(edx) * For wow it's a call through fs. * FIXME - core exports various is_*_syscall routines (such as * instr_is_wow64_syscall()) which we could use here instead of * duplicating if they were more flexible about when they could * be called (instr_is_wow64_syscall() for ex. asserts if not * in a wow process). */ if (/* int 2e or x64 or win8 sysenter */ (instr_is_syscall(instr) && found_eax && (expect_int2e || expect_x64 || expect_sysenter)) || /* sysenter case */ (expect_sysenter && found_edx && found_eax && instr_is_call_indirect(instr) && /* XP SP{0,1}, 2003 SP0: call *edx */ ((opnd_is_reg(instr_get_target(instr)) && opnd_get_reg(instr_get_target(instr)) == REG_EDX) || /* XP SP2, 2003 SP1: call *(edx) */ (opnd_is_base_disp(instr_get_target(instr)) && opnd_get_base(instr_get_target(instr)) == REG_EDX && opnd_get_index(instr_get_target(instr)) == REG_NULL && opnd_get_disp(instr_get_target(instr)) == 0))) || /* wow case * we don't require found_ecx b/c win8 does not use ecx */ (expect_wow && found_eax && instr_is_call_indirect(instr) && opnd_is_far_base_disp(instr_get_target(instr)) && opnd_get_base(instr_get_target(instr)) == REG_NULL && opnd_get_index(instr_get_target(instr)) == REG_NULL && opnd_get_segment(instr_get_target(instr)) == SEG_FS)) { found_syscall = true; } else if (instr_is_return(instr)) { if (!found_ret) { process_ret(instr, info); found_ret = true; } break; } else if (instr_is_cti(instr)) { if (instr_get_opcode(instr) == OP_call) { /* handle win8 x86 which has sysenter callee adjacent-"inlined" * ntdll!NtYieldExecution: * 77d7422c b801000000 mov eax,1 * 77d74231 e801000000 call ntdll!NtYieldExecution+0xb (77d74237) * 77d74236 c3 ret * 77d74237 8bd4 mov edx,esp * 77d74239 0f34 sysenter * 77d7423b c3 ret */ byte *tgt; assert(opnd_is_pc(instr_get_target(instr))); tgt = opnd_get_pc(instr_get_target(instr)); /* we expect only ret or ret imm, and possibly some nops (in gdi32). * XXX: what about jmp to shared ret (seen in the past on some syscalls)? */ if (tgt > pc && tgt <= pc + 16) { bool ok = false; do { if (pc == tgt) { ok = true; break; } instr_reset(dcontext, instr); pc = decode(dcontext, pc, instr); if (verbose) dr_print_instr(dcontext, STDOUT, instr, ""); if (instr_is_return(instr)) { process_ret(instr, info); found_ret = true; } else if (!instr_is_nop(instr)) break; num_instr++; } while (num_instr <= MAX_INSTRS_BEFORE_SYSCALL); if (ok) continue; } } /* assume not a syscall wrapper if we hit a cti */ break; /* give up gracefully */ } else if ((!found_eax || !found_edx || !found_ecx) && instr_get_opcode(instr) == OP_mov_imm && opnd_is_reg(instr_get_dst(instr, 0))) { if (!found_eax && opnd_get_reg(instr_get_dst(instr, 0)) == REG_EAX) { info->sysnum = (int) opnd_get_immed_int(instr_get_src(instr, 0)); found_eax = true; } else if (!found_edx && opnd_get_reg(instr_get_dst(instr, 0)) == REG_EDX) { int imm = (int) opnd_get_immed_int(instr_get_src(instr, 0)); if (imm == 0x7ffe0300) found_edx = true; } else if (!found_ecx && opnd_get_reg(instr_get_dst(instr, 0)) == REG_ECX) { found_ecx = true; info->fixup_index = (int) opnd_get_immed_int(instr_get_src(instr, 0)); } } else if (instr_get_opcode(instr) == OP_xor && opnd_is_reg(instr_get_src(instr, 0)) && opnd_get_reg(instr_get_src(instr, 0)) == REG_ECX && opnd_is_reg(instr_get_dst(instr, 0)) && opnd_get_reg(instr_get_dst(instr, 0)) == REG_ECX) { /* xor to 0 */ found_ecx = true; info->fixup_index = 0; } num_instr++; if (num_instr > MAX_INSTRS_BEFORE_SYSCALL) /* wrappers should be short! */ break; /* avoid weird cases like NPXEMULATORTABLE */ } instr_destroy(dcontext, instr); return found_syscall; }