aboutsummaryrefslogtreecommitdiffstats
path: root/elf.py
blob: 776dd913a94da234a527cd2d5fed974e314b8117 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#!/usr/bin/env python3
import sys
import subv

"""
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
"""


def w(b):
    sys.stdout.buffer.write(b)
    return len(b)


def wi(num, size=2):
    sys.stdout.buffer.write(num.to_bytes(size, byteorder="little"))
    return size


def padto(offset, cursor):
    missing = offset - cursor
    if missing < 0:
        raise ValueError("cursor {} already past offset {}!".format(cursor, offset))
    w(b"\x00" * missing)
    return offset


def write_elf_header(segments):
    c = 0

    code = next(s for s in segments if s["name"] == "code")
    entrypoint = code["addr"]

    c += w(b"\x7fELF")  # ELF magic
    c += wi(0x010101, 4)  # 32bit, little endian
    c += wi(0, 8)  # reserved
    c += wi(0x02)  # e_type
    c += wi(0xF3)  # e_machine
    c += wi(0x01, 4)  # e_version
    c += wi(entrypoint, 4)  # e_entry
    c += wi(0x34, 4)  # e_phoff (Program Header offset)
    c += wi(0x00, 4)  # e_shoff (Section Header offset, unused)
    c += wi(0x0004, 4)  # e_flags
    c += wi(0x34)  # e_ehsize
    c += wi(0x20)  # e_phentsize
    c += wi(len(segments))  # e_phnum
    c += wi(0x28)  # e_shentsize
    c += wi(0x0)  # e_shnum
    c += wi(0x0)  # e_shstrndx
    return c


def write_program_header(segment, c, start):
    align = 0x1000
    offset = (1 + start // align) * align
    segment["offset"] = offset
    start = segment["addr"]
    size = len(segment["content"])
    flags = 5 if segment["name"] == "code" else 6

    if offset % align != start % align:
        extra = "\n{:02x} {:02x}".format(offset, offset % align)
        extra += "\n{:02x} {:02x}".format(start, start % align)
        raise ValueError("improper alignment:" + extra)

    c += wi(0x1, 4)  # p_type
    c += wi(offset, 4)  # p_offset
    c += wi(start, 4)  # p_vaddr
    c += wi(start, 4)  # p_paddr
    c += wi(size, 4)  # p_filesz
    c += wi(size, 4)  # p_memsz
    c += wi(flags, 4)  # p_flags, 0x5=rx, 0x6=rw
    c += wi(align, 4)  # p_align
    return c


@subv.with_parsed_lines
def elf(iter):
    segments = []
    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 == "instr":
            if segment == None:
                raise ValueError("label or code outside of segment!")

            segment["content"] += line["instr"]
        elif type != "empty":
            raise ValueError("elf input should contain only segments and data!")

    c = write_elf_header(segments)
    segment_start = 0x1000
    for seg in segments:
        c = write_program_header(seg, c, segment_start)
        segment_start = seg["offset"] + len(seg["content"])

    for seg in segments:
        c = padto(seg["offset"], c)
        for part in seg["content"]:
            c += wi(part[0], 1)

    yield


if __name__ == "__main__":
    for line in elf(sys.stdin):
        pass