1#
2# Copyright 2021 Espressif Systems (Shanghai) CO., LTD
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17import hashlib
18import os
19
20from construct import (AlignedStruct, Bytes, Const, Container, GreedyRange, Int16ul, Int32ul, Padding, Pointer,
21                       Sequence, Struct, this)
22
23try:
24    from typing import Optional
25except ImportError:
26    pass
27
28# Following structs are based on spec
29# https://refspecs.linuxfoundation.org/elf/elf.pdf
30# and source code
31# IDF_PATH/components/espcoredump/include_core_dump/elf.h
32
33ElfIdentification = Struct(
34    'EI_MAG' / Const(b'\x7fELF'),
35    'EI_CLASS' / Const(b'\x01'),  # ELFCLASS32
36    'EI_DATA' / Const(b'\x01'),  # ELFDATA2LSB
37    'EI_VERSION' / Const(b'\x01'),  # EV_CURRENT
38    Padding(9),
39)
40
41ElfHeader = Struct(
42    'e_ident' / ElfIdentification,
43    'e_type' / Int16ul,
44    'e_machine' / Int16ul,
45    'e_version' / Int32ul,
46    'e_entry' / Int32ul,
47    'e_phoff' / Int32ul,
48    'e_shoff' / Int32ul,
49    'e_flags' / Int32ul,
50    'e_ehsize' / Int16ul,
51    'e_phentsize' / Int16ul,
52    'e_phnum' / Int16ul,
53    'e_shentsize' / Int16ul,
54    'e_shnum' / Int16ul,
55    'e_shstrndx' / Int16ul,
56)
57
58SectionHeader = Struct(
59    'sh_name' / Int32ul,
60    'sh_type' / Int32ul,
61    'sh_flags' / Int32ul,
62    'sh_addr' / Int32ul,
63    'sh_offset' / Int32ul,
64    'sh_size' / Int32ul,
65    'sh_link' / Int32ul,
66    'sh_info' / Int32ul,
67    'sh_addralign' / Int32ul,
68    'sh_entsize' / Int32ul,
69)
70
71ProgramHeader = Struct(
72    'p_type' / Int32ul,
73    'p_offset' / Int32ul,
74    'p_vaddr' / Int32ul,
75    'p_paddr' / Int32ul,
76    'p_filesz' / Int32ul,
77    'p_memsz' / Int32ul,
78    'p_flags' / Int32ul,
79    'p_align' / Int32ul,
80)
81
82ElfHeaderTables = Struct(
83    'elf_header' / ElfHeader,
84    'program_headers' / Pointer(this.elf_header.e_phoff, ProgramHeader[this.elf_header.e_phnum]),
85    'section_headers' / Pointer(this.elf_header.e_shoff, SectionHeader[this.elf_header.e_shnum]),
86)
87
88NoteSection = AlignedStruct(
89    4,
90    'namesz' / Int32ul,
91    'descsz' / Int32ul,
92    'type' / Int32ul,
93    'name' / Bytes(this.namesz),
94    'desc' / Bytes(this.descsz),
95)
96
97NoteSections = GreedyRange(NoteSection)
98
99
100class ElfFile(object):
101    """
102    Elf class to a single elf file
103    """
104
105    SHN_UNDEF = 0x00
106    SHT_PROGBITS = 0x01
107    SHT_STRTAB = 0x03
108    SHT_NOBITS = 0x08
109
110    PT_LOAD = 0x01
111    PT_NOTE = 0x04
112
113    ET_CORE = 0x04
114
115    EV_CURRENT = 0x01
116
117    def __init__(self, elf_path=None, e_type=None, e_machine=None):
118        # type: (Optional[str], Optional[int], Optional[int]) -> None
119        self.e_type = e_type
120        self.e_machine = e_machine
121
122        self._struct = None  # type: Optional[Struct]
123        self._model = None  # type: Optional[Container]
124
125        self.sections = []  # type: list[ElfSection]
126        self.load_segments = []  # type: list[ElfSegment]
127        self.note_segments = []  # type: list[ElfNoteSegment]
128
129        if elf_path and os.path.isfile(elf_path):
130            self.read_elf(elf_path)
131
132    def read_elf(self, elf_path):  # type: (str) -> None
133        """
134        Read elf file, also write to ``self.model``, ``self.program_headers``,
135        ``self.section_headers``
136        :param elf_path: elf file path
137        :return: None
138        """
139        with open(elf_path, 'rb') as fr:
140            elf_bytes = fr.read()
141        header_tables = ElfHeaderTables.parse(elf_bytes)
142        self.e_type = header_tables.elf_header.e_type
143        self.e_machine = header_tables.elf_header.e_machine
144
145        self._struct = self._generate_struct_from_headers(header_tables)
146        self._model = self._struct.parse(elf_bytes)
147
148        self.load_segments = [ElfSegment(seg.ph.p_vaddr,
149                                         seg.data,
150                                         seg.ph.p_flags) for seg in self._model.load_segments]
151        self.note_segments = [ElfNoteSegment(seg.ph.p_vaddr,
152                                             seg.data,
153                                             seg.ph.p_flags) for seg in self._model.note_segments]
154        self.sections = [ElfSection(self._parse_string_table(self._model.string_table, sec.sh.sh_name),
155                                    sec.sh.sh_addr,
156                                    sec.data,
157                                    sec.sh.sh_flags) for sec in self._model.sections]
158
159    @staticmethod
160    def _parse_string_table(byte_str, offset):  # type: (bytes, int) -> str
161        section_name_str = byte_str[offset:]
162        string_end = section_name_str.find(0x00)
163
164        if (string_end == -1):
165            raise ValueError('Unable to get section name from section header string table')
166
167        name = section_name_str[:string_end].decode('utf-8')
168
169        return name
170
171    def _generate_struct_from_headers(self, header_tables):  # type: (Container) -> Struct
172        """
173        Generate ``construct`` Struct for this file
174        :param header_tables: contains elf_header, program_headers, section_headers
175        :return: Struct of the whole file
176        """
177        elf_header = header_tables.elf_header
178        program_headers = header_tables.program_headers
179        section_headers = header_tables.section_headers
180        assert program_headers or section_headers
181
182        string_table_sh = None
183        load_segment_subcons = []
184        note_segment_subcons = []
185        # Here we point back to make segments know their program headers
186        for i, ph in enumerate(program_headers):
187            args = [
188                'ph' / Pointer(elf_header.e_phoff + i * ProgramHeader.sizeof(), ProgramHeader),
189                'data' / Pointer(ph.p_offset, Bytes(ph.p_filesz)),
190            ]
191            if ph.p_vaddr == 0 and ph.p_type == self.PT_NOTE:
192                args.append('note_secs' / Pointer(ph.p_offset, NoteSections))
193                note_segment_subcons.append(Struct(*args))
194            elif ph.p_vaddr != 0:
195                load_segment_subcons.append(Struct(*args))
196
197        section_subcons = []
198        for i, sh in enumerate(section_headers):
199            if sh.sh_type == self.SHT_STRTAB and i == elf_header.e_shstrndx:
200                string_table_sh = sh
201            elif sh.sh_addr != 0 and sh.sh_type == self.SHT_PROGBITS:
202                section_subcons.append(Struct(
203                    'sh' / Pointer(elf_header.e_shoff + i * SectionHeader.sizeof(), SectionHeader),
204                    'data' / Pointer(sh.sh_offset, Bytes(sh.sh_size)),
205                ))
206
207        args = [
208            'elf_header' / ElfHeader,
209            'load_segments' / Sequence(*load_segment_subcons),
210            'note_segments' / Sequence(*note_segment_subcons),
211            'sections' / Sequence(*section_subcons),
212        ]
213        if string_table_sh is not None:
214            args.append('string_table' / Pointer(string_table_sh.sh_offset, Bytes(string_table_sh.sh_size)))
215
216        return Struct(*args)
217
218    @property
219    def sha256(self):  # type: () -> bytes
220        """
221        :return: SHA256 hash of the input ELF file
222        """
223        sha256 = hashlib.sha256()
224        sha256.update(self._struct.build(self._model))  # type: ignore
225        return sha256.digest()
226
227
228class ElfSection(object):
229    SHF_WRITE = 0x01
230    SHF_ALLOC = 0x02
231    SHF_EXECINSTR = 0x04
232    SHF_MASKPROC = 0xf0000000
233
234    def __init__(self, name, addr, data, flags):  # type: (str, int, bytes, int) -> None
235        self.name = name
236        self.addr = addr
237        self.data = data
238        self.flags = flags
239
240    def attr_str(self):  # type: () -> str
241        if self.flags & self.SHF_MASKPROC:
242            return 'MS'
243
244        res = 'R'
245        res += 'W' if self.flags & self.SHF_WRITE else ' '
246        res += 'X' if self.flags & self.SHF_EXECINSTR else ' '
247        res += 'A' if self.flags & self.SHF_ALLOC else ' '
248        return res
249
250    def __repr__(self):  # type: () -> str
251        return '{:>32} [Addr] 0x{:>08X}, [Size] 0x{:>08X} {:>4}' \
252            .format(self.name, self.addr, len(self.data), self.attr_str())
253
254
255class ElfSegment(object):
256    PF_X = 0x01
257    PF_W = 0x02
258    PF_R = 0x04
259
260    def __init__(self, addr, data, flags):  # type: (int, bytes, int) -> None
261        self.addr = addr
262        self.data = data
263        self.flags = flags
264        self.type = ElfFile.PT_LOAD
265
266    def attr_str(self):  # type: () -> str
267        res = ''
268        res += 'R' if self.flags & self.PF_R else ' '
269        res += 'W' if self.flags & self.PF_W else ' '
270        res += 'E' if self.flags & self.PF_X else ' '
271        return res
272
273    @staticmethod
274    def _type_str():  # type: () -> str
275        return 'LOAD'
276
277    def __repr__(self):  # type: () -> str
278        return '{:>8} Addr 0x{:>08X}, Size 0x{:>08X} Flags {:4}' \
279            .format(self._type_str(), self.addr, len(self.data), self.attr_str())
280
281
282class ElfNoteSegment(ElfSegment):
283    def __init__(self, addr, data, flags):  # type: (int, bytes, int) -> None
284        super(ElfNoteSegment, self).__init__(addr, data, flags)
285        self.type = ElfFile.PT_NOTE
286        self.note_secs = NoteSections.parse(self.data)
287
288    @staticmethod
289    def _type_str():  # type: () -> str
290        return 'NOTE'
291
292
293TASK_STATUS_CORRECT = 0x00
294TASK_STATUS_TCB_CORRUPTED = 0x01
295TASK_STATUS_STACK_CORRUPTED = 0x02
296
297EspTaskStatus = Struct(
298    'task_index' / Int32ul,
299    'task_flags' / Int32ul,
300    'task_tcb_addr' / Int32ul,
301    'task_stack_start' / Int32ul,
302    'task_stack_len' / Int32ul,
303    'task_name' / Bytes(16),
304)
305
306
307class ESPCoreDumpElfFile(ElfFile):
308    PT_INFO = 8266
309    PT_TASK_INFO = 678
310    PT_EXTRA_INFO = 677
311
312    CURR_TASK_MARKER = 0xdeadbeef
313
314    # ELF file machine type
315    EM_XTENSA = 0x5E
316    EM_RISCV = 0xF3
317
318    def __init__(self, elf_path=None, e_type=None, e_machine=None):
319        # type: (Optional[str], Optional[int], Optional[int]) -> None
320        _e_type = e_type or self.ET_CORE
321        _e_machine = e_machine or self.EM_XTENSA
322        super(ESPCoreDumpElfFile, self).__init__(elf_path, _e_type, _e_machine)
323
324    def add_segment(self, addr, data, seg_type, flags):  # type: (int, bytes, int, int) -> None
325        if seg_type != self.PT_NOTE:
326            self.load_segments.append(ElfSegment(addr, data, flags))
327        else:
328            self.note_segments.append(ElfNoteSegment(addr, data, flags))
329
330    def dump(self, output_path):  # type: (str) -> None
331        """
332        Dump self.model into file
333        :param output_path: output file path
334        :return: None
335        """
336        res = b''
337        res += ElfHeader.build({
338            'e_type': self.e_type,
339            'e_machine': self.e_machine,
340            'e_version': self.EV_CURRENT,
341            'e_entry': 0,
342            'e_phoff': ElfHeader.sizeof(),
343            'e_shoff': 0,
344            'e_flags': 0,
345            'e_ehsize': ElfHeader.sizeof(),
346            'e_phentsize': ProgramHeader.sizeof(),
347            'e_phnum': len(self.load_segments) + len(self.note_segments),
348            'e_shentsize': 0,
349            'e_shnum': 0,
350            'e_shstrndx': self.SHN_UNDEF,
351        })
352
353        offset = ElfHeader.sizeof() + (len(self.load_segments) + len(self.note_segments)) * ProgramHeader.sizeof()
354        _segments = self.load_segments + self.note_segments  # type: ignore
355        for seg in _segments:
356            res += ProgramHeader.build({
357                'p_type': seg.type,
358                'p_offset': offset,
359                'p_vaddr': seg.addr,
360                'p_paddr': seg.addr,
361                'p_filesz': len(seg.data),
362                'p_memsz': len(seg.data),
363                'p_flags': seg.flags,
364                'p_align': 0,
365            })
366            offset += len(seg.data)
367
368        for seg in _segments:
369            res += seg.data
370
371        with open(output_path, 'wb') as fw:
372            fw.write(res)
373