1 #!/usr/local/bin/python 2 ################################################################### 3 # PIC16_disasm.py 4 # V 0.3 5 # 9/99 - Tim Deagan 6 # 7 # Disassemble an intel hex file for a PIC 16Cxx microcontroller. 8 # 9 # asmlist format: {lineaddr:[hexval, 10 # labelname, 11 # mnemonic, 12 # arg_one, 13 # comma, 14 # arg_two, 15 # comment]} 16 # 17 ################################################################### 18 import os,string,sys # import the modules we'll need throughout 19 20 Dbug = 0 # set to 0 to turn off additional debug output 21 code_limit = 0x1fff # upper range in memory for code 22 hexcode = {} # init some global data structures 23 asmlist = {} 24 # setup the register lookup table 25 reglist={ 26 0x00:"IND0", 27 0x01:"TMR0/RTCC/OPTION", 28 0x02:"PCL", 29 0x03:"STATUS", 30 0x04:"FSR", 31 0x05:"PORTA/TRISA", 32 0x06:"PORTB/TRISB", 33 0x08:"EEDATA/EECON1", 34 0x09:"EEADR/EECON2", 35 0x0A:"PCLATH", 36 0x0B:"INTCON"} 37 38 # asmlist format: {lineaddr:[hexval,labelname,mnemonic,arg_one,comma,arg_two,comment]} 39 # setup variables as their indices for readability in some functions 40 hexval = 0 41 labelname = 1 42 mnemonic = 2 43 arg_one = 3 44 comma = 4 45 arg_two = 5 46 comment = 6 47 48 ################################################################### 49 def hexlineparser(line): 50 "Parse an Intel Hex File line into a dictionary" 51 hexlen = line[1:3] # number of data bytes on the line 52 hexaddr = line[3:7] # starting address of data on line 53 hextype = line[7:9] # 00 - data, 01 - eof 54 hexdata = line[9:-2] # data bytes (use words for the 14 bit PIC instructions) 55 hexcrc = line[-2:] # crc of line 56 57 linelen = hexish(hexlen) # decompose line length 58 lineaddr = hexish(hexaddr) # decompose starting address 59 60 if hextype != '01': # if not eof 61 for i in range(0,linelen,2): # grab each data word 62 hexcode[lineaddr + i] = hexdata[i*2:i*2+4] # populate dict. with addr:data 63 64 ################################################################### 65 def hexcodeparser_pass1(i): 66 "Parse a dictionary of machine hex into Assembly language" 67 hexval = hexcode[i][2:4]+hexcode[i][0:2] # flip LSB,MSB to MSB,LSB 68 hinibble = hexish(hexval[0]) & 0x03 # get 2 bits (00xx000000000000) 69 lineaddr = i/2 # make byte addresses into word addresses 70 labelname = "" # initialize values 71 mnemonic = "" 72 arg_one = "" 73 comma = "" 74 arg_two = "" 75 comment = "" 76 77 if lineaddr <= code_limit: # make sure we're parsing within the code space 78 # for comment readability I'll number the 14 relevant bits of the 16 bit word 79 # 0 - 13 with 0 being the least signficant bit 80 if hinibble == 0: # one major group starts with 00 as bits 12,13 81 znibble = hexish(hexval[1]) # get bits 8 - 11 82 de_zero = (hexish(hexval[2]) >> 3) & 0x01 # get bit 7 83 fi_zero = hexish(hexval[2:]) & 0x7f # get bits 0 - 6 84 de_print = ["W","F"] # setup target names 85 86 if znibble == 0: # subgroup of instructions based on bits 8-11 87 if de_zero: # if bit 7 is 1 then 88 mnemonic = "MOVWF" # it's MOVWF 89 arg_one = reglistlookup(fi_zero) # resolve the address 90 else: # bit 7 is 0, so... 91 zero_ops = ["NOP","?","?","SLEEP","?","?DATA?","?", 92 "CLRWDT","RETURN","RETFIE"] 93 # it's one of these instructions. I used question marks since there 94 # are no operands for those indexes and I wanted to use a simple list 95 lb_zero = hexish(hexval[3:]) # check bits 0-3 96 mnemonic = zero_ops[lb_zero] # use result as index to instr. 97 98 elif znibble == 1: # Check next value of bits 8-11 99 zero_ops = ["CLRW","CLRF"] # setup instruction list 100 mnemonic = zero_ops[de_zero] # choose instruction based on bit 7 101 if de_zero: # if bit 7 = 1 102 arg_one = hex(fi_zero) # there is an arg., get its hex value 103 104 elif znibble >= 2: # check next value of bits 8-11 105 zero_ops = ["SUBWF","DECF","IORWF","ANDWF","XORWF", 106 "ADDWF","MOVF","COMF","INCF","DECFSZ", 107 "RRF","RLF","SWAPF","INCFSZ"] # setup instruction list 108 mnemonic = zero_ops[znibble - 2] # use value of bits 8-11 minus 2 as index 109 arg_two = de_print[de_zero] # use bit 7 to get 'W' or 'F' as 2nd arg. 110 comma = "," # put a comma between arguments 111 arg_one = reglistlookup(fi_zero) # resolve the address 112 113 elif hinibble == 1: # check the next value of bits 12-13 114 one_ops = ["BCF","BSF","BTFSC","BTFSS"] # setup the instruction list 115 op_one = (hexish(hexval[1]) >> 2) & 0x03 # turn bits 10-11 into index 116 mnemonic = one_ops[op_one] # get the instruction 117 fi_one = hexish(hexval[2:4]) & 0x7f # bit number to test is bits 7-9 118 arg_one = reglistlookup(fi_one) # resolve the address 119 comma = "," # put a nice comma between args 120 arg_two = hex((hexish(hexval[1:3]) >> 3) & 0x07) # file to test is bits 0-6 121 122 elif hinibble == 2: # check the next value of bits 12-13 123 two_ops = ["CALL","GOTO"] # setup the instruction list 124 op_two = (hexish(hexval[1]) >> 3) & 0x01 # index is bit 11 125 mnemonic = two_ops[op_two] # get the instruction 126 ad_two = hexish(hexval) & 0x7ff # jump address is bits 0-10 127 arg_one = hex(ad_two) # cleanup the jump target 128 129 elif hinibble == 3: # check the next value of bits 12-13 130 # this one is extra funky since there are a buch of 'don't care' bits among the 131 # distinguishing bits. They are all least significant bits, so I use a greater 132 # than or equal statement to find them. I used two lists instead of a dictionary 133 # to skip the xxx.keys(), xxx.sort() and xxx.rev() steps, since I needed them to 134 # to be sequenced in my specific order rather than a sorted order. 135 three_ops = ["ADDLW","SUBLW","XORLW","ANDLW", 136 "IORLW","RETLW","MOVLW"] # setup instruction list 137 three_keys = [14,12,10,9,8,4,0] # setup values to map to instructions 138 op_three = hexish(hexval[1]) # distinguishing bits are bits 8-11 139 arg_one = hex(hexish(hexval[2:])) # literal is bits 0-7 140 for j in three_keys: # walk through the key values 141 if op_three >= j: # if the distinguishing bits >= key 142 mnemonic = three_ops[three_keys.index(j)] # grab the instruction that maps 143 break # jump out of this loop 144 145 asmlist[lineaddr]=[hexval,labelname,mnemonic,arg_one,comma,arg_two,comment] # create dict entry 146 147 ################################################################### 148 def hexcodeparser_pass2(): 149 "Second pass Disassembly parser, replaces CALL and GOTO args with labels" 150 target_hold = {} # init place to hold targets 151 target_count = 0 # start with a 0 target count 152 addrlist = asmlist.keys() # get addresses from the first pass dict. 153 for test_addr in addrlist: # walk the addresses 154 test_list = asmlist[test_addr] # get the details for the test address 155 if test_list[mnemonic] == 'GOTO' or test_list[mnemonic] == 'CALL': # look for jumps 156 test_target = string.atoi(test_list[arg_one],16) # turn the target address into a key 157 target_list = asmlist[test_target] # get the details of the target 158 if not target_hold.has_key(test_target): # if we haven't done this target... 159 target_count = target_count + 1 # inc the target count 160 target_hold[test_target] = target_count # add a target dict entry 161 target_list[labelname] = "TARG_" + str(target_count) # a unique label for target 162 asmlist[test_target] = [target_list[hexval], # rebuild target dict entry 163 target_list[labelname]+":" 164 ] + target_list[mnemonic:] 165 test_list[arg_one] = target_list[labelname] # change the test line's jump to label 166 asmlist[test_addr] = test_list # rebuild the test dict entry 167 if Dbug: print ".", # if debugging, print a timing dot 168 return target_hold # send back the list of targets 169 170 ################################################################### 171 def print_listing(): 172 "Output pretty disassembly and symbols" 173 codebreak = 0 # init some variables 174 idbreak = 0 175 configbreak = 0 176 eeprombreak = 0 177 178 # print listing from hex info 179 addrlist = asmlist.keys() # get address list from dict 180 addrlist.sort() # order it 181 for addr in addrlist: # walk it 182 if addr <= 0x1fff and codebreak == 0: # address in code space? 183 # layout header 184 print "\nASSEMBLY CODE" 185 print "-------------" 186 print "ADDR WORD LABEL OPERAND ARG1,ARG2" 187 print "---- ---- ------- ------- -----------" 188 codebreak = 1 # don't print header again 189 elif addr >= 0x2000 and addr <= 0x2003 and idbreak == 0: # address is code ID? 190 # layout header 191 print "\n\nCODE ID(S)" 192 print "----------" 193 print "ADDR WORD" 194 print "---- ----" 195 idbreak = 1 # don't print header again 196 elif addr == 0x2007 and configbreak == 0: # address is the config word? 197 # layout header 198 print "\n\nCONFIGURATION WORD" 199 print "------------------" 200 cfg = configworddecode(asmlist[addr][hexval]) # decode configuration word 201 wds = cfg.keys() 202 for wd in wds: print cfg[wd] # output configuration data 203 print "\nADDR WORD" 204 print "---- ----" 205 configbreak = 1 # don't print header again 206 elif addr > 0x2007 and eeprombreak == 0: # address is eeprom data? 207 # layout header 208 print "\n\nEEPROM DATA" 209 print "-----------" 210 print "ADDR WORD" 211 print "---- ----" 212 eeprombreak = 1 # don't print header again 213 # Setup some comments 214 if addr == 0x0000: # test for restart vector 215 asmlist[addr][comment] = ";Restart Vector" # setup comment 216 elif addr == 0x0004: # test for interrupt vector 217 asmlist[addr][comment] = ";Interrupt Vector" # setup comment 218 219 # Output the line 220 print "%04X %s %-08s %-06s %s%s%s \t%s" % tuple([addr,] + asmlist[addr]) 221 222 # print symbol table header and info 223 print "\n\nSYMBOL TABLE" 224 print "------------" 225 print "ADDR SYMBOL" 226 print "---- ------" 227 # print labels 228 reportlist = target_hold.keys() # get list of target addresses 229 reportlist.sort() # order them 230 for item in reportlist: # walk them 231 print "%04X %s%-12s" % (item,'TARG_', target_hold[item]) # output address and label 232 # print registers 233 portlist = reglist.keys() # get list of known register labels 234 portlist.sort() # order them 235 for item in portlist: # walk them 236 print "%04X %-17s" % (item,reglist[item]) # output address and label 237 238 print "\n" # end with a blank line 239 240 ################################################################### 241 def hexish(tohex): 242 "Change an ascii hex string to a hexadecimal integer" 243 return string.atoi("0x" + tohex,16) 244 245 ################################################################### 246 def reglistlookup(targetfile): 247 "translate hex to register name if appropriate" 248 if reglist.has_key(targetfile): # determine if targetfile is a known address 249 return reglist[targetfile] # if so, use the nice name(s) for it 250 else: 251 return hex(targetfile) # otherwise use the hex value 252 253 ################################################################### 254 def configworddecode(cfgwd): 255 "translate the config word into list of flags" 256 cfgwdh = hexish(cfgwd) # turn ascii into hex 257 if (cfgwdh >> 4) & 0x01:cfg = "CP=on," # Code Protection 258 else:cfg = "CP=off," 259 if (cfgwdh >> 3) & 0x01: cfg = cfg + "PWRT=on," # Power-up Timer Enable 260 else: cfg = cfg + "PWRT=off," 261 if (cfgwdh >> 2) & 0x01: cfg = cfg + "WDT=on," # Watchdog Timer Enable 262 else: cfg = cfg + "WDT=off," 263 if cfgwdh & 0x03 == 0: cfg = cfg + "OSC=LP" # Oscillator Selection 264 elif cfgwdh & 0x03 == 1: cfg = cfg + "OSC=XT" 265 elif cfgwdh & 0x03 == 2: cfg = cfg + "OSC=HS" 266 elif cfgwdh & 0x03 == 3: cfg = cfg + "OSC=RC" 267 268 return cfg # return the decoded values 269 270 ############################################################### 271 # main entry to the program 272 ############################################################### 273 if __name__ == '__main__': 274 for filename in sys.argv[1:]: # do each passed file 275 print "\n"+filename # output file name 276 file = open(filename, 'r') # open file 277 for line in file.readlines(): # grab each line from the file 278 hexlineparser(line) # decompose into address:data 279 file.close() # close the file 280 281 addrs = hexcode.keys() # grab keys (addr.) from line parser output 282 addrs.sort() # sort them 283 for i in addrs: # walk addresses 284 hexcodeparser_pass1(i) # perform 1stpass disassembly 285 target_hold = hexcodeparser_pass2() # perform 2ndpass disassembly (label jumps) 286 287 print_listing() # output usable code to stdout 288 hexcode = {} # reinitialize storage 289 asmlist = {}