Source for gnu.gcj.runtime.NameFinder

   1: /* NameFinder.java -- Translates addresses to StackTraceElements.
   2:    Copyright (C) 2002, 2004 Free Software Foundation, Inc.
   3: 
   4:    This file is part of libgcj.
   5: 
   6: This software is copyrighted work licensed under the terms of the
   7: Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
   8: details.  */
   9: 
  10: package gnu.gcj.runtime;
  11: 
  12: import gnu.classpath.Configuration;
  13: import gnu.gcj.RawData;
  14: 
  15: import java.lang.StringBuffer;
  16: 
  17: import java.io.BufferedReader;
  18: import java.io.BufferedWriter;
  19: import java.io.InputStreamReader;
  20: import java.io.OutputStreamWriter;
  21: import java.io.IOException;
  22: import java.io.File;
  23: import java.util.Collections;
  24: import java.util.Iterator;
  25: import java.util.HashMap;
  26: import java.util.HashSet;
  27: import java.util.Set;
  28: 
  29: 
  30: /**
  31:  * Lookup addresses (represented as longs) to find source & line number info.
  32:  *
  33:  * The following system property is available (defaults to true):
  34:  * <li>
  35:  * <ul><code>gnu.gcj.runtime.NameFinder.use_addr2line</code>
  36:  *     Whether an external process, addr2line, should be used to look up
  37:  *     source file and line number info. Throwable.printStackTrace() will
  38:  *     be faster if this property is set to 'false'.
  39:  * </ul>
  40:  * <ul><code>gnu.gcj.runtime.NameFinder.remove_unknown</code>
  41:  *     Whether calls to unknown functions (class and method names are unknown)
  42:  *     should be removed from the stack trace. </ul>
  43:  * </li>
  44:  *
  45:  * <code>close()</code> should be called to get rid of all resources.
  46:  *
  47:  * This class is used from <code>java.lang.VMThrowable</code>.
  48:  *
  49:  * @author Mark Wielaard (mark@klomp.org)
  50:  */
  51: public class NameFinder
  52: {
  53:   /**
  54:    * The name of the binary to look up.
  55:    */
  56:   private String binaryFile;
  57:   private String sourceFile;
  58:   private int lineNum;
  59:   private HashMap procs = new HashMap();
  60:   /**
  61:    * Set of binary files that addr2line should not be called on.
  62:    */
  63:   private static Set blacklist = Collections.synchronizedSet(new HashSet());
  64: 
  65:   private static boolean use_addr2line
  66:           = Boolean.valueOf(System.getProperty
  67:                 ("gnu.gcj.runtime.NameFinder.use_addr2line", "true")
  68:             ).booleanValue();
  69: 
  70:   private static boolean show_raw
  71:           = Boolean.valueOf(System.getProperty
  72:                 ("gnu.gcj.runtime.NameFinder.show_raw", "false")
  73:             ).booleanValue();
  74: 
  75:   /**
  76:    * Return true if raw addresses should be printed in stacktraces
  77:    * when no line number information is available.
  78:    */
  79:   static final boolean showRaw()
  80:   {
  81:     return show_raw;
  82:   }
  83: 
  84:   private static final boolean remove_unknown
  85:       = Boolean.valueOf(System.getProperty
  86:         ("gnu.gcj.runtime.NameFinder.remove_unknown", "true")
  87:         ).booleanValue();
  88: 
  89:   /**
  90:    * Return true if non-Java frames should be removed from stack
  91:    * traces.
  92:    */
  93:   static final boolean removeUnknown()
  94:   {
  95:     return remove_unknown;
  96:   }
  97: 
  98:   class Addr2Line
  99:   {
 100:     Process proc;
 101:     BufferedWriter out;
 102:     BufferedReader in;
 103: 
 104:     Addr2Line(String binaryFile)
 105:     {
 106:       try
 107:       {
 108:     String[] exec = new String[] {"addr2line", "-e", binaryFile};
 109:     Runtime runtime = Runtime.getRuntime();
 110:     proc = runtime.exec(exec);
 111:       }
 112:       catch (IOException ioe)
 113:       {
 114:       }
 115: 
 116:       if (proc != null)
 117:       {
 118:     in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
 119:     out = new BufferedWriter(new OutputStreamWriter(proc.getOutputStream()));
 120:       }
 121:     }
 122:     
 123:     void close()
 124:     {
 125:       try
 126:       {
 127:     if (in != null)
 128:       in.close();
 129:     if (out != null)
 130:       out.close();
 131:       }
 132:       catch (IOException x) {}
 133:       if (proc != null)
 134:     proc.destroy();
 135:     }
 136:   }
 137: 
 138:   /**
 139:    * Create a new NameFinder to lookup names in binaryFile. Call close to get rid of any 
 140:    * resources created while using the <code>lookup</code> methods.
 141:    */
 142:   public NameFinder()
 143:   {
 144:   }
 145: 
 146:   /**
 147:    * Returns the source file name if lookup() was successful. If the source file could not be 
 148:    * determined, the binary name will be returned instead.
 149:    */
 150:   public String getSourceFile()
 151:   {
 152:     String file;
 153:     if (sourceFile != null)
 154:       file = sourceFile;
 155:     else
 156:       file = binaryFile;
 157:     
 158:     return file.substring(file.lastIndexOf(File.separator) + 1, file.length());
 159:   }
 160: 
 161:   /**
 162:    * If lookup() was successful, returns the line number of addr. If the line number could not
 163:    * be determined, -1 is returned.
 164:    */  
 165:   public int getLineNum()
 166:   {
 167:     return lineNum;
 168:   }
 169:   
 170:   public void lookup (String file, long addr)
 171:   {
 172:     binaryFile = file;
 173:     sourceFile = null;
 174:     lineNum = -1;
 175:     
 176:     if (! use_addr2line || blacklist.contains(file))
 177:       return;
 178:     Addr2Line addr2line = (Addr2Line) procs.get(file);
 179:     if (addr2line == null)
 180:       {
 181:       addr2line = new Addr2Line(file);
 182:       procs.put(file, addr2line);
 183:       }
 184:     
 185:     if (addr2line.proc == null)
 186:       {
 187:         use_addr2line = false;
 188:     return;
 189:       }
 190:     
 191:     String hexAddr = "0x" + Long.toHexString(addr);
 192:     String name;
 193: 
 194:     try
 195:       {
 196:       addr2line.out.write(hexAddr);
 197:       addr2line.out.newLine();
 198:       addr2line.out.flush();
 199:       String result = addr2line.in.readLine();
 200: 
 201:       if (result.indexOf("??") == -1)
 202:     {
 203:       int split = result.lastIndexOf(':');
 204:       sourceFile = result.substring(0, split);
 205:       String lineNumStr = result.substring(split + 1, result.length());
 206:       lineNum = Integer.parseInt (lineNumStr);
 207:     }
 208:       else
 209:         {
 210:       /* This binary has no debug info (assuming addr was valid). 
 211:          Avoid repeat addr2line invocations. */
 212:        blacklist.add(binaryFile);
 213:     }
 214:       }
 215:     catch (IOException ioe)
 216:       {
 217:       addr2line = null;
 218:       }
 219:     catch (NumberFormatException x)
 220:       {
 221:       }
 222:   }
 223: 
 224:   /**
 225:    * Returns human readable method name and aguments given a method type
 226:    * signature as known to the interpreter and a classname.
 227:    */
 228:   public static String demangleInterpreterMethod(String m, String cn)
 229:   {
 230:     int index = 0;
 231:     int length = m.length();
 232:     StringBuffer sb = new StringBuffer(length);
 233: 
 234:     // Figure out the real method name
 235:     if (m.startsWith("<init>"))
 236:       {
 237:     String className;
 238:     int i = cn.lastIndexOf('.');
 239:     if (i < 0)
 240:       className = cn;
 241:     else
 242:       className = cn.substring(i + 1);
 243:     sb.append(className);
 244:     index += 7;
 245:       }
 246:     else
 247:       {
 248:     int i = m.indexOf('(');
 249:     if (i > 0)
 250:       {
 251:         sb.append(m.substring(0,i));
 252:         index += i + 1;
 253:       }
 254:       }
 255: 
 256:     sb.append('(');
 257: 
 258:     // Demangle the type arguments
 259:     int arrayDepth = 0;
 260:     char c = (index < length) ? m.charAt(index) : ')';
 261:     while (c != ')')      
 262:       {
 263:     String type;
 264:     switch(c)
 265:     {
 266:           case 'B':
 267:             type = "byte";
 268:         break;
 269:           case 'C':
 270:             type = "char";
 271:         break;
 272:           case 'D':
 273:             type = "double";
 274:         break;
 275:           case 'F':
 276:             type = "float";
 277:         break;
 278:           case 'I':
 279:             type = "int";
 280:         break;
 281:           case 'J':
 282:             type = "long";
 283:         break;
 284:           case 'S':
 285:             type = "short";
 286:         break;
 287:           case 'Z':
 288:             type = "boolean";
 289:         break;
 290:           case 'L':
 291:         int i = m.indexOf(';', index);
 292:         if (i > 0)
 293:           {
 294:         type = m.substring(index+1, i);
 295:         index = i;
 296:           }
 297:         else
 298:           type = "<unknown ref>";
 299:         break;
 300:           case '[':
 301:         type = "";
 302:         arrayDepth++;
 303:         break;
 304:           default:
 305:         type = "<unknown " + c + '>';
 306:     }
 307:     sb.append(type);
 308: 
 309:     // Handle arrays
 310:     if (c != '[' && arrayDepth > 0)
 311:       while (arrayDepth > 0)
 312:         {
 313:           sb.append("[]");
 314:           arrayDepth--;
 315:         }
 316: 
 317:     index++;
 318:     char nc = (index < length) ? m.charAt(index) : ')';
 319:     if (c != '[' && nc  != ')')
 320:       sb.append(", ");
 321:     c = nc;
 322:       }
 323: 
 324:     // Stop. We are not interested in the return type.
 325:     sb.append(')');
 326:     return sb.toString();
 327:   }
 328: 
 329:   /**
 330:    * Releases all resources used by this NameFinder.
 331:    */
 332:   public void close()
 333:   {
 334:     Iterator itr = procs.values().iterator();
 335:     while (itr.hasNext())
 336:       {
 337:         Addr2Line proc = (Addr2Line) itr.next();
 338:         proc.close();
 339:       }
 340:   }
 341: }