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