001 /*
002 * This file is part of the Jikes RVM project (http://jikesrvm.org).
003 *
004 * This file is licensed to You under the Eclipse Public License (EPL);
005 * You may not use this file except in compliance with the License. You
006 * may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/eclipse-1.0.php
009 *
010 * See the COPYRIGHT.txt file distributed with this work for information
011 * regarding copyright ownership.
012 */
013 package org.jikesrvm.runtime;
014
015 import static org.jikesrvm.ArchitectureSpecific.StackframeLayoutConstants.INVISIBLE_METHOD_ID;
016 import static org.jikesrvm.ArchitectureSpecific.StackframeLayoutConstants.STACKFRAME_SENTINEL_FP;
017
018 import org.jikesrvm.VM;
019 import org.jikesrvm.Options;
020 import org.jikesrvm.classloader.Atom;
021 import org.jikesrvm.classloader.MemberReference;
022 import org.jikesrvm.classloader.RVMMethod;
023 import org.jikesrvm.classloader.NormalMethod;
024 import org.jikesrvm.compilers.common.CompiledMethod;
025 import org.jikesrvm.compilers.common.CompiledMethods;
026 import org.jikesrvm.compilers.opt.runtimesupport.OptCompiledMethod;
027 import org.jikesrvm.compilers.opt.runtimesupport.OptEncodedCallSiteTree;
028 import org.jikesrvm.compilers.opt.runtimesupport.OptMachineCodeMap;
029 import org.jikesrvm.scheduler.RVMThread;
030 import org.vmmagic.pragma.Uninterruptible;
031 import org.vmmagic.pragma.NoInline;
032 import org.vmmagic.unboxed.Address;
033 import org.vmmagic.unboxed.Offset;
034
035 /**
036 * A list of compiled method and instructionOffset pairs that describe the state
037 * of the call stack at a particular instant.
038 */
039 public class StackTrace {
040 /**
041 * The compiled method ids of the stack trace. Ordered with the top of the stack at
042 * 0 and the bottom of the stack at the end of the array
043 */
044 private final int[] compiledMethods;
045
046 /** The offset of the instruction within the compiled method */
047 private final int[] instructionOffsets;
048
049 /** Index of the last stack trace; only used to support VM.VerboseStackTracePeriod */
050 private static int lastTraceIndex = 0;
051
052 /**
053 * Create a trace for the call stack of RVMThread.getThreadForStackTrace
054 * (normally the current thread unless we're in GC)
055 */
056 @NoInline
057 public StackTrace() {
058 boolean isVerbose = false;
059 int traceIndex = 0;
060 if (VM.VerifyAssertions && VM.VerboseStackTracePeriod > 0) {
061 // Poor man's atomic integer, to get through bootstrap
062 synchronized(StackTrace.class) {
063 traceIndex = lastTraceIndex++;
064 }
065 isVerbose = (traceIndex % VM.VerboseStackTracePeriod == 0);
066 }
067 RVMThread stackTraceThread = RVMThread.getCurrentThread().getThreadForStackTrace();
068 if (stackTraceThread != RVMThread.getCurrentThread()) {
069 // (1) Count the number of frames comprising the stack.
070 int numFrames = countFramesNoGC(stackTraceThread);
071 // (2) Construct arrays to hold raw data
072 compiledMethods = new int[numFrames];
073 instructionOffsets = new int[numFrames];
074 // (3) Fill in arrays
075 recordFramesNoGC(stackTraceThread);
076 } else {
077 // (1) Count the number of frames comprising the stack.
078 int numFrames = countFramesUninterruptible(stackTraceThread);
079 // (2) Construct arrays to hold raw data
080 compiledMethods = new int[numFrames];
081 instructionOffsets = new int[numFrames];
082 // (3) Fill in arrays
083 recordFramesUninterruptible(stackTraceThread);
084 }
085 // Debugging trick: print every nth stack trace created
086 if (isVerbose) {
087 VM.disableGC();
088 VM.sysWriteln("[ BEGIN Verbosely dumping stack at time of creating StackTrace # ", traceIndex);
089 RVMThread.dumpStack();
090 VM.sysWriteln("END Verbosely dumping stack at time of creating StackTrace # ", traceIndex, " ]");
091 VM.enableGC();
092 }
093 }
094
095 /**
096 * Walk the stack counting the number of stack frames encountered.
097 * The stack being walked isn't our stack so GC must be disabled.
098 * @return number of stack frames encountered
099 */
100 private int countFramesNoGC(RVMThread stackTraceThread) {
101 int stackFrameCount = 0;
102 VM.disableGC(); // so fp & ip don't change under our feet
103 Address fp;
104 Address ip;
105 /* Stack trace for a sleeping thread */
106 fp = stackTraceThread.contextRegisters.getInnermostFramePointer();
107 ip = stackTraceThread.contextRegisters.getInnermostInstructionAddress();
108 while (Magic.getCallerFramePointer(fp).NE(STACKFRAME_SENTINEL_FP)) {
109 int compiledMethodId = Magic.getCompiledMethodID(fp);
110 if (compiledMethodId != INVISIBLE_METHOD_ID) {
111 CompiledMethod compiledMethod =
112 CompiledMethods.getCompiledMethod(compiledMethodId);
113 if ((compiledMethod.getCompilerType() != CompiledMethod.TRAP) &&
114 compiledMethod.hasBridgeFromNativeAnnotation()) {
115 // skip native frames, stopping at last native frame preceeding the
116 // Java To C transition frame
117 fp = RuntimeEntrypoints.unwindNativeStackFrame(fp);
118 }
119 }
120 stackFrameCount++;
121 ip = Magic.getReturnAddress(fp);
122 fp = Magic.getCallerFramePointer(fp);
123 }
124 VM.enableGC();
125 return stackFrameCount;
126 }
127
128 /**
129 * Walk the stack recording the stack frames encountered.
130 * The stack being walked isn't our stack so GC must be disabled.
131 */
132 private void recordFramesNoGC(RVMThread stackTraceThread) {
133 int stackFrameCount = 0;
134 VM.disableGC(); // so fp & ip don't change under our feet
135 Address fp;
136 Address ip;
137 /* Stack trace for a sleeping thread */
138 fp = stackTraceThread.contextRegisters.getInnermostFramePointer();
139 ip = stackTraceThread.contextRegisters.getInnermostInstructionAddress();
140 while (Magic.getCallerFramePointer(fp).NE(STACKFRAME_SENTINEL_FP)) {
141 int compiledMethodId = Magic.getCompiledMethodID(fp);
142 compiledMethods[stackFrameCount] = compiledMethodId;
143 if (compiledMethodId != INVISIBLE_METHOD_ID) {
144 CompiledMethod compiledMethod =
145 CompiledMethods.getCompiledMethod(compiledMethodId);
146 if (compiledMethod.getCompilerType() != CompiledMethod.TRAP) {
147 instructionOffsets[stackFrameCount] =
148 compiledMethod.getInstructionOffset(ip).toInt();
149 if (compiledMethod.hasBridgeFromNativeAnnotation()) {
150 // skip native frames, stopping at last native frame preceeding the
151 // Java To C transition frame
152 fp = RuntimeEntrypoints.unwindNativeStackFrame(fp);
153 }
154 }
155 }
156 stackFrameCount++;
157 ip = Magic.getReturnAddress(fp);
158 fp = Magic.getCallerFramePointer(fp);
159 }
160 VM.enableGC();
161 }
162
163 /**
164 * Walk the stack counting the number of stack frames encountered.
165 * The stack being walked is our stack, so code is Uninterrupible to stop the
166 * stack moving.
167 * @return number of stack frames encountered
168 */
169 @Uninterruptible
170 @NoInline
171 private int countFramesUninterruptible(RVMThread stackTraceThread) {
172 int stackFrameCount = 0;
173 Address fp;
174 Address ip;
175 /* Stack trace for the current thread */
176 fp = Magic.getFramePointer();
177 ip = Magic.getReturnAddress(fp);
178 fp = Magic.getCallerFramePointer(fp);
179 while (Magic.getCallerFramePointer(fp).NE(STACKFRAME_SENTINEL_FP)) {
180 int compiledMethodId = Magic.getCompiledMethodID(fp);
181 if (compiledMethodId != INVISIBLE_METHOD_ID) {
182 CompiledMethod compiledMethod =
183 CompiledMethods.getCompiledMethod(compiledMethodId);
184 if ((compiledMethod.getCompilerType() != CompiledMethod.TRAP) &&
185 compiledMethod.hasBridgeFromNativeAnnotation()) {
186 // skip native frames, stopping at last native frame preceeding the
187 // Java To C transition frame
188 fp = RuntimeEntrypoints.unwindNativeStackFrame(fp);
189 }
190 }
191 stackFrameCount++;
192 ip = Magic.getReturnAddress(fp);
193 fp = Magic.getCallerFramePointer(fp);
194 }
195 //VM.sysWriteln("stack frame count = ",stackFrameCount);
196 return stackFrameCount;
197 }
198
199 /**
200 * Walk the stack recording the stack frames encountered
201 * The stack being walked is our stack, so code is Uninterrupible to stop the
202 * stack moving.
203 */
204 @Uninterruptible
205 @NoInline
206 private void recordFramesUninterruptible(RVMThread stackTraceThread) {
207 int stackFrameCount = 0;
208 Address fp;
209 Address ip;
210 /* Stack trace for the current thread */
211 fp = Magic.getFramePointer();
212 ip = Magic.getReturnAddress(fp);
213 fp = Magic.getCallerFramePointer(fp);
214 while (Magic.getCallerFramePointer(fp).NE(STACKFRAME_SENTINEL_FP)) {
215 //VM.sysWriteln("at stackFrameCount = ",stackFrameCount);
216 int compiledMethodId = Magic.getCompiledMethodID(fp);
217 compiledMethods[stackFrameCount] = compiledMethodId;
218 if (compiledMethodId != INVISIBLE_METHOD_ID) {
219 CompiledMethod compiledMethod =
220 CompiledMethods.getCompiledMethod(compiledMethodId);
221 if (compiledMethod.getCompilerType() != CompiledMethod.TRAP) {
222 instructionOffsets[stackFrameCount] =
223 compiledMethod.getInstructionOffset(ip).toInt();
224 if (compiledMethod.hasBridgeFromNativeAnnotation()) {
225 //VM.sysWriteln("native!");
226 // skip native frames, stopping at last native frame preceeding the
227 // Java To C transition frame
228 fp = RuntimeEntrypoints.unwindNativeStackFrame(fp);
229 }
230 } else {
231 //VM.sysWriteln("trap!");
232 }
233 } else {
234 //VM.sysWriteln("invisible method!");
235 }
236 stackFrameCount++;
237 ip = Magic.getReturnAddress(fp);
238 fp = Magic.getCallerFramePointer(fp);
239 }
240 }
241
242 /** Class to wrap up a stack frame element */
243 public static class Element {
244 /** Stack trace's method, null => invisible or trap */
245 private final RVMMethod method;
246 /** Line number of element */
247 private final int lineNumber;
248 /** Is this an invisible method? */
249 private final boolean isInvisible;
250 /** Is this a hardware trap method? */
251 private final boolean isTrap;
252 /** Constructor for non-opt compiled methods */
253 Element(CompiledMethod cm, int off) {
254 isInvisible = (cm == null);
255 if (!isInvisible) {
256 isTrap = cm.getCompilerType() == CompiledMethod.TRAP;
257 if (!isTrap) {
258 method = cm.getMethod();
259 lineNumber = cm.findLineNumberForInstruction(Offset.fromIntSignExtend(off));
260 } else {
261 method = null;
262 lineNumber = 0;
263 }
264 } else {
265 isTrap = false;
266 method = null;
267 lineNumber = 0;
268 }
269 }
270 /** Constructor for opt compiled methods */
271 Element(RVMMethod method, int ln) {
272 this.method = method;
273 lineNumber = ln;
274 isTrap = false;
275 isInvisible = false;
276 }
277 /** Get source file name */
278 public String getFileName() {
279 if (isInvisible || isTrap) {
280 return null;
281 } else {
282 Atom fn = method.getDeclaringClass().getSourceName();
283 return (fn != null) ? fn.toString() : null;
284 }
285 }
286 /** Get class name */
287 public String getClassName() {
288 if (isInvisible || isTrap) {
289 return "";
290 } else {
291 return method.getDeclaringClass().toString();
292 }
293 }
294 /** Get class */
295 public Class<?> getElementClass() {
296 if (isInvisible || isTrap) {
297 return null;
298 }
299 return method.getDeclaringClass().getClassForType();
300 }
301 /** Get method name */
302 public String getMethodName() {
303 if (isInvisible) {
304 return "<invisible method>";
305 } else if (isTrap) {
306 return "<hardware trap>";
307 } else {
308 return method.getName().toString();
309 }
310 }
311 /** Get line number */
312 public int getLineNumber() {
313 return lineNumber;
314 }
315 public boolean isNative() {
316 if (isInvisible || isTrap) {
317 return false;
318 } else {
319 return method.isNative();
320 }
321 }
322 }
323
324 /**
325 * Get the compiled method at element
326 */
327 private CompiledMethod getCompiledMethod(int element) {
328 if ((element >= 0) && (element < compiledMethods.length)) {
329 int mid = compiledMethods[element];
330 if (mid != INVISIBLE_METHOD_ID) {
331 return CompiledMethods.getCompiledMethod(mid);
332 }
333 }
334 return null;
335 }
336
337 /** Return the stack trace for use by the Throwable API */
338 public Element[] getStackTrace(Throwable cause) {
339 int first = firstRealMethod(cause);
340 int last = lastRealMethod(first);
341 Element[] elements = new Element[countFrames(first, last)];
342 if (!VM.BuildForOptCompiler) {
343 int element = 0;
344 for (int i=first; i <= last; i++) {
345 elements[element] = new Element(getCompiledMethod(i), instructionOffsets[i]);
346 element++;
347 }
348 } else {
349 int element = 0;
350 for (int i=first; i <= last; i++) {
351 CompiledMethod compiledMethod = getCompiledMethod(i);
352 if ((compiledMethod == null) ||
353 (compiledMethod.getCompilerType() != CompiledMethod.OPT)) {
354 // Invisible or non-opt compiled method
355 elements[element] = new Element(compiledMethod, instructionOffsets[i]);
356 element++;
357 } else {
358 Offset instructionOffset = Offset.fromIntSignExtend(instructionOffsets[i]);
359 OptCompiledMethod optInfo = (OptCompiledMethod)compiledMethod;
360 OptMachineCodeMap map = optInfo.getMCMap();
361 int iei = map.getInlineEncodingForMCOffset(instructionOffset);
362 if (iei < 0) {
363 elements[element] = new Element(compiledMethod, instructionOffsets[i]);
364 element++;
365 } else {
366 int[] inlineEncoding = map.inlineEncoding;
367 int bci = map.getBytecodeIndexForMCOffset(instructionOffset);
368 for (; iei >= 0; iei = OptEncodedCallSiteTree.getParent(iei, inlineEncoding)) {
369 int mid = OptEncodedCallSiteTree.getMethodID(iei, inlineEncoding);
370 RVMMethod method = MemberReference.getMemberRef(mid).asMethodReference().getResolvedMember();
371 int lineNumber = ((NormalMethod)method).getLineNumberForBCIndex(bci);
372 elements[element] = new Element(method, lineNumber);
373 element++;
374 if (iei > 0) {
375 bci = OptEncodedCallSiteTree.getByteCodeOffset(iei, inlineEncoding);
376 }
377 }
378 }
379 }
380 }
381 }
382 return elements;
383 }
384
385 /**
386 * Count number of stack frames including those inlined by the opt compiler
387 * @param first the first compiled method to look from
388 * @param last the last compiled method to look to
389 */
390 private int countFrames(int first, int last) {
391 int numElements=0;
392 if (!VM.BuildForOptCompiler) {
393 numElements = last - first + 1;
394 } else {
395 for (int i=first; i <= last; i++) {
396 CompiledMethod compiledMethod = getCompiledMethod(i);
397 if ((compiledMethod == null) ||
398 (compiledMethod.getCompilerType() != CompiledMethod.OPT)) {
399 // Invisible or non-opt compiled method
400 numElements++;
401 } else {
402 Offset instructionOffset = Offset.fromIntSignExtend(instructionOffsets[i]);
403 OptCompiledMethod optInfo = (OptCompiledMethod)compiledMethod;
404 OptMachineCodeMap map = optInfo.getMCMap();
405 int iei = map.getInlineEncodingForMCOffset(instructionOffset);
406 if (iei < 0) {
407 numElements++;
408 } else {
409 int[] inlineEncoding = map.inlineEncoding;
410 for (; iei >= 0; iei = OptEncodedCallSiteTree.getParent(iei, inlineEncoding)) {
411 numElements++;
412 }
413 }
414 }
415 }
416 }
417 return numElements;
418 }
419
420 /**
421 * Find the first non-VM method/exception initializer method in the stack
422 * trace. As we're working with the compiled methods we're assumig the
423 * constructor of the exception won't have been inlined into the throwing
424 * method.
425 *
426 * @param cause the cause of generating the stack trace marking the end of the
427 * frames to elide
428 * @return the index of the method throwing the exception or else 0
429 */
430 private int firstRealMethod(Throwable cause) {
431 /* We expect a hardware trap to look like:
432 * at org.jikesrvm.runtime.StackTrace.<init>(StackTrace.java:78)
433 * at java.lang.VMThrowable.fillInStackTrace(VMThrowable.java:67)
434 * at java.lang.Throwable.fillInStackTrace(Throwable.java:498)
435 * at java.lang.Throwable.<init>(Throwable.java:159)
436 * at java.lang.Throwable.<init>(Throwable.java:147)
437 * at java.lang.Exception.<init>(Exception.java:66)
438 * at java.lang.RuntimeException.<init>(RuntimeException.java:64)
439 * at java.lang.NullPointerException.<init>(NullPointerException.java:69)
440 * at org.jikesrvm.runtime.RuntimeEntrypoints.deliverHardwareException(RuntimeEntrypoints.java:682)
441 * at <hardware trap>(Unknown Source:0)
442 *
443 * and a software trap to look like:
444 * at org.jikesrvm.runtime.StackTrace.<init>(StackTrace.java:78)
445 * at java.lang.VMThrowable.fillInStackTrace(VMThrowable.java:67)
446 * at java.lang.Throwable.fillInStackTrace(Throwable.java:498)
447 * at java.lang.Throwable.<init>(Throwable.java:159)
448 * at java.lang.Error.<init>(Error.java:81)
449 * at java.lang.LinkageError.<init>(LinkageError.java:72)
450 * at java.lang.ExceptionInInitializerError.<init>(ExceptionInInitializerError.java:85)
451 * at java.lang.ExceptionInInitializerError.<init>(ExceptionInInitializerError.java:75)
452 *
453 * and an OutOfMemoryError to look like:
454 * ???
455 * ...
456 * at org.jikesrvm.mm.mminterface.MemoryManager.allocateSpace(MemoryManager.java:613)
457 * ...
458 * at org.jikesrvm.runtime.RuntimeEntrypoints.unresolvedNewArray(RuntimeEntrypoints.java:401)
459 */
460 if (Options.stackTraceFull) {
461 return 0;
462 } else {
463 int element = 0;
464 CompiledMethod compiledMethod = getCompiledMethod(element);
465
466 // Deal with OutOfMemoryError
467 if (cause instanceof OutOfMemoryError) {
468 // (1) search until RuntimeEntrypoints
469 while((element < compiledMethods.length) &&
470 (compiledMethod != null) &&
471 compiledMethod.getMethod().getDeclaringClass().getClassForType() != RuntimeEntrypoints.class) {
472 element++;
473 compiledMethod = getCompiledMethod(element);
474 }
475 // (2) continue until not RuntimeEntrypoints
476 while((element < compiledMethods.length) &&
477 (compiledMethod != null) &&
478 compiledMethod.getMethod().getDeclaringClass().getClassForType() == RuntimeEntrypoints.class) {
479 element++;
480 compiledMethod = getCompiledMethod(element);
481 }
482 return element;
483 }
484
485 // (1) remove any StackTrace frames
486 while((element < compiledMethods.length) &&
487 (compiledMethod != null) &&
488 compiledMethod.getMethod().getDeclaringClass().getClassForType() == StackTrace.class) {
489 element++;
490 compiledMethod = getCompiledMethod(element);
491 }
492 // (2) remove any VMThrowable frames
493 if (VM.BuildForGnuClasspath) {
494 while((element < compiledMethods.length) &&
495 (compiledMethod != null) &&
496 compiledMethod.getMethod().getDeclaringClass().getClassForType().getName().equals("java.lang.VMThrowable")) {
497 element++;
498 compiledMethod = getCompiledMethod(element);
499 }
500 }
501 // (3) remove any Throwable frames
502 while((element < compiledMethods.length) &&
503 (compiledMethod != null) &&
504 compiledMethod.getMethod().getDeclaringClass().getClassForType() == java.lang.Throwable.class) {
505 element++;
506 compiledMethod = getCompiledMethod(element);
507 }
508 // (4) remove frames belonging to exception constructors upto the causes constructor
509 while((element < compiledMethods.length) &&
510 (compiledMethod != null) &&
511 (compiledMethod.getMethod().getDeclaringClass().getClassForType() != cause.getClass()) &&
512 compiledMethod.getMethod().isObjectInitializer() &&
513 compiledMethod.getMethod().getDeclaringClass().isThrowable()) {
514 element++;
515 compiledMethod = getCompiledMethod(element);
516 }
517 // (5) remove frames belonging to the causes constructor
518 // NB This can be made to incorrectly elide frames if the cause
519 // exception is thrown from a constructor of the cause exception, however,
520 // Sun's VM has the same problem
521 while((element < compiledMethods.length) &&
522 (compiledMethod != null) &&
523 (compiledMethod.getMethod().getDeclaringClass().getClassForType() == cause.getClass()) &&
524 compiledMethod.getMethod().isObjectInitializer()) {
525 element++;
526 compiledMethod = getCompiledMethod(element);
527 }
528 // (6) remove possible hardware exception deliverer frames
529 if (element < compiledMethods.length - 2) {
530 compiledMethod = getCompiledMethod(element+1);
531 if ((compiledMethod != null) &&
532 compiledMethod.getCompilerType() == CompiledMethod.TRAP) {
533 element+=2;
534 }
535 }
536 return element;
537 }
538 }
539 /**
540 * Find the first non-VM method at the end of the stack trace
541 * @param first the first real method of the stack trace
542 * @return compiledMethods.length-1 if no non-VM methods found else the index of
543 * the method
544 */
545 private int lastRealMethod(int first) {
546 /* We expect an exception on the main thread to look like:
547 * at <invisible method>(Unknown Source:0)
548 * at org.jikesrvm.runtime.Reflection.invoke(Reflection.java:132)
549 * at org.jikesrvm.scheduler.MainThread.run(MainThread.java:195)
550 * at org.jikesrvm.scheduler.RVMThread.run(RVMThread.java:534)
551 * at org.jikesrvm.scheduler.RVMThread.startoff(RVMThread.java:1113
552 *
553 * and on another thread to look like:
554 * at org.jikesrvm.scheduler.RVMThread.run(RVMThread.java:534)
555 * at org.jikesrvm.scheduler.RVMThread.startoff(RVMThread.java:1113)
556 */
557 int max = compiledMethods.length-1;
558 if (Options.stackTraceFull) {
559 return max;
560 } else {
561 // Start at end of array and elide a frame unless we find a place to stop
562 for (int i=max; i >= first; i--) {
563 if (compiledMethods[i] == INVISIBLE_METHOD_ID) {
564 // we found an invisible method, assume next method if this is sane
565 if (i-1 >= 0) {
566 return i-1;
567 } else {
568 return max; // not sane => return max
569 }
570 }
571 CompiledMethod compiledMethod = getCompiledMethod(i);
572 if (compiledMethod.getCompilerType() == CompiledMethod.TRAP) {
573 // looks like we've gone too low
574 return max;
575 }
576 Class<?> frameClass = compiledMethod.getMethod().getDeclaringClass().getClassForType();
577 if ((frameClass != org.jikesrvm.scheduler.MainThread.class) &&
578 (frameClass != org.jikesrvm.scheduler.RVMThread.class) &&
579 (frameClass != org.jikesrvm.runtime.Reflection.class)){
580 // Found a non-VM method
581 return i;
582 }
583 }
584 // No frame found
585 return max;
586 }
587 }
588 }
589