apptrace.py 20 KB

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