| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757 |
- # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
- # SPDX-License-Identifier: Apache-2.0
- from __future__ import print_function
- import os
- import sys
- try:
- from urlparse import urlparse
- except ImportError:
- from urllib.parse import urlparse
- import os.path
- import socketserver as SocketServer
- import subprocess
- import tempfile
- import threading
- import time
- import elftools.elf.constants as elfconst
- import elftools.elf.elffile as elffile
- def clock():
- if sys.version_info >= (3, 3):
- return time.process_time()
- else:
- return time.clock()
- def addr2line(toolchain, elf_path, addr):
- """
- Creates trace reader.
- Parameters
- ----------
- toolchain : string
- toolchain prefix to retrieve source line locations using addresses
- elf_path : string
- path to ELF file to use
- addr : int
- address to retrieve source line location
- Returns
- -------
- string
- source line location string
- """
- try:
- return subprocess.check_output(['%saddr2line' % toolchain, '-e', elf_path, '0x%x' % addr]).decode('utf-8')
- except subprocess.CalledProcessError:
- return ''
- class ParseError(RuntimeError):
- """
- Parse error exception
- """
- def __init__(self, message):
- RuntimeError.__init__(self, message)
- class ReaderError(RuntimeError):
- """
- Trace reader error exception
- """
- def __init__(self, message):
- RuntimeError.__init__(self, message)
- class ReaderTimeoutError(ReaderError):
- """
- Trace reader timeout error
- """
- def __init__(self, tmo, sz):
- ReaderError.__init__(self, 'Timeout %f sec while reading %d bytes!' % (tmo, sz))
- class ReaderShutdownRequest(ReaderError):
- """
- Trace reader shutdown request error
- Raised when user presses CTRL+C (SIGINT).
- """
- def __init__(self):
- ReaderError.__init__(self, 'Shutdown request!')
- class Reader:
- """
- Base abstract reader class
- """
- def __init__(self, tmo):
- """
- Constructor
- Parameters
- ----------
- tmo : int
- read timeout
- """
- self.timeout = tmo
- self.need_stop = False
- def read(self, sz):
- """
- Reads a number of bytes
- Parameters
- ----------
- sz : int
- number of bytes to read
- Returns
- -------
- bytes object
- read bytes
- Returns
- -------
- ReaderTimeoutError
- if timeout expires
- ReaderShutdownRequest
- if SIGINT was received during reading
- """
- pass
- def readline(self):
- """
- Reads line
- Parameters
- ----------
- sz : int
- number of bytes to read
- Returns
- -------
- string
- read line
- """
- pass
- def forward(self, sz):
- """
- Moves read pointer to a number of bytes
- Parameters
- ----------
- sz : int
- number of bytes to read
- """
- pass
- def cleanup(self):
- """
- Cleans up reader
- """
- self.need_stop = True
- class FileReader(Reader):
- """
- File reader class
- """
- def __init__(self, path, tmo):
- """
- Constructor
- Parameters
- ----------
- path : string
- path to file to read
- tmo : int
- see Reader.__init__()
- """
- Reader.__init__(self, tmo)
- self.trace_file_path = path
- self.trace_file = open(path, 'rb')
- def read(self, sz):
- """
- see Reader.read()
- """
- data = b''
- start_tm = clock()
- while not self.need_stop:
- data += self.trace_file.read(sz - len(data))
- if len(data) == sz:
- break
- if self.timeout != -1 and clock() >= start_tm + self.timeout:
- raise ReaderTimeoutError(self.timeout, sz)
- if self.need_stop:
- raise ReaderShutdownRequest()
- return data
- def get_pos(self):
- """
- Retrieves current file read position
- Returns
- -------
- int
- read position
- """
- return self.trace_file.tell()
- def readline(self, linesep=os.linesep):
- """
- see Reader.read()
- """
- line = ''
- start_tm = clock()
- while not self.need_stop:
- line += self.trace_file.readline().decode('utf-8')
- if line.endswith(linesep):
- break
- if self.timeout != -1 and clock() >= start_tm + self.timeout:
- raise ReaderTimeoutError(self.timeout, 1)
- if self.need_stop:
- raise ReaderShutdownRequest()
- return line
- def forward(self, sz):
- """
- see Reader.read()
- """
- cur_pos = self.trace_file.tell()
- start_tm = clock()
- while not self.need_stop:
- file_sz = os.path.getsize(self.trace_file_path)
- if file_sz - cur_pos >= sz:
- break
- if self.timeout != -1 and clock() >= start_tm + self.timeout:
- raise ReaderTimeoutError(self.timeout, sz)
- if self.need_stop:
- raise ReaderShutdownRequest()
- self.trace_file.seek(sz, os.SEEK_CUR)
- class NetRequestHandler:
- """
- Handler for incoming network requests (connections, datagrams)
- """
- def handle(self):
- while not self.server.need_stop:
- data = self.rfile.read(1024)
- if len(data) == 0:
- break
- self.server.wtrace.write(data)
- self.server.wtrace.flush()
- class NetReader(FileReader):
- """
- Base netwoek socket reader class
- """
- def __init__(self, tmo):
- """
- see Reader.__init__()
- """
- fhnd,fname = tempfile.mkstemp()
- FileReader.__init__(self, fname, tmo)
- self.wtrace = os.fdopen(fhnd, 'wb')
- self.server_thread = threading.Thread(target=self.serve_forever)
- self.server_thread.start()
- def cleanup(self):
- """
- see Reader.cleanup()
- """
- FileReader.cleanup(self)
- self.shutdown()
- self.server_close()
- self.server_thread.join()
- time.sleep(0.1)
- self.trace_file.close()
- self.wtrace.close()
- class TCPRequestHandler(NetRequestHandler, SocketServer.StreamRequestHandler):
- """
- Handler for incoming TCP connections
- """
- pass
- class TCPReader(NetReader, SocketServer.TCPServer):
- """
- TCP socket reader class
- """
- def __init__(self, host, port, tmo):
- """
- Constructor
- Parameters
- ----------
- host : string
- see SocketServer.BaseServer.__init__()
- port : int
- see SocketServer.BaseServer.__init__()
- tmo : int
- see Reader.__init__()
- """
- SocketServer.TCPServer.__init__(self, (host, port), TCPRequestHandler)
- NetReader.__init__(self, tmo)
- class UDPRequestHandler(NetRequestHandler, SocketServer.DatagramRequestHandler):
- """
- Handler for incoming UDP datagrams
- """
- pass
- class UDPReader(NetReader, SocketServer.UDPServer):
- """
- UDP socket reader class
- """
- def __init__(self, host, port, tmo):
- """
- Constructor
- Parameters
- ----------
- host : string
- see SocketServer.BaseServer.__init__()
- port : int
- see SocketServer.BaseServer.__init__()
- tmo : int
- see Reader.__init__()
- """
- SocketServer.UDPServer.__init__(self, (host, port), UDPRequestHandler)
- NetReader.__init__(self, tmo)
- def reader_create(trc_src, tmo):
- """
- Creates trace reader.
- Parameters
- ----------
- trc_src : string
- trace source URL. Supports 'file:///path/to/file' or (tcp|udp)://host:port
- tmo : int
- read timeout
- Returns
- -------
- Reader
- reader object or None if URL scheme is not supported
- """
- url = urlparse(trc_src)
- if len(url.scheme) == 0 or url.scheme == 'file':
- if os.name == 'nt':
- # workaround for Windows path
- return FileReader(trc_src[7:], tmo)
- else:
- return FileReader(url.path, tmo)
- if url.scheme == 'tcp':
- return TCPReader(url.hostname, url.port, tmo)
- if url.scheme == 'udp':
- return UDPReader(url.hostname, url.port, tmo)
- return None
- class TraceEvent:
- """
- Base class for all trace events.
- """
- def __init__(self, name, core_id, evt_id):
- self.name = name
- self.ctx_name = 'None'
- self.in_irq = False
- self.core_id = core_id
- self.id = evt_id
- self.ts = 0
- self.params = {}
- @property
- def ctx_desc(self):
- if self.in_irq:
- return 'IRQ "%s"' % self.ctx_name
- return 'task "%s"' % self.ctx_name
- def to_jsonable(self):
- res = self.__dict__
- params = {}
- for p in self.params:
- params.update(self.params[p].to_jsonable())
- res['params'] = params
- return res
- class TraceDataProcessor:
- """
- Base abstract class for all trace data processors.
- """
- def __init__(self, print_events, keep_all_events=False):
- """
- Constructor.
- Parameters
- ----------
- print_events : bool
- if True every event will be printed as they arrive
- keep_all_events : bool
- if True all events will be kept in self.events in the order they arrive
- """
- self.print_events = print_events
- self.keep_all_events = keep_all_events
- self.total_events = 0
- self.events = []
- # This can be changed by the root procesor that includes several sub-processors.
- # It is used access some method of root processor which can contain methods/data common for all sub-processors.
- # Common info could be current execution context, info about running tasks, available IRQs etc.
- self.root_proc = self
- def _print_event(self, event):
- """
- Base method to print an event.
- Parameters
- ----------
- event : object
- Event object
- """
- print('EVENT[{:d}]: {}'.format(self.total_events, event))
- def print_report(self):
- """
- Base method to print report.
- """
- print('Processed {:d} events'.format(self.total_events))
- def cleanup(self):
- """
- Base method to make cleanups.
- """
- pass
- def on_new_event(self, event):
- """
- Base method to process event.
- """
- if self.print_events:
- self._print_event(event)
- if self.keep_all_events:
- self.events.append(event)
- self.total_events += 1
- class LogTraceParseError(ParseError):
- """
- Log trace parse error exception.
- """
- pass
- def get_str_from_elf(felf, str_addr):
- """
- Retrieves string from ELF file.
- Parameters
- ----------
- felf : elffile.ELFFile
- open ELF file handle to retrive format string from
- str_addr : int
- address of the string
- Returns
- -------
- string
- string or None if it was not found
- """
- tgt_str = ''
- for sect in felf.iter_sections():
- if sect['sh_addr'] == 0 or (sect['sh_flags'] & elfconst.SH_FLAGS.SHF_ALLOC) == 0:
- continue
- if str_addr < sect['sh_addr'] or str_addr >= sect['sh_addr'] + sect['sh_size']:
- continue
- sec_data = sect.data()
- for i in range(str_addr - sect['sh_addr'], sect['sh_size']):
- if type(sec_data) is str:
- ch = sec_data[i]
- else:
- ch = str(chr(sec_data[i]))
- if ch == '\0':
- break
- tgt_str += ch
- if len(tgt_str) > 0:
- return tgt_str
- return None
- class LogTraceEvent:
- """
- Log trace event.
- """
- def __init__(self, fmt_addr, log_args):
- """
- Constructor.
- Parameters
- ----------
- fmt_addr : int
- address of the format string
- log_args : list
- list of log message arguments
- """
- self.fmt_addr = fmt_addr
- self.args = log_args
- def get_message(self, felf):
- """
- Retrieves log message.
- Parameters
- ----------
- felf : elffile.ELFFile
- open ELF file handle to retrive format string from
- Returns
- -------
- string
- formatted log message
- Raises
- ------
- LogTraceParseError
- if format string has not been found in ELF file
- """
- fmt_str = get_str_from_elf(felf, self.fmt_addr)
- if not fmt_str:
- raise LogTraceParseError('Failed to find format string for 0x%x' % self.fmt_addr)
- prcnt_idx = 0
- for i, arg in enumerate(self.args):
- prcnt_idx = fmt_str.find('%', prcnt_idx, -2) # TODO: check str ending with %
- if prcnt_idx == -1:
- break
- prcnt_idx += 1 # goto next char
- if fmt_str[prcnt_idx] == 's':
- # find string
- arg_str = get_str_from_elf(felf, self.args[i])
- if arg_str:
- self.args[i] = arg_str
- else:
- self.args[i] = '<None>'
- fmt_str = fmt_str.replace('%p', '%x')
- return fmt_str % tuple(self.args)
- class BaseLogTraceDataProcessorImpl:
- """
- Base implementation for log data processors.
- """
- def __init__(self, print_log_events=False, elf_path=''):
- """
- Constructor.
- Parameters
- ----------
- print_log_events : bool
- if True every log event will be printed as they arrive
- elf_path : string
- path to ELF file to retrieve format strings for log messages
- """
- if len(elf_path):
- self.felf = elffile.ELFFile(open(elf_path, 'rb'))
- else:
- self.felf = None
- self.print_log_events = print_log_events
- self.messages = []
- def cleanup(self):
- """
- Cleanup
- """
- if self.felf:
- self.felf.stream.close()
- def print_report(self):
- """
- Prints log report
- """
- print('=============== LOG TRACE REPORT ===============')
- print('Processed {:d} log messages.'.format(len(self.messages)))
- def on_new_event(self, event):
- """
- Processes log events.
- Parameters
- ----------
- event : LogTraceEvent
- Event object.
- """
- msg = event.get_message(self.felf)
- self.messages.append(msg)
- if self.print_log_events:
- print(msg, end='')
- class HeapTraceParseError(ParseError):
- """
- Heap trace parse error exception.
- """
- pass
- class HeapTraceDuplicateAllocError(HeapTraceParseError):
- """
- Heap trace duplicate allocation error exception.
- """
- def __init__(self, addr, new_size, prev_size):
- """
- Constructor.
- Parameters
- ----------
- addr : int
- memory block address
- new_size : int
- size of the new allocation
- prev_size : int
- size of the previous allocation
- """
- HeapTraceParseError.__init__(self, """Duplicate alloc @ 0x{:x}!
- New alloc is {:d} bytes,
- previous is {:d} bytes.""".format(addr, new_size, prev_size))
- class HeapTraceEvent:
- """
- Heap trace event.
- """
- def __init__(self, trace_event, alloc, toolchain='', elf_path=''):
- """
- Constructor.
- Parameters
- ----------
- sys_view_event : TraceEvent
- trace event object related to this heap event
- alloc : bool
- True for allocation event, otherwise False
- toolchain_pref : string
- toolchain prefix to retrieve source line locations using addresses
- elf_path : string
- path to ELF file to retrieve format strings for log messages
- """
- self.trace_event = trace_event
- self.alloc = alloc
- self.toolchain = toolchain
- self.elf_path = elf_path
- if self.alloc:
- self.size = self.trace_event.params['size'].value
- else:
- self.size = None
- @property
- def addr(self):
- return self.trace_event.params['addr'].value
- @property
- def callers(self):
- return self.trace_event.params['callers'].value
- def __repr__(self):
- if len(self.toolchain) and len(self.elf_path):
- callers = os.linesep
- for addr in self.trace_event.params['callers'].value:
- if addr == 0:
- break
- callers += '{}'.format(addr2line(self.toolchain, self.elf_path, addr))
- else:
- callers = ''
- for addr in self.trace_event.params['callers'].value:
- if addr == 0:
- break
- if len(callers):
- callers += ':'
- callers += '0x{:x}'.format(addr)
- if self.alloc:
- return '[{:.9f}] HEAP: Allocated {:d} bytes @ 0x{:x} from {} on core {:d} by: {}'.format(self.trace_event.ts,
- self.size, self.addr,
- self.trace_event.ctx_desc,
- self.trace_event.core_id,
- callers)
- else:
- return '[{:.9f}] HEAP: Freed bytes @ 0x{:x} from {} on core {:d} by: {}'.format(self.trace_event.ts,
- self.addr, self.trace_event.ctx_desc,
- self.trace_event.core_id, callers)
- class BaseHeapTraceDataProcessorImpl:
- """
- Base implementation for heap data processors.
- """
- def __init__(self, print_heap_events=False):
- """
- Constructor.
- Parameters
- ----------
- print_heap_events : bool
- if True every heap event will be printed as they arrive
- """
- self._alloc_addrs = {}
- self.allocs = []
- self.frees = []
- self.heap_events_count = 0
- self.print_heap_events = print_heap_events
- def on_new_event(self, event):
- """
- Processes heap events. Keeps track of active allocations list.
- Parameters
- ----------
- event : HeapTraceEvent
- Event object.
- """
- self.heap_events_count += 1
- if self.print_heap_events:
- print(event)
- if event.alloc:
- if event.addr in self._alloc_addrs:
- raise HeapTraceDuplicateAllocError(event.addr, event.size, self._alloc_addrs[event.addr].size)
- self.allocs.append(event)
- self._alloc_addrs[event.addr] = event
- else:
- # do not treat free on unknown addresses as errors, because these blocks coould be allocated when tracing was disabled
- if event.addr in self._alloc_addrs:
- event.size = self._alloc_addrs[event.addr].size
- self.allocs.remove(self._alloc_addrs[event.addr])
- del self._alloc_addrs[event.addr]
- else:
- self.frees.append(event)
- def print_report(self):
- """
- Prints heap report
- """
- print('=============== HEAP TRACE REPORT ===============')
- print('Processed {:d} heap events.'.format(self.heap_events_count))
- if len(self.allocs) == 0:
- print('OK - Heap errors was not found.')
- return
- leaked_bytes = 0
- for alloc in self.allocs:
- leaked_bytes += alloc.size
- print(alloc)
- for free in self.frees:
- if free.addr > alloc.addr and free.addr <= alloc.addr + alloc.size:
- print('Possible wrong free operation found')
- print(free)
- print('Found {:d} leaked bytes in {:d} blocks.'.format(leaked_bytes, len(self.allocs)))
|