// Copyright 2006 The Android Open Source Project #ifndef TRACE_READER_H #define TRACE_READER_H #include #include #include #include #include #include "read_elf.h" #include "trace_reader_base.h" #include "hash_table.h" struct TraceReaderEmptyStruct { }; template 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::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 *hash_; bool demangle_; }; template TraceReader::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(512); AddPredefinedRegions(current_); demangle_ = true; } template TraceReader::~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 int cmp_symbol_addr(const void *a, const void *b) { typedef typename TraceReader::symbol_type stype; const stype *syma = static_cast(a); const stype *symb = static_cast(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 int cmp_region_addr(const void *a, const void *b) { typedef typename TraceReader::region_type rtype; const rtype *ma = *static_cast(a); const rtype *mb = *static_cast(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 typename TraceReader::symbol_type* TraceReader::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 typename TraceReader::ProcessState* TraceReader::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 typename TraceReader::ProcessState* TraceReader::GetNextProcess() { while (next_pid_ < kNumPids) { if (processes_[next_pid_]) return processes_[next_pid_++]; next_pid_ += 1; } next_pid_ = 0; return NULL; } template const char* TraceReader::GetProcessName(int pid) { if (pid < 0 || pid >= kNumPids || processes_[pid] == NULL) return "(unknown)"; return processes_[pid]->name; } template void TraceReader::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 void TraceReader::InitRegionSymbols(region_type *region, int nsymbols) { region->nsymbols = nsymbols; region->symbols = new symbol_type[nsymbols]; memset(region->symbols, 0, nsymbols * sizeof(symbol_type)); } template void TraceReader::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 void TraceReader::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 void TraceReader::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 void TraceReader::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 bool TraceReader::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(§ion_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); // 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 void TraceReader::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 void TraceReader::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 void TraceReader::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); } template void TraceReader::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); } 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 void TraceReader::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 void TraceReader::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 typename TraceReader::region_type * TraceReader::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 int TraceReader::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 typename TraceReader::symbol_type * TraceReader::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 typename TraceReader::symbol_type * TraceReader::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 void TraceReader::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 int TraceReader::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 void TraceReader::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 void TraceReader::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 typename TraceReader::symbol_type* TraceReader::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 void TraceReader::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 bool TraceReader::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 */