#!/usr/bin/env python3 """Minimal but complete MCS-51 (8051) disassembler.""" SFR = { 0x80: 'P0', 0x81: 'SP', 0x82: 'DPL', 0x83: 'DPH', 0x84: 'DPL1', 0x85: 'DPH1', 0x86: 'DPS', 0x87: 'PCON', 0x88: 'TCON', 0x89: 'TMOD', 0x8A: 'TL0', 0x8B: 'TL1', 0x8C: 'TH0', 0x8D: 'TH1', 0x8E: 'CKCON', 0x90: 'P1', 0x91: 'TMR3CN', 0x98: 'SCON0', 0x99: 'SBUF0', 0xA0: 'P2', 0xA8: 'IE', 0xB0: 'P3', 0xB8: 'IP', 0xC0: 'SMB0CN', 0xC8: 'TMR2CN', 0xD0: 'PSW', 0xD8: 'WDTCN', 0xE0: 'ACC', 0xE8: 'EIE1', 0xF0: 'B', 0xF8: 'EIP1', } BITSFR = {0xD0: 'PSW', 0xD8: 'WDTCN', 0xE0: 'ACC', 0xF0: 'B', 0x80: 'P0', 0x88: 'TCON', 0x90: 'P1', 0x98: 'SCON0', 0xA0: 'P2', 0xA8: 'IE', 0xB0: 'P3', 0xB8: 'IP', 0xC0: 'SMB0CN', 0xC8: 'TMR2CN'} def d(a): return SFR.get(a, f'0x{a:02X}') def bit(a): base = a & 0xF8 if a >= 0x80 else 0x20 + (a >> 3) if a >= 0x80: return f'{BITSFR.get(base, f"0x{base:02X}")}.{a & 7}' return f'0x{base:02X}.{a & 7}' # name, length, operand formatter def _(n, l, f=None): return (n, l, f) def build(): t = {} simple = { 0x00: 'NOP', 0x03: 'RR A', 0x04: 'INC A', 0x06: 'INC @R0', 0x07: 'INC @R1', 0x13: 'RRC A', 0x14: 'DEC A', 0x16: 'DEC @R0', 0x17: 'DEC @R1', 0x22: 'RET', 0x23: 'RL A', 0x26: 'ADD A,@R0', 0x27: 'ADD A,@R1', 0x32: 'RETI', 0x33: 'RLC A', 0x36: 'ADDC A,@R0', 0x37: 'ADDC A,@R1', 0x46: 'ORL A,@R0', 0x47: 'ORL A,@R1', 0x56: 'ANL A,@R0', 0x57: 'ANL A,@R1', 0x66: 'XRL A,@R0', 0x67: 'XRL A,@R1', 0x73: 'JMP @A+DPTR', 0x83: 'MOVC A,@A+PC', 0x84: 'DIV AB', 0x93: 'MOVC A,@A+DPTR', 0x96: 'SUBB A,@R0', 0x97: 'SUBB A,@R1', 0xA3: 'INC DPTR', 0xA4: 'MUL AB', 0xA5: 'DB 0A5h', 0xB3: 'CPL C', 0xC3: 'CLR C', 0xC4: 'SWAP A', 0xC6: 'XCH A,@R0', 0xC7: 'XCH A,@R1', 0xD3: 'SETB C', 0xD4: 'DA A', 0xD6: 'XCHD A,@R0', 0xD7: 'XCHD A,@R1', 0xE0: 'MOVX A,@DPTR', 0xE2: 'MOVX A,@R0', 0xE3: 'MOVX A,@R1', 0xE4: 'CLR A', 0xE6: 'MOV A,@R0', 0xE7: 'MOV A,@R1', 0xF0: 'MOVX @DPTR,A', 0xF2: 'MOVX @R0,A', 0xF3: 'MOVX @R1,A', 0xF4: 'CPL A', 0xF6: 'MOV @R0,A', 0xF7: 'MOV @R1,A', } for k, v in simple.items(): t[k] = (v, 1, None) # register-form groups: base opcode -> template with {r} for base, tmpl in ((0x08, 'INC R{r}'), (0x18, 'DEC R{r}'), (0x28, 'ADD A,R{r}'), (0x38, 'ADDC A,R{r}'), (0x48, 'ORL A,R{r}'), (0x58, 'ANL A,R{r}'), (0x68, 'XRL A,R{r}'), (0x98, 'SUBB A,R{r}'), (0xC8, 'XCH A,R{r}'), (0xE8, 'MOV A,R{r}'), (0xF8, 'MOV R{r},A')): for r in range(8): t[base + r] = (tmpl.format(r=r), 1, None) for r in range(8): t[0x78 + r] = (f'MOV R{r},#{{i}}', 2, 'imm') t[0x88 + r] = (f'MOV {{d}},R{r}', 2, 'dir') t[0xA8 + r] = (f'MOV R{r},{{d}}', 2, 'dir') t[0xB8 + r] = (f'CJNE R{r},#{{i}},{{t}}', 3, 'immrel') t[0xD8 + r] = (f'DJNZ R{r},{{t}}', 2, 'rel') two_dir = {0x05: 'INC {d}', 0x15: 'DEC {d}', 0x25: 'ADD A,{d}', 0x35: 'ADDC A,{d}', 0x42: 'ORL {d},A', 0x45: 'ORL A,{d}', 0x52: 'ANL {d},A', 0x55: 'ANL A,{d}', 0x62: 'XRL {d},A', 0x65: 'XRL A,{d}', 0x86: 'MOV {d},@R0', 0x87: 'MOV {d},@R1', 0x95: 'SUBB A,{d}', 0xA6: 'MOV @R0,{d}', 0xA7: 'MOV @R1,{d}', 0xC0: 'PUSH {d}', 0xC5: 'XCH A,{d}', 0xD0: 'POP {d}', 0xE5: 'MOV A,{d}', 0xF5: 'MOV {d},A'} for k, v in two_dir.items(): t[k] = (v, 2, 'dir') two_imm = {0x24: 'ADD A,#{i}', 0x34: 'ADDC A,#{i}', 0x44: 'ORL A,#{i}', 0x54: 'ANL A,#{i}', 0x64: 'XRL A,#{i}', 0x74: 'MOV A,#{i}', 0x76: 'MOV @R0,#{i}', 0x77: 'MOV @R1,#{i}', 0x94: 'SUBB A,#{i}'} for k, v in two_imm.items(): t[k] = (v, 2, 'imm') two_bit = {0x72: 'ORL C,{b}', 0x82: 'ANL C,{b}', 0x92: 'MOV {b},C', 0xA0: 'ORL C,/{b}', 0xA2: 'MOV C,{b}', 0xB0: 'ANL C,/{b}', 0xB2: 'CPL {b}', 0xC2: 'CLR {b}', 0xD2: 'SETB {b}'} for k, v in two_bit.items(): t[k] = (v, 2, 'bitop') two_rel = {0x40: 'JC {t}', 0x50: 'JNC {t}', 0x60: 'JZ {t}', 0x70: 'JNZ {t}', 0x80: 'SJMP {t}'} for k, v in two_rel.items(): t[k] = (v, 2, 'rel') t[0x10] = ('JBC {b},{t}', 3, 'bitrel') t[0x20] = ('JB {b},{t}', 3, 'bitrel') t[0x30] = ('JNB {b},{t}', 3, 'bitrel') t[0x02] = ('LJMP {a}', 3, 'addr16') t[0x12] = ('LCALL {a}', 3, 'addr16') t[0x90] = ('MOV DPTR,#{a}', 3, 'addr16') t[0x43] = ('ORL {d},#{i}', 3, 'dirimm') t[0x53] = ('ANL {d},#{i}', 3, 'dirimm') t[0x63] = ('XRL {d},#{i}', 3, 'dirimm') t[0x75] = ('MOV {d},#{i}', 3, 'dirimm') t[0x85] = ('MOV {d2},{d1}', 3, 'dirdir') t[0xB4] = ('CJNE A,#{i},{t}', 3, 'immrel') t[0xB5] = ('CJNE A,{d},{t}', 3, 'dirrel') t[0xB6] = ('CJNE @R0,#{i},{t}', 3, 'immrel') t[0xB7] = ('CJNE @R1,#{i},{t}', 3, 'immrel') t[0xD5] = ('DJNZ {d},{t}', 3, 'dirrel') for hi in range(8): t[(hi << 5) | 0x01] = ('AJMP {a}', 2, 'addr11') t[(hi << 5) | 0x11] = ('ACALL {a}', 2, 'addr11') return t TAB = build() def disasm_one(mem, pc): """Return (text, length, target_or_None, is_call, ends_block).""" op = mem[pc] ent = TAB.get(op) if ent is None: return (f'DB 0{op:02X}h', 1, None, False, False) name, ln, kind = ent b = mem[pc:pc + ln] if len(b) < ln: return (f'DB 0{op:02X}h', 1, None, False, False) nxt = pc + ln tgt = None if kind == 'imm': s = name.format(i=f'0{b[1]:02X}h') elif kind == 'dir': s = name.format(d=d(b[1])) elif kind == 'bitop': s = name.format(b=bit(b[1])) elif kind == 'rel': tgt = (nxt + ((b[1] ^ 0x80) - 0x80)) & 0xFFFF s = name.format(t=f'0x{tgt:04X}') elif kind == 'bitrel': tgt = (nxt + ((b[2] ^ 0x80) - 0x80)) & 0xFFFF s = name.format(b=bit(b[1]), t=f'0x{tgt:04X}') elif kind == 'immrel': tgt = (nxt + ((b[2] ^ 0x80) - 0x80)) & 0xFFFF s = name.format(i=f'0{b[1]:02X}h', t=f'0x{tgt:04X}') elif kind == 'dirrel': tgt = (nxt + ((b[2] ^ 0x80) - 0x80)) & 0xFFFF s = name.format(d=d(b[1]), t=f'0x{tgt:04X}') elif kind == 'dirimm': s = name.format(d=d(b[1]), i=f'0{b[2]:02X}h') elif kind == 'dirdir': s = name.format(d1=d(b[1]), d2=d(b[2])) elif kind == 'addr16': a = (b[1] << 8) | b[2] s = name.format(a=f'0x{a:04X}') if op != 0x90: tgt = a elif kind == 'addr11': a = (nxt & 0xF800) | ((op & 0xE0) << 3) | b[1] tgt = a s = name.format(a=f'0x{a:04X}') else: s = name is_call = op in (0x12,) or (op & 0x1F) == 0x11 ends = op in (0x02, 0x22, 0x32, 0x80, 0x73) or (op & 0x1F) == 0x01 return (s, ln, tgt, is_call, ends) class Image: def __init__(self, path, base): self.data = open(path, 'rb').read() self.base = base def __getitem__(self, k): if isinstance(k, slice): return self.data[k.start - self.base:k.stop - self.base] return self.data[k - self.base] def __contains__(self, a): return self.base <= a < self.base + len(self.data) def listing(img, start, count=40, labels=None): out, pc = [], start for _ in range(count): if pc not in img: break s, ln, tgt, _c, ends = disasm_one(img, pc) raw = bytes(img[pc:pc + ln]).hex(' ') lab = f'{labels.get(pc,""):>12} ' if labels else '' out.append(f'{lab}{pc:04X}: {raw:<9} {s}') pc += ln return '\n'.join(out)