blob: b91cb1bcc22e4affb1984ab9024b309c5315e2e2 [file] [log] [blame]
// 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 */