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