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.
diff --git a/emulator/qtools/callstack.h b/emulator/qtools/callstack.h
index b73efea..8982330 100644
--- a/emulator/qtools/callstack.h
+++ b/emulator/qtools/callstack.h
@@ -32,7 +32,9 @@
     typedef SYM symbol_type;
     static const uint32_t kCausedException = 0x01;
     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
     uint32_t    addr;           // return address when this function returns
@@ -43,7 +45,7 @@
 
 template <class FRAME, class BASE = CallStackBase>
 class CallStack : public BASE {
-  public:
+public:
     typedef FRAME frame_type;
     typedef typename FRAME::symbol_type symbol_type;
     typedef typename FRAME::symbol_type::region_type region_type;
@@ -57,7 +59,7 @@
     void    threadStart(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) {
         mNativeOnly = nativeOnly;
     }
@@ -66,38 +68,36 @@
 
     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 };
+private:
+    enum Action { NONE, PUSH, POP, NATIVE_PUSH };
 
     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,
-                             uint64_t time);
+                             uint64_t time, int flags);
     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;
+
+    // This is a global switch that disables Java methods from appearing
+    // on the stack.
     bool        mNativeOnly;
+  
+    // This keeps track of whether native frames are currently allowed on the
+    // stack.
+    bool        mAllowNativeFrames;
 
     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;
 
@@ -125,10 +125,7 @@
     mNumFrames = numFrames;
     mFrames = new FRAME[mNumFrames];
     mTop = 0;
-
-    mSnapshotNumFrames = numFrames;
-    mSnapshotFrames = new FRAME[mSnapshotNumFrames];
-    mSnapshotTop = 0;
+    mAllowNativeFrames = true;
 
     memset(&mDummyFunction, 0, sizeof(symbol_type));
     memset(&mDummyRegion, 0, sizeof(region_type));
@@ -139,7 +136,6 @@
     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.
@@ -169,11 +165,29 @@
         // instead.
         if (function->vm_sym != NULL)
             function = function->vm_sym;
+    } else {
+        doMethodAction(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.
     if (action == POP && mPrevFunction->vm_sym == NULL) {
         // Pop off the previous function first.
@@ -198,11 +212,16 @@
             doPush(event, function);
         }
     }
+#endif
 
     // If the stack is now empty, then push the current function.
     if (mTop == 0) {
         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;
@@ -465,12 +484,19 @@
     if ((function->flags & symbol_type::kIsVectorStart) && mTop > 0)
         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>
-void CallStack<FRAME, BASE>::doSimplePush(symbol_type *function,
-                                          uint32_t addr, uint64_t time)
+void CallStack<FRAME, BASE>::doSimplePush(symbol_type *function, uint32_t addr,
+                                          uint64_t time, int flags)
 {
     // Check for stack overflow
     if (mTop >= mNumFrames) {
@@ -479,30 +505,12 @@
         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].flags = flags;
     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;
 }
@@ -517,17 +525,25 @@
     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;
+    if (mNativeOnly)
+        return;
 
-        // 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);
+    // If the stack is empty, then allow more native frames.
+    // Otherwise, if we are transitioning from Java to native, then allow
+    // more native frames.
+    // Otherwise, if we are transitioning from native to Java, then disallow
+    // more native frames.
+    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 @@
 }
 
 template<class FRAME, class BASE>
-typename CallStack<FRAME, BASE>::Action
-CallStack<FRAME, BASE>::getMethodAction(BBEvent *event, symbol_type *function)
+void CallStack<FRAME, BASE>::doMethodPop(BBEvent *event, uint32_t addr,
+                                         const uint32_t flags)
 {
-    if (function->vm_sym == NULL && mPrevFunction->vm_sym == NULL) {
-        return NONE;
+    uint64_t time = event->time - mSkippedTime;
+
+    // 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;
-    uint32_t prevAddr = mPrevFunction->addr + mPrevFunction->region->base_addr;
-    uint32_t addr = function->addr + function->region->base_addr;
+    // If we found a matching frame then pop the stack up to and including
+    // that frame.
+    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
     // 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) {
         sCurrentMethod = sNextMethod;
         if (mTrace->ReadMethod(&sNextMethod)) {
@@ -693,59 +734,26 @@
     }
 
     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;
+        uint64_t time = event->time - mSkippedTime;
+        int flags = sCurrentMethod.flags;
+        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);
         }
 
-        // 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);
+        // 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;
+        if (sNextMethod.time != ~0ull && mTrace->ReadMethod(&sNextMethod)) {
+            sNextMethod.time = ~0ull;
         }
-
-        // Push the new frames from the native stack
-        for (int jj = ii; jj < mTop; ++jj) {
-            mFrames[jj].push(jj, time, this);
-        }
-        break;
     }
 }
 
@@ -764,16 +772,4 @@
     }
 }
 
-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 */
diff --git a/emulator/qtools/hash_table.h b/emulator/qtools/hash_table.h
index 45786ec..4ea9ed5 100644
--- a/emulator/qtools/hash_table.h
+++ b/emulator/qtools/hash_table.h
@@ -21,6 +21,7 @@
     typedef T value_type;
 
     void         Update(const char *key, T value);
+    bool         Remove(const char *key);
     T            Find(const char *key);
     entry_type*  GetFirst();
     entry_type*  GetNext();
