From 309870e414548e3cd6634819490239a2efb85a0e Mon Sep 17 00:00:00 2001 From: zwelch Date: Wed, 15 Jul 2009 23:39:37 +0000 Subject: [PATCH] David Brownell : Initial support for disassembling Thumb2 code. This works only for Cortex-M3 cores so far. Eventually other cores will also need Thumb2 support ... but they don't yet support any kind of disassembly. - Update the 16-bit Thumb decoder: * Understand CPS, REV*, SETEND, {U,S}XT{B,H} opcodes added by ARMv6. (It already seems to treat CPY as MOV.) * Understand CB, CBNZ, WFI, IT, and other opcodes added by in Thumb2. - A new Thumb2 instruction decode routine is provided. * This has a different signature: pass the target, not the instruction, so it can fetch a second halfword when needed. The instruction size is likewise returned to the caller. * 32-bit instructions are recognized but not yet decoded. - Start using the current "UAL" syntax in some cases. "SWI" is renamed as "SVC"; "LDMIA" as "LDM"; "STMIA" as "STM". - Define a new "cortex_m3 disassemble addr count" command to give access to this disassembly. Sanity checked against "objdump -d" output; a bunch of the new instructions checked out fine. git-svn-id: svn://svn.berlios.de/openocd/trunk@2530 b42882b7-edfa-0310-969c-e2dbd0fdcd60 --- doc/openocd.texi | 5 + src/target/arm_disassembler.c | 254 ++++++++++++++++++++++++++++++++-- src/target/arm_disassembler.h | 5 + src/target/cortex_m3.c | 53 ++++++- 4 files changed, 300 insertions(+), 17 deletions(-) diff --git a/doc/openocd.texi b/doc/openocd.texi index f97037310..18077ccd6 100644 --- a/doc/openocd.texi +++ b/doc/openocd.texi @@ -5003,6 +5003,11 @@ If @var{value} is defined, first assigns that. @subsection Cortex-M3 specific commands @cindex Cortex-M3 +@deffn Command {cortex_m3 disassemble} address count +@cindex disassemble +Disassembles @var{count} Thumb2 instructions starting at @var{address}. +@end deffn + @deffn Command {cortex_m3 maskisr} (@option{on}|@option{off}) Control masking (disabling) interrupts during target step/resume. @end deffn diff --git a/src/target/arm_disassembler.c b/src/target/arm_disassembler.c index 94479261d..bd14a6e9c 100644 --- a/src/target/arm_disassembler.c +++ b/src/target/arm_disassembler.c @@ -21,6 +21,7 @@ #include "config.h" #endif +#include "target.h" #include "arm_disassembler.h" #include "log.h" @@ -63,7 +64,9 @@ int evaluate_swi(uint32_t opcode, uint32_t address, arm_instruction_t *instructi { instruction->type = ARM_SWI; - snprintf(instruction->text, 128, "0x%8.8" PRIx32 "\t0x%8.8" PRIx32 "\tSWI 0x%6.6" PRIx32 "", address, opcode, (opcode & 0xffffff)); + snprintf(instruction->text, 128, + "0x%8.8" PRIx32 "\t0x%8.8" PRIx32 "\tSVC %#6.6" PRIx32, + address, opcode, (opcode & 0xffffff)); return ERROR_OK; } @@ -614,7 +617,8 @@ int evaluate_ldm_stm(uint32_t opcode, uint32_t address, arm_instruction_t *instr if (U) { instruction->info.load_store_multiple.addressing_mode = 0; - addressing_mode = "IA"; + /* "IA" is the default in UAL syntax */ + addressing_mode = ""; } else { @@ -1180,6 +1184,7 @@ int arm_evaluate_opcode(uint32_t opcode, uint32_t address, arm_instruction_t *in /* clear fields, to avoid confusion */ memset(instruction, 0, sizeof(arm_instruction_t)); instruction->opcode = opcode; + instruction->instruction_size = 4; /* catch opcodes with condition field [31:28] = b1111 */ if ((opcode & 0xf0000000) == 0xf0000000) @@ -1356,7 +1361,11 @@ int evaluate_b_bl_blx_thumb(uint16_t opcode, uint32_t address, arm_instruction_t mnemonic = "BL"; break; } - /* TODO: deals correctly with dual opcodes BL/BLX ... */ + + /* TODO: deal correctly with dual opcode (prefixed) BL/BLX; + * these are effectively 32-bit instructions even in Thumb1. + * Might be simplest to always use the Thumb2 decoder. + */ snprintf(instruction->text, 128, "0x%8.8" PRIx32 "\t0x%4.4x\t%s 0x%8.8" PRIx32 , address, opcode,mnemonic, target_address); @@ -1887,12 +1896,12 @@ int evaluate_load_store_multiple_thumb(uint16_t opcode, uint32_t address, arm_in if (L) { instruction->type = ARM_LDM; - mnemonic = "LDMIA"; + mnemonic = "LDM"; } else { instruction->type = ARM_STM; - mnemonic = "STMIA"; + mnemonic = "STM"; } snprintf(ptr_name,7,"r%i!, ",Rn); } @@ -1945,7 +1954,9 @@ int evaluate_cond_branch_thumb(uint16_t opcode, uint32_t address, arm_instructio if (cond == 0xf) { instruction->type = ARM_SWI; - snprintf(instruction->text, 128, "0x%8.8" PRIx32 "\t0x%4.4x\tSWI 0x%02" PRIx32 , address, opcode, offset); + snprintf(instruction->text, 128, + "0x%8.8" PRIx32 "\t0x%4.4x\tSVC 0x%02" PRIx32, + address, opcode, offset); return ERROR_OK; } else if (cond == 0xe) @@ -1971,11 +1982,148 @@ int evaluate_cond_branch_thumb(uint16_t opcode, uint32_t address, arm_instructio return ERROR_OK; } +static int evaluate_cb_thumb(uint16_t opcode, uint32_t address, + arm_instruction_t *instruction) +{ + unsigned offset; + + /* added in Thumb2 */ + offset = (opcode >> 3) & 0x1f; + offset |= (opcode & 0x0200) >> 4; + + snprintf(instruction->text, 128, + "0x%8.8" PRIx32 "\t0x%4.4x\tCB%sZ r%d, %#8.8" PRIx32, + address, opcode, + (opcode & 0x0800) ? "N" : "", + opcode & 0x7, address + 4 + (offset << 1)); + + return ERROR_OK; +} + +static int evaluate_extend_thumb(uint16_t opcode, uint32_t address, + arm_instruction_t *instruction) +{ + /* added in ARMv6 */ + snprintf(instruction->text, 128, + "0x%8.8" PRIx32 "\t0x%4.4x\t%cXT%c r%d, r%d", + address, opcode, + (opcode & 0x0080) ? 'U' : 'S', + (opcode & 0x0040) ? 'B' : 'H', + opcode & 0x7, (opcode >> 3) & 0x7); + + return ERROR_OK; +} + +static int evaluate_cps_thumb(uint16_t opcode, uint32_t address, + arm_instruction_t *instruction) +{ + /* added in ARMv6 */ + if ((opcode & 0x0ff0) == 0x0650) + snprintf(instruction->text, 128, + "0x%8.8" PRIx32 "\t0x%4.4x\tSETEND %s", + address, opcode, + (opcode & 0x80) ? "BE" : "LE"); + else /* ASSUME (opcode & 0x0fe0) == 0x0660 */ + snprintf(instruction->text, 128, + "0x%8.8" PRIx32 "\t0x%4.4x\tCPSI%c %s%s%s", + address, opcode, + (opcode & 0x0010) ? 'D' : 'E', + (opcode & 0x0004) ? "A" : "", + (opcode & 0x0002) ? "I" : "", + (opcode & 0x0001) ? "F" : ""); + + return ERROR_OK; +} + +static int evaluate_byterev_thumb(uint16_t opcode, uint32_t address, + arm_instruction_t *instruction) +{ + char *suffix; + + /* added in ARMv6 */ + switch (opcode & 0x00c0) { + case 0: + suffix = ""; + break; + case 1: + suffix = "16"; + break; + default: + suffix = "SH"; + break; + } + snprintf(instruction->text, 128, + "0x%8.8" PRIx32 "\t0x%4.4x\tREV%s r%d, r%d", + address, opcode, suffix, + opcode & 0x7, (opcode >> 3) & 0x7); + + return ERROR_OK; +} + +static int evaluate_hint_thumb(uint16_t opcode, uint32_t address, + arm_instruction_t *instruction) +{ + char *hint; + + switch ((opcode >> 4) & 0x0f) { + case 0: + hint = "NOP"; + break; + case 1: + hint = "YIELD"; + break; + case 2: + hint = "WFE"; + break; + case 3: + hint = "WFI"; + break; + case 4: + hint = "SEV"; + break; + default: + hint = "HINT (UNRECOGNIZED)"; + break; + } + + snprintf(instruction->text, 128, + "0x%8.8" PRIx32 "\t0x%4.4x\t%s", + address, opcode, hint); + + return ERROR_OK; +} + +static int evaluate_ifthen_thumb(uint16_t opcode, uint32_t address, + arm_instruction_t *instruction) +{ + unsigned cond = (opcode >> 4) & 0x0f; + char *x = "", *y = "", *z = ""; + + if (opcode & 0x01) + z = (opcode & 0x02) ? "T" : "E"; + if (opcode & 0x03) + y = (opcode & 0x04) ? "T" : "E"; + if (opcode & 0x07) + x = (opcode & 0x08) ? "T" : "E"; + + snprintf(instruction->text, 128, + "0x%8.8" PRIx32 "\t0x%4.4x\tIT%s%s%s %s", + address, opcode, + x, y, z, arm_condition_strings[cond]); + + /* NOTE: strictly speaking, the next 1-4 instructions should + * now be displayed with the relevant conditional suffix... + */ + + return ERROR_OK; +} + int thumb_evaluate_opcode(uint16_t opcode, uint32_t address, arm_instruction_t *instruction) { /* clear fields, to avoid confusion */ memset(instruction, 0, sizeof(arm_instruction_t)); instruction->opcode = opcode; + instruction->instruction_size = 2; if ((opcode & 0xe000) == 0x0000) { @@ -2033,18 +2181,44 @@ int thumb_evaluate_opcode(uint16_t opcode, uint32_t address, arm_instruction_t * /* Misc */ if ((opcode & 0xf000) == 0xb000) { - if ((opcode & 0x0f00) == 0x0000) + switch ((opcode >> 8) & 0x0f) { + case 0x0: return evaluate_adjust_stack_thumb(opcode, address, instruction); - else if ((opcode & 0x0f00) == 0x0e00) + case 0x1: + case 0x3: + case 0x9: + case 0xb: + return evaluate_cb_thumb(opcode, address, instruction); + case 0x2: + return evaluate_extend_thumb(opcode, address, instruction); + case 0x4: + case 0x5: + case 0xc: + case 0xd: + return evaluate_load_store_multiple_thumb(opcode, address, + instruction); + case 0x6: + return evaluate_cps_thumb(opcode, address, instruction); + case 0xa: + if ((opcode & 0x00c0) == 0x0080) + break; + return evaluate_byterev_thumb(opcode, address, instruction); + case 0xe: return evaluate_breakpoint_thumb(opcode, address, instruction); - else if ((opcode & 0x0600) == 0x0400) /* push pop */ - return evaluate_load_store_multiple_thumb(opcode, address, instruction); - else - { - instruction->type = ARM_UNDEFINED_INSTRUCTION; - snprintf(instruction->text, 128, "0x%8.8" PRIx32 "\t0x%4.4x\tUNDEFINED INSTRUCTION", address, opcode); - return ERROR_OK; + case 0xf: + if (opcode & 0x000f) + return evaluate_ifthen_thumb(opcode, address, + instruction); + else + return evaluate_hint_thumb(opcode, address, + instruction); } + + instruction->type = ARM_UNDEFINED_INSTRUCTION; + snprintf(instruction->text, 128, + "0x%8.8" PRIx32 "\t0x%4.4x\tUNDEFINED INSTRUCTION", + address, opcode); + return ERROR_OK; } /* Load/Store multiple */ @@ -2078,6 +2252,56 @@ int thumb_evaluate_opcode(uint16_t opcode, uint32_t address, arm_instruction_t * return -1; } +/* + * REVISIT for Thumb2 instructions, instruction->type and friends aren't + * always set. That means eventual arm_simulate_step() support for Thumb2 + * will need work in this area. + */ +int thumb2_opcode(target_t *target, uint32_t address, arm_instruction_t *instruction) +{ + int retval; + uint16_t op; + uint32_t opcode; + + /* clear low bit ... it's set on function pointers */ + address &= ~1; + + /* clear fields, to avoid confusion */ + memset(instruction, 0, sizeof(arm_instruction_t)); + + /* read first halfword, see if this is the only one */ + retval = target_read_u16(target, address, &op); + if (retval != ERROR_OK) + return retval; + + switch (op & 0xf800) { + case 0xf800: + case 0xf000: + case 0xe800: + /* 32-bit instructions */ + instruction->instruction_size = 4; + opcode = op << 16; + retval = target_read_u16(target, address + 2, &op); + if (retval != ERROR_OK) + return retval; + opcode |= op; + instruction->opcode = opcode; + break; + default: + /* 16-bit: Thumb1 + IT + CBZ/CBNZ + ... */ + return thumb_evaluate_opcode(op, address, instruction); + } + + /* FIXME decode the 32-bit instructions */ + + LOG_DEBUG("Can't decode 32-bit Thumb2 yet (opcode=%08x)", opcode); + + snprintf(instruction->text, 128, + "0x%8.8" PRIx32 "\t0x%8.8x\t... 32-bit Thumb2 ...", + address, opcode); + return ERROR_OK; +} + int arm_access_size(arm_instruction_t *instruction) { if ((instruction->type == ARM_LDRB) diff --git a/src/target/arm_disassembler.h b/src/target/arm_disassembler.h index a8b9aba11..b841d6cd0 100644 --- a/src/target/arm_disassembler.h +++ b/src/target/arm_disassembler.h @@ -185,6 +185,9 @@ typedef struct arm_instruction_s char text[128]; uint32_t opcode; + /* return value ... Thumb-2 sizes vary */ + unsigned instruction_size; + union { arm_b_bl_bx_blx_instr_t b_bl_bx_blx; arm_data_proc_instr_t data_proc; @@ -196,6 +199,8 @@ typedef struct arm_instruction_s extern int arm_evaluate_opcode(uint32_t opcode, uint32_t address, arm_instruction_t *instruction); extern int thumb_evaluate_opcode(uint16_t opcode, uint32_t address, arm_instruction_t *instruction); +extern int thumb2_opcode(target_t *target, uint32_t address, + arm_instruction_t *instruction); extern int arm_access_size(arm_instruction_t *instruction); #define COND(opcode) (arm_condition_strings[(opcode & 0xf0000000) >> 28]) diff --git a/src/target/cortex_m3.c b/src/target/cortex_m3.c index 53ecd0a90..2e19cd678 100644 --- a/src/target/cortex_m3.c +++ b/src/target/cortex_m3.c @@ -34,6 +34,7 @@ #include "cortex_m3.h" #include "target_request.h" #include "target_type.h" +#include "arm_disassembler.h" /* cli handling */ @@ -1646,6 +1647,47 @@ int cortex_m3_target_create(struct target_s *target, Jim_Interp *interp) return ERROR_OK; } +/* + * REVISIT Thumb2 disassembly should work for all ARMv7 cores, as well + * as at least ARM-1156T2. The interesting thing about Cortex-M is + * that *only* Thumb2 disassembly matters. There are also some small + * additions to Thumb2 that are specific to ARMv7-M. + */ +static int +handle_cortex_m3_disassemble_command(struct command_context_s *cmd_ctx, + char *cmd, char **args, int argc) +{ + int retval = ERROR_OK; + target_t *target = get_current_target(cmd_ctx); + uint32_t address; + unsigned long count; + arm_instruction_t cur_instruction; + + if (argc != 2) { + command_print(cmd_ctx, + "usage: cortex_m3 disassemble
"); + return ERROR_OK; + } + + errno = 0; + address = strtoul(args[0], NULL, 0); + if (errno) + return ERROR_FAIL; + count = strtoul(args[1], NULL, 0); + if (errno) + return ERROR_FAIL; + + while (count--) { + retval = thumb2_opcode(target, address, &cur_instruction); + if (retval != ERROR_OK) + return retval; + command_print(cmd_ctx, "%s", cur_instruction.text); + address += cur_instruction.instruction_size; + } + + return ERROR_OK; +} + int cortex_m3_register_commands(struct command_context_s *cmd_ctx) { int retval; @@ -1653,8 +1695,15 @@ int cortex_m3_register_commands(struct command_context_s *cmd_ctx) retval = armv7m_register_commands(cmd_ctx); - cortex_m3_cmd = register_command(cmd_ctx, NULL, "cortex_m3", NULL, COMMAND_ANY, "cortex_m3 specific commands"); - register_command(cmd_ctx, cortex_m3_cmd, "maskisr", handle_cortex_m3_mask_interrupts_command, COMMAND_EXEC, "mask cortex_m3 interrupts ['on'|'off']"); + cortex_m3_cmd = register_command(cmd_ctx, NULL, "cortex_m3", + NULL, COMMAND_ANY, "cortex_m3 specific commands"); + + register_command(cmd_ctx, cortex_m3_cmd, "disassemble", + handle_cortex_m3_disassemble_command, COMMAND_EXEC, + "disassemble Thumb2 instructions
"); + register_command(cmd_ctx, cortex_m3_cmd, "maskisr", + handle_cortex_m3_mask_interrupts_command, COMMAND_EXEC, + "mask cortex_m3 interrupts ['on'|'off']"); return retval; }