Skip to content

Commit d5f5d3c

Browse files
committed
Add first implementation.
Signed-off-by: Jan Losinski <losinski@wh2.tu-dresden.de>
1 parent eaa3930 commit d5f5d3c

File tree

1 file changed

+284
-0
lines changed

1 file changed

+284
-0
lines changed

decoder.py

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import re
5+
import subprocess
6+
from collections import namedtuple
7+
8+
import sys
9+
10+
import os
11+
12+
EXCEPTIONS = [
13+
"Illegal instruction",
14+
"SYSCALL instruction",
15+
"InstructionFetchError: Processor internal physical address or data error during instruction fetch",
16+
"LoadStoreError: Processor internal physical address or data error during load or store",
17+
"Level1Interrupt: Level-1 interrupt as indicated by set level-1 bits in the INTERRUPT register",
18+
"Alloca: MOVSP instruction, if caller's registers are not in the register file",
19+
"IntegerDivideByZero: QUOS, QUOU, REMS, or REMU divisor operand is zero",
20+
"reserved",
21+
"Privileged: Attempt to execute a privileged operation when CRING ? 0",
22+
"LoadStoreAlignmentCause: Load or store to an unaligned address",
23+
"reserved",
24+
"reserved",
25+
"InstrPIFDataError: PIF data error during instruction fetch",
26+
"LoadStorePIFDataError: Synchronous PIF data error during LoadStore access",
27+
"InstrPIFAddrError: PIF address error during instruction fetch",
28+
"LoadStorePIFAddrError: Synchronous PIF address error during LoadStore access",
29+
"InstTLBMiss: Error during Instruction TLB refill",
30+
"InstTLBMultiHit: Multiple instruction TLB entries matched",
31+
"InstFetchPrivilege: An instruction fetch referenced a virtual address at a ring level less than CRING",
32+
"reserved",
33+
"InstFetchProhibited: An instruction fetch referenced a page mapped with an attribute that does not permit instruction fetch",
34+
"reserved",
35+
"reserved",
36+
"reserved",
37+
"LoadStoreTLBMiss: Error during TLB refill for a load or store",
38+
"LoadStoreTLBMultiHit: Multiple TLB entries matched for a load or store",
39+
"LoadStorePrivilege: A load or store referenced a virtual address at a ring level less than CRING",
40+
"reserved",
41+
"LoadProhibited: A load referenced a page mapped with an attribute that does not permit loads",
42+
"StoreProhibited: A store referenced a page mapped with an attribute that does not permit stores"
43+
]
44+
45+
PLATFORMS = {
46+
"ESP8266": "lx106",
47+
"ESP32": "esp32"
48+
}
49+
50+
EXCEPTION_REGEX = re.compile("^Exception \\((?P<exc>[0-9]*)\\):$")
51+
COUNTER_REGEX = re.compile('^epc1=(?P<epc1>0x[0-9a-f]+) epc2=(?P<epc2>0x[0-9a-f]+) epc3=(?P<epc3>0x[0-9a-f]+) '
52+
'excvaddr=(?P<excvaddr>0x[0-9a-f]+) depc=(?P<depc>0x[0-9a-f]+)$')
53+
CTX_REGEX = re.compile("^ctx: (?P<ctx>.+)$")
54+
POINTER_REGEX = re.compile('^sp: (?P<sp>[0-9a-f]+) end: (?P<end>[0-9a-f]+) offset: (?P<offset>[0-9a-f]+)$')
55+
STACK_BEGIN = '>>>stack>>>'
56+
STACK_END = '<<<stack<<<'
57+
STACK_REGEX = re.compile(
58+
'^(?P<off>[0-9a-f]+):\W+(?P<c1>[0-9a-f]+) (?P<c2>[0-9a-f]+) (?P<c3>[0-9a-f]+) (?P<c4>[0-9a-f]+)$')
59+
60+
StackLine = namedtuple("StackLine", ["offset", "content"])
61+
62+
63+
class ExceptionDataParser(object):
64+
def __init__(self):
65+
self.exception = None
66+
67+
self.epc1 = None
68+
self.epc2 = None
69+
self.epc3 = None
70+
self.excvaddr = None
71+
self.depc = None
72+
73+
self.ctx = None
74+
75+
self.sp = None
76+
self.end = None
77+
self.offset = None
78+
79+
self.stack = []
80+
81+
def _parse_exception(self, line):
82+
match = EXCEPTION_REGEX.match(line)
83+
if match is not None:
84+
self.exception = int(match.group('exc'))
85+
return self._parse_counters
86+
return self._parse_exception
87+
88+
def _parse_counters(self, line):
89+
match = COUNTER_REGEX.match(line)
90+
if match is not None:
91+
self.epc1 = match.group("epc1")
92+
self.epc2 = match.group("epc2")
93+
self.epc3 = match.group("epc3")
94+
self.excvaddr = match.group("excvaddr")
95+
self.depc = match.group("depc")
96+
return self._parse_ctx
97+
return self._parse_counters
98+
99+
def _parse_ctx(self, line):
100+
match = CTX_REGEX.match(line)
101+
if match is not None:
102+
self.ctx = match.group("ctx")
103+
return self._parse_pointers
104+
return self._parse_ctx
105+
106+
def _parse_pointers(self, line):
107+
match = POINTER_REGEX.match(line)
108+
if match is not None:
109+
self.sp = match.group("sp")
110+
self.end = match.group("end")
111+
self.offset = match.group("offset")
112+
return self._parse_stack_begin
113+
return self._parse_pointers
114+
115+
def _parse_stack_begin(self, line):
116+
if line == STACK_BEGIN:
117+
return self._parse_stack_line
118+
return self._parse_stack_begin
119+
120+
def _parse_stack_line(self, line):
121+
if line != STACK_END:
122+
match = STACK_REGEX.match(line)
123+
if match is not None:
124+
self.stack.append(StackLine(offset=match.group("off"),
125+
content=(match.group("c1"), match.group("c2"), match.group("c3"),
126+
match.group("c4"))))
127+
return self._parse_stack_line
128+
return None
129+
130+
def parse_file(self, file):
131+
func = self._parse_exception
132+
133+
for line in file:
134+
func = func(line.strip())
135+
136+
if func is not None:
137+
print("ERROR: Parser not complete!")
138+
sys.exit(1)
139+
140+
141+
class AddressResolver(object):
142+
def __init__(self, tool_path, elf_path):
143+
self._tool = tool_path
144+
self._elf = elf_path
145+
self._address_map = {}
146+
147+
def _lookup(self, addresses):
148+
cmd = [self._tool, "-aipfC", "-e", self._elf] + [addr for addr in addresses if addr is not None]
149+
output = subprocess.check_output(cmd, encoding="utf-8")
150+
151+
line_regex = re.compile("^(?P<addr>[0-9a-fx]+): (?P<result>.+)$")
152+
for line in output.splitlines():
153+
line = line.strip()
154+
match = line_regex.match(line)
155+
156+
if match is None:
157+
continue
158+
159+
if match.group("result") == '?? ??:0':
160+
continue
161+
162+
self._address_map[match.group("addr")] = match.group("result")
163+
164+
def fill(self, parser):
165+
addresses = [parser.epc1, parser.epc2, parser.epc3, parser.excvaddr, parser.sp, parser.end, parser.offset]
166+
for line in parser.stack:
167+
addresses.extend(line.content)
168+
169+
self._lookup(addresses)
170+
171+
def _sanitize_addr(self, addr):
172+
if addr.startswith("0x"):
173+
addr = addr[2:]
174+
175+
fill = "0" * (8 - len(addr))
176+
return "0x" + fill + addr
177+
178+
def resolve_addr(self, addr):
179+
out = self._sanitize_addr(addr)
180+
181+
if out in self._address_map:
182+
out += ": " + self._address_map[out]
183+
184+
return out
185+
186+
def resolve_stack_addr(self, addr, full=True):
187+
addr = self._sanitize_addr(addr)
188+
if addr in self._address_map:
189+
return addr + ": " + self._address_map[addr]
190+
191+
if full:
192+
return "[DATA (0x" + addr + ")]"
193+
194+
return None
195+
196+
197+
def print_addr(name, value, resolver):
198+
print("{}:{} {}".format(name, " " * (8 - len(name)), resolver.resolve_addr(value)))
199+
200+
201+
def print_stack_full(lines, resolver):
202+
print("stack:")
203+
for line in lines:
204+
print(line.offset + ":")
205+
for content in line.content:
206+
print(" " + resolver.resolve_stack_addr(content))
207+
208+
209+
def print_stack(lines, resolver):
210+
print("stack:")
211+
for line in lines:
212+
for content in line.content:
213+
out = resolver.resolve_stack_addr(content, full=False)
214+
if out is None:
215+
continue
216+
print(out)
217+
218+
219+
def print_result(parser, resolver, full=True):
220+
print('Exception: {} ({})'.format(parser.exception, EXCEPTIONS[parser.exception]))
221+
222+
print("")
223+
print_addr("epc1", parser.epc1, resolver)
224+
print_addr("epc2", parser.epc2, resolver)
225+
print_addr("epc3", parser.epc3, resolver)
226+
print_addr("excvaddr", parser.excvaddr, resolver)
227+
print_addr("depc", parser.depc, resolver)
228+
229+
print("")
230+
print("ctx: " + parser.ctx)
231+
232+
print("")
233+
print_addr("sp", parser.sp, resolver)
234+
print_addr("end", parser.end, resolver)
235+
print_addr("offset", parser.offset, resolver)
236+
237+
print("")
238+
if full:
239+
print_stack_full(parser.stack, resolver)
240+
else:
241+
print_stack(parser.stack, resolver)
242+
243+
244+
def parse_args():
245+
parser = argparse.ArgumentParser(description="decode ESP Stacktraces.")
246+
247+
parser.add_argument("-p", "--platform", help="The platform to decode from", choices=PLATFORMS.keys(),
248+
default="ESP8266")
249+
parser.add_argument("-t", "--tool", help="Path to the xtensa toolchain",
250+
default="~/.platformio/packages/toolchain-xtensa/")
251+
parser.add_argument("-e", "--elf", help="path to elf file", required=True)
252+
parser.add_argument("-f", "--full", help="Print full stack dump", action="store_true")
253+
parser.add_argument("file", help="The file to read the exception data from ('-' for STDIN)", default="-")
254+
255+
return parser.parse_args()
256+
257+
258+
if __name__ == "__main__":
259+
args = parse_args()
260+
261+
if args.file == "-":
262+
file = sys.stdin
263+
else:
264+
if not os.path.exists(args.file):
265+
print("ERROR: file " + args.file + " not found")
266+
sys.exit(1)
267+
file = open(args.file, "r")
268+
269+
addr2line = os.path.join(os.path.abspath(os.path.expanduser(args.tool)),
270+
"bin/xtensa-" + PLATFORMS[args.platform] + "-elf-addr2line")
271+
if not os.path.exists(addr2line):
272+
print("ERROR: addr2line not found (" + addr2line + ")")
273+
274+
elf_file = os.path.abspath(os.path.expanduser(args.elf))
275+
if not os.path.exists(elf_file):
276+
print("ERROR: elf file not found (" + elf_file + ")")
277+
278+
parser = ExceptionDataParser()
279+
resolver = AddressResolver(addr2line, elf_file)
280+
281+
parser.parse_file(file)
282+
resolver.fill(parser)
283+
284+
print_result(parser, resolver, args.full)

0 commit comments

Comments
 (0)