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