blob: 84e009280e4b0b5f2f6928ac71c432011227184e [file] [log] [blame]
#include "meminspect.h"
#include <android-base/unique_fd.h>
#include "ziparchive/zip_archive.h"
using namespace std;
using namespace android::base;
using namespace ::android::base;
const static VmaRange VMA_RANGE_EMPTY = VmaRange(0, 0);
uint32_t VmaRange::end_offset() const {
return offset + length;
}
uint64_t VmaRangeGroup::compute_total_size() {
uint64_t total_size = 0;
for (auto&& range : ranges) {
total_size += range.length;
}
return total_size;
}
void VmaRangeGroup::apply_offset(uint64_t offset) {
for (auto&& range : ranges) {
range.offset += offset;
}
}
void VmaRangeGroup::compute_coverage(const VmaRange& range, VmaRangeGroup& out_memres) const {
for (auto&& resident_range : ranges) {
VmaRange intersect_res = resident_range.intersect(range);
if (!intersect_res.is_empty()) {
out_memres.ranges.push_back(intersect_res);
}
}
}
bool VmaRange::is_empty() const {
return length == 0;
}
VmaRange VmaRange::intersect(const VmaRange& target) const {
// First check if the slice is outside our range
if (target.end_offset() <= this->offset) {
return VMA_RANGE_EMPTY;
}
if (target.offset >= this->end_offset()) {
return VMA_RANGE_EMPTY;
}
VmaRange result;
// the slice should now be inside the range so compute the intersection.
result.offset = std::max(target.offset, this->offset);
uint32_t res_end = std::min(target.end_offset(), end_offset());
result.length = res_end - result.offset;
return result;
}
VmaRange VmaRange::union_merge(const VmaRange& target) const {
VmaRange result = intersect(target);
if (result.is_empty()) {
// Disjointed ranges, no merge.
return VMA_RANGE_EMPTY;
}
// Since there is an intersection, merge ranges between lowest
// and highest value.
result.offset = std::min(offset, target.offset);
uint32_t res_end = std::max(target.end_offset(), end_offset());
result.length = res_end - result.offset;
return result;
}
void align_ranges(std::vector<VmaRange>& vmas_to_align, unsigned int alignment) {
for (auto&& vma_to_align : vmas_to_align) {
uint32_t unaligned_offset = vma_to_align.offset % alignment;
vma_to_align.offset -= unaligned_offset;
vma_to_align.length += unaligned_offset;
}
}
bool compare_range(VmaRange& a, VmaRange& b) {
return a.offset < b.offset;
}
std::vector<VmaRange> merge_ranges(const std::vector<VmaRange>& ranges) {
if (ranges.size() <= 1) {
// Not enough ranges to perform a merge.
return ranges;
}
std::vector<VmaRange> to_merge_ranges = ranges;
std::vector<VmaRange> merged_ranges;
// Sort the ranges to make a slightly more efficient merging.
std::sort(to_merge_ranges.begin(), to_merge_ranges.end(), compare_range);
// The first element will always start as-is, then start merging with subsequent elements.
merged_ranges.push_back(to_merge_ranges[0]);
for (int iMerged = 0, iTarget = 1; iTarget < to_merge_ranges.size(); ++iTarget) {
VmaRange merged = merged_ranges[iMerged].union_merge(to_merge_ranges[iTarget]);
if (!merged.is_empty()) {
// Merge was successful, swallow range.
merged_ranges[iMerged] = merged;
} else {
// Merge failed, add disjointed range.
merged_ranges.push_back(to_merge_ranges[iTarget]);
++iMerged;
}
}
return merged_ranges;
}
int64_t get_file_size(const std::string& file) {
unique_fd file_ufd(open(file.c_str(), O_RDONLY));
int fd = file_ufd.get();
if (fd == -1) {
return -1;
}
struct stat fstat_res;
int res = fstat(fd, &fstat_res);
if (res == -1) {
return -1;
}
return fstat_res.st_size;
}
int probe_resident_memory(string probed_file,
/*out*/ VmaRangeGroup& resident_ranges, int pages_per_mincore) {
unique_fd probed_file_ufd(open(probed_file.c_str(), O_RDONLY));
int probe_fd = probed_file_ufd.get();
if (probe_fd == -1) {
return MEMINSPECT_FAIL_OPEN;
}
int64_t total_bytes = get_file_size(probed_file);
if (total_bytes < 0) {
return MEMINSPECT_FAIL_FSTAT;
}
char* base_address =
(char*)mmap(0, (uint64_t)total_bytes, PROT_READ, MAP_SHARED, probe_fd, /*offset*/ 0);
// this determines how many pages to inspect per mincore syscall
unsigned char* window = new unsigned char[pages_per_mincore];
unsigned int page_size = sysconf(_SC_PAGESIZE);
unsigned long bytes_inspected = 0;
// total bytes in inspection window
unsigned long window_bytes = page_size * pages_per_mincore;
char* window_base;
bool started_vma_range = false;
uint32_t resident_vma_start_offset = 0;
for (window_base = base_address; bytes_inspected < total_bytes;
window_base += window_bytes, bytes_inspected += window_bytes) {
int res = mincore(window_base, window_bytes, window);
if (res != 0) {
if (errno == ENOMEM) {
// Did not find page, maybe it's a hole.
continue;
}
return MEMINSPECT_FAIL_MINCORE;
}
// Inspect the provided mincore window result sequentially
// and as soon as a change in residency happens a range is
// created or finished.
for (int iWin = 0; iWin < pages_per_mincore; ++iWin) {
if ((window[iWin] & (unsigned char)1) != 0) {
// Page is resident
if (!started_vma_range) {
// End of range
started_vma_range = true;
uint32_t window_offset = iWin * page_size;
resident_vma_start_offset = window_base + window_offset - base_address;
}
} else {
// Page is not resident
if (started_vma_range) {
// Start of range
started_vma_range = false;
uint32_t window_offset = iWin * page_size;
uint32_t resident_vma_end_offset = window_base + window_offset - base_address;
uint32_t resident_len = resident_vma_end_offset - resident_vma_start_offset;
VmaRange vma_range(resident_vma_start_offset, resident_len);
resident_ranges.ranges.push_back(vma_range);
}
}
}
}
// This was the last window, so close any opened vma range
if (started_vma_range) {
started_vma_range = false;
uint32_t in_memory_vma_end = window_base - base_address;
uint32_t resident_len = in_memory_vma_end - resident_vma_start_offset;
VmaRange vma_range(resident_vma_start_offset, resident_len);
resident_ranges.ranges.push_back(vma_range);
}
return 0;
}
ZipMemInspector::~ZipMemInspector() {
CloseArchive(handle_);
delete probe_resident_;
}
ZipEntryCoverage ZipEntryCoverage::compute_coverage(const VmaRangeGroup& probe) const {
ZipEntryCoverage file_coverage;
file_coverage.info = info;
// Compute coverage for each range in file against probe which represents a set of ranges.
for (auto&& range : coverage.ranges) {
probe.compute_coverage(range, file_coverage.coverage);
}
return file_coverage;
}
std::vector<ZipEntryCoverage> ZipMemInspector::compute_coverage(
const std::vector<ZipEntryCoverage>& files, VmaRangeGroup* probe) {
if (probe == nullptr) {
// No probe to calculate coverage against, so coverage is zero.
return std::vector<ZipEntryCoverage>();
}
std::vector<ZipEntryCoverage> file_coverages;
// Find the file coverage against provided probe.
for (auto&& file : files) {
// For each file, compute coverage against the probe which represents a list of ranges.
ZipEntryCoverage file_coverage = file.compute_coverage(*probe);
file_coverages.push_back(file_coverage);
}
return file_coverages;
}
void ZipMemInspector::add_file_info(ZipEntryInfo& file) {
entry_infos_.push_back(file);
}
int ZipMemInspector::compute_per_file_coverage() {
if (entry_infos_.empty()) {
// We haven't read the file information yet, so do it now.
if (read_files_and_offsets()) {
cerr << "Could not read zip entries to compute coverages." << endl;
return 1;
}
}
// All existing files should consider their whole memory as present by default.
std::vector<ZipEntryCoverage> entry_coverages;
for (auto&& entry_info : entry_infos_) {
ZipEntryCoverage entry_coverage;
entry_coverage.info = entry_info;
VmaRange file_vma_range(entry_info.offset_in_zip, entry_info.file_size_bytes);
entry_coverage.coverage.ranges.push_back(file_vma_range);
entry_coverage.coverage.compute_total_size();
entry_coverages.push_back(entry_coverage);
}
if (probe_resident_ != nullptr) {
// We decided to compute coverage based on a probe
entry_coverages_ = compute_coverage(entry_coverages, probe_resident_);
} else {
// No probe means whole file coverage
entry_coverages_ = entry_coverages;
}
return 0;
}
VmaRangeGroup* ZipMemInspector::get_probe() {
return probe_resident_;
}
void ZipMemInspector::set_existing_probe(VmaRangeGroup* probe) {
this->probe_resident_ = probe;
}
std::vector<ZipEntryCoverage>& ZipMemInspector::get_file_coverages() {
return entry_coverages_;
}
int ZipMemInspector::probe_resident() {
probe_resident_ = new VmaRangeGroup();
int res = probe_resident_memory(filename_, *probe_resident_);
if (res != 0) {
// Failed to probe
return res;
}
return 0;
}
std::vector<ZipEntryInfo>& ZipMemInspector::get_file_infos() {
return entry_infos_;
}
int ZipMemInspector::read_files_and_offsets() {
if (OpenArchive(filename_.c_str(), &handle_) < 0) {
return 1;
}
void* cookie;
int res = StartIteration(handle_, &cookie);
if (res != 0) {
return 1;
}
ZipEntry64 entry;
string name;
while (Next(cookie, &entry, &name) == 0) {
ZipEntryInfo file;
file.name = name;
file.offset_in_zip = entry.offset;
file.file_size_bytes = entry.compressed_length;
file.uncompressed_size = entry.uncompressed_length;
entry_infos_.push_back(file);
}
return 0;
}