Skip to content

Commit a78672d

Browse files
Replace addr2line with GDB to get accurate line #s
Addr2line on the xtensa seems to have trouble with identifying the proper file:line number for an exception address. Until there is a fix, the workaround is to use GDB to locate them. See jcmvbkbc/binutils-gdb-xtensa#5 . Replace the addr2line processing/formatting with gdb's formatting, leaving the output identical with one exception: addresses with no source code (i.e. not code, but constant data somewhere or just random variables on the stack) *will not print*. They're silently ignored in the output. This also now presents the EPC1/PC and EXCVADDR on their own lines hilighted at the top of the dump. Additionally, handle the case where the exception dumper itself on the ESP8266 hits the WDT (i.e. during dump of an infinite recursion). In this case the final "<<<stack<<<" line may not be generated, so simply parse everything from ">>>stack>>>" to the end.
1 parent 8ba299b commit a78672d

File tree

1 file changed

+186
-73
lines changed

1 file changed

+186
-73
lines changed

src/EspExceptionDecoder.java

Lines changed: 186 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
//import processing.app.SketchData;
4444
import processing.app.debug.TargetPlatform;
4545
import processing.app.helpers.FileUtils;
46+
import processing.app.helpers.OSUtils;
4647
import processing.app.helpers.ProcessUtils;
4748
import processing.app.tools.Tool;
4849

@@ -96,9 +97,35 @@ public String getMenuTitle() {
9697
return "ESP Exception Decoder";
9798
}
9899

