blob: 4b7cb01edb3a1e313cc7ed322a53f6404a221d6b [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "common/memory_state.h"
#include <errno.h>
#include <execinfo.h>
#include <malloc.h>
#include <stdio.h>
#include <sstream>
#include <algorithm>
#include "base/memory/singleton.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#include "common/alog.h"
#include "common/backtrace.h"
#if defined(__native_client__)
static const uintptr_t kTrampolinesStartAddress = 0x10000;
static const uintptr_t kIRTStartAddress = 0xfa00000;
#endif
// TODO(crbug.com/238463): Drop this.
#if defined(__native_client__)
extern "C" int nacl_list_mappings(
arc::NaClMemMappingInfo* info, size_t count, size_t* result_count);
#endif
namespace arc {
ProcessMapHeader::ProcessMapHeader(const struct dl_phdr_info* info, int idx) {
type_ = info->dlpi_phdr[idx].p_type;
library_ = info->dlpi_name;
base_address_ = info->dlpi_addr;
object_address_ = info->dlpi_phdr[idx].p_vaddr;
memory_size_ = info->dlpi_phdr[idx].p_memsz;
file_offset_ = info->dlpi_phdr[idx].p_offset;
flags_ = info->dlpi_phdr[idx].p_flags;
#if defined(__native_client__)
// Handle special cases to work around limitations of phdr runtime info.
if (library_.size() == 0)
library_ = std::string("arc_") + ARC_TARGET + ".nexe";
#endif
}
std::string ProcessMapHeader::GetTypeStr() const {
switch (type_) {
case PT_NULL: return "NULL";
case PT_LOAD: return "LOAD";
case PT_DYNAMIC: return "DYNAMIC";
case PT_INTERP: return "INTERP";
case PT_NOTE: return "NOTE";
case PT_SHLIB: return "SHLIB";
case PT_PHDR: return "PHDR";
case PT_TLS: return "TLS";
}
return base::StringPrintf("type%d", type_);
}
std::string ProcessMapHeader::GetFlagsStr() const {
return base::StringPrintf(
"%s%s%s", (flags_ & PF_R ? "R" : ""), (flags_ & PF_W ? "W" : ""),
(flags_ & PF_X ? "X" : ""));
}
std::string ProcessMapHeader::ConvertToString() const {
// We do not use PRIxxx macros here because Bionic for KitKat no longer
// defines such macros for C++.
return base::StringPrintf(
"%10x - %10x base %10x %s/%-3s %s %d",
GetVirtualAddress(), GetVirtualAddress() + memory_size_,
base_address_, GetTypeStr().c_str(), GetFlagsStr().c_str(),
GetLibrary().c_str(), file_offset_);
}
std::string ProcessMapHeader::ConvertToJSON() const {
// We do not use PRIxxx macros here. See ConvertToString().
return base::StringPrintf(
"{"
"\"type\":\"%s\","
"\"library\":\"%s\","
"\"baseAddress\":%u,"
"\"objectAddress\":%u,"
"\"memorySize\":%u,"
"\"fileOffset\":%u,"
"\"flags\":%u"
"}",
GetTypeStr().c_str(),
GetLibrary().c_str(),
GetBaseAddress(),
GetObjectAddress(),
GetMemorySize(),
GetFileOffset(),
GetFlags());
}
int ProcessMapHeader::DumpPhdrCallback(
struct dl_phdr_info* info, size_t size, void* data) {
List* list = static_cast<List*>(data);
for (int i = 0; i < info->dlpi_phnum; i++) {
list->push_back(ProcessMapHeader(info, i));
}
return 0;
}
void ProcessMapHeader::DumpLayout(ProcessMapHeader::List* list) {
#if defined(__arm__) && (__native_client__)
// TODO(crbug.com/256982): Implement this.
ALOGW("ProcessMapHeader::DumpLayout is not supported on ARM NaCl yet.");
#else
dl_iterate_phdr(DumpPhdrCallback, list);
#endif
}
bool ProcessMapHeader::CompareByVirtualAddress(
ProcessMapHeader i, ProcessMapHeader j) {
return (i.GetVirtualAddress() < j.GetVirtualAddress());
}
void ProcessMapHeader::SortByVirtualAddress(ProcessMapHeader::List* list) {
std::sort((*list).begin(), (*list).end(), CompareByVirtualAddress);
}
void ProcessMapHeader::AddSyntheticLibraries(
size_t loader_size, size_t irt_size, ProcessMapHeader::List* list) {
#if defined(__native_client__)
// Add synthetic entries for special NaCl regions.
// Trampolines.
struct dl_phdr_info trampolines;
ElfW(Phdr) trampolines_phdr;
memset(&trampolines, 0, sizeof(trampolines));
memset(&trampolines_phdr, 0, sizeof(trampolines_phdr));
trampolines.dlpi_name = "NaCl Trampolines + runnable-ld.so";
trampolines.dlpi_addr = kTrampolinesStartAddress;
trampolines.dlpi_phdr = &trampolines_phdr;
trampolines.dlpi_phnum = 1;
trampolines_phdr.p_type = PT_LOAD;
trampolines_phdr.p_vaddr = 0;
trampolines_phdr.p_memsz = loader_size;
trampolines_phdr.p_offset = 0;
trampolines_phdr.p_flags = 0;
list->push_back(ProcessMapHeader(&trampolines, 0));
// IRT.
struct dl_phdr_info irt;
ElfW(Phdr) irt_phdr;
memset(&irt, 0, sizeof(irt));
memset(&irt_phdr, 0, sizeof(irt_phdr));
#if defined(__i386__)
irt.dlpi_name = "nacl_irt_x86_32.nexe";
#elif defined(__x86_64__)
irt.dlpi_name = "nacl_irt_x86_64.nexe";
#elif defined(__arm__)
irt.dlpi_name = "nacl_irt_arm.nexe";
#else
# error "Unsupported NaCl platform"
#endif
irt.dlpi_addr = kIRTStartAddress;
irt.dlpi_phdr = &irt_phdr;
irt.dlpi_phnum = 1;
irt_phdr.p_type = PT_LOAD;
irt_phdr.p_vaddr = 0;
irt_phdr.p_memsz = irt_size;
irt_phdr.p_offset = 0;
irt_phdr.p_flags = 0;
list->push_back(ProcessMapHeader(&irt, 0));
#endif
}
void ProcessMapHeader::PrintLayout(int type_filter) {
List list;
DumpLayout(&list);
SortByVirtualAddress(&list);
std::stringstream sstr;
for (uint i = 0; i < list.size(); i++) {
if (type_filter != -1 && type_filter != list[i].type_)
continue;
sstr << list[i].ConvertToString() << "\n";
}
// Use stderr to avoid extra stuff prepended by logger.
fprintf(stderr, "%s", sstr.str().c_str());
}
std::string ProcessMapHeader::ConvertListToJSON(
const ProcessMapHeader::List& list) {
std::string result;
result += "[";
for (uint i = 0; i < list.size(); ++i) {
if (i != 0)
result += ",";
result += list[i].ConvertToJSON();
}
result += "]";
return result;
}
// MemoryMappingBacktraceMap keeps a mapping of start address to end address
// plus backtrace.
struct MemoryMappingBacktrace {
static const int kBacktraceCapacity = 100;
uintptr_t end; // Keeping this as uintptr_t to allow comparisons without
// casting.
// A raw backtrace as a series of code addresses. Decoded to symbols on
// demand.
void* backtrace[kBacktraceCapacity];
int backtrace_size;
};
MemoryMappingBacktraceMap::MemoryMappingBacktraceMap()
: mu_(new base::Lock) {
backtracer_ = BacktraceInterface::Get();
}
MemoryMappingBacktraceMap::~MemoryMappingBacktraceMap() {
delete mu_;
delete backtracer_;
for (Map::iterator i = memory_.begin(); i != memory_.end(); ++i) {
delete i->second;
}
}
int MemoryMappingBacktraceMap::GetUninterestingLayers() {
// Number of layers to cull from the backtrace:
// BacktracerInterface::Backtrace
// MemoryMappingBacktraceMap::MapCurrentStackFrame
// __wrap_mmap
return 3;
}
MemoryMappingBacktraceMap* MemoryMappingBacktraceMap::GetInstance() {
return Singleton<MemoryMappingBacktraceMap,
LeakySingletonTraits<MemoryMappingBacktraceMap> >::get();
}
void MemoryMappingBacktraceMap::MapCurrentStackFrame(
void* addr, size_t length) {
Unmap(addr, length);
base::AutoLock lock(*mu_);
size_t addri = reinterpret_cast<size_t>(addr);
MemoryMappingBacktrace* trace = new MemoryMappingBacktrace;
trace->backtrace_size = backtracer_->Backtrace(
trace->backtrace, MemoryMappingBacktrace::kBacktraceCapacity);
trace->end = addri + length;
memory_[addri] = trace;
}
void MemoryMappingBacktraceMap::Unmap(void* addr, size_t length) {
// This operation is a NOP in the case where there are no mappings
// in [addr:addr+length). (Matches some munmap definitions and
// relied on inside Add above).
base::AutoLock lock(*mu_);
size_t addri = reinterpret_cast<size_t>(addr);
// Loop over all regions intersecting the area to remove.
Map::iterator start = memory_.lower_bound(addri);
// If the first region starting at least at addri is does not begin at
// exactly addri, the region in front of it may need to be clipped.
// Go back one assuming we are not on the first region already.
if (start != memory_.begin() && start->first != addri)
--start;
Map::iterator end = memory_.upper_bound(addri + length);
Map::iterator i = start;
while (i != end) {
// Split the current region if needed.
//
// There are six cases.
// |--| = original region.
// (--) = region to remove [addr, addr + length).
// xxxx = region deleted
//
// (i)
// a b c d
// |-----xxxxxxxxxxxxx-----|
// (-------------)
//
// (ii)
// b a c d
// |xxxxxxxx-----|
// (-------------)
//
// (iii)
// a b d c
// |-----xxxxxxxx|
// (-------------)
//
// (iv)
// b a d c
// |xxxxxxxxxxxxx|
// (-----------------------)
//
// (v)
// a d b c
// |-----|
// (-----)
//
// (vi)
// b c a d
// |-----|
// (-----)
//
// If the regions not overlap (cases v and vi), skip to the next region.
if (addri + length <= i->first ||
i->second->end <= addri) {
++i;
continue;
}
// In the remaining cases, note that only in the cases where b > a does a
// left half survive the split (from a to b). Similarly, only when c > d
// does a right half survive (from c to d).
// Remove the old region, compute the ranges of the left and right sections,
// keeping only those with positive size.
size_t left_start = i->first;
size_t left_end = addri;
size_t right_start = addri + length;
size_t right_end = i->second->end;
// Extract the current area.
MemoryMappingBacktrace* trace = i->second;
Map::iterator next = i;
++next;
memory_.erase(i);
// Add left and right divisions if they are needed.
if (left_end > left_start) {
MemoryMappingBacktrace* left_trace = new MemoryMappingBacktrace;
*left_trace = *trace;
left_trace->end = left_end;
memory_[left_start] = left_trace;
}
if (right_end > right_start) {
MemoryMappingBacktrace* right_trace = new MemoryMappingBacktrace;
*right_trace = *trace;
right_trace->end = right_end;
memory_[right_start] = right_trace;
}
delete trace;
i = next;
}
}
std::string MemoryMappingBacktraceMap::ConvertBacktraceToJSON(void* addr) {
base::AutoLock lock(*mu_);
size_t addri = reinterpret_cast<size_t>(addr);
// Find the first range after addr.
Map::iterator i = memory_.upper_bound(addri);
// If it is the lowest range, give up.
if (i == memory_.begin())
return "[]";
// Look at the range in front of it.
--i;
// If it does not include this address, give up.
if (addri < i->first || addri >= i->second->end)
return "[]";
char** names = backtracer_->BacktraceSymbols(i->second->backtrace,
i->second->backtrace_size);
std::string result;
result += "[";
// Skip the initial uninteresting ones.
for (int j = GetUninterestingLayers(); j < i->second->backtrace_size; ++j) {
if (j != GetUninterestingLayers())
result += ",";
result += "\"";
result += BacktraceInterface::DemangleAll(names[j]);
result += "\"";
}
result += "]";
free(names);
return result;
}
MemoryMappingInfo::MemoryMappingInfo(const struct NaClMemMappingInfo* info)
: info_(*info) {
}
void MemoryMappingInfo::DumpRegions(MemoryMappingInfo::List* list) {
#if defined(__native_client__)
static const size_t capacity = 0x10000;
NaClMemMappingInfo* regions = new NaClMemMappingInfo[capacity];
size_t count = 0;
int ret = nacl_list_mappings(regions, capacity, &count);
if (ret != 0) {
ALOGE("nacl_list_mappings failed with errno:%d", errno);
delete[] regions;
return;
}
if (count > capacity) {
ALOGE("nacl_list_mappings returning only the first %u of %u mappings.",
capacity, count);
count = capacity;
}
for (size_t i = 0; i < count; ++i) {
list->push_back(MemoryMappingInfo(&regions[i]));
}
delete[] regions;
#else
ALOGW("nacl_list_mappings not available on this platform.");
#endif
}
std::string MemoryMappingInfo::ConvertToJSON() const {
void* start = reinterpret_cast<void*>(GetStart());
std::string backtrace = MemoryMappingBacktraceMap::GetInstance()->
ConvertBacktraceToJSON(start);
// We do not use PRIxxx macros here. See ConvertToString().
return base::StringPrintf(
"{"
"\"start\":%u,"
"\"size\":%u,"
"\"prot\":%u,"
"\"maxProt\":%u,"
"\"vmmapType\":%u,"
"\"backtrace\":%s"
"}",
GetStart(),
GetSize(),
GetProtection(),
GetMaximumProtection(),
GetVmmapType(),
backtrace.c_str());
}
std::string MemoryMappingInfo::ConvertListToJSON(
const MemoryMappingInfo::List& list) {
std::string result;
result += "[";
for (uint i = 0; i < list.size(); ++i) {
if (i != 0)
result += ",";
result += list[i].ConvertToJSON();
}
result += "]";
return result;
}
void MemoryMappingInfo::ExtractNaClSizes(
const MemoryMappingInfo::List& list,
size_t* loader_size, size_t* irt_size) {
*loader_size = 0;
*irt_size = 0;
#if defined(__native_client__)
for (uint i = 0; i < list.size(); ++i) {
if (list[i].GetStart() == kTrampolinesStartAddress)
*loader_size = list[i].GetSize();
if (list[i].GetStart() == kIRTStartAddress)
*irt_size = list[i].GetSize();
}
#endif
}
std::string MemoryState::DumpMallocInfoAsJSON() {
struct mallinfo minfo = mallinfo();
return base::StringPrintf(
"{"
"\"arena\":%d,"
"\"ordblks\":%d,"
"\"hblks\":%d,"
"\"hblkhd\":%d,"
"\"uordblks\":%d,"
"\"fordblks\":%d,"
"\"keepcost\":%d"
"}", minfo.arena,
minfo.ordblks,
minfo.hblks,
minfo.hblkhd,
minfo.uordblks,
minfo.fordblks,
minfo.keepcost);
}
std::string MemoryState::DumpAsJSON() {
MemoryMappingInfo::List mmi;
MemoryMappingInfo::DumpRegions(&mmi);
std::string mmis = MemoryMappingInfo::ConvertListToJSON(mmi);
size_t loader_size, irt_size;
MemoryMappingInfo::ExtractNaClSizes(mmi, &loader_size, &irt_size);
ProcessMapHeader::List pmh;
ProcessMapHeader::DumpLayout(&pmh);
ProcessMapHeader::AddSyntheticLibraries(loader_size, irt_size, &pmh);
std::string pmhs = ProcessMapHeader::ConvertListToJSON(pmh);
std::string minfo = DumpMallocInfoAsJSON();
return base::StringPrintf(
"{"
"\"namespace\":\"memory-state\","
"\"command\":\"snapshot\","
"\"data\":{"
"\"processMapHeaders\": %s,"
"\"memoryMappingInfo\": %s,"
"\"mallinfo\": %s,"
"\"arcTarget\": \"%s\""
"}"
"}", pmhs.c_str(), mmis.c_str(), minfo.c_str(), ARC_TARGET);
}
} // namespace arc