apptrace.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757
  1. # SPDX-FileCopyrightText: 2022 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):
  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), TCPRequestHandler)
  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):
  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), UDPRequestHandler)
  279. NetReader.__init__(self, tmo)
  280. def reader_create(trc_src, tmo):
  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. return TCPReader(url.hostname, url.port, tmo)
  303. if url.scheme == 'udp':
  304. return UDPReader(url.hostname, url.port, tmo)
  305. return None
  306. class TraceEvent:
  307. """
  308. Base class for all trace events.
  309. """
  310. def __init__(self, name, core_id, evt_id):
  311. self.name = name
  312. self.ctx_name = 'None'
  313. self.in_irq = False
  314. self.core_id = core_id
  315. self.id = evt_id
  316. self.ts = 0
  317. self.params = {}
  318. @property
  319. def ctx_desc(self):
  320. if self.in_irq:
  321. return 'IRQ "%s"' % self.ctx_name
  322. return 'task "%s"' % self.ctx_name
  323. def to_jsonable(self):
  324. res = self.__dict__
  325. params = {}
  326. for p in self.params:
  327. params.update(self.params[p].to_jsonable())
  328. res['params'] = params
  329. return res
  330. class TraceDataProcessor:
  331. """
  332. Base abstract class for all trace data processors.
  333. """
  334. def __init__(self, print_events, keep_all_events=False):
  335. """
  336. Constructor.
  337. Parameters
  338. ----------
  339. print_events : bool
  340. if True every event will be printed as they arrive
  341. keep_all_events : bool
  342. if True all events will be kept in self.events in the order they arrive
  343. """
  344. self.print_events = print_events
  345. self.keep_all_events = keep_all_events
  346. self.total_events = 0
  347. self.events = []
  348. # This can be changed by the root procesor that includes several sub-processors.
  349. # It is used access some method of root processor which can contain methods/data common for all sub-processors.
  350. # Common info could be current execution context, info about running tasks, available IRQs etc.
  351. self.root_proc = self
  352. def _print_event(self, event):
  353. """
  354. Base method to print an event.
  355. Parameters
  356. ----------
  357. event : object
  358. Event object
  359. """
  360. print('EVENT[{:d}]: {}'.format(self.total_events, event))
  361. def print_report(self):
  362. """
  363. Base method to print report.
  364. """
  365. print('Processed {:d} events'.format(self.total_events))
  366. def cleanup(self):
  367. """
  368. Base method to make cleanups.
  369. """
  370. pass
  371. def on_new_event(self, event):
  372. """
  373. Base method to process event.
  374. """
  375. if self.print_events:
  376. self._print_event(event)
  377. if self.keep_all_events:
  378. self.events.append(event)
  379. self.total_events += 1
  380. class LogTraceParseError(ParseError):
  381. """
  382. Log trace parse error exception.
  383. """
  384. pass
  385. def get_str_from_elf(felf, str_addr):
  386. """
  387. Retrieves string from ELF file.
  388. Parameters
  389. ----------
  390. felf : elffile.ELFFile
  391. open ELF file handle to retrive format string from
  392. str_addr : int
  393. address of the string
  394. Returns
  395. -------
  396. string
  397. string or None if it was not found
  398. """
  399. tgt_str = ''
  400. for sect in felf.iter_sections():
  401. if sect['sh_addr'] == 0 or (sect['sh_flags'] & elfconst.SH_FLAGS.SHF_ALLOC) == 0:
  402. continue
  403. if str_addr < sect['sh_addr'] or str_addr >= sect['sh_addr'] + sect['sh_size']:
  404. continue
  405. sec_data = sect.data()
  406. for i in range(str_addr - sect['sh_addr'], sect['sh_size']):
  407. if type(sec_data) is str:
  408. ch = sec_data[i]
  409. else:
  410. ch = str(chr(sec_data[i]))
  411. if ch == '\0':
  412. break
  413. tgt_str += ch
  414. if len(tgt_str) > 0:
  415. return tgt_str
  416. return None
  417. class LogTraceEvent:
  418. """
  419. Log trace event.
  420. """
  421. def __init__(self, fmt_addr, log_args):
  422. """
  423. Constructor.
  424. Parameters
  425. ----------
  426. fmt_addr : int
  427. address of the format string
  428. log_args : list
  429. list of log message arguments
  430. """
  431. self.fmt_addr = fmt_addr
  432. self.args = log_args
  433. def get_message(self, felf):
  434. """
  435. Retrieves log message.
  436. Parameters
  437. ----------
  438. felf : elffile.ELFFile
  439. open ELF file handle to retrive format string from
  440. Returns
  441. -------
  442. string
  443. formatted log message
  444. Raises
  445. ------
  446. LogTraceParseError
  447. if format string has not been found in ELF file
  448. """
  449. fmt_str = get_str_from_elf(felf, self.fmt_addr)
  450. if not fmt_str:
  451. raise LogTraceParseError('Failed to find format string for 0x%x' % self.fmt_addr)
  452. prcnt_idx = 0
  453. for i, arg in enumerate(self.args):
  454. prcnt_idx = fmt_str.find('%', prcnt_idx, -2) # TODO: check str ending with %
  455. if prcnt_idx == -1:
  456. break
  457. prcnt_idx += 1 # goto next char
  458. if fmt_str[prcnt_idx] == 's':
  459. # find string
  460. arg_str = get_str_from_elf(felf, self.args[i])
  461. if arg_str:
  462. self.args[i] = arg_str
  463. else:
  464. self.args[i] = '<None>'
  465. fmt_str = fmt_str.replace('%p', '%x')
  466. return fmt_str % tuple(self.args)
  467. class BaseLogTraceDataProcessorImpl:
  468. """
  469. Base implementation for log data processors.
  470. """
  471. def __init__(self, print_log_events=False, elf_path=''):
  472. """
  473. Constructor.
  474. Parameters
  475. ----------
  476. print_log_events : bool
  477. if True every log event will be printed as they arrive
  478. elf_path : string
  479. path to ELF file to retrieve format strings for log messages
  480. """
  481. if len(elf_path):
  482. self.felf = elffile.ELFFile(open(elf_path, 'rb'))
  483. else:
  484. self.felf = None
  485. self.print_log_events = print_log_events
  486. self.messages = []
  487. def cleanup(self):
  488. """
  489. Cleanup
  490. """
  491. if self.felf:
  492. self.felf.stream.close()
  493. def print_report(self):
  494. """
  495. Prints log report
  496. """
  497. print('=============== LOG TRACE REPORT ===============')
  498. print('Processed {:d} log messages.'.format(len(self.messages)))
  499. def on_new_event(self, event):
  500. """
  501. Processes log events.
  502. Parameters
  503. ----------
  504. event : LogTraceEvent
  505. Event object.
  506. """
  507. msg = event.get_message(self.felf)
  508. self.messages.append(msg)
  509. if self.print_log_events:
  510. print(msg, end='')
  511. class HeapTraceParseError(ParseError):
  512. """
  513. Heap trace parse error exception.
  514. """
  515. pass
  516. class HeapTraceDuplicateAllocError(HeapTraceParseError):
  517. """
  518. Heap trace duplicate allocation error exception.
  519. """
  520. def __init__(self, addr, new_size, prev_size):
  521. """
  522. Constructor.
  523. Parameters
  524. ----------
  525. addr : int
  526. memory block address
  527. new_size : int
  528. size of the new allocation
  529. prev_size : int
  530. size of the previous allocation
  531. """
  532. HeapTraceParseError.__init__(self, """Duplicate alloc @ 0x{:x}!
  533. New alloc is {:d} bytes,
  534. previous is {:d} bytes.""".format(addr, new_size, prev_size))
  535. class HeapTraceEvent:
  536. """
  537. Heap trace event.
  538. """
  539. def __init__(self, trace_event, alloc, toolchain='', elf_path=''):
  540. """
  541. Constructor.
  542. Parameters
  543. ----------
  544. sys_view_event : TraceEvent
  545. trace event object related to this heap event
  546. alloc : bool
  547. True for allocation event, otherwise False
  548. toolchain_pref : string
  549. toolchain prefix to retrieve source line locations using addresses
  550. elf_path : string
  551. path to ELF file to retrieve format strings for log messages
  552. """
  553. self.trace_event = trace_event
  554. self.alloc = alloc
  555. self.toolchain = toolchain
  556. self.elf_path = elf_path
  557. if self.alloc:
  558. self.size = self.trace_event.params['size'].value
  559. else:
  560. self.size = None
  561. @property
  562. def addr(self):
  563. return self.trace_event.params['addr'].value
  564. @property
  565. def callers(self):
  566. return self.trace_event.params['callers'].value
  567. def __repr__(self):
  568. if len(self.toolchain) and len(self.elf_path):
  569. callers = os.linesep
  570. for addr in self.trace_event.params['callers'].value:
  571. if addr == 0:
  572. break
  573. callers += '{}'.format(addr2line(self.toolchain, self.elf_path, addr))
  574. else:
  575. callers = ''
  576. for addr in self.trace_event.params['callers'].value:
  577. if addr == 0:
  578. break
  579. if len(callers):
  580. callers += ':'
  581. callers += '0x{:x}'.format(addr)
  582. if self.alloc:
  583. return '[{:.9f}] HEAP: Allocated {:d} bytes @ 0x{:x} from {} on core {:d} by: {}'.format(self.trace_event.ts,
  584. self.size, self.addr,
  585. self.trace_event.ctx_desc,
  586. self.trace_event.core_id,
  587. callers)
  588. else:
  589. return '[{:.9f}] HEAP: Freed bytes @ 0x{:x} from {} on core {:d} by: {}'.format(self.trace_event.ts,
  590. self.addr, self.trace_event.ctx_desc,
  591. self.trace_event.core_id, callers)
  592. class BaseHeapTraceDataProcessorImpl:
  593. """
  594. Base implementation for heap data processors.
  595. """
  596. def __init__(self, print_heap_events=False):
  597. """
  598. Constructor.
  599. Parameters
  600. ----------
  601. print_heap_events : bool
  602. if True every heap event will be printed as they arrive
  603. """
  604. self._alloc_addrs = {}
  605. self.allocs = []
  606. self.frees = []
  607. self.heap_events_count = 0
  608. self.print_heap_events = print_heap_events
  609. def on_new_event(self, event):
  610. """
  611. Processes heap events. Keeps track of active allocations list.
  612. Parameters
  613. ----------
  614. event : HeapTraceEvent
  615. Event object.
  616. """
  617. self.heap_events_count += 1
  618. if self.print_heap_events:
  619. print(event)
  620. if event.alloc:
  621. if event.addr in self._alloc_addrs:
  622. raise HeapTraceDuplicateAllocError(event.addr, event.size, self._alloc_addrs[event.addr].size)
  623. self.allocs.append(event)
  624. self._alloc_addrs[event.addr] = event
  625. else:
  626. # do not treat free on unknown addresses as errors, because these blocks coould be allocated when tracing was disabled
  627. if event.addr in self._alloc_addrs:
  628. event.size = self._alloc_addrs[event.addr].size
  629. self.allocs.remove(self._alloc_addrs[event.addr])
  630. del self._alloc_addrs[event.addr]
  631. else:
  632. self.frees.append(event)
  633. def print_report(self):
  634. """
  635. Prints heap report
  636. """
  637. print('=============== HEAP TRACE REPORT ===============')
  638. print('Processed {:d} heap events.'.format(self.heap_events_count))
  639. if len(self.allocs) == 0:
  640. print('OK - Heap errors was not found.')
  641. return
  642. leaked_bytes = 0
  643. for alloc in self.allocs:
  644. leaked_bytes += alloc.size
  645. print(alloc)
  646. for free in self.frees:
  647. if free.addr > alloc.addr and free.addr <= alloc.addr + alloc.size:
  648. print('Possible wrong free operation found')
  649. print(free)
  650. print('Found {:d} leaked bytes in {:d} blocks.'.format(leaked_bytes, len(self.allocs)))