diff options
Diffstat (limited to 'libjava/gnu/gcj/runtime/NameFinder.java')
-rw-r--r-- | libjava/gnu/gcj/runtime/NameFinder.java | 548 |
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(); + } +} |