diff options
| author | s-ol <s+removethis@s-ol.nu> | 2021-05-22 14:24:21 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2021-05-22 14:24:21 +0000 |
| commit | 79a5f333d7ccb5f581108d81637628287788550d (patch) | |
| tree | f09b43e833e500f4298665691201dfb02fea38e3 | |
| parent | more error reporting in format.py (diff) | |
| download | subv-79a5f333d7ccb5f581108d81637628287788550d.tar.gz subv-79a5f333d7ccb5f581108d81637628287788550d.zip | |
support non-byte aligned data lines
| -rw-r--r-- | bits.py | 8 | ||||
| -rwxr-xr-x | elf.py | 6 | ||||
| -rwxr-xr-x | format.py | 9 | ||||
| -rwxr-xr-x | pack.py | 35 | ||||
| -rw-r--r-- | subv.py | 24 | ||||
| -rwxr-xr-x | survey.py | 43 |
6 files changed, 90 insertions, 35 deletions
@@ -1,3 +1,5 @@ +empty = (0, 0) + def u(num, bits): """ parse an unsigned integer into a bitfield. @@ -83,7 +85,7 @@ def concat(*parts): size += psize return (val, size) -def slice(bits, hi, lo): +def slice(bits, hi, lo, allow_empty=False): """ slice a bitfield given hi and lo bit indices. >>> slice((0x7f, 8), 7, 4) @@ -105,6 +107,8 @@ def slice(bits, hi, lo): Traceback (most recent call last): ... ValueError: cant slice reverse range + >>> slice((0xf, 4), 2, 3, allow_empty=True) + (0, 0) >>> slice((0xf, 4), 3, -1) Traceback (most recent call last): ... @@ -113,6 +117,8 @@ def slice(bits, hi, lo): (val, size) = bits if hi < lo: + if allow_empty: + return empty raise ValueError("cant slice reverse range") elif lo < 0 or hi >= size: raise ValueError("slice [{}:{}] out of range ({} bit value)".format(hi, lo, size)) @@ -90,13 +90,13 @@ if __name__ == "__main__": (name, addr) = line['segment'] segment = { 'name': name, 'addr': addr, 'content': [] } segments.append(segment) - elif line['type'] == 'instr': + elif line['type'] == 'data': if segment == None: raise ValueError("label or code outside of segment!") - segment['content'] += line['instr'] + segment['content'] += line['data'] else: - raise ValueError("elf input should contain only segments and instructions!") + raise ValueError("elf input should contain only segments and data!") cursor = 0 @@ -283,7 +283,6 @@ instr_map = { } def format(iter): - segment = None for line_no, raw_line in enumerate(iter, start=1): try: line = subv.parse(raw_line) @@ -296,7 +295,7 @@ parsed as {} raise Exception(message) from e try: - if segment == 'code' and line['type'] == 'instr': + if line['type'] == 'instr': op = line['instr'][0] assert len(op) == 2, 'instruction without op label: {}'.format(op) @@ -308,11 +307,11 @@ parsed as {} raise ValueError("opcode {} doesn't match label {} (expected {})" .format(op, label, expected)) - line['instr'] = formatter(line['instr']) + formatted = formatter(line['instr']) + formatted[0] = (*formatted[0], label) + line['instr'] = formatted yield subv.format(line) else: - if line['type'] == 'segment': - segment = line['segment'][0] yield line['raw'] except AssertionError as e: message = ''' @@ -58,25 +58,48 @@ def byteify(word): out = [] for i in range(0, size, 8): - byte = bits.slice(word, i+7, i) + byte = bits.slice(word, i+7, i) out.append(byte[:1]) return out def pack(iter): segment = None + + out = [] + buf = bits.empty + + def flush_bytes(): + tmp = out[:] + out.clear() + return { + 'type': 'data', + 'data': tmp, + 'comment': None, + } + for line in iter: line = subv.parse(line) - if line['type'] == 'instr': - fields = [bits.from_part(p) for p in line['instr']] - total = bits.concat(*fields) + if line['type'] == 'data': + for part in line['data']: + buf = bits.concat(buf, bits.from_part(part)) + while buf[1] >= 8: + byte = bits.slice(buf, 7, 0) + buf = bits.slice(buf, buf[1] - 1, 8, allow_empty=True) + out.append(byte[:1]) - line['instr'] = byteify(total) - yield subv.format(line) + if len(out) == 4: + yield subv.format(flush_bytes()) else: + assert buf[1] == 0, "segment '{}' end isn't byte-aligned".format(segment) + if out: + yield subv.format(flush_bytes()) + if line['type'] == 'segment': segment = line['segment'][0] yield line['raw'] + assert buf[1] == 0, "segment '{}' end isn't byte-aligned".format(segment) + if __name__ == '__main__': import sys for line in pack(sys.stdin): @@ -1,6 +1,7 @@ import re white = re.compile(r'[ \t\.\n]+') hex = re.compile(r'^\-?(0x)?[0-9a-f]+$') +num = re.compile(r'^\d+$') slice_re = re.compile(r'^([^\[]+)(?:\[(\d+):(\d+)\])?$') field_re = re.compile(r'^(imm|off)(\d+)$') @@ -126,7 +127,9 @@ def classify(line): >>> classify('$some:label:') 'label' >>> classify('ff/op 0/subop/add 1/rd/x1 label[11:0]/imm12') - 'instr' + 'data' + >>> classify('ff/8 0/3 2/5') + 'data' """ if line == '': return 'empty' @@ -135,7 +138,7 @@ def classify(line): elif line.endswith(':'): # label return 'label' else: - return 'instr' + return 'data' def parse(line): """ clean, classify and parse lines. @@ -154,8 +157,10 @@ def parse(line): parsed = parse_segment(clean) elif type == 'label': parsed = parse_label(clean) - elif type == 'instr': + elif type == 'data': parsed = parse_instr(clean) + if len(parsed[0]) > 1 and not num.match(parsed[0][-1]): + type = 'instr' else: parsed = None @@ -258,13 +263,14 @@ def format(line): ... }) 'ff/op 00/subop/add 01/rd/x1 label[11:0]/imm12 # this does things.' """ - if line['type'] == 'instr': - packed = ' '.join(format_part(part) for part in line['instr']) - if line['comment']: - packed = packed + ' # ' + line['comment'] - return packed + type = line['type'] + if type == 'instr' or type == 'data': + packed = ' '.join(format_part(part) for part in line[type]) + if line['comment']: + packed = packed + ' # ' + line['comment'] + return packed else: - raise NotImplementedError() + raise NotImplementedError("type {}".format(type)) def dump(line): """ debug-friendly string representation of parsed lines. @@ -94,37 +94,58 @@ def survey(iter): queue = [] map = {} addr = -1 + bits = 0 for line in iter: line = subv.parse(line) line['addr'] = addr queue.append(line) # step forward addr - if line['type'] == 'segment': + type = line['type'] + if type == 'segment': segment, addr = line['segment'] - elif line['type'] == 'label': + bits = 0 + elif type == 'label': + assert bits == 0, "label isn't byte aligned" map[line['label']] = addr - elif line['type'] == 'instr': + elif type == 'data': + for part in line['data']: + bits += int(part[1]) + + if bits >= 8: + addr += bits // 8 + bits = bits % 8 + elif type == 'instr': + assert bits == 0, "instruction isn't byte aligned ({} bits left)".format(8 - bits) + assert addr % 2 == 0, "instruction isn't 2-byte aligned" + bits = 0 for part in line['instr']: if subv.is_reference(part): - ref = subv.parse_reference(part) - bits += ref['size'] + ref = subv.parse_reference(part) + bits += ref['size'] else: - bits += int(part[1]) + bits += int(part[1]) - assert bits % 8 == 0, "line not byte-aligned" + assert bits % 8 == 0, "instruction size not multiple of 8 bits: {}".format(bits) addr += bits // 8 + bits = 0 for line in queue: - if line['type'] == 'instr': + type = line['type'] + if type == 'instr' or type == 'data': instr = [] - for part in line['instr']: + if type == 'instr': + # strip label from opcode + op = line[type].pop(0) + instr.append(op[:2]) + + for part in line[type]: observed = observe(part, line['addr'], map) instr.append(observed) - line['instr'] = instr + line[type] = instr yield subv.format(line) - elif line['type'] == 'segment': + elif type == 'segment': yield line['raw'] if __name__ == '__main__': |
