| #!/usr/bin/env python3 |
| ############################################################################ |
| # tools/parsetrace.py |
| # |
| # SPDX-License-Identifier: Apache-2.0 |
| # |
| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. The |
| # ASF licenses this file to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance with the |
| # License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations |
| # under the License. |
| # |
| ############################################################################ |
| |
| import argparse |
| import bisect |
| import logging |
| import os |
| import re |
| import struct |
| import subprocess |
| from typing import Union |
| |
| try: |
| import colorlog |
| import cxxfilt |
| import parse |
| import serial |
| from elftools.elf.elffile import ELFFile |
| from elftools.elf.sections import SymbolTableSection |
| from pycstruct import pycstruct |
| from pydantic import BaseModel |
| |
| except ModuleNotFoundError: |
| print("Please execute the following command to install dependencies:") |
| print("pip install pyelftools cxxfilt pydantic parse pycstruct colorlog serial") |
| exit(1) |
| |
| logger = logging.getLogger(__name__) |
| logger.setLevel(logging.INFO) |
| |
| stream_handler = logging.StreamHandler() |
| formatter = colorlog.ColoredFormatter( |
| "%(log_color)s %(message)s", |
| log_colors={ |
| "DEBUG": "light_black", |
| "INFO": "white", |
| "WARNING": "yellow", |
| "ERROR": "red", |
| }, |
| ) |
| |
| stream_handler.setFormatter(formatter) |
| logger.addHandler(stream_handler) |
| |
| |
| class SymbolTables(object): |
| def __init__(self, file): |
| elffile = open(file, "rb") |
| self.elffile = ELFFile(elffile) |
| self.elfinfo = dict() |
| self.typeinfo = dict() |
| self.symbol_dict = dict() |
| self.addr_list = list() |
| self.__parse_header() |
| self.parse_symbol() |
| |
| def __parse_header(self): |
| elf_header = self.elffile.header |
| bitness = elf_header["e_ident"]["EI_CLASS"] |
| self.elfinfo["bitwides"] = 32 if bitness == "ELFCLASS32" else 64 |
| endianness = elf_header["e_ident"]["EI_DATA"] |
| self.elfinfo["byteorder"] = "little" if endianness == "ELFDATA2LSB" else "big" |
| self.typeinfo["size_t"] = "uint%d" % self.elfinfo["bitwides"] |
| self.typeinfo["long"] = "int%d" % self.elfinfo["bitwides"] |
| self.typeinfo["pid_t"] = "int32" |
| |
| def __symbol_filter(self, symbol): |
| if symbol["st_info"]["type"] != "STT_FUNC": |
| return None |
| if symbol["st_info"]["bind"] == "STB_WEAK": |
| return None |
| if symbol["st_shndx"] == "SHN_UNDEF": |
| return None |
| return symbol |
| |
| def __get_symtable(self): |
| symbol_tables = [ |
| s |
| for _, s in enumerate(self.elffile.iter_sections()) |
| if isinstance(s, SymbolTableSection) |
| and s.name == ".symtab" |
| and s["sh_entsize"] |
| ] |
| |
| if not symbol_tables: |
| return None |
| |
| return symbol_tables[0] |
| |
| def parse_symbol(self): |
| if self.elffile is None: |
| return |
| |
| symtable = self.__get_symtable() |
| for nsym, symbol in enumerate(symtable.iter_symbols()): |
| if self.__symbol_filter(symbol) is not None: |
| try: |
| symbol_name = cxxfilt.demangle(symbol.name) |
| except Exception: |
| symbol_name = symbol.name |
| self.symbol_dict[symbol["st_value"] & ~0x01] = symbol_name |
| self.addr_list = sorted(self.symbol_dict) |
| |
| def get_typesize(self, type_name): |
| if not self.elffile.has_dwarf_info(): |
| raise ValueError("not found dwarf info!") |
| |
| dwarfinfo = self.elffile.get_dwarf_info() |
| for CU in dwarfinfo.iter_CUs(): |
| for DIE in CU.iter_DIEs(): |
| if DIE.tag == "DW_TAG_typedef": |
| name = DIE.attributes["DW_AT_name"].value.decode("utf-8") |
| if name == type_name: |
| if "DW_AT_type" in DIE.attributes: |
| type_attr = DIE.attributes["DW_AT_type"] |
| base_type_die = dwarfinfo.get_DIE_from_refaddr( |
| type_attr.value + CU.cu_offset |
| ) |
| if base_type_die.tag == "DW_TAG_base_type": |
| size = base_type_die.attributes["DW_AT_byte_size"].value |
| elif base_type_die.tag == "DW_TAG_typedef": |
| type_name = base_type_die.attributes[ |
| "DW_AT_name" |
| ].value.decode("utf-8") |
| continue |
| else: |
| size = DIE.attributes["DW_AT_byte_size"].value |
| return size |
| raise ValueError("not found type") |
| |
| def readstring(self, addr): |
| data = b"" |
| while True: |
| data += self.read(addr, 256) |
| data = data.split(b"\x00")[0] |
| if len(data) < 256: |
| break |
| return data.decode("utf-8") |
| |
| def read(self, addr, size): |
| for segment in self.elffile.iter_segments(): |
| seg_addr = segment["p_paddr"] |
| seg_size = min(segment["p_memsz"], segment["p_filesz"]) |
| if addr >= seg_addr and addr + size <= seg_addr + seg_size: |
| data = segment.data() |
| start = addr - seg_addr |
| return data[start : start + size] |
| |
| def addr2symbol(self, addr: int): |
| index = bisect.bisect(self.addr_list, addr) |
| if index != -1: |
| return self.symbol_dict[self.addr_list[index - 1]] |
| return "<%#x>: unknown function" % addr |
| |
| |
| class OtherModel(BaseModel): |
| payload: str |
| |
| def dump(self): |
| return self.payload |
| |
| def parse(self, string): |
| pattern = re.compile( |
| r"[sched_switch|sched_wakeup_new|" |
| r"sched_waking|irq_handler_entry|irq_handler_exit]" |
| ) |
| ret = pattern.match(string) |
| if ret is not None: |
| return OtherModel(payload=string) |
| |
| |
| class ATraceModel(BaseModel): |
| sign: str |
| pid: int |
| func: str |
| |
| def parse(self, string): |
| pattern = parse.compile("tracing_mark_write: {sign:>.1}|{pid:d}|{func}") |
| |
| ret = pattern.parse(string) |
| if ret is not None: |
| return ATraceModel(**ret.named) |
| |
| def dump(self): |
| return "tracing_mark_write: %c|%d|%s" % ( |
| self.sign, |
| self.pid, |
| self.func, |
| ) |
| |
| |
| class TraceModel(BaseModel): |
| name: str |
| tid: int |
| cpu: int |
| time: float |
| payload: Union[ATraceModel, OtherModel] |
| |
| def dump_one_trace(self): |
| header = "%16s-%-5d [%03d] %12.6f: %s" % ( |
| self.name, |
| self.tid, |
| self.cpu, |
| self.time, |
| self.payload.dump(), |
| ) |
| return header |
| |
| |
| class Trace(object): |
| def __payloadParse(self, string): |
| trace = ATraceModel.parse(ATraceModel, string) |
| if trace is not None: |
| return trace |
| trace = OtherModel.parse(OtherModel, string) |
| if trace is not None: |
| return trace |
| |
| def __init__(self, file): |
| with open(file, "rb+") as tracefile: |
| self.lines = tracefile.read() |
| self.lines = self.lines.split(b"\n") |
| self.all_trace = list() |
| self.parse() |
| |
| def parse(self): |
| header_pattern = parse.compile( |
| "{name}-{tid:d}{:s}[{cpu:d}]{:s}{time:f}: {payload:payload}", |
| dict(payload=self.__payloadParse), |
| ) |
| |
| for line in self.lines: |
| try: |
| line = line.decode("utf-8") |
| ret = header_pattern.parse(line.strip()) |
| if ret and ret.named["payload"]: |
| self.all_trace.append(TraceModel(**ret.named)) |
| except Exception: |
| continue |
| |
| def dump_trace(self): |
| formatted = ["# tracer: nop", "#"] |
| for trace in self.all_trace: |
| formatted.append(trace.dump_one_trace()) |
| return formatted |
| |
| |
| class ParseBinaryLogTool: |
| def __init__( |
| self, |
| binary_log_path, |
| elf_nuttx_path, |
| out_path=None, |
| size_long=4, |
| config_endian_big=False, |
| ): |
| self.binary_log_path = binary_log_path |
| self.elf_nuttx_path = elf_nuttx_path |
| self.out_path = out_path |
| self.symbol_tables = SymbolTables(self.elf_nuttx_path) |
| self.symbol_tables.parse_symbol() |
| with open(self.binary_log_path, "rb") as f: |
| self.in_bytes = f.read() |
| self.parsed = list() |
| self.task_name_dict = dict() |
| self.size_long = size_long |
| self.size_note_common = 3 + size_long * 3 |
| self.config_endian_big = config_endian_big |
| |
| def parse_by_endian(self, lis): |
| res = [hex(e)[2:] for e in lis] # strip prefix "0x" |
| res = [e if len(e) == 2 else "0" + e if len(e) == 1 else "00" for e in res] |
| if not self.config_endian_big: |
| res.reverse() |
| res = "0x" + "".join(res) |
| return int(res, 16), res |
| |
| def parse_one(self, st: int): |
| if st >= len(self.in_bytes): |
| print("error, index break bound") |
| one = pycstruct.StructDef() |
| one.add("uint8", "nc_length") |
| one.add("uint8", "nc_type") |
| one.add("uint8", "nc_priority") |
| one.add("uint8", "nc_cpu") |
| one.add("uint8", "nc_pid", self.size_long) |
| one.add("uint8", "nc_systime_sec", self.size_long) |
| one.add("uint8", "nc_systime_nsec", self.size_long) |
| res = one.deserialize(self.in_bytes, st) |
| |
| # case type |
| if res["nc_type"] == 0: |
| one.add("uint8", "nsa_name", res["nc_length"] - self.size_note_common) |
| elif res["nc_type"] == 22: |
| one.add("uint8", "nst_ip", self.size_long) # pointer of func |
| one.add("uint8", "nst_data") # B|E |
| elif res["nc_type"] == 20: # case: NOTE_IRQ_ENTER |
| one.add("uint8", "nih_irq") |
| elif res["nc_type"] == 21: # case: NOTE_IRQ_LEAVE |
| one.add("uint8", "nih_irq") |
| else: |
| print(f'skipped note, nc_type={res["nc_type"]}') |
| |
| res = one.deserialize(self.in_bytes, st) |
| # parse pid, systime ... |
| res["nc_pid"] = self.parse_by_endian(res["nc_pid"])[0] |
| res["nc_systime_sec"] = self.parse_by_endian(res["nc_systime_sec"])[0] |
| res["nc_systime_nsec"] = self.parse_by_endian(res["nc_systime_nsec"])[0] |
| if "nst_ip" in res: |
| res["nst_ip"] = self.parse_by_endian(res["nst_ip"])[1] |
| |
| # parse cpu, name ... |
| if "nc_cpu" not in res: |
| res["nc_cpu"] = 0 |
| if "nsa_name" in res: |
| nsa_name = "".join(chr(i) for i in res["nsa_name"][:-1]) |
| self.task_name_dict[res["nc_pid"]] = nsa_name |
| if "nst_data" in res: |
| res["nst_data"] = chr(res["nst_data"]) |
| return res |
| |
| def track_one(self, one): # print by case |
| nc_type = one["nc_type"] |
| nc_pid = one["nc_pid"] |
| nc_cpu = one["nc_cpu"] |
| nsa_name = self.task_name_dict.get(nc_pid, "noname") |
| float_time = float( |
| str(one["nc_systime_sec"]) + "." + str(one["nc_systime_nsec"]) |
| ) |
| |
| # case nc_type |
| a_model, other_model = None, None |
| if nc_type == 0: # case: NOTE_START |
| payload = ( |
| f"sched_wakeup_new: comm={nsa_name} pid={nc_pid} target_cpu={nc_cpu}" |
| ) |
| other_model = OtherModel(payload="").parse(payload) |
| if nc_type == 3: # case: NOTE_RESUME |
| payload = f"sched_waking: comm={nsa_name} pid={nc_pid} target_cpu={nc_cpu}" |
| other_model = OtherModel(payload="").parse(payload) |
| if nc_type == 22: # case: NOTE_DUMP_STRING |
| func_name = self.symbol_tables.symbol_dict.get( |
| int(one["nst_ip"], 16), "no_func_name" |
| ) |
| payload = f'tracing_mark_write: {one["nst_data"]}|{nc_pid}|{func_name}' |
| a_model = ATraceModel(sign="", pid=-1, func="").parse(payload) |
| if nc_type == 20: # case: NOTE_IRQ_ENTER |
| payload = f'irq_handler_entry: irq={one["nih_irq"]} name={one["nih_irq"]}' |
| other_model = OtherModel(payload="").parse(payload) |
| if nc_type == 21: # case: NOTE_IRQ_LEAVE |
| payload = f'irq_handler_exit: irq={one["nih_irq"]} name={one["nih_irq"]}' |
| other_model = OtherModel(payload="").parse(payload) |
| |
| for mod in [a_model, other_model]: |
| if mod is not None: |
| self.parsed.append( |
| TraceModel( |
| name=nsa_name, |
| tid=nc_pid, |
| cpu=nc_cpu, |
| time=float_time, |
| payload=mod, |
| ) |
| ) |
| |
| def parse_binary_log(self): |
| st = 0 |
| while st < len(self.in_bytes): |
| one = self.parse_one(st) |
| self.track_one(one) |
| st += one["nc_length"] |
| if self.out_path is not None: |
| with open(self.out_path, "wt") as f: |
| for mod in self.parsed: |
| f.write(mod.dump_one_trace() + "\n") |
| else: |
| for mod in self.parsed: |
| print(f"debug, dump one={mod.dump_one_trace()}") |
| |
| |
| class TraceDecoder(SymbolTables): |
| def __init__(self, elffile): |
| super().__init__(elffile) |
| self.data = b"" |
| self.typeinfo["time_t"] = "int%d" % (self.get_typesize("time_t") * 8) |
| |
| def note_common_define(self): |
| note_common = pycstruct.StructDef(alignment=4) |
| note_common.add("uint8", "nc_length") |
| note_common.add("uint8", "nc_type") |
| note_common.add("uint8", "nc_priority") |
| note_common.add("uint8", "nc_cpu") |
| note_common.add(self.typeinfo["pid_t"], "nc_pid") |
| note_common.add(self.typeinfo["time_t"], "nc_systime_sec") |
| note_common.add(self.typeinfo["long"], "nc_systime_nsec") |
| return note_common |
| |
| def note_printf_define(self, length): |
| struct_def = pycstruct.StructDef(alignment=4) |
| struct_def.add(self.note_common_define(), "npt_cmn") |
| struct_def.add(self.typeinfo["size_t"], "npt_ip") |
| struct_def.add(self.typeinfo["size_t"], "npt_fmt") |
| struct_def.add("uint32", "npt_type") |
| if length > 0: |
| struct_def.add("uint8", "npt_data", length=length) |
| return struct_def |
| |
| def extract_int(self, fmt, data): |
| pattern = re.match( |
| r"%([-+ #0]*)?(\d+|\*)?(\.)?(\d+|\*)?([hljzt]|ll|hh)?([diuxXop])", fmt |
| ).groups() |
| format = "%" if pattern[0] is None else "%" + pattern[0] |
| |
| if pattern[4] == "l" or pattern[4] == "z" or pattern[4] == "t": |
| length = 4 if self.typeinfo["size_t"] == "int32" else 8 |
| elif pattern[4] == "ll": |
| length = 8 |
| elif pattern[4] == "h": |
| length = 2 |
| elif pattern[4] == "hh": |
| length = 1 |
| else: |
| length = 4 |
| |
| width = 0 |
| if pattern[1] == "*": |
| width = int.from_bytes( |
| data[:4], byteorder=self.elfinfo["byteorder"], signed=True |
| ) |
| data = data[4:] |
| format += str(width) |
| elif pattern[1] is not None: |
| width = int(pattern[1]) |
| format += pattern[1] |
| |
| format += pattern[5] |
| if pattern[5] == "d" or pattern[5] == "i": |
| value = int.from_bytes( |
| data[:length], byteorder=self.elfinfo["byteorder"], signed=True |
| ) |
| elif ( |
| pattern[5] == "u" |
| or pattern[5] == "x" |
| or pattern[5] == "X" |
| or pattern[5] == "o" |
| or pattern[5] == "O" |
| ): |
| value = int.from_bytes( |
| data[:length], byteorder=self.elfinfo["byteorder"], signed=False |
| ) |
| |
| value = format % value |
| return "%s", length, value |
| |
| def extract_float(self, fmt, data): |
| pattern = re.match( |
| r"%([-+ #0]*)?(\d+|\*)?(\.)?(\d+|\*)?(L)?([fFeEgGaA])", fmt |
| ).groups() |
| if pattern[4] == "L": |
| length = 16 |
| else: |
| length = 8 |
| value = struct.unpack("<d", data[:length])[0] |
| return "%s", length, fmt % value |
| |
| def extract_string(self, fmt, data): |
| length = 0 |
| if fmt == "%.*s": |
| length = int.from_bytes( |
| data[:4], byteorder=self.elfinfo["byteorder"], signed=True |
| ) |
| data = data[4:] |
| else: |
| length = 0 |
| |
| try: |
| string = data.split(b"\x00")[0].decode("utf-8") |
| if len(data.split(b"\x00")[0]) != len(string) and len(string): |
| size = 4 if self.typeinfo["size_t"] == "uint32" else 8 |
| address = int.from_bytes( |
| data[:size], byteorder=self.elfinfo["byteorder"], signed=False |
| ) |
| string = f"<<0x{address}>>" |
| length += size |
| else: |
| length += len(string) + 1 |
| except Exception: |
| size = 4 if self.typeinfo["size_t"] == "uint32" else 8 |
| address = int.from_bytes( |
| data[:size], byteorder=self.elfinfo["byteorder"], signed=False |
| ) |
| string = f"<<0x{address}>>" |
| length += size |
| return "%s", length, string |
| |
| def extract_point(self, fmt, data): |
| length = 4 if self.typeinfo["size_t"] == "int32" else 8 |
| value = int.from_bytes( |
| data[:length], byteorder=self.elfinfo["byteorder"], signed=False |
| ) |
| return "%s", length, f"{value:x}" |
| |
| conversions = { |
| r"%p": extract_point, |
| r"%c": lambda _0, _1, data: ("%c", 1, chr(data[0])), |
| r"%([-+ #0]*)?(\d+|\*)?(\.)?(\d+|\*)?(L)?([fFeEgGaA])": extract_float, |
| r"%([-+ #0]*)?(\d+|\*)?(\.)?(\d+|\*)?([hljzt]|ll|hh)?([diuxXop])": extract_int, |
| r"%([ ]*)?([\d+|\*])?(\.)?([\d+|\*])?s": extract_string, |
| } |
| |
| patterns = {re.compile(pattern): func for pattern, func in conversions.items()} |
| |
| def printf(self, format, data): |
| try: |
| fmt = [] |
| values = [] |
| |
| parts = [ |
| part |
| for part in re.split( |
| r"(%[-+#0\s]*[\d|\*]*(?:\.[\d|\*])?[lhjztL]?[diufFeEgGxXoscpn%])", |
| format, |
| ) |
| ] |
| for part in parts: |
| if "%" not in part: |
| fmt.append(part) |
| continue |
| |
| for pattern, handler in self.patterns.items(): |
| if pattern.match(part): |
| part, length, value = handler(self, part, data) |
| values.append(value) |
| fmt.append(part) |
| data = data[length:] |
| break |
| else: |
| fmt.append(part) |
| |
| return "".join(fmt) % tuple(values) |
| except Exception as e: |
| logger.error(f"format failed: {e}") |
| |
| def print_format(self, note): |
| payload = dict() |
| payload["time"] = ( |
| note["npt_cmn"]["nc_systime_sec"] |
| + note["npt_cmn"]["nc_systime_nsec"] / 1000000000 |
| ) |
| payload["pid"] = note["npt_cmn"]["nc_pid"] |
| payload["cpu"] = ( |
| 0 if "nc_cpu" not in note["npt_cmn"] else note["npt_cmn"]["nc_cpu"] |
| ) |
| payload["format"] = self.readstring(note["npt_fmt"]) |
| prefix = "[{time:.9f}] [{pid}] [CPU{cpu}]: ".format(**payload) |
| string = self.printf(payload["format"], note["npt_data"]).rstrip("\n") |
| logger.info(prefix + string) |
| |
| def parse_note(self, rawdata=None): |
| while len(self.data) > 0: |
| data = self.data if rawdata is None else rawdata |
| try: |
| common_struct = self.note_common_define() |
| if len(data) < common_struct.size(): |
| return |
| common_note = common_struct.deserialize(data) |
| nc_length = common_note["nc_length"] |
| if nc_length < common_struct.size(): |
| raise ValueError("Invalid note length") |
| |
| if common_note["nc_type"] == 24: |
| note_struct = self.note_printf_define(0) |
| length = nc_length - note_struct.size() |
| note = note_struct.deserialize(data) |
| note["npt_data"] = data[ |
| note_struct.size() : note_struct.size() + length |
| ] |
| self.print_format(note) |
| else: |
| raise ValueError("Invalid note type") |
| except Exception as e: |
| logger.debug(f"skip one byte, data: {hex(self.data[0])} {e}") |
| self.data = data[1:] |
| continue |
| |
| self.data = data[nc_length:] |
| |
| def tty_received(self): |
| while True: |
| data = ser.read(16384) |
| if len(data) == 0: |
| continue |
| self.data += data |
| logger.debug(f"serial buffer data: {self.data.hex()}") |
| decode.parse_note() |
| |
| |
| def parse_arguments(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument("-t", "--trace", help="original trace file") |
| parser.add_argument("-e", "--elf", help="elf file") |
| parser.add_argument("-d", "--device", help="Physical serial device name") |
| parser.add_argument( |
| "-b", "--baudrate", help="Physical serial device baud rate", default=115200 |
| ) |
| parser.add_argument("-v", "--verbose", help="verbose output", action="store_true") |
| parser.add_argument( |
| "-o", |
| "--output", |
| help="filtered trace file, default output trace.systrace", |
| default="trace.systrace", |
| ) |
| return parser.parse_args() |
| |
| |
| if __name__ == "__main__": |
| args = parse_arguments() |
| out_path = args.output if args.output else "trace.systrace" |
| logger.setLevel(logging.DEBUG if args.verbose else logging.INFO) |
| |
| if args.trace is None and args.device is None: |
| print("error, please add trace file path or device name") |
| print( |
| "usage: parsetrace.py [-h] [-t TRACE] [-e ELF] [-d DEVICE] [-b BAUDRATE] [-v] [-o OUTPUT]" |
| ) |
| exit(1) |
| |
| if args.trace: |
| file_type = subprocess.check_output(f"file -b {args.trace}", shell=True) |
| file_type = str(file_type, "utf-8").lower() |
| if "ascii" in file_type: |
| print("trace log type is text") |
| trace = Trace(args.trace) |
| if args.elf: |
| symbol = SymbolTables(args.elf) |
| symbol.parse_symbol() |
| |
| for onetrace in trace.all_trace: |
| if isinstance(onetrace.payload, ATraceModel) and re.fullmatch( |
| r"^0x[0-9a-fA-F]+$", onetrace.payload.func |
| ): |
| onetrace.payload.func = symbol.addr2symbol( |
| int(onetrace.payload.func, 16) |
| ) |
| |
| lines = trace.dump_trace() |
| with open(out_path, "w") as out: |
| out.write("\n".join(lines)) |
| out.write("\n") |
| print(os.path.abspath(out_path)) |
| else: |
| print("trace log type is binary") |
| if args.elf: |
| print( |
| "parse_binary_log, default config, size_long=4, config_endian_big=False, config_smp=0" |
| ) |
| parse_binary_log_tool = ParseBinaryLogTool( |
| args.trace, args.elf, out_path |
| ) |
| parse_binary_log_tool.symbol_tables.parse_symbol() |
| parse_binary_log_tool.parse_binary_log() |
| else: |
| print("error, please add elf file path") |
| |
| if args.device: |
| if args.elf is None: |
| print("error, please add elf file path") |
| exit(1) |
| |
| decode = TraceDecoder(args.elf) |
| with serial.Serial(args.device, baudrate=args.baudrate) as ser: |
| ser.timeout = 0 |
| decode.tty_received() |