diff options
| author | iximeow <me@iximeow.net> | 2018-07-09 21:53:10 -0700 | 
|---|---|---|
| committer | iximeow <me@iximeow.net> | 2018-07-09 21:53:10 -0700 | 
| commit | bb7bbc04ecb9e4feaecf59d7230f377e4c6fc143 (patch) | |
| tree | dfad417c3037964c68b68b74c59509fc8d6642c0 /source/notes/pic-mcu/pickit2/pk2cmd-stuff/pydare.py | |
| parent | aab989297b2f6b5ee4501ae1da1f4ca681cc6b7e (diff) | |
add pic notes
Diffstat (limited to 'source/notes/pic-mcu/pickit2/pk2cmd-stuff/pydare.py')
| -rw-r--r-- | source/notes/pic-mcu/pickit2/pk2cmd-stuff/pydare.py | 610 | 
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") | 
