17
17
import subprocess
18
18
import sys
19
19
20
- from platformio .compat import path_to_unicode , WINDOWS , PY2
20
+ from platformio .compat import path_to_unicode , WINDOWS
21
21
from platformio .project .exception import PlatformioException
22
22
from platformio .project .helpers import load_project_ide_data
23
23
from platformio .commands .device import DeviceMonitorFilter
@@ -31,53 +31,74 @@ class Esp32ExceptionDecoder(DeviceMonitorFilter):
31
31
32
32
def __call__ (self ):
33
33
self .buffer = ""
34
- self . backtrace_re = re . compile (
35
- r"^Backtrace: ?((0x[0-9a-fA-F]{8}:0x[0-9a-fA-F]{8} ?)+)\s*"
36
- )
34
+ # regex matches potential PC value (0x4xxxxxxx)
35
+ # Logic identical to https://github.com/espressif/esp-idf/blob/master/tools/idf_monitor_base/constants.py#L56
36
+ self . pcaddr_re = re . compile ( r'0x4[0-9a-f]{7}' , re . IGNORECASE )
37
37
38
38
self .firmware_path = None
39
39
self .addr2line_path = None
40
40
self .enabled = self .setup_paths ()
41
41
42
- if self .config .get ("env:" + self .environment , "build_type" ) != "debug" :
43
- print (
42
+ return self
43
+
44
+ def setup_paths (self ):
45
+ self .project_dir = path_to_unicode (os .path .abspath (self .project_dir ))
46
+
47
+ self .project_strip_dir = os .environ .get ("esp32_exception_decoder_project_strip_dir" )
48
+ self .firmware_path = os .environ .get ("esp32_exception_decoder_firmware_path" )
49
+ self .addr2line_path = os .environ .get ("esp32_exception_decoder_addr2line_path" )
50
+
51
+ if self .project_strip_dir is None :
52
+ self .project_strip_dir = self .project_dir
53
+
54
+ try :
55
+ if self .firmware_path is None or self .addr2line_path is None :
56
+ # Only load if necessary, as the call is expensive
57
+ data = load_project_ide_data (self .project_dir , self .environment )
58
+
59
+ if self .firmware_path is None :
60
+ # Only do this check when the firmware path is not externally provided
61
+ if self .config .get ("env:" + self .environment , "build_type" ) != "debug" :
62
+ print (
44
63
"""
45
64
Please build project in debug configuration to get more details about an exception.
46
65
See https://docs.platformio.org/page/projectconf/build_configurations.html
47
66
48
67
"""
49
- )
50
-
51
- return self
68
+ )
69
+ self .firmware_path = data ["prog_path" ]
52
70
53
- def setup_paths (self ):
54
- self .project_dir = path_to_unicode (os .path .abspath (self .project_dir ))
55
- try :
56
- data = load_project_ide_data (self .project_dir , self .environment )
57
- self .firmware_path = data ["prog_path" ]
58
71
if not os .path .isfile (self .firmware_path ):
59
72
sys .stderr .write (
60
- "%s: firmware at %s does not exist, rebuild the project?\n "
73
+ "%s: disabling, firmware at %s does not exist, rebuild the project?\n "
61
74
% (self .__class__ .__name__ , self .firmware_path )
62
75
)
63
76
return False
64
77
65
- cc_path = data .get ("cc_path" , "" )
66
- if "-gcc" in cc_path :
67
- path = cc_path .replace ("-gcc" , "-addr2line" )
68
- if os .path .isfile (path ):
69
- self .addr2line_path = path
70
- return True
78
+ if self .addr2line_path is None :
79
+ cc_path = data .get ("cc_path" , "" )
80
+ if "-gcc" in cc_path :
81
+ self .addr2line_path = cc_path .replace ("-gcc" , "-addr2line" )
82
+ else :
83
+ sys .stderr .write (
84
+ "%s: disabling, failed to find addr2line.\n " % self .__class__ .__name__
85
+ )
86
+ return False
87
+
88
+ if not os .path .isfile (self .addr2line_path ):
89
+ sys .stderr .write (
90
+ "%s: disabling, addr2line at %s does not exist\n "
91
+ % (self .__class__ .__name__ , self .addr2line_path )
92
+ )
93
+ return False
94
+
95
+ return True
71
96
except PlatformioException as e :
72
97
sys .stderr .write (
73
98
"%s: disabling, exception while looking for addr2line: %s\n "
74
99
% (self .__class__ .__name__ , e )
75
100
)
76
101
return False
77
- sys .stderr .write (
78
- "%s: disabling, failed to find addr2line.\n " % self .__class__ .__name__
79
- )
80
- return False
81
102
82
103
def rx (self , text ):
83
104
if not self .enabled :
@@ -97,36 +118,36 @@ def rx(self, text):
97
118
self .buffer = ""
98
119
last = idx + 1
99
120
100
- m = self .backtrace_re .match (line )
101
- if m is None :
102
- continue
121
+ # Output each trace on a separate line below ours
122
+ # Logic identical to https://github.com/espressif/esp-idf/blob/master/tools/idf_monitor_base/logger.py#L131
123
+ for m in re .finditer (self .pcaddr_re , line ):
124
+ if m is None :
125
+ continue
126
+
127
+ trace = self .get_backtrace (m )
128
+ if len (trace ) != "" :
129
+ text = text [: last ] + trace + text [last :]
130
+ last += len (trace )
103
131
104
- trace = self .get_backtrace (m )
105
- if len (trace ) != "" :
106
- text = text [: idx + 1 ] + trace + text [idx + 1 :]
107
- last += len (trace )
108
132
return text
109
133
110
134
def get_backtrace (self , match ):
111
135
trace = ""
112
136
enc = "mbcs" if WINDOWS else "utf-8"
113
137
args = [self .addr2line_path , u"-fipC" , u"-e" , self .firmware_path ]
114
- if PY2 :
115
- args = [a .encode (enc ) for a in args ]
116
138
try :
117
- for i , addr in enumerate (match .group (1 ).split ()):
118
- if PY2 :
119
- addr = addr .encode (enc )
120
- output = (
121
- subprocess .check_output (args + [addr ])
122
- .decode (enc )
123
- .strip ()
124
- )
125
- output = output .replace (
126
- "\n " , "\n "
127
- ) # newlines happen with inlined methods
128
- output = self .strip_project_dir (output )
129
- trace += " #%-2d %s in %s\n " % (i , addr , output )
139
+ addr = match .group ()
140
+ output = (
141
+ subprocess .check_output (args + [addr ])
142
+ .decode (enc )
143
+ .strip ()
144
+ )
145
+ output = output .replace (
146
+ "\n " , "\n "
147
+ ) # newlines happen with inlined methods
148
+ output = self .strip_project_dir (output )
149
+ # Output the trace in yellow color so that it is easier to spot
150
+ trace += "\033 [33m=> %s: %s\033 [0m\n " % (addr , output )
130
151
except subprocess .CalledProcessError as e :
131
152
sys .stderr .write (
132
153
"%s: failed to call %s: %s\n "
@@ -136,8 +157,8 @@ def get_backtrace(self, match):
136
157
137
158
def strip_project_dir (self , trace ):
138
159
while True :
139
- idx = trace .find (self .project_dir )
160
+ idx = trace .find (self .project_strip_dir )
140
161
if idx == - 1 :
141
162
break
142
- trace = trace [:idx ] + trace [idx + len (self .project_dir ) + 1 :]
163
+ trace = trace [:idx ] + trace [idx + len (self .project_strip_dir ) + 1 :]
143
164
return trace
0 commit comments