From 8ba299b4eefeaefaa719704a22d537ab2c2b5ac6 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Mon, 22 Jan 2018 19:13:15 -0800 Subject: [PATCH 1/2] Add optional, automatic OOM logger parsing An updated ESP8266 panic function can print out the calling function/line and size requested for the last malloc/realloc/calloc/new allocation that failed, without the overhead of full the OOM stack. Add parsing for this line, when present, and output the function, file, line, and amount of memory requested to the display. When not present, do nothing different. --- src/EspExceptionDecoder.java | 45 ++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/EspExceptionDecoder.java b/src/EspExceptionDecoder.java index a9c6428..516a916 100644 --- a/src/EspExceptionDecoder.java +++ b/src/EspExceptionDecoder.java @@ -382,9 +382,54 @@ private void parseText(){ sysExec(command); } + private void parseAlloc() { + String content = inputArea.getText(); + Pattern p = Pattern.compile("last failed alloc call: 40[0-2](\\d|[-A-F]){5}\\((\\d)+\\)"); + Matcher m = p.matcher(content); + if (m.find()) { + String fs = content.substring(m.start(), m.end()); + Pattern p2 = Pattern.compile("40[0-2](\\d|[A-F]){5}\\b"); + Matcher m2 = p2.matcher(fs); + if (m2.find()) { + String addr = fs.substring(m2.start(), m2.end()); + Pattern p3 = Pattern.compile("\\((\\d)+\\)"); + Matcher m3 = p3.matcher(fs); + if (m3.find()) { + String size = fs.substring(m3.start()+1, m3.end()-1); + + String command[] = new String[5]; + command[0] = tool.getAbsolutePath(); + command[1] = "-aipfC"; + command[2] = "-e"; + command[3] = elf.getAbsolutePath(); + command[4] = addr; + + try { + final Process proc = ProcessUtils.exec(command); + InputStreamReader reader = new InputStreamReader(proc.getInputStream()); + int c; + String line = ""; + while ((c = reader.read()) != -1){ + if((char)c == '\r') + continue; + if((char)c == '\n' && line != ""){ + outputText += "Memory allocation of " + size + " bytes failed at " + line + "\n"; + break; + } else { + line += (char)c; + } + } + reader.close(); + } catch (Exception er) { } + } + } + } + } + private void runParser(){ outputText = "
\n";
     parseException();
+    parseAlloc();
     parseText();
   }
 

From a78672da204151cc93979a96ed9f89139a73893f Mon Sep 17 00:00:00 2001
From: "Earle F. Philhower, III" 
Date: Mon, 22 Jan 2018 21:34:31 -0800
Subject: [PATCH 2/2] 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 https://github.com/jcmvbkbc/binutils-gdb-xtensa/issues/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>>>" to the end.
---
 src/EspExceptionDecoder.java | 259 +++++++++++++++++++++++++----------
 1 file changed, 186 insertions(+), 73 deletions(-)

diff --git a/src/EspExceptionDecoder.java b/src/EspExceptionDecoder.java
index 516a916..ed288bd 100644
--- a/src/EspExceptionDecoder.java
+++ b/src/EspExceptionDecoder.java
@@ -43,6 +43,7 @@
 //import processing.app.SketchData;
 import processing.app.debug.TargetPlatform;
 import processing.app.helpers.FileUtils;
+import processing.app.helpers.OSUtils;
 import processing.app.helpers.ProcessUtils;
 import processing.app.tools.Tool;
 
@@ -96,9 +97,35 @@ public String getMenuTitle() {
     return "ESP Exception Decoder";
   }
 
