apptrace.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761
  1. # SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
  2. # SPDX-License-Identifier: Apache-2.0
  3. from __future__ import print_function
  4. import os
  5. import sys
  6. try:
  7. from urlparse import urlparse
  8. except ImportError:
  9. from urllib.parse import urlparse
  10. import os.path
  11. import socketserver as SocketServer
  12. import subprocess
  13. import tempfile
  14. import threading
  15. import time
  16. import elftools.elf.constants as elfconst
  17. import elftools.elf.elffile as elffile
  18. def clock():
  19. if sys.version_info >= (3, 3):
  20. return time.process_time()
  21. else:
  22. return time.clock()
  23. def addr2line(toolchain, elf_path, addr):
  24. """
  25. Creates trace reader.
  26. Parameters
  27. ----------
  28. toolchain : string
  29. toolchain prefix to retrieve source line locations using addresses
  30. elf_path : string
  31. path to ELF file to use
  32. addr : int
  33. address to retrieve source line location
  34. Returns
  35. -------
  36. string
  37. source line location string
  38. """
  39. try:
  40. return subprocess.check_output(['%saddr2line' % toolchain, '-e', elf_path, '0x%x' % addr]).decode('utf-8')
  41. except subprocess.CalledProcessError:
  42. return ''
  43. class ParseError(RuntimeError):
  44. """
  45. Parse error exception
  46. """
  47. def __init__(self, message):
  48. RuntimeError.__init__(self, message)
  49. class ReaderError(RuntimeError):
  50. """
  51. Trace reader error exception
  52. """
  53. def __init__(self, message):
  54. RuntimeError.__init__(self, message)
  55. class ReaderTimeoutError(ReaderError):
  56. """
  57. Trace reader timeout error
  58. """
  59. def __init__(self, tmo, sz):
  60. ReaderError.__init__(self, 'Timeout %f sec while reading %d bytes!' % (tmo, sz))
  61. class ReaderShutdownRequest(ReaderError):
  62. """
  63. Trace reader shutdown request error
  64. Raised when user presses CTRL+C (SIGINT).
  65. """
  66. def __init__(self):
  67. ReaderError.__init__(self, 'Shutdown request!')
  68. class Reader:
  69. """
  70. Base abstract reader class
  71. """
  72. def __init__(self, tmo):
  73. """
  74. Constructor
  75. Parameters
  76. ----------
  77. tmo : int
  78. read timeout
  79. """
  80. self.timeout = tmo
  81. self.need_stop = False
  82. def read(self, sz):
  83. """
  84. Reads a number of bytes
  85. Parameters
  86. ----------
  87. sz : int
  88. number of bytes to read
  89. Returns
  90. -------
  91. bytes object
  92. read bytes
  93. Returns
  94. -------
  95. ReaderTimeoutError
  96. if timeout expires
  97. ReaderShutdownRequest
  98. if SIGINT was received during reading
  99. """
  100. pass
  101. def readline(self):
  102. """
  103. Reads line
  104. Parameters
  105. ----------
  106. sz : int
  107. number of bytes to read
  108. Returns
  109. -------
  110. string
  111. read line
  112. """
  113. pass
  114. def forward(self, sz):
  115. """
  116. Moves read pointer to a number of bytes
  117. Parameters
  118. ----------
  119. sz : int
  120. number of bytes to read
  121. """
  122. pass
  123. def cleanup(self):
  124. """
  125. Cleans up reader
  126. """
  127. self.need_stop = True
  128. class FileReader(Reader):
  129. """
  130. File reader class
  131. """
  132. def __init__(self, path, tmo):
  133. """
  134. Constructor
  135. Parameters
  136. ----------
  137. path : string
  138. path to file to read
  139. tmo : int
  140. see Reader.__init__()
  141. """
  142. Reader.__init__(self, tmo)
  143. self.trace_file_path = path
  144. self.trace_file = open(path, 'rb')
  145. def read(self, sz):
  146. """
  147. see Reader.read()
  148. """
  149. data = b''
  150. start_tm = clock()
  151. while not self.need_stop:
  152. data += self.trace_file.read(sz - len(data))
  153. if len(data) == sz:
  154. break
  155. if self.timeout != -1 and clock() >= start_tm + self.timeout:
  156. raise ReaderTimeoutError(self.timeout, sz)
  157. if self.need_stop:
  158. raise ReaderShutdownRequest()
  159. return data
  160. def get_pos(self):
  161. """
  162. Retrieves current file read position
  163. Returns
  164. -------
  165. int
  166. read position
  167. """
  168. return self.trace_file.tell()
  169. def readline(self, linesep=os.linesep):
  170. """
  171. see Reader.read()
  172. """
  173. line = ''
  174. start_tm = clock()
  175. while not self.need_stop:
  176. line += self.trace_file.readline().decode('utf-8')
  177. if line.endswith(linesep):
  178. break
  179. if self.timeout != -1 and clock() >= start_tm + self.timeout:
  180. raise ReaderTimeoutError(self.timeout, 1)
  181. if self.need_stop:
  182. raise ReaderShutdownRequest()
  183. return line
  184. def forward(self, sz):
  185. """
  186. see Reader.read()
  187. """
  188. cur_pos = self.trace_file.tell()
  189. start_tm = clock()
  190. while not self.need_stop:
  191. file_sz = os.path.getsize(self.trace_file_path)
  192. if file_sz - cur_pos >= sz:
  193. break
  194. if self.timeout != -1 and clock() >= start_tm + self.timeout:
  195. raise ReaderTimeoutError(self.timeout, sz)
  196. if self.need_stop:
  197. raise ReaderShutdownRequest()
  198. self.trace_file.seek(sz, os.SEEK_CUR)
  199. class NetRequestHandler:
  200. """
  201. Handler for incoming network requests (connections, datagrams)
  202. """
  203. def handle(self):
  204. while not self.server.need_stop:
  205. data = self.rfile.read(1024)
  206. if len(data) == 0:
  207. break
  208. self.server.wtrace.write(data)
  209. self.server.wtrace.flush()
  210. class NetReader(FileReader):
  211. """
  212. Base netwoek socket reader class
  213. """
  214. def __init__(self, tmo):
  215. """
  216. see Reader.__init__()
  217. """
  218. fhnd,fname = tempfile.mkstemp()
  219. FileReader.__init__(self, fname, tmo)
  220. self.wtrace = os.fdopen(fhnd, 'wb')
  221. self.server_thread = threading.Thread(target=self.serve_forever)
  222. self.server_thread.start()
  223. def cleanup(self):
  224. """
  225. see Reader.cleanup()
  226. """
  227. FileReader.cleanup(self)
  228. self.shutdown()
  229. self.server_close()
  230. self.server_thread.join()
  231. time.sleep(0.1)
  232. self.trace_file.close()
  233. self.wtrace.close()
  234. class TCPRequestHandler(NetRequestHandler, SocketServer.StreamRequestHandler):
  235. """
  236. Handler for incoming TCP connections
  237. """
  238. pass
  239. class TCPReader(NetReader, SocketServer.TCPServer):
  240. """
  241. TCP socket reader class
  242. """
  243. def __init__(self, host, port, tmo, handler=TCPRequestHandler):
  244. """
  245. Constructor
  246. Parameters
  247. ----------
  248. host : string
  249. see SocketServer.BaseServer.__init__()
  250. port : int
  251. see SocketServer.BaseServer.__init__()
  252. tmo : int
  253. see Reader.__init__()
  254. """
  255. SocketServer.TCPServer.__init__(self, (host, port), handler)
  256. NetReader.__init__(self, tmo)
  257. class UDPRequestHandler(NetRequestHandler, SocketServer.DatagramRequestHandler):
  258. """
  259. Handler for incoming UDP datagrams
  260. """
  261. pass
  262. class UDPReader(NetReader, SocketServer.UDPServer):
  263. """
  264. UDP socket reader class
  265. """
  266. def __init__(self, host, port, tmo, handler=UDPRequestHandler):
  267. """
  268. Constructor
  269. Parameters
  270. ----------
  271. host : string
  272. see SocketServer.BaseServer.__init__()
  273. port : int
  274. see SocketServer.BaseServer.__init__()
  275. tmo : int
  276. see Reader.__init__()
  277. """
  278. SocketServer.UDPServer.__init__(self, (host, port), handler)
  279. NetReader.__init__(self, tmo)
  280. def reader_create(trc_src, tmo, handler=None):
  281. """
  282. Creates trace reader.
  283. Parameters
  284. ----------
  285. trc_src : string
  286. trace source URL. Supports 'file:///path/to/file' or (tcp|udp)://host:port
  287. tmo : int
  288. read timeout
  289. Returns
  290. -------
  291. Reader
  292. reader object or None if URL scheme is not supported
  293. """
  294. url = urlparse(trc_src)
  295. if len(url.scheme) == 0 or url.scheme == 'file':
  296. if os.name == 'nt':
  297. # workaround for Windows path
  298. return FileReader(trc_src[7:], tmo)
  299. else:
  300. return FileReader(url.path, tmo)
  301. if url.scheme == 'tcp':
  302. if handler is not None:
  303. return TCPReader(url.hostname, url.port, tmo, handler)
  304. return TCPReader(url.hostname, url.port, tmo)
  305. if url.scheme == 'udp':
  306. if handler is not None:
  307. return UDPReader(url.hostname, url.port, tmo, handler)
  308. return UDPReader(url.hostname, url.port, tmo)
  309. return None
  310. class TraceEvent:
  311. """
  312. Base class for all trace events.
  313. """
  314. def __init__(self, name, core_id, evt_id):
  315. self.name = name
  316. self.ctx_name = 'None'
  317. self.in_irq = False
  318. self.core_id = core_id
  319. self.id = evt_id
  320. self.ts = 0
  321. self.params = {}
  322. @property
  323. def ctx_desc(self):
  324. if self.in_irq:
  325. return 'IRQ "%s"' % self.ctx_name
  326. return 'task "%s"' % self.ctx_name
  327. def to_jsonable(self):
  328. res = self.__dict__
  329. params = {}
  330. for p in self.params:
  331. params.update(self.params[p].to_jsonable())
  332. res['params'] = params
  333. return res
  334. class TraceDataProcessor:
  335. """
  336. Base abstract class for all trace data processors.
  337. """
  338. def __init__(self, print_events, keep_all_events=False):
  339. """
  340. Constructor.
  341. Parameters
  342. ----------
  343. print_events : bool
  344. if True every event will be printed as they arrive
  345. keep_all_events : bool
  346. if True all events will be kept in self.events in the order they arrive
  347. """
  348. self.print_events = print_events
  349. self.keep_all_events = keep_all_events
  350. self.total_events = 0
  351. self.events = []
  352. # This can be changed by the root procesor that includes several sub-processors.
  353. # It is used access some method of root processor which can contain methods/data common for all sub-processors.
  354. # Common info could be current execution context, info about running tasks, available IRQs etc.
  355. self.root_proc = self
  356. def _print_event(self, event):
  357. """
  358. Base method to print an event.
  359. Parameters
  360. ----------
  361. event : object
  362. Event object
  363. """
  364. print('EVENT[{:d}]: {}'.format(self.total_events, event))
  365. def print_report(self):
  366. """
  367. Base method to print report.
  368. """
  369. print('Processed {:d} events'.format(self.total_events))
  370. def cleanup(self):
  371. """
  372. Base method to make cleanups.
  373. """
  374. pass
  375. def on_new_event(self, event):
  376. """
  377. Base method to process event.
  378. """
  379. if self.print_events:
  380. self._print_event(event)
  381. if self.keep_all_events:
  382. self.events.append(event)
  383. self.total_events += 1
  384. class LogTraceParseError(ParseError):
  385. """
  386. Log trace parse error exception.
  387. """
  388. pass
  389. def get_str_from_elf(felf, str_addr):
  390. """
  391. Retrieves string from ELF file.
  392. Parameters
  393. ----------
  394. felf : elffile.ELFFile
  395. open ELF file handle to retrive format string from
  396. str_addr : int
  397. address of the string
  398. Returns
  399. -------
  400. string
  401. string or None if it was not found
  402. """
  403. tgt_str = ''
  404. for sect in felf.iter_sections():
  405. if sect['sh_addr'] == 0 or (sect['sh_flags'] & elfconst.SH_FLAGS.SHF_ALLOC) == 0:
  406. continue
  407. if str_addr < sect['sh_addr'] or str_addr >= sect['sh_addr'] + sect['sh_size']:
  408. continue
  409. sec_data = sect.data()
  410. for i in range(str_addr - sect['sh_addr'], sect['sh_size']):
  411. if type(sec_data) is str:
  412. ch = sec_data[i]
  413. else:
  414. ch = str(chr(sec_data[i]))
  415. if ch == '\0':
  416. break
  417. tgt_str += ch
  418. if len(tgt_str) > 0:
  419. return tgt_str
  420. return None
  421. class LogTraceEvent:
  422. """
  423. Log trace event.
  424. """
  425. def __init__(self, fmt_addr, log_args):
  426. """
  427. Constructor.
  428. Parameters
  429. ----------
  430. fmt_addr : int
  431. address of the format string
  432. log_args : list
  433. list of log message arguments
  434. """
  435. self.fmt_addr = fmt_addr
  436. self.args = log_args
  437. def get_message(self, felf):
  438. """
  439. Retrieves log message.
  440. Parameters
  441. ----------
  442. felf : elffile.ELFFile
  443. open ELF file handle to retrive format string from
  444. Returns
  445. -------
  446. string
  447. formatted log message
  448. Raises
  449. ------
  450. LogTraceParseError
  451. if format string has not been found in ELF file
  452. """
  453. fmt_str = get_str_from_elf(felf, self.fmt_addr)
  454. if not fmt_str:
  455. raise LogTraceParseError('Failed to find format string for 0x%x' % self.fmt_addr)
  456. prcnt_idx = 0
  457. for i, arg in enumerate(self.args):
  458. prcnt_idx = fmt_str.find('%', prcnt_idx, -2) # TODO: check str ending with %
  459. if prcnt_idx == -1:
  460. break
  461. prcnt_idx += 1 # goto next char
  462. if fmt_str[prcnt_idx] == 's':
  463. # find string
  464. arg_str = get_str_from_elf(felf, self.args[i])
  465. if arg_str:
  466. self.args[i] = arg_str
  467. else:
  468. self.args[i] = '<None>'
  469. fmt_str = fmt_str.replace('%p', '%x')
  470. return fmt_str % tuple(self.args)
  471. class BaseLogTraceDataProcessorImpl:
  472. """
  473. Base implementation for log data processors.
  474. """
  475. def __init__(self, print_log_events=False, elf_path=''):
  476. """
  477. Constructor.
  478. Parameters
  479. ----------
  480. print_log_events : bool
  481. if True every log event will be printed as they arrive
  482. elf_path : string
  483. path to ELF file to retrieve format strings for log messages
  484. """
  485. if len(elf_path):
  486. self.felf = elffile.ELFFile(open(elf_path, 'rb'))
  487. else:
  488. self.felf = None
  489. self.print_log_events = print_log_events
  490. self.messages = []
  491. def cleanup(self):
  492. """
  493. Cleanup
  494. """
  495. if self.felf:
  496. self.felf.stream.close()
  497. def print_report(self):
  498. """
  499. Prints log report
  500. """
  501. print('=============== LOG TRACE REPORT ===============')
  502. print('Processed {:d} log messages.'.format(len(self.messages)))
  503. def on_new_event(self, event):
  504. """
  505. Processes log events.
  506. Parameters
  507. ----------
  508. event : LogTraceEvent
  509. Event object.
  510. """
  511. msg = event.get_message(self.felf)
  512. self.messages.append(msg)
  513. if self.print_log_events:
  514. print(msg, end='')
  515. class HeapTraceParseError(ParseError):
  516. """
  517. Heap trace parse error exception.
  518. """
  519. pass
  520. class HeapTraceDuplicateAllocError(HeapTraceParseError):
  521. """
  522. Heap trace duplicate allocation error exception.
  523. """
  524. def __init__(self, addr, new_size, prev_size):
  525. """
  526. Constructor.
  527. Parameters
  528. ----------
  529. addr : int
  530. memory block address
  531. new_size : int
  532. size of the new allocation
  533. prev_size : int
  534. size of the previous allocation
  535. """
  536. HeapTraceParseError.__init__(self, """Duplicate alloc @ 0x{:x}!
  537. New alloc is {:d} bytes,
  538. previous is {:d} bytes.""".format(addr, new_size, prev_size))
  539. class HeapTraceEvent:
  540. """
  541. Heap trace event.
  542. """
  543. def __init__(self, trace_event, alloc, toolchain='', elf_path=''):
  544. """
  545. Constructor.
  546. Parameters
  547. ----------
  548. sys_view_event : TraceEvent
  549. trace event object related to this heap event
  550. alloc : bool
  551. True for allocation event, otherwise False
  552. toolchain_pref : string
  553. toolchain prefix to retrieve source line locations using addresses
  554. elf_path : string
  555. path to ELF file to retrieve format strings for log messages
  556. """
  557. self.trace_event = trace_event
  558. self.alloc = alloc
  559. self.toolchain = toolchain
  560. self.elf_path = elf_path
  561. if self.alloc:
  562. self.size = self.trace_event.params['size'].value
  563. else:
  564. self.size = None
  565. @property
  566. def addr(self):
  567. return self.trace_event.params['addr'].value
  568. @property
  569. def callers(self):
  570. return self.trace_event.params['callers'].value
  571. def __repr__(self):
  572. if len(self.toolchain) and len(self.elf_path):
  573. callers = os.linesep
  574. for addr in self.trace_event.params['callers'].value:
  575. if addr == 0:
  576. break
  577. callers += '{}'.format(addr2line(self.toolchain, self.elf_path, addr))
  578. else:
  579. callers = ''
  580. for addr in self.trace_event.params['callers'].value:
  581. if addr == 0:
  582. break
  583. if len(callers):
  584. callers += ':'
  585. callers += '0x{:x}'.format(addr)
  586. if self.alloc:
  587. return '[{:.9f}] HEAP: Allocated {:d} bytes @ 0x{:x} from {} on core {:d} by: {}'.format(self.trace_event.ts,
  588. self.size, self.addr,
  589. self.trace_event.ctx_desc,
  590. self.trace_event.core_id,
  591. callers)
  592. else:
  593. return '[{:.9f}] HEAP: Freed bytes @ 0x{:x} from {} on core {:d} by: {}'.format(self.trace_event.ts,
  594. self.addr, self.trace_event.ctx_desc,
  595. self.trace_event.core_id, callers)
  596. class BaseHeapTraceDataProcessorImpl:
  597. """
  598. Base implementation for heap data processors.
  599. """
  600. def __init__(self, print_heap_events=False):
  601. """
  602. Constructor.
  603. Parameters
  604. ----------
  605. print_heap_events : bool
  606. if True every heap event will be printed as they arrive
  607. """
  608. self._alloc_addrs = {}
  609. self.allocs = []
  610. self.frees = []
  611. self.heap_events_count = 0
  612. self.print_heap_events = print_heap_events
  613. def on_new_event(self, event):
  614. """
  615. Processes heap events. Keeps track of active allocations list.
  616. Parameters
  617. ----------
  618. event : HeapTraceEvent
  619. Event object.
  620. """
  621. self.heap_events_count += 1
  622. if self.print_heap_events:
  623. print(event)
  624. if event.alloc:
  625. if event.addr in self._alloc_addrs:
  626. raise HeapTraceDuplicateAllocError(event.addr, event.size, self._alloc_addrs[event.addr].size)
  627. self.allocs.append(event)
  628. self._alloc_addrs[event.addr] = event
  629. else:
  630. # do not treat free on unknown addresses as errors, because these blocks coould be allocated when tracing was disabled
  631. if event.addr in self._alloc_addrs:
  632. event.size = self._alloc_addrs[event.addr].size
  633. self.allocs.remove(self._alloc_addrs[event.addr])
  634. del self._alloc_addrs[event.addr]
  635. else:
  636. self.frees.append(event)
  637. def print_report(self):
  638. """
  639. Prints heap report
  640. """
  641. print('=============== HEAP TRACE REPORT ===============')
  642. print('Processed {:d} heap events.'.format(self.heap_events_count))
  643. if len(self.allocs) == 0:
  644. print('OK - Heap errors was not found.')
  645. return
  646. leaked_bytes = 0
  647. for alloc in self.allocs:
  648. leaked_bytes += alloc.size
  649. print(alloc)
  650. for free in self.frees:
  651. if free.addr > alloc.addr and free.addr <= alloc.addr + alloc.size:
  652. print('Possible wrong free operation found')
  653. print(free)
  654. print('Found {:d} leaked bytes in {:d} blocks.'.format(leaked_bytes, len(self.allocs)))