loader.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  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 base64
  17. import binascii
  18. import hashlib
  19. import logging
  20. import os
  21. import subprocess
  22. import sys
  23. import tempfile
  24. from construct import AlignedStruct, Bytes, GreedyRange, Int32ul, Padding, Struct, abs_, this
  25. from . import ESPCoreDumpLoaderError
  26. from .elf import (TASK_STATUS_CORRECT, TASK_STATUS_TCB_CORRUPTED, ElfFile, ElfSegment, ESPCoreDumpElfFile,
  27. EspTaskStatus, NoteSection)
  28. from .riscv import Esp32c3Methods
  29. from .xtensa import Esp32Methods, Esp32S2Methods, Esp32S3Methods
  30. try:
  31. from typing import Optional, Tuple
  32. except ImportError:
  33. pass
  34. IDF_PATH = os.getenv('IDF_PATH', '')
  35. PARTTOOL_PY = os.path.join(IDF_PATH, 'components', 'partition_table', 'parttool.py')
  36. ESPTOOL_PY = os.path.join(IDF_PATH, 'components', 'esptool_py', 'esptool', 'esptool.py')
  37. # Following structs are based on source code
  38. # components/espcoredump/include_core_dump/esp_core_dump_priv.h
  39. EspCoreDumpV1Header = Struct(
  40. 'tot_len' / Int32ul,
  41. 'ver' / Int32ul,
  42. 'task_num' / Int32ul,
  43. 'tcbsz' / Int32ul,
  44. )
  45. EspCoreDumpV2Header = Struct(
  46. 'tot_len' / Int32ul,
  47. 'ver' / Int32ul,
  48. 'task_num' / Int32ul,
  49. 'tcbsz' / Int32ul,
  50. 'segs_num' / Int32ul,
  51. )
  52. CRC = Int32ul
  53. SHA256 = Bytes(32)
  54. TaskHeader = Struct(
  55. 'tcb_addr' / Int32ul,
  56. 'stack_top' / Int32ul,
  57. 'stack_end' / Int32ul,
  58. )
  59. MemSegmentHeader = Struct(
  60. 'mem_start' / Int32ul,
  61. 'mem_sz' / Int32ul,
  62. 'data' / Bytes(this.mem_sz),
  63. )
  64. class EspCoreDumpVersion(object):
  65. """Core dump version class, it contains all version-dependent params
  66. """
  67. # Chip IDs should be in sync with components/esp_hw_support/include/esp_chip_info.h
  68. ESP32 = 0
  69. ESP32S2 = 2
  70. ESP32S3 = 9
  71. XTENSA_CHIPS = [ESP32, ESP32S2, ESP32S3]
  72. ESP32C3 = 5
  73. RISCV_CHIPS = [ESP32C3]
  74. COREDUMP_SUPPORTED_TARGETS = XTENSA_CHIPS + RISCV_CHIPS
  75. def __init__(self, version=None): # type: (int) -> None
  76. """Constructor for core dump version
  77. """
  78. super(EspCoreDumpVersion, self).__init__()
  79. if version is None:
  80. self.version = 0
  81. else:
  82. self.set_version(version)
  83. @staticmethod
  84. def make_dump_ver(major, minor): # type: (int, int) -> int
  85. return ((major & 0xFF) << 8) | ((minor & 0xFF) << 0)
  86. def set_version(self, version): # type: (int) -> None
  87. self.version = version
  88. @property
  89. def chip_ver(self): # type: () -> int
  90. return (self.version & 0xFFFF0000) >> 16
  91. @property
  92. def dump_ver(self): # type: () -> int
  93. return self.version & 0x0000FFFF
  94. @property
  95. def major(self): # type: () -> int
  96. return (self.version & 0x0000FF00) >> 8
  97. @property
  98. def minor(self): # type: () -> int
  99. return self.version & 0x000000FF
  100. class EspCoreDumpLoader(EspCoreDumpVersion):
  101. # "legacy" stands for core dumps v0.1 (before IDF v4.1)
  102. BIN_V1 = EspCoreDumpVersion.make_dump_ver(0, 1)
  103. BIN_V2 = EspCoreDumpVersion.make_dump_ver(0, 2)
  104. ELF_CRC32 = EspCoreDumpVersion.make_dump_ver(1, 0)
  105. ELF_SHA256 = EspCoreDumpVersion.make_dump_ver(1, 1)
  106. def __init__(self): # type: () -> None
  107. super(EspCoreDumpLoader, self).__init__()
  108. self.core_src_file = None # type: Optional[str]
  109. self.core_src_struct = None
  110. self.core_src = None
  111. self.core_elf_file = None # type: Optional[str]
  112. self.header = None
  113. self.header_struct = EspCoreDumpV1Header
  114. self.checksum_struct = CRC
  115. # target classes will be assigned in ``_reload_coredump``
  116. self.target_methods = Esp32Methods()
  117. self.temp_files = [] # type: list[str]
  118. def _create_temp_file(self): # type: () -> str
  119. t = tempfile.NamedTemporaryFile('wb', delete=False)
  120. # Here we close this at first to make sure the read/write is wrapped in context manager
  121. # Otherwise the result will be wrong if you read while open in another session
  122. t.close()
  123. self.temp_files.append(t.name)
  124. return t.name
  125. def _load_core_src(self): # type: () -> str
  126. """
  127. Write core elf into ``self.core_src``,
  128. Return the target str by reading core elf
  129. """
  130. with open(self.core_src_file, 'rb') as fr: # type: ignore
  131. coredump_bytes = fr.read()
  132. _header = EspCoreDumpV1Header.parse(coredump_bytes) # first we use V1 format to get version
  133. self.set_version(_header.ver)
  134. if self.dump_ver == self.ELF_CRC32:
  135. self.checksum_struct = CRC
  136. self.header_struct = EspCoreDumpV2Header
  137. elif self.dump_ver == self.ELF_SHA256:
  138. self.checksum_struct = SHA256
  139. self.header_struct = EspCoreDumpV2Header
  140. elif self.dump_ver == self.BIN_V1:
  141. self.checksum_struct = CRC
  142. self.header_struct = EspCoreDumpV1Header
  143. elif self.dump_ver == self.BIN_V2:
  144. self.checksum_struct = CRC
  145. self.header_struct = EspCoreDumpV2Header
  146. else:
  147. raise ESPCoreDumpLoaderError('Core dump version "0x%x" is not supported!' % self.dump_ver)
  148. self.core_src_struct = Struct(
  149. 'header' / self.header_struct,
  150. 'data' / Bytes(this.header.tot_len - self.header_struct.sizeof() - self.checksum_struct.sizeof()),
  151. 'checksum' / self.checksum_struct,
  152. )
  153. self.core_src = self.core_src_struct.parse(coredump_bytes) # type: ignore
  154. # Reload header if header struct changes after parsing
  155. if self.header_struct != EspCoreDumpV1Header:
  156. self.header = EspCoreDumpV2Header.parse(coredump_bytes)
  157. if self.chip_ver in self.COREDUMP_SUPPORTED_TARGETS:
  158. if self.chip_ver == self.ESP32:
  159. self.target_methods = Esp32Methods() # type: ignore
  160. elif self.chip_ver == self.ESP32S2:
  161. self.target_methods = Esp32S2Methods() # type: ignore
  162. elif self.chip_ver == self.ESP32C3:
  163. self.target_methods = Esp32c3Methods() # type: ignore
  164. elif self.chip_ver == self.ESP32S3:
  165. self.target_methods = Esp32S3Methods() # type: ignore
  166. else:
  167. raise NotImplementedError
  168. else:
  169. raise ESPCoreDumpLoaderError('Core dump chip "0x%x" is not supported!' % self.chip_ver)
  170. return self.target_methods.TARGET # type: ignore
  171. def _validate_dump_file(self): # type: () -> None
  172. if self.chip_ver not in self.COREDUMP_SUPPORTED_TARGETS:
  173. raise ESPCoreDumpLoaderError('Invalid core dump chip version: "{}", should be <= "0x{:X}"'
  174. .format(self.chip_ver, self.ESP32S2))
  175. if self.checksum_struct == CRC:
  176. self._crc_validate()
  177. elif self.checksum_struct == SHA256:
  178. self._sha256_validate()
  179. def _crc_validate(self): # type: () -> None
  180. data_crc = binascii.crc32(
  181. EspCoreDumpV2Header.build(self.core_src.header) + self.core_src.data) & 0xffffffff # type: ignore
  182. if data_crc != self.core_src.checksum: # type: ignore
  183. raise ESPCoreDumpLoaderError(
  184. 'Invalid core dump CRC %x, should be %x' % (data_crc, self.core_src.crc)) # type: ignore
  185. def _sha256_validate(self): # type: () -> None
  186. data_sha256 = hashlib.sha256(
  187. EspCoreDumpV2Header.build(self.core_src.header) + self.core_src.data) # type: ignore
  188. data_sha256_str = data_sha256.hexdigest()
  189. sha256_str = binascii.hexlify(self.core_src.checksum).decode('ascii') # type: ignore
  190. if data_sha256_str != sha256_str:
  191. raise ESPCoreDumpLoaderError('Invalid core dump SHA256 "{}", should be "{}"'
  192. .format(data_sha256_str, sha256_str))
  193. def create_corefile(self, exe_name=None, e_machine=ESPCoreDumpElfFile.EM_XTENSA):
  194. # type: (Optional[str], int) -> None
  195. """
  196. Creates core dump ELF file
  197. """
  198. self._validate_dump_file()
  199. self.core_elf_file = self._create_temp_file()
  200. if self.dump_ver in [self.ELF_CRC32,
  201. self.ELF_SHA256]:
  202. self._extract_elf_corefile(exe_name, e_machine)
  203. elif self.dump_ver in [self.BIN_V1,
  204. self.BIN_V2]:
  205. self._extract_bin_corefile(e_machine)
  206. else:
  207. raise NotImplementedError
  208. def _extract_elf_corefile(self, exe_name=None, e_machine=ESPCoreDumpElfFile.EM_XTENSA): # type: (str, int) -> None
  209. """
  210. Reads the ELF formatted core dump image and parse it
  211. """
  212. with open(self.core_elf_file, 'wb') as fw: # type: ignore
  213. fw.write(self.core_src.data) # type: ignore
  214. core_elf = ESPCoreDumpElfFile(self.core_elf_file, e_machine=e_machine) # type: ignore
  215. # Read note segments from core file which are belong to tasks (TCB or stack)
  216. for seg in core_elf.note_segments:
  217. for note_sec in seg.note_secs:
  218. # Check for version info note
  219. if note_sec.name == 'ESP_CORE_DUMP_INFO' \
  220. and note_sec.type == ESPCoreDumpElfFile.PT_INFO \
  221. and exe_name:
  222. exe_elf = ElfFile(exe_name)
  223. app_sha256 = binascii.hexlify(exe_elf.sha256)
  224. coredump_sha256_struct = Struct(
  225. 'ver' / Int32ul,
  226. 'sha256' / Bytes(64) # SHA256 as hex string
  227. )
  228. coredump_sha256 = coredump_sha256_struct.parse(note_sec.desc[:coredump_sha256_struct.sizeof()])
  229. if coredump_sha256.sha256 != app_sha256:
  230. raise ESPCoreDumpLoaderError(
  231. 'Invalid application image for coredump: coredump SHA256({!r}) != app SHA256({!r}).'
  232. .format(coredump_sha256, app_sha256))
  233. if coredump_sha256.ver != self.version:
  234. raise ESPCoreDumpLoaderError(
  235. 'Invalid application image for coredump: coredump SHA256 version({}) != app SHA256 version({}).'
  236. .format(coredump_sha256.ver, self.version))
  237. @staticmethod
  238. def _get_aligned_size(size, align_with=4): # type: (int, int) -> int
  239. if size % align_with:
  240. return align_with * (size // align_with + 1)
  241. return size
  242. @staticmethod
  243. def _build_note_section(name, sec_type, desc): # type: (str, int, str) -> bytes
  244. b_name = bytearray(name, encoding='ascii') + b'\0'
  245. return NoteSection.build({ # type: ignore
  246. 'namesz': len(b_name),
  247. 'descsz': len(desc),
  248. 'type': sec_type,
  249. 'name': b_name,
  250. 'desc': desc,
  251. })
  252. def _extract_bin_corefile(self, e_machine=ESPCoreDumpElfFile.EM_XTENSA): # type: (int) -> None
  253. """
  254. Creates core dump ELF file
  255. """
  256. coredump_data_struct = Struct(
  257. 'tasks' / GreedyRange(
  258. AlignedStruct(
  259. 4,
  260. 'task_header' / TaskHeader,
  261. 'tcb' / Bytes(self.header.tcbsz), # type: ignore
  262. 'stack' / Bytes(abs_(this.task_header.stack_top - this.task_header.stack_end)), # type: ignore
  263. )
  264. ),
  265. 'mem_seg_headers' / MemSegmentHeader[self.core_src.header.segs_num] # type: ignore
  266. )
  267. core_elf = ESPCoreDumpElfFile(e_machine=e_machine)
  268. notes = b''
  269. core_dump_info_notes = b''
  270. task_info_notes = b''
  271. coredump_data = coredump_data_struct.parse(self.core_src.data) # type: ignore
  272. for i, task in enumerate(coredump_data.tasks):
  273. stack_len_aligned = self._get_aligned_size(abs(task.task_header.stack_top - task.task_header.stack_end))
  274. task_status_kwargs = {
  275. 'task_index': i,
  276. 'task_flags': TASK_STATUS_CORRECT,
  277. 'task_tcb_addr': task.task_header.tcb_addr,
  278. 'task_stack_start': min(task.task_header.stack_top, task.task_header.stack_end),
  279. 'task_stack_end': max(task.task_header.stack_top, task.task_header.stack_end),
  280. 'task_stack_len': stack_len_aligned,
  281. 'task_name': Padding(16).build({}) # currently we don't have task_name, keep it as padding
  282. }
  283. # Write TCB
  284. try:
  285. if self.target_methods.tcb_is_sane(task.task_header.tcb_addr, self.header.tcbsz): # type: ignore
  286. core_elf.add_segment(task.task_header.tcb_addr,
  287. task.tcb,
  288. ElfFile.PT_LOAD,
  289. ElfSegment.PF_R | ElfSegment.PF_W)
  290. elif task.task_header.tcb_addr and self.target_methods.addr_is_fake(task.task_header.tcb_addr):
  291. task_status_kwargs['task_flags'] |= TASK_STATUS_TCB_CORRUPTED
  292. except ESPCoreDumpLoaderError as e:
  293. logging.warning('Skip TCB {} bytes @ 0x{:x}. (Reason: {})'
  294. .format(self.header.tcbsz, task.task_header.tcb_addr, e)) # type: ignore
  295. # Write stack
  296. try:
  297. if self.target_methods.stack_is_sane(task_status_kwargs['task_stack_start'],
  298. task_status_kwargs['task_stack_end']):
  299. core_elf.add_segment(task_status_kwargs['task_stack_start'],
  300. task.stack,
  301. ElfFile.PT_LOAD,
  302. ElfSegment.PF_R | ElfSegment.PF_W)
  303. elif (task_status_kwargs['task_stack_start']
  304. and self.target_methods.addr_is_fake(task_status_kwargs['task_stack_start'])):
  305. task_status_kwargs['task_flags'] |= TASK_STATUS_TCB_CORRUPTED
  306. core_elf.add_segment(task_status_kwargs['task_stack_start'],
  307. task.stack,
  308. ElfFile.PT_LOAD,
  309. ElfSegment.PF_R | ElfSegment.PF_W)
  310. except ESPCoreDumpLoaderError as e:
  311. logging.warning('Skip task\'s ({:x}) stack {} bytes @ 0x{:x}. (Reason: {})'
  312. .format(task_status_kwargs['tcb_addr'],
  313. task_status_kwargs['stack_len_aligned'],
  314. task_status_kwargs['stack_base'],
  315. e))
  316. try:
  317. logging.debug('Stack start_end: 0x{:x} @ 0x{:x}'
  318. .format(task.task_header.stack_top, task.task_header.stack_end))
  319. task_regs, extra_regs = self.target_methods.get_registers_from_stack(
  320. task.stack,
  321. task.task_header.stack_end > task.task_header.stack_top
  322. )
  323. except Exception as e:
  324. raise ESPCoreDumpLoaderError(str(e))
  325. task_info_notes += self._build_note_section('TASK_INFO',
  326. ESPCoreDumpElfFile.PT_TASK_INFO,
  327. EspTaskStatus.build(task_status_kwargs))
  328. notes += self._build_note_section('CORE',
  329. ElfFile.PT_LOAD,
  330. self.target_methods.build_prstatus_data(task.task_header.tcb_addr,
  331. task_regs))
  332. if len(core_dump_info_notes) == 0: # the first task is the crashed task
  333. core_dump_info_notes += self._build_note_section('ESP_CORE_DUMP_INFO',
  334. ESPCoreDumpElfFile.PT_INFO,
  335. Int32ul.build(self.header.ver)) # type: ignore
  336. _regs = [task.task_header.tcb_addr]
  337. # For xtensa, we need to put the exception registers into the extra info as well
  338. if e_machine == ESPCoreDumpElfFile.EM_XTENSA and extra_regs:
  339. for reg_id in extra_regs:
  340. _regs.extend([reg_id, extra_regs[reg_id]])
  341. core_dump_info_notes += self._build_note_section(
  342. 'EXTRA_INFO',
  343. ESPCoreDumpElfFile.PT_EXTRA_INFO,
  344. Int32ul[len(_regs)].build(_regs)
  345. )
  346. if self.dump_ver == self.BIN_V2:
  347. for header in coredump_data.mem_seg_headers:
  348. logging.debug('Read memory segment {} bytes @ 0x{:x}'.format(header.mem_sz, header.mem_start))
  349. core_elf.add_segment(header.mem_start, header.data, ElfFile.PT_LOAD, ElfSegment.PF_R | ElfSegment.PF_W)
  350. # add notes
  351. try:
  352. core_elf.add_segment(0, notes, ElfFile.PT_NOTE, 0)
  353. except ESPCoreDumpLoaderError as e:
  354. logging.warning('Skip NOTES segment {:d} bytes @ 0x{:x}. (Reason: {})'.format(len(notes), 0, e))
  355. # add core dump info notes
  356. try:
  357. core_elf.add_segment(0, core_dump_info_notes, ElfFile.PT_NOTE, 0)
  358. except ESPCoreDumpLoaderError as e:
  359. logging.warning('Skip core dump info NOTES segment {:d} bytes @ 0x{:x}. (Reason: {})'
  360. .format(len(core_dump_info_notes), 0, e))
  361. try:
  362. core_elf.add_segment(0, task_info_notes, ElfFile.PT_NOTE, 0)
  363. except ESPCoreDumpLoaderError as e:
  364. logging.warning('Skip failed tasks info NOTES segment {:d} bytes @ 0x{:x}. (Reason: {})'
  365. .format(len(task_info_notes), 0, e))
  366. # dump core ELF
  367. core_elf.e_type = ElfFile.ET_CORE
  368. core_elf.dump(self.core_elf_file) # type: ignore
  369. class ESPCoreDumpFlashLoader(EspCoreDumpLoader):
  370. ESP_COREDUMP_PART_TABLE_OFF = 0x8000
  371. def __init__(self, offset, target=None, port=None, baud=None):
  372. # type: (int, Optional[str], Optional[str], Optional[int]) -> None
  373. super(ESPCoreDumpFlashLoader, self).__init__()
  374. self.port = port
  375. self.baud = baud
  376. self._get_core_src(offset, target)
  377. self.target = self._load_core_src()
  378. def _get_core_src(self, off, target=None): # type: (int, Optional[str]) -> None
  379. """
  380. Loads core dump from flash using parttool or elftool (if offset is set)
  381. """
  382. try:
  383. if off:
  384. logging.info('Invoke esptool to read image.')
  385. self._invoke_esptool(off=off, target=target)
  386. else:
  387. logging.info('Invoke parttool to read image.')
  388. self._invoke_parttool()
  389. except subprocess.CalledProcessError as e:
  390. if e.output:
  391. logging.info(e.output)
  392. logging.error('Error during the subprocess execution')
  393. def _invoke_esptool(self, off=None, target=None): # type: (Optional[int], Optional[str]) -> None
  394. """
  395. Loads core dump from flash using elftool
  396. """
  397. if target is None:
  398. target = 'auto'
  399. tool_args = [sys.executable, ESPTOOL_PY, '-c', target]
  400. if self.port:
  401. tool_args.extend(['-p', self.port])
  402. if self.baud:
  403. tool_args.extend(['-b', str(self.baud)])
  404. self.core_src_file = self._create_temp_file()
  405. try:
  406. (part_offset, part_size) = self._get_core_dump_partition_info()
  407. if not off:
  408. off = part_offset # set default offset if not specified
  409. logging.warning('The core dump image offset is not specified. Use partition offset: %d.', part_offset)
  410. if part_offset != off:
  411. logging.warning('Predefined image offset: %d does not match core dump partition offset: %d', off,
  412. part_offset)
  413. # Here we use V1 format to locate the size
  414. tool_args.extend(['read_flash', str(off), str(EspCoreDumpV1Header.sizeof())])
  415. tool_args.append(self.core_src_file) # type: ignore
  416. # read core dump length
  417. et_out = subprocess.check_output(tool_args)
  418. if et_out:
  419. logging.info(et_out.decode('utf-8'))
  420. header = EspCoreDumpV1Header.parse(open(self.core_src_file, 'rb').read()) # type: ignore
  421. if not header or not 0 < header.tot_len <= part_size:
  422. logging.error('Incorrect size of core dump image: {}, use partition size instead: {}'
  423. .format(header.tot_len, part_size))
  424. coredump_len = part_size
  425. else:
  426. coredump_len = header.tot_len
  427. # set actual size of core dump image and read it from flash
  428. tool_args[-2] = str(coredump_len)
  429. et_out = subprocess.check_output(tool_args)
  430. if et_out:
  431. logging.info(et_out.decode('utf-8'))
  432. except subprocess.CalledProcessError as e:
  433. logging.error('esptool script execution failed with err %d', e.returncode)
  434. logging.debug("Command ran: '%s'", e.cmd)
  435. logging.debug('Command out:')
  436. logging.debug(e.output)
  437. raise e
  438. def _invoke_parttool(self): # type: () -> None
  439. """
  440. Loads core dump from flash using parttool
  441. """
  442. tool_args = [sys.executable, PARTTOOL_PY]
  443. if self.port:
  444. tool_args.extend(['--port', self.port])
  445. tool_args.extend(['read_partition', '--partition-type', 'data', '--partition-subtype', 'coredump', '--output'])
  446. self.core_src_file = self._create_temp_file()
  447. try:
  448. tool_args.append(self.core_src_file) # type: ignore
  449. # read core dump partition
  450. et_out = subprocess.check_output(tool_args)
  451. if et_out:
  452. logging.info(et_out.decode('utf-8'))
  453. except subprocess.CalledProcessError as e:
  454. logging.error('parttool script execution failed with err %d', e.returncode)
  455. logging.debug("Command ran: '%s'", e.cmd)
  456. logging.debug('Command out:')
  457. logging.debug(e.output)
  458. raise e
  459. def _get_core_dump_partition_info(self, part_off=None): # type: (Optional[int]) -> Tuple[int, int]
  460. """
  461. Get core dump partition info using parttool
  462. """
  463. logging.info('Retrieving core dump partition offset and size...')
  464. if not part_off:
  465. part_off = self.ESP_COREDUMP_PART_TABLE_OFF
  466. try:
  467. tool_args = [sys.executable, PARTTOOL_PY, '-q', '--partition-table-offset', str(part_off)]
  468. if self.port:
  469. tool_args.extend(['--port', self.port])
  470. invoke_args = tool_args + ['get_partition_info', '--partition-type', 'data',
  471. '--partition-subtype', 'coredump',
  472. '--info', 'offset', 'size']
  473. res = subprocess.check_output(invoke_args).strip()
  474. (offset_str, size_str) = res.rsplit(b'\n')[-1].split(b' ')
  475. size = int(size_str, 16)
  476. offset = int(offset_str, 16)
  477. logging.info('Core dump partition offset=%d, size=%d', offset, size)
  478. except subprocess.CalledProcessError as e:
  479. logging.error('parttool get partition info failed with err %d', e.returncode)
  480. logging.debug("Command ran: '%s'", e.cmd)
  481. logging.debug('Command out:')
  482. logging.debug(e.output)
  483. logging.error('Check if the coredump partition exists in partition table.')
  484. raise e
  485. return offset, size
  486. class ESPCoreDumpFileLoader(EspCoreDumpLoader):
  487. def __init__(self, path, is_b64=False): # type: (str, bool) -> None
  488. super(ESPCoreDumpFileLoader, self).__init__()
  489. self.is_b64 = is_b64
  490. self._get_core_src(path)
  491. self.target = self._load_core_src()
  492. def _get_core_src(self, path): # type: (str) -> None
  493. """
  494. Loads core dump from (raw binary or base64-encoded) file
  495. """
  496. logging.debug('Load core dump from "%s", %s format', path, 'b64' if self.is_b64 else 'raw')
  497. if not self.is_b64:
  498. self.core_src_file = path
  499. else:
  500. self.core_src_file = self._create_temp_file()
  501. with open(self.core_src_file, 'wb') as fw:
  502. with open(path, 'rb') as fb64:
  503. while True:
  504. line = fb64.readline()
  505. if len(line) == 0:
  506. break
  507. data = base64.standard_b64decode(line.rstrip(b'\r\n'))
  508. fw.write(data) # type: ignore