首页 > 要闻 >

徒手编写了一个STM8的反汇编工具

一个字节能表示的范围除了 0x90, 0x91, 0x92, 0x72 用来做指令前缀,其它几乎都用来作操作码了。当然许多指令都有多种寻址模式的(比如加法是谁和谁相加,需要指定),因此用了不止一个操作码。算上寻址模式,256种指令都不够用的,所以STM8靠前面增加前缀字节来扩展。从手册里面截一个例子如下(这是XOR指令的多种编码):

在指令的操作码后面就是提供数据或地址的字节了,长度由操作码加上前缀来决定。编写反汇编程序就是写一个根据字节数据流的查表过程。上面我做的那个表只是划分了指令的分布,涉及到寻址模式的细节还是得一边写一边查手册。从表上看,操作码的高半字节大概可以把指令划分为几类,再用低半字节去细分指令,于是我的程序解码第一步就是一个 switch-case 结构来划分任务:

int decode_instr(unsigned char opcode)


(资料图片)

{

switch(opcode>>4)

{

case 1: case 0x0A: case 0x0B: case 0x0C:

case 0x0D: case 0x0E: case 0x0F:

return decode_group1(opcode);

case 0: case 3: case 4: case 6: case 7:

return decode_group2(opcode);

case 5:

if(Prefix==0x72)

return decode_group2(opcode);

else

return decode_5x(opcode);

case 8:

return decode_8x(opcode);

case 2:

return decode_2x(opcode);

case 9:

return decode_9x(opcode);

default:

return -1;

}

}

解码的结果是放到全局变量里面的,返回值只代表了指令是否有效。例如,表格最右边一列的指令我是这样解析的:

int decode_9x(unsigned char opcode)

{

AutoXY=1;

switch(opcode&0x0f)

{

case 0: return set_prefix(0x90);

case 1: return set_prefix(0x91);

case 2: return set_prefix(0x92);

case 3: format(0, LDW, regX, regY);

format(0x90, LDW, regY, regX);

return 1;

case 4: format(0, LDW, regSP, regX);

return 1;

case 5: format(0, LD, regXH, regA);

return 1;

case 6: format(0, LDW, regX, regSP);

return 1;

case 7: format(0, LD, regXL, regA);

return 1;

case 8: format(0, RCF, 0, 0);

return 1;

case 9: format(0, SCF, 0, 0);

return 1;

case 0xA: format(0, RIM, 0, 0);

return 1;

case 0xB: format(0, SIM, 0, 0);

return 1;

case 0xC: format(0, RVF, 0, 0);

return 1;

case 0xD: format(0, NOP, 0, 0);

return 1;

case 0xE: format(0, LD, regA, regXH);

return 1;

case 0xF: format(0, LD, regA, regXL);

return 1;

default:

return -1;

}

}

主要是靠 format() 函数根据当前的指令前缀来翻译操作码:指令名称,寻址的第一操作数、第二操作数。若一共写 256 个 case 分支就太繁琐了,需要抓住共性,像表格中绿色背景的这一组指令我是这么处理的:

int decode_group2(unsigned char opcode)

