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 t = RVMThread.getCurrentThread(); 068 // (1) Count the number of frames comprising the stack. 069 int numFrames = countFramesUninterruptible(t); 070 // (2) Construct arrays to hold raw data 071 compiledMethods = new int[numFrames]; 072 instructionOffsets = new int[numFrames]; 073 // (3) Fill in arrays 074 recordFramesUninterruptible(t); 075 // Debugging trick: print every nth stack trace created 076 if (isVerbose) { 077 VM.disableGC(); 078 VM.sysWriteln("[ BEGIN Verbosely dumping stack at time of creating StackTrace # ", traceIndex); 079 RVMThread.dumpStack(); 080 VM.sysWriteln("END Verbosely dumping stack at time of creating StackTrace # ", traceIndex, " ]"); 081 VM.enableGC(); 082 } 083 } 084 085 /** 086 * Walk the stack counting the number of stack frames encountered. 087 * The stack being walked is our stack, so code is Uninterruptible to stop the 088 * stack moving. 089 * @return number of stack frames encountered 090 */ 091 @Uninterruptible 092 @NoInline 093 private int countFramesUninterruptible(RVMThread stackTraceThread) { 094 int stackFrameCount = 0; 095 Address fp; 096 /* Stack trace for the current thread */ 097 fp = Magic.getFramePointer(); 098 fp = Magic.getCallerFramePointer(fp); 099 while (Magic.getCallerFramePointer(fp).NE(STACKFRAME_SENTINEL_FP)) { 100 int compiledMethodId = Magic.getCompiledMethodID(fp); 101 if (compiledMethodId != INVISIBLE_METHOD_ID) { 102 CompiledMethod compiledMethod = 103 CompiledMethods.getCompiledMethod(compiledMethodId); 104 if ((compiledMethod.getCompilerType() != CompiledMethod.TRAP) && 105 compiledMethod.hasBridgeFromNativeAnnotation()) { 106 // skip native frames, stopping at last native frame preceeding the 107 // Java To C transition frame 108 fp = RuntimeEntrypoints.unwindNativeStackFrame(fp); 109 } 110 } 111 stackFrameCount++; 112 fp = Magic.getCallerFramePointer(fp); 113 } 114 //VM.sysWriteln("stack frame count = ",stackFrameCount); 115 return stackFrameCount; 116 } 117 118 /** 119 * Walk the stack recording the stack frames encountered 120 * The stack being walked is our stack, so code is Uninterrupible to stop the 121 * stack moving. 122 */ 123 @Uninterruptible 124 @NoInline 125 private void recordFramesUninterruptible(RVMThread stackTraceThread) { 126 int stackFrameCount = 0; 127 Address fp; 128 Address ip; 129 /* Stack trace for the current thread */ 130 fp = Magic.getFramePointer(); 131 ip = Magic.getReturnAddress(fp); 132 fp = Magic.getCallerFramePointer(fp); 133 while (Magic.getCallerFramePointer(fp).NE(STACKFRAME_SENTINEL_FP)) { 134 //VM.sysWriteln("at stackFrameCount = ",stackFrameCount); 135 int compiledMethodId = Magic.getCompiledMethodID(fp); 136 compiledMethods[stackFrameCount] = compiledMethodId; 137 if (compiledMethodId != INVISIBLE_METHOD_ID) { 138 CompiledMethod compiledMethod = 139 CompiledMethods.getCompiledMethod(compiledMethodId); 140 if (compiledMethod.getCompilerType() != CompiledMethod.TRAP) { 141 instructionOffsets[stackFrameCount] = 142 compiledMethod.getInstructionOffset(ip).toInt(); 143 if (compiledMethod.hasBridgeFromNativeAnnotation()) { 144 //VM.sysWriteln("native!"); 145 // skip native frames, stopping at last native frame preceeding the 146 // Java To C transition frame 147 fp = RuntimeEntrypoints.unwindNativeStackFrame(fp); 148 } 149 } else { 150 //VM.sysWriteln("trap!"); 151 } 152 } else { 153 //VM.sysWriteln("invisible method!"); 154 } 155 stackFrameCount++; 156 ip = Magic.getReturnAddress(fp, stackTraceThread); 157 fp = Magic.getCallerFramePointer(fp); 158 } 159 } 160 161 /** Class to wrap up a stack frame element */ 162 public static class Element { 163 /** Stack trace's method, null => invisible or trap */ 164 private final RVMMethod method; 165 /** Line number of element */ 166 private final int lineNumber; 167 /** Is this an invisible method? */ 168 private final boolean isInvisible; 169 /** Is this a hardware trap method? */ 170 private final boolean isTrap; 171 /** Constructor for non-opt compiled methods */ 172 Element(CompiledMethod cm, int off) { 173 isInvisible = (cm == null); 174 if (!isInvisible) { 175 isTrap = cm.getCompilerType() == CompiledMethod.TRAP; 176 if (!isTrap) { 177 method = cm.getMethod(); 178 lineNumber = cm.findLineNumberForInstruction(Offset.fromIntSignExtend(off)); 179 } else { 180 method = null; 181 lineNumber = 0; 182 } 183 } else { 184 isTrap = false; 185 method = null; 186 lineNumber = 0; 187 } 188 } 189 /** Constructor for opt compiled methods */ 190 Element(RVMMethod method, int ln) { 191 this.method = method; 192 lineNumber = ln; 193 isTrap = false; 194 isInvisible = false; 195 } 196 /** Get source file name */ 197 public String getFileName() { 198 if (isInvisible || isTrap) { 199 return null; 200 } else { 201 Atom fn = method.getDeclaringClass().getSourceName(); 202 return (fn != null) ? fn.toString() : null; 203 } 204 } 205 /** Get class name */ 206 public String getClassName() { 207 if (isInvisible || isTrap) { 208 return ""; 209 } else { 210 return method.getDeclaringClass().toString(); 211 } 212 } 213 /** Get class */ 214 public Class<?> getElementClass() { 215 if (isInvisible || isTrap) { 216 return null; 217 } 218 return method.getDeclaringClass().getClassForType(); 219 } 220 /** Get method name */ 221 public String getMethodName() { 222 if (isInvisible) { 223 return "<invisible method>"; 224 } else if (isTrap) { 225 return "<hardware trap>"; 226 } else { 227 return method.getName().toString(); 228 } 229 } 230 /** Get line number */ 231 public int getLineNumber() { 232 return lineNumber; 233 } 234 public boolean isNative() { 235 if (isInvisible || isTrap) { 236 return false; 237 } else { 238 return method.isNative(); 239 } 240 } 241 } 242 243 /** 244 * Get the compiled method at element 245 */ 246 private CompiledMethod getCompiledMethod(int element) { 247 if ((element >= 0) && (element < compiledMethods.length)) { 248 int mid = compiledMethods[element]; 249 if (mid != INVISIBLE_METHOD_ID) { 250 return CompiledMethods.getCompiledMethod(mid); 251 } 252 } 253 return null; 254 } 255 256 /** Return the stack trace for use by the Throwable API */ 257 public Element[] getStackTrace(Throwable cause) { 258 int first = firstRealMethod(cause); 259 int last = lastRealMethod(first); 260 Element[] elements = new Element[countFrames(first, last)]; 261 if (!VM.BuildForOptCompiler) { 262 int element = 0; 263 for (int i=first; i <= last; i++) { 264 elements[element] = new Element(getCompiledMethod(i), instructionOffsets[i]); 265 element++; 266 } 267 } else { 268 int element = 0; 269 for (int i=first; i <= last; i++) { 270 CompiledMethod compiledMethod = getCompiledMethod(i); 271 if ((compiledMethod == null) || 272 (compiledMethod.getCompilerType() != CompiledMethod.OPT)) { 273 // Invisible or non-opt compiled method 274 elements[element] = new Element(compiledMethod, instructionOffsets[i]); 275 element++; 276 } else { 277 Offset instructionOffset = Offset.fromIntSignExtend(instructionOffsets[i]); 278 OptCompiledMethod optInfo = (OptCompiledMethod)compiledMethod; 279 OptMachineCodeMap map = optInfo.getMCMap(); 280 int iei = map.getInlineEncodingForMCOffset(instructionOffset); 281 if (iei < 0) { 282 elements[element] = new Element(compiledMethod, instructionOffsets[i]); 283 element++; 284 } else { 285 int[] inlineEncoding = map.inlineEncoding; 286 int bci = map.getBytecodeIndexForMCOffset(instructionOffset); 287 for (; iei >= 0; iei = OptEncodedCallSiteTree.getParent(iei, inlineEncoding)) { 288 int mid = OptEncodedCallSiteTree.getMethodID(iei, inlineEncoding); 289 RVMMethod method = MemberReference.getMemberRef(mid).asMethodReference().getResolvedMember(); 290 int lineNumber = ((NormalMethod)method).getLineNumberForBCIndex(bci); 291 elements[element] = new Element(method, lineNumber); 292 element++; 293 if (iei > 0) { 294 bci = OptEncodedCallSiteTree.getByteCodeOffset(iei, inlineEncoding); 295 } 296 } 297 } 298 } 299 } 300 } 301 return elements; 302 } 303 304 /** 305 * Count number of stack frames including those inlined by the opt compiler 306 * @param first the first compiled method to look from 307 * @param last the last compiled method to look to 308 */ 309 private int countFrames(int first, int last) { 310 int numElements=0; 311 if (!VM.BuildForOptCompiler) { 312 numElements = last - first + 1; 313 } else { 314 for (int i=first; i <= last; i++) { 315 CompiledMethod compiledMethod = getCompiledMethod(i); 316 if ((compiledMethod == null) || 317 (compiledMethod.getCompilerType() != CompiledMethod.OPT)) { 318 // Invisible or non-opt compiled method 319 numElements++; 320 } else { 321 Offset instructionOffset = Offset.fromIntSignExtend(instructionOffsets[i]); 322 OptCompiledMethod optInfo = (OptCompiledMethod)compiledMethod; 323 OptMachineCodeMap map = optInfo.getMCMap(); 324 int iei = map.getInlineEncodingForMCOffset(instructionOffset); 325 if (iei < 0) { 326 numElements++; 327 } else { 328 int[] inlineEncoding = map.inlineEncoding; 329 for (; iei >= 0; iei = OptEncodedCallSiteTree.getParent(iei, inlineEncoding)) { 330 numElements++; 331 } 332 } 333 } 334 } 335 } 336 return numElements; 337 } 338 339 /** 340 * Find the first non-VM method/exception initializer method in the stack 341 * trace. As we're working with the compiled methods we're assuming the 342 * constructor of the exception won't have been inlined into the throwing 343 * method. 344 * 345 * @param cause the cause of generating the stack trace marking the end of the 346 * frames to elide 347 * @return the index of the method throwing the exception or else 0 348 */ 349 private int firstRealMethod(Throwable cause) { 350 /* We expect a hardware trap to look like: 351 * at org.jikesrvm.runtime.StackTrace.<init>(StackTrace.java:78) 352 * at java.lang.VMThrowable.fillInStackTrace(VMThrowable.java:67) 353 * at java.lang.Throwable.fillInStackTrace(Throwable.java:498) 354 * at java.lang.Throwable.<init>(Throwable.java:159) 355 * at java.lang.Throwable.<init>(Throwable.java:147) 356 * at java.lang.Exception.<init>(Exception.java:66) 357 * at java.lang.RuntimeException.<init>(RuntimeException.java:64) 358 * at java.lang.NullPointerException.<init>(NullPointerException.java:69) 359 * at org.jikesrvm.runtime.RuntimeEntrypoints.deliverHardwareException(RuntimeEntrypoints.java:682) 360 * at <hardware trap>(Unknown Source:0) 361 * 362 * and a software trap to look like: 363 * at org.jikesrvm.runtime.StackTrace.<init>(StackTrace.java:78) 364 * at java.lang.VMThrowable.fillInStackTrace(VMThrowable.java:67) 365 * at java.lang.Throwable.fillInStackTrace(Throwable.java:498) 366 * at java.lang.Throwable.<init>(Throwable.java:159) 367 * at java.lang.Error.<init>(Error.java:81) 368 * at java.lang.LinkageError.<init>(LinkageError.java:72) 369 * at java.lang.ExceptionInInitializerError.<init>(ExceptionInInitializerError.java:85) 370 * at java.lang.ExceptionInInitializerError.<init>(ExceptionInInitializerError.java:75) 371 * 372 * and an OutOfMemoryError to look like: 373 * ??? 374 * ... 375 * at org.jikesrvm.mm.mminterface.MemoryManager.allocateSpace(MemoryManager.java:613) 376 * ... 377 * at org.jikesrvm.runtime.RuntimeEntrypoints.unresolvedNewArray(RuntimeEntrypoints.java:401) 378 */ 379 if (Options.stackTraceFull) { 380 return 0; 381 } else { 382 int element = 0; 383 CompiledMethod compiledMethod = getCompiledMethod(element); 384 385 // Deal with OutOfMemoryError 386 if (cause instanceof OutOfMemoryError) { 387 // (1) search until RuntimeEntrypoints 388 while((element < compiledMethods.length) && 389 (compiledMethod != null) && 390 compiledMethod.getMethod().getDeclaringClass().getClassForType() != RuntimeEntrypoints.class) { 391 element++; 392 compiledMethod = getCompiledMethod(element); 393 } 394 // (2) continue until not RuntimeEntrypoints 395 while((element < compiledMethods.length) && 396 (compiledMethod != null) && 397 compiledMethod.getMethod().getDeclaringClass().getClassForType() == RuntimeEntrypoints.class) { 398 element++; 399 compiledMethod = getCompiledMethod(element); 400 } 401 return element; 402 } 403 404 // (1) remove any StackTrace frames 405 while((element < compiledMethods.length) && 406 (compiledMethod != null) && 407 compiledMethod.getMethod().getDeclaringClass().getClassForType() == StackTrace.class) { 408 element++; 409 compiledMethod = getCompiledMethod(element); 410 } 411 // (2) remove any VMThrowable frames 412 if (VM.BuildForGnuClasspath) { 413 while((element < compiledMethods.length) && 414 (compiledMethod != null) && 415 compiledMethod.getMethod().getDeclaringClass().getClassForType().getName().equals("java.lang.VMThrowable")) { 416 element++; 417 compiledMethod = getCompiledMethod(element); 418 } 419 } 420 // (3) remove any Throwable frames 421 while((element < compiledMethods.length) && 422 (compiledMethod != null) && 423 compiledMethod.getMethod().getDeclaringClass().getClassForType() == java.lang.Throwable.class) { 424 element++; 425 compiledMethod = getCompiledMethod(element); 426 } 427 // (4) remove frames belonging to exception constructors upto the causes constructor 428 while((element < compiledMethods.length) && 429 (compiledMethod != null) && 430 (compiledMethod.getMethod().getDeclaringClass().getClassForType() != cause.getClass()) && 431 compiledMethod.getMethod().isObjectInitializer() && 432 compiledMethod.getMethod().getDeclaringClass().isThrowable()) { 433 element++; 434 compiledMethod = getCompiledMethod(element); 435 } 436 // (5) remove frames belonging to the causes constructor 437 // NB This can be made to incorrectly elide frames if the cause 438 // exception is thrown from a constructor of the cause exception, however, 439 // Sun's VM has the same problem 440 while((element < compiledMethods.length) && 441 (compiledMethod != null) && 442 (compiledMethod.getMethod().getDeclaringClass().getClassForType() == cause.getClass()) && 443 compiledMethod.getMethod().isObjectInitializer()) { 444 element++; 445 compiledMethod = getCompiledMethod(element); 446 } 447 // (6) remove possible hardware exception deliverer frames 448 if (element < compiledMethods.length - 2) { 449 compiledMethod = getCompiledMethod(element+1); 450 if ((compiledMethod != null) && 451 compiledMethod.getCompilerType() == CompiledMethod.TRAP) { 452 element+=2; 453 } 454 } 455 return element; 456 } 457 } 458 /** 459 * Find the first non-VM method at the end of the stack trace 460 * @param first the first real method of the stack trace 461 * @return compiledMethods.length-1 if no non-VM methods found else the index of 462 * the method 463 */ 464 private int lastRealMethod(int first) { 465 /* We expect an exception on the main thread to look like: 466 * at <invisible method>(Unknown Source:0) 467 * at org.jikesrvm.runtime.Reflection.invoke(Reflection.java:132) 468 * at org.jikesrvm.scheduler.MainThread.run(MainThread.java:195) 469 * at org.jikesrvm.scheduler.RVMThread.run(RVMThread.java:534) 470 * at org.jikesrvm.scheduler.RVMThread.startoff(RVMThread.java:1113 471 * 472 * and on another thread to look like: 473 * at org.jikesrvm.scheduler.RVMThread.run(RVMThread.java:534) 474 * at org.jikesrvm.scheduler.RVMThread.startoff(RVMThread.java:1113) 475 */ 476 int max = compiledMethods.length-1; 477 if (Options.stackTraceFull) { 478 return max; 479 } else { 480 // Start at end of array and elide a frame unless we find a place to stop 481 for (int i=max; i >= first; i--) { 482 if (compiledMethods[i] == INVISIBLE_METHOD_ID) { 483 // we found an invisible method, assume next method if this is sane 484 if (i-1 >= 0) { 485 return i-1; 486 } else { 487 return max; // not sane => return max 488 } 489 } 490 CompiledMethod compiledMethod = getCompiledMethod(i); 491 if (compiledMethod.getCompilerType() == CompiledMethod.TRAP) { 492 // looks like we've gone too low 493 return max; 494 } 495 Class<?> frameClass = compiledMethod.getMethod().getDeclaringClass().getClassForType(); 496 if ((frameClass != org.jikesrvm.scheduler.MainThread.class) && 497 (frameClass != org.jikesrvm.scheduler.RVMThread.class) && 498 (frameClass != org.jikesrvm.runtime.Reflection.class)){ 499 // Found a non-VM method 500 return i; 501 } 502 } 503 // No frame found 504 return max; 505 } 506 } 507 } 508