aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2021-05-22 14:24:21 +0000
committers-ol <s+removethis@s-ol.nu>2021-05-22 14:24:21 +0000
commit79a5f333d7ccb5f581108d81637628287788550d (patch)
treef09b43e833e500f4298665691201dfb02fea38e3
parentmore error reporting in format.py (diff)
downloadsubv-79a5f333d7ccb5f581108d81637628287788550d.tar.gz
subv-79a5f333d7ccb5f581108d81637628287788550d.zip
support non-byte aligned data lines
-rw-r--r--bits.py8
-rwxr-xr-xelf.py6
-rwxr-xr-xformat.py9
-rwxr-xr-xpack.py35
-rw-r--r--subv.py24
-rwxr-xr-xsurvey.py43
6 files changed, 90 insertions, 35 deletions
diff --git a/bits.py b/bits.py
index b3409da..b8cf441 100644
--- a/bits.py
+++ b/bits.py
@@ -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))
diff --git a/elf.py b/elf.py
index a3379c5..4427263 100755
--- a/elf.py
+++ b/elf.py
@@ -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
diff --git a/format.py b/format.py
index d43f835..419f6df 100755
--- a/format.py
+++ b/format.py
@@ -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 = '''
diff --git a/pack.py b/pack.py
index f57d645..13a4971 100755
--- a/pack.py
+++ b/pack.py
@@ -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):
diff --git a/subv.py b/subv.py
index 43bfa35..0615159 100644
--- a/subv.py
+++ b/subv.py
@@ -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.
diff --git a/survey.py b/survey.py
index bf9e551..1420e19 100755
--- a/survey.py
+++ b/survey.py
@@ -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__':