{

int instr;

AutoXY=1;

switch(opcode&0x0f)

{

case 1:

switch(opcode>>4)

{

case 0: format(0, RRWA, regX, 0); return 1;

case 3: format(0, EXG, regA, longmem); return 1;

case 4: format(0, EXG, regA, regXL); return 1;

case 6: format(0, EXG, regA, regYL); return 1;

default: return -1;

}

break;

case 2:

switch(opcode>>4)

{

case 0: format(0, RLWA, regX, 0); return 1;

case 3: format(0, POP, longmem, 0); return 1;

case 4: format(0, MUL, regX, regA); return 1;

case 6: format(0, DIV, regX, regA); return 1;

case 7: return set_prefix(0x72);

}

break;

case 5:

switch(opcode>>4)

{

case 3: format(0, MOV, longmem, imm8); return 1;

case 4: format(0, MOV, mem, mem); return 1;

case 6: format(0, DIVW, regX, regY); return 1;

default: return -1;

}

break;

case 0xB:

switch(opcode>>4)

{

case 3: format(0, PUSH, longmem, 0); return 1;

case 4: format(0, PUSH, imm8, 0); return 1;

case 6: format(0, LD, offSP, regA); return 1;

case 7: format(0, LD, regA, offSP); return 1;

default: return -1;

}

break;

case 0:instr=NEG; break;

case 3:instr=CPL; break;

case 4:instr=SRL; break;

case 6:instr=RRC; break;

case 7:instr=SRA; break;

case 8:instr=SLL; break;

case 9:instr=RLC; break;

case 0xA:instr=DEC; break;

case 0xC:instr=INC; break;

case 0xD:instr=TNZ; break;

case 0xE:instr=SWAP; break;

case 0xF:instr=CLR; break;

default: return -1;

}

switch(opcode>>4)

{

case 0: format(0, instr, offSP, 0); return 1;

case 3: format(0, instr, mem, 0);

format(0x92, instr, shortptr, 0);

format(0x72, instr, longptr, 0);

return 1;

case 4: format(0, instr, regA, 0);

format(0x72, instr, longoffX, 0);

return 1;

case 5: format(0x72, instr, longmem, 0);

return 1;

case 6: format(0, instr, offX, 0);

format(0x92, instr, sptr_offX, 0);

format(0x72, instr, lptr_offX, 0);

format(0x91, instr, sptr_offY, 0);

return 1;

case 7: format(0, instr, indX, 0);

return 1;

default: return -1;

}

}

在给 format() 这个函数的参数中,指令和操作数类型都是用数值来表示的——用 enum 定义:

#define SI_BASE 100

#define SA_BASE 1000

enum{

ADC=SI_BASE, ADD, ADDW, AND, BCCM, BCP, BCPL, BREAK, BRES, BSET, BTJF, BTJT, CALL,

CALLF, CALLR, CCF, CLR, CLRW, CP, CPW, CPL, CPLW, DEC, DECW, DIV, DIVW, EXG,

EXGW, HALT, INC, INCW, INT, IRET, JP, JPF, JRA,

JRC, JREQ, JRF, JRH, JRIH, JRIL, JRM, JRMI, JRNC, JRNE, JRNH, JRNM, JRNV,

JRPL, JRSGE, JRSGT, JRSLE, JRSLT, JRT, JRUGE, JRUGT, JRULE, JRULT, JRV,

LD, LDF, LDW, MOV, MUL, NEG, NEGW, NOP, OR, POP, POPW, PUSH, PUSHW, RCF, RET,

RETF, RIM, RLC, RLCW, RLWA, RRC, RRCW, RRWA, RVF, SBC, SCF, SIM, SLL, SLLW,

SRA, SRAW, SRL, SRLW, SUB, SUBW, SWAP, SWAPW, TNZ, TNZW, TRAP, WFE, WFI, XOR

};

enum{

regA=SA_BASE, regX, regY, regXH, regXL, regYH, regYL, regCC, regSP,

imm8, imm16, rel, mem, longmem, offX, offY, offSP, longoffX, longoffY,

indX, indY, shortptr, longptr, sptr_offX, sptr_offY, lptr_offX, lptr_offY,

ext, extoffX, extoffY

};

我想这么写而不是直接写字符串的原因是,字符串万一写错了很难检查出来。写成常量编译器可以检查,再统一对应到字符串即可。format() 函数是这么实现的:

void format(unsigned char pre, int instr, int opr1, int opr2)

