elf.py 12 KB

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