mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
379 lines
12 KiB
Python
379 lines
12 KiB
Python
#
|
|
# Copyright 2021 Espressif Systems (Shanghai) CO., LTD
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
|
|
import hashlib
|
|
import os
|
|
|
|
from construct import (AlignedStruct, Bytes, Const, Container, GreedyRange, Int16ul, Int32ul, Padding, Pointer,
|
|
Sequence, Struct, this)
|
|
|
|
try:
|
|
from typing import Optional
|
|
except ImportError:
|
|
pass
|
|
|
|
# Following structs are based on spec
|
|
# https://refspecs.linuxfoundation.org/elf/elf.pdf
|
|
# and source code
|
|
# IDF_PATH/components/espcoredump/include_core_dump/elf.h
|
|
|
|
ElfIdentification = Struct(
|
|
'EI_MAG' / Const(b'\x7fELF'),
|
|
'EI_CLASS' / Const(b'\x01'), # ELFCLASS32
|
|
'EI_DATA' / Const(b'\x01'), # ELFDATA2LSB
|
|
'EI_VERSION' / Const(b'\x01'), # EV_CURRENT
|
|
Padding(9),
|
|
)
|
|
|
|
ElfHeader = Struct(
|
|
'e_ident' / ElfIdentification,
|
|
'e_type' / Int16ul,
|
|
'e_machine' / Int16ul,
|
|
'e_version' / Int32ul,
|
|
'e_entry' / Int32ul,
|
|
'e_phoff' / Int32ul,
|
|
'e_shoff' / Int32ul,
|
|
'e_flags' / Int32ul,
|
|
'e_ehsize' / Int16ul,
|
|
'e_phentsize' / Int16ul,
|
|
'e_phnum' / Int16ul,
|
|
'e_shentsize' / Int16ul,
|
|
'e_shnum' / Int16ul,
|
|
'e_shstrndx' / Int16ul,
|
|
)
|
|
|
|
SectionHeader = Struct(
|
|
'sh_name' / Int32ul,
|
|
'sh_type' / Int32ul,
|
|
'sh_flags' / Int32ul,
|
|
'sh_addr' / Int32ul,
|
|
'sh_offset' / Int32ul,
|
|
'sh_size' / Int32ul,
|
|
'sh_link' / Int32ul,
|
|
'sh_info' / Int32ul,
|
|
'sh_addralign' / Int32ul,
|
|
'sh_entsize' / Int32ul,
|
|
)
|
|
|
|
ProgramHeader = Struct(
|
|
'p_type' / Int32ul,
|
|
'p_offset' / Int32ul,
|
|
'p_vaddr' / Int32ul,
|
|
'p_paddr' / Int32ul,
|
|
'p_filesz' / Int32ul,
|
|
'p_memsz' / Int32ul,
|
|
'p_flags' / Int32ul,
|
|
'p_align' / Int32ul,
|
|
)
|
|
|
|
ElfHeaderTables = Struct(
|
|
'elf_header' / ElfHeader,
|
|
'program_headers' / Pointer(this.elf_header.e_phoff, ProgramHeader[this.elf_header.e_phnum]),
|
|
'section_headers' / Pointer(this.elf_header.e_shoff, SectionHeader[this.elf_header.e_shnum]),
|
|
)
|
|
|
|
NoteSection = AlignedStruct(
|
|
4,
|
|
'namesz' / Int32ul,
|
|
'descsz' / Int32ul,
|
|
'type' / Int32ul,
|
|
'name' / Bytes(this.namesz),
|
|
'desc' / Bytes(this.descsz),
|
|
)
|
|
|
|
NoteSections = GreedyRange(NoteSection)
|
|
|
|
|
|
class ElfFile(object):
|
|
"""
|
|
Elf class to a single elf file
|
|
"""
|
|
|
|
SHN_UNDEF = 0x00
|
|
SHT_PROGBITS = 0x01
|
|
SHT_STRTAB = 0x03
|
|
SHT_NOBITS = 0x08
|
|
|
|
PT_LOAD = 0x01
|
|
PT_NOTE = 0x04
|
|
|
|
ET_CORE = 0x04
|
|
|
|
EV_CURRENT = 0x01
|
|
|
|
def __init__(self, elf_path=None, e_type=None, e_machine=None):
|
|
# type: (Optional[str], Optional[int], Optional[int]) -> None
|
|
self.e_type = e_type
|
|
self.e_machine = e_machine
|
|
|
|
self._struct = None # type: Optional[Struct]
|
|
self._model = None # type: Optional[Container]
|
|
|
|
self.sections = [] # type: list[ElfSection]
|
|
self.load_segments = [] # type: list[ElfSegment]
|
|
self.note_segments = [] # type: list[ElfNoteSegment]
|
|
|
|
self.sha256 = b'' # type: bytes
|
|
|
|
if elf_path and os.path.isfile(elf_path):
|
|
self.read_elf(elf_path)
|
|
|
|
def read_elf(self, elf_path): # type: (str) -> None
|
|
"""
|
|
Read elf file, also write to ``self.model``, ``self.program_headers``,
|
|
``self.section_headers``
|
|
:param elf_path: elf file path
|
|
:return: None
|
|
"""
|
|
with open(elf_path, 'rb') as fr:
|
|
elf_bytes = fr.read()
|
|
header_tables = ElfHeaderTables.parse(elf_bytes)
|
|
self.e_type = header_tables.elf_header.e_type
|
|
self.e_machine = header_tables.elf_header.e_machine
|
|
|
|
self._struct = self._generate_struct_from_headers(header_tables)
|
|
self._model = self._struct.parse(elf_bytes)
|
|
|
|
self.load_segments = [ElfSegment(seg.ph.p_vaddr,
|
|
seg.data,
|
|
seg.ph.p_flags) for seg in self._model.load_segments]
|
|
self.note_segments = [ElfNoteSegment(seg.ph.p_vaddr,
|
|
seg.data,
|
|
seg.ph.p_flags) for seg in self._model.note_segments]
|
|
self.sections = [ElfSection(self._parse_string_table(self._model.string_table, sec.sh.sh_name),
|
|
sec.sh.sh_addr,
|
|
sec.data,
|
|
sec.sh.sh_flags) for sec in self._model.sections]
|
|
|
|
# calculate sha256 of the input bytes (note: this may not be the same as the sha256 of any generated
|
|
# output struct, as the ELF parser may change some details.)
|
|
sha256 = hashlib.sha256()
|
|
sha256.update(elf_bytes)
|
|
self.sha256 = sha256.digest()
|
|
|
|
@staticmethod
|
|
def _parse_string_table(byte_str, offset): # type: (bytes, int) -> str
|
|
section_name_str = byte_str[offset:]
|
|
string_end = section_name_str.find(0x00)
|
|
|
|
if (string_end == -1):
|
|
raise ValueError('Unable to get section name from section header string table')
|
|
|
|
name = section_name_str[:string_end].decode('utf-8')
|
|
|
|
return name
|
|
|
|
def _generate_struct_from_headers(self, header_tables): # type: (Container) -> Struct
|
|
"""
|
|
Generate ``construct`` Struct for this file
|
|
:param header_tables: contains elf_header, program_headers, section_headers
|
|
:return: Struct of the whole file
|
|
"""
|
|
elf_header = header_tables.elf_header
|
|
program_headers = header_tables.program_headers
|
|
section_headers = header_tables.section_headers
|
|
assert program_headers or section_headers
|
|
|
|
string_table_sh = None
|
|
load_segment_subcons = []
|
|
note_segment_subcons = []
|
|
# Here we point back to make segments know their program headers
|
|
for i, ph in enumerate(program_headers):
|
|
args = [
|
|
'ph' / Pointer(elf_header.e_phoff + i * ProgramHeader.sizeof(), ProgramHeader),
|
|
'data' / Pointer(ph.p_offset, Bytes(ph.p_filesz)),
|
|
]
|
|
if ph.p_vaddr == 0 and ph.p_type == self.PT_NOTE:
|
|
args.append('note_secs' / Pointer(ph.p_offset, NoteSections))
|
|
note_segment_subcons.append(Struct(*args))
|
|
elif ph.p_vaddr != 0:
|
|
load_segment_subcons.append(Struct(*args))
|
|
|
|
section_subcons = []
|
|
for i, sh in enumerate(section_headers):
|
|
if sh.sh_type == self.SHT_STRTAB and i == elf_header.e_shstrndx:
|
|
string_table_sh = sh
|
|
elif sh.sh_addr != 0 and sh.sh_type == self.SHT_PROGBITS:
|
|
section_subcons.append(Struct(
|
|
'sh' / Pointer(elf_header.e_shoff + i * SectionHeader.sizeof(), SectionHeader),
|
|
'data' / Pointer(sh.sh_offset, Bytes(sh.sh_size)),
|
|
))
|
|
|
|
args = [
|
|
'elf_header' / ElfHeader,
|
|
'load_segments' / Sequence(*load_segment_subcons),
|
|
'note_segments' / Sequence(*note_segment_subcons),
|
|
'sections' / Sequence(*section_subcons),
|
|
]
|
|
if string_table_sh is not None:
|
|
args.append('string_table' / Pointer(string_table_sh.sh_offset, Bytes(string_table_sh.sh_size)))
|
|
|
|
return Struct(*args)
|
|
|
|
|
|
class ElfSection(object):
|
|
SHF_WRITE = 0x01
|
|
SHF_ALLOC = 0x02
|
|
SHF_EXECINSTR = 0x04
|
|
SHF_MASKPROC = 0xf0000000
|
|
|
|
def __init__(self, name, addr, data, flags): # type: (str, int, bytes, int) -> None
|
|
self.name = name
|
|
self.addr = addr
|
|
self.data = data
|
|
self.flags = flags
|
|
|
|
def attr_str(self): # type: () -> str
|
|
if self.flags & self.SHF_MASKPROC:
|
|
return 'MS'
|
|
|
|
res = 'R'
|
|
res += 'W' if self.flags & self.SHF_WRITE else ' '
|
|
res += 'X' if self.flags & self.SHF_EXECINSTR else ' '
|
|
res += 'A' if self.flags & self.SHF_ALLOC else ' '
|
|
return res
|
|
|
|
def __repr__(self): # type: () -> str
|
|
return '{:>32} [Addr] 0x{:>08X}, [Size] 0x{:>08X} {:>4}' \
|
|
.format(self.name, self.addr, len(self.data), self.attr_str())
|
|
|
|
|
|
class ElfSegment(object):
|
|
PF_X = 0x01
|
|
PF_W = 0x02
|
|
PF_R = 0x04
|
|
|
|
def __init__(self, addr, data, flags): # type: (int, bytes, int) -> None
|
|
self.addr = addr
|
|
self.data = data
|
|
self.flags = flags
|
|
self.type = ElfFile.PT_LOAD
|
|
|
|
def attr_str(self): # type: () -> str
|
|
res = ''
|
|
res += 'R' if self.flags & self.PF_R else ' '
|
|
res += 'W' if self.flags & self.PF_W else ' '
|
|
res += 'E' if self.flags & self.PF_X else ' '
|
|
return res
|
|
|
|
@staticmethod
|
|
def _type_str(): # type: () -> str
|
|
return 'LOAD'
|
|
|
|
def __repr__(self): # type: () -> str
|
|
return '{:>8} Addr 0x{:>08X}, Size 0x{:>08X} Flags {:4}' \
|
|
.format(self._type_str(), self.addr, len(self.data), self.attr_str())
|
|
|
|
|
|
class ElfNoteSegment(ElfSegment):
|
|
def __init__(self, addr, data, flags): # type: (int, bytes, int) -> None
|
|
super(ElfNoteSegment, self).__init__(addr, data, flags)
|
|
self.type = ElfFile.PT_NOTE
|
|
self.note_secs = NoteSections.parse(self.data)
|
|
for note in self.note_secs:
|
|
# note.name should include a terminating NUL byte, plus possible
|
|
# padding
|
|
#
|
|
# (note: construct.PaddingString can't parse this if there
|
|
# are non-zero padding bytes after the NUL, it also parses those.)
|
|
note.name = note.name.split(b'\x00')[0]
|
|
|
|
@staticmethod
|
|
def _type_str(): # type: () -> str
|
|
return 'NOTE'
|
|
|
|
|
|
TASK_STATUS_CORRECT = 0x00
|
|
TASK_STATUS_TCB_CORRUPTED = 0x01
|
|
TASK_STATUS_STACK_CORRUPTED = 0x02
|
|
|
|
EspTaskStatus = Struct(
|
|
'task_index' / Int32ul,
|
|
'task_flags' / Int32ul,
|
|
'task_tcb_addr' / Int32ul,
|
|
'task_stack_start' / Int32ul,
|
|
'task_stack_len' / Int32ul,
|
|
'task_name' / Bytes(16),
|
|
)
|
|
|
|
|
|
class ESPCoreDumpElfFile(ElfFile):
|
|
PT_INFO = 8266
|
|
PT_TASK_INFO = 678
|
|
PT_EXTRA_INFO = 677
|
|
|
|
CURR_TASK_MARKER = 0xdeadbeef
|
|
|
|
# ELF file machine type
|
|
EM_XTENSA = 0x5E
|
|
EM_RISCV = 0xF3
|
|
|
|
def __init__(self, elf_path=None, e_type=None, e_machine=None):
|
|
# type: (Optional[str], Optional[int], Optional[int]) -> None
|
|
_e_type = e_type or self.ET_CORE
|
|
_e_machine = e_machine or self.EM_XTENSA
|
|
super(ESPCoreDumpElfFile, self).__init__(elf_path, _e_type, _e_machine)
|
|
|
|
def add_segment(self, addr, data, seg_type, flags): # type: (int, bytes, int, int) -> None
|
|
if seg_type != self.PT_NOTE:
|
|
self.load_segments.append(ElfSegment(addr, data, flags))
|
|
else:
|
|
self.note_segments.append(ElfNoteSegment(addr, data, flags))
|
|
|
|
def dump(self, output_path): # type: (str) -> None
|
|
"""
|
|
Dump self.model into file
|
|
:param output_path: output file path
|
|
:return: None
|
|
"""
|
|
res = b''
|
|
res += ElfHeader.build({
|
|
'e_type': self.e_type,
|
|
'e_machine': self.e_machine,
|
|
'e_version': self.EV_CURRENT,
|
|
'e_entry': 0,
|
|
'e_phoff': ElfHeader.sizeof(),
|
|
'e_shoff': 0,
|
|
'e_flags': 0,
|
|
'e_ehsize': ElfHeader.sizeof(),
|
|
'e_phentsize': ProgramHeader.sizeof(),
|
|
'e_phnum': len(self.load_segments) + len(self.note_segments),
|
|
'e_shentsize': 0,
|
|
'e_shnum': 0,
|
|
'e_shstrndx': self.SHN_UNDEF,
|
|
})
|
|
|
|
offset = ElfHeader.sizeof() + (len(self.load_segments) + len(self.note_segments)) * ProgramHeader.sizeof()
|
|
_segments = self.load_segments + self.note_segments # type: ignore
|
|
for seg in _segments:
|
|
res += ProgramHeader.build({
|
|
'p_type': seg.type,
|
|
'p_offset': offset,
|
|
'p_vaddr': seg.addr,
|
|
'p_paddr': seg.addr,
|
|
'p_filesz': len(seg.data),
|
|
'p_memsz': len(seg.data),
|
|
'p_flags': seg.flags,
|
|
'p_align': 0,
|
|
})
|
|
offset += len(seg.data)
|
|
|
|
for seg in _segments:
|
|
res += seg.data
|
|
|
|
with open(output_path, 'wb') as fw:
|
|
fw.write(res)
|