| /* NameFinder.java -- Translates addresses to StackTraceElements. |
| Copyright (C) 2002, 2004 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_internal</code> |
| * Whether runtime internal calls (methods in the internal _Jv_* classes |
| * 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(); |
| |
| // The remove_interpreter name is an old 3.3/3.4 (deprecated) synonym. |
| private static final boolean remove_internal |
| = (Boolean.valueOf(System.getProperty |
| ("gnu.gcj.runtime.NameFinder.remove_internal", "true") |
| ).booleanValue() |
| || |
| 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; |
| |
| /** |
| * Flag set if using addr2name.awk instead of addr2line from binutils. |
| */ |
| private boolean usingAddr2name = false; |
| |
| /** |
| * 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); |
| usingAddr2name = true; |
| } |
| catch (IOException ioe2) { addr2line = null; } |
| } |
| |
| if (addr2line != null) |
| { |
| addr2lineIn = new BufferedReader |
| (new InputStreamReader(addr2line.getInputStream())); |
| addr2lineOut = new BufferedWriter |
| (new OutputStreamWriter(addr2line.getOutputStream())); |
| } |
| } |
| } |
| |
| /** |
| * 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); |
| |
| /** |
| * Returns the label that is exported for the given method name. |
| */ |
| native private String getExternalLabel(String name); |
| |
| /** |
| * 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(); |
| |
| // addr2line uses symbolic debugging information instead |
| // of the actually exported labels as addr2name.awk does. |
| // This name might need some modification, depending on |
| // the system, to make it a label like that returned |
| // by addr2name.awk or dladdr. |
| if (! usingAddr2name) |
| if (name != null && ! "??".equals (name)) |
| name = getExternalLabel (name); |
| } |
| 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, StackTrace trace) |
| { |
| RawData addrs = trace.stackTraceAddrs(); |
| int length = trace.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 internal = 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 internal frames. |
| unknown = 0; |
| internal = 0; |
| } |
| else if (remove_unknown && CName == null |
| && (MName == null || MName.startsWith("0x"))) |
| unknown++; |
| else if (remove_internal |
| && ((CName == null |
| && MName != null && MName.startsWith("ffi_")) |
| || (CName != null && CName.startsWith("_Jv_")) |
| || (CName == null && MName != null |
| && MName.startsWith("_Jv_")))) |
| internal++; |
| else if (("java.lang.Thread".equals(CName) |
| || "gnu.java.lang.MainThread".equals(CName)) |
| && "run()".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 internal |
| // calls or have no usefull information whatsoever. |
| // Unless that means we filter out all info. |
| int nr_elements = end - begin - unknown - internal + 1; |
| if ((begin > 0 || end < length-1 || unknown > 0 || internal > 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_internal |
| && ((CName == null |
| && MName != null && MName.startsWith("ffi_")) |
| || (CName != null && CName.startsWith("_Jv_")) |
| || (CName == null && MName != null |
| && MName.startsWith("_Jv_")))) |
| ; // Skip internal runtime frame |
| else |
| { |
| // Null Class or Method name in elements are not allowed. |
| if (MName == null || CName == null) |
| { |
| MName = MName == null ? "" : MName; |
| CName = CName == null ? "" : CName; |
| stack[pos] = newElement(elements[i].getFileName(), |
| elements[i].getLineNumber(), |
| CName, MName, |
| elements[i].isNativeMethod()); |
| } |
| else |
| stack[pos] = elements[i]; |
| pos++; |
| } |
| } |
| } |
| else |
| stack = elements; |
| |
| return stack; |
| } |
| |
| /** |
| * Native helper method to create a StackTraceElement. Needed to work |
| * around normal Java access restrictions. |
| */ |
| native static private StackTraceElement newElement(String fileName, |
| int lineNumber, |
| String className, |
| String methName, |
| boolean isNative); |
| |
| /** |
| * 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 newElement(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.lastIndexOf(':'); |
| 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 newElement(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(); |
| } |
| } |