#!/usr/bin/env python3 import sys import subv from enum import Enum from makeelf.elf import * import makeelf.elfstruct """ Takes a packed & surveyed hex stream (with section headers) and packs it into a simulatable ELF file that can be run as a kernel: qemu-system-riscv32 -nographic -machine sifive_u -bios none -kernel out.elf sifive-u has lots of memory mapped peripherals, see https://github.com/qemu/qemu/blob/master/hw/riscv/sifive_u.c#L66-L83 Among them are two memory-mapped virtual UARTs at 0x10010000 and 0x10011000. Writing to UART0+0x0 writes to qemu's output TTY, and reading from UART0+0x4 reads from it. The MSB in UART0+0x4 is set if there is nothing to read. See full documentation here: https://sifive.cdn.prismic.io/sifive/4d063bf8-3ae6-4db6-9843-ee9076ebadf7_fe310-g000.pdf """ EM = Enum("EM", {"EM_RISCV": 243} | EM.__members__) makeelf.elfstruct.EM = EM class ELF(ELF): def __bytes__(self): """Serialize ELF object into block of bytes Makes some header updates and serializes object to file, so output should always be valid ELF file""" cursor = len(self.Elf.Ehdr) # update offsets in Ehdr regarding Phdrs if len(self.Elf.Phdr_table) > 0: Phdr_len = 0 for Phdr in self.Elf.Phdr_table: Phdr_len += len(Phdr) self.Elf.Ehdr.e_phoff = cursor self.Elf.Ehdr.e_phentsize = len(self.Elf.Phdr_table[0]) self.Elf.Ehdr.e_phnum = len(self.Elf.Phdr_table) cursor += Phdr_len else: self.Elf.Ehdr.e_phoff = 0 self.Elf.Ehdr.e_phentsize = 0 self.Elf.Ehdr.e_phnum = 0 # update offsets in Ehdr regarding Shdrs if len(self.Elf.Shdr_table) > 0: Shdr_len = 0 for Shdr in self.Elf.Shdr_table: Shdr_len += len(Shdr) self.Elf.Ehdr.e_shoff = cursor self.Elf.Ehdr.e_shentsize = len(self.Elf.Shdr_table[0]) self.Elf.Ehdr.e_shnum = len(self.Elf.Shdr_table) cursor += Shdr_len else: self.Elf.Ehdr.e_shoff = 0 self.Elf.Ehdr.e_shentsize = 0 self.Elf.Ehdr.e_shnum = 0 # update section offsets in section headers for i, Shdr in enumerate(self.Elf.Shdr_table): section_len = len(self.Elf.sections[i]) if Shdr.sh_addralign > 1: Shdr.sh_offset = (1 + cursor // Shdr.sh_addralign) * Shdr.sh_addralign else: Shdr.sh_offset = cursor Shdr.sh_size = section_len cursor = Shdr.sh_offset + Shdr.sh_size # update segment offsets in segment headers for Phdr in self.Elf.Phdr_table: assert Phdr.Shdr in self.Elf.Shdr_table Phdr.p_offset = Phdr.Shdr.sh_offset return bytes(self.Elf) ## Add new program header, describing segment in memory # \details This function is for executable and shared objects only. On # other types of ELFs causes exception. Currently appended segment can only # be of type PT_LOAD # \param sec_id id of section already describing this segment # \param addr virtual address at which segment will be loaded # \param mem_size size of segment after loading into memory # \returns ID of newly added segment def append_segment(self, sec_id, addr=None, mem_size=-1, flags="rwx", align=1): if self.Elf.Ehdr.e_type not in [ET.ET_EXEC, ET.ET_DYN]: raise Exception( "ELF type is not executable neither shared (e_type" " is %s)" % self.hdr.e_type ) # extract section from section list Shdr = self.Elf.Shdr_table[sec_id] # set address to this of section linked if default if addr is None: addr = Shdr.sh_addr # set memory size to this of section linked if default if mem_size == -1: mem_size = Shdr.sh_size # set alignment to this of section linked if default if align == 1: align = Shdr.sh_addralign # create p_flags, based on flags parameter # FIXME: if flags is instance of bitmap p_flags = 0 if "r" in flags: p_flags |= PF.PF_R if "w" in flags: p_flags |= PF.PF_W if "x" in flags: p_flags |= PF.PF_X # call internal adder interface id, hdr = self._append_segment( ptype=PT.PT_LOAD, vaddr=addr, paddr=addr, file_size=Shdr.sh_size, mem_size=mem_size, flags=p_flags, align=align, ) hdr.Shdr = Shdr return id def _append_segment( self, ptype, vaddr, paddr, file_size, mem_size, flags=0, align=1 ): # create instance of Phdr Phdr = Elf32_Phdr( p_type=ptype, p_offset=0, p_vaddr=vaddr, p_paddr=paddr, p_filesz=file_size, p_memsz=mem_size, p_flags=flags, p_align=align, little=self.little, ) # add Phdr to elf object ret = len(self.Elf.Phdr_table) self.Elf.Phdr_table.append(Phdr) return ret, Phdr SECTION_NAMES = { "code": ".text", "data": ".data", } @subv.with_parsed_lines def elf(iter): segments = [] labels = [] segment = None for _, line in iter: type = line["type"] if type == "segment": (name, addr) = line["segment"] segment = {"name": name, "addr": addr, "content": []} segments.append(segment) elif type == "label": if segment is None: raise ValueError("label outside of segment!") labels.append( { "name": line["label"], "segment": segment["name"], "offset": len(segment["content"]), } ) elif type == "instr": if segment is None: raise ValueError("code outside of segment!") segment["content"] += line["instr"] elif type != "empty": raise ValueError("elf input should not contain '{}' segments".format(type)) out = ELF( e_class=ELFCLASS.ELFCLASS32, e_data=ELFDATA.ELFDATA2LSB, e_type=ET.ET_REL, e_machine=EM(0xF3), ) out.Elf.Ehdr.e_entry = next(s for s in segments if s["name"] == "code")["addr"] out.Elf.Phdr_table.clear() sec_ids = {} for seg in segments: name = SECTION_NAMES[seg["name"]] sec_flags = SHF.SHF_ALLOC seg_flags = "w" if seg["name"] == "code": sec_flags |= SHF.SHF_EXECINSTR seg_flags += "x" else: sec_flags |= SHF.SHF_WRITE seg_flags += "w" id = out._append_section(name, seg["content"], seg["addr"], sh_addralign=4, sh_flags=sec_flags) # out.append_segment(id, flags=seg_flags) sec_ids[seg["name"]] = id for label in labels: sect_id = sec_ids[label["segment"]] out.append_symbol( label["name"], sect_id, label["offset"], 4, sym_type=STT.STT_FUNC, sym_binding=STB.STB_GLOBAL ) sys.stdout.buffer.write(bytes(out)) yield if __name__ == "__main__": for line in elf(sys.stdin): pass