From 2ef483cbe19da82f17cc88fa8d6e85d5e376b4d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Bo=C4=8Dek?= Date: Thu, 12 Mar 2020 12:53:43 +0100 Subject: [PATCH] Add esp_exception_decoder filter for device monitor (#286) * Add esp_exception_decoder filter for device monitor This filter resolves the ESP backtraces as they come into the device monitor. Add --filter=esp_exception_decoder to monitor_flags to use it. * EspExceptionDecoder: fixes for Windows and Python2 * EspExceptionDecoder: rename to Esp32ExceptionDecoder * Esp32ExceptionDecoder: handle inlined addresses nicely * Esp32ExceptionDecore: fix pylint errors --- monitor/filter_exception_decoder.py | 113 ++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 monitor/filter_exception_decoder.py diff --git a/monitor/filter_exception_decoder.py b/monitor/filter_exception_decoder.py new file mode 100644 index 0000000..6edb4b6 --- /dev/null +++ b/monitor/filter_exception_decoder.py @@ -0,0 +1,113 @@ +# Copyright (c) 2014-present PlatformIO +# +# 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. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import re +import subprocess +import sys + +from platformio.compat import get_filesystem_encoding, path_to_unicode +from platformio.project.exception import PlatformioException +from platformio.project.helpers import load_project_ide_data +from platformio.commands.device import DeviceMonitorFilter + +# By design, __init__ is called inside miniterm and we can't pass context to it. +# pylint: disable=attribute-defined-outside-init + +class Esp32ExceptionDecoder(DeviceMonitorFilter): + NAME = "esp32_exception_decoder" + + def __call__(self): + self.buffer = "" + self.backtrace_re = re.compile(r"^Backtrace: ((0x[0-9a-f]+:0x[0-9a-f]+ ?)+)\s*$") + + self.firmware_path = None + self.addr2line_path = None + self.enabled = self.setup_paths() + return self + + def setup_paths(self): + self.project_dir = path_to_unicode(os.path.abspath(self.project_dir)) + try: + data = load_project_ide_data(self.project_dir, self.environment) + self.firmware_path = data["prog_path"] + if not os.path.isfile(self.firmware_path): + sys.stderr.write("%s: firmware at %s does not exist, rebuild the project?\n" % + (self.__class__.__name__, self.firmware_path)) + return False + + cc_path = data.get("cc_path", "") + if "-gcc" in cc_path: + path = cc_path.replace("-gcc", "-addr2line") + if os.path.isfile(path): + self.addr2line_path = path + return True + except PlatformioException as e: + sys.stderr.write("%s: disabling, exception while looking for addr2line: %s\n" % + (self.__class__.__name__, e)) + return False + sys.stderr.write("%s: disabling, failed to find addr2line.\n" % self.__class__.__name__) + return False + + def rx(self, text): + if not self.enabled: + return text + + last = 0 + while True: + idx = text.find("\n", last) + if idx == -1: + if len(self.buffer) < 4096: + self.buffer += text[last:] + break + + line = text[last:idx] + if self.buffer: + line = self.buffer + line + self.buffer = "" + last = idx+1 + + m = self.backtrace_re.match(line) + if m is None: + continue + + trace = self.get_backtrace(m) + if len(trace) != "": + text = text[:idx+1] + trace + text[idx+1:] + last += len(trace) + return text + + def get_backtrace(self, match): + trace = "" + enc = get_filesystem_encoding() + args = ( self.addr2line_path, "-fipC", "-e", self.firmware_path ) + args = [ a.encode(enc) for a in args ] + try: + for i, addr in enumerate(match.group(1).split()): + output = subprocess.check_output(args + [ addr.encode(enc) ]).decode(enc).strip() + output = output.replace("\n", "\n ") # newlines happen with inlined methods + output = self.strip_project_dir(output) + trace += " #%-2d %s in %s\n" % (i, addr, output) + except subprocess.CalledProcessError as e: + sys.stderr.write("%s: failed to call %s: %s\n" % + (self.__class__.__name__, self.addr2line_path, e)) + return trace + + def strip_project_dir(self, trace): + while True: + idx = trace.find(self.project_dir) + if idx == -1: + break + trace = trace[:idx] + trace[idx+len(self.project_dir)+1:] + return trace