summaryrefslogtreecommitdiff
path: root/source/notes/pic-mcu/pickit2/pk2cmd-stuff/pydare.py
diff options
context:
space:
mode:
Diffstat (limited to 'source/notes/pic-mcu/pickit2/pk2cmd-stuff/pydare.py')
-rw-r--r--source/notes/pic-mcu/pickit2/pk2cmd-stuff/pydare.py610
1 files changed, 610 insertions, 0 deletions
diff --git a/source/notes/pic-mcu/pickit2/pk2cmd-stuff/pydare.py b/source/notes/pic-mcu/pickit2/pk2cmd-stuff/pydare.py
new file mode 100644
index 0000000..f24f69f
--- /dev/null
+++ b/source/notes/pic-mcu/pickit2/pk2cmd-stuff/pydare.py
@@ -0,0 +1,610 @@
+import json
+import importlib
+import traceback
+import sys
+import os
+
+import pic16
+import pic18
+import pic24
+
+running = True
+DEBUG = False
+
+ANSI_BLUE = "\x1b[34m"
+ANSI_YELLOW = "\x1b[33m"
+ANSI_DARK_GREEN = "\x1b[32m"
+ANSI_GREEN = "\x1b[1m\x1b[32m"
+ANSI_RED = "\x1b[31m"
+ANSI_RESET = "\x1b[0m"
+
+COLOR_REGISTER = ANSI_YELLOW
+COLOR_FUNCTION = ANSI_GREEN
+COLOR_LABEL = ANSI_DARK_GREEN
+COLOR_ADDRESS = ANSI_DARK_GREEN
+
+state = {
+ 'running': True,
+ 'dirty': False
+}
+
+def parse_parts(msg):
+ parts = []
+ i = 0
+ wordstart = 0
+ word = ""
+ stripped = msg.strip()
+
+ in_quotes = False
+ escaping = False
+
+ while i < len(stripped):
+ if escaping:
+ # escaping means even spaces aren't the end of the word, so
+ word = word + stripped[i]
+ escaping = False
+ else:
+ if stripped[i] == ' ':
+ if in_quotes:
+ # this isn't actually the end of a word
+ word = word + stripped[i]
+ else:
+ parts.append(word)
+ word = ""
+ elif stripped[i] == '\\':
+ escaping = True
+ elif stripped[i] == '"':
+ in_quotes = not in_quotes
+ else:
+ word = word + stripped[i]
+
+ i = i + 1
+
+ parts.append(word)
+ word = ""
+
+ # todo: signal unmatched quotes
+
+ return parts
+
+def readnum(s):
+ if s.startswith("0x"):
+ return int(s[2:], 16)
+ else:
+ return int(s)
+
+def parse_cmd(cmd):
+ cmd_parts = parse_parts(cmd)
+ smol_to_big = {
+ "o": "open",
+ "c": "define-comment",
+ "c+": "define-comment",
+ "q": "quit",
+ "d": "disassemble",
+ "h": "help"
+ }
+
+ result = {}
+
+ if len(cmd_parts) > 0:
+ result['type'] = cmd_parts[0]
+ if result['type'] in smol_to_big:
+ result['type'] = smol_to_big[result['type']]
+
+ result['params'] = cmd_parts[1:]
+ result['raw_text'] = cmd
+
+ if len(cmd_parts) > 1 and cmd_parts[-1][0] == '@':
+ result['where'] = resolve_addr(cmd_parts[-1][1:], state)
+ elif len(cmd_parts) > 2 and cmd_parts[-2] == '@':
+ result['where'] = resolve_addr(cmd_parts[-1], state)
+
+ if 'where' in result and result['where'] is None:
+ result['invalid'] = True
+
+ return result
+
+def resolve_addr(where, state):
+ # where may be the name of a label or function
+ if where in state['notes']['functions-reverse']:
+ return state['notes']['functions-reverse'][where]
+ elif where in state['notes']['labels-reverse']:
+ return state['notes']['labels-reverse'][where]
+ else:
+ # if it's not there, it better be a number...
+ try:
+ return readnum(where)
+ except ValueError:
+ print("{} is not a known function or label.".format(where))
+
+def do_help(cmd, state):
+ print("haha, help")
+
+def do_open(cmd, state):
+ f = open(cmd['params'][0])
+ buf = f.read(6)
+ f.close()
+ if buf == ":02000":
+ return do_openhex(cmd, state)
+ else:
+ return do_openbin(cmd, state)
+
+def do_openbin(cmd, state):
+ return inneropen(cmd, state, lambda x: { 0: x.read() })
+
+def do_openhex(cmd, state):
+ return inneropen(cmd, state, lambda x: read_hex(x))
+
+def read_hex(f):
+ lines = f.readlines()
+ regions = { 0: [] }
+ curr_region = regions[0]
+
+ for line in lines:
+ if not line.startswith(':'):
+ continue
+# raise Exception("invalid hex line, needs to start with a ':', but was: " + line)
+ bytecount = int(line[1:3], 16)
+ addr = int(line[3:7], 16)
+ rec_type = int(line[7:9], 16)
+ data_end = bytecount * 2 + 9
+ data = line[9:data_end]
+ # ignoring checksum because lazy
+
+ if rec_type == 4:
+ ext_linear_addr = int(data, 16)
+ if ext_linear_addr not in regions:
+ regions[ext_linear_addr] = []
+
+ curr_region = regions[ext_linear_addr]
+ elif rec_type == 0:
+ for i in range(bytecount):
+ if len(curr_region) <= addr + i:
+ curr_region.extend([0] * (1 + addr + i - len(curr_region)))
+ curr_region[addr + i] = int(data[i*2:i*2 + 2], 16)
+ elif rec_type == 1:
+ if DEBUG:
+ print("Read HEX file ({} regions)".format(len(regions)))
+ return regions
+ else:
+ raise Exception("Unsupported record type: " + str(rec_type))
+
+def do_goto(cmd, state):
+ try:
+ dest = int(cmd['params'][0])
+ except ValueError:
+ # parse error, might be a function or label!
+ # labels first...
+ name = cmd['params'][0]
+ if name in state['notes']['labels-reverse']:
+ dest = state['notes']['labels-reverse'][name]
+ if name in state['notes']['functions-reverse']:
+ dest = state['notes']['functions-reverse'][name]
+ state['cursor'] = int(cmd['params'][0])
+
+def do_goto_bank(cmd, state):
+ param = cmd['params'][0]
+ if not param in state['all-regions']:
+ state['all-regions'][param] = []
+
+ state['data'] = state['all-regions'][param]
+
+def inneropen(cmd, state, readfn):
+ newfile = cmd['params'][0]
+ if os.path.isfile(newfile):
+ newdata = None
+ try:
+ f = open(newfile)
+ newdata = readfn(f)
+ f.close()
+ except Exception as e:
+ print("Failed to open new file: {}".format(newfile))
+ print("Got: {}".format(e))
+ print(traceback.format_exc())
+ return
+ state['notes'] = {
+ 'comments': {},
+ 'functions': {},
+ 'functions-reverse': {},
+ 'labels': {},
+ 'labels-reverse': {}
+ }
+ state['cursor'] = 0
+ state['file'] = newfile
+ state['all-regions'] = newdata
+ state['selected-region'] = newdata.keys()[0]
+ state['data'] = newdata[state['selected-region']]
+ elif os.path.isdir(newfile):
+ print("Cannot open {}, it is a directory".format(newfile))
+ else:
+ print("File {} does not exist".format(newfile))
+
+ if 'arch-name' not in state or state['arch-name'] is None:
+ # default arch to the best cpu, pic16
+ do_setarch({ 'params': ['pic16'] }, state)
+
+ if DEBUG:
+ print("Opened {} as {}".format(newfile, state['arch-name']))
+
+def do_comment(cmd, state):
+ state['notes']['comments'][cmd['where']] = cmd['params'][0]
+ state['dirty'] = True
+
+def new_function(name):
+ return {
+ "name": name,
+ "params": None,
+ "returns": None
+ }
+
+def do_undefine_function(cmd, state):
+ name = cmd['params'][0]
+ if cmd['params'][0] in state['notes']['functions-reverse']:
+ fnaddr = state['notes']['functions-reverse'][cmd['params'][0]]
+ del state['notes']['functions'][fnaddr]
+ del state['notes']['functions-reverse'][name]
+ return
+ else:
+ print("Function {} is not defined".format(name))
+
+def do_define_function(cmd, state):
+ if cmd['params'][0] in state['notes']['functions-reverse']:
+ fnaddr = state['notes']['functions-reverse'][cmd['params'][0]]
+ if fnaddr != cmd['where']:
+ print("Function {} is already defined at {}".format(cmd['params'][0], fnaddr))
+ return
+ else:
+ newname = cmd['params'][0]
+ fn = state['notes']['functions'][cmd['where']]
+ del state['notes']['functions-reverse'][fn['name']]
+ state['notes']['functions-reverse'][newname] = fn
+ fn['name'] = newname
+ else:
+ state['notes']['functions'][cmd['where']] = new_function(cmd['params'][0])
+ state['notes']['functions-reverse'][cmd['params'][0]] = cmd['where']
+ state['dirty'] = True
+
+def do_define_label(cmd, state):
+ state['notes']['labels'][cmd['where']] = cmd['params'][0]
+ state['notes']['labels-reverse'][cmd['params'][0]] = cmd['where']
+ state['dirty'] = True
+
+def colorize(string, color):
+ return "{}{}{}".format(
+ color,
+ string,
+ ANSI_RESET
+ )
+
+def do_disassemble(cmd, state):
+ if 'data' not in state:
+ print("No file currently open")
+ return
+ if 'cursor' not in state:
+ print("No cursor into the file (this is a bug - a file must be open?")
+ return
+
+ if 'where' in cmd:
+ where = cmd['where']
+ else:
+ where = state['cursor']
+
+ if len(cmd['params']) > 0:
+ count = int(cmd['params'][0])
+ else:
+ count = 1
+
+ arch = state['arch']
+
+ try:
+ disassembled = 0
+ while disassembled < count:
+ prewhere = where
+ (where, instr) = arch.disassemble(state['data'], where)
+ if 'ops' in instr:
+ newops = instr['ops']
+ for i, op in enumerate(instr['ops']):
+ if isinstance(op, dict) and 'type' in op:
+ note = None
+ if op['type'] == "absolutedest":
+ if op['value'] in state['notes']['functions']:
+ note = state['notes']['functions'][op['value']]['name']
+ note = colorize(note, COLOR_FUNCTION)
+ elif op['value'] in state['notes']['labels']:
+ note = state['notes']['labels'][op['value']]
+ note = colorize(note, COLOR_LABEL)
+ else:
+ note = colorize(hex(op['value']), COLOR_ADDRESS)
+ elif op['type'] == "relpostdest":
+ # if there's a thing there replace with it
+ newops[i] = "relpostdest"
+ ea = prewhere + instr['length'] * 2 + op['value']
+ if ea in state['notes']['functions']:
+ note = state['notes']['functions'][ea]['name']
+ note = colorize(note, COLOR_FUNCTION)
+ elif ea in state['notes']['labels']:
+ note = state['notes']['labels'][ea]
+ note = colorize(note, COLOR_LABEL)
+ else:
+ note = "0x{:x} (ip+2{}{})".format(
+ ea,
+ "+" if op['value'] >= 0 else "",
+ hex(op['value'])
+ )
+ note = colorize(note, COLOR_ADDRESS)
+ elif op['type'] == "relpredest":
+ pass
+ elif op['type'] == "register" or op['type'] == "banked-register":
+ nicename = arch.reg_name(op['value'])
+ if nicename is None:
+ if isinstance(op['value'], str):
+ note = op['value']
+ else:
+ note = hex(op['value'])
+ else:
+ note = nicename
+
+ note = colorize(note, COLOR_REGISTER)
+
+ if not note is None:
+ newops[i] = str(note)
+
+ instr['ops'] = newops
+
+ prefix = None
+ if prewhere in state['notes']['functions']:
+ fn = state['notes']['functions'][prewhere]
+ prefix = colorize(
+ "{:08x}> start of {}\n".format(prewhere, fn['name']),
+ COLOR_FUNCTION
+ )
+ elif prewhere in state['notes']['labels']:
+ label = state['notes']['labels'][prewhere]
+ prefix = colorize(
+ "{:08x}: {}\n".format(prewhere, label),
+ COLOR_LABEL
+ )
+ instrstring = arch.render(instr)
+ to_show = "{:08x}: {}".format(prewhere, instrstring)
+ if prewhere in state['notes']['comments']:
+ to_show = "{: <35} {}; {}{}".format(
+ to_show,
+ ANSI_BLUE,
+ state['notes']['comments'][prewhere],
+ ANSI_RESET
+ )
+ if not prefix is None:
+ to_show = prefix + to_show
+ print(to_show)
+ disassembled = disassembled + 1
+ except Exception as e:
+ print("Exception while disassembling: {}".format(e))
+ print(traceback.format_exc())
+
+def do_setarch(cmd, state):
+ try:
+ loaded_arch = importlib.import_module(cmd['params'][0])
+ except ImportError:
+ print("Cannot find module for arch '{}'".format(cmd['params'][0]))
+ except Exception as e:
+ print("General error loading '{}': {}".format(cmd['params'][0], e))
+
+ state['arch-name'] = cmd['params'][0]
+ state['arch'] = loaded_arch
+
+def dict_from_file(path):
+ f = open(path)
+ result = json.loads(f.read())
+ f.close()
+ return result
+
+def dict_to_file(path, data):
+ f = open(path, 'w')
+ f.write(json.dumps(data, sort_keys=True, indent=2))
+ f.close()
+
+# so json.dumps turns numeric keys into strings.
+# this breaks some dictionaries.
+def fix_keys(obj):
+ redo = []
+ for k in obj:
+ try:
+ knum = int(k)
+ redo.append(k)
+ except ValueError:
+ pass
+
+ for k in redo:
+ knum = int(k)
+ obj[knum] = obj[k]
+ del obj[k]
+
+def do_loaddb(cmd, state):
+ if len(cmd['params']) == 0:
+ if 'file' in state:
+ dbpath = state['file'] + '.nrt'
+ else:
+ print("No db path provided nor file loaded - I don't know what to load!")
+ return
+ else:
+ dbpath = cmd['params'][0]
+ if os.path.isdir(dbpath) and os.path.isdir(dbpath + '/.git'):
+ if DEBUG:
+ print("Loading {} ...".format(dbpath))
+ try:
+ dbroot = dict_from_file(dbpath + '/root.json')
+ do_setarch({ "params": [dbroot['arch-name']] }, state)
+ state['notes']['comments'] = dict_from_file(dbpath + '/comments.json')
+ fix_keys(state['notes']['comments'])
+ state['notes']['functions'] = dict_from_file(dbpath + '/functions.json')
+ state['notes']['functions-reverse'] = dict_from_file(dbpath + '/functions-reverse.json')
+ fix_keys(state['notes']['functions'])
+ state['notes']['labels'] = dict_from_file(dbpath + '/labels.json')
+ state['notes']['labels-reverse'] = dict_from_file(dbpath + '/labels-reverse.json')
+ fix_keys(state['notes']['labels'])
+ state['default-dbpath'] = dbpath
+# check db file path matches current file path?
+ if DEBUG:
+ print("wow you're really using this, huh?")
+ except Exception as e:
+ print("Error loading db: {}".format(e))
+ elif os.path.isdir(dbpath):
+ print("Cannot load {}, there is no repository there".format(dbpath))
+ elif os.path.isfile(dbpath):
+ print("Cannot load {}, it is a file".format(dbpath))
+ else:
+ print("Cannot load {}, it does not exist".format(dbpath))
+
+def do_savedb(cmd, state):
+ if len(cmd['params']) > 0:
+ dbpath = cmd['params'][0]
+ elif 'default-dbpath' in state:
+ dbpath = state['default-dbpath']
+ else:
+ dbpath = state['file'] + '.nrt'
+
+ if os.path.isfile(dbpath):
+ print("File is present, but should be a directory. Cannot save.")
+ return
+ elif os.path.isdir(dbpath):
+ if not os.path.isdir(dbpath + '/.git'):
+ print("dbpath is a directory, but there is no git repo there. Opting to not save.")
+ return
+ else:
+ # dbpath exists, and there's a git repo there, we can proceed
+ pass
+ else:
+ # none of the directories exist, so we can start fresh
+ os.mkdir(dbpath)
+ # TODO: pray the input filename doesn't have a ' in it i guess
+ os.system('cd \'{}\' && git init'.format(dbpath))
+
+ to_save = {}
+ to_save['arch-name'] = state['arch-name']
+ dict_to_file(dbpath + '/root.json', to_save)
+ dict_to_file(dbpath + '/comments.json', state['notes']['comments'])
+ dict_to_file(dbpath + '/functions.json', state['notes']['functions'])
+ dict_to_file(dbpath + '/functions-reverse.json', state['notes']['functions-reverse'])
+ dict_to_file(dbpath + '/labels.json', state['notes']['labels'])
+ dict_to_file(dbpath + '/labels-reverse.json', state['notes']['labels-reverse'])
+ os.system('cd \'{}\' && git add . && git commit -m "automatic save"'.format(dbpath))
+ state['dirty'] = False
+
+def do_quit(cmd, state):
+ # prompt before saving
+ if not 'dirty' in state or state['dirty']:
+ do_savedb({ "params": [] }, state)
+ state['running'] = False
+
+def do_sh(cmd, state):
+ if len(cmd['params']) == 1:
+ os.system(cmd['params'][0])
+ else:
+ print("sh expects exactly one argument (may be a string)")
+
+def do_info(cmd, state):
+ if 'file' in state:
+ print("File: {}".format(state['file']))
+ print("Regions:")
+ keys = state['all-regions'].keys()
+ keys.sort()
+ for key in keys:
+ print(" {}{}: {} bytes".format(
+ '*' if key == state['selected-region'] else ' ',
+ key,
+ hex(len(state['all-regions'][key]))
+ ))
+
+def readnum(string):
+ if string.startswith('0x'):
+ return int(string, 16)
+ else:
+ return int(string)
+
+def do_list_comments(cmd, state):
+ print("Comments:")
+ for l in state['notes']['comments']:
+ print("{}: 0x{:x}".format(state['notes']['comments'][l], l))
+
+def do_list_functions(cmd, state):
+ print("Functions:")
+ for l in state['notes']['functions']:
+ print("{}: 0x{:x}".format(state['notes']['functions'][l]['name'], l))
+
+def do_list_labels(cmd, state):
+ print("Labels:")
+ for l in state['notes']['labels']:
+ print("{}: 0x{:x}".format(state['notes']['labels'][l], l))
+
+def do_hexprint(cmd, state):
+ count = readnum(cmd['params'][0])
+ start = cmd['where']
+ idx = start
+
+ while count > idx - start:
+ sys.stdout.write("{:08x}: ".format(idx))
+ for i in range(16):
+ if count <= idx - start:
+ break
+ sys.stdout.write("{:02x} ".format(state['data'][idx]),)
+ idx = idx + 1
+ sys.stdout.write('\n')
+
+cmdmap = {
+ "px": do_hexprint,
+ "define-comment": do_comment,
+ "define-function": do_define_function,
+ "undefine-function": do_undefine_function,
+ "define-label": do_define_label,
+ "list-comments": do_list_comments,
+ "list-functions": do_list_functions,
+ "list-labels": do_list_labels,
+ "open": do_open,
+ "openhex": do_openhex,
+ "arch": do_setarch,
+ "disassemble": do_disassemble,
+ "loaddb": do_loaddb,
+ "savedb": do_savedb,
+ "quit": do_quit,
+ "help": do_help,
+ "info": do_info,
+ "goto": do_goto,
+ "goto-bank": do_goto_bank,
+ "sh": do_sh
+}
+
+def do_cmd_thing(cmd, state):
+ if 'invalid' in cmd:
+ pass # can't do anything with it.
+ elif cmd['type'] in cmdmap:
+ cmdmap[cmd['type']](cmd, state)
+ else:
+ print("I don't recognize the command `{}`".format(cmd['raw_text']))
+
+for i, arg in enumerate(sys.argv):
+ # just the path of this script
+ # i bet that only applies because i'm running as
+ # `python pydare.py ...`
+ if i == 0:
+ continue
+ do_cmd_thing(parse_cmd(arg), state)
+
+# if len(sys.argv) > 1:
+# do_open({"params": [sys.argv[1]]}, state)
+# if len(sys.argv) > 2:
+# do_setarch({"params": [sys.argv[2]]}, state)
+
+import readline
+readline.parse_and_bind("")
+if os.path.isfile(".pydare_history"):
+ readline.read_history_file(".pydare_history")
+
+while state['running']:
+ try:
+ do_cmd_thing(parse_cmd(raw_input("> ")), state)
+ except Exception as e:
+ print("Unhandled exception: {}".format(e))
+ print(traceback.format_exc())
+
+if not os.path.isdir(".pydare_history"):
+ readline.write_history_file(".pydare_history")