X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=disas.c;h=d9aa713a408abd81cf529db2eab741c82b30b9bb;hb=bea0279b72fcd7bf5b5305fabf3a3cbee3a3eaa6;hp=54eea3f9c94a1ed7dae63dcf7acbcd02defec694;hpb=e67277f8f32d56c64c7993163f41bb4338aec566;p=mirror_qemu.git diff --git a/disas.c b/disas.c index 54eea3f9c9..d9aa713a40 100644 --- a/disas.c +++ b/disas.c @@ -6,6 +6,7 @@ #include "cpu.h" #include "disas/disas.h" +#include "disas/capstone.h" typedef struct CPUDebug { struct disassemble_info info; @@ -171,15 +172,257 @@ static int print_insn_od_target(bfd_vma pc, disassemble_info *info) return print_insn_objdump(pc, info, "OBJD-T"); } -/* Disassemble this for me please... (debugging). 'flags' has the following - values: - i386 - 1 means 16 bit code, 2 means 64 bit code - ppc - bits 0:15 specify (optionally) the machine instruction set; - bit 16 indicates little endian. - other targets - unused - */ +#ifdef CONFIG_CAPSTONE +/* Temporary storage for the capstone library. This will be alloced via + malloc with a size private to the library; thus there's no reason not + to share this across calls and across host vs target disassembly. */ +static __thread cs_insn *cap_insn; + +/* Initialize the Capstone library. */ +/* ??? It would be nice to cache this. We would need one handle for the + host and one for the target. For most targets we can reset specific + parameters via cs_option(CS_OPT_MODE, new_mode), but we cannot change + CS_ARCH_* in this way. Thus we would need to be able to close and + re-open the target handle with a different arch for the target in order + to handle AArch64 vs AArch32 mode switching. */ +static cs_err cap_disas_start(disassemble_info *info, csh *handle) +{ + cs_mode cap_mode = info->cap_mode; + cs_err err; + + cap_mode += (info->endian == BFD_ENDIAN_BIG ? CS_MODE_BIG_ENDIAN + : CS_MODE_LITTLE_ENDIAN); + + err = cs_open(info->cap_arch, cap_mode, handle); + if (err != CS_ERR_OK) { + return err; + } + + /* ??? There probably ought to be a better place to put this. */ + if (info->cap_arch == CS_ARCH_X86) { + /* We don't care about errors (if for some reason the library + is compiled without AT&T syntax); the user will just have + to deal with the Intel syntax. */ + cs_option(*handle, CS_OPT_SYNTAX, CS_OPT_SYNTAX_ATT); + } + + /* "Disassemble" unknown insns as ".byte W,X,Y,Z". */ + cs_option(*handle, CS_OPT_SKIPDATA, CS_OPT_ON); + + /* Allocate temp space for cs_disasm_iter. */ + if (cap_insn == NULL) { + cap_insn = cs_malloc(*handle); + if (cap_insn == NULL) { + cs_close(handle); + return CS_ERR_MEM; + } + } + return CS_ERR_OK; +} + +static void cap_dump_insn_units(disassemble_info *info, cs_insn *insn, + int i, int n) +{ + fprintf_function print = info->fprintf_func; + FILE *stream = info->stream; + + switch (info->cap_insn_unit) { + case 4: + if (info->endian == BFD_ENDIAN_BIG) { + for (; i < n; i += 4) { + print(stream, " %08x", ldl_be_p(insn->bytes + i)); + + } + } else { + for (; i < n; i += 4) { + print(stream, " %08x", ldl_le_p(insn->bytes + i)); + } + } + break; + + case 2: + if (info->endian == BFD_ENDIAN_BIG) { + for (; i < n; i += 2) { + print(stream, " %04x", lduw_be_p(insn->bytes + i)); + } + } else { + for (; i < n; i += 2) { + print(stream, " %04x", lduw_le_p(insn->bytes + i)); + } + } + break; + + default: + for (; i < n; i++) { + print(stream, " %02x", insn->bytes[i]); + } + break; + } +} + +static void cap_dump_insn(disassemble_info *info, cs_insn *insn) +{ + fprintf_function print = info->fprintf_func; + int i, n, split; + + print(info->stream, "0x%08" PRIx64 ": ", insn->address); + + n = insn->size; + split = info->cap_insn_split; + + /* Dump the first SPLIT bytes of the instruction. */ + cap_dump_insn_units(info, insn, 0, MIN(n, split)); + + /* Add padding up to SPLIT so that mnemonics line up. */ + if (n < split) { + int width = (split - n) / info->cap_insn_unit; + width *= (2 * info->cap_insn_unit + 1); + print(info->stream, "%*s", width, ""); + } + + /* Print the actual instruction. */ + print(info->stream, " %-8s %s\n", insn->mnemonic, insn->op_str); + + /* Dump any remaining part of the insn on subsequent lines. */ + for (i = split; i < n; i += split) { + print(info->stream, "0x%08" PRIx64 ": ", insn->address + i); + cap_dump_insn_units(info, insn, i, MIN(n, i + split)); + print(info->stream, "\n"); + } +} + +/* Disassemble SIZE bytes at PC for the target. */ +static bool cap_disas_target(disassemble_info *info, uint64_t pc, size_t size) +{ + uint8_t cap_buf[1024]; + csh handle; + cs_insn *insn; + size_t csize = 0; + + if (cap_disas_start(info, &handle) != CS_ERR_OK) { + return false; + } + insn = cap_insn; + + while (1) { + size_t tsize = MIN(sizeof(cap_buf) - csize, size); + const uint8_t *cbuf = cap_buf; + + target_read_memory(pc + csize, cap_buf + csize, tsize, info); + csize += tsize; + size -= tsize; + + while (cs_disasm_iter(handle, &cbuf, &csize, &pc, insn)) { + cap_dump_insn(info, insn); + } + + /* If the target memory is not consumed, go back for more... */ + if (size != 0) { + /* ... taking care to move any remaining fractional insn + to the beginning of the buffer. */ + if (csize != 0) { + memmove(cap_buf, cbuf, csize); + } + continue; + } + + /* Since the target memory is consumed, we should not have + a remaining fractional insn. */ + if (csize != 0) { + (*info->fprintf_func)(info->stream, + "Disassembler disagrees with translator " + "over instruction decoding\n" + "Please report this to qemu-devel@nongnu.org\n"); + } + break; + } + + cs_close(&handle); + return true; +} + +/* Disassemble SIZE bytes at CODE for the host. */ +static bool cap_disas_host(disassemble_info *info, void *code, size_t size) +{ + csh handle; + const uint8_t *cbuf; + cs_insn *insn; + uint64_t pc; + + if (cap_disas_start(info, &handle) != CS_ERR_OK) { + return false; + } + insn = cap_insn; + + cbuf = code; + pc = (uintptr_t)code; + + while (cs_disasm_iter(handle, &cbuf, &size, &pc, insn)) { + cap_dump_insn(info, insn); + } + if (size != 0) { + (*info->fprintf_func)(info->stream, + "Disassembler disagrees with TCG over instruction encoding\n" + "Please report this to qemu-devel@nongnu.org\n"); + } + + cs_close(&handle); + return true; +} + +#if !defined(CONFIG_USER_ONLY) +/* Disassemble COUNT insns at PC for the target. */ +static bool cap_disas_monitor(disassemble_info *info, uint64_t pc, int count) +{ + uint8_t cap_buf[32]; + csh handle; + cs_insn *insn; + size_t csize = 0; + + if (cap_disas_start(info, &handle) != CS_ERR_OK) { + return false; + } + insn = cap_insn; + + while (1) { + /* We want to read memory for one insn, but generically we do not + know how much memory that is. We have a small buffer which is + known to be sufficient for all supported targets. Try to not + read beyond the page, Just In Case. For even more simplicity, + ignore the actual target page size and use a 1k boundary. If + that turns out to be insufficient, we'll come back around the + loop and read more. */ + uint64_t epc = QEMU_ALIGN_UP(pc + csize + 1, 1024); + size_t tsize = MIN(sizeof(cap_buf) - csize, epc - pc); + const uint8_t *cbuf = cap_buf; + + /* Make certain that we can make progress. */ + assert(tsize != 0); + info->read_memory_func(pc, cap_buf + csize, tsize, info); + csize += tsize; + + if (cs_disasm_iter(handle, &cbuf, &csize, &pc, insn)) { + cap_dump_insn(info, insn); + if (--count <= 0) { + break; + } + } + memmove(cap_buf, cbuf, csize); + } + + cs_close(&handle); + return true; +} +#endif /* !CONFIG_USER_ONLY */ +#else +# define cap_disas_target(i, p, s) false +# define cap_disas_host(i, p, s) false +# define cap_disas_monitor(i, p, c) false +#endif /* CONFIG_CAPSTONE */ + +/* Disassemble this for me please... (debugging). */ void target_disas(FILE *out, CPUState *cpu, target_ulong code, - target_ulong size, int flags) + target_ulong size) { CPUClass *cc = CPU_GET_CLASS(cpu); target_ulong pc; @@ -193,6 +436,10 @@ void target_disas(FILE *out, CPUState *cpu, target_ulong code, s.info.buffer_vma = code; s.info.buffer_length = size; s.info.print_address_func = generic_print_address; + s.info.cap_arch = -1; + s.info.cap_mode = 0; + s.info.cap_insn_unit = 4; + s.info.cap_insn_split = 4; #ifdef TARGET_WORDS_BIGENDIAN s.info.endian = BFD_ENDIAN_BIG; @@ -204,32 +451,10 @@ void target_disas(FILE *out, CPUState *cpu, target_ulong code, cc->disas_set_info(cpu, &s.info); } -#if defined(TARGET_I386) - if (flags == 2) { - s.info.mach = bfd_mach_x86_64; - } else if (flags == 1) { - s.info.mach = bfd_mach_i386_i8086; - } else { - s.info.mach = bfd_mach_i386_i386; - } - s.info.print_insn = print_insn_i386; -#elif defined(TARGET_PPC) - if ((flags >> 16) & 1) { - s.info.endian = BFD_ENDIAN_LITTLE; - } - if (flags & 0xFFFF) { - /* If we have a precise definition of the instruction set, use it. */ - s.info.mach = flags & 0xFFFF; - } else { -#ifdef TARGET_PPC64 - s.info.mach = bfd_mach_ppc64; -#else - s.info.mach = bfd_mach_ppc; -#endif + if (s.info.cap_arch >= 0 && cap_disas_target(&s.info, code, size)) { + return; } - s.info.disassembler_options = (char *)"any"; - s.info.print_insn = print_insn_ppc; -#endif + if (s.info.print_insn == NULL) { s.info.print_insn = print_insn_od_target; } @@ -237,18 +462,6 @@ void target_disas(FILE *out, CPUState *cpu, target_ulong code, for (pc = code; size > 0; pc += count, size -= count) { fprintf(out, "0x" TARGET_FMT_lx ": ", pc); count = s.info.print_insn(pc, &s.info); -#if 0 - { - int i; - uint8_t b; - fprintf(out, " {"); - for(i = 0; i < count; i++) { - target_read_memory(pc + i, &b, 1, &s.info); - fprintf(out, " %02x", b); - } - fprintf(out, " }"); - } -#endif fprintf(out, "\n"); if (count < 0) break; @@ -276,6 +489,10 @@ void disas(FILE *out, void *code, unsigned long size) s.info.buffer = code; s.info.buffer_vma = (uintptr_t)code; s.info.buffer_length = size; + s.info.cap_arch = -1; + s.info.cap_mode = 0; + s.info.cap_insn_unit = 4; + s.info.cap_insn_split = 4; #ifdef HOST_WORDS_BIGENDIAN s.info.endian = BFD_ENDIAN_BIG; @@ -287,14 +504,35 @@ void disas(FILE *out, void *code, unsigned long size) #elif defined(__i386__) s.info.mach = bfd_mach_i386_i386; print_insn = print_insn_i386; + s.info.cap_arch = CS_ARCH_X86; + s.info.cap_mode = CS_MODE_32; + s.info.cap_insn_unit = 1; + s.info.cap_insn_split = 8; #elif defined(__x86_64__) s.info.mach = bfd_mach_x86_64; print_insn = print_insn_i386; + s.info.cap_arch = CS_ARCH_X86; + s.info.cap_mode = CS_MODE_64; + s.info.cap_insn_unit = 1; + s.info.cap_insn_split = 8; #elif defined(_ARCH_PPC) s.info.disassembler_options = (char *)"any"; print_insn = print_insn_ppc; + s.info.cap_arch = CS_ARCH_PPC; +# ifdef _ARCH_PPC64 + s.info.cap_mode = CS_MODE_64; +# endif +#elif defined(__riscv) && defined(CONFIG_RISCV_DIS) +#if defined(_ILP32) || (__riscv_xlen == 32) + print_insn = print_insn_riscv32; +#elif defined(_LP64) + print_insn = print_insn_riscv64; +#else +#error unsupported RISC-V ABI +#endif #elif defined(__aarch64__) && defined(CONFIG_ARM_A64_DIS) print_insn = print_insn_arm_a64; + s.info.cap_arch = CS_ARCH_ARM64; #elif defined(__alpha__) print_insn = print_insn_alpha; #elif defined(__sparc__) @@ -302,6 +540,8 @@ void disas(FILE *out, void *code, unsigned long size) s.info.mach = bfd_mach_sparc_v9b; #elif defined(__arm__) print_insn = print_insn_arm; + s.info.cap_arch = CS_ARCH_ARM; + /* TCG only generates code for arm mode. */ #elif defined(__MIPSEB__) print_insn = print_insn_big_mips; #elif defined(__MIPSEL__) @@ -313,6 +553,11 @@ void disas(FILE *out, void *code, unsigned long size) #elif defined(__hppa__) print_insn = print_insn_hppa; #endif + + if (s.info.cap_arch >= 0 && cap_disas_host(&s.info, code, size)) { + return; + } + if (print_insn == NULL) { print_insn = print_insn_od_host; } @@ -345,26 +590,20 @@ const char *lookup_symbol(target_ulong orig_addr) #include "monitor/monitor.h" -static int monitor_disas_is_physical; - static int -monitor_read_memory (bfd_vma memaddr, bfd_byte *myaddr, int length, +physical_read_memory(bfd_vma memaddr, bfd_byte *myaddr, int length, struct disassemble_info *info) { CPUDebug *s = container_of(info, CPUDebug, info); - if (monitor_disas_is_physical) { - cpu_physical_memory_read(memaddr, myaddr, length); - } else { - cpu_memory_rw_debug(s->cpu, memaddr, myaddr, length, 0); - } + address_space_read(s->cpu->as, memaddr, MEMTXATTRS_UNSPECIFIED, + myaddr, length); return 0; } -/* Disassembler for the monitor. - See target_disas for a description of flags. */ +/* Disassembler for the monitor. */ void monitor_disas(Monitor *mon, CPUState *cpu, - target_ulong pc, int nb_insn, int is_physical, int flags) + target_ulong pc, int nb_insn, int is_physical) { CPUClass *cc = CPU_GET_CLASS(cpu); int count, i; @@ -373,11 +612,14 @@ void monitor_disas(Monitor *mon, CPUState *cpu, INIT_DISASSEMBLE_INFO(s.info, (FILE *)mon, monitor_fprintf); s.cpu = cpu; - monitor_disas_is_physical = is_physical; - s.info.read_memory_func = monitor_read_memory; + s.info.read_memory_func + = (is_physical ? physical_read_memory : target_read_memory); s.info.print_address_func = generic_print_address; - s.info.buffer_vma = pc; + s.info.cap_arch = -1; + s.info.cap_mode = 0; + s.info.cap_insn_unit = 4; + s.info.cap_insn_split = 4; #ifdef TARGET_WORDS_BIGENDIAN s.info.endian = BFD_ENDIAN_BIG; @@ -389,31 +631,10 @@ void monitor_disas(Monitor *mon, CPUState *cpu, cc->disas_set_info(cpu, &s.info); } -#if defined(TARGET_I386) - if (flags == 2) { - s.info.mach = bfd_mach_x86_64; - } else if (flags == 1) { - s.info.mach = bfd_mach_i386_i8086; - } else { - s.info.mach = bfd_mach_i386_i386; - } - s.info.print_insn = print_insn_i386; -#elif defined(TARGET_PPC) - if (flags & 0xFFFF) { - /* If we have a precise definition of the instruction set, use it. */ - s.info.mach = flags & 0xFFFF; - } else { -#ifdef TARGET_PPC64 - s.info.mach = bfd_mach_ppc64; -#else - s.info.mach = bfd_mach_ppc; -#endif - } - if ((flags >> 16) & 1) { - s.info.endian = BFD_ENDIAN_LITTLE; + if (s.info.cap_arch >= 0 && cap_disas_monitor(&s.info, pc, nb_insn)) { + return; } - s.info.print_insn = print_insn_ppc; -#endif + if (!s.info.print_insn) { monitor_printf(mon, "0x" TARGET_FMT_lx ": Asm output not supported on this arch\n", pc);