diff options
Diffstat (limited to 'emulator/qtools/callstack.h')
-rw-r--r-- | emulator/qtools/callstack.h | 760 |
1 files changed, 760 insertions, 0 deletions
diff --git a/emulator/qtools/callstack.h b/emulator/qtools/callstack.h new file mode 100644 index 0000000..b869956 --- /dev/null +++ b/emulator/qtools/callstack.h @@ -0,0 +1,760 @@ +// 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 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(); + void showSnapshotStack(); + + 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 mNumFrames; + FRAME *mFrames; + int mTop; // index of the next stack frame to write + + 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) { +#if 0 + showStack(); +#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 + if (function->flags & symbol_type::kIsVectorStart) { + printf("stack before entering exception\n"); + showStack(); + } +#endif + + // If the previous function was 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 ((mPrevFunction->flags & symbol_type::kIsVectorTable) && mTop > 0) { + 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 + printf(" doPush(): popping to user mode, bb_addr: 0x%08x\n", + event->bb_addr); + showStack(); +#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 + 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(); + 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. + 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 (mTop - stackLevel > 7) { + printf("popping thru level %d\n", stackLevel); + showStack(); + } +#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) { + 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() +{ + fprintf(stderr, "mTop: %d skippedTime: %llu\n", mTop, mSkippedTime); + for (int ii = 0; ii < mTop; ++ii) { + fprintf(stderr, " %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, mFrames[ii].function->addr, + mFrames[ii].function->name); + } +} + +template<class FRAME, class BASE> +void CallStack<FRAME, BASE>::showSnapshotStack() +{ + fprintf(stderr, "mSnapshotTop: %d\n", mSnapshotTop); + for (int ii = 0; ii < mSnapshotTop; ++ii) { + fprintf(stderr, " %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 */ |