Skip to content

Instantly share code, notes, and snippets.

@rednaxelafx
Created April 1, 2012 08:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rednaxelafx/2273700 to your computer and use it in GitHub Desktop.
Save rednaxelafx/2273700 to your computer and use it in GitHub Desktop.
BCI of topmost interpreter Java frame shown in HotSpot crash log may be incorrect

When the topmost frame of a Java thread is an interpreted frame, the BCI printed in the crash log may be incorrect: when it shows +0, the actual BCI may be larger than that.

The BCX stored in the frame isn't always up-to-date. It only syncs up with the BCP register (ESI on x86, R13 on x64) at a few special points, such as when calling into the runtime, or other native code, or other Java methods.

If a Java thread crashed in the interpreter itself (e.g. segfault in write barrier due to card table corruption), then the info shown for the topmost interpreted Java frame might not be up-to-date. It's necessary to check the point of crash site, and the value in the BCP register.


Relevant code:

class AbstractInterpreter: AllStatic {
 protected:
  static StubQueue* _code;                                      // the interpreter code (codelets)
};

class TemplateInterpreter: public AbstractInterpreter {
  // this only returns whether a pc is within generated code for the interpreter.
  static bool       contains(address pc)                        { return _code != NULL && _code->contains(pc); }
};

class Interpreter: public CC_INTERP_ONLY(CppInterpreter) NOT_CC_INTERP(TemplateInterpreter) {
};

// A StubQueue maintains a queue of stubs.
// Note: All sizes (spaces) are given in bytes.

class StubQueue: public CHeapObj {
 private:
  address        _stub_buffer;                   // where all stubs are stored
  int            _buffer_size;                   // the buffer size in bytes
  int            _buffer_limit;                  // the (byte) index of the actual buffer limit (_buffer_limit <= _buffer_size)

  bool  contains(address pc) const               { return _stub_buffer <= pc && pc < _stub_buffer + _buffer_limit; }
};

void VMError::report(outputStream* st) {
  STEP(120, "(printing native stack)" )

     if (_verbose) {
       frame fr = _context ? os::fetch_frame_from_context(_context)
                           : os::current_frame();

       // see if it's a valid frame
       if (fr.pc()) {
          st->print_cr("Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)");

          // initialize decoder to decode C frames
          Decoder decoder;

          int count = 0;
          while (count++ < StackPrintLimit) {
             fr.print_on_error(st, buf, sizeof(buf));
             st->cr();
             if (os::is_first_C_frame(&fr)) break;
             fr = os::get_sender_for_C_frame(&fr);
          }

          if (count > StackPrintLimit) {
             st->print_cr("...<more frames>...");
          }

          st->cr();
       }
     }
}

// frame::print_on_error() is called by fatal error handler. Notice that we may
// crash inside this function if stack frame is corrupted. The fatal error
// handler can catch and handle the crash. Here we assume the frame is valid.
//
// First letter indicates type of the frame:
//    J: Java frame (compiled)
//    j: Java frame (interpreted)
//    V: VM frame (C/C++)
//    v: Other frames running VM generated code (e.g. stubs, adapters, etc.)
//    C: C/C++ frame
//
// We don't need detailed frame type as that in frame::print_name(). "C"
// suggests the problem is in user lib; everything else is likely a VM bug.

void frame::print_on_error(outputStream* st, char* buf, int buflen, bool verbose) const {
  if (_cb != NULL) {
    if (Interpreter::contains(pc())) {
      methodOop m = this->interpreter_frame_method();
      if (m != NULL) {
        m->name_and_sig_as_C_string(buf, buflen);
        st->print("j  %s", buf);
        st->print("+%d", this->interpreter_frame_bci());
      } else {
        st->print("j  " PTR_FORMAT, pc());
      }
    } else if (StubRoutines::contains(pc())) {
      StubCodeDesc* desc = StubCodeDesc::desc_for(pc());
      if (desc != NULL) {
        st->print("v  ~StubRoutines::%s", desc->name());
      } else {
        st->print("v  ~StubRoutines::" PTR_FORMAT, pc());
      }
    } else if (_cb->is_buffer_blob()) {
      st->print("v  ~BufferBlob::%s", ((BufferBlob *)_cb)->name());
    } else if (_cb->is_nmethod()) {
      methodOop m = ((nmethod *)_cb)->method();
      if (m != NULL) {
        m->name_and_sig_as_C_string(buf, buflen);
        st->print("J  %s", buf);
      } else {
        st->print("J  " PTR_FORMAT, pc());
      }
    } else if (_cb->is_runtime_stub()) {
      st->print("v  ~RuntimeStub::%s", ((RuntimeStub *)_cb)->name());
    } else if (_cb->is_deoptimization_stub()) {
      st->print("v  ~DeoptimizationBlob");
    } else if (_cb->is_exception_stub()) {
      st->print("v  ~ExceptionBlob");
    } else if (_cb->is_safepoint_stub()) {
      st->print("v  ~SafepointBlob");
    } else {
      st->print("v  blob " PTR_FORMAT, pc());
    }
  } else {
    print_C_frame(st, buf, buflen, pc());
  }
}

