aboutsummaryrefslogtreecommitdiff
path: root/libjava/gnu/gcj/runtime/NameFinder.java
diff options
context:
space:
mode:
Diffstat (limited to 'libjava/gnu/gcj/runtime/NameFinder.java')
-rw-r--r--libjava/gnu/gcj/runtime/NameFinder.java548
1 files changed, 548 insertions, 0 deletions
diff --git a/libjava/gnu/gcj/runtime/NameFinder.java b/libjava/gnu/gcj/runtime/NameFinder.java
new file mode 100644
index 00000000000..1d729d252ac
--- /dev/null
+++ b/libjava/gnu/gcj/runtime/NameFinder.java
@@ -0,0 +1,548 @@
+/* NameFinder.java -- Translates addresses to StackTraceElements.
+ Copyright (C) 2002 Free Software Foundation, Inc.
+
+ This file is part of libgcj.
+
+This software is copyrighted work licensed under the terms of the
+Libgcj License. Please consult the file "LIBGCJ_LICENSE" for
+details. */
+
+package gnu.gcj.runtime;
+
+import gnu.gcj.RawData;
+
+import java.lang.StringBuffer;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.IOException;
+import java.io.File;
+
+/**
+ * Helper class that translates addresses (represented as longs) to a
+ * StackTraceElement array.
+ *
+ * There are a couple of system properties that can be set to manipulate the
+ * result (all default to true):
+ * <li>
+ * <ul><code>gnu.gcj.runtime.NameFinder.demangle</code>
+ * Whether names should be demangled.</ul>
+ * <ul><code>gnu.gcj.runtime.NameFinder.sanitize</code></ul>
+ * Whether calls to initialize exceptions and starting the runtime system
+ * should be removed from the stack trace. Only done when names are
+ * demangled.</ul>
+ * <ul><code>gnu.gcj.runtime.NameFinder.remove_unknown</code>
+ * Whether calls to unknown functions (class and method names are unknown)
+ * should be removed from the stack trace. Only done when the stack is
+ * sanitized.</ul>
+ * <ul><code>gnu.gcj.runtime.NameFinder.remove_interpreter</code>
+ * Whether runtime interpreter calls (methods in the _Jv_InterpMethod class
+ * and functions starting with 'ffi_') should be removed from the stack
+ * trace. Only done when the stack is sanitized.</ul>
+ * <ul><code>gnu.gcj.runtime.NameFinder.use_addr2line</code>
+ * Whether an external process (addr2line or addr2name.awk) should be used
+ * as fallback to convert the addresses to function names when the runtime
+ * is unable to do it through <code>dladdr</code>.</ul>
+ * </li>
+ *
+ * <code>close()</code> should be called to get rid of all resources.
+ *
+ * This class is used from <code>java.lang.VMThrowable</code>.
+ *
+ * Currently the <code>lookup(long[])</code> method is not thread safe.
+ * It can easily be made thread safe by synchronizing access to all external
+ * processes when used.
+ *
+ * @author Mark Wielaard (mark@klomp.org)
+ */
+public class NameFinder
+{
+ // Set these to false when not needed.
+ private static final boolean demangle
+ = Boolean.valueOf(System.getProperty
+ ("gnu.gcj.runtime.NameFinder.demangle", "true")
+ ).booleanValue();
+ private static final boolean sanitize
+ = Boolean.valueOf(System.getProperty
+ ("gnu.gcj.runtime.NameFinder.sanitize", "true")
+ ).booleanValue();
+ private static final boolean remove_unknown
+ = Boolean.valueOf(System.getProperty
+ ("gnu.gcj.runtime.NameFinder.remove_unknown", "true")
+ ).booleanValue();
+ private static final boolean remove_interpreter
+ = Boolean.valueOf(System.getProperty
+ ("gnu.gcj.runtime.NameFinder.remove_interpreter", "true")
+ ).booleanValue();
+ private static final boolean use_addr2line
+ = Boolean.valueOf(System.getProperty
+ ("gnu.gcj.runtime.NameFinder.use_addr2line", "true")
+ ).booleanValue();
+
+ /**
+ * The name of the currently running executable.
+ */
+ private final String executable;
+
+ /**
+ * Process used for demangling names.
+ */
+ private Process cppfilt;
+
+ private BufferedWriter cppfiltOut;
+ private BufferedReader cppfiltIn;
+
+ /**
+ * Process used for translating addresses to function/file names.
+ */
+ private Process addr2line;
+
+ private BufferedWriter addr2lineOut;
+ private BufferedReader addr2lineIn;
+
+ /**
+ * Creates a new NameFinder. Call close to get rid of any resources
+ * created while using the <code>lookup</code> methods.
+ */
+ public NameFinder()
+ {
+ executable = getExecutable();
+ Runtime runtime = Runtime.getRuntime();
+ if (demangle)
+ {
+ try
+ {
+ String[] exec = new String[] {"c++filt", "-s", "java"};
+ cppfilt = runtime.exec(exec);
+ cppfiltIn = new BufferedReader
+ (new InputStreamReader(cppfilt.getInputStream()));
+ cppfiltOut = new BufferedWriter
+ (new OutputStreamWriter(cppfilt.getOutputStream()));
+ }
+ catch (IOException ioe)
+ {
+ if (cppfilt != null)
+ cppfilt.destroy();
+ cppfilt = null;
+ }
+ }
+
+ if (use_addr2line)
+ {
+ try
+ {
+ String[] exec = new String[] {"addr2line", "-f", "-e", executable};
+ addr2line = runtime.exec(exec);
+ }
+ catch (IOException ioe)
+ {
+ try
+ {
+ String[] exec = new String[] {"addr2name.awk", executable};
+ addr2line = runtime.exec(exec);
+ }
+ catch (IOException ioe2) { addr2line = null; }
+ }
+
+ if (addr2line != null)
+ {
+ try
+ {
+ addr2lineIn = new BufferedReader
+ (new InputStreamReader(addr2line.getInputStream()));
+ addr2lineOut = new BufferedWriter
+ (new OutputStreamWriter(addr2line.getOutputStream()));
+ }
+ catch (IOException ioe)
+ {
+ addr2line.destroy();
+ addr2line = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the name of the currently running process.
+ */
+ native private static String getExecutable();
+
+ /**
+ * Tries to use dladdr to create the nth StackTraceElement from the given
+ * addresses. Returns null on failure.
+ */
+ native private StackTraceElement dladdrLookup(RawData addrs, int n);
+
+ /**
+ * Returns the nth element from the stack as a hex encoded String.
+ */
+ native private String getAddrAsString(RawData addrs, int n);
+
+ /**
+ * If nth element of stack is an interpreted frame, return the
+ * element representing the method being interpreted.
+ */
+ native private StackTraceElement lookupInterp(RawData addrs, int n);
+
+ /**
+ * Creates the nth StackTraceElement from the given native stacktrace.
+ */
+ private StackTraceElement lookup(RawData addrs, int n)
+ {
+ StackTraceElement result;
+
+ result = lookupInterp(addrs, n);
+ if (result == null)
+ result = dladdrLookup(addrs, n);
+ if (result == null)
+ {
+ String name = null;
+ String file = null;
+
+ String hex = getAddrAsString(addrs, n);
+
+ if (addr2line != null)
+ {
+ try
+ {
+ addr2lineOut.write(hex);
+ addr2lineOut.newLine();
+ addr2lineOut.flush();
+ name = addr2lineIn.readLine();
+ file = addr2lineIn.readLine();
+ }
+ catch (IOException ioe) { addr2line = null; }
+ }
+
+ if (name == null || "??".equals(name))
+ name = hex;
+
+ result = createStackTraceElement(name, file);
+ }
+
+ return result;
+ }
+
+ /**
+ * Given an Throwable and a native stacktrace returns an array of
+ * StackTraceElement containing class, method, file and linenumbers.
+ */
+ public StackTraceElement[] lookup(Throwable t, RawData addrs, int length)
+ {
+ StackTraceElement[] elements = new StackTraceElement[length];
+ for (int i=0; i < length; i++)
+ elements[i] = lookup(addrs, i);
+
+ if (demangle && sanitize)
+ return sanitizeStack(elements, t);
+ else
+ return elements;
+ }
+
+
+ /**
+ * Removes calls to initialize exceptions and the runtime system from
+ * the stack trace including stack frames of which nothing usefull is known.
+ * Throw away the top of the stack till we find the constructor(s)
+ * of this Throwable or at least the contructors of java.lang.Throwable
+ * or the actual fillInStackTrace call.
+ * Also throw away from the top everything before and including a runtime
+ * _Jv_Throw call.
+ */
+ private static StackTraceElement[] sanitizeStack(StackTraceElement[] elements,
+ Throwable t)
+ {
+ StackTraceElement[] stack;
+
+ String className = t.getClass().getName();
+ String consName;
+ int lastDot = className.lastIndexOf('.');
+ if (lastDot == -1)
+ consName = className + '(';
+ else
+ consName = className.substring(lastDot + 1) + '(';
+
+ int unknown = 0;
+ int interpreter = 0;
+ int last_throw = -1;
+ int length = elements.length;
+ int end = length-1;
+ for (int i = 0; i < length; i++)
+ {
+ String CName = elements[i].getClassName();
+ String MName = elements[i].getMethodName();
+ if ((CName == null && MName != null && MName.startsWith("_Jv_Throw"))
+ ||
+ (CName != null
+ && (CName.equals(className)
+ || CName.equals("java.lang.Throwable")
+ || CName.equals("java.lang.VMThrowable"))
+ && MName != null
+ && (MName.startsWith(consName)
+ || MName.startsWith("Throwable(")
+ || MName.startsWith("fillInStackTrace("))))
+ {
+ last_throw = i;
+ // Reset counting of unknown and interpreter frames.
+ unknown = 0;
+ interpreter = 0;
+ }
+ else if (remove_unknown && CName == null
+ && (MName == null || MName.startsWith("0x")))
+ unknown++;
+ else if (remove_interpreter
+ && ((CName == null
+ && MName != null && MName.startsWith("ffi_"))
+ || (CName != null && CName.equals("_Jv_InterpMethod"))))
+ interpreter++;
+ else if ("main(java.lang.String[])".equals(MName))
+ {
+ end = i;
+ break;
+ }
+ }
+ int begin = last_throw+1;
+
+ // Now filter out everything at the start and the end that is not part
+ // of the "normal" user program including any elements that are interpreter
+ // calls or have no usefull information whatsoever.
+ // Unless that means we filter out all info.
+ int nr_elements = end-begin-unknown-interpreter+1;
+ if ((begin > 0 || end < length-1 || unknown > 0 || interpreter > 0)
+ && nr_elements > 0)
+ {
+ stack = new StackTraceElement[nr_elements];
+ int pos =0;
+ for (int i=begin; i<=end; i++)
+ {
+ String MName = elements[i].getMethodName();
+ String CName = elements[i].getClassName();
+ if (remove_unknown && CName == null
+ && (MName == null || MName.startsWith("0x")))
+ ; // Skip unknown frame
+ else if (remove_interpreter
+ && ((CName == null
+ && MName != null && MName.startsWith("ffi_"))
+ || (CName != null && CName.equals("_Jv_InterpMethod"))))
+ ; // Skip interpreter runtime frame
+ else
+ {
+ stack[pos] = elements[i];
+ pos++;
+ }
+ }
+ }
+ else
+ stack = elements;
+
+ return stack;
+ }
+
+ /**
+ * Creates a StackTraceElement given a string and a filename.
+ * Splits the given string into the class and method part.
+ * The string name will be a demangled to a fully qualified java method
+ * string. The string file will be decomposed into a file name and possibly
+ * a line number. The name should never be null, but the file may be if it
+ * is unknown.
+ */
+ private StackTraceElement createStackTraceElement(String name, String file)
+ {
+ if (!demangle)
+ return new StackTraceElement(file, -1, null, name, false);
+
+ String s = demangleName(name);
+ String methodName = s;
+ String className = null;
+ int bracket = s.indexOf('(');
+ if (bracket > 0)
+ {
+ int dot = s.lastIndexOf('.', bracket);
+ if (dot > 0)
+ {
+ className = s.substring(0, dot);
+ methodName = s.substring(dot+1, s.length());
+ }
+ }
+
+ String fileName = file;
+ int line = -1;
+ if (fileName != null)
+ {
+ int colon = file.indexOf(':');
+ if (colon > 0)
+ {
+ fileName = file.substring(0, colon);
+ try
+ {
+ line = Integer.parseInt(file.substring(colon+1, file.length()));
+ }
+ catch (NumberFormatException nfe) { /* ignore */ }
+ }
+
+ if (line == 0)
+ line =-1;
+
+ if ("".equals(fileName) || "??".equals(fileName))
+ fileName = null;
+ else if (fileName != null)
+ {
+ try
+ {
+ fileName = new File(fileName).getCanonicalPath();
+ }
+ catch (IOException ioe) { /* ignore */ }
+ }
+ }
+
+ return new StackTraceElement(fileName, line, className, methodName, false);
+ }
+
+ /**
+ * Demangles the given String if possible. Returns the demangled String or
+ * the original string if demangling is impossible.
+ */
+ private String demangleName(String s)
+ {
+ if (cppfilt != null)
+ {
+ try
+ {
+ cppfiltOut.write(s);
+ cppfiltOut.newLine();
+ cppfiltOut.flush();
+ return cppfiltIn.readLine();
+ }
+ catch (IOException ioe) { cppfilt.destroy(); cppfilt = null; }
+ }
+
+ return s;
+ }
+
+ /**
+ * Returns human readable method name and aguments given a method type
+ * signature as known to the interpreter and a classname.
+ */
+ public static String demangleInterpreterMethod(String m, String cn)
+ {
+ int index = 0;
+ int length = m.length();
+ StringBuffer sb = new StringBuffer(length);
+
+ // Figure out the real method name
+ if (m.startsWith("<init>"))
+ {
+ String className;
+ int i = cn.lastIndexOf('.');
+ if (i < 0)
+ className = cn;
+ else
+ className = cn.substring(i + 1);
+ sb.append(className);
+ index += 7;
+ }
+ else
+ {
+ int i = m.indexOf('(');
+ if (i > 0)
+ {
+ sb.append(m.substring(0,i));
+ index += i + 1;
+ }
+ }
+
+ sb.append('(');
+
+ // Demangle the type arguments
+ int arrayDepth = 0;
+ char c = (index < length) ? m.charAt(index) : ')';
+ while (c != ')')
+ {
+ String type;
+ switch(c)
+ {
+ case 'B':
+ type = "byte";
+ break;
+ case 'C':
+ type = "char";
+ break;
+ case 'D':
+ type = "double";
+ break;
+ case 'F':
+ type = "float";
+ break;
+ case 'I':
+ type = "int";
+ break;
+ case 'J':
+ type = "long";
+ break;
+ case 'S':
+ type = "short";
+ break;
+ case 'Z':
+ type = "boolean";
+ break;
+ case 'L':
+ int i = m.indexOf(';', index);
+ if (i > 0)
+ {
+ type = m.substring(index+1, i);
+ index = i;
+ }
+ else
+ type = "<unknown ref>";
+ break;
+ case '[':
+ type = "";
+ arrayDepth++;
+ break;
+ default:
+ type = "<unknown " + c + '>';
+ }
+ sb.append(type);
+
+ // Handle arrays
+ if (c != '[' && arrayDepth > 0)
+ while (arrayDepth > 0)
+ {
+ sb.append("[]");
+ arrayDepth--;
+ }
+
+ index++;
+ char nc = (index < length) ? m.charAt(index) : ')';
+ if (c != '[' && nc != ')')
+ sb.append(", ");
+ c = nc;
+ }
+
+ // Stop. We are not interested in the return type.
+ sb.append(')');
+ return sb.toString();
+ }
+
+ /**
+ * Releases all resources used by this NameFinder.
+ */
+ public void close()
+ {
+ if (cppfilt != null)
+ cppfilt.destroy();
+
+ if (addr2line != null)
+ addr2line.destroy();
+ }
+
+ /**
+ * Calls close to get rid of all resources.
+ */
+ protected void finalize()
+ {
+ close();
+ }
+}