source: trunk/gcc/libjava/gnu/gcj/runtime/NameFinder.java

Last change on this file was 1389, checked in by bird, 21 years ago

Initial revision

  • Property cvs2svn:cvs-rev set to 1.1
  • Property svn:eol-style set to native
  • Property svn:executable set to *
File size: 14.8 KB
Line 
1/* NameFinder.java -- Translates addresses to StackTraceElements.
2 Copyright (C) 2002 Free Software Foundation, Inc.
3
4 This file is part of libgcj.
5
6This software is copyrighted work licensed under the terms of the
7Libgcj License. Please consult the file "LIBGCJ_LICENSE" for
8details. */
9
10package gnu.gcj.runtime;
11
12import gnu.gcj.RawData;
13
14import java.lang.StringBuffer;
15
16import java.io.BufferedReader;
17import java.io.BufferedWriter;
18import java.io.InputStreamReader;
19import java.io.OutputStreamWriter;
20import java.io.IOException;
21import java.io.File;
22
23/**
24 * Helper class that translates addresses (represented as longs) to a
25 * StackTraceElement array.
26 *
27 * There are a couple of system properties that can be set to manipulate the
28 * result (all default to true):
29 * <li>
30 * <ul><code>gnu.gcj.runtime.NameFinder.demangle</code>
31 * Whether names should be demangled.</ul>
32 * <ul><code>gnu.gcj.runtime.NameFinder.sanitize</code></ul>
33 * Whether calls to initialize exceptions and starting the runtime system
34 * should be removed from the stack trace. Only done when names are
35 * demangled.</ul>
36 * <ul><code>gnu.gcj.runtime.NameFinder.remove_unknown</code>
37 * Whether calls to unknown functions (class and method names are unknown)
38 * should be removed from the stack trace. Only done when the stack is
39 * sanitized.</ul>
40 * <ul><code>gnu.gcj.runtime.NameFinder.remove_interpreter</code>
41 * Whether runtime interpreter calls (methods in the _Jv_InterpMethod class
42 * and functions starting with 'ffi_') should be removed from the stack
43 * trace. Only done when the stack is sanitized.</ul>
44 * <ul><code>gnu.gcj.runtime.NameFinder.use_addr2line</code>
45 * Whether an external process (addr2line or addr2name.awk) should be used
46 * as fallback to convert the addresses to function names when the runtime
47 * is unable to do it through <code>dladdr</code>.</ul>
48 * </li>
49 *
50 * <code>close()</code> should be called to get rid of all resources.
51 *
52 * This class is used from <code>java.lang.VMThrowable</code>.
53 *
54 * Currently the <code>lookup(long[])</code> method is not thread safe.
55 * It can easily be made thread safe by synchronizing access to all external
56 * processes when used.
57 *
58 * @author Mark Wielaard (mark@klomp.org)
59 */
60public class NameFinder
61{
62 // Set these to false when not needed.
63 private static final boolean demangle
64 = Boolean.valueOf(System.getProperty
65 ("gnu.gcj.runtime.NameFinder.demangle", "true")
66 ).booleanValue();
67 private static final boolean sanitize
68 = Boolean.valueOf(System.getProperty
69 ("gnu.gcj.runtime.NameFinder.sanitize", "true")
70 ).booleanValue();
71 private static final boolean remove_unknown
72 = Boolean.valueOf(System.getProperty
73 ("gnu.gcj.runtime.NameFinder.remove_unknown", "true")
74 ).booleanValue();
75 private static final boolean remove_interpreter
76 = Boolean.valueOf(System.getProperty
77 ("gnu.gcj.runtime.NameFinder.remove_interpreter", "true")
78 ).booleanValue();
79 private static final boolean use_addr2line
80 = Boolean.valueOf(System.getProperty
81 ("gnu.gcj.runtime.NameFinder.use_addr2line", "true")
82 ).booleanValue();
83
84 /**
85 * The name of the currently running executable.
86 */
87 private final String executable;
88
89 /**
90 * Process used for demangling names.
91 */
92 private Process cppfilt;
93
94 private BufferedWriter cppfiltOut;
95 private BufferedReader cppfiltIn;
96
97 /**
98 * Process used for translating addresses to function/file names.
99 */
100 private Process addr2line;
101
102 private BufferedWriter addr2lineOut;
103 private BufferedReader addr2lineIn;
104
105 /**
106 * Flag set if using addr2name.awk instead of addr2line from binutils.
107 */
108 private boolean usingAddr2name = false;
109
110 /**
111 * Creates a new NameFinder. Call close to get rid of any resources
112 * created while using the <code>lookup</code> methods.
113 */
114 public NameFinder()
115 {
116 executable = getExecutable();
117 Runtime runtime = Runtime.getRuntime();
118 if (demangle)
119 {
120 try
121 {
122 String[] exec = new String[] {"c++filt", "-s", "java"};
123 cppfilt = runtime.exec(exec);
124 cppfiltIn = new BufferedReader
125 (new InputStreamReader(cppfilt.getInputStream()));
126 cppfiltOut = new BufferedWriter
127 (new OutputStreamWriter(cppfilt.getOutputStream()));
128 }
129 catch (IOException ioe)
130 {
131 if (cppfilt != null)
132 cppfilt.destroy();
133 cppfilt = null;
134 }
135 }
136
137 if (use_addr2line)
138 {
139 try
140 {
141 String[] exec = new String[] {"addr2line", "-f", "-e", executable};
142 addr2line = runtime.exec(exec);
143 }
144 catch (IOException ioe)
145 {
146 try
147 {
148 String[] exec = new String[] {"addr2name.awk", executable};
149 addr2line = runtime.exec(exec);
150 usingAddr2name = true;
151 }
152 catch (IOException ioe2) { addr2line = null; }
153 }
154
155 if (addr2line != null)
156 {
157 try
158 {
159 addr2lineIn = new BufferedReader
160 (new InputStreamReader(addr2line.getInputStream()));
161 addr2lineOut = new BufferedWriter
162 (new OutputStreamWriter(addr2line.getOutputStream()));
163 }
164 catch (IOException ioe)
165 {
166 addr2line.destroy();
167 addr2line = null;
168 }
169 }
170 }
171 }
172
173 /**
174 * Returns the name of the currently running process.
175 */
176 native private static String getExecutable();
177
178 /**
179 * Tries to use dladdr to create the nth StackTraceElement from the given
180 * addresses. Returns null on failure.
181 */
182 native private StackTraceElement dladdrLookup(RawData addrs, int n);
183
184 /**
185 * Returns the nth element from the stack as a hex encoded String.
186 */
187 native private String getAddrAsString(RawData addrs, int n);
188
189 /**
190 * Returns the label that is exported for the given method name.
191 */
192 native private String getExternalLabel(String name);
193
194 /**
195 * If nth element of stack is an interpreted frame, return the
196 * element representing the method being interpreted.
197 */
198 native private StackTraceElement lookupInterp(RawData addrs, int n);
199
200 /**
201 * Creates the nth StackTraceElement from the given native stacktrace.
202 */
203 private StackTraceElement lookup(RawData addrs, int n)
204 {
205 StackTraceElement result;
206
207 result = lookupInterp(addrs, n);
208 if (result == null)
209 result = dladdrLookup(addrs, n);
210 if (result == null)
211 {
212 String name = null;
213 String file = null;
214
215 String hex = getAddrAsString(addrs, n);
216
217 if (addr2line != null)
218 {
219 try
220 {
221 addr2lineOut.write(hex);
222 addr2lineOut.newLine();
223 addr2lineOut.flush();
224 name = addr2lineIn.readLine();
225 file = addr2lineIn.readLine();
226
227 // addr2line uses symbolic debugging information instead
228 // of the actually exported labels as addr2name.awk does.
229 // This name might need some modification, depending on
230 // the system, to make it a label like that returned
231 // by addr2name.awk or dladdr.
232 if (! usingAddr2name)
233 if (name != null && ! "??".equals (name))
234 name = getExternalLabel (name);
235 }
236 catch (IOException ioe) { addr2line = null; }
237 }
238
239 if (name == null || "??".equals(name))
240 name = hex;
241
242 result = createStackTraceElement(name, file);
243 }
244
245 return result;
246 }
247
248 /**
249 * Given an Throwable and a native stacktrace returns an array of
250 * StackTraceElement containing class, method, file and linenumbers.
251 */
252 public StackTraceElement[] lookup(Throwable t, RawData addrs, int length)
253 {
254 StackTraceElement[] elements = new StackTraceElement[length];
255 for (int i=0; i < length; i++)
256 elements[i] = lookup(addrs, i);
257
258 if (demangle && sanitize)
259 return sanitizeStack(elements, t);
260 else
261 return elements;
262 }
263
264
265 /**
266 * Removes calls to initialize exceptions and the runtime system from
267 * the stack trace including stack frames of which nothing usefull is known.
268 * Throw away the top of the stack till we find the constructor(s)
269 * of this Throwable or at least the contructors of java.lang.Throwable
270 * or the actual fillInStackTrace call.
271 * Also throw away from the top everything before and including a runtime
272 * _Jv_Throw call.
273 */
274 private static StackTraceElement[] sanitizeStack(StackTraceElement[] elements,
275 Throwable t)
276 {
277 StackTraceElement[] stack;
278
279 String className = t.getClass().getName();
280 String consName;
281 int lastDot = className.lastIndexOf('.');
282 if (lastDot == -1)
283 consName = className + '(';
284 else
285 consName = className.substring(lastDot + 1) + '(';
286
287 int unknown = 0;
288 int interpreter = 0;
289 int last_throw = -1;
290 int length = elements.length;
291 int end = length-1;
292 for (int i = 0; i < length; i++)
293 {
294 String CName = elements[i].getClassName();
295 String MName = elements[i].getMethodName();
296 if ((CName == null && MName != null && MName.startsWith("_Jv_Throw"))
297 ||
298 (CName != null
299 && (CName.equals(className)
300 || CName.equals("java.lang.Throwable")
301 || CName.equals("java.lang.VMThrowable"))
302 && MName != null
303 && (MName.startsWith(consName)
304 || MName.startsWith("Throwable(")
305 || MName.startsWith("fillInStackTrace("))))
306 {
307 last_throw = i;
308 // Reset counting of unknown and interpreter frames.
309 unknown = 0;
310 interpreter = 0;
311 }
312 else if (remove_unknown && CName == null
313 && (MName == null || MName.startsWith("0x")))
314 unknown++;
315 else if (remove_interpreter
316 && ((CName == null
317 && MName != null && MName.startsWith("ffi_"))
318 || (CName != null && CName.equals("_Jv_InterpMethod"))))
319 interpreter++;
320 else if ("main(java.lang.String[])".equals(MName))
321 {
322 end = i;
323 break;
324 }
325 }
326 int begin = last_throw+1;
327
328 // Now filter out everything at the start and the end that is not part
329 // of the "normal" user program including any elements that are interpreter
330 // calls or have no usefull information whatsoever.
331 // Unless that means we filter out all info.
332 int nr_elements = end-begin-unknown-interpreter+1;
333 if ((begin > 0 || end < length-1 || unknown > 0 || interpreter > 0)
334 && nr_elements > 0)
335 {
336 stack = new StackTraceElement[nr_elements];
337 int pos =0;
338 for (int i=begin; i<=end; i++)
339 {
340 String MName = elements[i].getMethodName();
341 String CName = elements[i].getClassName();
342 if (remove_unknown && CName == null
343 && (MName == null || MName.startsWith("0x")))
344 ; // Skip unknown frame
345 else if (remove_interpreter
346 && ((CName == null
347 && MName != null && MName.startsWith("ffi_"))
348 || (CName != null && CName.equals("_Jv_InterpMethod"))))
349 ; // Skip interpreter runtime frame
350 else
351 {
352 stack[pos] = elements[i];
353 pos++;
354 }
355 }
356 }
357 else
358 stack = elements;
359
360 return stack;
361 }
362
363 /**
364 * Creates a StackTraceElement given a string and a filename.
365 * Splits the given string into the class and method part.
366 * The string name will be a demangled to a fully qualified java method
367 * string. The string file will be decomposed into a file name and possibly
368 * a line number. The name should never be null, but the file may be if it
369 * is unknown.
370 */
371 private StackTraceElement createStackTraceElement(String name, String file)
372 {
373 if (!demangle)
374 return new StackTraceElement(file, -1, null, name, false);
375
376 String s = demangleName(name);
377 String methodName = s;
378 String className = null;
379 int bracket = s.indexOf('(');
380 if (bracket > 0)
381 {
382 int dot = s.lastIndexOf('.', bracket);
383 if (dot > 0)
384 {
385 className = s.substring(0, dot);
386 methodName = s.substring(dot+1, s.length());
387 }
388 }
389
390 String fileName = file;
391 int line = -1;
392 if (fileName != null)
393 {
394 int colon = file.lastIndexOf(':');
395 if (colon > 0)
396 {
397 fileName = file.substring(0, colon);
398 try
399 {
400 line = Integer.parseInt(file.substring(colon+1, file.length()));
401 }
402 catch (NumberFormatException nfe) { /* ignore */ }
403 }
404
405 if (line == 0)
406 line =-1;
407
408 if ("".equals(fileName) || "??".equals(fileName))
409 fileName = null;
410 else if (fileName != null)
411 {
412 try
413 {
414 fileName = new File(fileName).getCanonicalPath();
415 }
416 catch (IOException ioe) { /* ignore */ }
417 }
418 }
419
420 return new StackTraceElement(fileName, line, className, methodName, false);
421 }
422
423 /**
424 * Demangles the given String if possible. Returns the demangled String or
425 * the original string if demangling is impossible.
426 */
427 private String demangleName(String s)
428 {
429 if (cppfilt != null)
430 {
431 try
432 {
433 cppfiltOut.write(s);
434 cppfiltOut.newLine();
435 cppfiltOut.flush();
436 return cppfiltIn.readLine();
437 }
438 catch (IOException ioe) { cppfilt.destroy(); cppfilt = null; }
439 }
440
441 return s;
442 }
443
444 /**
445 * Returns human readable method name and aguments given a method type
446 * signature as known to the interpreter and a classname.
447 */
448 public static String demangleInterpreterMethod(String m, String cn)
449 {
450 int index = 0;
451 int length = m.length();
452 StringBuffer sb = new StringBuffer(length);
453
454 // Figure out the real method name
455 if (m.startsWith("<init>"))
456 {
457 String className;
458 int i = cn.lastIndexOf('.');
459 if (i < 0)
460 className = cn;
461 else
462 className = cn.substring(i + 1);
463 sb.append(className);
464 index += 7;
465 }
466 else
467 {
468 int i = m.indexOf('(');
469 if (i > 0)
470 {
471 sb.append(m.substring(0,i));
472 index += i + 1;
473 }
474 }
475
476 sb.append('(');
477
478 // Demangle the type arguments
479 int arrayDepth = 0;
480 char c = (index < length) ? m.charAt(index) : ')';
481 while (c != ')')
482 {
483 String type;
484 switch(c)
485 {
486 case 'B':
487 type = "byte";
488 break;
489 case 'C':
490 type = "char";
491 break;
492 case 'D':
493 type = "double";
494 break;
495 case 'F':
496 type = "float";
497 break;
498 case 'I':
499 type = "int";
500 break;
501 case 'J':
502 type = "long";
503 break;
504 case 'S':
505 type = "short";
506 break;
507 case 'Z':
508 type = "boolean";
509 break;
510 case 'L':
511 int i = m.indexOf(';', index);
512 if (i > 0)
513 {
514 type = m.substring(index+1, i);
515 index = i;
516 }
517 else
518 type = "<unknown ref>";
519 break;
520 case '[':
521 type = "";
522 arrayDepth++;
523 break;
524 default:
525 type = "<unknown " + c + '>';
526 }
527 sb.append(type);
528
529 // Handle arrays
530 if (c != '[' && arrayDepth > 0)
531 while (arrayDepth > 0)
532 {
533 sb.append("[]");
534 arrayDepth--;
535 }
536
537 index++;
538 char nc = (index < length) ? m.charAt(index) : ')';
539 if (c != '[' && nc != ')')
540 sb.append(", ");
541 c = nc;
542 }
543
544 // Stop. We are not interested in the return type.
545 sb.append(')');
546 return sb.toString();
547 }
548
549 /**
550 * Releases all resources used by this NameFinder.
551 */
552 public void close()
553 {
554 if (cppfilt != null)
555 cppfilt.destroy();
556
557 if (addr2line != null)
558 addr2line.destroy();
559 }
560
561 /**
562 * Calls close to get rid of all resources.
563 */
564 protected void finalize()
565 {
566 close();
567 }
568}
Note: See TracBrowser for help on using the repository browser.