| #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; |
| } |