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    */
showRaw()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    */
removeUnknown()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 
Addr2Line(String binaryFile)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 
close()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    */
NameFinder()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    */
getSourceFile()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    */
getLineNum()165   public int getLineNum()
166   {
167     return lineNum;
168   }
169 
lookup(String file, long addr)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    */
demangleInterpreterMethod(String m, String cn)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    */
close()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 }
342