#!/usr/bin/python # coding=utf-8 """Tool to analyze and display the contents of /proc//maps""" import re import itertools import argparse from dataclasses import dataclass MAPS_LINE_RE = re.compile(r""" (?P[0-9a-f]+)-(?P[0-9a-f]+)\s+ # Address (?P\S+)\s+ # Permissions (?P[0-9a-f]+)\s+ # Map offset (?P\S+)\s+ # Device node (?P\d+)\s+ # Inode (?P.*)\s+ # Pathname """, re.VERBOSE) def human_bytes(size): modifier = 1 while size > 1024: modifier *= 1024 size /= 1024 return "%.1f%s" % (size, { 1024**0: 'b', 1024**1: 'k', 1024**2: 'M', 1024**3: 'G', 1024**4: 'T', }.get(modifier, " x%d" % modifier)) @dataclass class Record: addr_start: int addr_end: int perms: str offset: int dev: str inode: int pathname: str @property def size(self): return self.addr_end - self.addr_start @property def human_size(self): return human_bytes(self.size) @property def readable(self): return self.perms[0] == "r" @property def writable(self): return self.perms[1] == "w" @property def executable(self): return self.perms[2] == "x" @property def shared(self): return self.perms[3] == "s" @property def private(self): return self.perms[3] == "p" @classmethod def parse(self, pid): records = [] with open("/proc/%d/maps" % pid) as fd: for line in fd: m = MAPS_LINE_RE.match(line) if not m: print("Skipping: %s" % line) continue addr_start, addr_end, perms, offset, dev, inode, pathname = m.groups() addr_start = int(addr_start, 16) addr_end = int(addr_end, 16) offset = int(offset, 16) records.append(Record( addr_start=addr_start, addr_end=addr_end, perms=perms, offset=offset, dev=dev, inode=inode, pathname=pathname, )) return records @classmethod def aggregate(self, records, only_used=False, only_private=False): named_records = {} anonymous_records = [] for record in records: if only_private and not record.private: continue if only_used and not record.readable and not record.writable and not record.shared and not record.pathname: continue if record.pathname: if record.pathname in named_records: other = named_records[record.pathname] named_records[record.pathname] = Record( min(record.addr_start, other.addr_start), max(record.addr_end, other.addr_end), perms=''.join("?" if c1 != c2 else c1 for c1, c2 in zip(record.perms, other.perms)), offset=0, dev='', inode='', pathname=record.pathname, ) else: named_records[record.pathname] = record else: anonymous_records.append(record) return list(sorted( itertools.chain(anonymous_records, named_records.values()), key=lambda r: r.size, reverse=True, )) if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument("pid", type=int, help="Process identifier (pid)") parser.add_argument("--only-used", "-u", action="store_true", help="Only show used pages (non readable, writable, executable and private pages)") parser.add_argument("--only-private", "-p", action="store_true", help="Only show private pages") args = parser.parse_args() records = Record.parse(args.pid) #records = Record.aggregate(records, only_used=args.only_used, only_private=args.only_private) print("\t".join([ "% 16s" % "Start of range", "% 16s" % "End of range", "% 12s" % "Size", "% 4s" % "Perms", "Path", ])) for record in records: print("\t".join([ "%016x" % record.addr_start, "%016x" % record.addr_end, "% 12s" % record.human_size, "% 4s" % record.perms, record.pathname, ])) print("") print("Total: %s" % human_bytes(sum(r.size for r in records)))