{

char replace=(AutoXY && Prefix==0x90 && pre==0);

if(replace)

{

int r1, r2;

r1=replace_X_Y(opr1);

r2=replace_X_Y(opr2);

if(r1>=SA_BASE && r2==0)

opr1=r1;

else

{

if(r2>=SA_BASE && r1==0)

opr2=r2;

else

return;

}

}

if(Prefix==pre ||replace)

{

if(instr

Str_inst="INVALID";

else

Str_inst=SYMI(instr);

if(opr1

Str_opr1=Empty;

else

Str_opr1=SYMA(opr1);

if(opr2

Str_opr2=Empty;

else

Str_opr2=SYMA(opr2);

}

}

format() 函数检查匹配指令前缀,匹配上了才把数值表示的指令和操作数类型转换成字符串,分别存到三个全局变量 Str_inst, Str_opr1, Str_opr2 中,其实这些字符串都是定义好的,也就是写指针而已。有个特殊处理是在 0x90 指令前缀下,自动将 X 寄存器替换为 Y 寄存器。再来看下主程序中怎么输出反汇编文本的,首先是初始换化几个全局变量,然后调用 decode_instr() 按照操作码分解指令,判断是否成功。如果遇到指令前缀,那么就重新取下一个字节;如果有前缀但指令未被识别,那么调用 decode_instr_special() 进行特殊指令的处理,也就是上面的表中没法表示出来的指令。若解码失败,先输出错误的信息。

for(p=code;p

{

if(Prefix==0)

printf("%5X:\t", base_addr+(p-code));

Str_inst=Empty;

Str_opr1=Empty;

Str_opr2=Empty;

BitOpr=0;

RevOpr=0;

AutoXY=0;

tmp=decode_instr(*p);

if(tmp==0)

{ // prefix set

printf("%02X ", Prefix);

continue;

}

if(tmp>0 && Str_inst==Empty && Prefix)

tmp=decode_instr_special(*p);

if(tmp==-1)

{

if(Prefix==0)

printf(" ");

printf("%02X ", *p);

printf(" ???????? Unknown");

}

下面就是解码成功后的翻译过程了,用 get_extra() 函数从代码数据中提取操作数(立即数、地址等),存放到dat1, dat2两个整型数,供后面用 printf() 输出。至于 printf() 需要的格式字符串,实际上是由解码得到的 Str_opr1, Str_opr2 结果提供的。这里还要特殊处理一下带位操作的指令(BCCM, BCPL, BRES, BSET, BTJF, BTJT这几个),其中的位是编码在操作码当中的,我在 format() 函数中并不把这个位编码作为一个操作数,尽管从汇编语言角度它应该是算一个操作数。

else// OK

{

int nx;

int i;

unsigned int dat1, dat2, arg1, arg2;

char bitpos[]=", #n";

char fmt_str[64];

nx=get_extra(p, &dat1, &dat2);

if(Str_opr1==SYMA(rel))

{

signed char offset=dat1;

dat1=base_addr+(p-code)+nx+1+offset;

}

if(Prefix==0)

printf(" ");

for(i=0;i<1+nx;i++)

printf("%02X ", p[i]);

for(;i<5;i++)

printf(" ");

if(BitOpr)

bitpos[3]="0"+(*p>>1&7);

else

bitpos[0]=0;

if(Str_opr1!=Empty)

{

if(Str_opr2==Empty) // one oprand

{

sprintf(fmt_str, "%s %s%s",Str_inst, Str_opr1, bitpos);

arg1=dat1;

}

else

{

if(RevOpr)

{

sprintf(fmt_str, "%s %s%s, %s",Str_inst, Str_opr2, bitpos, Str_opr1);

if(strchr(Str_opr2,"%"))

{

arg1=dat2;

arg2=dat1;

}

else

arg1=dat1;

}

else

{

sprintf(fmt_str, "%s %s%s, %s",Str_inst, Str_opr1, bitpos, Str_opr2);

if(strchr(Str_opr1,"%"))

{

arg1=dat1;

arg2=dat2;

}

else

arg1=dat2;

}

}

}

else

strcpy(fmt_str, Str_inst);

printf(fmt_str, arg1, arg2);

p+=nx;

}

Prefix=0;

printf("\n");

完整的源程序可点击下方阅读原文下载。贴一个运行的结果:反汇编内容是我写的LED点灯测试程序,不包括中断向量表。

暂时还不能确定指令有没有遗漏,后面边用边检查吧。

本文来源网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。

往期推荐

《嵌入式Linux驱动大全》

关键词:

责任编辑:Rex_09

推荐阅读

燕赵小将 “蓉”耀大运

· 2023-08-09 20:01:08

新亮剑田雨 新亮剑3

· 2023-08-09 19:21:13

关于我们  联系我们  商务合作  诚聘英才  网站地图

Copyright @ 2008-2020 3c.rexun.cn Corporation, All Rights Reserved

热讯网 - 热讯科技网 版权所有 豫ICP备20005723号-6
文章投诉邮箱:2 9 5 9 1 1 5 7 8@qq.com 违法信息举报邮箱:jubao@123777.net.cn

营业执照公示信息