Browse Source

fxconv: allow any tree of referencing structures

Lephenixnoir 3 months ago
Signed by: Lephenixnoir GPG Key ID: 1BBA026E13FC0495
  1. 192


@ -154,27 +154,55 @@ def u16(x):
def u32(x):
return bytes([ (x >> 24) & 255, (x >> 16) & 255, (x >> 8) & 255, x & 255 ])
def ref(base, offset=None, padding=None):
if isinstance(base, bytearray):
def ref(base, offset=None, padding=None, prefix_underscore=True):
if isinstance(base, bytes) or isinstance(base, bytearray):
base = bytes(base)
if isinstance(base, bytes):
assert offset is None
if offset is not None:
raise FxconvError(f"reference to bytes does not allow offset")
if padding and len(base) % padding != 0:
base += bytes(padding - len(base) % padding)
return Ref(base, "", 0)
return Ref("bytes", base)
elif isinstance(base, str):
assert padding is None
return Ref(b"", base, offset or 0)
if padding is not None:
raise FxconvError(f"reference to name does not allow padding")
if prefix_underscore:
base = "_" + base
if offset is not None:
offset = int(offset)
base = f"{base} + {offset}"
return Ref("name", base)
elif isinstance(base, ObjectData):
if offset is not None or padding is not None:
raise FxconvError("reference to structure does not allow offset " +
"or padding")
return Ref("struct", base)
raise FxconvError(f"invalid type {type(base)} for ref()")
def ptr(base):
return ref(base)
raise FxconvError(f"invalid type {type(base)} for ref()")
def chars(text, length, require_final_nul=True):
btext = bytes(text, 'utf-8')
if len(btext) >= length and require_final_nul:
raise FxconvError(f"'{text}' does not fit within {length} bytes")
return btext + bytes(length - len(btext))
ptr = ref
def string(text):
return ref(bytes(text, 'utf-8') + bytes([0]))
def sym(name):
return Sym("_" + name)
Ref = collections.namedtuple("Ref", ["data", "name", "offset"])
# There are 3 kinds of Refs:
# "bytes" -> target is a bytes(), we point to that data
# "name" -> target is an str like "_sym+2", we point to that
# "struct" -> target is an ObjectData
Ref = collections.namedtuple("Ref", ["kind", "target"])
Sym = collections.namedtuple("Sym", ["name"])
class ObjectData:
@ -183,53 +211,105 @@ class ObjectData:
other data generated along the output structure.
def __init__(self):
def __init__(self, alignment=4):
"""Construct an empty ObjectData sequence."""
self.elements = []
self.static_data = bytes()
if alignment & (alignment - 1) != 0:
raise FxconvError(f"invalid ObjectData alignment {align} (not a " +
"power of 2)")
self.alignment = alignment
# Elements in the structure: bytes, Ref, Sym, ObjectData
self.inner = []
def __add__(self, other):
if isinstance(other, bytes):
elif isinstance(other, bytearray):
elif isinstance(other, Ref) and
self.elements.append((, other.offset))
elif isinstance(other, Ref) and
self.elements.append(("", len(self.static_data)))
self.static_data +=
elif isinstance(other, ObjectData):
# Shift all offsets into other.static_data by len(self.static_data)
el = other.elements
for i in range(len(el)):
if not isinstance(el[i], bytes):
name, offset = el
if not name:
el[i] = (name, offset + len(self.static_data))
# Extend elements and data
self.elements += el
self.static_data += static_data
if isinstance(other, bytes) or isinstance(other, bytearray):
elif isinstance(other, Ref):
elif isinstance(other, Sym):
elif isinstance(other, ObjectData):
return self
def data(self):
return self.static_data
def element_size(el):
if isinstance(el, bytes):
return len(el)
elif isinstance(el, Ref):
return 4
elif isinstance(el, Sym):
return 0
elif isinstance(el, tuple): # linked sub-ObjectData
return el[1]
raise Exception(f"invalid _element_length: {el}")
def align(self, size, alignment, elements):
padding = (alignment - size) % alignment
if padding != 0:
return padding
def link(self, symbol):
inner = []
outer = []
elements = []
size = 0
# First unfold all structures within [inner] as we accumulate the total
# size of the inner data
for el in self.inner:
if isinstance(el, ObjectData):
size += self.align(size, el.alignment, inner)
code, code_size ="{symbol} + {size}")
inner.append((code, code_size))
size += code_size
size += self.element_size(el)
# Then replace complex references with unfolded data appended at the
# end of the structure
for el in inner:
if isinstance(el, Ref) and el.kind == "bytes":
elements.append(Ref("name", f"{symbol} + {size}"))
size += self.element_size(
elif isinstance(el, Ref) and el.kind == "struct":
size += self.align(size,, outer)
elements.append(Ref("name", f"{symbol} + {size}"))
code, code_size ="{symbol} + {size}")
outer.append((code, code_size))
size += code_size
def assembly(self, data_symbol):
assembly = ""
for el in self.elements:
elements += outer
# Make sure the whole structure is properly aligned
size += self.align(size, self.alignment, elements)
# Finally, generate actual assembler code based on all elements
asm = ""
for el in elements:
if isinstance(el, bytes):
assembly += ".byte " + ",".join(hex(x) for x in el) + "\n"
asm += ".byte " + ",".join(hex(x) for x in el) + "\n"
elif isinstance(el, Ref) and el.kind == "name":
asm += f".long {}\n"
elif isinstance(el, Sym):
assembly += f".global {}\n"
assembly += f"{}:\n"
name, offset = el
name = "_" + name if name != "" else data_symbol
assembly += f".long {name} + {offset}\n"
return assembly
asm += f".global {}\n"
asm += f"{}:\n"
elif isinstance(el, tuple): # linked ObjectData
asm += el[0]
return asm, size
# User-friendly synonym
Structure = ObjectData
@ -1081,17 +1161,17 @@ def elf(data, output, symbol, toolchain=None, arch=None, section=None,
# Unfold ObjectData into data and assembly
if isinstance(data, ObjectData):
assembly = (assembly or "") + f"""
.section .rodata
.global {symbol}
""" + data.assembly(symbol + "_staticdata")
asm = ".section .rodata\n"
asm += f".global {symbol}\n"
asm += f"{symbol}:\n"
asm +=[0]
asm += (assembly or "")
symbol = symbol + "_staticdata"
data =
data = None
assembly = asm
if data is None and assembly is None:
raise fxconv.FxconvError("elf() but no data and no assembly")
raise FxconvError("elf() but no data and no assembly")
# Toolchain parameters