espcoredump.py 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119
  1. #!/usr/bin/env python
  2. #
  3. # ESP32 core dump Utility
  4. import sys
  5. import os
  6. import argparse
  7. import subprocess
  8. import tempfile
  9. import struct
  10. import array
  11. import errno
  12. import base64
  13. idf_path = os.getenv('IDF_PATH')
  14. if idf_path:
  15. sys.path.insert(0, os.path.join(idf_path, 'components', 'esptool_py', 'esptool'))
  16. try:
  17. import esptool
  18. except ImportError:
  19. print "Esptool is not found! Set proper $IDF_PATH in environment."
  20. sys.exit(2)
  21. __version__ = "0.1-dev"
  22. if os.name == 'nt':
  23. CLOSE_FDS = False
  24. else:
  25. CLOSE_FDS = True
  26. class ESPCoreDumpError(RuntimeError):
  27. """Core dump runtime error class
  28. """
  29. def __init__(self, message):
  30. """Constructor for core dump error
  31. """
  32. super(ESPCoreDumpError, self).__init__(message)
  33. class BinStruct(object):
  34. """Binary structure representation
  35. Subclasses must specify actual structure layout using 'fields' and 'format' members.
  36. For example, the following subclass represents structure with two fields:
  37. f1 of size 2 bytes and 4 bytes f2. Little endian.
  38. class SomeStruct(BinStruct):
  39. fields = ("f1",
  40. "f2")
  41. format = "<HL"
  42. Then subclass can be used to initialize fields of underlaying structure and convert it to binary representation:
  43. f = open('some_struct.bin', 'wb')
  44. s = SomeStruct()
  45. s.f1 = 1
  46. s.f2 = 10
  47. f.write(s.dump())
  48. f.close()
  49. """
  50. def __init__(self, buf=None):
  51. """Base constructor for binary structure objects
  52. """
  53. if buf is None:
  54. buf = b'\0' * self.sizeof()
  55. fields = struct.unpack(self.__class__.format, buf[:self.sizeof()])
  56. self.__dict__.update(zip(self.__class__.fields, fields))
  57. def sizeof(self):
  58. """Returns the size of the structure represented by specific subclass
  59. """
  60. return struct.calcsize(self.__class__.format)
  61. def dump(self):
  62. """Returns binary representation of structure
  63. """
  64. keys = self.__class__.fields
  65. return struct.pack(self.__class__.format, *(self.__dict__[k] for k in keys))
  66. # def __str__(self):
  67. # keys = self.__class__.fields
  68. # return (self.__class__.__name__ + "({" +
  69. # ", ".join("%s:%r" % (k, self.__dict__[k]) for k in keys) +
  70. # "})")
  71. class Elf32FileHeader(BinStruct):
  72. """ELF32 file header
  73. """
  74. fields = ("e_ident",
  75. "e_type",
  76. "e_machine",
  77. "e_version",
  78. "e_entry",
  79. "e_phoff",
  80. "e_shoff",
  81. "e_flags",
  82. "e_ehsize",
  83. "e_phentsize",
  84. "e_phnum",
  85. "e_shentsize",
  86. "e_shnum",
  87. "e_shstrndx")
  88. format = "<16sHHLLLLLHHHHHH"
  89. def __init__(self, buf=None):
  90. """Constructor for ELF32 file header structure
  91. """
  92. super(Elf32FileHeader, self).__init__(buf)
  93. if buf is None:
  94. # Fill in sane ELF header for LSB32
  95. self.e_ident = "\x7fELF\1\1\1\0\0\0\0\0\0\0\0\0"
  96. self.e_version = ESPCoreDumpElfFile.EV_CURRENT
  97. self.e_ehsize = self.sizeof()
  98. class Elf32ProgramHeader(BinStruct):
  99. """ELF32 program header
  100. """
  101. fields = ("p_type",
  102. "p_offset",
  103. "p_vaddr",
  104. "p_paddr",
  105. "p_filesz",
  106. "p_memsz",
  107. "p_flags",
  108. "p_align")
  109. format = "<LLLLLLLL"
  110. class Elf32NoteDesc(object):
  111. """ELF32 note descriptor
  112. """
  113. def __init__(self, name, type, desc):
  114. """Constructor for ELF32 note descriptor
  115. """
  116. self.name = bytearray(name, encoding='ascii') + b'\0'
  117. self.type = type
  118. self.desc = desc
  119. def dump(self):
  120. """Returns binary representation of ELF32 note descriptor
  121. """
  122. hdr = struct.pack("<LLL", len(self.name), len(self.desc), self.type)
  123. # pad for 4 byte alignment
  124. name = self.name + ((4 - len(self.name)) % 4) * b'\0'
  125. desc = self.desc + ((4 - len(self.desc)) % 4) * b'\0'
  126. return hdr + name + desc
  127. class XtensaPrStatus(BinStruct):
  128. """Xtensa program status structure"""
  129. fields = ("si_signo", "si_code", "si_errno",
  130. "pr_cursig",
  131. "pr_pad0",
  132. "pr_sigpend",
  133. "pr_sighold",
  134. "pr_pid",
  135. "pr_ppid",
  136. "pr_pgrp",
  137. "pr_sid",
  138. "pr_utime",
  139. "pr_stime",
  140. "pr_cutime",
  141. "pr_cstime")
  142. format = "<3LHHLLLLLLQQQQ"
  143. class ESPCoreDumpSegment(esptool.ImageSegment):
  144. """ Wrapper class for a program segment in core ELF file, has a segment
  145. type and flags as well as the common properties of an ImageSegment.
  146. """
  147. # segment flags
  148. PF_X = 0x1 # Execute
  149. PF_W = 0x2 # Write
  150. PF_R = 0x4 # Read
  151. def __init__(self, addr, data, type, flags):
  152. """Constructor for program segment
  153. """
  154. super(ESPCoreDumpSegment, self).__init__(addr, data)
  155. self.flags = flags
  156. self.type = type
  157. def __repr__(self):
  158. """Returns string representation of program segment
  159. """
  160. return "%s %s %s" % (self.type, self.attr_str(), super(ESPCoreDumpSegment, self).__repr__())
  161. def attr_str(self):
  162. """Returns string representation of program segment attributes
  163. """
  164. str = ''
  165. if self.flags & self.PF_R:
  166. str += 'R'
  167. else:
  168. str += ' '
  169. if self.flags & self.PF_W:
  170. str += 'W'
  171. else:
  172. str += ' '
  173. if self.flags & self.PF_X:
  174. str += 'X'
  175. else:
  176. str += ' '
  177. return str
  178. class ESPCoreDumpSection(esptool.ELFSection):
  179. """ Wrapper class for a section in core ELF file, has a section
  180. flags as well as the common properties of an esptool.ELFSection.
  181. """
  182. # section flags
  183. SHF_WRITE = 0x1
  184. SHF_ALLOC = 0x2
  185. SHF_EXECINSTR = 0x4
  186. def __init__(self, name, addr, data, flags):
  187. """Constructor for section
  188. """
  189. super(ESPCoreDumpSection, self).__init__(name, addr, data)
  190. self.flags = flags
  191. def __repr__(self):
  192. """Returns string representation of section
  193. """
  194. return "%s %s" % (super(ESPCoreDumpSection, self).__repr__(), self.attr_str())
  195. def attr_str(self):
  196. """Returns string representation of section attributes
  197. """
  198. str = "R"
  199. if self.flags & self.SHF_WRITE:
  200. str += 'W'
  201. else:
  202. str += ' '
  203. if self.flags & self.SHF_EXECINSTR:
  204. str += 'X'
  205. else:
  206. str += ' '
  207. if self.flags & self.SHF_ALLOC:
  208. str += 'A'
  209. else:
  210. str += ' '
  211. return str
  212. class ESPCoreDumpElfFile(esptool.ELFFile):
  213. """ Wrapper class for core dump ELF file
  214. """
  215. # ELF file type
  216. ET_NONE = 0x0 # No file type
  217. ET_REL = 0x1 # Relocatable file
  218. ET_EXEC = 0x2 # Executable file
  219. ET_DYN = 0x3 # Shared object file
  220. ET_CORE = 0x4 # Core file
  221. # ELF file version
  222. EV_NONE = 0x0
  223. EV_CURRENT = 0x1
  224. # ELF file machine type
  225. EM_NONE = 0x0
  226. EM_XTENSA = 0x5E
  227. # section types
  228. SEC_TYPE_PROGBITS = 0x01
  229. SEC_TYPE_STRTAB = 0x03
  230. # special section index
  231. SHN_UNDEF = 0x0
  232. # program segment types
  233. PT_NULL = 0x0
  234. PT_LOAD = 0x1
  235. PT_DYNAMIC = 0x2
  236. PT_INTERP = 0x3
  237. PT_NOTE = 0x4
  238. PT_SHLIB = 0x5
  239. PT_PHDR = 0x6
  240. def __init__(self, name=None):
  241. """Constructor for core dump ELF file
  242. """
  243. if name:
  244. super(ESPCoreDumpElfFile, self).__init__(name)
  245. else:
  246. self.sections = []
  247. self.program_segments = []
  248. self.e_type = self.ET_NONE
  249. self.e_machine = self.EM_NONE
  250. def _read_elf_file(self, f):
  251. """Reads core dump from ELF file
  252. """
  253. # read the ELF file header
  254. LEN_FILE_HEADER = 0x34
  255. try:
  256. (ident,type,machine,_version,
  257. self.entrypoint,phoff,shoff,_flags,
  258. _ehsize, phentsize,phnum,_shentsize,
  259. shnum,shstrndx) = struct.unpack("<16sHHLLLLLHHHHHH", f.read(LEN_FILE_HEADER))
  260. except struct.error as e:
  261. raise ESPCoreDumpError("Failed to read a valid ELF header from %s: %s" % (self.name, e))
  262. if ident[0] != '\x7f' or ident[1:4] != 'ELF':
  263. raise ESPCoreDumpError("%s has invalid ELF magic header" % self.name)
  264. if machine != self.EM_XTENSA:
  265. raise ESPCoreDumpError("%s does not appear to be an Xtensa ELF file. e_machine=%04x" % (self.name, machine))
  266. self.e_type = type
  267. self.e_machine = machine
  268. if shnum > 0:
  269. self._read_sections(f, shoff, shstrndx)
  270. else:
  271. self.sections = []
  272. if phnum > 0:
  273. self._read_program_segments(f, phoff, phentsize, phnum)
  274. else:
  275. self.program_segments = []
  276. def _read_sections(self, f, section_header_offs, shstrndx):
  277. """Reads core dump sections from ELF file
  278. """
  279. f.seek(section_header_offs)
  280. section_header = f.read()
  281. LEN_SEC_HEADER = 0x28
  282. if len(section_header) == 0:
  283. raise ESPCoreDumpError("No section header found at offset %04x in ELF file." % section_header_offs)
  284. if len(section_header) % LEN_SEC_HEADER != 0:
  285. print 'WARNING: Unexpected ELF section header length %04x is not mod-%02x' % (len(section_header),LEN_SEC_HEADER)
  286. # walk through the section header and extract all sections
  287. section_header_offsets = range(0, len(section_header), LEN_SEC_HEADER)
  288. def read_section_header(offs):
  289. name_offs,sec_type,flags,lma,sec_offs,size = struct.unpack_from("<LLLLLL", section_header[offs:])
  290. return (name_offs, sec_type, flags, lma, size, sec_offs)
  291. all_sections = [read_section_header(offs) for offs in section_header_offsets]
  292. prog_sections = [s for s in all_sections if s[1] == esptool.ELFFile.SEC_TYPE_PROGBITS]
  293. # search for the string table section
  294. if not shstrndx * LEN_SEC_HEADER in section_header_offsets:
  295. raise ESPCoreDumpError("ELF file has no STRTAB section at shstrndx %d" % shstrndx)
  296. _,sec_type,_,_,sec_size,sec_offs = read_section_header(shstrndx * LEN_SEC_HEADER)
  297. if sec_type != esptool.ELFFile.SEC_TYPE_STRTAB:
  298. print 'WARNING: ELF file has incorrect STRTAB section type 0x%02x' % sec_type
  299. f.seek(sec_offs)
  300. string_table = f.read(sec_size)
  301. # build the real list of ELFSections by reading the actual section names from the
  302. # string table section, and actual data for each section from the ELF file itself
  303. def lookup_string(offs):
  304. raw = string_table[offs:]
  305. return raw[:raw.index('\x00')]
  306. def read_data(offs,size):
  307. f.seek(offs)
  308. return f.read(size)
  309. prog_sections = [ESPCoreDumpSection(lookup_string(n_offs), lma, read_data(offs, size), flags) for (n_offs, _type, flags, lma, size, offs) in prog_sections
  310. if lma != 0]
  311. self.sections = prog_sections
  312. def _read_program_segments(self, f, seg_table_offs, entsz, num):
  313. """Reads core dump program segments from ELF file
  314. """
  315. f.seek(seg_table_offs)
  316. seg_table = f.read(entsz*num)
  317. LEN_SEG_HEADER = 0x20
  318. if len(seg_table) == 0:
  319. raise ESPCoreDumpError("No program header table found at offset %04x in ELF file." % seg_table_offs)
  320. if len(seg_table) % LEN_SEG_HEADER != 0:
  321. print 'WARNING: Unexpected ELF program header table length %04x is not mod-%02x' % (len(seg_table),LEN_SEG_HEADER)
  322. # walk through the program segment table and extract all segments
  323. seg_table_offs = range(0, len(seg_table), LEN_SEG_HEADER)
  324. def read_program_header(offs):
  325. type,offset,vaddr,_paddr,filesz,_memsz,flags,_align = struct.unpack_from("<LLLLLLLL", seg_table[offs:])
  326. return (type,offset,vaddr,filesz,flags)
  327. all_segments = [read_program_header(offs) for offs in seg_table_offs]
  328. prog_segments = [s for s in all_segments if s[0] == self.PT_LOAD]
  329. # build the real list of ImageSegment by reading actual data for each segment from the ELF file itself
  330. def read_data(offs,size):
  331. f.seek(offs)
  332. return f.read(size)
  333. prog_segments = [ESPCoreDumpSegment(vaddr, read_data(offset, filesz), type, flags) for (type, offset, vaddr, filesz,flags) in prog_segments
  334. if vaddr != 0]
  335. self.program_segments = prog_segments
  336. def add_program_segment(self, addr, data, type, flags):
  337. """Adds new program segment
  338. """
  339. # TODO: currently merging with existing segments is not supported
  340. data_sz = len(data)
  341. # check for overlapping and merge if needed
  342. if addr != 0 and data_sz != 0:
  343. for ps in self.program_segments:
  344. seg_len = len(ps.data)
  345. if addr >= ps.addr and addr < (ps.addr + seg_len):
  346. raise ESPCoreDumpError("Can not add overlapping region [%x..%x] to ELF file. Conflict with existing [%x..%x]." %
  347. (addr, addr + data_sz - 1, ps.addr, ps.addr + seg_len - 1))
  348. if (addr + data_sz) > ps.addr and (addr + data_sz) <= (ps.addr + seg_len):
  349. raise ESPCoreDumpError("Can not add overlapping region [%x..%x] to ELF file. Conflict with existing [%x..%x]." %
  350. (addr, addr + data_sz - 1, ps.addr, ps.addr + seg_len - 1))
  351. # append
  352. self.program_segments.append(ESPCoreDumpSegment(addr, data, type, flags))
  353. def dump(self, f):
  354. """Write core dump contents to file
  355. """
  356. # TODO: currently dumps only program segments.
  357. # dumping sections is not supported yet
  358. # write ELF header
  359. ehdr = Elf32FileHeader()
  360. ehdr.e_type = self.e_type
  361. ehdr.e_machine = self.e_machine
  362. ehdr.e_entry = 0
  363. ehdr.e_phoff = ehdr.sizeof()
  364. ehdr.e_shoff = 0
  365. ehdr.e_flags = 0
  366. ehdr.e_phentsize = Elf32ProgramHeader().sizeof()
  367. ehdr.e_phnum = len(self.program_segments)
  368. ehdr.e_shentsize = 0
  369. ehdr.e_shnum = 0
  370. ehdr.e_shstrndx = self.SHN_UNDEF
  371. f.write(ehdr.dump())
  372. # write program header table
  373. cur_off = ehdr.e_ehsize + ehdr.e_phnum * ehdr.e_phentsize
  374. for i in range(len(self.program_segments)):
  375. phdr = Elf32ProgramHeader()
  376. phdr.p_type = self.program_segments[i].type
  377. phdr.p_offset = cur_off
  378. phdr.p_vaddr = self.program_segments[i].addr
  379. phdr.p_paddr = phdr.p_vaddr # TODO
  380. phdr.p_filesz = len(self.program_segments[i].data)
  381. phdr.p_memsz = phdr.p_filesz # TODO
  382. phdr.p_flags = self.program_segments[i].flags
  383. phdr.p_align = 0 # TODO
  384. f.write(phdr.dump())
  385. cur_off += phdr.p_filesz
  386. # write program segments
  387. for i in range(len(self.program_segments)):
  388. f.write(self.program_segments[i].data)
  389. class ESPCoreDumpLoaderError(ESPCoreDumpError):
  390. """Core dump loader error class
  391. """
  392. def __init__(self, message):
  393. """Constructor for core dump loader error
  394. """
  395. super(ESPCoreDumpLoaderError, self).__init__(message)
  396. class ESPCoreDumpLoader(object):
  397. """Core dump loader base class
  398. """
  399. ESP32_COREDUMP_HDR_FMT = '<3L'
  400. ESP32_COREDUMP_HDR_SZ = struct.calcsize(ESP32_COREDUMP_HDR_FMT)
  401. ESP32_COREDUMP_TSK_HDR_FMT = '<3L'
  402. ESP32_COREDUMP_TSK_HDR_SZ = struct.calcsize(ESP32_COREDUMP_TSK_HDR_FMT)
  403. def __init__(self):
  404. """Base constructor for core dump loader
  405. """
  406. self.fcore = None
  407. def _get_registers_from_stack(self, data, grows_down):
  408. """Returns list of registers (in GDB format) from xtensa stack frame
  409. """
  410. # from "gdb/xtensa-tdep.h"
  411. # typedef struct
  412. # {
  413. #0 xtensa_elf_greg_t pc;
  414. #1 xtensa_elf_greg_t ps;
  415. #2 xtensa_elf_greg_t lbeg;
  416. #3 xtensa_elf_greg_t lend;
  417. #4 xtensa_elf_greg_t lcount;
  418. #5 xtensa_elf_greg_t sar;
  419. #6 xtensa_elf_greg_t windowstart;
  420. #7 xtensa_elf_greg_t windowbase;
  421. #8..63 xtensa_elf_greg_t reserved[8+48];
  422. #64 xtensa_elf_greg_t ar[64];
  423. # } xtensa_elf_gregset_t;
  424. REG_PC_IDX=0
  425. REG_PS_IDX=1
  426. REG_LB_IDX=2
  427. REG_LE_IDX=3
  428. REG_LC_IDX=4
  429. REG_SAR_IDX=5
  430. REG_WS_IDX=6
  431. REG_WB_IDX=7
  432. REG_AR_START_IDX=64
  433. REG_AR_NUM=64
  434. # FIXME: acc to xtensa_elf_gregset_t number of regs must be 128,
  435. # but gdb complanis when it less then 129
  436. REG_NUM=129
  437. XT_SOL_EXIT=0
  438. XT_SOL_PC=1
  439. XT_SOL_PS=2
  440. XT_SOL_NEXT=3
  441. XT_SOL_AR_START=4
  442. XT_SOL_AR_NUM=4
  443. XT_SOL_FRMSZ=8
  444. XT_STK_EXIT=0
  445. XT_STK_PC=1
  446. XT_STK_PS=2
  447. XT_STK_AR_START=3
  448. XT_STK_AR_NUM=16
  449. XT_STK_SAR=19
  450. XT_STK_EXCCAUSE=20
  451. XT_STK_EXCVADDR=21
  452. XT_STK_LBEG=22
  453. XT_STK_LEND=23
  454. XT_STK_LCOUNT=24
  455. XT_STK_FRMSZ=25
  456. regs = [0] * REG_NUM
  457. # TODO: support for growing up stacks
  458. if not grows_down:
  459. raise ESPCoreDumpLoaderError("Growing up stacks are not supported for now!")
  460. ex_struct = "<%dL" % XT_STK_FRMSZ
  461. if len(data) < struct.calcsize(ex_struct):
  462. raise ESPCoreDumpLoaderError("Too small stack to keep frame: %d bytes!" % len(data))
  463. stack = struct.unpack(ex_struct, data[:struct.calcsize(ex_struct)])
  464. # Stack frame type indicator is always the first item
  465. rc = stack[XT_STK_EXIT]
  466. if rc != 0:
  467. regs[REG_PC_IDX] = stack[XT_STK_PC]
  468. regs[REG_PS_IDX] = stack[XT_STK_PS]
  469. for i in range(XT_STK_AR_NUM):
  470. regs[REG_AR_START_IDX + i] = stack[XT_STK_AR_START + i]
  471. regs[REG_SAR_IDX] = stack[XT_STK_SAR]
  472. regs[REG_LB_IDX] = stack[XT_STK_LBEG]
  473. regs[REG_LE_IDX] = stack[XT_STK_LEND]
  474. regs[REG_LC_IDX] = stack[XT_STK_LCOUNT]
  475. # FIXME: crashed and some running tasks (e.g. prvIdleTask) have EXCM bit set
  476. # and GDB can not unwind callstack properly (it implies not windowed call0)
  477. if regs[REG_PS_IDX] & (1 << 5):
  478. regs[REG_PS_IDX] &= ~(1 << 4)
  479. else:
  480. regs[REG_PC_IDX] = stack[XT_SOL_PC]
  481. regs[REG_PS_IDX] = stack[XT_SOL_PS]
  482. for i in range(XT_SOL_AR_NUM):
  483. regs[REG_AR_START_IDX + i] = stack[XT_SOL_AR_START + i]
  484. nxt = stack[XT_SOL_NEXT]
  485. # TODO: remove magic hack with saved PC to get proper value
  486. regs[REG_PC_IDX] = ((regs[REG_PC_IDX] & 0x3FFFFFFF) | 0x40000000)
  487. if regs[REG_PC_IDX] & 0x80000000:
  488. regs[REG_PC_IDX] = (regs[REG_PC_IDX] & 0x3fffffff) | 0x40000000;
  489. if regs[REG_AR_START_IDX + 0] & 0x80000000:
  490. regs[REG_AR_START_IDX + 0] = (regs[REG_AR_START_IDX + 0] & 0x3fffffff) | 0x40000000;
  491. return regs
  492. def remove_tmp_file(self, fname):
  493. """Silently removes temporary file
  494. """
  495. try:
  496. os.remove(fname)
  497. except OSError as e:
  498. if e.errno != errno.ENOENT:
  499. print "Warning failed to remove temp file '%s' (%d)!" % (fname, e.errno)
  500. def cleanup(self):
  501. """Cleans up loader resources
  502. """
  503. if self.fcore:
  504. self.fcore.close()
  505. if self.fcore_name:
  506. self.remove_tmp_file(self.fcore_name)
  507. def create_corefile(self, core_fname=None, off=0):
  508. """Creates core dump ELF file
  509. """
  510. core_off = off
  511. data = self.read_data(core_off, self.ESP32_COREDUMP_HDR_SZ)
  512. tot_len,task_num,tcbsz = struct.unpack_from(self.ESP32_COREDUMP_HDR_FMT, data)
  513. tcbsz_aligned = tcbsz
  514. if tcbsz_aligned % 4:
  515. tcbsz_aligned = 4*(tcbsz_aligned/4 + 1)
  516. core_off += self.ESP32_COREDUMP_HDR_SZ
  517. core_elf = ESPCoreDumpElfFile()
  518. notes = b''
  519. for i in range(task_num):
  520. data = self.read_data(core_off, self.ESP32_COREDUMP_TSK_HDR_SZ)
  521. tcb_addr,stack_top,stack_end = struct.unpack_from(self.ESP32_COREDUMP_TSK_HDR_FMT, data)
  522. if stack_end > stack_top:
  523. stack_len = stack_end - stack_top
  524. stack_base = stack_top
  525. else:
  526. stack_len = stack_top - stack_end
  527. stack_base = stack_end
  528. stack_len_aligned = stack_len
  529. if stack_len_aligned % 4:
  530. stack_len_aligned = 4*(stack_len_aligned/4 + 1)
  531. core_off += self.ESP32_COREDUMP_TSK_HDR_SZ
  532. data = self.read_data(core_off, tcbsz_aligned)
  533. if tcbsz != tcbsz_aligned:
  534. core_elf.add_program_segment(tcb_addr, data[:tcbsz - tcbsz_aligned], ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W)
  535. else:
  536. core_elf.add_program_segment(tcb_addr, data, ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W)
  537. core_off += tcbsz_aligned
  538. data = self.read_data(core_off, stack_len_aligned)
  539. if stack_len != stack_len_aligned:
  540. data = data[:stack_len - stack_len_aligned]
  541. core_elf.add_program_segment(stack_base, data, ESPCoreDumpElfFile.PT_LOAD, ESPCoreDumpSegment.PF_R | ESPCoreDumpSegment.PF_W)
  542. core_off += stack_len_aligned
  543. try:
  544. task_regs = self._get_registers_from_stack(data, stack_end > stack_top)
  545. except Exception as e:
  546. print e
  547. return None
  548. prstatus = XtensaPrStatus()
  549. prstatus.pr_cursig = 0 # TODO: set sig only for current/failed task
  550. prstatus.pr_pid = i # TODO: use pid assigned by OS
  551. note = Elf32NoteDesc("CORE", 1, prstatus.dump() + struct.pack("<%dL" % len(task_regs), *task_regs)).dump()
  552. notes += note
  553. # add notes
  554. core_elf.add_program_segment(0, notes, ESPCoreDumpElfFile.PT_NOTE, 0)
  555. core_elf.e_type = ESPCoreDumpElfFile.ET_CORE
  556. core_elf.e_machine = ESPCoreDumpElfFile.EM_XTENSA
  557. if core_fname:
  558. fce = open(core_fname, 'wb')
  559. else:
  560. fhnd,core_fname = tempfile.mkstemp()
  561. fce = os.fdopen(fhnd, 'wb')
  562. core_elf.dump(fce)
  563. fce.close()
  564. return core_fname
  565. def read_data(self, off, sz):
  566. """Reads data from raw core dump got from flash or UART
  567. """
  568. self.fcore.seek(off)
  569. data = self.fcore.read(sz)
  570. return data
  571. class ESPCoreDumpFileLoader(ESPCoreDumpLoader):
  572. """Core dump file loader class
  573. """
  574. def __init__(self, path, b64 = False):
  575. """Constructor for core dump file loader
  576. """
  577. super(ESPCoreDumpFileLoader, self).__init__()
  578. self.fcore = self._load_coredump(path, b64)
  579. def _load_coredump(self, path, b64):
  580. """Loads core dump from (raw binary or base64-encoded) file
  581. """
  582. self.fcore_name = None
  583. if b64:
  584. fhnd,self.fcore_name = tempfile.mkstemp()
  585. fcore = os.fdopen(fhnd, 'wb')
  586. fb64 = open(path, 'rb')
  587. try:
  588. while True:
  589. line = fb64.readline()
  590. if len(line) == 0:
  591. break
  592. data = base64.standard_b64decode(line.rstrip('\r\n'))
  593. fcore.write(data)
  594. fcore.close()
  595. fcore = open(self.fcore_name, 'rb')
  596. except Exception as e:
  597. if self.fcore_name:
  598. self.remove_tmp_file(self.fcore_name)
  599. raise e
  600. finally:
  601. fb64.close()
  602. else:
  603. fcore = open(path, 'rb')
  604. return fcore
  605. class ESPCoreDumpFlashLoader(ESPCoreDumpLoader):
  606. """Core dump flash loader class
  607. """
  608. ESP32_COREDUMP_FLASH_MAGIC_START = 0xE32C04ED
  609. ESP32_COREDUMP_FLASH_MAGIC_END = 0xE32C04ED
  610. ESP32_COREDUMP_FLASH_MAGIC_FMT = '<L'
  611. ESP32_COREDUMP_FLASH_MAGIC_SZ = struct.calcsize(ESP32_COREDUMP_FLASH_MAGIC_FMT)
  612. ESP32_COREDUMP_FLASH_HDR_FMT = '<4L'
  613. ESP32_COREDUMP_FLASH_HDR_SZ = struct.calcsize(ESP32_COREDUMP_FLASH_HDR_FMT)
  614. def __init__(self, off, tool_path=None, chip='esp32', port=None, baud=None):
  615. """Constructor for core dump flash loader
  616. """
  617. super(ESPCoreDumpFlashLoader, self).__init__()
  618. if not tool_path:
  619. self.path = esptool.__file__
  620. _,e = os.path.splitext(self.path)
  621. if e == '.pyc':
  622. self.path = self.path[:-1]
  623. else:
  624. self.path = tool_path
  625. self.port = port
  626. self.baud = baud
  627. self.chip = chip
  628. self.dump_sz = 0
  629. self.fcore = self._load_coredump(off)
  630. def _load_coredump(self, off):
  631. """Loads core dump from flash
  632. """
  633. tool_args = [sys.executable, self.path, '-c', self.chip]
  634. if self.port:
  635. tool_args.extend(['-p', self.port])
  636. if self.baud:
  637. tool_args.extend(['-b', str(self.baud)])
  638. tool_args.extend(['read_flash', str(off), str(self.ESP32_COREDUMP_FLASH_HDR_SZ), ''])
  639. self.fcore_name = None
  640. try:
  641. fhnd,self.fcore_name = tempfile.mkstemp()
  642. tool_args[-1] = self.fcore_name
  643. # read core dump length
  644. et_out = subprocess.check_output(tool_args)
  645. print et_out
  646. f = os.fdopen(fhnd, 'rb')
  647. self.dump_sz = self._read_core_dump_length(f)
  648. # read core dump
  649. tool_args[-2] = str(self. dump_sz)
  650. et_out = subprocess.check_output(tool_args)
  651. print et_out
  652. except subprocess.CalledProcessError as e:
  653. print "esptool script execution failed with err %d" % e.returncode
  654. print "Command ran: '%s'" % e.cmd
  655. print "Command out:"
  656. print e.output
  657. if self.fcore_name:
  658. self.remove_tmp_file(self.fcore_name)
  659. raise e
  660. return f
  661. def _read_core_dump_length(self, f):
  662. """Reads core dump length
  663. """
  664. data = f.read(4*4)
  665. mag1,tot_len,task_num,tcbsz = struct.unpack_from(self.ESP32_COREDUMP_FLASH_HDR_FMT, data)
  666. if mag1 != self.ESP32_COREDUMP_FLASH_MAGIC_START:
  667. raise ESPCoreDumpLoaderError("Invalid start magic number!")
  668. return tot_len
  669. def create_corefile(self, core_fname=None):
  670. """Checks flash coredump data integrity and creates ELF file
  671. """
  672. data = self.read_data(0, self.ESP32_COREDUMP_FLASH_MAGIC_SZ)
  673. mag1, = struct.unpack_from(self.ESP32_COREDUMP_FLASH_MAGIC_FMT, data)
  674. if mag1 != self.ESP32_COREDUMP_FLASH_MAGIC_START:
  675. raise ESPCoreDumpLoaderError("Invalid start marker %x" % mag1)
  676. data = self.read_data(self.dump_sz-self.ESP32_COREDUMP_FLASH_MAGIC_SZ, self.ESP32_COREDUMP_FLASH_MAGIC_SZ)
  677. mag2, = struct.unpack_from(self.ESP32_COREDUMP_FLASH_MAGIC_FMT, data)
  678. if mag2 != self.ESP32_COREDUMP_FLASH_MAGIC_END:
  679. raise ESPCoreDumpLoaderError("Invalid end marker %x" % mag2)
  680. return super(ESPCoreDumpFlashLoader, self).create_corefile(core_fname, off=self.ESP32_COREDUMP_FLASH_MAGIC_SZ)
  681. class GDBMIOutRecordHandler(object):
  682. """GDB/MI output record handler base class
  683. """
  684. TAG = ''
  685. def __init__(self, f, verbose=False):
  686. """Base constructor for GDB/MI output record handler
  687. """
  688. self.verbose = verbose
  689. def execute(self, ln):
  690. """Base method to execute GDB/MI output record handler function
  691. """
  692. if self.verbose:
  693. print "%s.execute: [[%s]]" % (self.__class__.__name__, ln)
  694. class GDBMIOutStreamHandler(GDBMIOutRecordHandler):
  695. """GDB/MI output stream handler class
  696. """
  697. def __init__(self, f, verbose=False):
  698. """Constructor for GDB/MI output stream handler
  699. """
  700. super(GDBMIOutStreamHandler, self).__init__(None, verbose)
  701. self.func = f
  702. def execute(self, ln):
  703. """Executes GDB/MI output stream handler function
  704. """
  705. GDBMIOutRecordHandler.execute(self, ln)
  706. if self.func:
  707. # remove TAG / quotes and replace c-string \n with actual NL
  708. self.func(ln[1:].strip('"').replace('\\n', '\n').replace('\\t', '\t'))
  709. class GDBMIResultHandler(GDBMIOutRecordHandler):
  710. """GDB/MI result handler class
  711. """
  712. TAG = '^'
  713. RC_DONE = 'done'
  714. RC_RUNNING = 'running'
  715. RC_CONNECTED = 'connected'
  716. RC_ERROR = 'error'
  717. RC_EXIT = 'exit'
  718. def __init__(self, verbose=False):
  719. """Constructor for GDB/MI result handler
  720. """
  721. super(GDBMIResultHandler, self).__init__(None, verbose)
  722. self.result_class = None
  723. self.result_str = None
  724. def _parse_rc(self, ln, rc):
  725. """Parses result code
  726. """
  727. rc_str = "{0}{1}".format(self.TAG, rc)
  728. if ln.startswith(rc_str):
  729. self.result_class = rc
  730. sl = len(rc_str)
  731. if len(ln) > sl:
  732. self.result_str = ln[sl:]
  733. if self.result_str.startswith(','):
  734. self.result_str = self.result_str[1:]
  735. else:
  736. print "Invalid result format: '%s'" % ln
  737. else:
  738. self.result_str = ''
  739. return True
  740. return False
  741. def execute(self, ln):
  742. """Executes GDB/MI result handler function
  743. """
  744. GDBMIOutRecordHandler.execute(self, ln)
  745. if self._parse_rc(ln, self.RC_DONE):
  746. return
  747. if self._parse_rc(ln, self.RC_RUNNING):
  748. return
  749. if self._parse_rc(ln, self.RC_CONNECTED):
  750. return
  751. if self._parse_rc(ln, self.RC_ERROR):
  752. return
  753. if self._parse_rc(ln, self.RC_EXIT):
  754. return
  755. print "Unknown GDB/MI result: '%s'" % ln
  756. class GDBMIStreamConsoleHandler(GDBMIOutStreamHandler):
  757. """GDB/MI console stream handler class
  758. """
  759. TAG = '~'
  760. def dbg_corefile(args):
  761. """ Command to load core dump from file or flash and run GDB debug session with it
  762. """
  763. global CLOSE_FDS
  764. loader = None
  765. if not args.core:
  766. loader = ESPCoreDumpFlashLoader(args.off, port=args.port)
  767. core_fname = loader.create_corefile(args.save_core)
  768. if not core_fname:
  769. print "Failed to create corefile!"
  770. loader.cleanup()
  771. return
  772. else:
  773. core_fname = args.core
  774. if args.core_format and args.core_format != 'elf':
  775. loader = ESPCoreDumpFileLoader(core_fname, args.core_format == 'b64')
  776. core_fname = loader.create_corefile(args.save_core)
  777. if not core_fname:
  778. print "Failed to create corefile!"
  779. loader.cleanup()
  780. return
  781. p = subprocess.Popen(
  782. bufsize = 0,
  783. args = [args.gdb,
  784. '--nw', # ignore .gdbinit
  785. '--core=%s' % core_fname, # core file
  786. args.prog],
  787. stdin = None, stdout = None, stderr = None,
  788. close_fds = CLOSE_FDS
  789. )
  790. p.wait()
  791. if loader:
  792. if not args.core and not args.save_core:
  793. loader.remove_tmp_file(core_fname)
  794. loader.cleanup()
  795. print 'Done!'
  796. def info_corefile(args):
  797. """ Command to load core dump from file or flash and print it's data in user friendly form
  798. """
  799. global CLOSE_FDS
  800. def gdbmi_console_stream_handler(ln):
  801. sys.stdout.write(ln)
  802. sys.stdout.flush()
  803. def gdbmi_read2prompt(f, out_handlers=None):
  804. while True:
  805. ln = f.readline().rstrip(' \r\n')
  806. if ln == '(gdb)':
  807. break
  808. elif len(ln) == 0:
  809. break
  810. elif out_handlers:
  811. for h in out_handlers:
  812. if ln.startswith(out_handlers[h].TAG):
  813. out_handlers[h].execute(ln)
  814. break
  815. def gdbmi_start(handlers):
  816. p = subprocess.Popen(
  817. bufsize = 0,
  818. args = [args.gdb,
  819. '--quiet', # inhibit dumping info at start-up
  820. '--nx', # inhibit window interface
  821. '--nw', # ignore .gdbinit
  822. '--interpreter=mi2', # use GDB/MI v2
  823. '--core=%s' % core_fname, # core file
  824. args.prog],
  825. stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.STDOUT,
  826. close_fds = CLOSE_FDS
  827. )
  828. gdbmi_read2prompt(p.stdout, handlers)
  829. return p
  830. def gdbmi_getinfo(p, handlers, gdb_cmd):
  831. for t in handlers:
  832. handlers[t].result_class = None
  833. p.stdin.write("-interpreter-exec console \"%s\"\n" % gdb_cmd)
  834. gdbmi_read2prompt(p.stdout, handlers)
  835. if not handlers[GDBMIResultHandler.TAG].result_class or handlers[GDBMIResultHandler.TAG].result_class == GDBMIResultHandler.RC_EXIT:
  836. print "GDB exited (%s / %s)!" % (handlers[GDBMIResultHandler.TAG].result_class, handlers[GDBMIResultHandler.TAG].result_str)
  837. p.wait()
  838. print "Problem occured! GDB exited, restart it."
  839. p = gdbmi_start(handlers)
  840. elif handlers[GDBMIResultHandler.TAG].result_class != GDBMIResultHandler.RC_DONE:
  841. print "GDB/MI command failed (%s / %s)!" % (handlers[GDBMIResultHandler.TAG].result_class, handlers[GDBMIResultHandler.TAG].result_str)
  842. return p
  843. loader = None
  844. if not args.core:
  845. loader = ESPCoreDumpFlashLoader(args.off, port=args.port)
  846. core_fname = loader.create_corefile(args.save_core)
  847. if not core_fname:
  848. print "Failed to create corefile!"
  849. loader.cleanup()
  850. return
  851. else:
  852. core_fname = args.core
  853. if args.core_format and args.core_format != 'elf':
  854. loader = ESPCoreDumpFileLoader(core_fname, args.core_format == 'b64')
  855. core_fname = loader.create_corefile(args.save_core)
  856. if not core_fname:
  857. print "Failed to create corefile!"
  858. loader.cleanup()
  859. return
  860. exe_elf = ESPCoreDumpElfFile(args.prog)
  861. core_elf = ESPCoreDumpElfFile(core_fname)
  862. merged_segs = []
  863. core_segs = core_elf.program_segments
  864. for s in exe_elf.sections:
  865. merged = False
  866. for ps in core_segs:
  867. if ps.addr <= s.addr and ps.addr + len(ps.data) >= s.addr:
  868. # sec: |XXXXXXXXXX|
  869. # seg: |...XXX.............|
  870. seg_addr = ps.addr
  871. if ps.addr + len(ps.data) <= s.addr + len(s.data):
  872. # sec: |XXXXXXXXXX|
  873. # seg: |XXXXXXXXXXX...|
  874. # merged: |XXXXXXXXXXXXXX|
  875. seg_len = len(s.data) + (s.addr - ps.addr)
  876. else:
  877. # sec: |XXXXXXXXXX|
  878. # seg: |XXXXXXXXXXXXXXXXX|
  879. # merged: |XXXXXXXXXXXXXXXXX|
  880. seg_len = len(ps.data)
  881. merged_segs.append((s.name, seg_addr, seg_len, s.attr_str(), True))
  882. core_segs.remove(ps)
  883. merged = True
  884. elif ps.addr >= s.addr and ps.addr <= s.addr + len(s.data):
  885. # sec: |XXXXXXXXXX|
  886. # seg: |...XXX.............|
  887. seg_addr = s.addr
  888. if (ps.addr + len(ps.data)) >= (s.addr + len(s.data)):
  889. # sec: |XXXXXXXXXX|
  890. # seg: |..XXXXXXXXXXX|
  891. # merged: |XXXXXXXXXXXXX|
  892. seg_len = len(s.data) + (ps.addr + len(ps.data)) - (s.addr + len(s.data))
  893. else:
  894. # sec: |XXXXXXXXXX|
  895. # seg: |XXXXXX|
  896. # merged: |XXXXXXXXXX|
  897. seg_len = len(s.data)
  898. merged_segs.append((s.name, seg_addr, seg_len, s.attr_str(), True))
  899. core_segs.remove(ps)
  900. merged = True
  901. if not merged:
  902. merged_segs.append((s.name, s.addr, len(s.data), s.attr_str(), False))
  903. handlers = {}
  904. handlers[GDBMIResultHandler.TAG] = GDBMIResultHandler(verbose=False)
  905. handlers[GDBMIStreamConsoleHandler.TAG] = GDBMIStreamConsoleHandler(None, verbose=False)
  906. p = gdbmi_start(handlers)
  907. print "==============================================================="
  908. print "==================== ESP32 CORE DUMP START ===================="
  909. handlers[GDBMIResultHandler.TAG].result_class = None
  910. handlers[GDBMIStreamConsoleHandler.TAG].func = gdbmi_console_stream_handler
  911. print "\n================== CURRENT THREAD REGISTERS ==================="
  912. p = gdbmi_getinfo(p, handlers, "info registers")
  913. print "\n==================== CURRENT THREAD STACK ====================="
  914. p = gdbmi_getinfo(p, handlers, "bt")
  915. print "\n======================== THREADS INFO ========================="
  916. p = gdbmi_getinfo(p, handlers, "info threads")
  917. print "\n======================= ALL MEMORY REGIONS ========================"
  918. print "Name Address Size Attrs"
  919. for ms in merged_segs:
  920. print "%s 0x%x 0x%x %s" % (ms[0], ms[1], ms[2], ms[3])
  921. for cs in core_segs:
  922. print ".coredump.tasks 0x%x 0x%x %s" % (cs.addr, len(cs.data), cs.attr_str())
  923. if args.print_mem:
  924. print "\n====================== CORE DUMP MEMORY CONTENTS ========================"
  925. for cs in core_elf.program_segments:
  926. print ".coredump.tasks 0x%x 0x%x %s" % (cs.addr, len(cs.data), cs.attr_str())
  927. p = gdbmi_getinfo(p, handlers, "x/%dx 0x%x" % (len(cs.data)/4, cs.addr))
  928. print "\n===================== ESP32 CORE DUMP END ====================="
  929. print "==============================================================="
  930. p.stdin.write('q\n')
  931. p.wait()
  932. p.stdin.close()
  933. p.stdout.close()
  934. if loader:
  935. if not args.core and not args.save_core:
  936. loader.remove_tmp_file(core_fname)
  937. loader.cleanup()
  938. print 'Done!'
  939. def main():
  940. parser = argparse.ArgumentParser(description='espcoredump.py v%s - ESP32 Core Dump Utility' % __version__, prog='espcoredump')
  941. parser.add_argument('--chip', '-c',
  942. help='Target chip type',
  943. choices=['auto', 'esp32'],
  944. default=os.environ.get('ESPTOOL_CHIP', 'auto'))
  945. parser.add_argument(
  946. '--port', '-p',
  947. help='Serial port device',
  948. default=os.environ.get('ESPTOOL_PORT', esptool.ESPLoader.DEFAULT_PORT))
  949. parser.add_argument(
  950. '--baud', '-b',
  951. help='Serial port baud rate used when flashing/reading',
  952. type=int,
  953. default=os.environ.get('ESPTOOL_BAUD', esptool.ESPLoader.ESP_ROM_BAUD))
  954. subparsers = parser.add_subparsers(
  955. dest='operation',
  956. help='Run coredumper {command} -h for additional help')
  957. parser_debug_coredump = subparsers.add_parser(
  958. 'dbg_corefile',
  959. help='Starts GDB debugging session with specified corefile')
  960. parser_debug_coredump.add_argument('--gdb', '-g', help='Path to gdb', default='xtensa-esp32-elf-gdb')
  961. parser_debug_coredump.add_argument('--core', '-c', help='Path to core dump file (if skipped core dump will be read from flash)', type=str)
  962. parser_debug_coredump.add_argument('--core-format', '-t', help='(elf, raw or b64). File specified with "-c" is an ELF ("elf"), raw (raw) or base64-encoded (b64) binary', type=str, default='elf')
  963. parser_debug_coredump.add_argument('--off', '-o', help='Ofsset of coredump partition in flash (type "make partition_table" to see).', type=int, default=0x110000)
  964. parser_debug_coredump.add_argument('--save-core', '-s', help='Save core to file. Othwerwise temporary core file will be deleted. Ignored with "-c"', type=str)
  965. parser_debug_coredump.add_argument('prog', help='Path to program\'s ELF binary', type=str)
  966. parser_info_coredump = subparsers.add_parser(
  967. 'info_corefile',
  968. help='Print core dump info from file')
  969. parser_info_coredump.add_argument('--gdb', '-g', help='Path to gdb', default='xtensa-esp32-elf-gdb')
  970. parser_info_coredump.add_argument('--core', '-c', help='Path to core dump file (if skipped core dump will be read from flash)', type=str)
  971. parser_info_coredump.add_argument('--core-format', '-t', help='(elf, raw or b64). File specified with "-c" is an ELF ("elf"), raw (raw) or base64-encoded (b64) binary', type=str, default='elf')
  972. parser_info_coredump.add_argument('--off', '-o', help='Ofsset of coredump partition in flash (type "make partition_table" to see).', type=int, default=0x110000)
  973. parser_info_coredump.add_argument('--save-core', '-s', help='Save core to file. Othwerwise temporary core file will be deleted. Does not work with "-c"', type=str)
  974. parser_info_coredump.add_argument('--print-mem', '-m', help='Print memory dump', action='store_true')
  975. parser_info_coredump.add_argument('prog', help='Path to program\'s ELF binary', type=str)
  976. # internal sanity check - every operation matches a module function of the same name
  977. for operation in subparsers.choices.keys():
  978. assert operation in globals(), "%s should be a module function" % operation
  979. args = parser.parse_args()
  980. print 'espcoredump.py v%s' % __version__
  981. operation_func = globals()[args.operation]
  982. operation_func(args)
  983. if __name__ == '__main__':
  984. try:
  985. main()
  986. except ESPCoreDumpError as e:
  987. print '\nA fatal error occurred: %s' % e
  988. sys.exit(2)