@@ -121,6 +122,31 @@
 }
 
 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>
 typename HashTable<T>::value_type HashTable<T>::Find(const char *key)
 {
     // Hash the key to get the table position
diff --git a/emulator/qtools/trace_reader.h b/emulator/qtools/trace_reader.h
index f73f17a..b91cb1b 100644
--- a/emulator/qtools/trace_reader.h
+++ b/emulator/qtools/trace_reader.h
@@ -62,6 +62,19 @@
             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
         char            *path;
         uint32_t        vstart;
@@ -100,6 +113,11 @@
         static const int kHasKernelRegion       = 0x08;
         static const int kHasFirstMmap          = 0x10;
 
+        struct methodFrame {
+            uint32_t    addr;
+            bool        isNative;
+        };
+
         ProcessState() {
             cpu_time = 0;
             tgid = 0;
@@ -153,7 +171,7 @@
         }
 
         // Dumps the stack contents to standard output.  For debugging.
-        void            DumpStack();
+        void            DumpStack(FILE *stream);
 
         uint64_t        cpu_time;
         uint64_t        start_time;
@@ -173,7 +191,7 @@
         ProcessState    *addr_manager;   // the address space manager process
         ProcessState    *next;
         int             method_stack_top;
-        uint32_t        method_stack[kMaxMethodStackSize];
+        methodFrame     method_stack[kMaxMethodStackSize];
         symbol_type     *current_method_sym;
     };
 
@@ -184,6 +202,7 @@
     void                CopyKernelRegion(ProcessState *pstate);
     void                ClearRegions(ProcessState *pstate);
     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         *GetSymbols(int *num_syms);
     ProcessState        *GetCurrentProcess()            { return current_; }
@@ -217,6 +236,10 @@
     void                AddRegion(ProcessState *pstate, region_type *region);
     region_type         *FindRegion(uint32_t addr, int nregions,
                                     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 *symbols, bool exact_match);
     symbol_type         *FindCurrentMethod(int pid, uint64_t time);
@@ -926,6 +949,63 @@
 }
 
 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>
 void TraceReader<T>::CopyRegions(ProcessState *parent, ProcessState *child)
 {
     // Copy the parent's address space
@@ -944,6 +1024,20 @@
 }
 
 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>
 typename TraceReader<T>::region_type *
 TraceReader<T>::FindRegion(uint32_t addr, int nregions, region_type **regions)
 {
@@ -968,6 +1062,30 @@
 }
 
 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>
 typename TraceReader<T>::symbol_type *
 TraceReader<T>::FindFunction(uint32_t addr, int nsyms, symbol_type *symbols,
                              bool exact_match)
@@ -1007,15 +1125,12 @@
             uint32_t sym_addr = addr - cached_func_->region->base_addr;
             if (sym_addr >= cached_func_->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
-                // on the current time and pid.
-                if (cached_func_->flags & symbol_type::kIsInterpreter) {
-                    symbol_type *sym = FindCurrentMethod(pid, time);
-                    if (sym != NULL) {
-                        sym->vm_sym = cached_func_;
-                        return sym;
-                    }
+
+                // Check if there is a Java method on the method trace.
+                symbol_type *sym = FindCurrentMethod(pid, time);
+                if (sym != NULL) {
+                    sym->vm_sym = cached_func_;
+                    return sym;
                 }
                 return cached_func_;
             }
@@ -1040,15 +1155,11 @@
     if (cached_func_ != NULL) {
         cached_func_->region = region;
 
-        // If this function is the virtual machine interpreter, then
-        // 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);
-            if (sym != NULL) {
-                sym->vm_sym = cached_func_;
-                return sym;
-            }
+        // Check if there is a Java method on the method trace.
+        symbol_type *sym = FindCurrentMethod(pid, time);
+        if (sym != NULL) {
+            sym->vm_sym = cached_func_;
+            return sym;
         }
     }
 
@@ -1142,11 +1253,17 @@
         current_->exit_val = event->pid;
         current_->flags |= ProcessState::kCalledExit;
         break;
+    case kPidMunmap:
+        FindAndRemoveRegion(current_, event->vstart, event->vend);
+        break;
     case kPidMmap:
         {
             region_type *region;
             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'
                 // address space.
                 region = new region_type;
@@ -1264,10 +1381,12 @@
 }
 
 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++) {
-        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 @@
 {
     uint32_t addr;
     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
         if (top >= pstate->kMaxMethodStackSize) {
             fprintf(stderr, "Stack overflow at time %llu\n", method_rec->time);
             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;
         addr = method_rec->addr;
     } else {
@@ -1293,14 +1416,27 @@
             return;
         }
         top -= 1;
-        addr = pstate->method_stack[top];
-        if (addr != method_rec->addr) {
+        addr = pstate->method_stack[top].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,
                     "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);
-            for (int ii = 0; ii <= top; ii++) {
-                fprintf(stderr, "  %d: 0x%x\n", ii, pstate->method_stack[ii]);
-            }
+            pstate->DumpStack(stderr);
+            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);
         }
 
@@ -1310,8 +1446,17 @@
             pstate->current_method_sym = NULL;
             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;
     region_type *region = FindRegion(addr, manager->nregions, manager->regions);
     uint32_t sym_addr = addr - region->base_addr;
@@ -1324,6 +1469,11 @@
     }
 }
 
+// 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>
 typename TraceReader<T>::symbol_type*
 TraceReader<T>::FindCurrentMethod(int pid, uint64_t time)