| /* 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.classpath.Configuration; |
| 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; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| |
| /** |
| * Lookup addresses (represented as longs) to find source & line number info. |
| * |
| * The following system property is available (defaults to true): |
| * <li> |
| * <ul><code>gnu.gcj.runtime.NameFinder.use_addr2line</code> |
| * Whether an external process, addr2line, should be used to look up |
| * source file and line number info. Throwable.printStackTrace() will |
| * be faster if this property is set to 'false'. |
| * </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. </ul> |
| * </li> |
| * |
| * <code>close()</code> should be called to get rid of all resources. |
| * |
| * This class is used from <code>java.lang.VMThrowable</code>. |
| * |
| * @author Mark Wielaard (mark@klomp.org) |
| */ |
| public class NameFinder |
| { |
| /** |
| * The name of the binary to look up. |
| */ |
| private String binaryFile; |
| private String sourceFile; |
| private int lineNum; |
| private HashMap procs = new HashMap(); |
| /** |
| * Set of binary files that addr2line should not be called on. |
| */ |
| private static Set blacklist = Collections.synchronizedSet(new HashSet()); |
| |
| private static boolean use_addr2line |
| = Boolean.valueOf(System.getProperty |
| ("gnu.gcj.runtime.NameFinder.use_addr2line", "true") |
| ).booleanValue(); |
| |
| private static boolean show_raw |
| = Boolean.valueOf(System.getProperty |
| ("gnu.gcj.runtime.NameFinder.show_raw", "false") |
| ).booleanValue(); |
| |
| /** |
| * Return true if raw addresses should be printed in stacktraces |
| * when no line number information is available. |
| */ |
| static final boolean showRaw() |
| { |
| return show_raw; |
| } |
| |
| private static final boolean remove_unknown |
| = Boolean.valueOf(System.getProperty |
| ("gnu.gcj.runtime.NameFinder.remove_unknown", "true") |
| ).booleanValue(); |
| |
| /** |
| * Return true if non-Java frames should be removed from stack |
| * traces. |
| */ |
| static final boolean removeUnknown() |
| { |
| return remove_unknown; |
| } |
| |
| class Addr2Line |
| { |
| Process proc; |
| BufferedWriter out; |
| BufferedReader in; |
| |
| Addr2Line(String binaryFile) |
| { |
| try |
| { |
| String[] exec = new String[] {"addr2line", "-e", binaryFile}; |
| Runtime runtime = Runtime.getRuntime(); |
| proc = runtime.exec(exec); |
| } |
| catch (IOException ioe) |
| { |
| } |
| |
| if (proc != null) |
| { |
| in = new BufferedReader(new InputStreamReader(proc.getInputStream())); |
| out = new BufferedWriter(new OutputStreamWriter(proc.getOutputStream())); |
| } |
| } |
| |
| void close() |
| { |
| try |
| { |
| if (in != null) |
| in.close(); |
| if (out != null) |
| out.close(); |
| } |
| catch (IOException x) {} |
| if (proc != null) |
| proc.destroy(); |
| } |
| } |
| |
| /** |
| * Create a new NameFinder to lookup names in binaryFile. Call close to get rid of any |
| * resources created while using the <code>lookup</code> methods. |
| */ |
| public NameFinder() |
| { |
| } |
| |
| /** |
| * Returns the source file name if lookup() was successful. If the source file could not be |
| * determined, the binary name will be returned instead. |
| */ |
| public String getSourceFile() |
| { |
| String file; |
| if (sourceFile != null) |
| file = sourceFile; |
| else |
| file = binaryFile; |
| |
| return file.substring(file.lastIndexOf(File.separator) + 1, file.length()); |
| } |
| |
| /** |
| * If lookup() was successful, returns the line number of addr. If the line number could not |
| * be determined, -1 is returned. |
| */ |
| public int getLineNum() |
| { |
| return lineNum; |
| } |
| |
| public void lookup (String file, long addr) |
| { |
| binaryFile = file; |
| sourceFile = null; |
| lineNum = -1; |
| |
| if (! use_addr2line || blacklist.contains(file)) |
| return; |
| Addr2Line addr2line = (Addr2Line) procs.get(file); |
| if (addr2line == null) |
| { |
| addr2line = new Addr2Line(file); |
| procs.put(file, addr2line); |
| } |
| |
| if (addr2line.proc == null) |
| { |
| use_addr2line = false; |
| return; |
| } |
| |
| String hexAddr = "0x" + Long.toHexString(addr); |
| String name; |
| |
| try |
| { |
| addr2line.out.write(hexAddr); |
| addr2line.out.newLine(); |
| addr2line.out.flush(); |
| String result = addr2line.in.readLine(); |
| |
| if (result.indexOf("??") == -1) |
| { |
| int split = result.lastIndexOf(':'); |
| sourceFile = result.substring(0, split); |
| String lineNumStr = result.substring(split + 1, result.length()); |
| lineNum = Integer.parseInt (lineNumStr); |
| } |
| else |
| { |
| /* This binary has no debug info (assuming addr was valid). |
| Avoid repeat addr2line invocations. */ |
| blacklist.add(binaryFile); |
| } |
| } |
| catch (IOException ioe) |
| { |
| addr2line = null; |
| } |
| catch (NumberFormatException x) |
| { |
| } |
| } |
| |
| /** |
| * 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() |
| { |
| Iterator itr = procs.values().iterator(); |
| while (itr.hasNext()) |
| { |
| Addr2Line proc = (Addr2Line) itr.next(); |
| proc.close(); |
| } |
| } |
| } |