#!/usr/bin/env python3
"""Énumère les régions mémoire d'un minidump, les attribue aux modules,
et met en évidence les grosses allocations privées (là où un packer déballe)."""
import struct, sys, mmap, math, collections

def ent(b):
    if len(b) < 256: return 0.0
    c = collections.Counter(b); n = len(b)
    return -sum(v/n*math.log2(v/n) for v in c.values())

def main(path, topn=18):
    f = open(path, "rb"); m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
    nst, dr = struct.unpack_from("<II", m, 8)
    st = {}
    for i in range(nst):
        t, s, r = struct.unpack_from("<III", m, dr + i*12); st[t] = (s, r)

    mods = []
    _, rva = st[4]
    for i in range(struct.unpack_from("<I", m, rva)[0]):
        o = rva + 4 + i*108
        base = struct.unpack_from("<Q", m, o)[0]
        size = struct.unpack_from("<I", m, o+8)[0]
        nr   = struct.unpack_from("<I", m, o+20)[0]
        ln   = struct.unpack_from("<I", m, nr)[0]
        nm   = m[nr+4:nr+4+ln].decode("utf-16-le","replace").split("\\")[-1]
        mods.append((base, size, nm))

    def owner(va):
        for b, s, nm in mods:
            if b <= va < b+s: return nm
        return None

    _, rva = st[9]
    nr, br = struct.unpack_from("<QQ", m, rva)
    regions, off = [], br
    for i in range(nr):
        s, sz = struct.unpack_from("<QQ", m, rva+16+i*16)
        regions.append((sz, s, off)); off += sz

    tot = sum(r[0] for r in regions)
    priv = [r for r in regions if owner(r[1]) is None]
    ptot = sum(r[0] for r in priv)
    print(f"{path}")
    print(f"  {len(regions)} régions · {tot/1048576:.0f} Mo · {len(mods)} modules")
    print(f"  hors module : {len(priv)} régions · {ptot/1048576:.0f} Mo "
          f"({ptot/tot*100:.0f} % du dump)\n")
    priv.sort(reverse=True)
    print(f"  {'VA':>16} {'taille':>11}  {'H':>5}  aperçu")
    for sz, va, fo in priv[:topn]:
        data = m[fo:fo+min(sz, 1<<20)]
        h = ent(data[:1<<18])
        txt = bytes(c if 32 <= c < 127 else 46 for c in data[:44]).decode()
        print(f"  0x{va:014x} {sz/1048576:8.2f} Mo {h:6.2f}  {txt}")
    return [(sz, va, fo) for sz, va, fo in priv]

if __name__ == "__main__":
    main(sys.argv[1], int(sys.argv[2]) if len(sys.argv) > 2 else 18)