inline methodOop* frame::interpreter_frame_method_addr() const {
  return (methodOop*)addr_at(interpreter_frame_method_offset);
}

jint frame::interpreter_frame_bci() const {
  assert(is_interpreted_frame(), "interpreted frame expected");
  intptr_t bcx = interpreter_frame_bcx();
  return is_bci(bcx) ? bcx : interpreter_frame_method()->bci_from((address)bcx);
}

// Note: The bcx usually contains the bcp; however during GC it contains the bci
//       (changed by gc_prologue() and gc_epilogue()) to be methodOop position
//       independent. These accessors make sure the correct value is returned
//       by testing the range of the bcx value. bcp's are guaranteed to be above
//       max_method_code_size, since methods are always allocated in OldSpace and
//       Eden is allocated before OldSpace.
//
//       The bcp is accessed sometimes during GC for ArgumentDescriptors; than
//       the correct translation has to be performed (was bug).

inline bool frame::is_bci(intptr_t bcx) {
#ifdef _LP64
  return ((uintptr_t) bcx) <= ((uintptr_t) max_method_code_size) ;
#else
  return 0 <= bcx && bcx <= max_method_code_size;
#endif
}

bcp's are guaranteed to be above max_method_code_size, since methods are always allocated in OldSpace and Eden is allocated before OldSpace.

This comment is inaccurate in the current implementation. methods are allocated in the PermGen not in the OldSpace, and the GC heap layout for ParallelGC is different then the one used by GenCollectedHeap.


Printing "Java stack" is also affected in the crash handler, and the corresponding code in SA (StackFrameStream). The code for inspect frame state in HotSpot assumes that the thread is at safepoint, or at least have the last_Java_sp set. So if a JavaThread has an interpreted Java frame at the top of the stack when the crash handler was called, its "Java stack" can't be shown.

//
// StackFrameStream iterates through the frames of a thread starting from
// top most frame. It automatically takes care of updating the location of
// all (callee-saved) registers. Notice: If a thread is stopped at
// a safepoint, all registers are saved, not only the callee-saved ones.
//
// Use:
//
//   for(StackFrameStream fst(thread); !fst.is_done(); fst.next()) {
//     ...
//   }
//
StackFrameStream::StackFrameStream(JavaThread *thread, bool update) : _reg_map(thread, update) {
  assert(thread->has_last_Java_frame(), "sanity check");
  _fr = thread->last_frame();
  _is_done = false;
}

void VMError::print_stack_trace(outputStream* st, JavaThread* jt,
                                char* buf, int buflen, bool verbose) {
  if (jt->has_last_Java_frame()) {
    st->print_cr("Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)");
    for(StackFrameStream sfs(jt); !sfs.is_done(); sfs.next()) {
      sfs.current()->print_on_error(st, buf, buflen, verbose);
      st->cr();
    }
  }
}

class JavaThread: public Thread {
 private:
  JavaFrameAnchor _anchor;                       // Encapsulation of current java frame and it state

  // Last frame anchor routines

  JavaFrameAnchor* frame_anchor(void)                { return &_anchor; }

  // last_Java_sp
  bool has_last_Java_frame() const                   { return _anchor.has_last_Java_frame(); }
  intptr_t* last_Java_sp() const                     { return _anchor.last_Java_sp(); }

  // Accessing frames
  frame last_frame() {
    _anchor.make_walkable(this);
    return pd_last_frame();
  }

  // thread_linux_x86.hpp
  frame pd_last_frame() {
    assert(has_last_Java_frame(), "must have last_Java_sp() when suspended");
    if (_anchor.last_Java_pc() != NULL) {
      return frame(_anchor.last_Java_sp(), _anchor.last_Java_fp(), _anchor.last_Java_pc());
    } else {
      // This will pick up pc from sp
      return frame(_anchor.last_Java_sp(), _anchor.last_Java_fp());
    }
  }
};

//
// An object for encapsulating the machine/os dependent part of a JavaThread frame state
//
class JavaFrameAnchor VALUE_OBJ_CLASS_SPEC {
 private:
  //
  // Whenever _last_Java_sp != NULL other anchor fields MUST be valid!
  // The stack may not be walkable [check with walkable() ] but the values must be valid.
  // The profiler apparently depends on this.
  //
  intptr_t* volatile _last_Java_sp;

  // Whenever we call from Java to native we can not be assured that the return
  // address that composes the last_Java_frame will be in an accessible location
  // so calls from Java to native store that pc (or one good enough to locate
  // the oopmap) in the frame anchor. Since the frames that call from Java to
  // native are never deoptimized we never need to patch the pc and so this
  // is acceptable.
  volatile  address _last_Java_pc;

  // tells whether the last Java frame is set
  // It is important that when last_Java_sp != NULL that the rest of the frame
  // anchor (including platform specific) all be valid.

  bool has_last_Java_frame() const                   { return _last_Java_sp != NULL; }
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment