From b249970a1227ed2f3fd2840e6cfc8e2a9d3cd783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 5 Mar 2023 14:55:12 +0100 Subject: [PATCH] add rudimentary exception decoder for RISC-V CPU --- arch/esp32/esp32c3.ini | 3 +- monitor/filter_c3_exception_decoder.py | 155 +++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 monitor/filter_c3_exception_decoder.py diff --git a/arch/esp32/esp32c3.ini b/arch/esp32/esp32c3.ini index 08ab377df..130664f72 100644 --- a/arch/esp32/esp32c3.ini +++ b/arch/esp32/esp32c3.ini @@ -6,7 +6,7 @@ build_src_filter = upload_speed = 961200 monitor_speed = 115200 debug_init_break = tbreak setup -monitor_filters = esp32_exception_decoder +monitor_filters = esp32_c3_exception_decoder board_build.filesystem = littlefs # Remove -DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL for low level BLE logging. @@ -21,7 +21,6 @@ build_flags = -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG -DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL - -DAXP_DEBUG_PORT=Serial -DCONFIG_BT_NIMBLE_ENABLED -DCONFIG_NIMBLE_CPP_LOG_LEVEL=2 -DCONFIG_BT_NIMBLE_MAX_CCCDS=20 diff --git a/monitor/filter_c3_exception_decoder.py b/monitor/filter_c3_exception_decoder.py new file mode 100644 index 000000000..e59b0be2a --- /dev/null +++ b/monitor/filter_c3_exception_decoder.py @@ -0,0 +1,155 @@ +# 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.project.exception import PlatformioException +from platformio.public import ( + DeviceMonitorFilterBase, + load_build_metadata, +) + +# By design, __init__ is called inside miniterm and we can't pass context to it. +# pylint: disable=attribute-defined-outside-init + +IS_WINDOWS = sys.platform.startswith("win") + + +class Esp32C3ExceptionDecoder(DeviceMonitorFilterBase): + NAME = "esp32_c3_exception_decoder" + + PCADDR_PATTERN = re.compile(r'0x4[0-9a-f]{7}', re.IGNORECASE) + + def __call__(self): + self.buffer = "" + self.pcaddr_re = self.PCADDR_PATTERN + + self.firmware_path = None + self.addr2line_path = None + self.enabled = self.setup_paths() + + if self.config.get("env:" + self.environment, "build_type") != "debug": + print( + """ +Please build project in debug configuration to get more details about an exception. +See https://docs.platformio.org/page/projectconf/build_configurations.html + +""" + ) + + return self + + def setup_paths(self): + self.project_dir = os.path.abspath(self.project_dir) + try: + data = load_build_metadata(self.project_dir, self.environment) + self.firmware_path = data["prog_path"] + if not os.path.isfile(self.firmware_path): + sys.stderr.write( + "%s: disabling, firmware at %s does not exist, rebuild the project?\n" + % (self.__class__.__name__, self.firmware_path) + ) + return False + + if self.addr2line_path is None: + cc_path = data.get("cc_path", "") + if "-gcc" in cc_path: + self.addr2line_path = cc_path.replace("-gcc", "-addr2line") + else: + sys.stderr.write( + "%s: disabling, failed to find addr2line.\n" + % self.__class__.__name__ + ) + return False + + if not os.path.isfile(self.addr2line_path): + sys.stderr.write( + "%s: disabling, addr2line at %s does not exist\n" + % (self.__class__.__name__, self.addr2line_path) + ) + return False + + return True + except PlatformioException as e: + sys.stderr.write( + "%s: disabling, exception while looking for addr2line: %s\n" + % (self.__class__.__name__, e) + ) + 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 + + # Output each trace on a separate line below ours + # Logic identical to https://github.com/espressif/esp-idf/blob/master/tools/idf_monitor_base/logger.py#L131 + for m in re.finditer(self.pcaddr_re, line): + if m is None: + continue + + trace = self.get_backtrace(m) + if len(trace) != "": + text = text[: last] + trace + text[last :] + last += len(trace) + + return text + + def get_backtrace(self, match): + trace = "\n" + enc = "mbcs" if IS_WINDOWS else "utf-8" + args = [self.addr2line_path, u"-fipC", u"-e", self.firmware_path] + try: + addr = match.group() + output = ( + subprocess.check_output(args + [addr]) + .decode(enc) + .strip() + ) + output = output.replace( + "\n", "\n " + ) # newlines happen with inlined methods + output = self.strip_project_dir(output) + # Output the trace in yellow color so that it is easier to spot + trace += "\033[33m=> %s: %s\033[0m\n" % (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