aboutsummaryrefslogtreecommitdiffstats
path: root/elf.py
blob: 8b7c6a71b13b2ecd0065db1ecaca92d54a5fd19b (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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#!/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