Skip to content

Commit 1d63084

Browse files
committed
Add esp_exception_decoder filter to device monitor
This filter can decode the crash traces emitted by Espressif devices. Add '--filter=esp_exception_decoder' to monitor_flags to use it. Fixes platformio/platform-espressif8266#31
1 parent 4366467 commit 1d63084

File tree

1 file changed

+97
-3
lines changed

1 file changed

+97
-3
lines changed

platformio/commands/device.py

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import os
16+
import re
17+
import subprocess
1518
import sys
1619
from fnmatch import fnmatch
17-
from os import getcwd
1820

1921
import click
2022
from serial.tools import miniterm
@@ -23,7 +25,8 @@
2325
from platformio.compat import dump_json_to_unicode
2426
from platformio.managers.platform import PlatformFactory
2527
from platformio.project.config import ProjectConfig
26-
from platformio.project.exception import NotPlatformIOProjectError
28+
from platformio.project.exception import PlatformioException, NotPlatformIOProjectError
29+
from platformio.project.helpers import load_project_ide_data
2730

2831

2932
@click.group(short_help="Monitor device or list existing")
@@ -108,6 +111,93 @@ def device_list( # pylint: disable=too-many-branches
108111

109112
return True
110113

114+
class EspExceptionDecoder(miniterm.Transform):
115+
NAME = "esp_exception_decoder"
116+
117+
project_dir = None
118+
firmware_path = None
119+
addr2line_path = None
120+
121+
@classmethod
122+
def setup_paths(cls, project_dir, environment):
123+
config = ProjectConfig.get_instance()
124+
if not environment:
125+
default_envs = config.default_envs()
126+
if default_envs:
127+
environment = default_envs[0]
128+
else:
129+
environment = config.envs()[0]
130+
131+
build_dir = config.get_optional_dir("build")
132+
cls.project_dir = project_dir
133+
if not cls.project_dir.endswith(os.path.sep):
134+
cls.project_dir += os.path.sep
135+
136+
try:
137+
data = load_project_ide_data(project_dir, environment)
138+
cls.firmware_path = os.path.join(build_dir, environment, "firmware.elf")
139+
if not os.path.isfile(cls.firmware_path):
140+
sys.stderr.write("%s: firmware at %s does not exist, rebuild the project?\n" %
141+
(cls.__name__, cls.firmware_path))
142+
return False
143+
144+
cc_path = data.get("cc_path", "")
145+
if "-gcc" in cc_path:
146+
path = cc_path.replace("-gcc", "-addr2line")
147+
if os.path.isfile(path):
148+
cls.addr2line_path = path
149+
return True
150+
except PlatformioException as e:
151+
sys.stderr.write("%s: disabling, exception while looking for addr2line: %s\n" %
152+
(cls.__name__, e))
153+
return False
154+
sys.stderr.write("%s: disabling, failed to find addr2line.\n" % cls.__name__)
155+
return False
156+
157+
def __init__(self, *args, **kwargs):
158+
super(EspExceptionDecoder, self).__init__(*args, **kwargs)
159+
160+
self.buffer = ""
161+
self.backtrace_re = re.compile(r"^Backtrace: ((0x[0-9a-f]+:0x[0-9a-f]+ ?)+)\s*$")
162+
163+
def rx(self, text):
164+
if self.addr2line_path is None:
165+
return text
166+
167+
last = 0
168+
while True:
169+
idx = text.find("\n", last)
170+
if idx == -1:
171+
self.buffer += text[last:]
172+
break
173+
174+
line = text[last:idx]
175+
if self.buffer:
176+
line = self.buffer + line
177+
self.buffer = ""
178+
last = idx+1
179+
180+
m = self.backtrace_re.match(line)
181+
if m is None:
182+
continue
183+
184+
trace = self.get_backtrace(m)
185+
if len(trace) != "":
186+
text = text[:idx+1] + trace + text[idx+1:]
187+
last += len(trace)
188+
return text
189+
190+
def get_backtrace(self, match):
191+
trace = ""
192+
try:
193+
args = [ self.addr2line_path, "-fipC", "-e", self.firmware_path ]
194+
for i, addr in enumerate(match.group(1).split()):
195+
output = subprocess.check_output(args + [ addr ]).decode("utf-8")
196+
trace += " #%d in %s\n" % (i, output.strip().replace(self.project_dir, ""))
197+
except subprocess.CalledProcessError as e:
198+
sys.stderr.write("%s: failed to call %s: %s\n" %
199+
(self.__class__.__name__, self.addr2line_path, e))
200+
return trace
111201

112202
@cli.command("monitor", short_help="Monitor device (Serial)")
113203
@click.option("--port", "-p", help="Port, a number or a device name")
@@ -165,7 +255,7 @@ def device_list( # pylint: disable=too-many-branches
165255
@click.option(
166256
"-d",
167257
"--project-dir",
168-
default=getcwd,
258+
default=os.getcwd,
169259
type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True),
170260
)
171261
@click.option(
@@ -217,6 +307,10 @@ def device_monitor(**kwargs): # pylint: disable=too-many-branches
217307
ignore=("port", "baud", "rts", "dtr", "environment", "project_dir"),
218308
)
219309

310+
if any((EspExceptionDecoder.NAME in a for a in sys.argv)):
311+
EspExceptionDecoder.setup_paths(kwargs["project_dir"], kwargs["environment"])
312+
miniterm.TRANSFORMATIONS[EspExceptionDecoder.NAME] = EspExceptionDecoder
313+
220314
try:
221315
miniterm.main(
222316
default_port=kwargs["port"],

0 commit comments

Comments
 (0)