+  // Original code from processing.app.helpers.ProcessUtils.exec()
+  // Need custom version to redirect STDERR to STDOUT for GDB processing
+  public static Process execRedirected(String[] command) throws IOException {
+    ProcessBuilder pb;
+
+    // No problems on linux and mac
+    if (!OSUtils.isWindows()) {
+      pb = new ProcessBuilder(command);
+    } else {
+      // Brutal hack to workaround windows command line parsing.
+      // http://stackoverflow.com/questions/5969724/java-runtime-exec-fails-to-escape-characters-properly
+      // http://msdn.microsoft.com/en-us/library/a1y7w461.aspx
+      // http://bugs.sun.com/view_bug.do?bug_id=6468220
+      // http://bugs.sun.com/view_bug.do?bug_id=6518827
+      String[] cmdLine = new String[command.length];
+      for (int i = 0; i < command.length; i++)
+        cmdLine[i] = command[i].replace("\"", "\\\"");
+      pb = new ProcessBuilder(cmdLine);
+      Map env = pb.environment();
+      env.put("CYGWIN", "nodosfilewarning");
+    }
+    pb.redirectErrorStream(true);
+
+    return pb.start();
+  }
+
   private int listenOnProcess(String[] arguments){
     try {
-      final Process p = ProcessUtils.exec(arguments);
+      final Process p = execRedirected(arguments);
       Thread thread = new Thread() {
         public void run() {
           try {
@@ -246,16 +273,16 @@ private void createAndUpload(){
       gccPath = platform.getFolder() + "/tools/xtensa-"+tc+"-elf";
     }
 
-    String addr2line;
+    String gdb;
     if(PreferencesData.get("runtime.os").contentEquals("windows"))
-      addr2line = "xtensa-"+tc+"-elf-addr2line.exe";
+      gdb = "xtensa-"+tc+"-elf-gdb.exe";
     else
-      addr2line = "xtensa-"+tc+"-elf-addr2line";
+      gdb = "xtensa-"+tc+"-elf-gdb";
 
-    tool = new File(gccPath + "/bin", addr2line);
+    tool = new File(gccPath + "/bin", gdb);
     if (!tool.exists() || !tool.isFile()) {
       System.err.println();
-      editor.statusError("ERROR: "+addr2line+" not found!");
+      editor.statusError("ERROR: "+gdb+" not found!");
       return;
     }
 
@@ -309,35 +336,49 @@ private void createAndUpload(){
     frame.setVisible(true);
   }
 
-  private void printLine(String line){
-    String address = "", method = "", file = "";
-    if(line.startsWith("0x")){
-      address = line.substring(0, line.indexOf(':'));
-      line = line.substring(line.indexOf(':') + 2);
-    } else if(line.startsWith("(inlined by)")){
-      line = line.substring(13);
-      address = "inlined by";
+  private String prettyPrintGDBLine(String line) {
+    String address = "", method = "", file = "", fileline = "", html = "";
+
+    if (!line.startsWith("0x")) {
+      return null;
     }
-    int atIndex = line.indexOf(" at ");
-    if(atIndex == -1)
-      return;
-    method = line.substring(0, atIndex);
-    line = line.substring(atIndex + 4);
-    file = line.substring(0, line.lastIndexOf(':'));
-    if(file.length() > 0){
-      int lastfs = file.lastIndexOf('/');
-      int lastbs = file.lastIndexOf('\\');
-      int slash = (lastfs > lastbs)?lastfs:lastbs;
-      if(slash != -1){
-        String filename = file.substring(slash+1);
-        file = file.substring(0,slash+1) + "" + filename + "";
-      }
+  
+    address = line.substring(0, line.indexOf(' '));
+    line = line.substring(line.indexOf(' ') + 1);
+
+    int atIndex = line.indexOf("is in ");
+    if(atIndex == -1) {
+      return null;
     }
-    line = line.substring(line.lastIndexOf(':') + 1);
-    String html = "" +
-      "" + address + ": " +
-      "" + method + " at " + file + " line " + line + "";
-    outputText += html +"\n";
+    try {
+        method = line.substring(atIndex + 6, line.lastIndexOf('(') - 1);
+        fileline = line.substring(line.lastIndexOf('(') + 1, line.lastIndexOf(')'));
+        file = fileline.substring(0, fileline.lastIndexOf(':'));
+        line = fileline.substring(fileline.lastIndexOf(':') + 1);
+        if(file.length() > 0){
+          int lastfs = file.lastIndexOf('/');
+          int lastbs = file.lastIndexOf('\\');
+          int slash = (lastfs > lastbs)?lastfs:lastbs;
+          if(slash != -1){
+            String filename = file.substring(slash+1);
+            file = file.substring(0,slash+1) + "" + filename + "";
+          }
+        }
+        html = "" + address + ": " +
+               "" + method + " at " +
+               file + " line " + line + "";
+    } catch (Exception e) {
+        // Something weird in the GDB output format, report what we can
+        html = "" + address + ":  " + line;
+    }
+
+    return html;
+  }
+
+  private void printLine(String line){
+    String s = prettyPrintGDBLine(line);
+    if (s != null) 
+      outputText += s +"\n";
   }
 
   public void run() {
@@ -357,9 +398,31 @@ private void parseException(){
     }
   }
 
-  private void parseText(){
+  // Strip out just the STACK lines or BACKTRACE line, and generate the reference log
+  private void parseStackOrBacktrace(String regexp, boolean multiLine, String stripAfter) {
     String content = inputArea.getText();
-    Pattern p = Pattern.compile("40[0-2](\\d|[a-f]){5}\\b");
+
+    Pattern strip;
+    if (multiLine) strip = Pattern.compile(regexp, Pattern.DOTALL);
+    else strip = Pattern.compile(regexp);
+    Matcher stripMatch = strip.matcher(content);
+    if (!stripMatch.find()) {
+      return; // Didn't find it in the text box.
+    }
+
+    // Strip out just the interesting bits to make RexExp sane
+    content = content.substring(stripMatch.start(), stripMatch.end());
+
+    if (stripAfter != null) {
+      Pattern after = Pattern.compile(stripAfter);
+      Matcher afterMatch = after.matcher(content);
+      if (afterMatch.find()) {
+          content = content.substring(0, afterMatch.start());
+      }
+    }
+
+    // Anything looking like an instruction address, dump!
+    Pattern p = Pattern.compile("40[0-2](\\d|[a-f]|[A-F]){5}\\b");
     int count = 0;
     Matcher m = p.matcher(content);
     while(m.find()) {
@@ -368,69 +431,119 @@ private void parseText(){
     if(count == 0){
       return;
     }
-    String command[] = new String[4+count];
+    String command[] = new String[7 + count*2];
     int i = 0;
     command[i++] = tool.getAbsolutePath();
-    command[i++] = "-aipfC";
-    command[i++] = "-e";
+    command[i++] = "--batch";
     command[i++] = elf.getAbsolutePath();
+    command[i++] = "-ex";
+    command[i++] = "set listsize 1";
     m = p.matcher(content);
     while(m.find()) {
-      command[i++] = content.substring(m.start(), m.end());
+      command[i++] = "-ex";
+      command[i++] = "l *0x"+content.substring(m.start(), m.end());
     }
-    outputText += "Decoding "+count+" results\n";
+    command[i++] = "-ex";
+    command[i++] = "q";
+    outputText += "\nDecoding stack results\n";
     sysExec(command);
   }
 
+  // Heavyweight call GDB, run list on address, and return result if it succeeded
+  private String decodeFunctionAtAddress( String addr ) {
+    String command[] = new String[9];
+    command[0] = tool.getAbsolutePath();
+    command[1] = "--batch";
+    command[2] = elf.getAbsolutePath();
+    command[3] = "-ex";
+    command[4] = "set listsize 1";
+    command[5] = "-ex";
+    command[6] = "l *0x" + addr;
+    command[7] = "-ex";
+    command[8] = "q";
+
+    try {
+      final Process proc = execRedirected(command);
+      InputStreamReader reader = new InputStreamReader(proc.getInputStream());
+      int c;
+      String line = "";
+      while ((c = reader.read()) != -1){
+        if((char)c == '\r')
+          continue;
+        if((char)c == '\n' && line != ""){
+          reader.close();
+          return prettyPrintGDBLine(line);
+        } else {
+         line += (char)c;
+        }
+      }
+      reader.close();
+    } catch (Exception er) { }
+    // Something went wrong
+    return null;
+  }
+
+  // Scan and report the last failed memory allocation attempt, if present on the ESP8266
   private void parseAlloc() {
     String content = inputArea.getText();
-    Pattern p = Pattern.compile("last failed alloc call: 40[0-2](\\d|[-A-F]){5}\\((\\d)+\\)");
+    Pattern p = Pattern.compile("last failed alloc call: 40[0-2](\\d|[a-f]|[A-F]){5}\\((\\d)+\\)");
     Matcher m = p.matcher(content);
     if (m.find()) {
       String fs = content.substring(m.start(), m.end());
-      Pattern p2 = Pattern.compile("40[0-2](\\d|[A-F]){5}\\b");
+      Pattern p2 = Pattern.compile("40[0-2](\\d|[a-f]|[A-F]){5}\\b");
       Matcher m2 = p2.matcher(fs);
       if (m2.find()) {
-          String addr = fs.substring(m2.start(), m2.end());
-          Pattern p3 = Pattern.compile("\\((\\d)+\\)");
-          Matcher m3 = p3.matcher(fs);
-          if (m3.find()) {
-            String size = fs.substring(m3.start()+1, m3.end()-1);
-
-            String command[] = new String[5];
-            command[0] = tool.getAbsolutePath();
-            command[1] = "-aipfC";
-            command[2] = "-e";
-            command[3] = elf.getAbsolutePath();
-            command[4] = addr;
-      
-            try {
-              final Process proc = ProcessUtils.exec(command);
-              InputStreamReader reader = new InputStreamReader(proc.getInputStream());
-              int c;
-              String line = "";
-              while ((c = reader.read()) != -1){
-                if((char)c == '\r')
-                  continue;
-                if((char)c == '\n' && line != ""){
-                  outputText += "Memory allocation of " + size + " bytes failed at " + line + "\n";
-                  break;
-                } else {
-                 line += (char)c;
-                }
-              }
-              reader.close();
-            } catch (Exception er) { }
+        String addr = fs.substring(m2.start(), m2.end());
+        Pattern p3 = Pattern.compile("\\((\\d)+\\)");
+        Matcher m3 = p3.matcher(fs);
+        if (m3.find()) {
+          String size = fs.substring(m3.start()+1, m3.end()-1);
+          String line = decodeFunctionAtAddress(addr);
+          if (line != null) {
+            outputText += "Memory allocation of " + size + " bytes failed at " + line + "\n";
           }
+        }
+      }
+    }
+  }
+
+  // Filter out a register output given a regex (ESP8266/ESP32 differ in format)
+  private void parseRegister(String regName, String prettyName) {
+    String content = inputArea.getText();
+    Pattern p = Pattern.compile(regName + "(\\d|[a-f]|[A-F]){8}\\b");
+    Matcher m = p.matcher(content);
+    if (m.find()) {
+      String fs = content.substring(m.start(), m.end());
+      Pattern p2 = Pattern.compile("(\\d|[a-f]|[A-F]){8}\\b");
+      Matcher m2 = p2.matcher(fs);
+      if (m2.find()) {
+        String addr = fs.substring(m2.start(), m2.end());
+        String line = decodeFunctionAtAddress(addr);
+        if (line != null) {
+          outputText += prettyName + ": " + line + "\n";
+        } else {
+          outputText += prettyName + ": 0x" + addr + "\n";
+        }
       }
     }
   }
 
   private void runParser(){
     outputText = "
\n";
+    // Main error cause
     parseException();
+    // ESP8266 register format
+    parseRegister("epc1=0x", "PC");
+    parseRegister("excvaddr=0x", "EXCVADDR");
+    // ESP32 register format
+    parseRegister("PC\\s*:\\s*(0x)?", "PC");
+    parseRegister("EXCVADDR\\s*:\\s*(0x)?", "EXCVADDR");
+    // Last memory allocation failure
     parseAlloc();
-    parseText();
+    // The stack on ESP8266, multiline
+    parseStackOrBacktrace(">>>stack>>>(.)*", true, "<<