/* * Interactive disassembler (IDA). * Copyright (c) 1990-99 by Ilfak Guilfanov. * ALL RIGHTS RESERVED. * E-mail: ig@datarescue.com * * Atmel AVR - 8-bit RISC processor * */ #include "avr.hpp" #include #include #include #include #include "notify_codes.hpp" int data_id; //-------------------------------------------------------------------------- static const char *const register_names[] = { "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23", "r24", "r25", "XL", "XH", "YL", "YH", "ZL", "ZH", "cs","ds", // virtual registers for code and data segments }; //----------------------------------------------------------------------- // AVR assembler //----------------------------------------------------------------------- static const char *const avr_header[] = { ".equ XL, 26", ".equ XH, 27", ".equ YL, 28", ".equ YH, 29", ".equ ZL, 30", ".equ ZH, 31", NULL }; static const asm_t avrasm = { AS_COLON|AS_N2CHR|ASH_HEXF3|ASD_DECF0|ASB_BINF3|ASO_OCTF0|AS_ONEDUP, 0, "AVR Assembler", 0, avr_header, // header lines ".org", // org ".exit", // end ";", // comment string '"', // string delimiter '\'', // char delimiter "\"'", // special symbols in char and string constants ".db", // ascii string directive ".db", // byte directive ".dw", // word directive ".dd", // double words NULL, // no qwords NULL, // oword (16 bytes) NULL, // float (4 bytes) NULL, // double (8 bytes) NULL, // tbyte (10/12 bytes) NULL, // packed decimal real NULL, // arrays (#h,#d,#v,#s(...) ".byte %s", // uninited arrays ".equ", // equ NULL, // 'seg' prefix (example: push seg seg001) NULL, // current IP (instruction pointer) NULL, // func_header NULL, // func_footer NULL, // "public" name keyword NULL, // "weak" name keyword NULL, // "extrn" name keyword NULL, // "comm" (communal variable) NULL, // get_type_name NULL, // "align" keyword '(', ')', // lbrace, rbrace NULL, // mod "&", // and "|", // or "^", // xor "~", // not "<<", // shl ">>", // shr NULL, // sizeof }; static const asm_t *const asms[] = { &avrasm, NULL }; //-------------------------------------------------------------------------- static const char cfgname[] = "avr.cfg"; //-------------------------------------------------------------------------- bool avr_iohandler_t::entry_processing(ea_t &ea1, const char * /*word*/, const char * /*cmt*/) { pm.helper.altset_ea(ea1, 1); create_insn(ea1); ea_t ea = get_first_fcref_from(ea1); if ( ea != BADADDR ) ea1 = ea; return false; // continue processing } //-------------------------------------------------------------------------- bool avr_iohandler_t::check_ioresp() const { return inf_like_binary() || pm.imageFile; } //-------------------------------------------------------------------------- bool avr_t::is_possible_subarch(int addr) const { // old version of gcc-arm don't use 31/51/etc subarches - only 3/5/... :( // maybe make option? return subarch == 0 || subarch == addr || (addr/10 == subarch); } //-------------------------------------------------------------------------- const char *avr_iohandler_t::iocallback(const ioports_t &_ports, const char *line) { int addr; char word[MAXSTR]; word[MAXSTR-1] = '\0'; CASSERT(MAXSTR == 1024); if ( qsscanf(line, "%1023[^=] = %d", word, &addr) == 2 ) { if ( streq(word, "RAM") ) { pm.ramsize = addr; return NULL; } if ( streq(word, "ROM") ) { pm.romsize = addr >> 1; return NULL; } if ( streq(word, "EEPROM") ) { pm.eepromsize = addr; return NULL; } if ( streq(word, "SUBARCH") ) { // set pm.subarch based on SUBARCH in the config file // it is needed to do XMEGA specific things for non-elf files pm.subarch = addr; return pm.is_possible_subarch(addr) ? NULL : IOPORT_SKIP_DEVICE; } } return standard_callback(_ports, line); } //-------------------------------------------------------------------------- struct avr_ioport_parser_t : public choose_ioport_parser_t { avr_t ± avr_ioport_parser_t(avr_t &_pm) : pm(_pm) {} virtual bool parse(qstring *, const char *line) override { int addr; char word[MAXSTR]; word[MAXSTR-1] = '\0'; CASSERT(MAXSTR == 1024); bool skip = qsscanf(line, "%1023[^=] = %d", word, &addr) == 2 && strcmp(word, "SUBARCH") == 0 && !pm.is_possible_subarch(addr); return !skip; } }; //-------------------------------------------------------------------------- const ioport_t *avr_t::find_port(ea_t address) { return find_ioport(ioh.ports, address); } //-------------------------------------------------------------------------- const char *avr_t::find_bit(ea_t address, size_t bit) { const ioport_bit_t *b = find_ioport_bit(ioh.ports, address, bit); return b ? b->name.c_str() : NULL; } //-------------------------------------------------------------------------- void avr_t::setup_avr_device(int resp_info) { if ( !choose_ioport_device(&ioh.device, cfgname) ) return; ioh.set_device_name(ioh.device.c_str(), resp_info); if ( get_first_seg() == NULL ) // set processor options before load file return; plan_range(0, BADADDR); // reanalyze program // resize the ROM segment { segment_t *s = getseg(node2ea(helper.altval(-1))); if ( s == NULL ) s = get_first_seg(); // for the old databases if ( s != NULL ) //-V547 's != 0' is always true { if ( s->size() > romsize ) warning("The input file is bigger than the ROM size of the current device"); set_segm_end(s->start_ea, s->start_ea+romsize, SEGMOD_KILL); } } // resize the RAM segment { segment_t *s = get_segm_by_name("RAM"); if ( s == NULL && ramsize != 0 ) { ea_t start = (inf_get_max_ea() + 0xFFFFF) & ~0xFFFFF; add_segm(start>>4, start, start+ramsize, "RAM", "DATA"); s = getseg(start); } ram = BADADDR; if ( s != NULL ) { int i; // offset added to I/O port address to get RAM address int ram_offset = 0; ram = s->start_ea; set_segm_end(ram, ram+ramsize, SEGMOD_KILL); if ( subarch < E_AVR_MACH_TINY ) { // legacy devices start with 32 GPRs // 0x20 needs to be added to the port address ram_offset = 0x20; // set register names for aliases in data memory for ( i=0; i < 32; i++ ) if ( !has_any_name(get_flags(ram+i)) ) set_name(ram+i, register_names[i], SN_NODUMMY); } // set I/O port names for ( i=0; i < ioh.ports.size(); i++ ) { const ioport_t &p = ioh.ports[i]; set_name(ram+p.address+ram_offset, p.name.c_str(), SN_NODUMMY); set_cmt(ram+p.address+ram_offset, p.cmt.c_str(), true); } } } } //-------------------------------------------------------------------------- const char *avr_t::set_idp_options( const char *keyword, int value_type, const void *value, bool idb_loaded) { if ( keyword == NULL ) { setup_avr_device(IORESP_INT); return IDPOPT_OK; } else if ( strcmp(keyword, "AVR_MCPU") == 0 ) { if ( value_type != IDPOPT_STR ) return IDPOPT_BADTYPE; ioh.device = (const char *) value; if ( idb_loaded ) ioh.set_device_name(ioh.device.c_str(), IORESP_NONE); return IDPOPT_OK; } return IDPOPT_BADKEY; } //-------------------------------------------------------------------------- bool avr_t::set_param_by_arch(void) { int max_rom, max_ram, max_eeprom; // preset MAXIMUM's of memory size's by mcpu subtype switch ( subarch ) { default: subarch = 0; return false; // LOGICAL ERROR? // at90s1200, attiny10, attiny11, attiny12, attiny15, attiny28 case E_AVR_MACH_AVR1: // ROM<=1k max_rom = 1024; max_ram = 32; max_eeprom = 64; break; // at90s2313, at90s2323, at90s2333, at90s2343, attiny22, attiny26, // at90s4414 /* XXX -> 8515 */, at90s4433, at90s4434 /* XXX -> 8535 */, // at90s8515, at90c8534, at90s8535 case E_AVR_MACH_AVR2: // ROM<=8k // attiny13, attiny13a, attiny2313, attiny24, attiny44, attiny84, // attiny25, attiny45, attiny85, attiny261, attiny461, attiny861, // attiny43u, attiny48, attiny88, at86rf401 // PASS THRU case E_AVR_MACH_AVR25: // ROM<=8k max_rom = 8*1024; max_ram = 512; max_eeprom = 512; break; // at43usb355, at76c711 case E_AVR_MACH_AVR3: // ROM>=8k<=64k max_rom = 64*1024; max_ram = 1024; max_eeprom = 0; break; // atmega103, at43usb320, case E_AVR_MACH_AVR31: // ROM>=65k&&<=128k, (RAM=65k, EEPROM=4k) max_rom = 128*1024; max_ram = 4*1024; max_eeprom = 4*1024; break; // attiny167, at90usb82, at90usb162 case E_AVR_MACH_AVR35: // ROM>=8k&&<=64k, max_rom = 64*1024; max_ram = 512; max_eeprom = 512; break; // atmega8, atmega48, atmega48p, atmega88, atmega88p, atmega8515, // atmega8535, atmega8hva, at90pwm1, at90pwm2, at90pwm2b, at90pwm3, // at90pwm3b case E_AVR_MACH_AVR4: // ROM<=8k max_rom = 8*1024; max_ram = 1024; max_eeprom = 512; break; // atmega16, atmega161, atmega162, atmega163, atmega164p, atmega165, // atmega165p, atmega168, atmega168p, atmega169, atmega169p, atmega32, // atmega323, atmega324p, atmega325, atmega325p, atmega3250, atmega3250p, // atmega328p, atmega329, atmega329p, atmega3290, atmega3290p, atmega406, // atmega64, atmega640, atmega644, atmega644p, atmega645, atmega649, // atmega6450, atmega6490, atmega16hva, at90can32, at90can64, at90pwm216, // at90pwm316, atmega32c1, atmega32m1, atmega32u4, at90usb646, at90usb647, // at94k case E_AVR_MACH_AVR5: // ROM>=8k&&<=64k max_rom = 64*1024; max_ram = 4*1024; max_eeprom = 2*1024; break; // atmega128, atmega1280, atmega1281, atmega1284p, // at90can128, at90usb1286, at90usb1287 case E_AVR_MACH_AVR51: // ROM=128k max_rom = 128*1024; max_ram = 16*1024; max_eeprom = 4*1024; break; // atmega2560, atmega2561 case E_AVR_MACH_AVR6: // ROM=256k (3-byte pc -- is supported?) max_rom = 256*1024; max_ram = 8*1024; max_eeprom = 4*1024; break; case E_AVR_MACH_XMEGA1: // ROM < 8K, ram=? max_rom = 8*1024; max_ram = 1024; max_eeprom = 512; break; // ATxmega16A4, ATxmega16D4, ATxmega32D4 case E_AVR_MACH_XMEGA2: // 8K < FLASH <= 64K, RAM <= 64K max_rom = 64*1024; max_ram = 64*1024; max_eeprom = 1024; break; // ATxmega32A4 case E_AVR_MACH_XMEGA3: // 8K < FLASH <= 64K, RAM > 64K max_rom = 64*1024; max_ram = 128*1024; // ? max_eeprom = 1024; break; // ATxmega64A3, ATxmega64D3 case E_AVR_MACH_XMEGA4: // 64K < FLASH <= 128K, RAM <= 64K max_rom = 128*1024; max_ram = 64*1024; max_eeprom = 2048; break; // ATxmega64A1 case E_AVR_MACH_XMEGA5: // 64K < FLASH <= 128K, RAM > 64K max_rom = 128*1024; max_ram = 128*1024; max_eeprom = 2048; break; // ATxmega128A3, ATxmega128D3, ATxmega192A3, ATxmega192D3, // ATxmega256A3B, ATxmega256A3, ATxmega256D3 case E_AVR_MACH_XMEGA6: // 128K < FLASH <= 256K, RAM <= 64K max_rom = 256*1024; max_ram = 64*1024; max_eeprom = 4096; break; // ATxmega128A1 case E_AVR_MACH_XMEGA7: // 128K < FLASH <= 256K, RAM > 64K max_rom = 256*1024; max_ram = 128*1024; max_eeprom = 4096; break; } avr_ioport_parser_t parser(*this); if ( !choose_ioport_device2(&ioh.device, cfgname, &parser) ) { ioh.device.sprnt("avr%d", subarch); ioh.device[sizeof("avrX")-1] = '\0'; romsize = max_rom >> 1; ramsize = max_ram; eepromsize = max_eeprom; } else { ioh.set_device_name(ioh.device.c_str(), IORESP_INT); plan_range(0, BADADDR); // reanalyze program } return true; } //-------------------------------------------------------------------------- static inline ea_t get16bit(ea_t ea) { if ( segtype(ea) == SEG_CODE ) return get_wide_byte(ea); return get_word(ea); } //-------------------------------------------------------------------------- ssize_t idaapi idb_listener_t::on_event(ssize_t code, va_list va) { switch ( code ) { case idb_event::segm_added: { segment_t *s = va_arg(va, segment_t *); qstring sclass; if ( get_segm_class(&sclass, s) > 0 && sclass == "DATA" ) set_default_dataseg(s->sel); } break; case idb_event::segm_moved: // A segment is moved // Fix processor dependent address sensitive information { ea_t from = va_arg(va, ea_t); ea_t to = va_arg(va, ea_t); asize_t size = va_arg(va, asize_t); //bool changed_netmap = va_argi(va, bool); nodeidx_t ndx1 = ea2node(from); nodeidx_t ndx2 = ea2node(to); pm.helper.altshift(ndx1, ndx2, size); // move address information } break; } return 0; } //-------------------------------------------------------------------------- static bool idaapi avr16_apply( const fixup_handler_t *fh, ea_t item_ea, ea_t fixup_ea, int opnum, bool /*is_macro*/, const fixup_data_t &fd) { avr_t &pm = *GET_MODULE_DATA(avr_t); if ( !pm.nonBinary || fd.has_base() || fd.is_unused() || fd.displacement != 0 ) { msg("%a: Unexpected or incorrect CUSTOM_FIXUP\n", fixup_ea); return false; } if ( is_unknown(get_flags(item_ea)) ) create_16bit_data(item_ea, 2); refinfo_t ri; ri.flags = fh->reftype; ri.base = fd.get_base(); ri.target = ri.base + fd.off; ri.tdelta = fd.displacement; op_offset_ex(item_ea, opnum, &ri); return true; } //-------------------------------------------------------------------------- //lint -e{818} could be declared const static int idaapi avr16_gen_expr( qstring * /*buf*/, qstring * /*format*/, ea_t ea, int numop, const refinfo_t &ri, ea_t /*from*/, adiff_t *opval, ea_t * /*target*/, ea_t * /*fullvalue*/, int /*getn_flags*/) { avr_t &pm = *GET_MODULE_DATA(avr_t); if ( !pm.nonBinary || numop != 0 || ri.type() == (pm.ref_avr16_id | REFINFO_CUSTOM) || ri.tdelta != 0 || ri.target == BADADDR || *opval != get16bit(ea) ) { msg("%a: Unexpected or incorrect CUSTOM offset\n", ea); return 0; } return 3; // process as a regular fixup } //-------------------------------------------------------------------------- static const custom_refinfo_handler_t ref_avr16 = { sizeof(custom_refinfo_handler_t), "AVR16", "AVR 16-bit offset", 0, // properties (currently 0) avr16_gen_expr, // gen_expr NULL, // calc_reference_data NULL, // get_format }; //---------------------------------------------------------------------- // This old-style callback only returns the processor module object. static ssize_t idaapi notify(void *, int msgid, va_list) { if ( msgid == processor_t::ev_get_procmod ) return size_t(SET_MODULE_DATA(avr_t)); return 0; } //---------------------------------------------------------------------- void avr_t::load_from_idb() { ioh.restore_device(); segment_t *s = get_segm_by_name("RAM"); if ( s != NULL ) ram = s->start_ea; } //-------------------------------------------------------------------------- ssize_t idaapi avr_t::on_event(ssize_t msgid, va_list va) { switch ( msgid ) { case processor_t::ev_init: helper.create(PROCMOD_NODE_NAME); hook_event_listener(HT_IDB, &idb_listener, &LPH); cfh_avr16.apply = avr16_apply; cfh_avr16_id = register_custom_fixup(&cfh_avr16); ref_avr16_id = register_custom_refinfo(&ref_avr16); cfh_avr16.reftype = REFINFO_CUSTOM | ref_avr16_id; break; case processor_t::ev_term: cfh_avr16.reftype = REFINFO_CUSTOM; unregister_custom_refinfo(ref_avr16_id); unregister_custom_fixup(cfh_avr16_id); unhook_event_listener(HT_IDB, &idb_listener); ioh.ports.clear(); clr_module_data(data_id); break; case avr_module_t::ev_set_machine_type: // elf-loader 'set machine type' and file type subarch = va_arg(va, int); imageFile = va_argi(va, bool); nonBinary = true; break; case processor_t::ev_newfile: // new file loaded // remember the ROM segment { segment_t *s = get_first_seg(); if ( s != NULL ) { if ( subarch == 0 ) set_segm_name(s, "ROM"); helper.altset(-1, ea2node(s->start_ea)); } } if ( subarch != 0 && set_param_by_arch() ) break; setup_avr_device(/*IORESP_AREA|*/IORESP_INT); // allow the user to select the device if ( subarch != 0 ) break; // create additional segments { ea_t start = (inf_get_max_ea() + 0xFFFFF) & ~0xFFFFF; if ( eepromsize != 0 ) { char *file = ask_file(false, "*.bin", "Please enter the binary EEPROM image file"); if ( file != NULL ) { add_segm(start>>4, start, start+eepromsize, "EEPROM", "DATA"); linput_t *li = open_linput(file, false); if ( li != NULL ) { uint64 size = qlsize(li); if ( size > eepromsize ) size = eepromsize; file2base(li, 0, start, start+size, FILEREG_NOTPATCHABLE); close_linput(li); } } } } break; case processor_t::ev_ending_undo: case processor_t::ev_oldfile: // old file loaded load_from_idb(); break; case processor_t::ev_newprc: // new processor type break; case processor_t::ev_newasm: // new assembler type break; case processor_t::ev_out_label: // The kernel is going to generate an instruction // label line or a function header { outctx_t *ctx = va_arg(va, outctx_t *); if ( helper.altval_ea(ctx->insn_ea) ) // if entry point { char buf[MAX_NUMBUF]; btoa(buf, sizeof(buf), ctx->insn_ea); ctx->gen_printf(DEFAULT_INDENT, COLSTR("%s %s", SCOLOR_ASMDIR), ash.origin, buf); } } break; case processor_t::ev_out_header: { outctx_t *ctx = va_arg(va, outctx_t *); avr_header(*ctx); return 1; } case processor_t::ev_out_footer: { outctx_t *ctx = va_arg(va, outctx_t *); avr_footer(*ctx); return 1; } case processor_t::ev_out_segstart: { outctx_t *ctx = va_arg(va, outctx_t *); segment_t *seg = va_arg(va, segment_t *); avr_segstart(*ctx, seg); return 1; } case processor_t::ev_out_segend: { outctx_t *ctx = va_arg(va, outctx_t *); segment_t *seg = va_arg(va, segment_t *); avr_segend(*ctx, seg); return 1; } case processor_t::ev_ana_insn: { insn_t *out = va_arg(va, insn_t *); return ana(out); } case processor_t::ev_emu_insn: { const insn_t *insn = va_arg(va, const insn_t *); return emu(*insn) ? 1 : -1; } case processor_t::ev_out_insn: { outctx_t *ctx = va_arg(va, outctx_t *); out_insn(*ctx); return 1; } case processor_t::ev_out_operand: { outctx_t *ctx = va_arg(va, outctx_t *); const op_t *op = va_arg(va, const op_t *); return out_opnd(*ctx, *op) ? 1 : -1; } case processor_t::ev_set_idp_options: { const char *keyword = va_arg(va, const char *); int value_type = va_arg(va, int); const char *value = va_arg(va, const char *); const char **errmsg = va_arg(va, const char **); bool idb_loaded = va_argi(va, bool); const char *ret = set_idp_options(keyword, value_type, value, idb_loaded); if ( ret == IDPOPT_OK ) return 1; if ( errmsg != NULL ) *errmsg = ret; return -1; } default: break; } return 0; } //-------------------------------------------------------------------------- // 1001 0101 0xx0 1000 ret // 1001 0101 0xx1 1000 reti static const uchar retcode_1[] = { 0x08, 0x95 }; // ret static const uchar retcode_2[] = { 0x18, 0x95 }; // reti static const uchar retcode_3[] = { 0x28, 0x95 }; // ret static const uchar retcode_4[] = { 0x38, 0x95 }; // reti static const uchar retcode_5[] = { 0x48, 0x95 }; // ret static const uchar retcode_6[] = { 0x58, 0x95 }; // reti static const uchar retcode_7[] = { 0x68, 0x95 }; // ret static const uchar retcode_8[] = { 0x78, 0x95 }; // reti static const bytes_t retcodes[] = { { sizeof(retcode_1), retcode_1 }, { sizeof(retcode_2), retcode_2 }, { sizeof(retcode_3), retcode_3 }, { sizeof(retcode_4), retcode_4 }, { sizeof(retcode_5), retcode_5 }, { sizeof(retcode_6), retcode_6 }, { sizeof(retcode_7), retcode_7 }, { sizeof(retcode_8), retcode_8 }, { 0, NULL } }; //----------------------------------------------------------------------- #define FAMILY "Atmel AVR series:" static const char *const shnames[] = { "AVR", NULL }; static const char *const lnames[] = { FAMILY"Atmel AVR", NULL }; //----------------------------------------------------------------------- // Processor Definition //----------------------------------------------------------------------- processor_t LPH = { IDP_INTERFACE_VERSION, // version PLFM_AVR, // id // flag PRN_HEX | PR_RNAMESOK, // flag2 PR2_IDP_OPTS, // the module has processor-specific configuration options 16, // 16 bits in a byte for code segments 8, // 8 bits in a byte for other segments shnames, lnames, asms, notify, register_names, // Register names qnumber(register_names), // Number of registers rVcs, // first rVds, // last 0, // size of a segment register rVcs, rVds, NULL, // No known code start sequences retcodes, AVR_null, AVR_last, Instructions, // instruc 0, // int tbyte_size; -- doesn't exist { 0, }, // char real_width[4]; // number of symbols after decimal point // 2byte float (0-does not exist) // normal float // normal double // long double AVR_ret, // Icode of return instruction. It is ok to give any of possible return instructions };