elf.py 12 KB

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