/************************************************************* * File: dis16.c * Purpose: disassembler for mips16 * Author: Phil Bunce (pjb@carmel.com) * Revision History: * 970505 Created * 980107 Corrected typedef for dis16fmt * 980715 Fixed for -EL mips16 * 990107 Fixed prob with extended instrs * 990303 Fixed disassembly of lbu */ #include #include #define RXN(i) (((i)>>8)&7) #define RYN(i) (((i)>>5)&7) #define RZN(i) (((i)>>2)&7) #define R32AN(i) (((i)>>0)&0x1f) #define R32BN(i) (((i)&0x18)|((i)>>5)&7) #define RX(i) gp16regs[RXN(i)] #define RY(i) gp16regs[RYN(i)] #define RZ(i) gp16regs[RZN(i)] #define SA(i) ((((i)>>2)&7)?(((i)>>2)&7):8) #define R32A(i) gp32regs[R32AN(i)] #define R32B(i) gp32regs[R32BN(i)] typedef enum { RA_O_SP, RX_O_SP, RY_O_RX1, RY_O_RX2, RX_O_PC, RX_I8, RX_UI8, RX_PC_I8, RX_SP_I8, RY_RX_I4, SP_I8, RZ_RX_RY, R32B_RZ, RY_R32A, RX_RY_SA, JAL_TARG, RX_RY, OFFSET8_ONLY, RY_I5, RY_PC_I5, RY_SP_I5, RY_O_SP3, RY_O_RX3, RY_O_PC, RY_O_RX0, RX_ONLY, RA_ONLY, RA_RX, OFFSET11_ONLY, RX_OFFSET, IMM6_ONLY, RY_RX, RY_SA, NO_OPS, JALX_TARG, JR_RX } dis16fmt; enum { SIMM4, SIMM5, UIMM5, UIMM8, SIMM8, SIMM11, SIMM15, UIMM16, SIMM16, SIMM16S}; typedef struct Dis16Rec { char *name; Ulong mask; Ulong val; int fmt; } Dis16Rec; Dis16Rec dis16tbl[] = { {"jal", 0xfc00,0x1800,JAL_TARG}, {"jalx", 0xfc00,0x1c00,JALX_TARG}, {"nop", 0xffff,0x6500,NO_OPS}, {"addiu", 0xf800,0x0000,RX_SP_I8}, /* rx, sp, imm8 */ {"addiu", 0xf800,0x0800,RX_PC_I8}, /* rx, pc, imm8 */ {"addiu", 0xf800,0x4800,RX_I8}, /* rx, imm8 */ {"addiu", 0xf810,0x4000,RY_RX_I4}, /* ry, rx, imm4 */ {"addiu", 0xff00,0x6300,SP_I8}, /* sp, imm8 */ {"addu", 0xf803,0xe001,RZ_RX_RY}, /* rz,rx,ry */ {"and", 0xf81f,0xe80c,RX_RY}, {"b", 0xf800,0x1000,OFFSET11_ONLY}, {"beqz", 0xf800,0x2000,RX_OFFSET}, {"bnez", 0xf800,0x2800,RX_OFFSET}, {"break", 0xf81f,0xe805,IMM6_ONLY}, {"bteqz", 0xff00,0x6000,OFFSET8_ONLY}, {"btnez", 0xff00,0x6100,OFFSET8_ONLY}, {"cmp", 0xf800,0x7000,RX_I8}, {"cmp", 0xf81f,0xe80a,RX_RY}, #if 0 /* 64-bit */ {"daddiu",0xf810,0x4010,RY_RX_I4}, {"daddiu",0xff00,0xfb00,SP_I8}, {"daddiu",0xff00,0xfd00,RY_I5}, {"daddiu",0xff00,0xfe00,RY_PC_I5}, {"daddiu",0xff00,0xff00,RY_SP_I5}, {"daddu", 0xf803,0xe000,RZ_RX_RY}, {"ddiv", 0xf81f,0xe81e,RX_RY}, {"ddivu", 0xf81f,0xe81f,RX_RY}, {"dmult", 0xf81f,0xe81c,RX_RY}, {"dmultu",0xf81f,0xe81d,RX_RY}, {"dsll", 0xf803,0x3001,RX_RY_SA}, {"dsll", 0xf81f,0xe814,RY_RX}, {"dsra", 0xf81f,0xe813,RY_SA}, {"dsra", 0xf81f,0xe817,RY_RX}, {"dsrl", 0xf81f,0xe808,RY_SA}, {"dsrl", 0xf81f,0xe816,RY_RX}, {"dsubu", 0xf803,0xe002,RZ_RX_RY}, {"lwu", 0xf800,0xb800,RY_O_RX2}, /* ry,off(rx) */ {"ld", 0xf800,0x3800,RY_O_RX3}, {"ld", 0xff00,0xf800,RY_O_SP3}, {"ld", 0xff00,0xfc00,RY_O_PC}, {"sd", 0xff00,0xfa00,RA_O_SP}, {"sd", 0xf800,0x7800,RY_O_RX3}, {"sd", 0xff00,0xf900,RY_O_SP3}, #endif {"div", 0xf81f,0xe81a,RX_RY}, {"divu", 0xf81f,0xe81b,RX_RY}, {"jalr", 0xf8ff,0xe840,RA_RX}, {"jr", 0xf8ff,0xe800,JR_RX}, {"jr", 0xffff,0xe820,RA_ONLY}, {"lb", 0xf800,0x8000,RY_O_RX0}, {"lh", 0xf800,0x8800,RY_O_RX1}, /* ry,off(rx) */ {"lbu", 0xf800,0xa000,RY_O_RX0}, {"lhu", 0xf800,0xa800,RY_O_RX1}, /* ry,off(rx) */ {"li", 0xf800,0x6800,RX_UI8}, {"lw", 0xf800,0x9000,RX_O_SP}, /* rx,off(sp) */ {"lw", 0xf800,0x9800,RY_O_RX2}, /* ry,off(rx) */ {"lw", 0xf800,0xb000,RX_O_PC}, /* rx,off(pc) */ {"mfhi", 0xf8ff,0xe810,RX_ONLY}, {"mflo", 0xf8ff,0xe812,RX_ONLY}, {"mult", 0xf81f,0xe818,RX_RY}, {"multu", 0xf81f,0xe819,RX_RY}, {"move", 0xff00,0x6500,R32B_RZ}, {"move", 0xff00,0x6700,RY_R32A}, {"neg", 0xf81f,0xe80b,RX_RY}, {"not", 0xf81f,0xe80f,RX_RY}, {"or", 0xf81f,0xe80d,RX_RY}, {"sb", 0xf800,0xc000,RY_O_RX0}, /* ry,off(rx) */ {"sh", 0xf800,0xc800,RY_O_RX1}, /* ry,off(rx) */ {"sll", 0xf803,0x3000,RX_RY_SA}, {"sllv", 0xf81f,0xe804,RY_RX}, {"slt", 0xf81f,0xe802,RX_RY}, {"slti", 0xf800,0x5000,RX_I8}, {"sltiu", 0xf800,0x5800,RX_I8}, {"sltu", 0xf81f,0xe803,RX_RY}, {"sra", 0xf803,0x3003,RX_RY_SA}, {"srav", 0xf81f,0xe807,RY_RX}, {"srl", 0xf803,0x3002,RX_RY_SA}, {"srlv", 0xf81f,0xe806,RY_RX}, {"subu", 0xf803,0xe803,RZ_RX_RY}, {"sw", 0xff00,0x6200,RA_O_SP}, /* ra,off(sp) */ {"sw", 0xf800,0xd000,RX_O_SP}, /* rx,off(sp) */ {"sw", 0xf800,0xd800,RY_O_RX2}, /* ry,off(rx) */ {"xor", 0xf81f,0xe80e,RX_RY}, {0}}; char *gp16regs[] = {"s0","s1","v0","v1","a0","a1","a2","a3"}; char gp16regNum[] = {16,17,2,3,4,5,6,7}; char *gp32regs[] = {"zero","AT","v0","v1","a0","a1","a2","a3", "t0,","t1","t2","t3","t4","t5","t6","t7", "s0","s1","s2","s3","s4","s5","s5","s6","s7", "t8","t9","k0","k1","gp","sp", "s8","ra"}; static Ulong imm(); static Ulong sgnext(); #define swap_halves(v) ((((Ulong)(v))>>16)&0xffff) | \ ((((Ulong)(v))<<16)&0xffff0000); /* hw0 and hw1 are for accessing half-word zero and half-word on. * Half-word zero is always the first read from memory (ie the occupying * the lowest numbered address). */ #ifdef MIPSEB #define hw0(v) (((Ulong)(v))>>16) #define hw1(v) (((Ulong)(v))&0xffff) #else #define hw0(v) (((Ulong)(v))&0xffff) #define hw1(v) (((Ulong)(v))>>16) #endif static Ulong getInst(Ulong instr); static int mips16_32bit_instr(Ulong instr); static Dis16Rec *getDis16Rec(Ulong instr); static Ulong get16Gpr(int n); /************************************************************* */ ADDR dis16(char *prnbuf,ADDR addr,Ulong wd) { int n,sz,extended; char tmp[200]; Ulong im,instr; Dis16Rec *t; ADDR vaddr; vaddr = addr&~1; instr = getInst(wd); extended = 0; if (mips16_32bit_instr(wd)) { addr += 2; sprintf(tmp," %04x%04x ",hw0(wd),hw1(wd)); extended = 1; } else sprintf(tmp," %04x ",instr); strcat(prnbuf,tmp); if ((t=getDis16Rec(wd))==0) { sprintf(tmp,".half %04x",instr); strcat(prnbuf,tmp); return(addr+2); } n = strlen(t->name); strcat(prnbuf,t->name); while(n++ < 8) strcat(prnbuf," "); switch (t->fmt) { case NO_OPS : *tmp=0; break; case RY_I5: im = imm(instr,(!extended)?SIMM5:SIMM16,wd,0); sprintf(tmp,"%s,%d",RY(instr),im); strcat(prnbuf,tmp); *tmp = 0; mkcomment(prnbuf,"# 0x%08lx",im); break; case RY_PC_I5: im = imm(instr,(!extended)?UIMM5:UIMM16,wd,2); sprintf(tmp,"%s,pc,%d",RY(instr),im); strcat(prnbuf,tmp); *tmp = 0; mkcomment(prnbuf,"# 0x%08lx",vaddr+im); break; case RY_SP_I5: im = imm(instr,(!extended)?UIMM5:UIMM16,wd,2); sprintf(tmp,"%s,sp,%d",RY(instr),im); strcat(prnbuf,tmp); *tmp = 0; mkcomment(prnbuf,"# 0x%08lx",im); break; case IMM6_ONLY: im = (instr>>5)&0x3f; sprintf(tmp,"%d",im); break; case RX_RY: sprintf(tmp,"%s,%s",RX(instr),RY(instr)); break; case RA_RX: sprintf(tmp,"ra,%s",RX(instr)); break; case RA_ONLY: sprintf(tmp,"ra"); break; case RX_ONLY: case JR_RX : sprintf(tmp,"%s",RX(instr)); break; case JAL_TARG: im = ((hw0(wd)&0x1f)<<21)| ((hw0(wd)&0x3e0)<<(16-5))| hw1(wd); im = (vaddr&0xf0000000)|(im<<2); if (!adr2symoff(tmp,im+1,0)) sprintf(tmp,"%x",im); strcat(prnbuf,tmp); *tmp = 0; mkcomment(prnbuf,"# 0x%08lx",im); break; case JALX_TARG: im = ((hw0(wd)&0x1f)<<21)| ((hw0(wd)&0x3e0)<<(16-5))| hw1(wd); im = (vaddr&0xf0000000)|(im<<2); if (!adr2symoff(tmp,im,0)) sprintf(tmp,"%x",im); strcat(prnbuf,tmp); *tmp = 0; mkcomment(prnbuf,"# 0x%08lx",im); break; case RX_RY_SA: sprintf(tmp,"%s,%s,%d",RX(instr),RY(instr),SA(instr)); break; case RY_SA: sprintf(tmp,"%s,%d",RY(instr),SA(instr)); break; /* ry,im,rx */ case RY_O_RX0: im = imm(instr,(!extended)?UIMM5:SIMM16,wd,0); sprintf(tmp,"%s,%d(%s)",RY(instr),im,RX(instr)); break; case RY_O_RX1: im = imm(instr,(!extended)?UIMM5:SIMM16,wd,1); sprintf(tmp,"%s,%d(%s)",RY(instr),im,RX(instr)); break; case RY_O_RX2: im = imm(instr,(!extended)?UIMM5:SIMM16,wd,2); sprintf(tmp,"%s,%d(%s)",RY(instr),im,RX(instr)); break; case RY_O_RX3: im = imm(instr,(!extended)?UIMM5:SIMM16,wd,3); sprintf(tmp,"%s,%d(%s)",RY(instr),im,RX(instr)); break; /* rx,im,im */ case RX_OFFSET: /* RX_IM_IM, SIMM8, SIMM16S, 1, $addr+2 */ sz = instr_size(addr,instr); im = vaddr+sz+imm(instr,(!extended)?SIMM8:SIMM16S,wd,1); sprintf(tmp,"%s,",RX(instr)); strcat(prnbuf,tmp); if (!adr2symoff(tmp,im+1,0)) sprintf(tmp,"%x",im); strcat(prnbuf,tmp); *tmp = 0; mkcomment(prnbuf,"# 0x%08lx",im); break; case RY_O_SP3: im = imm(instr,(!extended)?UIMM8:SIMM16,wd,3); sprintf(tmp,"%s,%d(sp)",RY(instr),im); break; case RX_O_SP: im = imm(instr,(!extended)?UIMM8:SIMM16,wd,2); sprintf(tmp,"%s,%d(sp)",RX(instr),im); break; case RX_I8: im = imm(instr,(!extended)?SIMM8:UIMM16,wd,0); sprintf(tmp,"%s,%d",RX(instr),im); strcat(prnbuf,tmp); *tmp = 0; mkcomment(prnbuf,"# 0x%08lx",im); break; case RX_UI8: im = imm(instr,(!extended)?UIMM8:UIMM16,wd,0); sprintf(tmp,"%s,%d",RX(instr),im); strcat(prnbuf,tmp); *tmp = 0; mkcomment(prnbuf,"# 0x%08lx",im); break; case RX_SP_I8: im = imm(instr,(!extended)?UIMM8:UIMM16,wd,2); sprintf(tmp,"%s,sp,%d",RX(instr),im); strcat(prnbuf,tmp); *tmp = 0; mkcomment(prnbuf,"# 0x%08lx",im); break; /* ry,rx,im */ case RY_RX_I4: im = imm(instr,(!extended)?SIMM4:SIMM15,wd,0); sprintf(tmp,"%s,%s,%d",RY(instr),RX(instr),im); break; /* rx,im,addr+im */ case RY_O_PC: im = imm(instr,(!extended)?UIMM8:SIMM16,wd,2); sprintf(tmp,"%s,%d(pc)",RY(instr),im); strcat(prnbuf,tmp); *tmp = 0; mkcomment(prnbuf,"# 0x%08lx",vaddr+im); break; case RX_O_PC: im = imm(instr,(!extended)?UIMM8:SIMM16,wd,2); sprintf(tmp,"%s,%d(pc)",RX(instr),im); strcat(prnbuf,tmp); *tmp = 0; mkcomment(prnbuf,"# 0x%08lx",vaddr+im); break; case RX_PC_I8: im = imm(instr,(!extended)?UIMM8:UIMM16,wd,2); sprintf(tmp,"%s,pc,%d",RX(instr),im); strcat(prnbuf,tmp); *tmp = 0; mkcomment(prnbuf,"# 0x%08lx",vaddr+im); break; /* im,im */ case OFFSET8_ONLY: /* bteqz btnez */ sz = instr_size(addr,instr); im = vaddr+sz+imm(instr,(!extended)?SIMM8:SIMM16S,wd,1); if (!adr2symoff(tmp,im+1,0)) sprintf(tmp,"%x",im); strcat(prnbuf,tmp); *tmp = 0; mkcomment(prnbuf,"# 0x%08lx",im); break; case OFFSET11_ONLY: /* b */ sz = instr_size(addr,instr); im = vaddr+sz+imm(instr,(!extended)?SIMM11:SIMM16S,wd,1); if (!adr2symoff(tmp,im+1,0)) sprintf(tmp,"%x",im); strcat(prnbuf,tmp); *tmp = 0; mkcomment(prnbuf,"# 0x%08lx",im); break; case SP_I8: im = imm(instr,(!extended)?SIMM8:UIMM16,wd,3); sprintf(tmp,"sp,%d",im); strcat(prnbuf,tmp); *tmp = 0; mkcomment(prnbuf,"# 0x%08lx",im); break; case RA_O_SP: im = imm(instr,(!extended)?UIMM8:SIMM16,wd,2); sprintf(tmp,"ra,%d(sp)",im); break; /* rx,rx,ry */ case RZ_RX_RY: sprintf(tmp,"%s,%s,%s",RZ(instr),RX(instr),RY(instr)); break; /* r32b,rz */ case R32B_RZ : sprintf(tmp,"%s,%s",R32B(instr),RZ(instr)); break; /* ry,r32a */ case RY_R32A : sprintf(tmp,"%s,%s",RY(instr),R32A(instr)); break; case RY_RX: sprintf(tmp,"%s,%s",RY(instr),RX(instr)); break; } if (*tmp) strcat(prnbuf,tmp); return(addr+2); } /************************************************************* */ static Ulong get16Gpr(int n) { return getGpr32(gp16regNum[n]); } /************************************************************* */ static Ulong imm(w,sz,wd,shamt) Ulong w; /* inst */ int sz; /* size (format) */ Ulong wd; /* entire word, needed for extended */ int shamt; /* shift amount */ { Ulong im; switch (sz) { case SIMM4 : im = (w&0xf)<>5)&0x7800)|((wd>>16)&0x07f0)|(wd&0x0f); im = sgnext(im,15); break; case UIMM16 : im = ((wd>>5)&0xf800)|((wd>>16)&0x07e0)|(wd&0x1f); break; case SIMM16 : im = ((wd>>5)&0xf800)|((wd>>16)&0x07e0)|(wd&0x1f); im = sgnext(im,16); break; case SIMM16S : im = (((wd>>5)&0xf800)|((wd>>16)&0x07e0)|(wd&0x1f))<>= 32-n; return((Ulong)sw); } /************************************************************* */ ADDR mips16_branch_target_address(ADDR adr,Ulong wd) { Ulong im,inst; int sz,extended; Dis16Rec *t; ADDR vaddr; if (mips16_is_extended(wd)) extended = 1; else extended = 0; if ((t=getDis16Rec(wd))==0) return(0); inst = getInst(wd); sz = instr_size(adr,inst); vaddr = adr & ~1; switch(t->fmt) { case OFFSET11_ONLY: /* b */ return adr+sz+imm(inst,(!extended)?SIMM11:SIMM16S,wd,1); case RX_OFFSET: /* beqz bnez */ return adr+sz+imm(inst,(!extended)?SIMM8:SIMM16S,wd,1); case OFFSET8_ONLY: /* btnez bteqz */ return adr+sz+imm(inst,(!extended)?SIMM8:SIMM16S,wd,1); case JAL_TARG: im = ((hw0(wd)&0x1f)<<21)| ((hw0(wd)&0x3e0)<<(16-5))|hw1(wd); return (vaddr&0xf0000000)|(im<<2)|1; case JALX_TARG: im = ((hw0(wd)&0x1f)<<21)| ((hw0(wd)&0x3e0)<<(16-5))| hw1(wd); return (vaddr&0xf0000000)|(im<<2); case RA_RX: case JR_RX: return get16Gpr(RXN(inst)); case RA_ONLY: return getGpr32(31); default: return(0); } } /************************************************************* * ADDR mips16_actual_branch_target_address(adr,wd) * Called from machine.c * Problems here */ ADDR mips16_actual_branch_target_address(ADDR adr,Ulong wd) { Ulong inst; int sz; ADDR target; target = mips16_branch_target_address(adr,wd); if (!mips16_is_conditional_branch(adr,wd)) return(target); if (mips16_is_extended(wd)) sz = 4; else sz = 2; inst = getInst(wd); /* beqz bnez bteqz btnez */ if ((inst&0xf800)==0x2000) return (get16Gpr(RXN(inst)))?adr+sz:target; else if ((inst&0xf800)==0x2800) return (get16Gpr(RXN(inst)))?target:adr+sz; else if ((inst&0xff00)==0x6000) return (getGpr32(24))?adr+sz:target; else if ((inst&0xff00)==0x6100) return (getGpr32(24))?target:adr+sz; return(0); } /************************************************************* * int mips16_is_extended(wd) */ int mips16_is_extended(wd) Ulong wd; { if ((hw0(wd)&0xf800)==0xf000) return(1); return(0); } /************************************************************* * int mips16_is_jal(addr,wd) * Called from machine.c */ int mips16_is_jal(ADDR addr,Ulong wd) { Dis16Rec *t; if ((t=getDis16Rec(wd))==0) return(0); switch(t->fmt) { case JAL_TARG: case JALX_TARG: case RA_RX: return(1); default: return(0); } } /************************************************************* * int mips16_is_jr(addr,wd) * Called by machine.c */ int mips16_is_jr(ADDR addr,Ulong wd) { Dis16Rec *t; if ((t=getDis16Rec(wd))==0) return(0); switch(t->fmt) { case RA_RX: case JR_RX: case RA_ONLY: return(1); default: return(0); } } /************************************************************* * int mips16_is_branch(addr,wd) * Called by machine.c */ int mips16_is_branch(ADDR addr,Ulong wd) { Dis16Rec *t; if ((t=getDis16Rec(wd))==0) return(0); switch(t->fmt) { case RX_OFFSET: case OFFSET11_ONLY: case OFFSET8_ONLY: case JAL_TARG: case JALX_TARG: case RA_RX: case JR_RX: case RA_ONLY: return(1); default: return(0); } } /************************************************************* */ int mips16_is_conditional_branch(ADDR addr,Ulong wd) { Dis16Rec *t; if ((t=getDis16Rec(wd))==0) return(0); switch(t->fmt) { case RX_OFFSET: case OFFSET8_ONLY: return(1); default: return(0); } } /************************************************************* */ static Dis16Rec *getDis16Rec(Ulong wd) { int i; Ulong inst; inst = getInst(wd); for (i=0;dis16tbl[i].name;i++) { if ((inst&dis16tbl[i].mask) == dis16tbl[i].val) return(&dis16tbl[i]); } return(0); } /************************************************************* */ static int mips16_32bit_instr(Ulong wd) { if ((hw0(wd)&0xf800)==0x1800) return(1); /* jal or jalx */ if (mips16_is_extended(wd)) return(1); return(0); } /************************************************************* */ static Ulong getInst(Ulong wd) { if (mips16_is_extended(wd)) return(hw1(wd)); else return(hw0(wd)); }