| /* ********************************************************** |
| * Copyright (c) 2011-2014 Google, Inc. All rights reserved. |
| * Copyright (c) 2001-2009 VMware, Inc. All rights reserved. |
| * **********************************************************/ |
| |
| /* |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * * Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * * Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * * Neither the name of VMware, Inc. nor the names of its contributors may be |
| * used to endorse or promote products derived from this software without |
| * specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
| * DAMAGE. |
| */ |
| |
| /* Copyright (c) 2003-2007 Determina Corp. */ |
| /* Copyright (c) 2001-2003 Massachusetts Institute of Technology */ |
| /* Copyright (c) 2001 Hewlett-Packard Company */ |
| |
| /* disassemble.c -- printing of instructions |
| * |
| * Note that when printing out instructions: |
| * Uses DR syntax of "srcs -> dsts" including implicit operands, unless |
| * -syntax_att (AT&T syntax) or -syntax_intel (Intel syntax) is specified. |
| * See the info pages for "as" for details on the differences. |
| */ |
| |
| /* |
| * XXX disassembly discrepancies: |
| * 1) I print "%st(0),%st(1)", gdb prints "%st,%st(1)" |
| * 2) I print movzx, gdb prints movzw (with an 'l' suffix tacked on) |
| * 3) gdb says bound and leave are to be printed "Intel order", not AT&T ?!? |
| * From gdb: "The enter and bound instructions are printed with operands |
| * in the same order as the intel book; everything else is printed in |
| * reverse order." |
| */ |
| |
| #include "../globals.h" |
| #include "arch.h" |
| #include "instr.h" |
| #include "decode.h" |
| #include "decode_fast.h" |
| #include "disassemble.h" |
| #include "../module_shared.h" |
| #include <string.h> |
| |
| /* these are only needed for symbolic address lookup: */ |
| #include "../fragment.h" /* for fragment_pclookup */ |
| #include "../link.h" /* for linkstub lookup */ |
| |
| #include "../fcache.h" /* for in_fcache */ |
| |
| #if defined(INTERNAL) || defined(DEBUG) || defined(CLIENT_INTERFACE) |
| |
| #ifdef DEBUG |
| /* case 10450: give messages to clients */ |
| /* we can't undef ASSERT b/c of DYNAMO_OPTION */ |
| # undef ASSERT_TRUNCATE |
| # undef ASSERT_BITFIELD_TRUNCATE |
| # undef ASSERT_NOT_REACHED |
| # define ASSERT_TRUNCATE DO_NOT_USE_ASSERT_USE_CLIENT_ASSERT_INSTEAD |
| # define ASSERT_BITFIELD_TRUNCATE DO_NOT_USE_ASSERT_USE_CLIENT_ASSERT_INSTEAD |
| # define ASSERT_NOT_REACHED DO_NOT_USE_ASSERT_USE_CLIENT_ASSERT_INSTEAD |
| #endif |
| |
| /**************************************************************************** |
| * Arch-specific routines |
| */ |
| |
| int |
| print_bytes_to_buffer(char *buf, size_t bufsz, size_t *sofar INOUT, |
| byte *pc, byte *next_pc, instr_t *instr); |
| |
| void |
| print_extra_bytes_to_buffer(char *buf, size_t bufsz, size_t *sofar INOUT, |
| byte *pc, byte *next_pc, int extra_sz, |
| const char *extra_bytes_prefix); |
| |
| void |
| opnd_base_disp_scale_disassemble(char *buf, size_t bufsz, size_t *sofar INOUT, |
| opnd_t opnd); |
| |
| bool |
| opnd_disassemble_noimplicit(char *buf, size_t bufsz, size_t *sofar INOUT, |
| dcontext_t *dcontext, instr_t *instr, |
| byte optype, opnd_t opnd, bool prev, bool multiple_encodings); |
| |
| const char * |
| instr_opcode_name_arch(instr_t *instr, const instr_info_t *info); |
| |
| const char * |
| instr_opcode_name_suffix_arch(instr_t *instr); |
| |
| void |
| print_instr_prefixes(dcontext_t *dcontext, instr_t *instr, |
| char *buf, size_t bufsz, size_t *sofar INOUT); |
| |
| int |
| print_opcode_suffix(instr_t *instr, char *buf, size_t bufsz, size_t *sofar INOUT); |
| |
| /**************************************************************************** |
| * Printing of instructions |
| */ |
| |
| void |
| disassemble_options_init(void) |
| { |
| dr_disasm_flags_t flags = DYNAMO_OPTION(disasm_mask); |
| if (DYNAMO_OPTION(syntax_intel)) { |
| flags |= DR_DISASM_INTEL; |
| flags &= ~DR_DISASM_ATT; /* mutually exclusive */ |
| } |
| if (DYNAMO_OPTION(syntax_att)) { |
| flags |= DR_DISASM_ATT; |
| flags &= ~DR_DISASM_INTEL; /* mutually exclusive */ |
| } |
| /* This option is separate as it's not strictly a disasm style */ |
| dynamo_options.decode_strict = TEST(DR_DISASM_STRICT_INVALID, flags); |
| if (DYNAMO_OPTION(decode_strict)) |
| flags |= DR_DISASM_STRICT_INVALID; /* for completeness */ |
| dynamo_options.disasm_mask = flags; |
| } |
| |
| DR_API |
| void |
| disassemble_set_syntax(dr_disasm_flags_t flags) |
| { |
| #ifndef STANDALONE_DECODER |
| options_make_writable(); |
| #endif |
| dynamo_options.disasm_mask = flags; |
| /* This option is separate as it's not strictly a disasm style */ |
| dynamo_options.decode_strict = TEST(DR_DISASM_STRICT_INVALID, flags); |
| #ifndef STANDALONE_DECODER |
| options_restore_readonly(); |
| #endif |
| } |
| |
| static void |
| internal_instr_disassemble(char *buf, size_t bufsz, size_t *sofar INOUT, |
| dcontext_t *dcontext, instr_t *instr); |
| |
| static inline const char * |
| immed_prefix(void) |
| { |
| return (TEST(DR_DISASM_INTEL, DYNAMO_OPTION(disasm_mask)) ? "" : "$"); |
| } |
| |
| void |
| reg_disassemble(char *buf, size_t bufsz, size_t *sofar INOUT, |
| reg_id_t reg, dr_opnd_flags_t flags, |
| const char *prefix, const char *suffix) |
| { |
| print_to_buffer(buf, bufsz, sofar, |
| TEST(DR_DISASM_INTEL, DYNAMO_OPTION(disasm_mask)) ? "%s%s%s%s" : |
| "%s%s%%%s%s", |
| prefix, TEST(DR_OPND_NEGATED, flags) ? "-" : "", |
| reg_names[reg], suffix); |
| } |
| |
| static const char * |
| opnd_size_suffix_dr(opnd_t opnd) |
| { |
| int sz = opnd_size_in_bytes(opnd_get_size(opnd)); |
| switch (sz) { |
| case 1: return "1byte"; |
| case 2: return "2byte"; |
| case 4: return "4byte"; |
| case 6: return "6byte"; |
| case 8: return "8byte"; |
| case 10: return "10byte"; |
| case 12: return "12byte"; |
| case 14: return "14byte"; |
| case 15: return "15byte"; |
| case 16: return "16byte"; |
| case 28: return "28byte"; |
| case 32: return "32byte"; |
| case 40: return "40byte"; |
| case 94: return "94byte"; |
| case 108: return "108byte"; |
| case 512: return "512byte"; |
| } |
| return ""; |
| } |
| |
| static const char * |
| opnd_size_suffix_intel(opnd_t opnd) |
| { |
| int sz = opnd_size_in_bytes(opnd_get_size(opnd)); |
| switch (sz) { |
| case 1: return "byte"; |
| case 2: return "word"; |
| case 4: return "dword"; |
| case 6: return "fword"; |
| case 8: return "qword"; |
| case 10: return "tbyte"; |
| case 12: return ""; |
| case 16: return "oword"; |
| case 32: return "yword"; |
| } |
| return ""; |
| } |
| |
| static void |
| opnd_mem_disassemble_prefix(char *buf, size_t bufsz, size_t *sofar INOUT, |
| dcontext_t *dcontext, opnd_t opnd) |
| { |
| if (TEST(DR_DISASM_INTEL, DYNAMO_OPTION(disasm_mask))) { |
| const char *size_str = opnd_size_suffix_intel(opnd); |
| if (size_str[0] != '\0') |
| print_to_buffer(buf, bufsz, sofar, "%s ptr [", size_str); |
| else /* assume size implied by opcode */ |
| print_to_buffer(buf, bufsz, sofar, "["); |
| } |
| |
| } |
| |
| static void |
| opnd_base_disp_disassemble(char *buf, size_t bufsz, size_t *sofar INOUT, |
| dcontext_t *dcontext, opnd_t opnd) |
| { |
| reg_id_t seg = opnd_get_segment(opnd); |
| reg_id_t base = opnd_get_base(opnd); |
| int disp = opnd_get_disp(opnd); |
| reg_id_t index = opnd_get_index(opnd); |
| |
| opnd_mem_disassemble_prefix(buf, bufsz, sofar, dcontext, opnd); |
| |
| if (seg != REG_NULL) |
| reg_disassemble(buf, bufsz, sofar, seg, 0, "", ":"); |
| |
| if (TEST(DR_DISASM_INTEL, DYNAMO_OPTION(disasm_mask))) { |
| if (base != REG_NULL) |
| reg_disassemble(buf, bufsz, sofar, base, 0, "", ""); |
| if (index != REG_NULL) { |
| reg_disassemble(buf, bufsz, sofar, index, opnd_get_flags(opnd), |
| (base != REG_NULL && |
| !TEST(DR_OPND_NEGATED, opnd_get_flags(opnd))) ? "+" : "", |
| ""); |
| opnd_base_disp_scale_disassemble(buf, bufsz, sofar, opnd); |
| } |
| } |
| |
| if (disp != 0 || (base == REG_NULL && index == REG_NULL) || |
| opnd_is_disp_encode_zero(opnd)) { |
| if (TEST(DR_DISASM_INTEL, DYNAMO_OPTION(disasm_mask)) |
| /* Always negating for ARM. I would do the same for x86 but I don't |
| * want to break any existing scripts. |
| */ |
| IF_ARM(|| true)) { |
| /* windbg negates if top byte is 0xff |
| * for x64 udis86 negates if at all negative |
| */ |
| if (IF_X64_ELSE(disp < 0, (disp & 0xff000000) == 0xff000000)) { |
| disp = -disp; |
| print_to_buffer(buf, bufsz, sofar, "-"); |
| } else if (base != REG_NULL || index != REG_NULL) |
| print_to_buffer(buf, bufsz, sofar, "+"); |
| } |
| if (disp >= INT8_MIN && disp <= INT8_MAX && |
| !opnd_is_disp_force_full(opnd)) |
| print_to_buffer(buf, bufsz, sofar, "0x%02x", disp); |
| else if (opnd_is_disp_short_addr(opnd)) |
| print_to_buffer(buf, bufsz, sofar, "0x%04x", disp); |
| else /* there are no 64-bit displacements */ |
| print_to_buffer(buf, bufsz, sofar, "0x%08x", disp); |
| } |
| |
| if (!TEST(DR_DISASM_INTEL, DYNAMO_OPTION(disasm_mask))) { |
| if (base != REG_NULL || index != REG_NULL) { |
| print_to_buffer(buf, bufsz, sofar, "("); |
| if (base != REG_NULL) |
| reg_disassemble(buf, bufsz, sofar, base, 0, "", ""); |
| if (index != REG_NULL) { |
| reg_disassemble(buf, bufsz, sofar, index, opnd_get_flags(opnd), ",", ""); |
| opnd_base_disp_scale_disassemble(buf, bufsz, sofar, opnd); |
| } |
| print_to_buffer(buf, bufsz, sofar, ")"); |
| } |
| } |
| |
| if (TEST(DR_DISASM_INTEL, DYNAMO_OPTION(disasm_mask))) |
| print_to_buffer(buf, bufsz, sofar, "]"); |
| } |
| |
| static bool |
| print_known_pc_target(char *buf, size_t bufsz, size_t *sofar INOUT, |
| dcontext_t *dcontext, byte *target) |
| { |
| bool printed = false; |
| #ifndef STANDALONE_DECODER |
| /* symbolic addresses */ |
| if (ENTER_DR_HOOK != NULL && target == (app_pc) ENTER_DR_HOOK) { |
| print_to_buffer(buf, bufsz, sofar, |
| "$"PFX" <enter_dynamorio_hook> ", target); |
| printed = true; |
| } else if (EXIT_DR_HOOK != NULL && target == (app_pc) EXIT_DR_HOOK) { |
| print_to_buffer(buf, bufsz, sofar, |
| "$"PFX" <exit_dynamorio_hook> ", target); |
| printed = true; |
| } else if (dcontext != NULL && dynamo_initialized && !standalone_library) { |
| const char *gencode_routine = NULL; |
| const char *ibl_brtype; |
| const char *ibl_name = |
| get_ibl_routine_name(dcontext, target, &ibl_brtype); |
| # ifdef X86 |
| if (ibl_name == NULL && in_coarse_stub_prefixes(target) && |
| *target == JMP_OPCODE) { |
| ibl_name = get_ibl_routine_name(dcontext, |
| PC_RELATIVE_TARGET(target+1), |
| &ibl_brtype); |
| } |
| # elif defined(ARM) |
| /* FIXME i#1551: NYI on ARM */ |
| ASSERT_NOT_IMPLEMENTED(false); |
| # endif |
| # ifdef WINDOWS |
| /* must test first, as get_ibl_routine_name will think "bb_ibl_indjmp" */ |
| if (dcontext != GLOBAL_DCONTEXT) { |
| if (target == shared_syscall_routine(dcontext)) |
| gencode_routine = "shared_syscall"; |
| else if (target == unlinked_shared_syscall_routine(dcontext)) |
| gencode_routine = "unlinked_shared_syscall"; |
| } else { |
| if (target == shared_syscall_routine_ex(dcontext _IF_X64(GENCODE_X64))) |
| gencode_routine = "shared_syscall"; |
| else if (target == unlinked_shared_syscall_routine_ex |
| (dcontext _IF_X64(GENCODE_X64))) |
| gencode_routine = "unlinked_shared_syscall"; |
| # ifdef X64 |
| else if (target == shared_syscall_routine_ex |
| (dcontext _IF_X64(GENCODE_X86))) |
| gencode_routine = "x86_shared_syscall"; |
| else if (target == unlinked_shared_syscall_routine_ex |
| (dcontext _IF_X64(GENCODE_X86))) |
| gencode_routine = "x86_unlinked_shared_syscall"; |
| # endif |
| } |
| # endif |
| if (ibl_name) { |
| /* can't use gencode_routine since need two strings here */ |
| print_to_buffer(buf, bufsz, sofar, "$"PFX" <%s_%s>", |
| target, ibl_name, ibl_brtype); |
| printed = true; |
| } else if (SHARED_FRAGMENTS_ENABLED() && target == |
| fcache_return_shared_routine(IF_X64(GENCODE_X64))) |
| gencode_routine = "fcache_return"; |
| # ifdef X64 |
| else if (SHARED_FRAGMENTS_ENABLED() && target == |
| fcache_return_shared_routine(IF_X64(GENCODE_X86))) |
| gencode_routine = "x86_fcache_return"; |
| # endif |
| else if (dcontext != GLOBAL_DCONTEXT && |
| target == fcache_return_routine(dcontext)) |
| gencode_routine = "fcache_return"; |
| else if (DYNAMO_OPTION(coarse_units)) { |
| if (target == fcache_return_coarse_prefix(target, NULL) || |
| target == fcache_return_coarse_routine(IF_X64(GENCODE_X64))) |
| gencode_routine = "fcache_return_coarse"; |
| else if (target == trace_head_return_coarse_prefix(target, NULL) || |
| target == trace_head_return_coarse_routine |
| (IF_X64(GENCODE_X64))) |
| gencode_routine = "trace_head_return_coarse"; |
| # ifdef X64 |
| else if (target == fcache_return_coarse_prefix(target, NULL) || |
| target == fcache_return_coarse_routine(IF_X64(GENCODE_X86))) |
| gencode_routine = "x86_fcache_return_coarse"; |
| else if (target == trace_head_return_coarse_prefix(target, NULL) || |
| target == trace_head_return_coarse_routine |
| (IF_X64(GENCODE_X86))) |
| gencode_routine = "x86_trace_head_return_coarse"; |
| # endif |
| } |
| #ifdef PROFILE_RDTSC |
| else if ((void *)target == profile_fragment_enter) |
| gencode_routine = "profile_fragment_enter"; |
| #endif |
| #ifdef TRACE_HEAD_CACHE_INCR |
| else if ((void *)target == trace_head_incr_routine(dcontext)) |
| gencode_routine = "trace_head_incr"; |
| #endif |
| |
| if (gencode_routine != NULL) { |
| print_to_buffer(buf, bufsz, sofar, "$"PFX" <%s> ", |
| target, gencode_routine); |
| printed = true; |
| } else if (!printed && fragment_initialized(dcontext)) { |
| /* see if target is in a fragment */ |
| bool alloc = false; |
| fragment_t *fragment; |
| #ifdef DEBUG |
| fragment_t wrapper; |
| /* Unfortunately our fast lookup by fcache unit has lock |
| * ordering issues which we get around by using the htable |
| * method, though that won't find invisible fragments |
| * (FIXME: for those could perhaps pass in a pointer). |
| * For !DEADLOCK_AVOIDANCE, OWN_MUTEX's conservative imprecision |
| * is fine. |
| */ |
| if ((SHARED_FRAGMENTS_ENABLED() && |
| self_owns_recursive_lock(&change_linking_lock)) |
| /* HACK to avoid recursion if the pclookup invokes |
| * decode_fragment() (for coarse target) and it then invokes |
| * disassembly |
| */ |
| IF_DEBUG(|| (dcontext != GLOBAL_DCONTEXT && |
| dcontext->in_opnd_disassemble))) { |
| fragment = fragment_pclookup_by_htable(dcontext, (void *)target, |
| &wrapper); |
| } else { |
| bool prev_flag = false; |
| if (dcontext != GLOBAL_DCONTEXT) { |
| prev_flag = dcontext->in_opnd_disassemble; |
| dcontext->in_opnd_disassemble = true; |
| } |
| #endif /* shouldn't be any logging so no disasm in the middle of sensitive ops */ |
| fragment = fragment_pclookup_with_linkstubs(dcontext, target, |
| &alloc); |
| #ifdef DEBUG |
| if (dcontext != GLOBAL_DCONTEXT) |
| dcontext->in_opnd_disassemble = prev_flag; |
| } |
| #endif |
| if (fragment != NULL) { |
| if (FCACHE_ENTRY_PC(fragment) == (cache_pc)target || |
| FCACHE_PREFIX_ENTRY_PC(fragment) == (cache_pc)target || |
| FCACHE_IBT_ENTRY_PC(fragment) == (cache_pc)target) { |
| #ifdef DEBUG |
| print_to_buffer(buf, bufsz, sofar, "$"PFX" <fragment %d> ", |
| target, fragment->id); |
| #else |
| print_to_buffer(buf, bufsz, sofar, "$"PFX" <fragment "PFX"> ", |
| target, fragment->tag); |
| #endif |
| printed = true; |
| } else if (!TEST(FRAG_FAKE, fragment->flags)) { |
| /* check exit stubs */ |
| linkstub_t *ls; |
| int ls_num = 0; |
| CLIENT_ASSERT(!TEST(FRAG_FAKE, fragment->flags), |
| "opnd_disassemble: invalid target"); |
| for (ls=FRAGMENT_EXIT_STUBS(fragment); ls; ls=LINKSTUB_NEXT_EXIT(ls)) { |
| if (target == EXIT_STUB_PC(dcontext, fragment, ls)) { |
| print_to_buffer(buf, bufsz, sofar, |
| "$"PFX" <exit stub %d> ", |
| target, ls_num); |
| printed = true; |
| break; |
| } |
| ls_num++; |
| } |
| } |
| if (alloc) |
| fragment_free(dcontext, fragment); |
| } else if (coarse_is_entrance_stub(target)) { |
| print_to_buffer(buf, bufsz, sofar, |
| "$"PFX" <entrance stub for "PFX"> ", |
| target, entrance_stub_target_tag(target, NULL)); |
| printed = true; |
| } |
| } |
| } else if (dynamo_initialized && !SHARED_FRAGMENTS_ENABLED() && |
| !standalone_library) { |
| print_to_buffer(buf, bufsz, sofar, "NULL DCONTEXT! "); |
| } |
| #endif /* !STANDALONE_DECODER */ |
| return printed; |
| } |
| |
| void |
| internal_opnd_disassemble(char *buf, size_t bufsz, size_t *sofar INOUT, |
| dcontext_t *dcontext, opnd_t opnd, |
| bool use_size_sfx) |
| { |
| switch (opnd.kind) { |
| case NULL_kind: |
| return; |
| case IMMED_INTEGER_kind: |
| { |
| int sz = opnd_size_in_bytes(opnd_get_size(opnd)); |
| ptr_int_t val = opnd_get_immed_int(opnd); |
| const char *sign = ""; |
| #ifdef ARM |
| /* On ARM we have no pointer-sized immeds so let's always negate */ |
| if (val < 0) { |
| sign = "-"; |
| val = -val; |
| } |
| #endif |
| /* PR 327775: when we don't know other operands we truncate. |
| * We rely on instr_disassemble to temporarily change operand |
| * size to sign-extend to match the size of adjacent operands. |
| */ |
| if (sz <= 1) { |
| print_to_buffer(buf, bufsz, sofar, "%s%s0x%02x", immed_prefix(), |
| sign, (uint)((byte)val)); |
| } else if (sz <= 2) { |
| print_to_buffer(buf, bufsz, sofar, "%s%s0x%04x", immed_prefix(), |
| sign, (uint)((unsigned short)val)); |
| } else if (sz <= 4) { |
| print_to_buffer(buf, bufsz, sofar, "%s%s0x%08x", immed_prefix(), |
| sign, (uint)val); |
| } else { |
| print_to_buffer(buf, bufsz, sofar, "%s%s0x"ZHEX64_FORMAT_STRING, |
| immed_prefix(), sign, val); |
| } |
| } |
| break; |
| case IMMED_FLOAT_kind: |
| { |
| /* need to save floating state around float printing */ |
| PRESERVE_FLOATING_POINT_STATE({ |
| uint top; uint bottom; |
| const char *sign; |
| double_print(opnd_get_immed_float(opnd), 6, |
| &top, &bottom, &sign); |
| print_to_buffer(buf, bufsz, sofar, "%s%s%u.%.6u", |
| immed_prefix(), sign, top, bottom); |
| }); |
| break; |
| } |
| case PC_kind: |
| { |
| app_pc target = opnd_get_pc(opnd); |
| if (!print_known_pc_target(buf, bufsz, sofar, dcontext, target)) { |
| print_to_buffer(buf, bufsz, sofar, "%s"PFX, immed_prefix(), target); |
| } |
| break; |
| } |
| case FAR_PC_kind: |
| /* constant is selector and not a SEG_ constant */ |
| print_to_buffer(buf, bufsz, sofar, "0x%04x:"PFX, |
| (ushort)opnd_get_segment_selector(opnd), opnd_get_pc(opnd)); |
| break; |
| case INSTR_kind: |
| print_to_buffer(buf, bufsz, sofar, "@"PFX, opnd_get_instr(opnd)); |
| break; |
| case FAR_INSTR_kind: |
| /* constant is selector and not a SEG_ constant */ |
| print_to_buffer(buf, bufsz, sofar, "0x%04x:@"PFX, |
| (ushort)opnd_get_segment_selector(opnd), opnd_get_instr(opnd)); |
| break; |
| case MEM_INSTR_kind: |
| print_to_buffer(buf, bufsz, sofar, IF_X64("<re> ")"@"PFX"+%d", |
| opnd_get_instr(opnd), opnd_get_mem_instr_disp(opnd)); |
| break; |
| case REG_kind: |
| reg_disassemble(buf, bufsz, sofar, opnd_get_reg(opnd), |
| opnd_get_flags(opnd), "", ""); |
| break; |
| case BASE_DISP_kind: |
| opnd_base_disp_disassemble(buf, bufsz, sofar, dcontext, opnd); |
| break; |
| #ifdef X64 |
| case REL_ADDR_kind: |
| print_to_buffer(buf, bufsz, sofar, "<rel> "); |
| /* fall-through */ |
| case ABS_ADDR_kind: |
| opnd_mem_disassemble_prefix(buf, bufsz, sofar, dcontext, opnd); |
| if (opnd_get_segment(opnd) != REG_NULL) |
| reg_disassemble(buf, bufsz, sofar, opnd_get_segment(opnd), 0, "", ":"); |
| print_to_buffer(buf, bufsz, sofar, PFX"%s", opnd_get_addr(opnd), |
| TEST(DR_DISASM_INTEL, DYNAMO_OPTION(disasm_mask)) ? "]" : ""); |
| break; |
| #endif |
| default: |
| print_to_buffer(buf, bufsz, sofar, "UNKNOWN OPERAND TYPE %d", opnd.kind); |
| CLIENT_ASSERT(false, "opnd_disassemble: invalid opnd type"); |
| } |
| |
| if (use_size_sfx) { |
| switch (opnd.kind) { |
| case NULL_kind: |
| case IMMED_INTEGER_kind: |
| case IMMED_FLOAT_kind: |
| case PC_kind: |
| case FAR_PC_kind: |
| break; |
| case REG_kind: |
| if (!opnd_is_reg_partial(opnd)) |
| break; |
| /* fall-through */ |
| default: { |
| const char *size_str = opnd_size_suffix_dr(opnd); |
| if (size_str[0] != '\0') |
| print_to_buffer(buf, bufsz, sofar, "[%s]", size_str); |
| } |
| } |
| } |
| |
| print_to_buffer(buf, bufsz, sofar, "%s", postop_suffix()); |
| } |
| |
| void |
| opnd_disassemble(dcontext_t *dcontext, opnd_t opnd, file_t outfile) |
| { |
| char buf[MAX_OPND_DIS_SZ]; |
| size_t sofar = 0; |
| internal_opnd_disassemble(buf, BUFFER_SIZE_ELEMENTS(buf), &sofar, dcontext, opnd, |
| false/*don't know*/); |
| /* not propagating bool return vals of print_to_buffer but should be plenty big */ |
| CLIENT_ASSERT(sofar < BUFFER_SIZE_ELEMENTS(buf) - 1, "internal buffer too small"); |
| os_write(outfile, buf, sofar); |
| } |
| |
| size_t |
| opnd_disassemble_to_buffer(dcontext_t *dcontext, opnd_t opnd, |
| char *buf, size_t bufsz) |
| |
| { |
| size_t sofar = 0; |
| internal_opnd_disassemble(buf, bufsz, &sofar, dcontext, opnd, false/*don't know*/); |
| return sofar; |
| } |
| |
| static int |
| print_bytes_to_file(file_t outfile, byte *pc, byte *next_pc, instr_t *inst) |
| { |
| char buf[MAX_PC_DIS_SZ]; |
| size_t sofar = 0; |
| int extra_sz = print_bytes_to_buffer(buf, BUFFER_SIZE_ELEMENTS(buf), &sofar, |
| pc, next_pc, inst); |
| CLIENT_ASSERT(sofar < BUFFER_SIZE_ELEMENTS(buf) - 1, "internal buffer too small"); |
| os_write(outfile, buf, sofar); |
| return extra_sz; |
| } |
| |
| static void |
| print_extra_bytes_to_file(file_t outfile, byte *pc, byte *next_pc, int extra_sz, |
| const char *extra_bytes_prefix) |
| { |
| char buf[MAX_PC_DIS_SZ]; |
| size_t sofar = 0; |
| print_extra_bytes_to_buffer(buf, BUFFER_SIZE_ELEMENTS(buf), &sofar, |
| pc, next_pc, extra_sz, extra_bytes_prefix); |
| CLIENT_ASSERT(sofar < BUFFER_SIZE_ELEMENTS(buf) - 1, "internal buffer too small"); |
| os_write(outfile, buf, sofar); |
| } |
| |
| /* Disassembles the instruction at pc and prints the result to buf. |
| * Returns a pointer to the pc of the next instruction. |
| * Returns NULL if the instruction at pc is invalid. |
| */ |
| static byte * |
| internal_disassemble(char *buf, size_t bufsz, size_t *sofar INOUT, |
| dcontext_t *dcontext, byte *pc, byte *orig_pc, |
| bool with_pc, bool with_bytes, const char *extra_bytes_prefix) |
| { |
| int extra_sz = 0; |
| byte * next_pc; |
| instr_t instr; |
| bool valid = true; |
| |
| instr_init(dcontext, &instr); |
| if (orig_pc != pc) |
| next_pc = decode_from_copy(dcontext, pc, orig_pc, &instr); |
| else |
| next_pc = decode(dcontext, pc, &instr); |
| if (next_pc == NULL) { |
| valid = false; |
| /* HACK: if decode_fast thinks it knows size use that */ |
| next_pc = decode_next_pc(dcontext, pc); |
| } |
| if (next_pc == NULL) { |
| valid = false; |
| /* last resort: arbitrarily pick 4 bytes */ |
| next_pc = pc + 4; |
| } |
| |
| if (with_pc) |
| print_to_buffer(buf, bufsz, sofar, " "PFX" ", orig_pc); |
| |
| if (with_bytes) { |
| extra_sz = print_bytes_to_buffer(buf, bufsz, sofar, pc, next_pc, &instr); |
| } |
| |
| internal_instr_disassemble(buf, bufsz, sofar, dcontext, &instr); |
| |
| /* XXX: should we give caller control over whether \n or \r\n? */ |
| print_to_buffer(buf, bufsz, sofar, "\n"); |
| |
| if (with_bytes && extra_sz > 0) { |
| if (with_pc) |
| print_to_buffer(buf, bufsz, sofar, IF_X64_ELSE("%21s","%13s")," "); |
| print_extra_bytes_to_buffer(buf, bufsz, sofar, pc, next_pc, extra_sz, |
| extra_bytes_prefix); |
| } |
| |
| instr_free(dcontext, &instr); |
| |
| return (valid ? next_pc : NULL); |
| } |
| |
| /* Disassembles the instruction at pc and prints the result to outfile. |
| * Returns a pointer to the pc of the next instruction. |
| * Returns NULL if the instruction at pc is invalid. |
| */ |
| static byte * |
| internal_disassemble_to_file(dcontext_t *dcontext, byte *pc, byte *orig_pc, |
| file_t outfile, bool with_pc, bool with_bytes, |
| const char *extra_bytes_prefix) |
| { |
| char buf[MAX_PC_DIS_SZ]; |
| size_t sofar = 0; |
| byte *next = internal_disassemble(buf, BUFFER_SIZE_ELEMENTS(buf), &sofar, |
| dcontext, pc, orig_pc, with_pc, with_bytes, |
| extra_bytes_prefix); |
| /* not propagating bool return vals of print_to_buffer but should be plenty big */ |
| CLIENT_ASSERT(sofar < BUFFER_SIZE_ELEMENTS(buf) - 1, "internal buffer too small"); |
| os_write(outfile, buf, sofar); |
| return next; |
| } |
| |
| /**************************************************************************** |
| * Exported routines |
| */ |
| |
| /* Disassembles the instruction at pc and prints the result to outfile |
| * Returns a pointer to the pc of the next instruction. |
| * Returns NULL if the instruction at pc is invalid. |
| */ |
| byte * |
| disassemble(dcontext_t *dcontext, byte *pc, file_t outfile) |
| { |
| return internal_disassemble_to_file(dcontext, pc, pc, outfile, true, false, ""); |
| } |
| |
| /* Disassembles a single instruction and prints its pc and |
| * bytes then the disassembly. |
| * Returns the pc of the next instruction. |
| * If the instruction at pc is invalid, guesses a size! |
| * This is b/c we internally use that feature, but I don't want to export it: |
| * so this unexported routine maintains it, and we don't have to change all |
| * our call sites to check for NULL. |
| */ |
| byte * |
| disassemble_with_bytes(dcontext_t *dcontext, byte *pc, file_t outfile) |
| { |
| byte *next_pc = internal_disassemble_to_file(dcontext, pc, pc, outfile, |
| true, true, ""); |
| if (next_pc == NULL) { |
| next_pc = decode_next_pc(dcontext, pc); |
| if (next_pc == NULL) |
| next_pc = pc + 4; /* guess size */ |
| } |
| return next_pc; |
| } |
| |
| /* Disassembles a single instruction, optionally printing its pc (if show_pc) |
| * and its raw bytes (show_bytes) beforehand. |
| * Returns the pc of the next instruction. |
| * FIXME: vs disassemble_with_bytes -- didn't want to update all callers |
| * so leaving, though should probably remove. |
| * Returns NULL if the instruction at pc is invalid. |
| */ |
| byte * |
| disassemble_with_info(dcontext_t *dcontext, byte *pc, file_t outfile, |
| bool show_pc, bool show_bytes) |
| { |
| return internal_disassemble_to_file(dcontext, pc, pc, outfile, |
| show_pc, show_bytes, ""); |
| } |
| |
| /* |
| * Decodes the instruction at address \p copy_pc as though |
| * it were located at address \p orig_pc, and then prints the |
| * instruction to file \p outfile. |
| * Prior to the instruction the address \p orig_pc is printed if \p show_pc and the raw |
| * bytes are printed if \p show_bytes. |
| * Returns the address of the subsequent instruction after the copy at |
| * \p copy_pc, or NULL if the instruction at \p copy_pc is invalid. |
| */ |
| byte * |
| disassemble_from_copy(dcontext_t *dcontext, byte *copy_pc, byte *orig_pc, |
| file_t outfile, bool show_pc, bool show_bytes) |
| { |
| return internal_disassemble_to_file(dcontext, copy_pc, orig_pc, outfile, |
| show_pc, show_bytes, ""); |
| } |
| |
| byte * |
| disassemble_to_buffer(dcontext_t *dcontext, byte *pc, byte *orig_pc, |
| bool show_pc, bool show_bytes, char *buf, size_t bufsz, |
| int *printed OUT) |
| { |
| size_t sofar = 0; |
| byte *next = internal_disassemble(buf, bufsz, &sofar, dcontext, |
| pc, orig_pc, show_pc, show_bytes, ""); |
| if (printed != NULL) |
| *printed = (int) sofar; |
| return next; |
| } |
| |
| static void |
| instr_disassemble_opnds_noimplicit(char *buf, size_t bufsz, size_t *sofar INOUT, |
| dcontext_t *dcontext, instr_t *instr) |
| { |
| /* We need to find the non-implicit operands */ |
| const instr_info_t *info; |
| int i, num; |
| byte optype; |
| /* avoid duplicate on ALU: only happens w/ 2dst, 3srcs */ |
| byte optype_already[3] = {0, 0, 0 /*0 == TYPE_NONE*/}; |
| opnd_t opnd; |
| bool prev = false, multiple_encodings = false; |
| |
| info = instr_get_instr_info(instr); |
| if (info != NULL && get_next_instr_info(info) != NULL && |
| instr_info_extra_opnds(info) == NULL) |
| multiple_encodings = true; |
| |
| info = get_encoding_info(instr); |
| if (info == NULL) { |
| print_to_buffer(buf, bufsz, sofar, "<INVALID>"); |
| return; |
| } |
| num = TEST(DR_DISASM_INTEL, DYNAMO_OPTION(disasm_mask)) ? |
| instr_num_dsts(instr) : instr_num_srcs(instr); |
| for (i=0; i<num; i++) { |
| bool printing; |
| opnd = TEST(DR_DISASM_INTEL, DYNAMO_OPTION(disasm_mask)) ? |
| instr_get_dst(instr, i) : instr_get_src(instr, i); |
| optype = instr_info_opnd_type(info, !TEST(DR_DISASM_INTEL, |
| DYNAMO_OPTION(disasm_mask)), i); |
| printing = opnd_disassemble_noimplicit(buf, bufsz, sofar, dcontext, |
| instr, optype, opnd, prev, |
| multiple_encodings); |
| /* w/o the "printing" check we suppress "push esp" => "push" */ |
| if (printing && i < 3) |
| optype_already[i] = optype; |
| prev = printing || prev; |
| } |
| num = TEST(DR_DISASM_INTEL, DYNAMO_OPTION(disasm_mask)) ? |
| instr_num_srcs(instr) : instr_num_dsts(instr); |
| for (i=0; i<num; i++) { |
| opnd = TEST(DR_DISASM_INTEL, DYNAMO_OPTION(disasm_mask)) ? |
| instr_get_src(instr, i) : instr_get_dst(instr, i); |
| optype = instr_info_opnd_type(info, TEST(DR_DISASM_INTEL, |
| DYNAMO_OPTION(disasm_mask)), i); |
| /* PR 312458: still not matching Intel-style tools like windbg or udis86: |
| * we need to suppress certain implicit operands, such as: |
| * - div dx, ax |
| * - imul ax |
| * - idiv edx, eax |
| * - in al |
| */ |
| |
| /* Don't re-do src==dst of ALU ops */ |
| if ((optype != optype_already[0] && optype != optype_already[1] && |
| optype != optype_already[2]) || |
| /* Don't suppress 2nd of st* if FP ALU */ |
| (i == 0 && opnd_is_reg(opnd) && reg_is_fp(opnd_get_reg(opnd)))) { |
| prev = opnd_disassemble_noimplicit(buf, bufsz, sofar, dcontext, |
| instr, optype, opnd, prev, |
| multiple_encodings) |
| || prev; |
| } |
| } |
| } |
| |
| static const char * |
| instr_opcode_name(instr_t *instr, const instr_info_t *info) |
| { |
| const char * res = instr_opcode_name_arch(instr, info); |
| if (res != NULL) |
| return res; |
| return info->name; |
| } |
| |
| static const char * |
| instr_opcode_name_suffix(instr_t *instr) |
| { |
| const char * res = instr_opcode_name_suffix_arch(instr); |
| if (res != NULL) |
| return res; |
| if (TEST(DR_DISASM_ATT, DYNAMO_OPTION(disasm_mask)) && instr_operands_valid(instr)) { |
| /* XXX: requiring both src and dst. Ideally we'd wait until we |
| * see if there is a register or in some cases an immed operand |
| * and then go back and add the suffix. This will do for now. |
| */ |
| if (instr_num_srcs(instr) > 0 && !opnd_is_reg(instr_get_src(instr, 0)) && |
| instr_num_dsts(instr) > 0 && !opnd_is_reg(instr_get_dst(instr, 0))) { |
| uint sz = instr_memory_reference_size(instr); |
| if (sz == 1) |
| return "b"; |
| else if (sz == 2) |
| return "w"; |
| else if (sz == 4) |
| return "l"; |
| else if (sz == 8) |
| return "q"; |
| } |
| } |
| return ""; |
| } |
| |
| static bool |
| instr_needs_opnd_size_sfx(instr_t *instr) |
| { |
| #ifdef DISASM_SUFFIX_ONLY_ON_MISMATCH /* disabled: see below */ |
| opnd_t src, dst; |
| if (TEST(DR_DISASM_NO_OPND_SIZE, DYNAMO_OPTION(disasm_mask))) |
| return false; |
| /* We really only care about the primary src and primary dst */ |
| if (instr_num_srcs(instr) == 0 || |
| instr_num_dsts(instr) == 0) |
| return false; |
| src = instr_get_src(instr, 0); |
| /* Avoid opcodes that have a 1-byte immed but all other operands |
| * the same size from triggering suffixes |
| */ |
| if (opnd_is_immed(src) && instr_num_srcs(instr) > 1) |
| return false; |
| dst = instr_get_dst(instr, 0); |
| return (opnd_get_size(src) != opnd_get_size(dst) || |
| /* We haven't sign-extended yet -- if we did maybe we wouldn't |
| * need this. Good to show size on mov of immed into memory. |
| */ |
| opnd_is_immed_int(src) || |
| opnd_is_reg_partial(src) || |
| opnd_is_reg_partial(dst)); |
| #else |
| /* Originally I tried only showing the sizes when they mismatch or |
| * can't be inferred (code above), but that gets a little tricky, |
| * and IMHO it's nice to see the size of all memory operands. We |
| * never print for immeds or non-partial regs, so we can just set |
| * to true for all instructions. |
| */ |
| if (TEST(DR_DISASM_NO_OPND_SIZE, DYNAMO_OPTION(disasm_mask))) |
| return false; |
| return true; |
| #endif |
| } |
| |
| static void |
| sign_extend_immed(instr_t *instr, int srcnum, opnd_t *src) |
| { |
| opnd_size_t opsz = OPSZ_NA; |
| bool resize = true; |
| if (opnd_is_immed_int(*src)) { |
| /* PR 327775: force operand to sign-extend if all other operands |
| * are of a larger and identical-to-each-other size (since we |
| * don't want to extend immeds used in stores) and are not |
| * multimedia registers (since immeds there are always indices). |
| */ |
| int j; |
| for (j=0; j<instr_num_srcs(instr); j++) { |
| if (j != srcnum) { |
| if (opnd_is_reg(instr_get_src(instr, j)) && |
| !reg_is_gpr(opnd_get_reg(instr_get_src(instr, j)))) { |
| resize = false; |
| break; |
| } |
| if (opsz == OPSZ_NA) |
| opsz = opnd_get_size(instr_get_src(instr, j)); |
| else if (opsz != opnd_get_size(instr_get_src(instr, j))) { |
| resize = false; |
| break; |
| } |
| } |
| } |
| for (j=0; j<instr_num_dsts(instr); j++) { |
| if (opnd_is_reg(instr_get_dst(instr, j)) && |
| !reg_is_gpr(opnd_get_reg(instr_get_dst(instr, j)))) { |
| resize = false; |
| break; |
| } |
| if (opsz == OPSZ_NA) |
| opsz = opnd_get_size(instr_get_dst(instr, j)); |
| else if (opsz != opnd_get_size(instr_get_dst(instr, j))) { |
| resize = false; |
| break; |
| } |
| } |
| if (resize && opsz != OPSZ_NA && !instr_is_interrupt(instr)) |
| opnd_set_size(src, opsz); |
| } |
| } |
| |
| /* |
| * Prints the instruction instr to file outfile. |
| * Does not print addr16 or data16 prefixes for other than just-decoded instrs, |
| * and does not check that the instruction has a valid encoding. |
| * Prints each operand with leading zeros indicating the size. |
| */ |
| static void |
| internal_instr_disassemble(char *buf, size_t bufsz, size_t *sofar INOUT, |
| dcontext_t *dcontext, instr_t *instr) |
| { |
| int i, sz; |
| const instr_info_t * info; |
| const char * name; |
| int name_width = 6; |
| bool use_size_sfx = false; |
| |
| if (!instr_valid(instr)) { |
| print_to_buffer(buf, bufsz, sofar, "<INVALID>"); |
| return; |
| } else if (instr_is_label(instr)) { |
| print_to_buffer(buf, bufsz, sofar, "<label>"); |
| return; |
| } else if (instr_opcode_valid(instr)) { |
| info = instr_get_instr_info(instr); |
| name = instr_opcode_name(instr, info); |
| } else |
| name = "<RAW>"; |
| |
| print_instr_prefixes(dcontext, instr, buf, bufsz, sofar); |
| |
| print_to_buffer(buf, bufsz, sofar, "%s%s", name, instr_opcode_name_suffix(instr)); |
| |
| name_width -= print_opcode_suffix(instr, buf, bufsz, sofar); |
| print_to_buffer(buf, bufsz, sofar, " "); |
| |
| IF_X64(CLIENT_ASSERT(CHECK_TRUNCATE_TYPE_int(strlen(name)), |
| "instr_disassemble: internal truncation error")); |
| sz = (int) strlen(name) + (int) strlen(instr_opcode_name_suffix(instr)); |
| for (i=sz; i<name_width; i++) |
| print_to_buffer(buf, bufsz, sofar, " "); |
| |
| /* operands */ |
| if (!instr_operands_valid(instr)) { |
| /* we could decode the raw bits, but caller should if they want that */ |
| byte *raw = instr_get_raw_bits(instr); |
| uint len = instr_length(dcontext, instr); |
| byte *b; |
| print_to_buffer(buf, bufsz, sofar, "<raw "PFX"-"PFX" ==", raw, raw + len); |
| for (b = raw; b < raw + len && b < raw + 9; b++) |
| print_to_buffer(buf, bufsz, sofar, " %02x", *b); |
| if (len > 9) |
| print_to_buffer(buf, bufsz, sofar, " ..."); |
| print_to_buffer(buf, bufsz, sofar, ">"); |
| return; |
| } |
| |
| if (TESTANY(DR_DISASM_INTEL|DR_DISASM_ATT, DYNAMO_OPTION(disasm_mask))) { |
| instr_disassemble_opnds_noimplicit(buf, bufsz, sofar, dcontext, instr); |
| return; |
| } |
| |
| use_size_sfx = instr_needs_opnd_size_sfx(instr); |
| |
| for (i=0; i<instr_num_srcs(instr); i++) { |
| opnd_t src = instr_get_src(instr, i); |
| sign_extend_immed(instr, i, &src); |
| internal_opnd_disassemble(buf, bufsz, sofar, dcontext, src, use_size_sfx); |
| } |
| if (instr_num_dsts(instr) > 0) { |
| print_to_buffer(buf, bufsz, sofar, "-> "); |
| for (i=0; i<instr_num_dsts(instr); i++) { |
| internal_opnd_disassemble(buf, bufsz, sofar, dcontext, |
| instr_get_dst(instr, i), use_size_sfx); |
| } |
| } |
| } |
| |
| /* |
| * Prints the instruction instr to file outfile. |
| * Does not print addr16 or data16 prefixes for other than just-decoded instrs, |
| * and does not check that the instruction has a valid encoding. |
| * Prints each operand with leading zeros indicating the size. |
| */ |
| void |
| instr_disassemble(dcontext_t *dcontext, instr_t *instr, file_t outfile) |
| { |
| char buf[MAX_INSTR_DIS_SZ]; |
| size_t sofar = 0; |
| internal_instr_disassemble(buf, BUFFER_SIZE_ELEMENTS(buf), &sofar, dcontext, instr); |
| /* not propagating bool return vals of print_to_buffer but should be plenty big */ |
| CLIENT_ASSERT(sofar < BUFFER_SIZE_ELEMENTS(buf) - 1, "internal buffer too small"); |
| os_write(outfile, buf, sofar); |
| } |
| |
| /* |
| * Prints the instruction \p instr to the buffer \p buf. |
| * Always null-terminates, and will not print more than \p bufsz characters, |
| * which includes the final null character. |
| * Returns the number of characters printed, not including the final null. |
| * |
| * Does not print address-size or data-size prefixes for other than |
| * just-decoded instrs, and does not check that the instruction has a |
| * valid encoding. Prints each operand with leading zeros indicating |
| * the size. |
| * The default is to use AT&T-style syntax, unless the \ref op_syntax_intel |
| * "-syntax_intel" runtime option is specified. |
| */ |
| size_t |
| instr_disassemble_to_buffer(dcontext_t *dcontext, instr_t *instr, |
| char *buf, size_t bufsz) |
| { |
| size_t sofar = 0; |
| internal_instr_disassemble(buf, bufsz, &sofar, dcontext, instr); |
| return sofar; |
| } |
| |
| #ifndef STANDALONE_DECODER |
| static inline const char* |
| exit_stub_type_desc(dcontext_t *dcontext, fragment_t *f, linkstub_t *l) |
| { |
| if (LINKSTUB_DIRECT(l->flags)) { |
| if (EXIT_IS_CALL(l->flags)) |
| return "call"; |
| if (EXIT_IS_JMP(l->flags)) |
| return "jmp/jcc"; |
| return "fall-through/speculated/IAT"; |
| /* FIXME: mark these appropriately */ |
| } else { |
| CLIENT_ASSERT(LINKSTUB_INDIRECT(l->flags), "invalid exit stub"); |
| if (TEST(LINK_RETURN, l->flags)) |
| return "ret"; |
| if (EXIT_IS_CALL(l->flags)) |
| return "indcall"; |
| if (TEST(LINK_JMP, l->flags)) /* JMP or IND_JMP_PLT */ |
| return "indjmp"; |
| #ifdef WINDOWS |
| if (is_shared_syscall_routine(dcontext, EXIT_TARGET_TAG(dcontext, f, l))) |
| return "shared_syscall"; |
| #endif |
| } |
| CLIENT_ASSERT(false, "unknown exit stub type"); |
| return "<unknown>"; |
| } |
| |
| /* Disassemble and pretty-print the generated code for fragment f. |
| * Header and body control whether header info and code itself are printed |
| */ |
| static void |
| common_disassemble_fragment(dcontext_t *dcontext, |
| fragment_t *f_in, file_t outfile, bool header, bool body) |
| { |
| cache_pc entry_pc, prefix_pc; |
| cache_pc pc; |
| cache_pc body_end_pc; |
| cache_pc end_pc; |
| linkstub_t *l; |
| int exit_num = 0; |
| #ifdef PROFILE_RDTSC |
| cache_pc profile_end = 0; |
| #endif |
| bool alloc; |
| fragment_t *f = f_in; |
| |
| if (header) { |
| #ifdef DEBUG |
| print_file(outfile, "Fragment %d, tag "PFX", flags 0x%x, %s%s%s%ssize %d%s%s:\n", |
| f->id, |
| #else |
| print_file(outfile, "Fragment tag "PFX", flags 0x%x, %s%s%s%ssize %d%s%s:\n", |
| #endif |
| f->tag, f->flags, |
| IF_X64_ELSE(FRAG_IS_32(f->flags) ? "32-bit, " : "", ""), |
| TEST(FRAG_COARSE_GRAIN, f->flags) ? "coarse, " : "", |
| (TEST(FRAG_SHARED, f->flags) ? "shared, " : |
| (SHARED_FRAGMENTS_ENABLED() ? |
| (TEST(FRAG_TEMP_PRIVATE, f->flags) ? "private temp, " : "private, ") : "")), |
| (TEST( FRAG_IS_TRACE, f->flags)) ? "trace, " : |
| (TEST(FRAG_IS_TRACE_HEAD, f->flags)) ? "tracehead, " : "", |
| f->size, |
| (TEST(FRAG_CANNOT_BE_TRACE, f->flags)) ?", cannot be trace":"", |
| (TEST(FRAG_MUST_END_TRACE, f->flags)) ?", must end trace":"", |
| (TEST(FRAG_CANNOT_DELETE, f->flags)) ?", cannot delete":""); |
| |
| DOLOG(2, LOG_SYMBOLS, { /* FIXME: this affects non-logging uses... dump_traces, etc. */ |
| char symbolbuf[MAXIMUM_SYMBOL_LENGTH]; |
| print_symbolic_address(f->tag, symbolbuf, sizeof(symbolbuf), false); |
| print_file(outfile, "\t%s\n", symbolbuf); |
| }); |
| } |
| |
| if (!body) |
| return; |
| |
| if (body && TEST(FRAG_FAKE, f->flags)) { |
| alloc = true; |
| f = fragment_recreate_with_linkstubs(dcontext, f_in); |
| } else { |
| alloc = false; |
| } |
| end_pc = f->start_pc + f->size; |
| body_end_pc = fragment_body_end_pc(dcontext, f); |
| entry_pc = FCACHE_ENTRY_PC(f); |
| prefix_pc = FCACHE_PREFIX_ENTRY_PC(f); |
| pc = FCACHE_IBT_ENTRY_PC(f); |
| if (pc != entry_pc) { |
| if (pc != prefix_pc) { |
| /* indirect branch target prefix exists */ |
| print_file(outfile, " -------- indirect branch target entry: --------\n"); |
| } |
| while (pc < entry_pc) { |
| if (pc == prefix_pc) { |
| print_file(outfile, " -------- prefix entry: --------\n"); |
| } |
| pc = (cache_pc) disassemble_with_bytes(dcontext, (byte *)pc, outfile); |
| } |
| print_file(outfile, " -------- normal entry: --------\n"); |
| } |
| |
| CLIENT_ASSERT(pc == entry_pc, "disassemble_fragment: invalid prefix"); |
| |
| #ifdef PROFILE_RDTSC |
| if (dynamo_options.profile_times && (f->flags & FRAG_IS_TRACE) != 0) { |
| int sz = profile_call_size(); |
| profile_end = pc + sz; |
| if (stats->loglevel < 3) { |
| /* don't print profile stuff to save space */ |
| print_file(outfile, " "PFX"..."PFX" = profile code\n", |
| pc, (pc+sz-1)); |
| pc += sz; |
| } else { |
| /* print profile stuff, but delineate it: */ |
| print_file(outfile, " -------- profile call: --------\n"); |
| } |
| } |
| #endif |
| |
| while (pc < body_end_pc) { |
| pc = (cache_pc) disassemble_with_bytes(dcontext, (byte *)pc, outfile); |
| #ifdef PROFILE_RDTSC |
| if (dynamo_options.profile_times && |
| (f->flags & FRAG_IS_TRACE) != 0 && |
| pc == profile_end) { |
| print_file(outfile, " -------- end profile call -----\n"); |
| } |
| #endif |
| } |
| |
| for (l = FRAGMENT_EXIT_STUBS(f); l; l = LINKSTUB_NEXT_EXIT(l)) { |
| cache_pc next_stop_pc; |
| linkstub_t *nxt; |
| /* store fragment pc since we don't want to walk forward in fragment */ |
| cache_pc frag_pc = pc; |
| print_file(outfile, " -------- exit stub %d: -------- <target: "PFX"> type: %s\n", |
| exit_num, EXIT_TARGET_TAG(dcontext, f, l), |
| exit_stub_type_desc(dcontext, f, l)); |
| if (!EXIT_HAS_LOCAL_STUB(l->flags, f->flags)) { |
| if (EXIT_STUB_PC(dcontext, f, l) != NULL) { |
| pc = EXIT_STUB_PC(dcontext, f, l); |
| next_stop_pc = pc + linkstub_size(dcontext, f, l); |
| } else if (TEST(FRAG_COARSE_GRAIN, f->flags)) { |
| cache_pc cti_pc = EXIT_CTI_PC(f, l); |
| if (cti_pc == end_pc) { |
| /* must be elided final jmp */ |
| print_file(outfile, " <no final jmp since elided>\n"); |
| print_file(outfile, " <no stub since linked and frozen>\n"); |
| CLIENT_ASSERT(pc == end_pc, "disassemble_fragment: invalid end"); |
| next_stop_pc = end_pc; |
| } else { |
| pc = entrance_stub_from_cti(cti_pc); |
| if (coarse_is_entrance_stub(pc)) { |
| next_stop_pc = pc + linkstub_size(dcontext, f, l); |
| } else { |
| CLIENT_ASSERT(in_fcache(pc), |
| "disassemble_fragment: invalid exit stub"); |
| print_file(outfile, " <no stub since linked and frozen>\n"); |
| next_stop_pc = pc; |
| } |
| } |
| } else { |
| if (TEST(LINK_SEPARATE_STUB, l->flags)) |
| print_file(outfile, " <no stub created since linked>\n"); |
| else if (!EXIT_HAS_STUB(l->flags, f->flags)) |
| print_file(outfile, " <no stub needed: -no_indirect_stubs>\n"); |
| else |
| CLIENT_ASSERT(false, "disassemble_fragment: invalid exit stub"); |
| next_stop_pc = pc; |
| } |
| } else { |
| for (nxt = LINKSTUB_NEXT_EXIT(l); nxt != NULL; nxt = LINKSTUB_NEXT_EXIT(nxt)) { |
| if (EXIT_HAS_LOCAL_STUB(nxt->flags, f->flags)) |
| break; |
| } |
| if (nxt != NULL) |
| next_stop_pc = EXIT_STUB_PC(dcontext, f, nxt); |
| else |
| next_stop_pc = pc + linkstub_size(dcontext, f, l); |
| CLIENT_ASSERT(next_stop_pc != NULL, "disassemble_fragment: invalid stubs"); |
| } |
| while (pc < next_stop_pc) { |
| pc = (cache_pc) disassemble_with_bytes(dcontext, (byte *)pc, outfile); |
| } |
| /* point pc back at tail of fragment code if it was off in separate stub land */ |
| if (TEST(LINK_SEPARATE_STUB, l->flags)) |
| pc = frag_pc; |
| exit_num++; |
| } |
| |
| if (TEST(FRAG_SELFMOD_SANDBOXED, f->flags)) { |
| DOSTATS({ /* skip stored sz */ end_pc -= sizeof(uint); }); |
| print_file(outfile, " -------- original code (from "PFX"-"PFX") -------- \n", |
| f->tag, (f->tag + (end_pc - pc))); |
| while (pc < end_pc) { |
| pc = (cache_pc) disassemble_with_bytes(dcontext, (byte *)pc, outfile); |
| } |
| } |
| |
| if (alloc) |
| fragment_free(dcontext, f); |
| } |
| |
| #ifdef DEBUG |
| void |
| disassemble_fragment(dcontext_t *dcontext, fragment_t *f, bool just_header) |
| { |
| if ((stats->logmask & LOG_EMIT) != 0) { |
| common_disassemble_fragment(dcontext, f, THREAD, |
| true, !just_header); |
| if (!just_header) |
| LOG(THREAD, LOG_EMIT, 1, "\n"); |
| } |
| } |
| #endif /* DEBUG */ |
| |
| void |
| disassemble_fragment_header(dcontext_t *dcontext, fragment_t *f, file_t outfile) |
| { |
| common_disassemble_fragment(dcontext, f, outfile, true, false); |
| } |
| |
| void |
| disassemble_fragment_body(dcontext_t *dcontext, fragment_t *f, file_t outfile) |
| { |
| common_disassemble_fragment(dcontext, f, outfile, false, true); |
| } |
| |
| void |
| disassemble_app_bb(dcontext_t *dcontext, app_pc tag, file_t outfile) |
| { |
| instrlist_t *ilist = build_app_bb_ilist(dcontext, tag, outfile); |
| instrlist_clear_and_destroy(dcontext, ilist); |
| } |
| |
| #endif /* !STANDALONE_DECODER */ |
| /***************************************************************************/ |
| |
| /* Two entry points to the disassembly routines: */ |
| |
| void |
| instrlist_disassemble(dcontext_t *dcontext, |
| app_pc tag, instrlist_t *ilist, file_t outfile) |
| { |
| int len, sz; |
| instr_t *instr; |
| byte *addr; |
| byte *next_addr; |
| byte bytes[64]; /* scratch array for encoding instrs */ |
| int level; |
| int offs = 0; |
| /* we want to print out the decode level each instr is at, so we have to |
| * do a little work |
| */ |
| |
| print_file(outfile, "TAG "PFX"\n", tag); |
| |
| for (instr = instrlist_first(ilist); instr; instr = instr_get_next(instr)) { |
| DOLOG(5, LOG_ALL, { |
| if (instr_raw_bits_valid(instr)) { |
| print_file(outfile, " <raw "PFX"-"PFX">::\n", |
| instr_get_raw_bits(instr), |
| instr_get_raw_bits(instr) + instr_length(dcontext, instr)); |
| } |
| if (instr_get_translation(instr) != NULL) { |
| print_file(outfile, " <translation "PFX">::\n", |
| instr_get_translation(instr)); |
| } |
| }); |
| if (instr_needs_encoding(instr)) { |
| byte *nxt_pc; |
| level = 4; |
| /* encode instr and then output as BINARY */ |
| nxt_pc = instr_encode_ignore_reachability(dcontext, instr, bytes); |
| ASSERT(nxt_pc != NULL); |
| len = (int) (nxt_pc - bytes); |
| addr = bytes; |
| CLIENT_ASSERT(len < 64, "instrlist_disassemble: too-long instr"); |
| } else { |
| addr = instr_get_raw_bits(instr); |
| len = instr_length(dcontext, instr); |
| if (instr_operands_valid(instr)) |
| level = 3; |
| else if (instr_opcode_valid(instr)) |
| level = 2; |
| else if (decode_sizeof(dcontext, addr, NULL _IF_X64(NULL)) == len) |
| level = 1; |
| else |
| level = 0; |
| } |
| |
| /* Print out individual instructions. Remember that multiple |
| * instructions may be packed into a single instr. |
| */ |
| if (level > 3) { |
| /* for L4 we want to see instr targets and don't care |
| * as much about raw bytes |
| */ |
| int extra_sz; |
| print_file(outfile, " +%-4d %c%d @"PFX" ", |
| offs, instr_is_app(instr) ? 'L' : 'm', level, instr); |
| extra_sz = print_bytes_to_file(outfile, addr, addr+len, instr); |
| instr_disassemble(dcontext, instr, outfile); |
| print_file(outfile, "\n"); |
| if (extra_sz > 0) { |
| print_file(outfile, IF_X64_ELSE("%30s","%22s"), " " ); |
| print_extra_bytes_to_file(outfile, addr, addr+len, extra_sz, ""); |
| } |
| offs += len; |
| len = 0; /* skip loop */ |
| } |
| while (len) { |
| print_file(outfile, " +%-4d %c%d "IF_X64_ELSE("%20s","%12s"), |
| offs, instr_is_app(instr) ? 'L' : 'm', level, " "); |
| next_addr = internal_disassemble_to_file |
| (dcontext, addr, addr, outfile, false, true, |
| IF_X64_ELSE(" "," ")); |
| if (next_addr == NULL) |
| break; |
| sz = (int) (next_addr - addr); |
| CLIENT_ASSERT(sz <= len, "instrlist_disassemble: invalid length"); |
| len -= sz; |
| addr += sz; |
| offs += sz; |
| } |
| DOLOG(5, LOG_ALL, { |
| print_file(outfile, "---- multi-instr boundary ----\n"); |
| }); |
| |
| #ifdef CUSTOM_EXIT_STUBS |
| /* custom exit stub? */ |
| if (instr_is_exit_cti(instr) && instr_is_app(instr)) { |
| instrlist_t * custom = instr_exit_stub_code(instr); |
| if (custom != NULL) { |
| print_file(outfile, "\t=> custom exit stub code:\n"); |
| instrlist_disassemble(dcontext, instr_get_branch_target_pc(instr), |
| custom, outfile); |
| } |
| } |
| #endif |
| |
| } |
| |
| print_file(outfile, "END "PFX"\n\n", tag); |
| } |
| |
| #endif /* INTERNAL || CLIENT_INTERFACE */ |
| |
| |
| /***************************************************************************/ |
| #ifndef STANDALONE_DECODER |
| |
| static void |
| callstack_dump_module_info(char *buf, size_t bufsz, size_t *sofar, |
| app_pc pc, uint flags) |
| { |
| if (TEST(CALLSTACK_MODULE_INFO, flags)) { |
| module_area_t *ma; |
| os_get_module_info_lock(); |
| ma = module_pc_lookup(pc); |
| if (ma != NULL) { |
| print_to_buffer(buf, bufsz, sofar, TEST(CALLSTACK_USE_XML, flags) ? |
| "mod=\""PFX"\" offs=\""PFX"\" " : " <%s+"PIFX">", |
| TEST(CALLSTACK_MODULE_PATH, flags) ? ma->full_path : |
| GET_MODULE_NAME(&ma->names), pc - ma->start); |
| } |
| os_get_module_info_unlock(); |
| } |
| } |
| |
| static void |
| internal_dump_callstack_to_buffer(char *buf, size_t bufsz, size_t *sofar, |
| app_pc cur_pc, app_pc ebp, uint flags) |
| { |
| ptr_uint_t *pc = (ptr_uint_t *) ebp; |
| int num = 0; |
| LOG_DECLARE(char symbolbuf[MAXIMUM_SYMBOL_LENGTH];) |
| const char *symbol_name = ""; |
| |
| if (TEST(CALLSTACK_ADD_HEADER, flags)) { |
| print_to_buffer(buf, bufsz, sofar, |
| TEST(CALLSTACK_USE_XML, flags) ? |
| "\t<call-stack>\n" : "Call stack:\n"); |
| } |
| |
| if (cur_pc != NULL) { |
| DOLOG(1, LOG_SYMBOLS, { |
| print_symbolic_address(cur_pc, symbolbuf, sizeof(symbolbuf), false); |
| symbol_name = symbolbuf; |
| }); |
| print_to_buffer(buf, bufsz, sofar, TEST(CALLSTACK_USE_XML, flags) ? |
| "\t<current_pc=\""PFX"\" name=\"%s\" " : |
| "\t"PFX" %s ", |
| cur_pc, symbol_name); |
| callstack_dump_module_info(buf, bufsz, sofar, cur_pc, flags); |
| print_to_buffer(buf, bufsz, sofar, TEST(CALLSTACK_USE_XML, flags) ? |
| "/>\n" : "\n"); |
| } |
| |
| while (pc != NULL && |
| is_readable_without_exception_query_os((byte *)pc, 8)) { |
| DOLOG(1, LOG_SYMBOLS, { |
| print_symbolic_address((app_pc)*(pc+1), symbolbuf, sizeof(symbolbuf), false); |
| symbol_name = symbolbuf; |
| }); |
| |
| print_to_buffer(buf, bufsz, sofar, TEST(CALLSTACK_USE_XML, flags) ? |
| "\t\t" : "\t"); |
| if (TEST(CALLSTACK_FRAME_PTR, flags)) { |
| print_to_buffer(buf, bufsz, sofar, TEST(CALLSTACK_USE_XML, flags) ? |
| "<frame ptr=\""PFX"\" parent=\""PFX"\" " : |
| "frame ptr "PFX" => parent "PFX", ", |
| pc, *pc); |
| } |
| print_to_buffer(buf, bufsz, sofar, TEST(CALLSTACK_USE_XML, flags) ? |
| "ret=\""PFX"\" name=\"%s\" " : PFX" %s ", |
| *(pc+1), symbol_name); |
| callstack_dump_module_info(buf, bufsz, sofar, (app_pc) *(pc+1), flags); |
| print_to_buffer(buf, bufsz, sofar, TEST(CALLSTACK_USE_XML, flags) ? |
| "/>\n" : "\n"); |
| |
| num++; |
| /* yes I've seen weird recursive cases before */ |
| if (pc == (ptr_uint_t *) *pc || num > 100) |
| break; |
| pc = (ptr_uint_t *) *pc; |
| } |
| |
| if (TESTALL(CALLSTACK_USE_XML | CALLSTACK_ADD_HEADER, flags)) |
| print_to_buffer(buf, bufsz, sofar, "\t</call-stack>\n"); |
| } |
| |
| static void |
| internal_dump_callstack(app_pc cur_pc, app_pc ebp, file_t outfile, bool dump_xml, |
| bool header) |
| { |
| char buf[MAX_LOG_LENGTH]; |
| size_t sofar = 0; |
| internal_dump_callstack_to_buffer(buf, BUFFER_SIZE_ELEMENTS(buf), &sofar, |
| cur_pc, ebp, |
| CALLSTACK_ADD_HEADER | |
| CALLSTACK_FRAME_PTR | |
| (dump_xml ? CALLSTACK_USE_XML : 0)); |
| print_file(outfile, "%s", buf); |
| } |
| |
| void |
| dump_callstack(app_pc pc, app_pc ebp, file_t outfile, bool dump_xml) |
| { |
| internal_dump_callstack(pc, ebp, outfile, dump_xml, true/*header*/); |
| } |
| |
| void |
| dump_callstack_to_buffer(char *buf, size_t bufsz, size_t *sofar, |
| app_pc pc, app_pc ebp, uint flags) |
| { |
| internal_dump_callstack_to_buffer(buf, bufsz, sofar, |
| pc, ebp, flags); |
| } |
| |
| #ifdef DEBUG |
| void |
| dump_mcontext_callstack(dcontext_t *dcontext) |
| { |
| priv_mcontext_t *mc = get_mcontext(dcontext); |
| LOG(THREAD, LOG_ALL, 1, "Call stack:\n"); |
| internal_dump_callstack((app_pc)mc->pc, |
| (app_pc)get_mcontext_frame_ptr(dcontext, mc), THREAD, |
| DUMP_NOT_XML, false/*!header*/); |
| } |
| #endif |
| |
| void |
| dump_dr_callstack(file_t outfile) |
| { |
| /* Since we're in DR we can't just clobber the saved app fields -- |
| * so we save them first |
| */ |
| app_pc our_ebp = 0; |
| GET_FRAME_PTR(our_ebp); |
| LOG(outfile, LOG_ALL, 1, "DynamoRIO call stack:\n"); |
| internal_dump_callstack(NULL /* don't care about cur pc */, our_ebp, |
| outfile, DUMP_NOT_XML, false/*!header*/); |
| } |
| |
| #endif /* !STANDALONE_DECODER */ |
| /***************************************************************************/ |