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/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)