apptrace.py 21 KB

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