Handle munmap() and add support for tracing JNI (native) calls.
The munmap() kernel calls are traced but the tracing code wasn't doing anything with them. This caused the number of mapped regions in a process to grow large in some cases and also caused symbol lookup errors in some rare cases. This change also adds support for new trace record types for supporting JNI (native) calls from Java into native code. This helps with constructing a more accurate call stack.
This commit is contained in:
@@ -32,7 +32,9 @@ class StackFrame {
|
|||||||
typedef SYM symbol_type;
|
typedef SYM symbol_type;
|
||||||
static const uint32_t kCausedException = 0x01;
|
static const uint32_t kCausedException = 0x01;
|
||||||
static const uint32_t kInterpreted = 0x02;
|
static const uint32_t kInterpreted = 0x02;
|
||||||
static const uint32_t kPopBarrier = (kCausedException | kInterpreted);
|
static const uint32_t kStartNative = 0x04;
|
||||||
|
static const uint32_t kPopBarrier = (kCausedException | kInterpreted
|
||||||
|
| kStartNative);
|
||||||
|
|
||||||
symbol_type *function; // the symbol for the function we entered
|
symbol_type *function; // the symbol for the function we entered
|
||||||
uint32_t addr; // return address when this function returns
|
uint32_t addr; // return address when this function returns
|
||||||
@@ -57,7 +59,7 @@ class CallStack : public BASE {
|
|||||||
void threadStart(uint64_t time);
|
void threadStart(uint64_t time);
|
||||||
void threadStop(uint64_t time);
|
void threadStop(uint64_t time);
|
||||||
|
|
||||||
// Set to true if you don't want to see any Java methods
|
// Set to true if you don't want to see any Java methods ever
|
||||||
void setNativeOnly(bool nativeOnly) {
|
void setNativeOnly(bool nativeOnly) {
|
||||||
mNativeOnly = nativeOnly;
|
mNativeOnly = nativeOnly;
|
||||||
}
|
}
|
||||||
@@ -66,38 +68,36 @@ class CallStack : public BASE {
|
|||||||
|
|
||||||
uint64_t getGlobalTime(uint64_t time) { return time + mSkippedTime; }
|
uint64_t getGlobalTime(uint64_t time) { return time + mSkippedTime; }
|
||||||
void showStack(FILE *stream);
|
void showStack(FILE *stream);
|
||||||
void showSnapshotStack(FILE *stream);
|
|
||||||
|
|
||||||
int mNumFrames;
|
int mNumFrames;
|
||||||
FRAME *mFrames;
|
FRAME *mFrames;
|
||||||
int mTop; // index of the next stack frame to write
|
int mTop; // index of the next stack frame to write
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum Action { NONE, PUSH, POP };
|
enum Action { NONE, PUSH, POP, NATIVE_PUSH };
|
||||||
|
|
||||||
Action getAction(BBEvent *event, symbol_type *function);
|
Action getAction(BBEvent *event, symbol_type *function);
|
||||||
Action getMethodAction(BBEvent *event, symbol_type *function);
|
void doMethodAction(BBEvent *event, symbol_type *function);
|
||||||
|
void doMethodPop(BBEvent *event, uint32_t addr, const uint32_t flags);
|
||||||
void doSimplePush(symbol_type *function, uint32_t addr,
|
void doSimplePush(symbol_type *function, uint32_t addr,
|
||||||
uint64_t time);
|
uint64_t time, int flags);
|
||||||
void doSimplePop(uint64_t time);
|
void doSimplePop(uint64_t time);
|
||||||
void doPush(BBEvent *event, symbol_type *function);
|
void doPush(BBEvent *event, symbol_type *function);
|
||||||
void doPop(BBEvent *event, symbol_type *function, Action methodAction);
|
void doPop(BBEvent *event, symbol_type *function, Action methodAction);
|
||||||
|
|
||||||
void transitionToJava();
|
|
||||||
void transitionFromJava(uint64_t time);
|
|
||||||
|
|
||||||
TraceReaderType *mTrace;
|
TraceReaderType *mTrace;
|
||||||
|
|
||||||
|
// This is a global switch that disables Java methods from appearing
|
||||||
|
// on the stack.
|
||||||
bool mNativeOnly;
|
bool mNativeOnly;
|
||||||
|
|
||||||
|
// This keeps track of whether native frames are currently allowed on the
|
||||||
|
// stack.
|
||||||
|
bool mAllowNativeFrames;
|
||||||
|
|
||||||
symbol_type mDummyFunction;
|
symbol_type mDummyFunction;
|
||||||
region_type mDummyRegion;
|
region_type mDummyRegion;
|
||||||
|
|
||||||
int mJavaTop;
|
|
||||||
|
|
||||||
int mSnapshotNumFrames;
|
|
||||||
FRAME *mSnapshotFrames;
|
|
||||||
int mSnapshotTop; // index of the next stack frame to write
|
|
||||||
|
|
||||||
symbol_type *mPrevFunction;
|
symbol_type *mPrevFunction;
|
||||||
BBEvent mPrevEvent;
|
BBEvent mPrevEvent;
|
||||||
|
|
||||||
@@ -125,10 +125,7 @@ CallStack<FRAME, BASE>::CallStack(int id, int numFrames, TraceReaderType *trace)
|
|||||||
mNumFrames = numFrames;
|
mNumFrames = numFrames;
|
||||||
mFrames = new FRAME[mNumFrames];
|
mFrames = new FRAME[mNumFrames];
|
||||||
mTop = 0;
|
mTop = 0;
|
||||||
|
mAllowNativeFrames = true;
|
||||||
mSnapshotNumFrames = numFrames;
|
|
||||||
mSnapshotFrames = new FRAME[mSnapshotNumFrames];
|
|
||||||
mSnapshotTop = 0;
|
|
||||||
|
|
||||||
memset(&mDummyFunction, 0, sizeof(symbol_type));
|
memset(&mDummyFunction, 0, sizeof(symbol_type));
|
||||||
memset(&mDummyRegion, 0, sizeof(region_type));
|
memset(&mDummyRegion, 0, sizeof(region_type));
|
||||||
@@ -139,7 +136,6 @@ CallStack<FRAME, BASE>::CallStack(int id, int numFrames, TraceReaderType *trace)
|
|||||||
memset(&mUserEvent, 0, sizeof(BBEvent));
|
memset(&mUserEvent, 0, sizeof(BBEvent));
|
||||||
mSkippedTime = 0;
|
mSkippedTime = 0;
|
||||||
mLastRunTime = 0;
|
mLastRunTime = 0;
|
||||||
mJavaTop = 0;
|
|
||||||
|
|
||||||
// Read the first two methods from the trace if we haven't already read
|
// Read the first two methods from the trace if we haven't already read
|
||||||
// from the method trace yet.
|
// from the method trace yet.
|
||||||
@@ -169,11 +165,29 @@ CallStack<FRAME, BASE>::updateStack(BBEvent *event, symbol_type *function)
|
|||||||
// instead.
|
// instead.
|
||||||
if (function->vm_sym != NULL)
|
if (function->vm_sym != NULL)
|
||||||
function = function->vm_sym;
|
function = function->vm_sym;
|
||||||
|
} else {
|
||||||
|
doMethodAction(event, function);
|
||||||
}
|
}
|
||||||
|
|
||||||
Action action = getAction(event, function);
|
Action action = getAction(event, function);
|
||||||
Action methodAction = getMethodAction(event, function);
|
|
||||||
|
|
||||||
|
// Allow native frames if we are executing in the kernel.
|
||||||
|
if (!mAllowNativeFrames
|
||||||
|
&& (function->region->flags & region_type::kIsKernelRegion) == 0) {
|
||||||
|
action = NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (function->vm_sym != NULL) {
|
||||||
|
function = function->vm_sym;
|
||||||
|
function->vm_sym = NULL;
|
||||||
|
}
|
||||||
|
if (action == PUSH) {
|
||||||
|
doPush(event, function);
|
||||||
|
} else if (action == POP) {
|
||||||
|
doPop(event, function, NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
// Pop off native functions before pushing or popping Java methods.
|
// Pop off native functions before pushing or popping Java methods.
|
||||||
if (action == POP && mPrevFunction->vm_sym == NULL) {
|
if (action == POP && mPrevFunction->vm_sym == NULL) {
|
||||||
// Pop off the previous function first.
|
// Pop off the previous function first.
|
||||||
@@ -198,11 +212,16 @@ CallStack<FRAME, BASE>::updateStack(BBEvent *event, symbol_type *function)
|
|||||||
doPush(event, function);
|
doPush(event, function);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// If the stack is now empty, then push the current function.
|
// If the stack is now empty, then push the current function.
|
||||||
if (mTop == 0) {
|
if (mTop == 0) {
|
||||||
uint64_t time = event->time - mSkippedTime;
|
uint64_t time = event->time - mSkippedTime;
|
||||||
doSimplePush(function, 0, time);
|
int flags = 0;
|
||||||
|
if (function->vm_sym != NULL) {
|
||||||
|
flags = FRAME::kInterpreted;
|
||||||
|
}
|
||||||
|
doSimplePush(function, 0, time, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
mPrevFunction = function;
|
mPrevFunction = function;
|
||||||
@@ -465,12 +484,19 @@ void CallStack<FRAME, BASE>::doPush(BBEvent *event, symbol_type *function)
|
|||||||
if ((function->flags & symbol_type::kIsVectorStart) && mTop > 0)
|
if ((function->flags & symbol_type::kIsVectorStart) && mTop > 0)
|
||||||
mFrames[mTop - 1].flags |= FRAME::kCausedException;
|
mFrames[mTop - 1].flags |= FRAME::kCausedException;
|
||||||
|
|
||||||
doSimplePush(function, retAddr, time);
|
// 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.
|
||||||
|
int flags = 0;
|
||||||
|
if (function->vm_sym != NULL) {
|
||||||
|
flags = FRAME::kInterpreted;
|
||||||
|
}
|
||||||
|
doSimplePush(function, retAddr, time, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class FRAME, class BASE>
|
template<class FRAME, class BASE>
|
||||||
void CallStack<FRAME, BASE>::doSimplePush(symbol_type *function,
|
void CallStack<FRAME, BASE>::doSimplePush(symbol_type *function, uint32_t addr,
|
||||||
uint32_t addr, uint64_t time)
|
uint64_t time, int flags)
|
||||||
{
|
{
|
||||||
// Check for stack overflow
|
// Check for stack overflow
|
||||||
if (mTop >= mNumFrames) {
|
if (mTop >= mNumFrames) {
|
||||||
@@ -479,30 +505,12 @@ void CallStack<FRAME, BASE>::doSimplePush(symbol_type *function,
|
|||||||
exit(1);
|
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].addr = addr;
|
||||||
mFrames[mTop].function = function;
|
mFrames[mTop].function = function;
|
||||||
mFrames[mTop].flags = 0;
|
mFrames[mTop].flags = flags;
|
||||||
mFrames[mTop].time = time;
|
mFrames[mTop].time = time;
|
||||||
mFrames[mTop].global_time = time + mSkippedTime;
|
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);
|
mFrames[mTop].push(mTop, time, this);
|
||||||
mTop += 1;
|
mTop += 1;
|
||||||
}
|
}
|
||||||
@@ -517,17 +525,25 @@ void CallStack<FRAME, BASE>::doSimplePop(uint64_t time)
|
|||||||
mTop -= 1;
|
mTop -= 1;
|
||||||
mFrames[mTop].pop(mTop, time, this);
|
mFrames[mTop].pop(mTop, time, this);
|
||||||
|
|
||||||
// Keep track of the number of Java methods we have on the stack.
|
if (mNativeOnly)
|
||||||
symbol_type *function = mFrames[mTop].function;
|
return;
|
||||||
if (!mNativeOnly && function->vm_sym != NULL) {
|
|
||||||
mJavaTop -= 1;
|
|
||||||
|
|
||||||
// When there are no more Java stack frames, then clean up
|
// If the stack is empty, then allow more native frames.
|
||||||
// the client's stack. We need to do this because the client
|
// Otherwise, if we are transitioning from Java to native, then allow
|
||||||
// doesn't see the changes to the native stack underlying the
|
// more native frames.
|
||||||
// fake Java stack until the last Java method is popped off.
|
// Otherwise, if we are transitioning from native to Java, then disallow
|
||||||
if (mJavaTop == 0) {
|
// more native frames.
|
||||||
transitionFromJava(time);
|
if (mTop == 0) {
|
||||||
|
mAllowNativeFrames = true;
|
||||||
|
} else {
|
||||||
|
bool newerIsJava = (mFrames[mTop].flags & FRAME::kInterpreted) != 0;
|
||||||
|
bool olderIsJava = (mFrames[mTop - 1].flags & FRAME::kInterpreted) != 0;
|
||||||
|
if (newerIsJava && !olderIsJava) {
|
||||||
|
// We are transitioning from Java to native
|
||||||
|
mAllowNativeFrames = true;
|
||||||
|
} else if (!newerIsJava && olderIsJava) {
|
||||||
|
// We are transitioning from native to Java
|
||||||
|
mAllowNativeFrames = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -671,20 +687,45 @@ void CallStack<FRAME, BASE>::popAll(uint64_t time)
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<class FRAME, class BASE>
|
template<class FRAME, class BASE>
|
||||||
typename CallStack<FRAME, BASE>::Action
|
void CallStack<FRAME, BASE>::doMethodPop(BBEvent *event, uint32_t addr,
|
||||||
CallStack<FRAME, BASE>::getMethodAction(BBEvent *event, symbol_type *function)
|
const uint32_t flags)
|
||||||
{
|
{
|
||||||
if (function->vm_sym == NULL && mPrevFunction->vm_sym == NULL) {
|
uint64_t time = event->time - mSkippedTime;
|
||||||
return NONE;
|
|
||||||
|
// Search the stack from the top down for a frame that contains a
|
||||||
|
// matching method.
|
||||||
|
int stackLevel;
|
||||||
|
for (stackLevel = mTop - 1; stackLevel >= 0; --stackLevel) {
|
||||||
|
if (mFrames[stackLevel].flags & flags) {
|
||||||
|
// If we are searching for a native method, then don't bother trying
|
||||||
|
// to match the address.
|
||||||
|
if (flags == FRAME::kStartNative)
|
||||||
|
break;
|
||||||
|
symbol_type *func = mFrames[stackLevel].function;
|
||||||
|
uint32_t methodAddr = func->region->base_addr + func->addr;
|
||||||
|
if (methodAddr == addr) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Action action = NONE;
|
// If we found a matching frame then pop the stack up to and including
|
||||||
uint32_t prevAddr = mPrevFunction->addr + mPrevFunction->region->base_addr;
|
// that frame.
|
||||||
uint32_t addr = function->addr + function->region->base_addr;
|
if (stackLevel >= 0) {
|
||||||
|
// Pop the stack frames
|
||||||
|
for (int ii = mTop - 1; ii >= stackLevel; --ii)
|
||||||
|
doSimplePop(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class FRAME, class BASE>
|
||||||
|
void CallStack<FRAME, BASE>::doMethodAction(BBEvent *event, symbol_type *function)
|
||||||
|
{
|
||||||
// If the events get ahead of the method trace, then read ahead until we
|
// 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
|
// 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.
|
// method trace for which we don't have a previous push. Such an unmatched
|
||||||
|
// pop can happen because the user can start tracing at any time and so
|
||||||
|
// there might already be a stack when we start tracing.
|
||||||
while (event->time >= sNextMethod.time) {
|
while (event->time >= sNextMethod.time) {
|
||||||
sCurrentMethod = sNextMethod;
|
sCurrentMethod = sNextMethod;
|
||||||
if (mTrace->ReadMethod(&sNextMethod)) {
|
if (mTrace->ReadMethod(&sNextMethod)) {
|
||||||
@@ -693,61 +734,28 @@ CallStack<FRAME, BASE>::getMethodAction(BBEvent *event, symbol_type *function)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event->time >= sCurrentMethod.time && event->pid == sCurrentMethod.pid) {
|
if (event->time >= sCurrentMethod.time && event->pid == sCurrentMethod.pid) {
|
||||||
if (addr == sCurrentMethod.addr || prevAddr == sCurrentMethod.addr) {
|
uint64_t time = event->time - mSkippedTime;
|
||||||
action = (sCurrentMethod.flags == 0) ? PUSH : POP;
|
int flags = sCurrentMethod.flags;
|
||||||
// We found a match, so read the next record.
|
if (flags == kMethodEnter) {
|
||||||
|
doSimplePush(function, 0, time, FRAME::kInterpreted);
|
||||||
|
mAllowNativeFrames = false;
|
||||||
|
} else if (flags == kNativeEnter) {
|
||||||
|
doSimplePush(function, 0, time, FRAME::kStartNative);
|
||||||
|
mAllowNativeFrames = true;
|
||||||
|
} else if (flags == kMethodExit || flags == kMethodException) {
|
||||||
|
doMethodPop(event, sCurrentMethod.addr, FRAME::kInterpreted);
|
||||||
|
} else if (flags == kNativeExit || flags == kNativeException) {
|
||||||
|
doMethodPop(event, sCurrentMethod.addr, FRAME::kStartNative);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We found a match, so read the next record. When we get to the end
|
||||||
|
// of the trace, we set the time to the maximum value (~0).
|
||||||
sCurrentMethod = sNextMethod;
|
sCurrentMethod = sNextMethod;
|
||||||
if (sNextMethod.time != ~0ull && mTrace->ReadMethod(&sNextMethod)) {
|
if (sNextMethod.time != ~0ull && mTrace->ReadMethod(&sNextMethod)) {
|
||||||
sNextMethod.time = ~0ull;
|
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>
|
template<class FRAME, class BASE>
|
||||||
void CallStack<FRAME, BASE>::showStack(FILE *stream)
|
void CallStack<FRAME, BASE>::showStack(FILE *stream)
|
||||||
@@ -764,16 +772,4 @@ void CallStack<FRAME, BASE>::showStack(FILE *stream)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 */
|
#endif /* CALL_STACK_H */
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class HashTable {
|
|||||||
typedef T value_type;
|
typedef T value_type;
|
||||||
|
|
||||||
void Update(const char *key, T value);
|
void Update(const char *key, T value);
|
||||||
|
bool Remove(const char *key);
|
||||||
T Find(const char *key);
|
T Find(const char *key);
|
||||||
entry_type* GetFirst();
|
entry_type* GetFirst();
|
||||||
entry_type* GetNext();
|
entry_type* GetNext();
|
||||||
@@ -120,6 +121,31 @@ void HashTable<T>::Update(const char *key, T value)
|
|||||||
num_entries_ += 1;
|
num_entries_ += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
bool HashTable<T>::Remove(const char *key)
|
||||||
|
{
|
||||||
|
// Hash the key to get the table position
|
||||||
|
int len = strlen(key);
|
||||||
|
int pos = HashFunction(key) & mask_;
|
||||||
|
|
||||||
|
// Search the chain for a matching key and keep track of the previous
|
||||||
|
// element in the chain.
|
||||||
|
entry_type *prev = NULL;
|
||||||
|
for (entry_type *ptr = table_[pos]; ptr; prev = ptr, ptr = ptr->next) {
|
||||||
|
if (strcmp(ptr->key, key) == 0) {
|
||||||
|
if (prev == NULL) {
|
||||||
|
table_[pos] = ptr->next;
|
||||||
|
} else {
|
||||||
|
prev->next = ptr->next;
|
||||||
|
}
|
||||||
|
delete ptr->key;
|
||||||
|
delete ptr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
typename HashTable<T>::value_type HashTable<T>::Find(const char *key)
|
typename HashTable<T>::value_type HashTable<T>::Find(const char *key)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -62,6 +62,19 @@ class TraceReader : public TraceReaderBase {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
region_entry *MakePrivateCopy(region_entry *dest) {
|
||||||
|
dest->refs = 0;
|
||||||
|
dest->path = Strdup(path);
|
||||||
|
dest->vstart = vstart;
|
||||||
|
dest->vend = vend;
|
||||||
|
dest->base_addr = base_addr;
|
||||||
|
dest->file_offset = file_offset;
|
||||||
|
dest->flags = flags;
|
||||||
|
dest->nsymbols = nsymbols;
|
||||||
|
dest->symbols = symbols;
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
int refs; // reference count
|
int refs; // reference count
|
||||||
char *path;
|
char *path;
|
||||||
uint32_t vstart;
|
uint32_t vstart;
|
||||||
@@ -100,6 +113,11 @@ class TraceReader : public TraceReaderBase {
|
|||||||
static const int kHasKernelRegion = 0x08;
|
static const int kHasKernelRegion = 0x08;
|
||||||
static const int kHasFirstMmap = 0x10;
|
static const int kHasFirstMmap = 0x10;
|
||||||
|
|
||||||
|
struct methodFrame {
|
||||||
|
uint32_t addr;
|
||||||
|
bool isNative;
|
||||||
|
};
|
||||||
|
|
||||||
ProcessState() {
|
ProcessState() {
|
||||||
cpu_time = 0;
|
cpu_time = 0;
|
||||||
tgid = 0;
|
tgid = 0;
|
||||||
@@ -153,7 +171,7 @@ class TraceReader : public TraceReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dumps the stack contents to standard output. For debugging.
|
// Dumps the stack contents to standard output. For debugging.
|
||||||
void DumpStack();
|
void DumpStack(FILE *stream);
|
||||||
|
|
||||||
uint64_t cpu_time;
|
uint64_t cpu_time;
|
||||||
uint64_t start_time;
|
uint64_t start_time;
|
||||||
@@ -173,7 +191,7 @@ class TraceReader : public TraceReaderBase {
|
|||||||
ProcessState *addr_manager; // the address space manager process
|
ProcessState *addr_manager; // the address space manager process
|
||||||
ProcessState *next;
|
ProcessState *next;
|
||||||
int method_stack_top;
|
int method_stack_top;
|
||||||
uint32_t method_stack[kMaxMethodStackSize];
|
methodFrame method_stack[kMaxMethodStackSize];
|
||||||
symbol_type *current_method_sym;
|
symbol_type *current_method_sym;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -184,6 +202,7 @@ class TraceReader : public TraceReaderBase {
|
|||||||
void CopyKernelRegion(ProcessState *pstate);
|
void CopyKernelRegion(ProcessState *pstate);
|
||||||
void ClearRegions(ProcessState *pstate);
|
void ClearRegions(ProcessState *pstate);
|
||||||
void CopyRegions(ProcessState *parent, ProcessState *child);
|
void CopyRegions(ProcessState *parent, ProcessState *child);
|
||||||
|
void DumpRegions(FILE *stream, ProcessState *pstate);
|
||||||
symbol_type *LookupFunction(int pid, uint32_t addr, uint64_t time);
|
symbol_type *LookupFunction(int pid, uint32_t addr, uint64_t time);
|
||||||
symbol_type *GetSymbols(int *num_syms);
|
symbol_type *GetSymbols(int *num_syms);
|
||||||
ProcessState *GetCurrentProcess() { return current_; }
|
ProcessState *GetCurrentProcess() { return current_; }
|
||||||
@@ -217,6 +236,10 @@ class TraceReader : public TraceReaderBase {
|
|||||||
void AddRegion(ProcessState *pstate, region_type *region);
|
void AddRegion(ProcessState *pstate, region_type *region);
|
||||||
region_type *FindRegion(uint32_t addr, int nregions,
|
region_type *FindRegion(uint32_t addr, int nregions,
|
||||||
region_type **regions);
|
region_type **regions);
|
||||||
|
int FindRegionIndex(uint32_t addr, int nregions,
|
||||||
|
region_type **regions);
|
||||||
|
void FindAndRemoveRegion(ProcessState *pstate,
|
||||||
|
uint32_t vstart, uint32_t vend);
|
||||||
symbol_type *FindFunction(uint32_t addr, int nsyms,
|
symbol_type *FindFunction(uint32_t addr, int nsyms,
|
||||||
symbol_type *symbols, bool exact_match);
|
symbol_type *symbols, bool exact_match);
|
||||||
symbol_type *FindCurrentMethod(int pid, uint64_t time);
|
symbol_type *FindCurrentMethod(int pid, uint64_t time);
|
||||||
@@ -925,6 +948,63 @@ void TraceReader<T>::AddRegion(ProcessState *pstate, region_type *region)
|
|||||||
qsort(manager->regions, nregions, sizeof(region_type*), cmp_region_addr<T>);
|
qsort(manager->regions, nregions, sizeof(region_type*), cmp_region_addr<T>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void TraceReader<T>::FindAndRemoveRegion(ProcessState *pstate, uint32_t vstart,
|
||||||
|
uint32_t vend)
|
||||||
|
{
|
||||||
|
ProcessState *manager = pstate->addr_manager;
|
||||||
|
int nregions = manager->nregions;
|
||||||
|
int index = FindRegionIndex(vstart, nregions, manager->regions);
|
||||||
|
region_type *region = manager->regions[index];
|
||||||
|
|
||||||
|
// If the region does not contain [vstart,vend], then return.
|
||||||
|
if (vstart < region->vstart || vend > region->vend)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If the existing region exactly matches the address range [vstart,vend]
|
||||||
|
// then remove the whole region.
|
||||||
|
if (vstart == region->vstart && vend == region->vend) {
|
||||||
|
// The regions are reference-counted.
|
||||||
|
if (region->refs == 0) {
|
||||||
|
// Free the region
|
||||||
|
hash_->Remove(region->path);
|
||||||
|
delete region;
|
||||||
|
} else {
|
||||||
|
region->refs -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nregions > 1) {
|
||||||
|
// Assign the region at the end of the array to this empty slot
|
||||||
|
manager->regions[index] = manager->regions[nregions - 1];
|
||||||
|
|
||||||
|
// Resort the regions into increasing start address
|
||||||
|
qsort(manager->regions, nregions - 1, sizeof(region_type*),
|
||||||
|
cmp_region_addr<T>);
|
||||||
|
}
|
||||||
|
manager->nregions = nregions - 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the existing region contains the given range and ends at the
|
||||||
|
// end of the given range (a common case for some reason), then
|
||||||
|
// truncate the existing region so that it ends at vstart (because
|
||||||
|
// we are deleting the range [vstart,vend]).
|
||||||
|
if (vstart > region->vstart && vend == region->vend) {
|
||||||
|
region_type *truncated;
|
||||||
|
|
||||||
|
if (region->refs == 0) {
|
||||||
|
// This region is not shared, so truncate it directly
|
||||||
|
truncated = region;
|
||||||
|
} else {
|
||||||
|
// This region is shared, so make a copy that we can truncate
|
||||||
|
region->refs -= 1;
|
||||||
|
truncated = region->MakePrivateCopy(new region_type);
|
||||||
|
}
|
||||||
|
truncated->vend = vstart;
|
||||||
|
manager->regions[index] = truncated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
void TraceReader<T>::CopyRegions(ProcessState *parent, ProcessState *child)
|
void TraceReader<T>::CopyRegions(ProcessState *parent, ProcessState *child)
|
||||||
{
|
{
|
||||||
@@ -943,6 +1023,20 @@ void TraceReader<T>::CopyRegions(ProcessState *parent, ProcessState *child)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void TraceReader<T>::DumpRegions(FILE *stream, ProcessState *pstate) {
|
||||||
|
ProcessState *manager = pstate->addr_manager;
|
||||||
|
for (int ii = 0; ii < manager->nregions; ++ii) {
|
||||||
|
fprintf(stream, " %08x - %08x offset: %5x nsyms: %4d refs: %d %s\n",
|
||||||
|
manager->regions[ii]->vstart,
|
||||||
|
manager->regions[ii]->vend,
|
||||||
|
manager->regions[ii]->file_offset,
|
||||||
|
manager->regions[ii]->nsymbols,
|
||||||
|
manager->regions[ii]->refs,
|
||||||
|
manager->regions[ii]->path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
typename TraceReader<T>::region_type *
|
typename TraceReader<T>::region_type *
|
||||||
TraceReader<T>::FindRegion(uint32_t addr, int nregions, region_type **regions)
|
TraceReader<T>::FindRegion(uint32_t addr, int nregions, region_type **regions)
|
||||||
@@ -967,6 +1061,30 @@ TraceReader<T>::FindRegion(uint32_t addr, int nregions, region_type **regions)
|
|||||||
return regions[low];
|
return regions[low];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
int TraceReader<T>::FindRegionIndex(uint32_t addr, int nregions,
|
||||||
|
region_type **regions)
|
||||||
|
{
|
||||||
|
int high = nregions;
|
||||||
|
int low = -1;
|
||||||
|
while (low + 1 < high) {
|
||||||
|
int middle = (high + low) / 2;
|
||||||
|
uint32_t middle_addr = regions[middle]->vstart;
|
||||||
|
if (middle_addr == addr)
|
||||||
|
return middle;
|
||||||
|
if (middle_addr > addr)
|
||||||
|
high = middle;
|
||||||
|
else
|
||||||
|
low = middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here then we did not find an exact address match. So use
|
||||||
|
// the closest region address that is less than the given address.
|
||||||
|
if (low < 0)
|
||||||
|
low = 0;
|
||||||
|
return low;
|
||||||
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
typename TraceReader<T>::symbol_type *
|
typename TraceReader<T>::symbol_type *
|
||||||
TraceReader<T>::FindFunction(uint32_t addr, int nsyms, symbol_type *symbols,
|
TraceReader<T>::FindFunction(uint32_t addr, int nsyms, symbol_type *symbols,
|
||||||
@@ -1007,16 +1125,13 @@ TraceReader<T>::LookupFunction(int pid, uint32_t addr, uint64_t time)
|
|||||||
uint32_t sym_addr = addr - cached_func_->region->base_addr;
|
uint32_t sym_addr = addr - cached_func_->region->base_addr;
|
||||||
if (sym_addr >= cached_func_->addr
|
if (sym_addr >= cached_func_->addr
|
||||||
&& sym_addr < (cached_func_ + 1)->addr) {
|
&& sym_addr < (cached_func_ + 1)->addr) {
|
||||||
// If this function is the virtual machine interpreter, then
|
|
||||||
// read the method trace to find the "real" method name based
|
// Check if there is a Java method on the method trace.
|
||||||
// on the current time and pid.
|
|
||||||
if (cached_func_->flags & symbol_type::kIsInterpreter) {
|
|
||||||
symbol_type *sym = FindCurrentMethod(pid, time);
|
symbol_type *sym = FindCurrentMethod(pid, time);
|
||||||
if (sym != NULL) {
|
if (sym != NULL) {
|
||||||
sym->vm_sym = cached_func_;
|
sym->vm_sym = cached_func_;
|
||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return cached_func_;
|
return cached_func_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1040,17 +1155,13 @@ TraceReader<T>::LookupFunction(int pid, uint32_t addr, uint64_t time)
|
|||||||
if (cached_func_ != NULL) {
|
if (cached_func_ != NULL) {
|
||||||
cached_func_->region = region;
|
cached_func_->region = region;
|
||||||
|
|
||||||
// If this function is the virtual machine interpreter, then
|
// Check if there is a Java method on the method trace.
|
||||||
// read the method trace to find the "real" method name based
|
|
||||||
// on the current time and pid.
|
|
||||||
if (cached_func_->flags & symbol_type::kIsInterpreter) {
|
|
||||||
symbol_type *sym = FindCurrentMethod(pid, time);
|
symbol_type *sym = FindCurrentMethod(pid, time);
|
||||||
if (sym != NULL) {
|
if (sym != NULL) {
|
||||||
sym->vm_sym = cached_func_;
|
sym->vm_sym = cached_func_;
|
||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return cached_func_;
|
return cached_func_;
|
||||||
}
|
}
|
||||||
@@ -1142,11 +1253,17 @@ void TraceReader<T>::HandlePidEvent(PidEvent *event)
|
|||||||
current_->exit_val = event->pid;
|
current_->exit_val = event->pid;
|
||||||
current_->flags |= ProcessState::kCalledExit;
|
current_->flags |= ProcessState::kCalledExit;
|
||||||
break;
|
break;
|
||||||
|
case kPidMunmap:
|
||||||
|
FindAndRemoveRegion(current_, event->vstart, event->vend);
|
||||||
|
break;
|
||||||
case kPidMmap:
|
case kPidMmap:
|
||||||
{
|
{
|
||||||
region_type *region;
|
region_type *region;
|
||||||
region_type *existing_region = hash_->Find(event->path);
|
region_type *existing_region = hash_->Find(event->path);
|
||||||
if (existing_region == NULL || existing_region->vstart != event->vstart) {
|
if (existing_region == NULL
|
||||||
|
|| existing_region->vstart != event->vstart
|
||||||
|
|| existing_region->vend != event->vend
|
||||||
|
|| existing_region->file_offset != event->offset) {
|
||||||
// Create a new region and add it to the current process'
|
// Create a new region and add it to the current process'
|
||||||
// address space.
|
// address space.
|
||||||
region = new region_type;
|
region = new region_type;
|
||||||
@@ -1264,10 +1381,12 @@ int TraceReader<T>::FindCurrentPid(uint64_t time)
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
void TraceReader<T>::ProcessState::DumpStack()
|
void TraceReader<T>::ProcessState::DumpStack(FILE *stream)
|
||||||
{
|
{
|
||||||
|
const char *native;
|
||||||
for (int ii = 0; ii < method_stack_top; ii++) {
|
for (int ii = 0; ii < method_stack_top; ii++) {
|
||||||
printf("%2d: 0x%08x\n", ii, method_stack[ii]);
|
native = method_stack[ii].isNative ? "n" : " ";
|
||||||
|
fprintf(stream, "%2d: %s 0x%08x\n", ii, native, method_stack[ii].addr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1277,13 +1396,17 @@ void TraceReader<T>::HandleMethodRecord(ProcessState *pstate,
|
|||||||
{
|
{
|
||||||
uint32_t addr;
|
uint32_t addr;
|
||||||
int top = pstate->method_stack_top;
|
int top = pstate->method_stack_top;
|
||||||
if (method_rec->flags == kMethodEnter) {
|
int flags = method_rec->flags;
|
||||||
|
bool isNative;
|
||||||
|
if (flags == kMethodEnter || flags == kNativeEnter) {
|
||||||
// Push this method on the stack
|
// Push this method on the stack
|
||||||
if (top >= pstate->kMaxMethodStackSize) {
|
if (top >= pstate->kMaxMethodStackSize) {
|
||||||
fprintf(stderr, "Stack overflow at time %llu\n", method_rec->time);
|
fprintf(stderr, "Stack overflow at time %llu\n", method_rec->time);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
pstate->method_stack[top] = method_rec->addr;
|
pstate->method_stack[top].addr = method_rec->addr;
|
||||||
|
isNative = (flags == kNativeEnter);
|
||||||
|
pstate->method_stack[top].isNative = isNative;
|
||||||
pstate->method_stack_top = top + 1;
|
pstate->method_stack_top = top + 1;
|
||||||
addr = method_rec->addr;
|
addr = method_rec->addr;
|
||||||
} else {
|
} else {
|
||||||
@@ -1293,14 +1416,27 @@ void TraceReader<T>::HandleMethodRecord(ProcessState *pstate,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
top -= 1;
|
top -= 1;
|
||||||
addr = pstate->method_stack[top];
|
addr = pstate->method_stack[top].addr;
|
||||||
if (addr != method_rec->addr) {
|
|
||||||
|
// If this is a non-native method then the address we are popping should
|
||||||
|
// match the top-of-stack address. Native pops don't always match the
|
||||||
|
// address of the native push for some reason.
|
||||||
|
if (addr != method_rec->addr && !pstate->method_stack[top].isNative) {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Stack method (0x%x) at index %d does not match trace record (0x%x) at time %llu\n",
|
"Stack method (0x%x) at index %d does not match trace record (0x%x) at time %llu\n",
|
||||||
addr, top, method_rec->addr, method_rec->time);
|
addr, top, method_rec->addr, method_rec->time);
|
||||||
for (int ii = 0; ii <= top; ii++) {
|
pstate->DumpStack(stderr);
|
||||||
fprintf(stderr, " %d: 0x%x\n", ii, pstate->method_stack[ii]);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we are popping a native method, then the top-of-stack should also
|
||||||
|
// be a native method.
|
||||||
|
bool poppingNative = (flags == kNativeExit) || (flags == kNativeException);
|
||||||
|
if (poppingNative != pstate->method_stack[top].isNative) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"Popping native vs. non-native mismatch at index %d time %llu\n",
|
||||||
|
top, method_rec->time);
|
||||||
|
pstate->DumpStack(stderr);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1310,8 +1446,17 @@ void TraceReader<T>::HandleMethodRecord(ProcessState *pstate,
|
|||||||
pstate->current_method_sym = NULL;
|
pstate->current_method_sym = NULL;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
addr = pstate->method_stack[top - 1];
|
addr = pstate->method_stack[top - 1].addr;
|
||||||
|
isNative = pstate->method_stack[top - 1].isNative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the top-of-stack is a native method, then set the current method
|
||||||
|
// to NULL.
|
||||||
|
if (isNative) {
|
||||||
|
pstate->current_method_sym = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ProcessState *manager = pstate->addr_manager;
|
ProcessState *manager = pstate->addr_manager;
|
||||||
region_type *region = FindRegion(addr, manager->nregions, manager->regions);
|
region_type *region = FindRegion(addr, manager->nregions, manager->regions);
|
||||||
uint32_t sym_addr = addr - region->base_addr;
|
uint32_t sym_addr = addr - region->base_addr;
|
||||||
@@ -1324,6 +1469,11 @@ void TraceReader<T>::HandleMethodRecord(ProcessState *pstate,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the current top-of-stack Java method, if any, for the given pid
|
||||||
|
// at the given time. The "time" parameter must be monotonically increasing
|
||||||
|
// across successive calls to this method.
|
||||||
|
// If the Java method stack is empty or if a native JNI method is on the
|
||||||
|
// top of the stack, then this method returns NULL.
|
||||||
template <class T>
|
template <class T>
|
||||||
typename TraceReader<T>::symbol_type*
|
typename TraceReader<T>::symbol_type*
|
||||||
TraceReader<T>::FindCurrentMethod(int pid, uint64_t time)
|
TraceReader<T>::FindCurrentMethod(int pid, uint64_t time)
|
||||||
|
|||||||
Reference in New Issue
Block a user