#! /usr/bin/env -S uv run --script
# /// script
# dependencies = [
# "pyelftools==0.31",
# ]
# ///
# This source file is part of the Swift open source project
#
# Copyright (c) 2023 Apple Inc. and the Swift project authors.
# Licensed under Apache License v2.0 with Runtime Library Exception
#
# See https://swift.org/LICENSE.txt for license information
#
# elf2hex -- Converts a statically-linked ELF executable into an "Intel HEX"
# file format suitable for flashing onto some embedded devices.
#
# Usage:
# $ elf2hex.py [--symbol-map ] [--relocate-data-segment]
#
# Example:
# $ elf2hex.py ./blink ./blink.hex --symbol-map ./blink.symbols
#
# The --relocate-data-segment option expects to be able to locate symbols with names
# - __data_start
# - __flash_data_start
# - __flash_data_len
# and then it physically relocates a segment located at __data_start to
# __flash_data_start, without changing virtual/physical addresses of any ELF
# headers. This means that the .hex file is not validly mapped until a boot-time
# reverse relocation step.
#
# See the linker script used in a particular demo folder for a detailed
# explanation of the linking, packing, and runtime relocation scheme.
#
import argparse
import json
import pathlib
import elftools.elf.elffile
def main():
parser = argparse.ArgumentParser()
parser.add_argument('input')
parser.add_argument('output')
parser.add_argument('--symbol-map')
parser.add_argument('--relocate-data-segment', action='store_true')
args = parser.parse_args()
inf = open(args.input, "rb")
outf = open(args.output, "wb")
def emitrecord(record):
checksum = 0
pos = 0
while pos < len(record):
checksum = (checksum + int(record[pos:pos + 2], 16)) % 256
pos += 2
checksum = (256 - checksum) % 256
outf.write((":" + record + f"{checksum:02X}" + "\n").encode())
def emit(vmaddr, data):
pos = 0
while pos < len(data):
chunklen = min(16, len(data) - pos)
chunk = data[pos:pos + chunklen]
chunkhex = chunk.hex().upper()
assert vmaddr < 0x100000000, f"vmaddr: {vmaddr:x}"
vmaddr_high = (vmaddr >> 16) & 0xffff
recordtype = "04" # Extended Linear Address
emitrecord(f"{2:02X}{0:04X}{recordtype}{vmaddr_high:04X}")
vmaddr_low = vmaddr & 0xffff
recordtype = "00" # Data
emitrecord(f"{chunklen:02X}{vmaddr_low:04X}{recordtype}{chunkhex}")
pos += chunklen
vmaddr += chunklen
elffile = elftools.elf.elffile.ELFFile(inf)
symbol_map = {}
symtab_section = elffile.get_section_by_name(".symtab")
for s in symtab_section.iter_symbols():
if s.entry.st_info.type not in ["STT_FUNC", "STT_NOTYPE"]:
continue
if s.name == "":
continue
symbol_map[s.name] = s.entry.st_value
if args.symbol_map is not None:
pathlib.Path(args.symbol_map).write_text(json.dumps(symbol_map))
relocations = {}
if args.relocate_data_segment:
__flash_data_start = symbol_map["__flash_data_start"]
__data_start = symbol_map["__data_start"]
__flash_data_len = symbol_map["__flash_data_len"]
print("Relocation info:")
print(f" __flash_data_start = 0x{__flash_data_start:08x}")
print(f" __data_start = 0x{__data_start:08x}")
print(f" __flash_data_len = 0x{__flash_data_len:08x}")
relocations = {__data_start: __flash_data_start}
for segment in elffile.iter_segments():
if segment.header.p_type != "PT_LOAD":
continue
vmaddr = segment.header.p_paddr
data = segment.data()
flags = ""
flags += "r" if segment.header.p_flags & 0x4 else "-"
flags += "w" if segment.header.p_flags & 0x2 else "-"
flags += "x" if segment.header.p_flags & 0x1 else "-"
print(f"PT_LOAD {flags} at 0x{segment.header.p_paddr:08x} - "
f"0x{segment.header.p_paddr + len(data):08x}, "
f"size {len(data)} "
f"(0x{len(data):04x})")
placement_addr = segment.header.p_paddr
if segment.header.p_paddr in relocations:
placement_addr = relocations[segment.header.p_paddr]
print(f" ... relocating to 0x{placement_addr:08x}")
emit(placement_addr, data)
chunklen = 0
vmaddr = 0
recordtype = "01" # EOF
emitrecord(f"{chunklen:02X}{vmaddr:04X}{recordtype}")
inf.close()
outf.close()
if __name__ == '__main__':
main()