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 */
013package org.jikesrvm.runtime;
014
015import org.jikesrvm.VM;
016import org.jikesrvm.architecture.AbstractRegisters;
017import org.jikesrvm.architecture.StackFrameLayout;
018import org.jikesrvm.Options;
019import org.jikesrvm.classloader.Atom;
020import org.jikesrvm.classloader.MemberReference;
021import org.jikesrvm.classloader.RVMMethod;
022import org.jikesrvm.classloader.NormalMethod;
023import org.jikesrvm.compilers.baseline.BaselineCompiledMethod;
024import org.jikesrvm.compilers.common.CompiledMethod;
025import org.jikesrvm.compilers.common.CompiledMethods;
026import org.jikesrvm.compilers.opt.runtimesupport.OptCompiledMethod;
027import org.jikesrvm.compilers.opt.runtimesupport.OptEncodedCallSiteTree;
028import org.jikesrvm.compilers.opt.runtimesupport.OptMachineCodeMap;
029import org.jikesrvm.scheduler.RVMThread;
030import org.vmmagic.pragma.Uninterruptible;
031import org.vmmagic.pragma.NoInline;
032import org.vmmagic.unboxed.Address;
033import 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 */
039public class StackTrace {
040
041  /**
042   * Prints an internal stack trace for every stack trace obtained via
043   * {@link #getStackTrace(Throwable)}. The internal stack trace
044   * has machine code offsets and bytecode index information for methods.
045   */
046  private static final boolean PRINT_INTERNAL_STACK_TRACE = false;
047
048  /**
049   * The compiled method ids of the stack trace. Ordered with the top of the stack at
050   * 0 and the bottom of the stack at the end of the array
051   */
052  private final int[] compiledMethods;
053
054  /** The offset of the instruction within the compiled method */
055  private final int[] instructionOffsets;
056
057  /** Index of the last stack trace; only used to support VM.VerboseStackTracePeriod */
058  private static int lastTraceIndex = 0;
059
060  /**
061   * Create a trace for the call stack of the current thread
062   */
063  @NoInline
064  public StackTrace() {
065    this(RVMThread.getCurrentThread());
066  }
067
068  /**
069   * Constructs a stack trace.
070   * <p>
071   * Note: no inlining directives here because they aren't necessary for correctness.
072   * This class removes all frames belonging to its methods.
073   *
074   * @param rvmThread the thread whose stack is examined. It is the caller's
075   *  responsibility to block that thread if required.
076   */
077  public StackTrace(RVMThread rvmThread) {
078    assertThreadBlockedOrCurrent(rvmThread);
079    boolean isVerbose = false;
080    int traceIndex = 0;
081    if (VM.VerifyAssertions && VM.VerboseStackTracePeriod > 0) {
082      // Poor man's atomic integer, to get through bootstrap
083      synchronized (StackTrace.class) {
084         traceIndex = lastTraceIndex++;
085      }
086      isVerbose = (traceIndex % VM.VerboseStackTracePeriod == 0);
087    }
088    // (1) Count the number of frames comprising the stack.
089    int numFrames = countFramesUninterruptible(rvmThread);
090    // (2) Construct arrays to hold raw data
091    compiledMethods = new int[numFrames];
092    instructionOffsets = new int[numFrames];
093    // (3) Fill in arrays
094    recordFramesUninterruptible(rvmThread);
095    // Debugging trick: print every nth stack trace created
096    if (isVerbose) {
097      VM.disableGC();
098      VM.sysWriteln("[ BEGIN Verbosely dumping stack at time of creating StackTrace # ", traceIndex);
099      RVMThread.dumpStack();
100      VM.sysWriteln("END Verbosely dumping stack at time of creating StackTrace # ", traceIndex, " ]");
101      VM.enableGC();
102    }
103  }
104
105  private void assertThreadBlockedOrCurrent(RVMThread rvmThread) {
106    if (VM.VerifyAssertions) {
107      if (rvmThread != RVMThread.getCurrentThread()) {
108        rvmThread.monitor().lockNoHandshake();
109        boolean blocked = rvmThread.isBlocked();
110        rvmThread.monitor().unlock();
111        if (!blocked) {
112          String msg = "Can only dump stack of blocked threads if not dumping" +
113              " own stack but thread " +  rvmThread + " was in state " +
114              rvmThread.getExecStatus() + " and wasn't blocked!";
115          VM._assert(VM.NOT_REACHED, msg);
116        }
117      }
118    }
119  }
120
121  /**
122   * Walk the stack counting the number of stack frames encountered.
123   * The stack being walked is our stack, so code is Uninterruptible to stop the
124   * stack moving.
125   *
126   * @param stackTraceThread the thread whose stack is walked
127   * @return number of stack frames encountered
128   */
129  @Uninterruptible
130  @NoInline
131  private int countFramesUninterruptible(RVMThread stackTraceThread) {
132    int stackFrameCount = 0;
133    Address fp;
134    /* Stack trace for the thread */
135    if (stackTraceThread == RVMThread.getCurrentThread()) {
136      fp = Magic.getFramePointer();
137    } else {
138      AbstractRegisters contextRegisters = stackTraceThread.getContextRegisters();
139      fp =  contextRegisters.getInnermostFramePointer();
140    }
141    fp = Magic.getCallerFramePointer(fp);
142    while (Magic.getCallerFramePointer(fp).NE(StackFrameLayout.getStackFrameSentinelFP())) {
143      int compiledMethodId = Magic.getCompiledMethodID(fp);
144      if (compiledMethodId != StackFrameLayout.getInvisibleMethodID()) {
145        CompiledMethod compiledMethod =
146          CompiledMethods.getCompiledMethod(compiledMethodId);
147        if ((compiledMethod.getCompilerType() != CompiledMethod.TRAP) &&
148            compiledMethod.hasBridgeFromNativeAnnotation()) {
149          // skip native frames, stopping at last native frame preceeding the
150          // Java To C transition frame
151          fp = RuntimeEntrypoints.unwindNativeStackFrame(fp);
152        }
153      }
154      stackFrameCount++;
155      fp = Magic.getCallerFramePointer(fp);
156    }
157    //VM.sysWriteln("stack frame count = ",stackFrameCount);
158    return stackFrameCount;
159  }
160
161  /**
162   * Walk the stack recording the stack frames encountered
163   * The stack being walked is our stack, so code is Uninterrupible to stop the
164   * stack moving.
165   *
166   * @param stackTraceThread the thread whose stack is walked
167   */
168  @Uninterruptible
169  @NoInline
170  private void recordFramesUninterruptible(RVMThread stackTraceThread) {
171    int stackFrameCount = 0;
172    Address fp;
173    Address ip;
174    /* Stack trace for the thread */
175    if (stackTraceThread == RVMThread.getCurrentThread()) {
176      fp = Magic.getFramePointer();
177    } else {
178      AbstractRegisters contextRegisters = stackTraceThread.getContextRegisters();
179      fp =  contextRegisters.getInnermostFramePointer();
180    }
181    ip = Magic.getReturnAddress(fp);
182    fp = Magic.getCallerFramePointer(fp);
183    while (Magic.getCallerFramePointer(fp).NE(StackFrameLayout.getStackFrameSentinelFP())) {
184      //VM.sysWriteln("at stackFrameCount = ",stackFrameCount);
185      int compiledMethodId = Magic.getCompiledMethodID(fp);
186      compiledMethods[stackFrameCount] = compiledMethodId;
187      if (compiledMethodId != StackFrameLayout.getInvisibleMethodID()) {
188        CompiledMethod compiledMethod =
189          CompiledMethods.getCompiledMethod(compiledMethodId);
190        if (compiledMethod.getCompilerType() != CompiledMethod.TRAP) {
191          instructionOffsets[stackFrameCount] =
192            compiledMethod.getInstructionOffset(ip).toInt();
193          if (compiledMethod.hasBridgeFromNativeAnnotation()) {
194            //VM.sysWriteln("native!");
195            // skip native frames, stopping at last native frame preceeding the
196            // Java To C transition frame
197            fp = RuntimeEntrypoints.unwindNativeStackFrame(fp);
198          }
199        } else {
200          //VM.sysWriteln("trap!");
201        }
202      } else {
203        //VM.sysWriteln("invisible method!");
204      }
205      stackFrameCount++;
206      ip = Magic.getReturnAddress(fp, stackTraceThread);
207      fp = Magic.getCallerFramePointer(fp);
208    }
209  }
210
211  /** Class to wrap up a stack frame element */
212  public static class Element {
213    /** Stack trace's method, null =&gt; invisible or trap */
214    protected final RVMMethod method;
215    /** Line number of element */
216    protected final int lineNumber;
217    /** Is this an invisible method? */
218    protected final boolean isInvisible;
219    /** Is this a hardware trap method? */
220    protected final boolean isTrap;
221
222    /**
223     * Constructor for non-opt compiled methods
224     * @param cm the compiled method
225     * @param off offset of the instruction from start of machine code,
226     *  in bytes
227     */
228    Element(CompiledMethod cm, int off) {
229      isInvisible = (cm == null);
230      if (!isInvisible) {
231        isTrap = cm.getCompilerType() == CompiledMethod.TRAP;
232        if (!isTrap) {
233          method = cm.getMethod();
234          lineNumber = cm.findLineNumberForInstruction(Offset.fromIntSignExtend(off));
235        } else {
236          method = null;
237          lineNumber = 0;
238        }
239      } else {
240        isTrap = false;
241        method = null;
242        lineNumber = 0;
243      }
244    }
245
246    /**
247     * Constructor for opt compiled methods.
248     * @param method the method that was called
249     * @param ln the line number
250     */
251    Element(RVMMethod method, int ln) {
252      this.method = method;
253      lineNumber = ln;
254      isTrap = false;
255      isInvisible = false;
256    }
257
258    /** @return source file name */
259    public String getFileName() {
260      if (isInvisible || isTrap) {
261        return null;
262      } else {
263        Atom fn = method.getDeclaringClass().getSourceName();
264        return (fn != null)  ? fn.toString() : null;
265      }
266    }
267
268    public String getClassName() {
269      if (isInvisible || isTrap) {
270        return "";
271      } else {
272        return method.getDeclaringClass().toString();
273      }
274    }
275
276    public Class<?> getElementClass() {
277      if (isInvisible || isTrap) {
278        return null;
279      }
280      return method.getDeclaringClass().getClassForType();
281    }
282
283    public String getMethodName() {
284      if (isInvisible) {
285        return "<invisible method>";
286      } else if (isTrap) {
287        return "<hardware trap>";
288      } else {
289        if (method != null) {
290          return method.getName().toString();
291        } else {
292          return "<unknown method: method was null>";
293        }
294      }
295    }
296
297    public int getLineNumber() {
298      return lineNumber;
299    }
300
301    public boolean isNative() {
302      if (isInvisible || isTrap) {
303        return false;
304      } else {
305        return method.isNative();
306      }
307    }
308  }
309
310  /**
311   * A stack trace element that contains additional debugging information,
312   * namely machine code offsets and byte code indexes.
313   */
314  static class InternalStackTraceElement extends Element {
315    /** machine code offset */
316    private Offset mcOffset;
317    /** byte code index */
318    private int bci;
319
320    /**
321     * Constructor for non-opt compiled methods
322     * @param cm the compiled method
323     * @param off offset of the instruction from start of machine code,
324     *  in bytes
325     */
326    InternalStackTraceElement(CompiledMethod cm, int off) {
327      super(cm, off);
328      if (!isInvisible) {
329        if (!isTrap) {
330          Offset machineCodeOffset = Offset.fromIntSignExtend(off);
331          mcOffset = machineCodeOffset;
332          if (cm instanceof BaselineCompiledMethod) {
333            bci = ((BaselineCompiledMethod) cm).findBytecodeIndexForInstruction(machineCodeOffset);
334          } else if (cm instanceof OptCompiledMethod) {
335            bci = ((OptCompiledMethod) cm).getMCMap().getBytecodeIndexForMCOffset(machineCodeOffset);
336          } else {
337            bci = 0;
338          }
339        } else {
340          mcOffset = Offset.zero();
341          bci = 0;
342        }
343      } else {
344        mcOffset = Offset.zero();
345        bci = 0;
346      }
347    }
348
349    /**
350     * Constructor for opt compiled methods.
351     * @param method the method that was called
352     * @param ln the line number
353     * @param mcOffset the machine code offset for the line
354     * @param bci the bytecode index for the line
355     *
356     */
357    InternalStackTraceElement(RVMMethod method, int ln, Offset mcOffset, int bci) {
358      super(method, ln);
359      this.mcOffset = mcOffset;
360      this.bci = bci;
361    }
362
363    void printForDebugging() {
364      VM.sysWrite("{IST: ");
365      VM.sysWrite(method.getDeclaringClass().toString());
366      VM.sysWrite(".");
367      VM.sysWrite(method.getName());
368      VM.sysWrite(" --- ");
369      VM.sysWrite("line_number: ");
370      VM.sysWrite(lineNumber);
371      VM.sysWrite(" byte_code_index: ");
372      VM.sysWrite(bci);
373      VM.sysWrite(" machine_code_offset: ");
374      VM.sysWrite(mcOffset);
375      VM.sysWriteln();
376    }
377  }
378
379  private CompiledMethod getCompiledMethod(int element) {
380    if ((element >= 0) && (element < compiledMethods.length)) {
381      int mid = compiledMethods[element];
382      if (mid != StackFrameLayout.getInvisibleMethodID()) {
383        return CompiledMethods.getCompiledMethod(mid);
384      }
385    }
386    return null;
387  }
388
389  /**
390   * @param cause the throwable that caused the stack trace
391   * @return the stack trace for use by the Throwable API
392   */
393  public Element[] getStackTrace(Throwable cause) {
394    int first = firstRealMethod(cause);
395    int last = lastRealMethod(first);
396    Element[] elements = buildStackTrace(first, last);
397    if (PRINT_INTERNAL_STACK_TRACE) {
398      VM.sysWriteln();
399      for (Element e : elements) {
400        InternalStackTraceElement internalEle = (InternalStackTraceElement) e;
401        internalEle.printForDebugging();
402      }
403    }
404    return elements;
405  }
406
407  private Element createStandardStackTraceElement(CompiledMethod cm, int off) {
408    if (!PRINT_INTERNAL_STACK_TRACE) {
409      return new Element(cm, off);
410    } else {
411      return new InternalStackTraceElement(cm, off);
412    }
413  }
414
415  private Element createOptStackTraceElement(RVMMethod m, int ln, Offset mcOffset, int bci) {
416    if (!PRINT_INTERNAL_STACK_TRACE) {
417      return new Element(m, ln);
418    } else {
419      return new InternalStackTraceElement(m, ln, mcOffset, bci);
420    }
421  }
422
423  private Element[] buildStackTrace(int first, int last) {
424    Element[] elements = new Element[countFrames(first, last)];
425    if (!VM.BuildForOptCompiler) {
426      int element = 0;
427      for (int i = first; i <= last; i++) {
428        elements[element] = createStandardStackTraceElement(getCompiledMethod(i), instructionOffsets[i]);
429        element++;
430      }
431    } else {
432      int element = 0;
433      for (int i = first; i <= last; i++) {
434        CompiledMethod compiledMethod = getCompiledMethod(i);
435        if ((compiledMethod == null) ||
436            (compiledMethod.getCompilerType() != CompiledMethod.OPT)) {
437          // Invisible or non-opt compiled method
438          elements[element] = createStandardStackTraceElement(compiledMethod, instructionOffsets[i]);
439          element++;
440        } else {
441          Offset instructionOffset = Offset.fromIntSignExtend(instructionOffsets[i]);
442          OptCompiledMethod optInfo = (OptCompiledMethod)compiledMethod;
443          OptMachineCodeMap map = optInfo.getMCMap();
444          int iei = map.getInlineEncodingForMCOffset(instructionOffset);
445          if (iei < 0) {
446            elements[element] = createStandardStackTraceElement(compiledMethod, instructionOffsets[i]);
447            element++;
448          } else {
449            int[] inlineEncoding = map.inlineEncoding;
450            int bci = map.getBytecodeIndexForMCOffset(instructionOffset);
451            for (; iei >= 0; iei = OptEncodedCallSiteTree.getParent(iei, inlineEncoding)) {
452              int mid = OptEncodedCallSiteTree.getMethodID(iei, inlineEncoding);
453              RVMMethod method = MemberReference.getMethodRef(mid).getResolvedMember();
454              int lineNumber = ((NormalMethod)method).getLineNumberForBCIndex(bci);
455              elements[element] = createOptStackTraceElement(method, lineNumber, instructionOffset, bci);
456              element++;
457              if (iei > 0) {
458                bci = OptEncodedCallSiteTree.getByteCodeOffset(iei, inlineEncoding);
459              }
460            }
461          }
462        }
463      }
464    }
465    return elements;
466  }
467
468  /**
469   * Count number of stack frames including those inlined by the opt compiler
470   * @param first the first compiled method to look from
471   * @param last the last compiled method to look to
472   * @return the number of stack frames
473   */
474  private int countFrames(int first, int last) {
475    int numElements = 0;
476    if (!VM.BuildForOptCompiler) {
477      numElements = last - first + 1;
478    } else {
479      for (int i = first; i <= last; i++) {
480        CompiledMethod compiledMethod = getCompiledMethod(i);
481        if ((compiledMethod == null) ||
482            (compiledMethod.getCompilerType() != CompiledMethod.OPT)) {
483          // Invisible or non-opt compiled method
484          numElements++;
485        } else {
486          Offset instructionOffset = Offset.fromIntSignExtend(instructionOffsets[i]);
487          OptCompiledMethod optInfo = (OptCompiledMethod)compiledMethod;
488          OptMachineCodeMap map = optInfo.getMCMap();
489          int iei = map.getInlineEncodingForMCOffset(instructionOffset);
490          if (iei < 0) {
491            numElements++;
492          } else {
493            int[] inlineEncoding = map.inlineEncoding;
494            for (; iei >= 0; iei = OptEncodedCallSiteTree.getParent(iei, inlineEncoding)) {
495              numElements++;
496            }
497          }
498        }
499      }
500    }
501    return numElements;
502  }
503
504  /**
505   * Find the first non-VM method/exception initializer method in the stack
506   * trace. As we're working with the compiled methods we're assuming the
507   * constructor of the exception won't have been inlined into the throwing
508   * method.
509   *
510   * @param cause the cause of generating the stack trace marking the end of the
511   *          frames to elide
512   * @return the index of the method throwing the exception or else 0
513   */
514  private int firstRealMethod(Throwable cause) {
515    /* We expect a hardware trap to look like:
516     * at org.jikesrvm.runtime.StackTrace.<init>(StackTrace.java:78)
517     * at java.lang.VMThrowable.fillInStackTrace(VMThrowable.java:67)
518     * at java.lang.Throwable.fillInStackTrace(Throwable.java:498)
519     * at java.lang.Throwable.<init>(Throwable.java:159)
520     * at java.lang.Throwable.<init>(Throwable.java:147)
521     * at java.lang.Exception.<init>(Exception.java:66)
522     * at java.lang.RuntimeException.<init>(RuntimeException.java:64)
523     * at java.lang.NullPointerException.<init>(NullPointerException.java:69)
524     * at org.jikesrvm.runtime.RuntimeEntrypoints.deliverHardwareException(RuntimeEntrypoints.java:682)
525     * at <hardware trap>(Unknown Source:0)
526     *
527     * and a software trap to look like:
528     * at org.jikesrvm.runtime.StackTrace.<init>(StackTrace.java:78)
529     * at java.lang.VMThrowable.fillInStackTrace(VMThrowable.java:67)
530     * at java.lang.Throwable.fillInStackTrace(Throwable.java:498)
531     * at java.lang.Throwable.<init>(Throwable.java:159)
532     * at java.lang.Error.<init>(Error.java:81)
533     * at java.lang.LinkageError.<init>(LinkageError.java:72)
534     * at java.lang.ExceptionInInitializerError.<init>(ExceptionInInitializerError.java:85)
535     * at java.lang.ExceptionInInitializerError.<init>(ExceptionInInitializerError.java:75)
536     *
537     * and an OutOfMemoryError to look like:
538     * ???
539     * ...
540     * at org.jikesrvm.mm.mminterface.MemoryManager.allocateSpace(MemoryManager.java:613)
541     * ...
542     * at org.jikesrvm.runtime.RuntimeEntrypoints.unresolvedNewArray(RuntimeEntrypoints.java:401)
543     */
544    if (Options.stackTraceFull) {
545      return 0;
546    } else {
547      int element = 0;
548      CompiledMethod compiledMethod = getCompiledMethod(element);
549
550      // Deal with OutOfMemoryError
551      if (cause instanceof OutOfMemoryError) {
552        // (1) search until RuntimeEntrypoints
553        while ((element < compiledMethods.length) &&
554            (compiledMethod != null) &&
555             compiledMethod.getMethod().getDeclaringClass().getClassForType() != RuntimeEntrypoints.class) {
556          element++;
557          compiledMethod = getCompiledMethod(element);
558        }
559        // (2) continue until not RuntimeEntrypoints
560        while ((element < compiledMethods.length) &&
561              (compiledMethod != null) &&
562              compiledMethod.getMethod().getDeclaringClass().getClassForType() == RuntimeEntrypoints.class) {
563          element++;
564          compiledMethod = getCompiledMethod(element);
565        }
566        return element;
567      }
568
569      // (1) remove any StackTrace frames
570      element = removeStackTraceFrames(element);
571      compiledMethod = getCompiledMethod(element);
572
573      // (2) remove any VMThrowable frames
574      if (VM.BuildForGnuClasspath) {
575        while ((element < compiledMethods.length) &&
576              (compiledMethod != null) &&
577              compiledMethod.getMethod().getDeclaringClass().getClassForType().getName().equals("java.lang.VMThrowable")) {
578          element++;
579          compiledMethod = getCompiledMethod(element);
580        }
581      }
582      // (3) remove any Throwable frames
583      while ((element < compiledMethods.length) &&
584            (compiledMethod != null) &&
585            compiledMethod.getMethod().getDeclaringClass().getClassForType() == java.lang.Throwable.class) {
586        element++;
587        compiledMethod = getCompiledMethod(element);
588      }
589      // (4) remove frames belonging to exception constructors upto the causes constructor
590      while ((element < compiledMethods.length) &&
591            (compiledMethod != null) &&
592            (compiledMethod.getMethod().getDeclaringClass().getClassForType() != cause.getClass()) &&
593            compiledMethod.getMethod().isObjectInitializer() &&
594            compiledMethod.getMethod().getDeclaringClass().isThrowable()) {
595        element++;
596        compiledMethod = getCompiledMethod(element);
597      }
598      // (5) remove frames belonging to the causes constructor
599      // NB This can be made to incorrectly elide frames if the cause
600      // exception is thrown from a constructor of the cause exception, however,
601      // Sun's VM has the same problem
602      while ((element < compiledMethods.length) &&
603            (compiledMethod != null) &&
604            (compiledMethod.getMethod().getDeclaringClass().getClassForType() == cause.getClass()) &&
605            compiledMethod.getMethod().isObjectInitializer()) {
606        element++;
607        compiledMethod = getCompiledMethod(element);
608      }
609      // (6) remove possible hardware exception deliverer frames
610      if (element < compiledMethods.length - 2) {
611        compiledMethod = getCompiledMethod(element + 1);
612        if ((compiledMethod != null) &&
613            compiledMethod.getCompilerType() == CompiledMethod.TRAP) {
614          element += 2;
615        }
616      }
617      return element;
618    }
619  }
620
621  /**
622   * Finds the first non-VM method in the stack trace. In this case, the assumption
623   * is that no exception occurred which makes the job of this method much easier
624   * than of {@link #firstRealMethod(Throwable)}: it is only necessary to skip
625   * frames from this class.
626   *
627   * @return the index of the method or else 0
628   */
629  private int firstRealMethod() {
630    return removeStackTraceFrames(0);
631  }
632
633  /**
634   * Removes all frames from the StackTrace class (i.e. this class) from
635   * the stack trace by skipping them.
636   * <p>
637   * Note: Callers must update all data relating to the element index themselves.
638   *
639   * @param element the element index
640   * @return an updated element index
641   */
642  private int removeStackTraceFrames(int element) {
643    CompiledMethod compiledMethod = getCompiledMethod(element);
644    while ((element < compiledMethods.length) &&
645          (compiledMethod != null) &&
646          compiledMethod.getMethod().getDeclaringClass().getClassForType() == StackTrace.class) {
647      element++;
648      compiledMethod = getCompiledMethod(element);
649    }
650    return element;
651  }
652
653  /**
654   * Find the first non-VM method at the end of the stack trace
655   * @param first the first real method of the stack trace
656   * @return compiledMethods.length-1 if no non-VM methods found else the index of
657   *         the method
658   */
659  private int lastRealMethod(int first) {
660    /* We expect an exception on the main thread to look like:
661     * at <invisible method>(Unknown Source:0)
662     * at org.jikesrvm.runtime.Reflection.invoke(Reflection.java:132)
663     * at org.jikesrvm.scheduler.MainThread.run(MainThread.java:195)
664     * at org.jikesrvm.scheduler.RVMThread.run(RVMThread.java:534)
665     * at org.jikesrvm.scheduler.RVMThread.startoff(RVMThread.java:1113
666     *
667     * and on another thread to look like:
668     * at org.jikesrvm.scheduler.RVMThread.run(RVMThread.java:534)
669     * at org.jikesrvm.scheduler.RVMThread.startoff(RVMThread.java:1113)
670     */
671    int max = compiledMethods.length - 1;
672    if (Options.stackTraceFull) {
673      return max;
674    } else {
675      // Start at end of array and elide a frame unless we find a place to stop
676      for (int i = max; i >= first; i--) {
677        if (compiledMethods[i] == StackFrameLayout.getInvisibleMethodID()) {
678          // we found an invisible method, assume next method if this is sane
679          if (i - 1 >= 0) {
680            return i - 1;
681          } else {
682            return max; // not sane => return max
683          }
684        }
685        CompiledMethod compiledMethod = getCompiledMethod(i);
686        if (compiledMethod.getCompilerType() == CompiledMethod.TRAP) {
687          // looks like we've gone too low
688          return max;
689        }
690        Class<?> frameClass = compiledMethod.getMethod().getDeclaringClass().getClassForType();
691        if ((frameClass != org.jikesrvm.scheduler.MainThread.class) &&
692            (frameClass != org.jikesrvm.scheduler.RVMThread.class) &&
693            (frameClass != org.jikesrvm.runtime.Reflection.class)) {
694          // Found a non-VM method
695          return i;
696        }
697      }
698      // No frame found
699      return max;
700    }
701  }
702
703  /**
704   * Gets a stack trace at the current point in time, assuming the thread didn't
705   * throw any exception.
706   *
707   * @param framesToSkip count of frames to skip. Note: frames from this class are always
708   * skipped and thus not included in the count. For example, if the caller were
709   * {@code foo()} and you wanted to skip {@code foo}'s frame, you would pass
710   * {@code 1}.
711   *
712   * @return a stack trace
713   */
714  public Element[] stackTraceNoException(int framesToSkip) {
715    if (VM.VerifyAssertions) VM._assert(framesToSkip >= 0, "Cannot skip negative amount of frames");
716    int first = firstRealMethod();
717    first += framesToSkip;
718    int last = lastRealMethod(first);
719    return buildStackTrace(first, last);
720  }
721
722}
723