Files
android_development/emulator/qtools/trace_reader.h
Jack Veenstra 2bb9bb4546 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.
2009-05-19 15:07:29 -07:00

1560 lines
54 KiB
C++

// Copyright 2006 The Android Open Source Project
#ifndef TRACE_READER_H
#define TRACE_READER_H
#include <string.h>
#include <inttypes.h>
#include <elf.h>
#include <assert.h>
#include <cxxabi.h>
#include "read_elf.h"
#include "trace_reader_base.h"
#include "hash_table.h"
struct TraceReaderEmptyStruct {
};
template <class T = TraceReaderEmptyStruct>
class TraceReader : public TraceReaderBase {
public:
struct region_entry;
typedef struct symbol_entry : public T {
typedef region_entry region_type;
// Define flag values
static const uint32_t kIsPlt = 0x01;
static const uint32_t kIsVectorStart = 0x02;
static const uint32_t kIsVectorTable = (kIsPlt | kIsVectorStart);
static const uint32_t kIsInterpreter = 0x04;
static const uint32_t kIsMethod = 0x08;
uint32_t addr;
// This may hold the name of the interpreted method instead of
// the name of the native function if the native function is a
// virtual machine interpreter.
const char *name;
// The symbol for the virtual machine interpreter, or NULL
symbol_entry *vm_sym;
region_type *region;
uint32_t flags;
} symbol_type;
typedef struct region_entry {
// Define flag values
static const uint32_t kIsKernelRegion = 0x01;
static const uint32_t kSharedSymbols = 0x02;
static const uint32_t kIsLibraryRegion = 0x04;
static const uint32_t kIsUserMappedRegion = 0x08;
region_entry() : refs(0), path(NULL), vstart(0), vend(0), base_addr(0),
file_offset(0), flags(0), nsymbols(0), symbols(NULL) {}
symbol_type *LookupFunctionByName(char *name) {
// Just do a linear search
for (int ii = 0; ii < nsymbols; ++ii) {
if (strcmp(symbols[ii].name, name) == 0)
return &symbols[ii];
}
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;
uint32_t vend;
uint32_t base_addr;
uint32_t file_offset;
uint32_t flags;
int nsymbols;
symbol_type *symbols;
} region_type;
typedef typename HashTable<region_type*>::entry_type hash_entry_type;
class ProcessState {
public:
// The "regions" array below is a pointer to array of pointers to
// regions. The size of the pointer array is kInitialNumRegions,
// but grows if needed. There is a separate region for each mmap
// call which includes shared libraries as well as .dex and .jar
// files. In addition, there is a region for the main executable
// for this process, as well as a few regions for the kernel.
//
// If a child process is a clone of a parent process, the
// regions array is unused. Instead, the "addr_manager" pointer is
// used to find the process that is the address space manager for
// both the parent and child processes.
static const int kInitialNumRegions = 10;
static const int kMaxMethodStackSize = 1000;
// Define values for the ProcessState flag bits
static const int kCalledExec = 0x01;
static const int kCalledExit = 0x02;
static const int kIsClone = 0x04;
static const int kHasKernelRegion = 0x08;
static const int kHasFirstMmap = 0x10;
struct methodFrame {
uint32_t addr;
bool isNative;
};
ProcessState() {
cpu_time = 0;
tgid = 0;
pid = 0;
parent_pid = 0;
exit_val = 0;
flags = 0;
argc = 0;
argv = NULL;
name = NULL;
nregions = 0;
max_regions = 0;
// Don't allocate space yet until we know if we are a clone.
regions = NULL;
parent = NULL;
addr_manager = this;
next = NULL;
current_method_sym = NULL;
method_stack_top = 0;
}
~ProcessState() {
delete[] name;
if ((flags & kIsClone) != 0) {
return;
}
// Free the regions. We must be careful not to free the symbols
// within each region because the symbols are sometimes shared
// between multiple regions. The TraceReader class has a hash
// table containing all the unique regions and it will free the
// region symbols in its destructor. We need to free only the
// regions and the array of region pointers.
//
// Each region is also reference-counted. The count is zero
// if no other processes are sharing this region.
for (int ii = 0; ii < nregions; ii++) {
if (regions[ii]->refs > 0) {
regions[ii]->refs -= 1;
continue;
}
delete regions[ii];
}
delete[] regions;
for (int ii = 0; ii < argc; ++ii)
delete[] argv[ii];
delete[] argv;
}
// Dumps the stack contents to standard output. For debugging.
void DumpStack(FILE *stream);
uint64_t cpu_time;
uint64_t start_time;
uint64_t end_time;
int tgid;
int pid;
int parent_pid;
int exit_val;
uint32_t flags;
int argc;
char **argv;
const char *name;
int nregions; // num regions in use
int max_regions; // max regions allocated
region_type **regions;
ProcessState *parent;
ProcessState *addr_manager; // the address space manager process
ProcessState *next;
int method_stack_top;
methodFrame method_stack[kMaxMethodStackSize];
symbol_type *current_method_sym;
};
TraceReader();
~TraceReader();
void ReadKernelSymbols(const char *kernel_file);
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_; }
ProcessState *GetProcesses(int *num_procs);
ProcessState *GetNextProcess();
const char *GetProcessName(int pid);
void SetRoot(const char *root) { root_ = root; }
void SetDemangle(bool demangle) { demangle_ = demangle; }
bool ReadMethodSymbol(MethodRec *method_record,
symbol_type **psym,
ProcessState **pproc);
protected:
virtual int FindCurrentPid(uint64_t time);
private:
static const int kNumPids = 32768;
static const uint32_t kIncludeLocalSymbols = 0x1;
void AddPredefinedRegion(region_type *region, const char *path,
uint32_t vstart, uint32_t vend,
uint32_t base);
void InitRegionSymbols(region_type *region, int nsymbols);
void AddRegionSymbol(region_type *region, int idx,
uint32_t addr, const char *name,
uint32_t flags);
void AddPredefinedRegions(ProcessState *pstate);
void demangle_names(int nfuncs, symbol_type *functions);
bool ReadElfSymbols(region_type *region, uint32_t flags);
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);
void PopulateSymbolsFromDexFile(const DexFileList *dexfile,
region_type *region);
void HandlePidEvent(PidEvent *event);
void HandleMethodRecord(ProcessState *pstate,
MethodRec *method_rec);
int cached_pid_;
symbol_type *cached_func_;
symbol_type unknown_;
int next_pid_;
PidEvent next_pid_event_;
ProcessState *processes_[kNumPids];
ProcessState *current_;
MethodRec next_method_;
uint64_t function_start_time_;
const char *root_;
HashTable<region_type*> *hash_;
bool demangle_;
};
template<class T>
TraceReader<T>::TraceReader()
{
static PidEvent event_no_action;
cached_pid_ = -1;
cached_func_ = NULL;
memset(&unknown_, 0, sizeof(symbol_type));
unknown_.name = "(unknown)";
next_pid_ = 0;
memset(&event_no_action, 0, sizeof(PidEvent));
event_no_action.rec_type = kPidNoAction;
next_pid_event_ = event_no_action;
for (int ii = 1; ii < kNumPids; ++ii)
processes_[ii] = NULL;
current_ = new ProcessState;
processes_[0] = current_;
next_method_.time = 0;
next_method_.addr = 0;
next_method_.flags = 0;
function_start_time_ = 0;
root_ = "";
hash_ = new HashTable<region_type*>(512);
AddPredefinedRegions(current_);
demangle_ = true;
}
template<class T>
TraceReader<T>::~TraceReader()
{
hash_entry_type *ptr;
for (ptr = hash_->GetFirst(); ptr; ptr = hash_->GetNext()) {
region_type *region = ptr->value;
// If the symbols are not shared with another region, then delete them.
if ((region->flags & region_type::kSharedSymbols) == 0) {
int nsymbols = region->nsymbols;
for (int ii = 0; ii < nsymbols; ii++) {
delete[] region->symbols[ii].name;
}
delete[] region->symbols;
}
delete[] region->path;
// Do not delete the region itself here. Each region
// is reference-counted and deleted by the ProcessState
// object that owns it.
}
delete hash_;
// Delete the ProcessState objects after the region symbols in
// the hash table above so that we still have valid region pointers
// when deleting the region symbols.
for (int ii = 0; ii < kNumPids; ++ii) {
delete processes_[ii];
}
}
// This function is used by the qsort() routine to sort symbols
// into increasing address order.
template<class T>
int cmp_symbol_addr(const void *a, const void *b) {
typedef typename TraceReader<T>::symbol_type stype;
const stype *syma = static_cast<stype const *>(a);
const stype *symb = static_cast<stype const *>(b);
uint32_t addr1 = syma->addr;
uint32_t addr2 = symb->addr;
if (addr1 < addr2)
return -1;
if (addr1 > addr2)
return 1;
// The addresses are the same, sort the symbols into
// increasing alphabetical order. But put symbols that
// that start with "_" last.
if (syma->name[0] == '_' || symb->name[0] == '_') {
// Count the number of leading underscores and sort the
// symbol with the most underscores last.
int aCount = 0;
while (syma->name[aCount] == '_')
aCount += 1;
int bCount = 0;
while (symb->name[bCount] == '_')
bCount += 1;
if (aCount < bCount) {
return -1;
}
if (aCount > bCount) {
return 1;
}
// If the symbols have the same number of underscores, then
// fall through and sort by the whole name.
}
return strcmp(syma->name, symb->name);
}
// This function is used by the qsort() routine to sort region entries
// into increasing address order.
template<class T>
int cmp_region_addr(const void *a, const void *b) {
typedef typename TraceReader<T>::region_type rtype;
const rtype *ma = *static_cast<rtype* const *>(a);
const rtype *mb = *static_cast<rtype* const *>(b);
uint32_t addr1 = ma->vstart;
uint32_t addr2 = mb->vstart;
if (addr1 < addr2)
return -1;
if (addr1 == addr2)
return 0;
return 1;
}
// This routine returns a new array containing all the symbols.
template<class T>
typename TraceReader<T>::symbol_type*
TraceReader<T>::GetSymbols(int *num_syms)
{
// Count the symbols
int nsyms = 0;
for (hash_entry_type *ptr = hash_->GetFirst(); ptr; ptr = hash_->GetNext()) {
region_type *region = ptr->value;
nsyms += region->nsymbols;
}
*num_syms = nsyms;
// Allocate space
symbol_type *syms = new symbol_type[nsyms];
symbol_type *next_sym = syms;
// Copy the symbols
for (hash_entry_type *ptr = hash_->GetFirst(); ptr; ptr = hash_->GetNext()) {
region_type *region = ptr->value;
memcpy(next_sym, region->symbols, region->nsymbols * sizeof(symbol_type));
next_sym += region->nsymbols;
}
return syms;
}
// This routine returns all the valid processes.
template<class T>
typename TraceReader<T>::ProcessState*
TraceReader<T>::GetProcesses(int *num_procs)
{
// Count the valid processes
int nprocs = 0;
for (int ii = 0; ii < kNumPids; ++ii) {
if (processes_[ii])
nprocs += 1;
}
// Allocate a new array to hold the valid processes.
ProcessState *procs = new ProcessState[nprocs];
// Copy the processes to the new array.
ProcessState *pstate = procs;
for (int ii = 0; ii < kNumPids; ++ii) {
if (processes_[ii])
memcpy(pstate++, processes_[ii], sizeof(ProcessState));
}
*num_procs = nprocs;
return procs;
}
// This routine returns the next valid process, or NULL if there are no
// more valid processes.
template<class T>
typename TraceReader<T>::ProcessState*
TraceReader<T>::GetNextProcess()
{
while (next_pid_ < kNumPids) {
if (processes_[next_pid_])
return processes_[next_pid_++];
next_pid_ += 1;
}
next_pid_ = 0;
return NULL;
}
template<class T>
const char* TraceReader<T>::GetProcessName(int pid)
{
if (pid < 0 || pid >= kNumPids || processes_[pid] == NULL)
return "(unknown)";
return processes_[pid]->name;
}
template<class T>
void TraceReader<T>::AddPredefinedRegion(region_type *region, const char *path,
uint32_t vstart, uint32_t vend,
uint32_t base)
{
// Copy the path to make it easy to delete later.
int len = strlen(path);
region->path = new char[len + 1];
strcpy(region->path, path);
region->vstart = vstart;
region->vend = vend;
region->base_addr = base;
region->flags = region_type::kIsKernelRegion;
}
template<class T>
void TraceReader<T>::InitRegionSymbols(region_type *region, int nsymbols)
{
region->nsymbols = nsymbols;
region->symbols = new symbol_type[nsymbols];
memset(region->symbols, 0, nsymbols * sizeof(symbol_type));
}
template<class T>
void TraceReader<T>::AddRegionSymbol(region_type *region, int idx,
uint32_t addr, const char *name,
uint32_t flags)
{
region->symbols[idx].addr = addr;
region->symbols[idx].name = Strdup(name);
region->symbols[idx].vm_sym = NULL;
region->symbols[idx].region = region;
region->symbols[idx].flags = flags;
}
template<class T>
void TraceReader<T>::AddPredefinedRegions(ProcessState *pstate)
{
region_type *region = new region_type;
AddPredefinedRegion(region, "(bootloader)", 0, 0x14, 0);
InitRegionSymbols(region, 2);
AddRegionSymbol(region, 0, 0, "(bootloader_start)", 0);
AddRegionSymbol(region, 1, 0x14, "(bootloader_end)", 0);
AddRegion(pstate, region);
hash_->Update(region->path, region);
region = new region_type;
AddPredefinedRegion(region, "(exception vectors)", 0xffff0000, 0xffff0500,
0xffff0000);
InitRegionSymbols(region, 2);
AddRegionSymbol(region, 0, 0x0, "(vector_start)",
symbol_type::kIsVectorStart);
AddRegionSymbol(region, 1, 0x500, "(vector_end)", 0);
AddRegion(pstate, region);
hash_->Update(region->path, region);
region = new region_type;
AddPredefinedRegion(region, "(atomic ops)", 0xffff0f80, 0xffff1000,
0xffff0f80);
// Mark this region as also being mapped in user-space.
// This isn't used anywhere in this code but client code can test for
// this flag and decide whether to treat this as kernel or user code.
region->flags |= region_type::kIsUserMappedRegion;
InitRegionSymbols(region, 4);
AddRegionSymbol(region, 0, 0x0, "(kuser_atomic_inc)", 0);
AddRegionSymbol(region, 1, 0x20, "(kuser_atomic_dec)", 0);
AddRegionSymbol(region, 2, 0x40, "(kuser_cmpxchg)", 0);
AddRegionSymbol(region, 3, 0x80, "(kuser_end)", 0);
AddRegion(pstate, region);
hash_->Update(region->path, region);
}
template<class T>
void TraceReader<T>::ReadKernelSymbols(const char *kernel_file)
{
region_type *region = new region_type;
// Copy the path to make it easy to delete later.
int len = strlen(kernel_file);
region->path = new char[len + 1];
strcpy(region->path, kernel_file);
region->flags = region_type::kIsKernelRegion;
ReadElfSymbols(region, kIncludeLocalSymbols);
region->vend = 0xffff0000;
AddRegion(processes_[0], region);
processes_[0]->flags |= ProcessState::kHasKernelRegion;
hash_->Update(region->path, region);
}
template<class T>
void TraceReader<T>::demangle_names(int nfuncs, symbol_type *functions)
{
char *demangled;
int status;
for (int ii = 0; ii < nfuncs; ++ii) {
demangled = NULL;
int len = strlen(functions[ii].name);
// If we don't check for "len > 1" then the demangler will incorrectly
// expand 1-letter function names. For example, "b" becomes "bool",
// "c" becomes "char" and "d" becomes "double". Also check that the
// first character is an underscore. Otherwise, on some strings
// the demangler will try to read past the end of the string (because
// the string is not really a C++ mangled name) and valgrind will
// complain.
if (demangle_ && len > 1 && functions[ii].name[0] == '_') {
demangled = abi::__cxa_demangle(functions[ii].name, 0, NULL,
&status);
}
if (demangled != NULL) {
delete[] functions[ii].name;
functions[ii].name = Strdup(demangled);
free(demangled);
}
}
}
// Adds the symbols from the given ELF file to the given process.
// Returns false if the file was not an ELF file or if there was an
// error trying to read the sections of the ELF file.
template<class T>
bool TraceReader<T>::ReadElfSymbols(region_type *region, uint32_t flags)
{
static char full_path[4096];
Elf32_Shdr *symtab, *symstr;
Elf32_Ehdr *hdr;
Elf32_Shdr *shdr;
full_path[0] = 0;
if (root_ && strcmp(root_, "/")) {
strcpy(full_path, root_);
}
strcat(full_path, region->path);
FILE *fobj = fopen(full_path, "r");
if(fobj == NULL) {
EmptyRegion:
// we need to create an (unknown) symbol with address 0, otherwise some
// other parts of the trace reader will simply crash when dealing with
// an empty region
region->vstart = 0;
region->nsymbols = 1;
region->symbols = new symbol_type[1];
memset(region->symbols, 0, sizeof(symbol_type));
region->symbols[0].addr = 0;
region->symbols[0].name = Strdup("(unknown)");
region->symbols[0].vm_sym = NULL;
region->symbols[0].region = region;
region->symbols[0].flags = 0;
if (fobj != NULL)
fclose(fobj);
return false;
}
hdr = ReadElfHeader(fobj);
if (hdr == NULL) {
fprintf(stderr, "Cannot read ELF header from '%s'\n", full_path);
goto EmptyRegion;
}
shdr = ReadSectionHeaders(hdr, fobj);
if(shdr == NULL) {
fprintf(stderr, "Can't read section headers from executable\n");
goto EmptyRegion;
}
char *section_names = ReadStringTable(hdr, shdr, fobj);
// Get the symbol table section
symtab = FindSymbolTableSection(hdr, shdr, section_names);
if (symtab == NULL || symtab->sh_size == 0) {
fprintf(stderr, "Can't read symbol table from '%s'\n", full_path);
goto EmptyRegion;
}
// Get the symbol string table section
symstr = FindSymbolStringTableSection(hdr, shdr, section_names);
if (symstr == NULL || symstr->sh_size == 0) {
fprintf(stderr, "Can't read symbol string table from '%s'\n", full_path);
goto EmptyRegion;
}
// Load the symbol string table data
char *symbol_names = new char[symstr->sh_size];
ReadSection(symstr, symbol_names, fobj);
int num_entries = symtab->sh_size / symtab->sh_entsize;
Elf32_Sym *elf_symbols = new Elf32_Sym[num_entries];
ReadSection(symtab, elf_symbols, fobj);
AdjustElfSymbols(hdr, elf_symbols, num_entries);
#if 0
printf("size: %d, ent_size: %d, num_entries: %d\n",
symtab->sh_size, symtab->sh_entsize, num_entries);
#endif
int nfuncs = 0;
// Allocate space for all of the symbols for now. We will
// reallocate space for just the function symbols after we
// know how many there are. Also, make sure there is room
// for some extra symbols, including the text section names.
int num_alloc = num_entries + hdr->e_shnum + 1;
symbol_type *func_symbols = new symbol_type[num_alloc];
memset(func_symbols, 0, num_alloc * sizeof(symbol_type));
// If this is the shared library for a virtual machine, then
// set the IsInterpreter flag for all symbols in that shared library.
// This will allow us to replace the symbol names with the name of
// the currently executing method on the virtual machine.
int symbol_flags = 0;
char *cp = strrchr(region->path, '/');
if (cp != NULL) {
// Move past the '/'
cp += 1;
} else {
// There was no '/', so use the whole path
cp = region->path;
}
if (strcmp(cp, "libdvm.so") == 0) {
symbol_flags = symbol_type::kIsInterpreter;
}
bool zero_found = false;
for (int ii = 1; ii < num_entries; ++ii) {
int idx = elf_symbols[ii].st_name;
// If the symbol does not have a name, or if the name starts with a
// dollar sign ($), then skip it.
if (idx == 0 || symbol_names[idx] == 0 || symbol_names[idx] == '$')
continue;
// If the section index is not executable, then skip it.
uint32_t section = elf_symbols[ii].st_shndx;
if (section == 0 || section >= hdr->e_shnum)
continue;
if ((shdr[section].sh_flags & SHF_EXECINSTR) == 0)
continue;
uint8_t sym_type = ELF32_ST_TYPE(elf_symbols[ii].st_info);
uint8_t sym_bind = ELF32_ST_BIND(elf_symbols[ii].st_info);
// Allow the caller to decide if we want local non-function
// symbols to be included. We currently include these symbols
// only for the kernel, where it is useful because the kernel
// has lots of assembly language labels that have meaningful names.
if ((flags & kIncludeLocalSymbols) == 0 && sym_bind == STB_LOCAL
&& sym_type != STT_FUNC) {
continue;
}
#if 0
printf("%08x %x %x %s\n",
elf_symbols[ii].st_value,
sym_bind,
sym_type,
&symbol_names[idx]);
#endif
if (sym_type != STT_FUNC && sym_type != STT_NOTYPE)
continue;
if (elf_symbols[ii].st_value == 0)
zero_found = true;
// The address of thumb functions seem to have the low bit set,
// even though the instructions are really at an even address.
uint32_t addr = elf_symbols[ii].st_value & ~0x1;
func_symbols[nfuncs].addr = addr;
func_symbols[nfuncs].name = Strdup(&symbol_names[idx]);
func_symbols[nfuncs].flags = symbol_flags;
nfuncs += 1;
}
// Add a [0, "(unknown)"] symbol pair if there is not already a
// symbol with the address zero. We don't need to reallocate space
// because we already have more than we need.
if (!zero_found) {
func_symbols[nfuncs].addr = 0;
func_symbols[nfuncs].name = Strdup("(0 unknown)");
nfuncs += 1;
}
// Add another entry at the end
func_symbols[nfuncs].addr = 0xffffffff;
func_symbols[nfuncs].name = Strdup("(end)");
nfuncs += 1;
// Add in the names of the text sections, but only if there
// are no symbols with that address already.
for (int section = 0; section < hdr->e_shnum; ++section) {
if ((shdr[section].sh_flags & SHF_EXECINSTR) == 0)
continue;
uint32_t addr = shdr[section].sh_addr;
// Search for a symbol with a matching address. The symbols aren't
// sorted yet so we just search the whole list.
int ii;
for (ii = 0; ii < nfuncs; ++ii) {
if (addr == func_symbols[ii].addr)
break;
}
if (ii == nfuncs) {
// Symbol at address "addr" does not exist, so add the text
// section name. This will usually add the ".plt" section
// (procedure linkage table).
int idx = shdr[section].sh_name;
func_symbols[nfuncs].addr = addr;
func_symbols[nfuncs].name = Strdup(&section_names[idx]);
if (strcmp(func_symbols[nfuncs].name, ".plt") == 0) {
func_symbols[nfuncs].flags |= symbol_type::kIsPlt;
// Change the name of the symbol to include the
// name of the library. Otherwise we will have lots
// of ".plt" symbols.
int len = strlen(region->path);
len += strlen(":.plt");
char *name = new char[len + 1];
strcpy(name, region->path);
strcat(name, ":.plt");
delete[] func_symbols[nfuncs].name;
func_symbols[nfuncs].name = name;
// Check if this is part of the virtual machine interpreter
char *cp = strrchr(region->path, '/');
if (cp != NULL) {
// Move past the '/'
cp += 1;
} else {
// There was no '/', so use the whole path
cp = region->path;
}
if (strcmp(cp, "libdvm.so") == 0) {
func_symbols[nfuncs].flags |= symbol_type::kIsInterpreter;
}
}
nfuncs += 1;
}
}
// Allocate just the space we need now that we know exactly
// how many symbols we have.
symbol_type *functions = new symbol_type[nfuncs];
// Copy the symbols to the functions array
memcpy(functions, func_symbols, nfuncs * sizeof(symbol_type));
delete[] func_symbols;
// Assign the region pointers
for (int ii = 0; ii < nfuncs; ++ii) {
functions[ii].region = region;
}
// Sort the symbols into increasing address order
qsort(functions, nfuncs, sizeof(symbol_type), cmp_symbol_addr<T>);
// If there are multiple symbols with the same address, then remove
// the duplicates. First, count the number of duplicates.
uint32_t prev_addr = ~0;
int num_duplicates = 0;
for (int ii = 0; ii < nfuncs; ++ii) {
if (prev_addr == functions[ii].addr)
num_duplicates += 1;
prev_addr = functions[ii].addr;
}
if (num_duplicates > 0) {
int num_uniq = nfuncs - num_duplicates;
// Allocate space for the unique functions
symbol_type *uniq_functions = new symbol_type[num_uniq];
// Copy the unique functions
prev_addr = ~0;
int next_uniq = 0;
for (int ii = 0; ii < nfuncs; ++ii) {
if (prev_addr == functions[ii].addr) {
delete[] functions[ii].name;
continue;
}
memcpy(&uniq_functions[next_uniq++], &functions[ii],
sizeof(symbol_type));
prev_addr = functions[ii].addr;
}
assert(next_uniq == num_uniq);
delete[] functions;
functions = uniq_functions;
nfuncs = num_uniq;
}
// Finally, demangle all of the symbol names
demangle_names(nfuncs, functions);
uint32_t min_addr = 0;
if (!zero_found)
min_addr = functions[1].addr;
if (region->vstart == 0)
region->vstart = min_addr;
region->nsymbols = nfuncs;
region->symbols = functions;
#if 0
printf("%s num symbols: %d min_addr: 0x%x\n", region->path, nfuncs, min_addr);
for (int ii = 0; ii < nfuncs; ++ii) {
printf("0x%08x %s\n", functions[ii].addr, functions[ii].name);
}
#endif
delete[] elf_symbols;
delete[] symbol_names;
delete[] section_names;
delete[] shdr;
delete hdr;
fclose(fobj);
return true;
}
template<class T>
void TraceReader<T>::CopyKernelRegion(ProcessState *pstate)
{
ProcessState *manager = pstate->addr_manager;
if (manager->flags & ProcessState::kHasKernelRegion)
return;
int nregions = processes_[0]->nregions;
region_type **regions = processes_[0]->regions;
for (int ii = 0; ii < nregions; ii++) {
if (regions[ii]->flags & region_type::kIsKernelRegion) {
AddRegion(manager, regions[ii]);
regions[ii]->refs += 1;
}
}
manager->flags |= ProcessState::kHasKernelRegion;
}
template<class T>
void TraceReader<T>::ClearRegions(ProcessState *pstate)
{
assert(pstate->pid != 0);
int nregions = pstate->nregions;
region_type **regions = pstate->regions;
// Decrement the reference count on all the regions
for (int ii = 0; ii < nregions; ii++) {
if (regions[ii]->refs > 0) {
regions[ii]->refs -= 1;
continue;
}
delete regions[ii];
}
delete[] pstate->regions;
pstate->regions = NULL;
pstate->nregions = 0;
pstate->max_regions = 0;
pstate->addr_manager = pstate;
pstate->flags &= ~ProcessState::kIsClone;
pstate->flags &= ~ProcessState::kHasKernelRegion;
CopyKernelRegion(pstate);
}
template<class T>
void TraceReader<T>::AddRegion(ProcessState *pstate, region_type *region)
{
ProcessState *manager = pstate->addr_manager;
if (manager->regions == NULL) {
manager->max_regions = ProcessState::kInitialNumRegions;
manager->regions = new region_type*[manager->max_regions];
manager->nregions = 0;
}
// Check if we need to grow the array
int nregions = manager->nregions;
int max_regions = manager->max_regions;
if (nregions >= max_regions) {
max_regions <<= 1;
manager->max_regions = max_regions;
region_type **regions = new region_type*[max_regions];
for (int ii = 0; ii < nregions; ii++) {
regions[ii] = manager->regions[ii];
}
delete[] manager->regions;
manager->regions = regions;
}
// Add the new region to the end of the array and resort
manager->regions[nregions] = region;
nregions += 1;
manager->nregions = nregions;
// Resort the regions into increasing start address
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>
void TraceReader<T>::CopyRegions(ProcessState *parent, ProcessState *child)
{
// Copy the parent's address space
ProcessState *manager = parent->addr_manager;
int nregions = manager->nregions;
child->nregions = nregions;
child->max_regions = manager->max_regions;
region_type **regions = new region_type*[manager->max_regions];
child->regions = regions;
memcpy(regions, manager->regions, nregions * sizeof(region_type*));
// Increment the reference count on all the regions
for (int ii = 0; ii < nregions; ii++) {
regions[ii]->refs += 1;
}
}
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)
{
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 regions[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 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>
typename TraceReader<T>::symbol_type *
TraceReader<T>::FindFunction(uint32_t addr, int nsyms, symbol_type *symbols,
bool exact_match)
{
int high = nsyms;
int low = -1;
while (low + 1 < high) {
int middle = (high + low) / 2;
uint32_t middle_addr = symbols[middle].addr;
if (middle_addr == addr)
return &symbols[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 function address that is less than the given address.
// We added a symbol with address zero so if there is no known
// function containing the given address, then we will return the
// "(unknown)" symbol.
if (low >= 0 && !exact_match)
return &symbols[low];
return NULL;
}
template<class T>
typename TraceReader<T>::symbol_type *
TraceReader<T>::LookupFunction(int pid, uint32_t addr, uint64_t time)
{
// Check if the previous match is still a good match.
if (cached_pid_ == pid) {
uint32_t vstart = cached_func_->region->vstart;
uint32_t vend = cached_func_->region->vend;
if (addr >= vstart && addr < vend) {
uint32_t sym_addr = addr - cached_func_->region->base_addr;
if (sym_addr >= cached_func_->addr
&& sym_addr < (cached_func_ + 1)->addr) {
// 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_;
}
}
}
ProcessState *pstate = processes_[pid];
if (pstate == NULL) {
// There is no process state for the specified pid.
// This should never happen.
cached_pid_ = -1;
cached_func_ = NULL;
return NULL;
}
ProcessState *manager = pstate->addr_manager;
cached_pid_ = pid;
region_type *region = FindRegion(addr, manager->nregions, manager->regions);
uint32_t sym_addr = addr - region->base_addr;
cached_func_ = FindFunction(sym_addr, region->nsymbols, region->symbols,
false /* no exact match */);
if (cached_func_ != NULL) {
cached_func_->region = region;
// 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_;
}
template <class T>
void TraceReader<T>::HandlePidEvent(PidEvent *event)
{
switch (event->rec_type) {
case kPidFork:
case kPidClone:
// event->pid is the process id of the child
if (event->pid >= kNumPids) {
fprintf(stderr, "Error: pid (%d) too large\n", event->pid);
exit(1);
}
// Create a new ProcessState struct for the child
// and link it in at the front of the list for that
// pid.
{
ProcessState *child = new ProcessState;
processes_[event->pid] = child;
child->pid = event->pid;
child->tgid = event->tgid;
// Link the new child at the front of the list (only needed if
// pids wrap around, which will probably never happen when
// tracing because it would take so long).
child->next = processes_[event->pid];
child->parent_pid = current_->pid;
child->parent = current_;
child->start_time = event->time;
child->name = Strdup(current_->name);
if (event->rec_type == kPidFork) {
CopyRegions(current_, child);
} else {
// Share the parent's address space
child->flags |= ProcessState::kIsClone;
// The address space manager for the clone is the same
// as the address space manager for the parent. This works
// even if the child later clones itself.
child->addr_manager = current_->addr_manager;
}
}
break;
case kPidSwitch:
// event->pid is the process id of the process we are
// switching to.
{
uint64_t elapsed = event->time - function_start_time_;
function_start_time_ = event->time;
current_->cpu_time += elapsed;
}
if (current_->flags & ProcessState::kCalledExit)
current_->end_time = event->time;
if (event->pid >= kNumPids) {
fprintf(stderr, "Error: pid (%d) too large\n", event->pid);
exit(1);
}
// If the process we are switching to does not exist, then
// create one. This can happen because the tracing code does
// not start tracing from the very beginning of the kernel.
current_ = processes_[event->pid];
if (current_ == NULL) {
current_ = new ProcessState;
processes_[event->pid] = current_;
current_->pid = event->pid;
current_->start_time = event->time;
CopyKernelRegion(current_);
}
#if 0
{
printf("switching to p%d\n", current_->pid);
ProcessState *manager = current_->addr_manager;
for (int ii = 0; ii < manager->nregions; ++ii) {
printf(" %08x - %08x offset: %d nsyms: %4d %s\n",
manager->regions[ii]->vstart,
manager->regions[ii]->vend,
manager->regions[ii]->file_offset,
manager->regions[ii]->nsymbols,
manager->regions[ii]->path);
}
}
#endif
break;
case kPidExit:
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
|| 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;
// The event->path is allocated by ReadPidEvent() and owned
// by us.
region->path = event->path;
region->vstart = event->vstart;
region->vend = event->vend;
region->file_offset = event->offset;
if (existing_region == NULL) {
DexFileList *dexfile = dex_hash_->Find(event->path);
if (dexfile != NULL) {
PopulateSymbolsFromDexFile(dexfile, region);
} else {
ReadElfSymbols(region, 0);
}
hash_->Update(region->path, region);
} else {
region->nsymbols = existing_region->nsymbols;
region->symbols = existing_region->symbols;
region->flags |= region_type::kSharedSymbols;
}
// The base_addr is subtracted from an address before the
// symbol name lookup and is either zero or event->vstart.
// HACK: Determine if base_addr is non-zero by looking at the
// second symbol address (skip the first symbol because that is
// the special symbol "(unknown)" with an address of zero).
if (region->nsymbols > 2 && region->symbols[1].addr < event->vstart)
region->base_addr = event->vstart;
// Treat all mmapped regions after the first as "libraries".
// Profiling tools can test for this property.
if (current_->flags & ProcessState::kHasFirstMmap)
region->flags |= region_type::kIsLibraryRegion;
else
current_->flags |= ProcessState::kHasFirstMmap;
#if 0
printf("%s vstart: 0x%x vend: 0x%x offset: 0x%x\n",
region->path, region->vstart, region->vend, region->file_offset);
#endif
} else {
region = existing_region;
region->refs += 1;
delete[] event->path;
}
AddRegion(current_, region);
}
break;
case kPidExec:
if (current_->argc > 0) {
for (int ii = 0; ii < current_->argc; ii++) {
delete[] current_->argv[ii];
}
delete[] current_->argv;
}
delete[] current_->name;
current_->argc = event->argc;
current_->argv = event->argv;
current_->name = Strdup(current_->argv[0]);
current_->flags |= ProcessState::kCalledExec;
ClearRegions(current_);
break;
case kPidName:
case kPidKthreadName:
{
ProcessState *pstate = processes_[event->pid];
if (pstate == NULL) {
pstate = new ProcessState;
if (event->rec_type == kPidKthreadName) {
pstate->tgid = event->tgid;
}
pstate->pid = event->pid;
pstate->start_time = event->time;
processes_[event->pid] = pstate;
CopyKernelRegion(pstate);
} else {
delete[] pstate->name;
}
pstate->name = event->path;
}
break;
case kPidNoAction:
break;
case kPidSymbolAdd:
delete[] event->path;
break;
case kPidSymbolRemove:
break;
}
}
// Finds the current pid for the given time. This routine reads the pid
// trace file and assumes that the "time" parameter is monotonically
// increasing.
template <class T>
int TraceReader<T>::FindCurrentPid(uint64_t time)
{
if (time < next_pid_event_.time)
return current_->pid;
while (1) {
HandlePidEvent(&next_pid_event_);
if (internal_pid_reader_->ReadPidEvent(&next_pid_event_)) {
next_pid_event_.time = ~0ull;
break;
}
if (next_pid_event_.time > time)
break;
}
return current_->pid;
}
template <class T>
void TraceReader<T>::ProcessState::DumpStack(FILE *stream)
{
const char *native;
for (int ii = 0; ii < method_stack_top; ii++) {
native = method_stack[ii].isNative ? "n" : " ";
fprintf(stream, "%2d: %s 0x%08x\n", ii, native, method_stack[ii].addr);
}
}
template <class T>
void TraceReader<T>::HandleMethodRecord(ProcessState *pstate,
MethodRec *method_rec)
{
uint32_t addr;
int top = pstate->method_stack_top;
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].addr = method_rec->addr;
isNative = (flags == kNativeEnter);
pstate->method_stack[top].isNative = isNative;
pstate->method_stack_top = top + 1;
addr = method_rec->addr;
} else {
if (top <= 0) {
// If the stack underflows, then set the current method to NULL.
pstate->current_method_sym = NULL;
return;
}
top -= 1;
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);
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);
}
pstate->method_stack_top = top;
if (top == 0) {
// When we empty the stack, set the current method to NULL
pstate->current_method_sym = NULL;
return;
}
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;
symbol_type *sym = FindFunction(sym_addr, region->nsymbols,
region->symbols, true /* exact match */);
pstate->current_method_sym = sym;
if (sym != NULL) {
sym->region = region;
}
}
// 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)
{
ProcessState *procState = processes_[pid];
if (time < next_method_.time) {
return procState->current_method_sym;
}
while (1) {
if (next_method_.time != 0) {
// We may have to process methods from a different pid so use
// a local variable here so that we don't overwrite procState.
ProcessState *pState = processes_[next_method_.pid];
HandleMethodRecord(pState, &next_method_);
}
if (internal_method_reader_->ReadMethod(&next_method_)) {
next_method_.time = ~0ull;
break;
}
if (next_method_.time > time)
break;
}
return procState->current_method_sym;
}
template <class T>
void TraceReader<T>::PopulateSymbolsFromDexFile(const DexFileList *dexfile,
region_type *region)
{
int nsymbols = dexfile->nsymbols;
DexSym *dexsyms = dexfile->symbols;
region->nsymbols = nsymbols + 1;
symbol_type *symbols = new symbol_type[nsymbols + 1];
memset(symbols, 0, (nsymbols + 1) * sizeof(symbol_type));
region->symbols = symbols;
for (int ii = 0; ii < nsymbols; ii++) {
symbols[ii].addr = dexsyms[ii].addr;
symbols[ii].name = Strdup(dexsyms[ii].name);
symbols[ii].vm_sym = NULL;
symbols[ii].region = region;
symbols[ii].flags = symbol_type::kIsMethod;
}
// Add an entry at the end with an address of 0xffffffff. This
// is required for LookupFunction() to work.
symbol_type *symbol = &symbols[nsymbols];
symbol->addr = 0xffffffff;
symbol->name = Strdup("(end)");
symbol->vm_sym = NULL;
symbol->region = region;
symbol->flags = symbol_type::kIsMethod;
}
template <class T>
bool TraceReader<T>::ReadMethodSymbol(MethodRec *method_record,
symbol_type **psym,
ProcessState **pproc)
{
if (internal_method_reader_->ReadMethod(&next_method_)) {
return true;
}
// Copy the whole MethodRec struct
*method_record = next_method_;
uint64_t time = next_method_.time;
// Read the pid trace file up to this point to make sure the
// process state is valid.
FindCurrentPid(time);
ProcessState *pstate = processes_[next_method_.pid];
*pproc = pstate;
HandleMethodRecord(pstate, &next_method_);
*psym = pstate->current_method_sym;
return false;
}
#endif /* TRACE_READER_H */