| /* ********************************************************** |
| * Copyright (c) 2011-2022 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 |
| * a -syntax_* runtime option is specified or disassemble_set_syntax() is called. |
| */ |
| |
| /* |
| * 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 "encode_api.h" |
| #include "instr.h" |
| #include "decode.h" |
| #include "decode_fast.h" |
| #include "disassemble.h" |
| #include "../module_shared.h" |
| #include "isa_regdeps/disassemble.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 */ |
| |
| #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 |
| d_r_print_encoding_first_line_to_buffer(char *buf, size_t bufsz, |
| size_t *sofar DR_PARAM_INOUT, byte *pc, |
| byte *next_pc, instr_t *instr); |
| |
| void |
| d_r_print_encoding_second_line_to_buffer(char *buf, size_t bufsz, |
| size_t *sofar DR_PARAM_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 DR_PARAM_INOUT, |
| opnd_t opnd); |
| |
| bool |
| opnd_disassemble_arch(char *buf, size_t bufsz, size_t *sofar DR_PARAM_INOUT, opnd_t opnd); |
| |
| bool |
| opnd_disassemble_noimplicit(char *buf, size_t bufsz, size_t *sofar DR_PARAM_INOUT, |
| dcontext_t *dcontext, instr_t *instr, byte optype, |
| opnd_t opnd, bool prev, bool multiple_encodings, bool dst, |
| int *idx DR_PARAM_OUT); |
| |
| void |
| print_instr_prefixes(dcontext_t *dcontext, instr_t *instr, char *buf, size_t bufsz, |
| size_t *sofar DR_PARAM_OUT); |
| |
| void |
| print_opcode_name(instr_t *instr, const char *name, char *buf, size_t bufsz, |
| size_t *sofar DR_PARAM_OUT); |
| |
| /**************************************************************************** |
| * 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 */ |
| } |
| if (DYNAMO_OPTION(syntax_arm)) { |
| flags |= DR_DISASM_ARM; |
| } |
| if (DYNAMO_OPTION(syntax_riscv)) { |
| flags |= DR_DISASM_RISCV; |
| } |
| /* 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 inline bool |
| dsts_first(void) |
| { |
| return TESTANY(DR_DISASM_INTEL | DR_DISASM_ARM | DR_DISASM_RISCV, |
| DYNAMO_OPTION(disasm_mask)); |
| } |
| |
| static inline bool |
| opmask_with_dsts(void) |
| { |
| return TESTANY(DR_DISASM_INTEL | DR_DISASM_ATT, DYNAMO_OPTION(disasm_mask)); |
| } |
| |
| static void |
| internal_instr_disassemble(char *buf, size_t bufsz, size_t *sofar DR_PARAM_INOUT, |
| dcontext_t *dcontext, instr_t *instr); |
| |
| static inline const char * |
| immed_prefix(void) |
| { |
| return (TEST(DR_DISASM_INTEL | DR_DISASM_RISCV, DYNAMO_OPTION(disasm_mask)) |
| ? "" |
| : (TEST(DR_DISASM_ARM, DYNAMO_OPTION(disasm_mask)) ? "#" : "$")); |
| } |
| |
| void |
| reg_disassemble(char *buf, size_t bufsz, size_t *sofar DR_PARAM_INOUT, reg_id_t reg, |
| dr_opnd_flags_t flags, const char *prefix, const char *suffix) |
| { |
| print_to_buffer(buf, bufsz, sofar, |
| TESTANY(DR_DISASM_INTEL | DR_DISASM_ARM | DR_DISASM_RISCV, |
| DYNAMO_OPTION(disasm_mask)) |
| ? "%s%s%s%s" |
| : "%s%s%%%s%s", |
| prefix, TEST(DR_OPND_NEGATED, flags) ? "-" : "", |
| get_register_name(reg), suffix); |
| } |
| |
| static const char * |
| opnd_size_suffix_dr(opnd_size_t opnd_sz) |
| { |
| int sz = opnd_size_in_bytes(opnd_sz); |
| switch (sz) { |
| case 1: return "1byte"; |
| case 2: return "2byte"; |
| case 3: return "3byte"; |
| 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 20: return "20byte"; |
| case 24: return "24byte"; |
| case 28: return "28byte"; |
| case 32: return "32byte"; |
| case 36: return "36byte"; |
| case 40: return "40byte"; |
| case 44: return "44byte"; |
| case 48: return "48byte"; |
| case 52: return "52byte"; |
| case 56: return "56byte"; |
| case 60: return "60byte"; |
| case 64: return "64byte"; |
| case 68: return "68byte"; |
| case 72: return "72byte"; |
| case 76: return "76byte"; |
| case 80: return "80byte"; |
| case 84: return "84byte"; |
| case 88: return "88byte"; |
| case 92: return "92byte"; |
| case 94: return "94byte"; |
| case 96: return "96byte"; |
| case 100: return "100byte"; |
| case 104: return "104byte"; |
| case 108: return "108byte"; |
| case 112: return "112byte"; |
| case 116: return "116byte"; |
| case 120: return "120byte"; |
| case 124: return "124byte"; |
| case 128: return "128byte"; |
| 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 ""; |
| } |
| |
| #ifdef AARCHXX |
| static const char * |
| opnd_size_element_suffix(opnd_t opnd) |
| { |
| int sz = opnd_get_vector_element_size(opnd); |
| switch (sz) { |
| case OPSZ_1: return ".b"; |
| case OPSZ_2: return ".h"; |
| case OPSZ_4: return ".s"; |
| case OPSZ_8: return ".d"; |
| case OPSZ_16: return ".q"; |
| } |
| return ""; |
| } |
| |
| static const char * |
| aarch64_reg_opnd_suffix(opnd_t opnd) |
| { |
| if (opnd_is_element_vector_reg(opnd)) |
| return opnd_size_element_suffix(opnd); |
| if (opnd_is_predicate_merge(opnd)) |
| return "/m"; |
| if (opnd_is_predicate_zero(opnd)) |
| return "/z"; |
| |
| return ""; |
| } |
| #endif |
| #ifdef AARCH64 |
| bool |
| aarch64_predicate_constraint_is_mapped(ptr_int_t value) |
| { |
| return value >= DR_PRED_CONSTR_FIRST_NUMBER && value <= DR_PRED_CONSTR_LAST_NUMBER; |
| } |
| |
| static const char * |
| aarch64_predicate_constraint_string(ptr_int_t value) |
| { |
| switch (value) { |
| case DR_PRED_CONSTR_POW2: return "POW2"; |
| case DR_PRED_CONSTR_VL1: return "VL1"; |
| case DR_PRED_CONSTR_VL2: return "VL2"; |
| case DR_PRED_CONSTR_VL3: return "VL3"; |
| case DR_PRED_CONSTR_VL4: return "VL4"; |
| case DR_PRED_CONSTR_VL5: return "VL5"; |
| case DR_PRED_CONSTR_VL6: return "VL6"; |
| case DR_PRED_CONSTR_VL7: return "VL7"; |
| case DR_PRED_CONSTR_VL8: return "VL8"; |
| case DR_PRED_CONSTR_VL16: return "VL16"; |
| case DR_PRED_CONSTR_VL32: return "VL32"; |
| case DR_PRED_CONSTR_VL64: return "VL64"; |
| case DR_PRED_CONSTR_VL128: return "VL128"; |
| case DR_PRED_CONSTR_VL256: return "VL256"; |
| case DR_PRED_CONSTR_MUL4: return "MUL4"; |
| case DR_PRED_CONSTR_MUL3: return "MUL3"; |
| case DR_PRED_CONSTR_ALL: return "ALL"; |
| default: return "UKNOWN_CONSTRAINT"; |
| } |
| } |
| |
| #endif |
| |
| static void |
| opnd_mem_disassemble_prefix(char *buf, size_t bufsz, size_t *sofar DR_PARAM_INOUT, |
| 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, "["); |
| } else if (TEST(DR_DISASM_ARM, DYNAMO_OPTION(disasm_mask))) { |
| print_to_buffer(buf, bufsz, sofar, "["); |
| } |
| } |
| |
| static void |
| opnd_base_disp_disassemble(char *buf, size_t bufsz, size_t *sofar DR_PARAM_INOUT, |
| 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); |
| |
| const char *base_suffix = ""; |
| const char *index_suffix = ""; |
| |
| #if defined(AARCH64) |
| if (reg_is_z(base)) |
| base_suffix = opnd_size_element_suffix(opnd); |
| if (reg_is_z(index)) |
| index_suffix = opnd_size_element_suffix(opnd); |
| #endif |
| |
| opnd_mem_disassemble_prefix(buf, bufsz, sofar, opnd); |
| |
| if (seg != REG_NULL) |
| reg_disassemble(buf, bufsz, sofar, seg, 0, "", ":"); |
| |
| if (TESTANY(DR_DISASM_INTEL | DR_DISASM_ARM, DYNAMO_OPTION(disasm_mask))) { |
| if (base != REG_NULL) |
| reg_disassemble(buf, bufsz, sofar, base, 0, "", base_suffix); |
| 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))) ? "+" |
| : "", |
| index_suffix); |
| 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 and AArch64. I would do the same for x86 but |
| * I don't want to break any existing scripts. |
| */ |
| IF_NOT_X86(|| true)) { |
| /* windbg negates if top byte is 0xff |
| * for x64 udis86 negates if at all negative |
| */ |
| if (TEST(DR_DISASM_ARM, DYNAMO_OPTION(disasm_mask))) |
| print_to_buffer(buf, bufsz, sofar, ", #"); |
| 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) { |
| if (TEST(DR_OPND_NEGATED, opnd_get_flags(opnd))) |
| print_to_buffer(buf, bufsz, sofar, "-"); |
| else if (!TEST(DR_DISASM_ARM, DYNAMO_OPTION(disasm_mask))) |
| print_to_buffer(buf, bufsz, sofar, "+"); |
| } |
| } else if (TEST(DR_DISASM_ATT, DYNAMO_OPTION(disasm_mask))) { |
| /* There seems to be a discrepency between windbg and binutils. The latter |
| * prints a '-' displacement for negative displacements both for att and |
| * intel. We are doing the same for att syntax, while we're following windbg |
| * for intel's syntax. XXX i#3574: should we do the same for intel's syntax? |
| */ |
| if (disp < 0) { |
| disp = -disp; |
| print_to_buffer(buf, bufsz, sofar, "-"); |
| } |
| } |
| if (TEST(DR_DISASM_ARM, DYNAMO_OPTION(disasm_mask))) |
| print_to_buffer(buf, bufsz, sofar, "%d", disp); |
| else if (TEST(DR_DISASM_RISCV, DYNAMO_OPTION(disasm_mask)) && |
| TEST(opnd_get_flags(opnd), DR_OPND_IMM_PRINT_DECIMAL)) |
| print_to_buffer(buf, bufsz, sofar, "%d", disp); |
| else if ((unsigned)disp <= 0xff && !opnd_is_disp_force_full(opnd)) |
| print_to_buffer(buf, bufsz, sofar, "0x%02x", disp); |
| else if ((unsigned)disp <= 0xffff IF_X86(&&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 (!TESTANY(DR_DISASM_INTEL | DR_DISASM_ARM, 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, "", base_suffix); |
| if (index != REG_NULL) { |
| reg_disassemble(buf, bufsz, sofar, index, opnd_get_flags(opnd), ",", |
| index_suffix); |
| opnd_base_disp_scale_disassemble(buf, bufsz, sofar, opnd); |
| } |
| print_to_buffer(buf, bufsz, sofar, ")"); |
| } |
| } |
| |
| if (TESTANY(DR_DISASM_INTEL | DR_DISASM_ARM, DYNAMO_OPTION(disasm_mask))) |
| print_to_buffer(buf, bufsz, sofar, "]"); |
| } |
| |
| static bool |
| print_known_pc_target(char *buf, size_t bufsz, size_t *sofar DR_PARAM_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) |
| if (ibl_name == NULL && in_coarse_stub_prefixes(target)) { |
| /* FIXME i#1575: 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_X86_64(GENCODE_X64))) |
| gencode_routine = "fcache_return"; |
| # ifdef X64 |
| else if (SHARED_FRAGMENTS_ENABLED() && |
| target == fcache_return_shared_routine(IF_X86_64(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_X86_64(GENCODE_X64))) |
| gencode_routine = "fcache_return_coarse"; |
| else if (target == trace_head_return_coarse_prefix(target, NULL) || |
| target == trace_head_return_coarse_routine(IF_X86_64(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_X86_64(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_X86_64(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 DR_PARAM_INOUT, |
| dcontext_t *dcontext, opnd_t opnd, bool use_size_sfx) |
| { |
| if (opnd_disassemble_arch(buf, bufsz, sofar, opnd)) |
| return; |
| |
| 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 few pointer-sized immeds so let's always negate */ |
| if (val < 0 && opnd_size_in_bytes(opnd_get_size(opnd)) < sizeof(void *)) { |
| 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 (TEST(DR_DISASM_ARM, DYNAMO_OPTION(disasm_mask))) { |
| print_to_buffer(buf, bufsz, sofar, "%s%s%d", immed_prefix(), sign, (uint)val); |
| #ifdef AARCH64 |
| } else if (TEST(opnd_get_flags(opnd), DR_OPND_IS_PREDICATE_CONSTRAINT) && |
| !aarch64_predicate_constraint_is_mapped(val)) { |
| print_to_buffer(buf, bufsz, sofar, aarch64_predicate_constraint_string(val)); |
| #endif |
| } else 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 { |
| int64 val64 = val; |
| IF_NOT_X64({ |
| if (opnd_is_immed_int64(opnd)) |
| val64 = opnd_get_immed_int64(opnd); |
| }); |
| print_to_buffer(buf, bufsz, sofar, "%s%s0x" ZHEX64_FORMAT_STRING, |
| immed_prefix(), sign, val64); |
| } |
| } break; |
| case IMMED_FLOAT_kind: { |
| /* Save floating state for 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; |
| } |
| #ifndef WINDOWS |
| /* XXX i#4488: x87 floating point immediates should be double precision. |
| * Type double currently not included for Windows because sizeof(opnd_t) does |
| * not equal EXPECTED_SIZEOF_OPND, triggering the ASSERT in d_r_arch_init(). |
| */ |
| case IMMED_DOUBLE_kind: { |
| PRESERVE_FLOATING_POINT_STATE({ |
| uint top; |
| uint bottom; |
| const char *sign; |
| double_print(opnd_get_immed_double(opnd), 6, &top, &bottom, &sign); |
| print_to_buffer(buf, bufsz, sofar, "%s%s%u.%.6u", immed_prefix(), sign, top, |
| bottom); |
| }); |
| break; |
| } |
| #endif |
| 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), "", |
| IF_AARCHXX_ELSE(aarch64_reg_opnd_suffix(opnd), "")); |
| break; |
| case BASE_DISP_kind: opnd_base_disp_disassemble(buf, bufsz, sofar, opnd); break; |
| #if defined(X64) || defined(ARM) |
| case REL_ADDR_kind: |
| print_to_buffer(buf, bufsz, sofar, "<rel> "); |
| /* fall-through */ |
| # ifdef X64 |
| case ABS_ADDR_kind: |
| # endif |
| opnd_mem_disassemble_prefix(buf, bufsz, sofar, 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 IMMED_DOUBLE_kind: |
| case PC_kind: |
| case FAR_PC_kind: break; |
| case REG_kind: |
| if (!opnd_is_reg_partial(opnd)) |
| break; |
| /* fall-through */ |
| default: { |
| opnd_size_t opnd_sz = opnd_get_size(opnd); |
| const char *size_str = opnd_size_suffix_dr(opnd_sz); |
| if (size_str[0] != '\0') |
| print_to_buffer(buf, bufsz, sofar, "[%s]", size_str); |
| } |
| } |
| } |
| } |
| |
| void |
| opnd_disassemble(void *drcontext, opnd_t opnd, file_t outfile) |
| { |
| dcontext_t *dcontext = (dcontext_t *)drcontext; |
| 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(void *drcontext, opnd_t opnd, char *buf, size_t bufsz) |
| |
| { |
| dcontext_t *dcontext = (dcontext_t *)drcontext; |
| 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 = d_r_print_encoding_first_line_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; |
| d_r_print_encoding_second_line_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 DR_PARAM_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 " ", |
| PC_AS_LOAD_TGT(instr_get_isa_mode(&instr), orig_pc)); |
| } |
| |
| dr_isa_mode_t instr_isa_mode = instr_get_isa_mode(&instr); |
| if (with_bytes) { |
| if (instr_isa_mode == DR_ISA_REGDEPS) { |
| extra_sz = |
| d_r_regdeps_print_encoding_first_line(buf, bufsz, sofar, pc, next_pc); |
| } else |
| extra_sz = d_r_print_encoding_first_line_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"), " "); |
| if (instr_isa_mode == DR_ISA_REGDEPS) { |
| d_r_regdeps_print_encoding_second_line(buf, bufsz, sofar, pc, next_pc, |
| extra_sz, extra_bytes_prefix); |
| } else { |
| d_r_print_encoding_second_line_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(void *drcontext, byte *pc, file_t outfile) |
| { |
| dcontext_t *dcontext = (dcontext_t *)drcontext; |
| 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(void *drcontext, byte *pc, file_t outfile, bool show_pc, |
| bool show_bytes) |
| { |
| dcontext_t *dcontext = (dcontext_t *)drcontext; |
| 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(void *drcontext, byte *copy_pc, byte *orig_pc, file_t outfile, |
| bool show_pc, bool show_bytes) |
| { |
| dcontext_t *dcontext = (dcontext_t *)drcontext; |
| return internal_disassemble_to_file(dcontext, copy_pc, orig_pc, outfile, show_pc, |
| show_bytes, ""); |
| } |
| |
| byte * |
| disassemble_to_buffer(void *drcontext, byte *pc, byte *orig_pc, bool show_pc, |
| bool show_bytes, char *buf, size_t bufsz, int *printed DR_PARAM_OUT) |
| { |
| dcontext_t *dcontext = (dcontext_t *)drcontext; |
| 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 DR_PARAM_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; |
| bool is_evex_mask_pending = 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; |
| |
| IF_X86({ /* XXX i#1683: not using yet on ARM so avoiding the cost */ |
| /* XXX: avoid the cost of encoding unless at L4 */ |
| info = get_encoding_info(instr); |
| if (info == NULL) { |
| print_to_buffer(buf, bufsz, sofar, "<INVALID>"); |
| return; |
| } |
| }); |
| num = dsts_first() ? instr_num_dsts(instr) : instr_num_srcs(instr); |
| for (i = 0; i < num; i++) { |
| bool printing = false; |
| opnd = dsts_first() ? instr_get_dst(instr, i) : instr_get_src(instr, i); |
| IF_X86_ELSE({ optype = instr_info_opnd_type(info, !dsts_first(), i); }, |
| { |
| /* XXX i#1683: -syntax_arm currently fails here on register lists |
| * and will trigger the assert in instr_info_opnd_type(). We |
| * don't use the optype on ARM yet though. |
| */ |
| optype = 0; |
| }); |
| bool is_evex_mask = !instr_is_opmask(instr) && opnd_is_reg(opnd) && |
| reg_is_opmask(opnd_get_reg(opnd)) && opmask_with_dsts(); |
| if (!is_evex_mask) { |
| print_to_buffer(buf, bufsz, sofar, ""); |
| printing = opnd_disassemble_noimplicit(buf, bufsz, sofar, dcontext, instr, |
| optype, opnd, prev, multiple_encodings, |
| dsts_first(), &i); |
| print_to_buffer(buf, bufsz, sofar, ""); |
| } else { |
| CLIENT_ASSERT(!dsts_first(), "Evex mask can only be a source."); |
| CLIENT_ASSERT(!is_evex_mask_pending, "There can only be one evex mask."); |
| is_evex_mask_pending = true; |
| } |
| /* w/o the "printing" check we suppress "push esp" => "push" */ |
| if (printing && i < 3) |
| optype_already[i] = optype; |
| prev = printing || prev; |
| } |
| num = dsts_first() ? instr_num_srcs(instr) : instr_num_dsts(instr); |
| for (i = 0; i < num; i++) { |
| bool print = true; |
| opnd = dsts_first() ? instr_get_src(instr, i) : instr_get_dst(instr, i); |
| IF_X86_ELSE({ optype = instr_info_opnd_type(info, dsts_first(), i); }, |
| { |
| /* XXX i#1683: see comment above */ |
| optype = 0; |
| }); |
| IF_X86({ |
| /* 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 */ |
| print = ((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)))); |
| }); |
| if (print) { |
| bool is_evex_mask = !instr_is_opmask(instr) && opnd_is_reg(opnd) && |
| reg_is_opmask(opnd_get_reg(opnd)) && opmask_with_dsts(); |
| print_to_buffer(buf, bufsz, sofar, is_evex_mask ? " {" : ""); |
| prev = opnd_disassemble_noimplicit(buf, bufsz, sofar, dcontext, instr, optype, |
| opnd, prev && !is_evex_mask, |
| multiple_encodings, !dsts_first(), &i) || |
| prev; |
| print_to_buffer(buf, bufsz, sofar, is_evex_mask ? "}" : ""); |
| } |
| } |
| if (is_evex_mask_pending) { |
| int mask_index = 0; |
| opnd = instr_get_src(instr, mask_index); |
| CLIENT_ASSERT(IF_X86_ELSE(true, false), "evex mask can only exist for x86."); |
| optype = instr_info_opnd_type(info, !dsts_first(), mask_index); |
| CLIENT_ASSERT(!instr_is_opmask(instr) && opnd_is_reg(opnd) && |
| reg_is_opmask(opnd_get_reg(opnd)) && opmask_with_dsts(), |
| "evex mask must always be the first source."); |
| print_to_buffer(buf, bufsz, sofar, " {"); |
| opnd_disassemble_noimplicit(buf, bufsz, sofar, dcontext, instr, optype, opnd, |
| false, multiple_encodings, dsts_first(), &mask_index); |
| print_to_buffer(buf, bufsz, sofar, "}"); |
| } |
| } |
| |
| static bool |
| instr_needs_opnd_size_sfx(instr_t *instr) |
| { |
| /* DR_ISA_REGDEPS instructions don't have sizes for operands. |
| * They only have a single operation size. |
| */ |
| if (instr_get_isa_mode(instr) == DR_ISA_REGDEPS) |
| return false; |
| |
| #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 !defined(X86) && !defined(ARM) |
| /* Automatic sign extension is probably only useful on Intel but |
| * is left enabled on ARM (AArch32) as it is what some tests expect. |
| */ |
| return; |
| #endif |
| |
| 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 to buf a space-separated string representation of the list of categories an |
| * instruction belongs to. |
| */ |
| static void |
| print_category_names_to_buffer(char *buf, size_t bufsz, size_t *sofar, uint category) |
| { |
| if (category == DR_INSTR_CATEGORY_UNCATEGORIZED) { |
| const char *category_name = instr_get_category_name(category); |
| print_to_buffer(buf, bufsz, sofar, "%s ", category_name); |
| return; |
| } |
| |
| /* We consider 0x80000000 enough to be future proof when adding new categories. |
| */ |
| const uint max_mask = 0x80000000; |
| for (uint mask = 0x1; mask <= max_mask; mask <<= 1) { |
| if (TESTANY(mask, category)) { |
| const char *category_name = instr_get_category_name(mask); |
| print_to_buffer(buf, bufsz, sofar, "%s ", category_name); |
| } |
| |
| /* Guard against 32 bit overflow. |
| */ |
| if (mask == max_mask) |
| break; |
| } |
| } |
| |
| /* |
| * 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 DR_PARAM_INOUT, |
| dcontext_t *dcontext, instr_t *instr) |
| { |
| int i; |
| const char *name; |
| int name_width = 6; |
| bool use_size_sfx = false; |
| size_t offs_pre_name, offs_post_name, offs_pre_opnds; |
| |
| /* Print the instruction categories instead of opcode for DR_ISA_REGDEPS |
| * instructions. Print the operation size right after. |
| */ |
| if (instr_get_isa_mode(instr) == DR_ISA_REGDEPS) { |
| uint category = instr_get_category(instr); |
| print_category_names_to_buffer(buf, bufsz, sofar, category); |
| opnd_size_t operation_size = instr->operation_size; |
| const char *operation_size_str = opnd_size_suffix_dr(operation_size); |
| if (operation_size_str[0] != '\0') |
| print_to_buffer(buf, bufsz, sofar, "[%s]", operation_size_str); |
| name = ""; |
| } else if (!instr_valid(instr)) { |
| print_to_buffer(buf, bufsz, sofar, "<INVALID>"); |
| return; |
| } else if (instr_is_label(instr)) { |
| /* Since labels with different note values are used during instrumentation |
| * to mark different regions, it is useful to display the note. |
| */ |
| print_to_buffer(buf, bufsz, sofar, "<label note=%p>", instr_get_note(instr)); |
| return; |
| } else if (instr_opcode_valid(instr)) { |
| #ifdef AARCH64 |
| /* We do not use instr_info_t encoding info on AArch64. FIXME i#1569 */ |
| name = get_opcode_name(instr_get_opcode(instr)); |
| #else |
| const instr_info_t *info = instr_get_instr_info(instr); |
| name = info->name; |
| #endif |
| } else |
| name = "<RAW>"; |
| |
| print_instr_prefixes(dcontext, instr, buf, bufsz, sofar); |
| |
| offs_pre_name = *sofar; |
| if (instr_opcode_valid(instr)) /* Avoid assert on level-0 bundle. */ |
| print_opcode_name(instr, name, buf, bufsz, sofar); |
| else |
| print_to_buffer(buf, bufsz, sofar, "%s", name); |
| offs_post_name = *sofar; |
| name_width -= (int)(offs_post_name - offs_pre_name); |
| print_to_buffer(buf, bufsz, sofar, " "); |
| for (i = 0; i < name_width; i++) |
| print_to_buffer(buf, bufsz, sofar, " "); |
| offs_pre_opnds = *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 | DR_DISASM_ARM, |
| DYNAMO_OPTION(disasm_mask))) { |
| #ifdef AARCH64 |
| /* TODO i#4382: Implement DR_DISASM_AARCH64. */ |
| SYSLOG_INTERNAL_WARNING_ONCE("Selected disassembly style is not implemented for " |
| "AArch64: no operands will be printed."); |
| #endif |
| instr_disassemble_opnds_noimplicit(buf, bufsz, sofar, dcontext, instr); |
| /* we avoid trailing spaces if no operands */ |
| if (*sofar == offs_pre_opnds) { |
| *sofar = offs_post_name; |
| buf[offs_post_name] = '\0'; |
| } |
| 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); |
| if (i > 0) |
| print_to_buffer(buf, bufsz, sofar, " "); |
| sign_extend_immed(instr, i, &src); |
| /* XXX i#1312: we may want to more closely resemble ATT and Intel syntax w.r.t. |
| * EVEX mask operand. Tools tend to print the mask in conjunction with the |
| * destination in {} brackets. |
| */ |
| bool is_evex_mask = !instr_is_opmask(instr) && opnd_is_reg(src) && |
| reg_is_opmask(opnd_get_reg(src)); |
| print_to_buffer(buf, bufsz, sofar, is_evex_mask ? "{" : ""); |
| internal_opnd_disassemble(buf, bufsz, sofar, dcontext, src, use_size_sfx); |
| print_to_buffer(buf, bufsz, sofar, is_evex_mask ? "}" : ""); |
| } |
| if (instr_num_dsts(instr) > 0) { |
| print_to_buffer(buf, bufsz, sofar, " ->"); |
| for (i = 0; i < instr_num_dsts(instr); i++) { |
| print_to_buffer(buf, bufsz, sofar, " "); |
| internal_opnd_disassemble(buf, bufsz, sofar, dcontext, |
| instr_get_dst(instr, i), use_size_sfx); |
| } |
| } |
| /* we avoid trailing spaces if no operands */ |
| if (*sofar == offs_pre_opnds) { |
| *sofar = offs_post_name; |
| buf[offs_post_name] = '\0'; |
| } |
| } |
| |
| /* |
| * 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(void *drcontext, instr_t *instr, file_t outfile) |
| { |
| dcontext_t *dcontext = (dcontext_t *)drcontext; |
| 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. |
| * Uses DR syntax unless otherwise specified (see disassemble_set_syntax()). |
| */ |
| size_t |
| instr_disassemble_to_buffer(void *drcontext, instr_t *instr, char *buf, size_t bufsz) |
| { |
| dcontext_t *dcontext = (dcontext_t *)drcontext; |
| 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: 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 (d_r_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); |
| if (LINKSTUB_DIRECT(l->flags)) |
| next_stop_pc -= DIRECT_EXIT_STUB_DATA_SZ; |
| 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); |
| } |
| if (LINKSTUB_DIRECT(l->flags) && DIRECT_EXIT_STUB_DATA_SZ > 0) { |
| ASSERT(DIRECT_EXIT_STUB_DATA_SZ == |
| sizeof(cache_pc) IF_AARCH64_ELSE( |
| +DIRECT_EXIT_STUB_DATA_SLOT_ALIGNMENT_PADDING, |
| IF_RISCV64(+DIRECT_EXIT_STUB_DATA_SLOT_ALIGNMENT_PADDING))); |
| if (stub_is_patched(dcontext, f, EXIT_STUB_PC(dcontext, f, l))) { |
| print_file(outfile, " <stored target: " PFX ">\n", |
| *(cache_pc *)IF_AARCH64_ELSE(ALIGN_FORWARD(next_stop_pc, 8), |
| next_stop_pc)); |
| } |
| pc += DIRECT_EXIT_STUB_DATA_SZ; |
| } |
| /* 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 ((d_r_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(void *drcontext, app_pc tag, instrlist_t *ilist, file_t outfile) |
| { |
| dcontext_t *dcontext = (dcontext_t *)drcontext; |
| 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); |
| CLIENT_ASSERT(nxt_pc != NULL, "failed to encode instr"); |
| len = (int)(nxt_pc - bytes); |
| addr = bytes; |
| CLIENT_ASSERT(len < sizeof(bytes), "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_X86_64(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 || |
| /* Print as an instr for L3 to get IT predicates */ |
| (level == 3 && !instr_is_cti_short_rewrite(instr, addr))) { |
| |
| /* 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, " "); |
| /* Leave level 0 alone as it may not be code. */ |
| if (level == 0) { |
| print_file(outfile, " <...%d bytes...>\n", instr->length); |
| next_addr = addr + instr->length; |
| } else { |
| 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"); }); |
| } |
| |
| print_file(outfile, "END " PFX "\n\n", tag); |
| } |
| |
| /***************************************************************************/ |
| #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 tid=" TIDFMT ">\n" |
| : "Thread " TIDFMT |
| " call stack:\n", |
| /* We avoid TLS tid to work on crashes */ |
| IF_WINDOWS_ELSE(d_r_get_thread_id(), get_sys_thread_id())); |
| } |
| |
| 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 */ |
| /***************************************************************************/ |