This fixes a bug that was causing a "stack overflow" because it sometimes wasn't popping off a Java method when it should have. I also changed the showStack() method so that instead of always printing to stderr, you can pass in a FILE* object to print to.
780 lines
27 KiB
C++
780 lines
27 KiB
C++
// Copyright 2006 The Android Open Source Project
|
|
|
|
#ifndef CALL_STACK_H
|
|
#define CALL_STACK_H
|
|
|
|
#include "opcode.h"
|
|
#include "armdis.h"
|
|
|
|
class CallStackBase {
|
|
public:
|
|
int getId() { return mId; }
|
|
void setId(int id) { mId = id; }
|
|
|
|
private:
|
|
int mId;
|
|
};
|
|
|
|
// Define a template class for the stack frame. The template parameter
|
|
// SYM is the symbol_type from the TraceReader<> template class. To
|
|
// use the CallStack class, the user derives a subclass of StackFrame
|
|
// and defines push() and pop() methods. This derived class is then
|
|
// passed as a template parameter to CallStack.
|
|
template <class SYM>
|
|
class StackFrame {
|
|
public:
|
|
|
|
virtual ~StackFrame() {};
|
|
|
|
virtual void push(int stackLevel, uint64_t time, CallStackBase *base) {};
|
|
virtual void pop(int stackLevel, uint64_t time, CallStackBase *base) {};
|
|
|
|
typedef SYM symbol_type;
|
|
static const uint32_t kCausedException = 0x01;
|
|
static const uint32_t kInterpreted = 0x02;
|
|
static const uint32_t kPopBarrier = (kCausedException | kInterpreted);
|
|
|
|
symbol_type *function; // the symbol for the function we entered
|
|
uint32_t addr; // return address when this function returns
|
|
uint32_t flags;
|
|
uint32_t time; // for debugging when a problem occurred
|
|
uint32_t global_time; // for debugging when a problem occurred
|
|
};
|
|
|
|
template <class FRAME, class BASE = CallStackBase>
|
|
class CallStack : public BASE {
|
|
public:
|
|
typedef FRAME frame_type;
|
|
typedef typename FRAME::symbol_type symbol_type;
|
|
typedef typename FRAME::symbol_type::region_type region_type;
|
|
typedef BASE base_type;
|
|
|
|
CallStack(int id, int numFrames, TraceReaderType *trace);
|
|
~CallStack();
|
|
|
|
void updateStack(BBEvent *event, symbol_type *function);
|
|
void popAll(uint64_t time);
|
|
void threadStart(uint64_t time);
|
|
void threadStop(uint64_t time);
|
|
|
|
// Set to true if you don't want to see any Java methods
|
|
void setNativeOnly(bool nativeOnly) {
|
|
mNativeOnly = nativeOnly;
|
|
}
|
|
|
|
int getStackLevel() { return mTop; }
|
|
|
|
uint64_t getGlobalTime(uint64_t time) { return time + mSkippedTime; }
|
|
void showStack(FILE *stream);
|
|
void showSnapshotStack(FILE *stream);
|
|
|
|
int mNumFrames;
|
|
FRAME *mFrames;
|
|
int mTop; // index of the next stack frame to write
|
|
|
|
private:
|
|
enum Action { NONE, PUSH, POP };
|
|
|
|
Action getAction(BBEvent *event, symbol_type *function);
|
|
Action getMethodAction(BBEvent *event, symbol_type *function);
|
|
void doSimplePush(symbol_type *function, uint32_t addr,
|
|
uint64_t time);
|
|
void doSimplePop(uint64_t time);
|
|
void doPush(BBEvent *event, symbol_type *function);
|
|
void doPop(BBEvent *event, symbol_type *function, Action methodAction);
|
|
|
|
void transitionToJava();
|
|
void transitionFromJava(uint64_t time);
|
|
|
|
TraceReaderType *mTrace;
|
|
bool mNativeOnly;
|
|
|
|
symbol_type mDummyFunction;
|
|
region_type mDummyRegion;
|
|
|
|
int mJavaTop;
|
|
|
|
int mSnapshotNumFrames;
|
|
FRAME *mSnapshotFrames;
|
|
int mSnapshotTop; // index of the next stack frame to write
|
|
|
|
symbol_type *mPrevFunction;
|
|
BBEvent mPrevEvent;
|
|
|
|
symbol_type *mUserFunction;
|
|
BBEvent mUserEvent; // the previous user-mode event
|
|
|
|
uint64_t mSkippedTime;
|
|
uint64_t mLastRunTime;
|
|
|
|
static MethodRec sCurrentMethod;
|
|
static MethodRec sNextMethod;
|
|
};
|
|
|
|
template<class FRAME, class BASE>
|
|
MethodRec CallStack<FRAME, BASE>::sCurrentMethod;
|
|
template<class FRAME, class BASE>
|
|
MethodRec CallStack<FRAME, BASE>::sNextMethod;
|
|
|
|
template<class FRAME, class BASE>
|
|
CallStack<FRAME, BASE>::CallStack(int id, int numFrames, TraceReaderType *trace)
|
|
{
|
|
mNativeOnly = false;
|
|
mTrace = trace;
|
|
BASE::setId(id);
|
|
mNumFrames = numFrames;
|
|
mFrames = new FRAME[mNumFrames];
|
|
mTop = 0;
|
|
|
|
mSnapshotNumFrames = numFrames;
|
|
mSnapshotFrames = new FRAME[mSnapshotNumFrames];
|
|
mSnapshotTop = 0;
|
|
|
|
memset(&mDummyFunction, 0, sizeof(symbol_type));
|
|
memset(&mDummyRegion, 0, sizeof(region_type));
|
|
mDummyFunction.region = &mDummyRegion;
|
|
mPrevFunction = &mDummyFunction;
|
|
memset(&mPrevEvent, 0, sizeof(BBEvent));
|
|
mUserFunction = &mDummyFunction;
|
|
memset(&mUserEvent, 0, sizeof(BBEvent));
|
|
mSkippedTime = 0;
|
|
mLastRunTime = 0;
|
|
mJavaTop = 0;
|
|
|
|
// Read the first two methods from the trace if we haven't already read
|
|
// from the method trace yet.
|
|
if (sCurrentMethod.time == 0) {
|
|
if (mTrace->ReadMethod(&sCurrentMethod)) {
|
|
sCurrentMethod.time = ~0ull;
|
|
sNextMethod.time = ~0ull;
|
|
}
|
|
if (sNextMethod.time != ~0ull && mTrace->ReadMethod(&sNextMethod)) {
|
|
sNextMethod.time = ~0ull;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<class FRAME, class BASE>
|
|
CallStack<FRAME, BASE>::~CallStack()
|
|
{
|
|
delete mFrames;
|
|
}
|
|
|
|
template<class FRAME, class BASE>
|
|
void
|
|
CallStack<FRAME, BASE>::updateStack(BBEvent *event, symbol_type *function)
|
|
{
|
|
if (mNativeOnly) {
|
|
// If this is an interpreted function, then use the native VM function
|
|
// instead.
|
|
if (function->vm_sym != NULL)
|
|
function = function->vm_sym;
|
|
}
|
|
|
|
Action action = getAction(event, function);
|
|
Action methodAction = getMethodAction(event, function);
|
|
|
|
// Pop off native functions before pushing or popping Java methods.
|
|
if (action == POP && mPrevFunction->vm_sym == NULL) {
|
|
// Pop off the previous function first.
|
|
doPop(event, function, NONE);
|
|
if (methodAction == POP) {
|
|
doPop(event, function, POP);
|
|
} else if (methodAction == PUSH) {
|
|
doPush(event, function);
|
|
}
|
|
} else {
|
|
if (methodAction != NONE) {
|
|
// If the method trace has a push or pop, then do it.
|
|
action = methodAction;
|
|
} else if (function->vm_sym != NULL) {
|
|
// This function is a Java method. Don't push or pop the
|
|
// Java method without a corresponding method trace record.
|
|
action = NONE;
|
|
}
|
|
if (action == POP) {
|
|
doPop(event, function, methodAction);
|
|
} else if (action == PUSH) {
|
|
doPush(event, function);
|
|
}
|
|
}
|
|
|
|
// If the stack is now empty, then push the current function.
|
|
if (mTop == 0) {
|
|
uint64_t time = event->time - mSkippedTime;
|
|
doSimplePush(function, 0, time);
|
|
}
|
|
|
|
mPrevFunction = function;
|
|
mPrevEvent = *event;
|
|
}
|
|
|
|
template<class FRAME, class BASE>
|
|
void
|
|
CallStack<FRAME, BASE>::threadStart(uint64_t time)
|
|
{
|
|
mSkippedTime += time - mLastRunTime;
|
|
}
|
|
|
|
template<class FRAME, class BASE>
|
|
void
|
|
CallStack<FRAME, BASE>::threadStop(uint64_t time)
|
|
{
|
|
mLastRunTime = time;
|
|
}
|
|
|
|
template<class FRAME, class BASE>
|
|
typename CallStack<FRAME, BASE>::Action
|
|
CallStack<FRAME, BASE>::getAction(BBEvent *event, symbol_type *function)
|
|
{
|
|
Action action;
|
|
uint32_t offset;
|
|
|
|
// Compute the offset from the start of the function to this basic
|
|
// block address.
|
|
offset = event->bb_addr - function->addr - function->region->base_addr;
|
|
|
|
// Get the previously executed instruction
|
|
Opcode op = OP_INVALID;
|
|
int numInsns = mPrevEvent.num_insns;
|
|
uint32_t insn = 0;
|
|
if (numInsns > 0) {
|
|
insn = mPrevEvent.insns[numInsns - 1];
|
|
if (mPrevEvent.is_thumb) {
|
|
insn = insn_unwrap_thumb(insn);
|
|
op = decode_insn_thumb(insn);
|
|
} else {
|
|
op = Arm::decode(insn);
|
|
}
|
|
}
|
|
|
|
// The number of bytes in the previous basic block depends on
|
|
// whether the basic block was ARM or THUMB instructions.
|
|
int numBytes;
|
|
if (mPrevEvent.is_thumb) {
|
|
numBytes = numInsns << 1;
|
|
} else {
|
|
numBytes = numInsns << 2;
|
|
}
|
|
|
|
// If this basic block follows the previous one, then return NONE.
|
|
// If we don't do this, then we may be fooled into thinking this
|
|
// is a POP if the previous block ended with a conditional
|
|
// (non-executed) ldmia instruction. We do this check before
|
|
// checking if we are in a different function because we otherwise
|
|
// we might be fooled into thinking this is a PUSH to a new function
|
|
// when it is really just a fall-thru into a local kernel symbol
|
|
// that just looks like a new function.
|
|
uint32_t prev_end_addr = mPrevEvent.bb_addr + numBytes;
|
|
if (prev_end_addr == event->bb_addr) {
|
|
return NONE;
|
|
}
|
|
|
|
// If this basic block is in the same function as the last basic block,
|
|
// then just return NONE (but see the exceptions below).
|
|
// Exception 1: if the function calls itself (offset == 0) then we
|
|
// want to push this function.
|
|
// Exception 2: if the function returns to itself, then we want
|
|
// to pop this function. We detect this case by checking if the last
|
|
// instruction in the previous basic block was a load-multiple (ldm)
|
|
// and included r15 as one of the loaded registers.
|
|
if (function == mPrevFunction) {
|
|
if (numInsns > 0) {
|
|
// If this is the beginning of the function and the previous
|
|
// instruction was not a branch, then it's a PUSH.
|
|
if (offset == 0 && op != OP_B && op != OP_THUMB_B)
|
|
return PUSH;
|
|
|
|
// If the previous instruction was an ldm that loaded r15,
|
|
// then it's a POP.
|
|
if (offset != 0 && ((op == OP_LDM && (insn & 0x8000))
|
|
|| (op == OP_THUMB_POP && (insn & 0x100)))) {
|
|
return POP;
|
|
}
|
|
}
|
|
|
|
return NONE;
|
|
}
|
|
|
|
// We have to figure out if this new function is a call or a
|
|
// return. We don't necessarily have a complete call stack (since
|
|
// we could have started tracing at any point), so we have to use
|
|
// heuristics. If the address we are jumping to is the beginning
|
|
// of a function, or if the instruction that took us there was
|
|
// either "bl" or "blx" then this is a PUSH. Also, if the
|
|
// function offset is non-zero and the previous instruction is a
|
|
// branch instruction, we will call it a PUSH. This happens in
|
|
// the kernel a lot when there is a branch to an offset from a
|
|
// label. A couple more special cases:
|
|
//
|
|
// - entering a .plt section ("procedure linkage table") is a PUSH,
|
|
// - an exception that jumps into the kernel vector entry point
|
|
// is also a push.
|
|
//
|
|
// If the function offset is non-zero and the previous instruction
|
|
// is a bx or some non-branch instruction, then it's a POP.
|
|
//
|
|
// There's another special case that comes up. The user code
|
|
// might execute an instruction that returns but before the pc
|
|
// starts executing in the caller, a kernel interrupt occurs.
|
|
// But it may be hard to tell if this is a return until after
|
|
// the kernel interrupt code is done and returns to user space.
|
|
// So we save the last user basic block and look at it when
|
|
// we come back into user space.
|
|
|
|
const uint32_t kIsKernelRegion = region_type::kIsKernelRegion;
|
|
|
|
if (((mPrevFunction->region->flags & kIsKernelRegion) == 0)
|
|
&& (function->region->flags & kIsKernelRegion)) {
|
|
// We just switched into the kernel. Save the previous
|
|
// user-mode basic block and function.
|
|
mUserEvent = mPrevEvent;
|
|
mUserFunction = mPrevFunction;
|
|
} else if ((mPrevFunction->region->flags & kIsKernelRegion)
|
|
&& ((function->region->flags & kIsKernelRegion) == 0)) {
|
|
// We just switched from kernel to user mode.
|
|
return POP;
|
|
}
|
|
|
|
action = PUSH;
|
|
if (offset != 0 && mPrevFunction != &mDummyFunction) {
|
|
// We are jumping into the middle of a function, so this is
|
|
// probably a return, not a function call. But look at the
|
|
// previous instruction first to see if it was a branch-and-link.
|
|
|
|
// If the previous instruction was not a branch (and not a
|
|
// branch-and-link) then POP; or if it is a "bx" instruction
|
|
// then POP because that is used to return from functions.
|
|
if (!isBranch(op) || op == OP_BX || op == OP_THUMB_BX) {
|
|
action = POP;
|
|
} else if (isBranch(op) && !isBranchLink(op)) {
|
|
// If the previous instruction was a normal branch to a
|
|
// local symbol then don't count it as a push or a pop.
|
|
action = NONE;
|
|
}
|
|
|
|
if (function->flags & symbol_type::kIsVectorTable)
|
|
action = PUSH;
|
|
}
|
|
return action;
|
|
}
|
|
|
|
|
|
template<class FRAME, class BASE>
|
|
void CallStack<FRAME, BASE>::doPush(BBEvent *event, symbol_type *function)
|
|
{
|
|
uint64_t time = event->time - mSkippedTime;
|
|
|
|
// Check for stack overflow
|
|
if (mTop >= mNumFrames) {
|
|
// Don't show the stack by default because this generates a lot
|
|
// of output and this is seen by users if there is an error when
|
|
// post-processing the trace. But this is useful for debugging.
|
|
#if 0
|
|
showStack(stderr);
|
|
#endif
|
|
fprintf(stderr, "Error: stack overflow (%d frames)\n", mTop);
|
|
exit(1);
|
|
}
|
|
|
|
// Compute the return address here because we may need to change
|
|
// it if we are popping off a frame for a vector table.
|
|
int numBytes;
|
|
if (mPrevEvent.is_thumb) {
|
|
numBytes = mPrevEvent.num_insns << 1;
|
|
} else {
|
|
numBytes = mPrevEvent.num_insns << 2;
|
|
}
|
|
uint32_t retAddr = mPrevEvent.bb_addr + numBytes;
|
|
|
|
// If this is a Java method then set the return address to zero.
|
|
// We won't be using it for popping the method and it may lead
|
|
// to false matches when searching the stack.
|
|
if (function->vm_sym != NULL) {
|
|
retAddr = 0;
|
|
}
|
|
|
|
#if 0
|
|
// For debugging only. Show the stack before entering the kernel
|
|
// exception-handling code.
|
|
if (function->flags & symbol_type::kIsVectorStart) {
|
|
printf("stack before entering exception\n");
|
|
showStack(stderr);
|
|
}
|
|
#endif
|
|
|
|
// If the top of stack is a vector table, then pop it
|
|
// off before pushing on the new function. Also, change the
|
|
// return address for the new function to the return address
|
|
// from the vector table.
|
|
if (mTop > 0
|
|
&& (mFrames[mTop - 1].function->flags & symbol_type::kIsVectorTable)) {
|
|
retAddr = mFrames[mTop - 1].addr;
|
|
doSimplePop(time);
|
|
}
|
|
|
|
const uint32_t kIsKernelRegion = region_type::kIsKernelRegion;
|
|
|
|
// The following code handles the case where one function, F1,
|
|
// calls another function, F2, but the before F2 can start
|
|
// executing, it takes a page fault (on the first instruction
|
|
// in F2). The kernel is entered, handles the page fault, and
|
|
// then returns to the called function. The problem is that
|
|
// this looks like a new function call to F2 from the kernel.
|
|
// The following code cleans up the stack by popping the
|
|
// kernel frames back to F1 (but not including F1). The
|
|
// return address for F2 also has to be fixed up to point to
|
|
// F1 instead of the kernel.
|
|
//
|
|
// We detect this case by checking if the previous basic block
|
|
// was in the kernel and the current basic block is not.
|
|
if ((mPrevFunction->region->flags & kIsKernelRegion)
|
|
&& ((function->region->flags & kIsKernelRegion) == 0)
|
|
&& mTop > 0) {
|
|
// We are switching from kernel mode to user mode.
|
|
#if 0
|
|
// For debugging.
|
|
printf(" doPush(): popping to user mode, bb_addr: 0x%08x\n",
|
|
event->bb_addr);
|
|
showStack(stderr);
|
|
#endif
|
|
do {
|
|
// Pop off the kernel frames until we reach the one that
|
|
// caused the exception.
|
|
doSimplePop(time);
|
|
|
|
// If the next stack frame is the one that caused an
|
|
// exception then stop popping frames.
|
|
if (mTop > 0
|
|
&& (mFrames[mTop - 1].flags & FRAME::kCausedException)) {
|
|
mFrames[mTop - 1].flags &= ~FRAME::kCausedException;
|
|
retAddr = mFrames[mTop].addr;
|
|
break;
|
|
}
|
|
} while (mTop > 0);
|
|
#if 0
|
|
// For debugging
|
|
printf(" doPush() popping to level %d, using retAddr 0x%08x\n",
|
|
mTop, retAddr);
|
|
#endif
|
|
}
|
|
|
|
// If we are starting an exception handler, then mark the previous
|
|
// stack frame so that we know where to return when the exception
|
|
// handler finishes.
|
|
if ((function->flags & symbol_type::kIsVectorStart) && mTop > 0)
|
|
mFrames[mTop - 1].flags |= FRAME::kCausedException;
|
|
|
|
doSimplePush(function, retAddr, time);
|
|
}
|
|
|
|
template<class FRAME, class BASE>
|
|
void CallStack<FRAME, BASE>::doSimplePush(symbol_type *function,
|
|
uint32_t addr, uint64_t time)
|
|
{
|
|
// Check for stack overflow
|
|
if (mTop >= mNumFrames) {
|
|
showStack(stderr);
|
|
fprintf(stderr, "too many stack frames (%d)\n", mTop);
|
|
exit(1);
|
|
}
|
|
|
|
// Keep track of the number of Java methods we push on the stack.
|
|
if (!mNativeOnly && function->vm_sym != NULL) {
|
|
// If we are pushing the first Java method on the stack, then
|
|
// save a snapshot of the stack so that we can clean things up
|
|
// later when we pop off the last Java stack frame.
|
|
if (mJavaTop == 0) {
|
|
transitionToJava();
|
|
}
|
|
mJavaTop += 1;
|
|
}
|
|
|
|
mFrames[mTop].addr = addr;
|
|
mFrames[mTop].function = function;
|
|
mFrames[mTop].flags = 0;
|
|
mFrames[mTop].time = time;
|
|
mFrames[mTop].global_time = time + mSkippedTime;
|
|
|
|
// If the function being pushed is a Java method, then mark it on
|
|
// the stack so that we don't pop it off until we get a matching
|
|
// trace record from the method trace file.
|
|
if (function->vm_sym != NULL) {
|
|
mFrames[mTop].flags = FRAME::kInterpreted;
|
|
}
|
|
|
|
mFrames[mTop].push(mTop, time, this);
|
|
mTop += 1;
|
|
}
|
|
|
|
template<class FRAME, class BASE>
|
|
void CallStack<FRAME, BASE>::doSimplePop(uint64_t time)
|
|
{
|
|
if (mTop <= 0) {
|
|
return;
|
|
}
|
|
|
|
mTop -= 1;
|
|
mFrames[mTop].pop(mTop, time, this);
|
|
|
|
// Keep track of the number of Java methods we have on the stack.
|
|
symbol_type *function = mFrames[mTop].function;
|
|
if (!mNativeOnly && function->vm_sym != NULL) {
|
|
mJavaTop -= 1;
|
|
|
|
// When there are no more Java stack frames, then clean up
|
|
// the client's stack. We need to do this because the client
|
|
// doesn't see the changes to the native stack underlying the
|
|
// fake Java stack until the last Java method is popped off.
|
|
if (mJavaTop == 0) {
|
|
transitionFromJava(time);
|
|
}
|
|
}
|
|
}
|
|
|
|
template<class FRAME, class BASE>
|
|
void CallStack<FRAME, BASE>::doPop(BBEvent *event, symbol_type *function,
|
|
Action methodAction)
|
|
{
|
|
uint64_t time = event->time - mSkippedTime;
|
|
|
|
// Search backward on the stack for a matching return address.
|
|
// The most common case is that we pop one stack frame, but
|
|
// sometimes we pop more than one.
|
|
int stackLevel;
|
|
bool allowMethodPop = (methodAction == POP);
|
|
for (stackLevel = mTop - 1; stackLevel >= 0; --stackLevel) {
|
|
if (event->bb_addr == mFrames[stackLevel].addr) {
|
|
// We found a matching return address on the stack.
|
|
break;
|
|
}
|
|
|
|
// If this stack frame caused an exception, then do not pop
|
|
// this stack frame.
|
|
if (mFrames[stackLevel].flags & FRAME::kPopBarrier) {
|
|
// If this is a Java method, then allow a pop only if we
|
|
// have a matching trace record.
|
|
if (mFrames[stackLevel].flags & FRAME::kInterpreted) {
|
|
if (allowMethodPop) {
|
|
// Allow at most one method pop
|
|
allowMethodPop = false;
|
|
continue;
|
|
}
|
|
}
|
|
stackLevel += 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we didn't find a matching return address then search the stack
|
|
// again for a matching function.
|
|
if (stackLevel < 0 || event->bb_addr != mFrames[stackLevel].addr) {
|
|
bool allowMethodPop = (methodAction == POP);
|
|
for (stackLevel = mTop - 1; stackLevel >= 0; --stackLevel) {
|
|
// Compare the function with the one in the stack frame.
|
|
if (function == mFrames[stackLevel].function) {
|
|
// We found a matching function. We want to pop up to but not
|
|
// including this frame. But allow popping this frame if this
|
|
// method called itself and we have a method pop.
|
|
if (allowMethodPop && function == mPrevFunction) {
|
|
// pop this frame
|
|
break;
|
|
}
|
|
// do not pop this frame
|
|
stackLevel += 1;
|
|
break;
|
|
}
|
|
|
|
// If this stack frame caused an exception, then do not pop
|
|
// this stack frame.
|
|
if (mFrames[stackLevel].flags & FRAME::kPopBarrier) {
|
|
// If this is a Java method, then allow a pop only if we
|
|
// have a matching trace record.
|
|
if (mFrames[stackLevel].flags & FRAME::kInterpreted) {
|
|
if (allowMethodPop) {
|
|
// Allow at most one method pop
|
|
allowMethodPop = false;
|
|
continue;
|
|
}
|
|
}
|
|
stackLevel += 1;
|
|
break;
|
|
}
|
|
}
|
|
if (stackLevel < 0)
|
|
stackLevel = 0;
|
|
}
|
|
|
|
// Note that if we didn't find a matching stack frame, we will pop
|
|
// the whole stack (unless there is a Java method or exception
|
|
// frame on the stack). This is intentional because we may have
|
|
// started the trace in the middle of an executing program that is
|
|
// returning up the stack and we do not know the whole stack. So
|
|
// the right thing to do is to empty the stack.
|
|
|
|
// If we are emptying the stack, then add the current function
|
|
// on top. If the current function is the same as the top of
|
|
// stack, then avoid an extraneous pop and push.
|
|
if (stackLevel == 0 && mFrames[0].function == function)
|
|
stackLevel = 1;
|
|
|
|
#if 0
|
|
// If we are popping off a large number of stack frames, then
|
|
// we might have a bug.
|
|
if (mTop - stackLevel > 7) {
|
|
printf("popping thru level %d\n", stackLevel);
|
|
showStack(stderr);
|
|
}
|
|
#endif
|
|
|
|
// Pop the stack frames
|
|
for (int ii = mTop - 1; ii >= stackLevel; --ii)
|
|
doSimplePop(time);
|
|
|
|
// Clear the "caused exception" bit on the current stack frame
|
|
if (mTop > 0) {
|
|
mFrames[mTop - 1].flags &= ~FRAME::kCausedException;
|
|
}
|
|
|
|
// Also handle the case where F1 calls F2 and F2 returns to
|
|
// F1, but before we can execute any instructions in F1, we
|
|
// switch to the kernel. Then when we return from the kernel
|
|
// we want to pop off F2 from the stack instead of pushing F1
|
|
// on top of F2. To handle this case, we saved the last
|
|
// user-mode basic block when we entered the kernel (in
|
|
// the getAction() function) and now we can check to see if
|
|
// that was a return to F1 instead of a call. We use the
|
|
// getAction() function to determine this.
|
|
const uint32_t kIsKernelRegion = region_type::kIsKernelRegion;
|
|
if ((mPrevFunction->region->flags & kIsKernelRegion)
|
|
&& ((function->region->flags & kIsKernelRegion) == 0)) {
|
|
mPrevEvent = mUserEvent;
|
|
mPrevFunction = mUserFunction;
|
|
if (getAction(event, function) == POP) {
|
|
// We may need to pop more than one frame, so just
|
|
// call doPop() again. This won't be an infinite loop
|
|
// here because we changed mPrevEvent to the last
|
|
// user-mode event.
|
|
doPop(event, function, methodAction);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<class FRAME, class BASE>
|
|
void CallStack<FRAME, BASE>::popAll(uint64_t time)
|
|
{
|
|
time -= mSkippedTime;
|
|
while (mTop != 0) {
|
|
doSimplePop(time);
|
|
}
|
|
}
|
|
|
|
template<class FRAME, class BASE>
|
|
typename CallStack<FRAME, BASE>::Action
|
|
CallStack<FRAME, BASE>::getMethodAction(BBEvent *event, symbol_type *function)
|
|
{
|
|
if (function->vm_sym == NULL && mPrevFunction->vm_sym == NULL) {
|
|
return NONE;
|
|
}
|
|
|
|
Action action = NONE;
|
|
uint32_t prevAddr = mPrevFunction->addr + mPrevFunction->region->base_addr;
|
|
uint32_t addr = function->addr + function->region->base_addr;
|
|
|
|
// If the events get ahead of the method trace, then read ahead until we
|
|
// sync up again. This can happen if there is a pop of a method in the
|
|
// method trace for which we don't have a previous push.
|
|
while (event->time >= sNextMethod.time) {
|
|
sCurrentMethod = sNextMethod;
|
|
if (mTrace->ReadMethod(&sNextMethod)) {
|
|
sNextMethod.time = ~0ull;
|
|
}
|
|
}
|
|
|
|
if (event->time >= sCurrentMethod.time && event->pid == sCurrentMethod.pid) {
|
|
if (addr == sCurrentMethod.addr || prevAddr == sCurrentMethod.addr) {
|
|
action = (sCurrentMethod.flags == 0) ? PUSH : POP;
|
|
// We found a match, so read the next record.
|
|
sCurrentMethod = sNextMethod;
|
|
if (sNextMethod.time != ~0ull && mTrace->ReadMethod(&sNextMethod)) {
|
|
sNextMethod.time = ~0ull;
|
|
}
|
|
}
|
|
}
|
|
return action;
|
|
}
|
|
|
|
// When the first Java method is pushed on the stack, this method is
|
|
// called to save a snapshot of the current native stack so that the
|
|
// client's view of the native stack can be patched up later when the
|
|
// Java stack is empty.
|
|
template<class FRAME, class BASE>
|
|
void CallStack<FRAME, BASE>::transitionToJava()
|
|
{
|
|
mSnapshotTop = mTop;
|
|
for (int ii = 0; ii < mTop; ++ii) {
|
|
mSnapshotFrames[ii] = mFrames[ii];
|
|
}
|
|
}
|
|
|
|
// When the Java stack becomes empty, the native stack becomes
|
|
// visible. This method is called when the Java stack becomes empty
|
|
// to patch up the client's view of the native stack, which may have
|
|
// changed underneath the Java stack. The stack snapshot is used to
|
|
// create a sequence of pops and pushes to make the client's view of
|
|
// the native stack match the current native stack.
|
|
template<class FRAME, class BASE>
|
|
void CallStack<FRAME, BASE>::transitionFromJava(uint64_t time)
|
|
{
|
|
int top = mTop;
|
|
if (top > mSnapshotTop) {
|
|
top = mSnapshotTop;
|
|
}
|
|
for (int ii = 0; ii < top; ++ii) {
|
|
if (mSnapshotFrames[ii].function->addr == mFrames[ii].function->addr) {
|
|
continue;
|
|
}
|
|
|
|
// Pop off all the rest of the frames from the snapshot
|
|
for (int jj = top - 1; jj >= ii; --jj) {
|
|
mSnapshotFrames[jj].pop(jj, time, this);
|
|
}
|
|
|
|
// Push the new frames from the native stack
|
|
for (int jj = ii; jj < mTop; ++jj) {
|
|
mFrames[jj].push(jj, time, this);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
template<class FRAME, class BASE>
|
|
void CallStack<FRAME, BASE>::showStack(FILE *stream)
|
|
{
|
|
fprintf(stream, "mTop: %d skippedTime: %llu\n", mTop, mSkippedTime);
|
|
for (int ii = 0; ii < mTop; ++ii) {
|
|
uint32_t addr = mFrames[ii].function->addr;
|
|
addr += mFrames[ii].function->region->vstart;
|
|
fprintf(stream, " %d: t %d gt %d f %x 0x%08x 0x%08x %s\n",
|
|
ii, mFrames[ii].time, mFrames[ii].global_time,
|
|
mFrames[ii].flags,
|
|
mFrames[ii].addr, addr,
|
|
mFrames[ii].function->name);
|
|
}
|
|
}
|
|
|
|
template<class FRAME, class BASE>
|
|
void CallStack<FRAME, BASE>::showSnapshotStack(FILE *stream)
|
|
{
|
|
fprintf(stream, "mSnapshotTop: %d\n", mSnapshotTop);
|
|
for (int ii = 0; ii < mSnapshotTop; ++ii) {
|
|
fprintf(stream, " %d: t %d f %x 0x%08x 0x%08x %s\n",
|
|
ii, mSnapshotFrames[ii].time, mSnapshotFrames[ii].flags,
|
|
mSnapshotFrames[ii].addr, mSnapshotFrames[ii].function->addr,
|
|
mSnapshotFrames[ii].function->name);
|
|
}
|
|
}
|
|
|
|
#endif /* CALL_STACK_H */
|