100+
// Original code from processing.app.helpers.ProcessUtils.exec()
101+
// Need custom version to redirect STDERR to STDOUT for GDB processing
102+
public static Process execRedirected(String[] command) throws IOException {
103+
ProcessBuilder pb;
104+
105+
// No problems on linux and mac
106+
if (!OSUtils.isWindows()) {
107+
pb = new ProcessBuilder(command);
108+
} else {
109+
// Brutal hack to workaround windows command line parsing.
110+
// http://stackoverflow.com/questions/5969724/java-runtime-exec-fails-to-escape-characters-properly
111+
// http://msdn.microsoft.com/en-us/library/a1y7w461.aspx
112+
// http://bugs.sun.com/view_bug.do?bug_id=6468220
113+
// http://bugs.sun.com/view_bug.do?bug_id=6518827
114+
String[] cmdLine = new String[command.length];
115+
for (int i = 0; i < command.length; i++)
116+
cmdLine[i] = command[i].replace("\"", "\\\"");
117+
pb = new ProcessBuilder(cmdLine);
118+
Map<String, String> env = pb.environment();
119+
env.put("CYGWIN", "nodosfilewarning");
120+
}
121+
pb.redirectErrorStream(true);
122+
123+
return pb.start();
124+
}
125+
99126
private int listenOnProcess(String[] arguments){
100127
try {
101-
final Process p = ProcessUtils.exec(arguments);
128+
final Process p = execRedirected(arguments);
102129
Thread thread = new Thread() {
103130
public void run() {
104131
try {
@@ -246,16 +273,16 @@ private void createAndUpload(){
246273
gccPath = platform.getFolder() + "/tools/xtensa-"+tc+"-elf";
247274
}
248275

249-
String addr2line;
276+
String gdb;
250277
if(PreferencesData.get("runtime.os").contentEquals("windows"))
251-
addr2line = "xtensa-"+tc+"-elf-addr2line.exe";
278+
gdb = "xtensa-"+tc+"-elf-gdb.exe";
252279
else
253-
addr2line = "xtensa-"+tc+"-elf-addr2line";
280+
gdb = "xtensa-"+tc+"-elf-gdb";
254281

255-
tool = new File(gccPath + "/bin", addr2line);
282+
tool = new File(gccPath + "/bin", gdb);
256283
if (!tool.exists() || !tool.isFile()) {
257284
System.err.println();
258-
editor.statusError("ERROR: "+addr2line+" not found!");
285+
editor.statusError("ERROR: "+gdb+" not found!");
259286
return;
260287
}
261288

@@ -309,35 +336,49 @@ private void createAndUpload(){
309336
frame.setVisible(true);
310337
}
311338

312-
private void printLine(String line){
313-
String address = "", method = "", file = "";
314-
if(line.startsWith("0x")){
315-
address = line.substring(0, line.indexOf(':'));
316-
line = line.substring(line.indexOf(':') + 2);
317-
} else if(line.startsWith("(inlined by)")){
318-
line = line.substring(13);
319-
address = "inlined by";
339+
private String prettyPrintGDBLine(String line) {
340+
String address = "", method = "", file = "", fileline = "", html = "";
341+
342+
if (!line.startsWith("0x")) {
343+
return null;
320344
}
321-
int atIndex = line.indexOf(" at ");
322-
if(atIndex == -1)
323-
return;
324-
method = line.substring(0, atIndex);
325-
line = line.substring(atIndex + 4);
326-
file = line.substring(0, line.lastIndexOf(':'));
327-
if(file.length() > 0){
328-
int lastfs = file.lastIndexOf('/');
329-
int lastbs = file.lastIndexOf('\\');
330-
int slash = (lastfs > lastbs)?lastfs:lastbs;
331-
if(slash != -1){
332-
String filename = file.substring(slash+1);
333-
file = file.substring(0,slash+1) + "<b>" + filename + "</b>";
334-
}
345+
346+
address = line.substring(0, line.indexOf(' '));
347+
line = line.substring(line.indexOf(' ') + 1);
348+
349+
int atIndex = line.indexOf("is in ");
350+
if(atIndex == -1) {
351+
return null;
335352
}
336-
line = line.substring(line.lastIndexOf(':') + 1);
337-
String html = "" +
338-
"<font color=green>" + address + ": </font>" +
339-
"<b><font color=blue>" + method + "</font></b> at " + file + " line <b>" + line + "</b>";
340-
outputText += html +"\n";
353+
try {
354+
method = line.substring(atIndex + 6, line.lastIndexOf('(') - 1);
355+
fileline = line.substring(line.lastIndexOf('(') + 1, line.lastIndexOf(')'));
356+
file = fileline.substring(0, fileline.lastIndexOf(':'));
357+
line = fileline.substring(fileline.lastIndexOf(':') + 1);
358+
if(file.length() > 0){
359+
int lastfs = file.lastIndexOf('/');
360+
int lastbs = file.lastIndexOf('\\');
361+
int slash = (lastfs > lastbs)?lastfs:lastbs;
362+
if(slash != -1){
363+
String filename = file.substring(slash+1);
364+
file = file.substring(0,slash+1) + "<b>" + filename + "</b>";
365+
}
366+
}
367+
html = "<font color=green>" + address + ": </font>" +
368+
"<b><font color=blue>" + method + "</font></b> at " +
369+
file + " line <b>" + line + "</b>";
370+
} catch (Exception e) {
371+
// Something weird in the GDB output format, report what we can
372+
html = "<font color=green>" + address + ": </font> " + line;
373+
}
374+
375+
return html;
376+
}
377+
378+
private void printLine(String line){
379+
String s = prettyPrintGDBLine(line);
380+
if (s != null)
381+
outputText += s +"\n";
341382
}
342383

343384
public void run() {
@@ -357,9 +398,31 @@ private void parseException(){
357398
}
358399
}
359400

360-
private void parseText(){
401+
// Strip out just the STACK lines or BACKTRACE line, and generate the reference log
402+
private void parseStackOrBacktrace(String regexp, boolean multiLine, String stripAfter) {
361403
String content = inputArea.getText();
362-
Pattern p = Pattern.compile("40[0-2](\\d|[a-f]){5}\\b");
404+
405+
Pattern strip;
406+
if (multiLine) strip = Pattern.compile(regexp, Pattern.DOTALL);
407+
else strip = Pattern.compile(regexp);
408+
Matcher stripMatch = strip.matcher(content);
409+
if (!stripMatch.find()) {
410+
return; // Didn't find it in the text box.
411+
}
412+
413+
// Strip out just the interesting bits to make RexExp sane
414+
content = content.substring(stripMatch.start(), stripMatch.end());
415+
416+
if (stripAfter != null) {
417+
Pattern after = Pattern.compile(stripAfter);
418+
Matcher afterMatch = after.matcher(content);
419+
if (afterMatch.find()) {
420+
content = content.substring(0, afterMatch.start());
421+
}
422+
}
423+
424+
// Anything looking like an instruction address, dump!
425+
Pattern p = Pattern.compile("40[0-2](\\d|[a-f]|[A-F]){5}\\b");
363426
int count = 0;
364427
Matcher m = p.matcher(content);
365428
while(m.find()) {
@@ -368,69 +431,119 @@ private void parseText(){
368431
if(count == 0){
369432
return;
370433
}
371-
String command[] = new String[4+count];
434+
String command[] = new String[7 + count*2];
372435
int i = 0;
373436
command[i++] = tool.getAbsolutePath();
374-
command[i++] = "-aipfC";
375-
command[i++] = "-e";
437+
command[i++] = "--batch";
376438
command[i++] = elf.getAbsolutePath();
439+
command[i++] = "-ex";
440+
command[i++] = "set listsize 1";
377441
m = p.matcher(content);
378442
while(m.find()) {
379-
command[i++] = content.substring(m.start(), m.end());
443+
command[i++] = "-ex";
444+
command[i++] = "l *0x"+content.substring(m.start(), m.end());
380445
}
381-
outputText += "<i>Decoding "+count+" results</i>\n";
446+
command[i++] = "-ex";
447+
command[i++] = "q";
448+
outputText += "\n<i>Decoding stack results</i>\n";
382449
sysExec(command);
383450
}
384451

452+
// Heavyweight call GDB, run list on address, and return result if it succeeded
453+
private String decodeFunctionAtAddress( String addr ) {
454+
String command[] = new String[9];
455+
command[0] = tool.getAbsolutePath();
456+
command[1] = "--batch";
457+
command[2] = elf.getAbsolutePath();
458+
command[3] = "-ex";
459+
command[4] = "set listsize 1";
460+
command[5] = "-ex";
461+
command[6] = "l *0x" + addr;
462+
command[7] = "-ex";
463+
command[8] = "q";
464+
465+
try {
466+
final Process proc = execRedirected(command);
467+
InputStreamReader reader = new InputStreamReader(proc.getInputStream());
468+
int c;
469+
String line = "";
470+
while ((c = reader.read()) != -1){
471+
if((char)c == '\r')
472+
continue;
473+
if((char)c == '\n' && line != ""){
474+
reader.close();
475+
return prettyPrintGDBLine(line);
476+
} else {
477+
line += (char)c;
478+
}
479+
}
480+
reader.close();
481+
} catch (Exception er) { }
482+
// Something went wrong
483+
return null;
484+
}
485+
486+
// Scan and report the last failed memory allocation attempt, if present on the ESP8266
385487
private void parseAlloc() {
386488
String content = inputArea.getText();
387-
Pattern p = Pattern.compile("last failed alloc call: 40[0-2](\\d|[-A-F]){5}\\((\\d)+\\)");
489+
Pattern p = Pattern.compile("last failed alloc call: 40[0-2](\\d|[a-f]|[A-F]){5}\\((\\d)+\\)");
388490
Matcher m = p.matcher(content);
389491
if (m.find()) {
390492
String fs = content.substring(m.start(), m.end());
391-
Pattern p2 = Pattern.compile("40[0-2](\\d|[A-F]){5}\\b");
493+
Pattern p2 = Pattern.compile("40[0-2](\\d|[a-f]|[A-F]){5}\\b");
392494
Matcher m2 = p2.matcher(fs);
393495
if (m2.find()) {
394-
String addr = fs.substring(m2.start(), m2.end());
395-
Pattern p3 = Pattern.compile("\\((\\d)+\\)");
396-
Matcher m3 = p3.matcher(fs);
397-
if (m3.find()) {
398-
String size = fs.substring(m3.start()+1, m3.end()-1);
399-
400-
String command[] = new String[5];
401-
command[0] = tool.getAbsolutePath();
402-
command[1] = "-aipfC";
403-
command[2] = "-e";
404-
command[3] = elf.getAbsolutePath();
405-
command[4] = addr;
406-
407-
try {
408-
final Process proc = ProcessUtils.exec(command);
409-
InputStreamReader reader = new InputStreamReader(proc.getInputStream());
410-
int c;
411-
String line = "";
412-
while ((c = reader.read()) != -1){
413-
if((char)c == '\r')
414-
continue;
415-
if((char)c == '\n' && line != ""){
416-
outputText += "Memory allocation of " + size + " bytes failed at " + line + "\n";
417-
break;
418-
} else {
419-
line += (char)c;
420-
}
421-
}
422-
reader.close();
423-
} catch (Exception er) { }
496+
String addr = fs.substring(m2.start(), m2.end());
497+
Pattern p3 = Pattern.compile("\\((\\d)+\\)");
498+
Matcher m3 = p3.matcher(fs);
499+
if (m3.find()) {
500+
String size = fs.substring(m3.start()+1, m3.end()-1);
501+
String line = decodeFunctionAtAddress(addr);
502+
if (line != null) {
503+
outputText += "Memory allocation of " + size + " bytes failed at " + line + "\n";
424504
}
505+
}
506+
}
507+
}
508+
}
509+
510+
// Filter out a register output given a regex (ESP8266/ESP32 differ in format)
511+
private void parseRegister(String regName, String prettyName) {
512+
String content = inputArea.getText();
513+
Pattern p = Pattern.compile(regName + "(\\d|[a-f]|[A-F]){8}\\b");
514+
Matcher m = p.matcher(content);
515+
if (m.find()) {
516+
String fs = content.substring(m.start(), m.end());
517+
Pattern p2 = Pattern.compile("(\\d|[a-f]|[A-F]){8}\\b");
518+
Matcher m2 = p2.matcher(fs);
519+
if (m2.find()) {
520+
String addr = fs.substring(m2.start(), m2.end());
521+
String line = decodeFunctionAtAddress(addr);
522+
if (line != null) {
523+
outputText += prettyName + ": " + line + "\n";
524+
} else {
525+
outputText += prettyName + ": <font color=\"green\">0x" + addr + "</font>\n";
526+
}
425527
}
426528
}
427529
}
428530

429531
private void runParser(){
430532
outputText = "<html><pre>\n";
533+
// Main error cause
431534
parseException();
535+
// ESP8266 register format
536+
parseRegister("epc1=0x", "<font color=\"red\">PC</font>");
537+
parseRegister("excvaddr=0x", "<font color=\"red\">EXCVADDR</font>");
538+
// ESP32 register format
539+
parseRegister("PC\\s*:\\s*(0x)?", "<font color=\"red\">PC</font>");
540+
parseRegister("EXCVADDR\\s*:\\s*(0x)?", "<font color=\"red\">EXCVADDR</font>");
541+
// Last memory allocation failure
432542
parseAlloc();
433-
parseText();
543+
// The stack on ESP8266, multiline
544+
parseStackOrBacktrace(">>>stack>>>(.)*", true, "<<<stack<<<");
545+
// The backtrace on ESP32, one-line only
546+
parseStackOrBacktrace("Backtrace:(.)*", false, null);
434547
}
435548

436549
private class CommitAction extends AbstractAction {

0 commit comments

Comments
 (0)