From 364d6bfc9f76bc904ca283ca92a9e8776f4d8941 Mon Sep 17 00:00:00 2001 From: "Matthias L. Jugel" Date: Wed, 14 Nov 2018 15:25:58 +0100 Subject: [PATCH] add memory trace parser to mbed client --- mbed/mbed.py | 11 ++-- mbed/mbed_debug.py | 132 ++++++++++++++++++++++++++++++++++++++++++ mbed/mbed_terminal.py | 12 +++- 3 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 mbed/mbed_debug.py diff --git a/mbed/mbed.py b/mbed/mbed.py index e9df7e32..cf9dd8d2 100755 --- a/mbed/mbed.py +++ b/mbed/mbed.py @@ -1933,7 +1933,7 @@ def formaturl(url, format="default"): return url # Wrapper for the MbedTermnal functionality -def mbed_sterm(port, baudrate=9600, echo=True, reset=False, sterm=False): +def mbed_sterm(port, baudrate=9600, echo=True, reset=False, trace=None, sterm=False): try: from .mbed_terminal import MbedTerminal except(ValueError): # relative import fails on Python 2 in non-package mode @@ -1952,7 +1952,7 @@ def mbed_sterm(port, baudrate=9600, echo=True, reset=False, sterm=False): mbed_serial = MbedTerminal(port, baudrate=baudrate, echo=echo) try: - if not mbed_serial.terminal(): + if not mbed_serial.terminal(trace=trace): error("Unable to open serial terminal.\nMost likely your pyserial is dated and needs to be updated with 'pip install --upgrade pyserial") except: pass @@ -3117,11 +3117,12 @@ def detect(): dict(name=['-b', '--baudrate'], help='Communication baudrate. Default: 9600'), dict(name=['-e', '--echo'], help='Switch local echo on/off. Default: on'), dict(name=['-r', '--reset'], action='store_true', help='Reset the targets (via SendBreak) before opening terminal.'), + dict(name=['-t', '--trace'], dest='trace', type=str, help='Enable mbed memory tracing output formatter and set reset string to watch for. Default: off'), hidden_aliases=['term'], help='Open serial terminal to connected target.\n\n', description=( "Open serial terminal to connected target (usually board), or connect to a user-specified COM port\n")) -def sterm(port=None, baudrate=None, echo=None, reset=False, sterm=True): +def sterm(port=None, baudrate=None, echo=None, reset=False, trace=None, sterm=True): # Gather remaining arguments args = remainder # Find the root of the program @@ -3133,7 +3134,7 @@ def sterm(port=None, baudrate=None, echo=None, reset=False, sterm=True): if port: action("Opening serial terminal to the specified COM port \"%s\"" % port) - mbed_sterm(port, baudrate=baudrate, echo=echo, reset=reset, sterm=sterm) + mbed_sterm(port, baudrate=baudrate, echo=echo, reset=reset, trace=trace, sterm=sterm) else: action("Detecting connected targets/boards to your system...") targets = program.get_detected_targets() @@ -3145,7 +3146,7 @@ def sterm(port=None, baudrate=None, echo=None, reset=False, sterm=True): action("Opening serial terminal to unknown target at \"%s\"" % target['serial']) else: action("Opening serial terminal to \"%s\"" % target['name']) - mbed_sterm(target['serial'], baudrate=baudrate, echo=echo, reset=reset, sterm=sterm) + mbed_sterm(target['serial'], baudrate=baudrate, echo=echo, reset=reset, trace=trace, sterm=sterm) # Generic config command diff --git a/mbed/mbed_debug.py b/mbed/mbed_debug.py new file mode 100644 index 00000000..4f841a5f --- /dev/null +++ b/mbed/mbed_debug.py @@ -0,0 +1,132 @@ +# mbed debug trace transformer, handling memory trace information +# Author: Matthias L. Jugel (@thinkberg) +# +# Copyright (c) 2018 ubirch GmbH, All Rights Reserved +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed 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. + +import re +import sys + +from serial.tools.miniterm import Transform + +def debug(s): + sys.stderr.write("D "+repr(s)+"\n") + +class MbedTransform(Transform): + mem = {} + allocated = 0 + reset = None + buffer = "" + + def __init__(self, reset_str=None): + self.reset = reset_str + if self.reset is not None: + sys.stderr.write("memory tracing enabled: resetting on '{}'\n".format(self.reset)) + + self.r_malloc = re.compile("([^#]*)#m:0x([0-9a-f]+);0x([0-9a-f]+)-(\\d+)") + self.r_calloc = re.compile("([^#]*)#c:0x([0-9a-f]+);0x([0-9a-f]+)-(\\d+);(\\d+)") + self.r_realloc = re.compile("([^#]*)#r:0x([0-9a-f]+);0x([0-9a-f]+)-0x([0-9a-f]+);(\\d+)") + self.r_free = re.compile("([^#]*)#f:0x([0-9a-f]+);0x([0-9a-f]+)-0x([0-9a-f]+)") + + def rx(self, rx_input): + # collect lines + out = "" + for c in rx_input: + if c == '\r' or c == '\n': + if len(self.buffer): + out += self.trace_line(self.buffer) + else: + out += "\n" + self.buffer = "" + continue + else: + self.buffer += c + continue + return out + + def trace_line(self, line): + # strip newline and carriage return + line = line.rstrip('\n').rstrip('\r') + + # if we detect the reset keyword, rest the map and memory counter + if self.reset is not None and self.reset in line: + self.mem = {} + self.allocated = 0 + line += "\n\n\033[91m>> RESET HEAP COUNTERS >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\033[0m\n" + line += "\033[4m (NUM) TOTAL [ ADDRESS] CHANGE (COMMENT)\033[0m" + return line + + # match malloc, realloc and free + m = self.r_malloc.search(line) + if m: + out = "" + if len(m.group(1)): + out += m.group(1) + if m.group(2) == "0x0": + out += "\033[1m!! (%03d) \033[31mmalloc failed\033[0m (%s)\n" % (len(self.mem), line) + else: + self.mem[m.group(2)] = int(m.group(4)) + self.allocated += int(m.group(4)) + out += "\033[1m== (%03d) \033[34m%8d\033[0m [%8x] \033[31m+%-6d\033[0m (%s)" % \ + (len(self.mem), self.allocated, int(m.group(2), 16), int(m.group(4)), line.replace(m.group(1), "")) + return out + + m = self.r_calloc.search(line) + if m: + out = "" + if len(m.group(1)): + out += m.group(1) + if m.group(2) == "0x0": + out += "\033[1m!! (%03d) \033[31mcalloc failed\033[0m (%s)\n" % (len(self.mem), line) + else: + total = int(m.group(4)) * int(m.group(5)) + self.mem[m.group(2)] = total + self.allocated += total + out += "\033[1m== (%03d) \033[34m%8d\033[0m [%8x] \033[31m+%-6d\033[0m (%s)" % \ + (len(self.mem), self.allocated, int(m.group(2), 16), total, line.replace(m.group(1), "")) + return out + + m = self.r_realloc.search(line) + if m: + out = "" + if len(m.group(1)): + out += m.group(1) + diff = 0 + if self.mem.has_key(m.group(2)): + diff = int(m.group(5)) - self.mem[m.group(2)] + self.mem[m.group(2)] = int(m.group(5)) + else: + out += "\033[33m!! (%03d) WARN: realloc() without previous allocation\033[0m (%s)\n" % (len(self.mem), line) + self.allocated += diff + out += "\033[1m== (%03d) \033[34m%8d\033[0m [%8x] \033[35m+%-6d\033[0m (%s)" % \ + (len(self.mem), self.allocated, int(m.group(2), 16), diff, line.replace(m.group(1), "")) + return out + + m = self.r_free.search(line) + if m: + out = "" + if len(m.group(1)): + out += m.group(1) + freed = 0 + if self.mem.has_key(m.group(4)): + freed = self.mem[m.group(4)] + self.allocated -= freed + del self.mem[m.group(4)] + else: + out += "\033[33m!! (%03d) WARN: free(0x%s)\033[0m\n" % (len(self.mem), m.group(4)) + out += "\033[1m== (%03d) \033[34m%8d\033[0m [%8x] \033[92m-%-6d\033[0m (%s)" % \ + (len(self.mem), self.allocated, int(m.group(4), 16), freed, line.replace(m.group(1), "")) + return out + + # print all other lines as is, so we can still use the log functionality + return line diff --git a/mbed/mbed_terminal.py b/mbed/mbed_terminal.py index 9600f29d..d5c6d5b7 100644 --- a/mbed/mbed_terminal.py +++ b/mbed/mbed_terminal.py @@ -41,10 +41,15 @@ def __init__(self, port, baudrate=9600, echo=True, timeout=10): self.serial = None return - def terminal(self, print_header=True): + def terminal(self, print_header=True, trace=None): try: import serial.tools.miniterm as miniterm - except (IOError, ImportError, OSError): + try: + from .mbed_debug import MbedTransform + except(ValueError): + from mbed_debug import MbedTransform + except (IOError, ImportError, OSError) as e: + print(repr(e)) return False term = miniterm.Miniterm(self.serial, echo=self.echo) @@ -53,6 +58,9 @@ def terminal(self, print_header=True): term.set_rx_encoding('UTF-8') term.set_tx_encoding('UTF-8') + if trace: + term.rx_transformations += [MbedTransform(reset_str=trace)] + def console_print(text): term.console.write('--- %s ---\n' % text)