/* * Interactive disassembler (IDA) * Copyright (c) 1990-98 by Ilfak Guilfanov. * E-mail: ig@datarescue.com * JVM module. * Copyright (c) 1995-2006 by Iouri Kharon. * E-mail: yjh@styx.cabel.net * * ALL RIGHTS RESERVED. * */ #include "java.hpp" #include #include #include "npooluti.hpp" #include "notify_codes.hpp" int data_id; //----------------------------------------------------------------------- #ifdef __debug__ NORETURN void _destroyed(const char *from) { error("Database is corrupted! [at: %s]", from); } //----------------------------------------------------------------------- NORETURN void _faterr(uchar mode, const char *from) { error("Internal error (%s) [at: %s]", mode ? "compatibility" : "idp", from); } #else //----------------------------------------------------------------------- NORETURN void _destroyed(void) { error("Database is corrupted!"); } //----------------------------------------------------------------------- NORETURN void _faterr(uchar mode) { error("Internal error (%s)", mode ? "compatibility" : "idp"); } #endif //----------------------------------------------------------------------- void java_t::sm_validate(const SegInfo *si) { ea_t segTopEA = si->start_ea + si->CodeSize; netnode temp(si->smNode); nodeidx_t nid = temp.supfirst(); if ( (ea_t)nid < si->start_ea ) goto destroyed; do { if ( (ea_t)nid >= segTopEA ) goto destroyed; if ( temp.supval(nid, NULL, 0) != sizeof(sm_info_t) ) goto destroyed; if ( !is_head(get_flags((ea_t)nid)) ) { remember_problem(PR_HEAD, (ea_t)nid); if ( !displayed_nl ) { displayed_nl = true; msg("\n"); } msg("StackMap refers to nonHead offset %X in Method#%u\n", (uint32)((ea_t)nid - si->start_ea), si->id.Number); } nid = temp.supnext(nid); } while ( nid != BADNODE ); return; destroyed: DESTROYED("sm_validate"); } //---------------------------------------------------------------------- // visble for upgrade ONLY void java_t::coagulate_unused_data(const SegInfo *ps) { uint size = 0; ea_t ea = ps->DataBase; ea_t top = ea + ps->DataSize; for ( ; ea < top; ea++ ) { if ( is_head(get_flags(ea)) && get_first_dref_to(ea) == BADADDR ) { ConstantNode.chardel(ea, UR_TAG); // unicode renaming support del_global_name(ea); del_items(ea, DELIT_SIMPLE); ++size; ea_t to; while ( (to=get_first_dref_from(ea)) != BADADDR ) del_dref(ea, to); } else if ( size ) { create_data(ea-size, align_flag(), size, BADNODE); size = 0; } } if ( size ) create_data(ea-size, align_flag(), size, BADNODE); } //-------------------------------------------------------------------------- //-------------------------------------------------------------------------- static int idaapi out_asm_file( FILE *fp, const qstring &line, bgcolor_t, bgcolor_t) { qstring qbuf; tag_remove(&qbuf, line); size_t len = qbuf.length(); size_t chk = len; if ( qbuf.last() == '\\' ) --len; if ( qfwrite(fp, qbuf.c_str(), len) != len ) return 0; if ( chk == len && qfputc('\n', fp) == EOF ) return 0; return 1; } //---------------------------------------------------------------------- static void idaapi func_header(outctx_t &ctx, func_t *) { ctx.ctxflags |= CTXF_LABEL_OK; } static void idaapi func_footer(outctx_t &, func_t *) {} static bool idaapi java_specseg(outctx_t &ctx, uchar) { java_data(ctx, false); return false; } //---------------------------------------------------------------------- // floating point conversion fpvalue_error_t idaapi j_realcvt(void *m, fpvalue_t *e, ushort swt) { return ieee_realcvt(m, e, swt | 0x80); } //---------------------------------------------------------------------- // Set IDP options. Either from the configuration file either allow the user // to specify them in a dialog box. const char *java_t::set_idp_options( const char *keyword, int value_type, const void * value, bool idb_loaded) { static const char form[] = "HELP\n" "JAVA specific options\n" "\n" " Multiline .debug\n" "\n" " If this option is on, IDA forces new .debug directive at every\n" " LR ('\\n') in the input string\n" "\n" " Hide StackMap(s)\n" "\n" " If this option is on, IDA hides .stack verification declarations\n" "\n" " Auto strings\n" "\n" " If this option is on, IDA makes 'prompt-string' after every CR in\n" " the quoted-string operand\n" "\n" " Save to jasmin\n" "\n" " If this option is on, IDA creates asm-file in the jasmin-\n" " compatibe form: concatenates 'prompted' string, reserved names\n" " will be enclosed in quotes.\n" " Also when this option is on IDA changes unicode-to-oem encoding to\n" " unicode-to-ansi encoding because jasmin expects ansi encoding.\n" "\n" " Enable encoding\n" "\n" " If this option is on, IDA converts unicode characters which\n" " can't be represented in current locale to ascii characters.\n" "\n" " Nopath .attribute\n" " If this option is on, IDA prints filename in '.attribute'\n" " directives without the path part.\n" "\n" "\n" " Bad index as string\n" " If this option is on, IDA will show invalid name/type references\n" " as a quoted string.\n" "ENDHELP\n" "JAVA specific options\n" "\n" " <~M~ultilne .debug :C>\n" " <~H~ide StackMap(s) :C>\n" " <~A~uto strings :C>\n" " <~S~ave to jasmin :C>\n" " <~E~nable encoding :C>\n" " <~N~opath .attribute :C>>\n" "\n" " <~B~ad index as string :C>>\n" "\n" "\n"; if ( !keyword ) { ushort tmp = (idpflags >> 16) & IDM__REQMASK; ushort flags = idpflags; if ( ask_form(form, &flags, &tmp) ) { int32 old = idpflags; idpflags = (flags & ~(IDM__REQMASK << 16)) | (tmp << 16); if ( (idpflags ^ old) & IDF_ENCODING ) rename_uninames(-1); } goto SAVE; } if ( value_type != IDPOPT_BIT ) return IDPOPT_BADTYPE; struct keyword_info_t { const char *name; int bit; }; static const keyword_info_t keywords[] = { { "JAVA_MULTILINE_DEBUG", IDF_MULTDEB }, { "JAVA_HIDE_STACKMAP", IDF_HIDESM }, { "JAVA_AUTO_STRING", IDF_AUTOSTR }, { "JAVA_ASMFILE_CONVERT", IDF_CONVERT }, { "JAVA_ENABLE_ENCODING", IDF_ENCODING }, { "JAVA_NOPATH_ATTRIBUTE", IDF_NOPATH }, { "JAVA_UNKATTR_REQUEST", IDM_REQUNK }, { "JAVA_UNKATTR_WARNING", IDM_WARNUNK }, }; for ( int i=0; i < qnumber(keywords); i++ ) { if ( strcmp(keywords[i].name, keyword) == 0 ) { setflag(idpflags, keywords[i].bit, *(int*)value != 0); goto SAVE; } } if ( streq(keyword, "JAVA_STARTASM_LIST") ) { start_asm_list = *(int*)value; return IDPOPT_OK; } else { return IDPOPT_BADKEY; } SAVE: if ( idb_loaded ) ConstantNode.altset(CNA_IDPFLAGS, (ushort)idpflags); return IDPOPT_OK; } //---------------------------------------------------------------------- static const asm_t jasmin_asm = { AS_COLON | ASH_HEXF3 | ASO_OCTF1 | ASD_DECF0 | AS_ONEDUP | ASB_BINF3, UAS_JASMIN, "Jasmin assembler", 0, // no help screen NULL, // header NULL, // origin NULL, // end of file ";", // comment string '"', // string delimiter '\'', // char delimiter "\"'\\", // special symbols in char and string constants "", // ascii string directive "", // byte directive NULL, // word directive NULL, // double words NULL, // qwords NULL, // oword (16 bytes) NULL, // float NULL, // double NULL, // no tbytes NULL, // no packreal NULL, // arrays: // #h - header(.byte,.word) // #d - size of array // #v - value of array elements NULL, //".reserv %s", // uninited data (reserve space) " = ", // equ NULL, // seg prefix NULL, // a_curip func_header, // func header func_footer, // func footer "", // public (disable ouput) NULL, // weak NULL, // extrn NULL, // comm NULL, // get_type_name NULL, // align '(', ')', // lbrace, rbrace NULL, // mod "&", // and "|", // or "^", // xor "!", // not "<<", // shl ">>", // shr NULL, // sizeof 0, // flag2 NULL, // cmnt2 NULL, // low8 NULL, // high8 NULL, // low16 NULL, // high16 NULL, // a_include_fmt NULL, // a_vstruc_fmt NULL, // a_rva }; //---------------------------------------------------------------------- static const asm_t list_asm = { AS_COLON | ASH_HEXF3 | ASO_OCTF1 | ASD_DECF0 | AS_ONEDUP | ASB_BINF3, 0, "User friendly listing", 0, // no help screen NULL, // header NULL, // origin NULL, // end of file "//", // comment string '"', // string delimiter '\'', // char delimiter "\"'\\", // special symbols in char and string constants "", // ascii string directive "", // byte directive NULL, // word directive NULL, // double words NULL, // qwords NULL, // oword (16 bytes) NULL, // float NULL, // double NULL, // no tbytes NULL, // no packreal NULL, // arrays: // #h - header(.byte,.word) // #d - size of array // #v - value of array elements NULL, //".reserv %s", // uninited data (reserve space) " = ", // equ NULL, // seg prefix NULL, // a_curip func_header, // func header func_footer, // func footer "", // public (disable ouput) NULL, // weak NULL, // extrn NULL, // comm NULL, // get_type_name NULL, // align '(', ')', // lbrace, rbrace NULL, // mod "&", // and "|", // or "^", // xor "!", // not "<<", // shl ">>", // shr NULL, // sizeof 0, // flag2 NULL, // cmnt2 NULL, // low8 NULL, // high8 NULL, // low16 NULL, // high16 NULL, // a_include_fmt NULL, // a_vstruc_fmt NULL, // a_rva }; //----------------------------------------------------------------------- static const asm_t *const asms[] = { &jasmin_asm, &list_asm, NULL }; static const char *const RegNames[] = { "vars", "optop", "frame", "cs", "ds" }; #define FAMILY "Java Virtual Machine:" static const char *const shnames[] = { "java", #ifdef __debug__ "_javaPC", #endif NULL }; static const char *const lnames[] = { FAMILY"Java", #ifdef __debug__ "Java full (IBM PC, debug mode)", #endif NULL }; //-------------------------------------------------------------------------- static const uchar retcode_0[] = { j_ret }; static const uchar retcode_1[] = { j_ireturn }; static const uchar retcode_2[] = { j_lreturn }; static const uchar retcode_3[] = { j_freturn }; static const uchar retcode_4[] = { j_dreturn }; static const uchar retcode_5[] = { j_areturn }; static const uchar retcode_6[] = { j_return }; static const uchar retcode_7[] = { j_wide, j_ret }; static const bytes_t retcodes[] = { { sizeof(retcode_0), retcode_0 }, { 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 }, { 0, NULL } }; //-------------------------------------------------------------------------- ssize_t idaapi idb_listener_t::on_event(ssize_t code, va_list) { switch ( code ) { case idb_event::closebase: memset(&pm.curClass, 0, sizeof(pm.curClass)); // no break case idb_event::savebase: pm.ConstantNode.altset(CNA_IDPFLAGS, (ushort)pm.idpflags); break; case idb_event::auto_empty: if ( !(pm.curClass.extflg & XFL_C_DONE) ) // kernel BUGs { pm.curClass.extflg |= XFL_C_DONE; msg("JavaLoader finalization stage..."); for ( int n = pm.curClass.MethodCnt; n; n-- ) { SegInfo si; if ( pm.ClassNode.supval(-n, &si, sizeof(si)) != sizeof(si) ) DESTROYED("postprocess"); if ( si.smNode || si.DataSize ) { show_addr(si.start_ea); if ( si.smNode ) pm.sm_validate(&si); if ( si.DataSize ) pm.coagulate_unused_data(&si); } } pm.ConstantNode.supset(CNS_CLASS, &pm.curClass, sizeof(pm.curClass)); // all chgs pm.sm_node = smn_ok; msg("OK\n"); } break; } return 0; } //---------------------------------------------------------------------- // 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(java_t)); return 0; } //-------------------------------------------------------------------------- ssize_t idaapi java_t::on_event(ssize_t msgid, va_list va) { int retcode = 0; switch ( msgid ) { case processor_t::ev_init: hook_event_listener(HT_IDB, &idb_listener, &LPH); inf_set_be(true); // reverse byte! break; case processor_t::ev_rename: va_arg(va, ea_t); for ( char const *pn, *p = va_arg(va, const char *); (pn = strchr(p, '\\')) != NULL; p = pn+1 ) { if ( *++pn != 'u' ) { inv_name: --retcode; // 0 warning("Backslash is accepted only as a unicode escape sequence in names"); break; } for ( int i = 0; i < 4; i++ ) if ( !qisxdigit((uchar)*++pn) ) goto inv_name; } break; case processor_t::ev_newfile: if ( inf_get_filetype() != f_LOADER ) { set_database_flag(DBFL_KILL); // clean up the database files error("The input file does not have a supported Java file format"); } database_loaded (va_arg(va, char *)); inf_set_lowoff(BADADDR); inf_set_highoff(BADADDR); break; case processor_t::ev_ending_undo: case processor_t::ev_oldfile: database_loaded(NULL); break; case processor_t::ev_term: unhook_event_listener(HT_IDB, &idb_listener); qfree(tsPtr); qfree(smBuf); qfree(annBuf); clr_module_data(data_id); break; #ifdef __debug__ case processor_t::ev_newprc: { int procnum = va_arg(va, int); bool keep_cfg = va_argi(va, bool); if ( procnum == 1 ) // debug mode { ph.flag &= ~(PR_DEFNUM | PR_NOCHANGE); ph.flag |= PRN_HEX; if ( inf_get_margin() == 77 && !inf.bin_prefix_size && !inf.show_line_pref() ) { if ( !keep_cfg ) inf_set_show_line_pref(true); --debugmode; } else { ++debugmode; } } else // normal node { ph.flag &= ~PR_DEFNUM; ph.flag |= PRN_DEC; if ( debugmode == -1 && inf.show_line_pref() && !inf.bin_prefix_size && inf_get_margin() == 77 && !keep_cfg ) { inf.show_line_pref(false); } debugmode = 0; } } break; #endif case java_module_t::ev_load_file: { linput_t *li = va_arg(va, linput_t *); FILE *f = qlfile(li); QASSERT(10082, f != NULL); bool manual = va_argi(va, bool); loader(f, manual); retcode = 0; } if ( start_asm_list ) set_target_assembler(1); break; case processor_t::ev_gen_src_file_lnnum: if ( jasmin() ) { outctx_t *ctx = va_arg(va, outctx_t *); va_arg(va, const char *); // skip file name size_t lineno = va_arg(va, size_t); ctx->gen_printf(2, COLSTR(".line %" FMT_Z, SCOLOR_ASMDIR), lineno); retcode = 1; } break; case processor_t::ev_gen_asm_or_lst: { if ( va_argi(va, bool) ) // starting (else end of generation ) { va_arg(va, FILE *); // output file (skip) bool isasm = va_argi(va, bool); // assembler-true, listing-false if ( isasm && (idpflags & IDF_CONVERT) ) { va_arg(va, int); // flags of gen_file() (skip) *va_arg(va, gen_outline_t**) = out_asm_file; idpflags |= IDM_OUTASM; } if ( isasm == jasmin() ) break; // need change mode? } else // end of generation. { idpflags &= ~IDM_OUTASM; if ( !mode_changed ) break; // mode changed? } mode_changed = !mode_changed; set_target_assembler(!inf_get_asmtype()); } break; case processor_t::ev_get_autocmt: { qstring *buf = va_arg(va, qstring *); const insn_t *insn = va_arg(va, insn_t *); if ( make_locvar_cmt(buf, *insn) ) retcode = 1; } break; case processor_t::ev_out_mnem: { outctx_t *ctx = va_arg(va, outctx_t *); out_mnem(*ctx); return 1; } case processor_t::ev_out_header: { outctx_t *ctx = va_arg(va, outctx_t *); java_header(*ctx); return 1; } case processor_t::ev_out_footer: { outctx_t *ctx = va_arg(va, outctx_t *); java_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 *); java_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 *); java_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_out_data: { outctx_t *ctx = va_arg(va, outctx_t *); bool analyze_only = va_argi(va, bool); java_data(*ctx, analyze_only); return 1; } case processor_t::ev_can_have_type: { const op_t *op = va_arg(va, const op_t *); return can_have_type(*op) ? 1 : -1; } case processor_t::ev_realcvt: { void *m = va_arg(va, void *); fpvalue_t *e = va_arg(va, fpvalue_t *); uint16 swt = va_argi(va, uint16); fpvalue_error_t code = j_realcvt(m, e, swt); return code == REAL_ERROR_OK ? 1 : code; } case processor_t::ev_gen_map_file: { int *nlines = va_arg(va, int *); FILE *fp = va_arg(va, FILE *); int code = gen_map_file(fp); if ( code == -1 ) return -1; *nlines = code; return 1; } case processor_t::ev_extract_address: { ea_t *out_ea = va_arg(va, ea_t *); ea_t screen_ea = va_arg(va, ea_t); const char *str = va_arg(va, const char *); size_t pos = va_arg(va, size_t); ea_t ea = get_ref_addr(screen_ea, str, pos); if ( ea == BADADDR ) return -1; if ( ea == (BADADDR-1) ) return 0; *out_ea = ea; return 1; } case processor_t::ev_out_special_item: { outctx_t *ctx = va_arg(va, outctx_t *); uchar seg_type = va_argi(va, uchar); java_specseg(*ctx, seg_type); return 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 retcode; } //----------------------------------------------------------------------- // Processor Definition //----------------------------------------------------------------------- processor_t LPH = { IDP_INTERFACE_VERSION, // version PLFM_JAVA, // id // flag PRN_DEC | PR_RNAMESOK | PR_NO_SEGMOVE, // flag2 PR2_REALCVT // the module has 'realcvt' event implementation | PR2_IDP_OPTS, // the module has processor-specific configuration options 8, // 8 bits in a byte for code segments 8, // 8 bits in a byte for other segments shnames, lnames, asms, notify, RegNames, // Regsiter names qnumber(RegNames), // Number of registers rVcs,rVds, 0, // size of a segment register rVcs,rVds, NULL, // No known code start sequences retcodes, 0,j_last, Instructions, // instruc 0, // size of tbyte {0,7,15,0}, // real width j_ret, // icode_return NULL, // Micro virtual machine description };