1# Copyright 2014 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import bisect
6
7
8PAGE_SIZE = 4096
9
10
11class Map(object):
12  """Models the memory map of a given |backends.Process|.
13
14  This is typically obtained by calling backends.Process.DumpMemoryMaps()."""
15
16  def __init__(self):
17    self.entries = []  # List of |MapEntry|, sorted by start address.
18
19  def Add(self, entry):
20    assert(isinstance(entry, MapEntry))
21    bisect.insort_right(self.entries, entry)
22
23  def Lookup(self, addr):
24    """Returns the MapEntry containing the given address, if any."""
25    idx = bisect.bisect_right(self.entries, addr) - 1
26    if idx < 0:
27      return None
28    entry = self.entries[idx]
29    assert(addr >= entry.start)
30    # bisect_right returns the latest element <= addr, but addr might fall after
31    # its end (in which case we want to return None here).
32    if addr > entry.end:
33      return None
34    return entry
35
36  def __getitem__(self, index):
37    return self.entries[index]
38
39  def __len__(self):
40    return len(self.entries)
41
42
43class MapEntry(object):
44  """An entry (address range + stats) in a memory |Map|."""
45
46  def __init__(self, start, end, prot_flags, mapped_file, mapped_offset,
47      priv_dirty_bytes=0, priv_clean_bytes=0, shared_dirty_bytes=0,
48      shared_clean_bytes=0, resident_pages=None):
49    assert(end > start)
50    assert(start >= 0)
51    self.start = start
52    self.end = end
53    self.prot_flags = prot_flags
54    self.mapped_file = mapped_file
55    self.mapped_offset = mapped_offset
56    self.priv_dirty_bytes = priv_dirty_bytes
57    self.priv_clean_bytes = priv_clean_bytes
58    self.shared_dirty_bytes = shared_dirty_bytes
59    self.shared_clean_bytes = shared_clean_bytes
60    # resident_pages is a bitmap (array of bytes) in which each bit represents
61    # the presence of its corresponding page.
62    self.resident_pages = resident_pages or []
63
64  def GetRelativeMMOffset(self, abs_addr):
65    """Converts abs_addr to the corresponding offset in the mm.
66
67    Returns:
68      A tuple: (page_index, offset_in_page)
69    """
70    assert(self.Contains(abs_addr))
71    offset = abs_addr - self.start
72    return (offset / PAGE_SIZE, offset & (PAGE_SIZE - 1))
73
74  def GetRelativeFileOffset(self, abs_addr):
75    """Converts abs_addr to the corresponding offset in the mapped file."""
76    assert(self.Contains(abs_addr))
77    return abs_addr - self.start + self.mapped_offset
78
79  def IsPageResident(self, relative_page_index):
80    """Checks whether a given memory page is resident in memory."""
81    assert(relative_page_index >= 0 and
82           relative_page_index < self.len / PAGE_SIZE)
83    arr_idx = relative_page_index / 8
84    arr_bit = relative_page_index % 8
85    # Trailing zeros are trimmed by memdump (to optimize dump time).
86    if arr_idx >= len(self.resident_pages):
87      return False
88    return (self.resident_pages[arr_idx] & (1 << arr_bit)) != 0
89
90  def Contains(self, abs_addr):
91    """Determines whether a given absolute address belongs to the current mm."""
92    return abs_addr >= self.start and abs_addr <= self.end
93
94  def __cmp__(self, other):
95    """Comparison operator required for bisect."""
96    if isinstance(other, MapEntry):
97      return self.start - other.start
98    elif isinstance(other, (long, int)):
99      return self.start - other
100    else:
101      raise Exception('Cannot compare with %s' % other.__class__)
102
103  @property
104  def len(self):
105    return self.end - self.start + 1
106
107  @property
108  def num_pages(self):
109    return self.len / PAGE_SIZE
110
111  @property
112  def rss_bytes(self):
113    return (self.priv_dirty_bytes + self.priv_clean_bytes +
114            self.shared_dirty_bytes + self.shared_clean_bytes)
115