#!/usr/bin/env python3
"""Runtime tracer for the SvT boot stall.

Static analysis and black-box probing both bottomed out: the client spin-waits
with zero I/O and no observable logging in the region between Dataset.Sync
completing and the frontend loading. This attaches to the live process and
hooks the Dataset methods directly, using x86 addresses recovered from
CodeRegistration.methodPointers in lib/x86/libil2cpp.so (1.19.22110).
"""

import json
import sys
import time

import frida

ADDRS = json.load(open("/root/ants/x86addrs.json"))

JS = r"""
var addrs = %s;
var base = null;

function install() {
    var m = Process.findModuleByName("libil2cpp.so");
    if (m === null) return false;
    base = m.base;
    send({t: "info", m: "libil2cpp.so @ " + base + "  size=" + m.size});

    for (var name in addrs) {
        (function (name, rva) {
            var addr = base.add(rva);
            try {
                Interceptor.attach(addr, {
                    onEnter: function (args) {
                        send({t: "call", n: name});
                    }
                });
            } catch (e) {
                send({t: "err", m: name + ": " + e});
            }
        })(name, addrs[name]);
    }
    send({t: "info", m: "hooks installed: " + Object.keys(addrs).length});
    return true;
}

if (!install()) {
    // libil2cpp is dlopen'd after process start; wait for it
    var iv = setInterval(function () {
        if (install()) clearInterval(iv);
    }, 200);
}
"""


def main():
    dev = frida.get_device_manager().add_remote_device("127.0.0.1:27042")
    pkg = "se.foglo.svt"
    print("spawning", pkg)
    pid = dev.spawn([pkg])
    session = dev.attach(pid)
    script = session.create_script(JS % json.dumps(ADDRS))

    counts = {}
    order = []

    def on_message(msg, data):
        if msg["type"] != "send":
            print("!!", msg)
            return
        p = msg["payload"]
        if p["t"] == "call":
            n = p["n"]
            counts[n] = counts.get(n, 0) + 1
            if counts[n] <= 3:
                order.append("%7.2fs  %s" % (time.time() - t0, n))
                print("%7.2fs  ->  %s" % (time.time() - t0, n))
        else:
            print("[%s] %s" % (p["t"], p.get("m")))

    script.on("message", on_message)
    script.load()
    t0 = time.time()
    dev.resume(pid)

    dur = int(sys.argv[1]) if len(sys.argv) > 1 else 60
    time.sleep(dur)

    print("\n=== call counts over %ds ===" % dur)
    for n, c in sorted(counts.items(), key=lambda x: -x[1]):
        print("  %-34s %d" % (n, c))
    if not counts:
        print("  (no Dataset methods called at all)")


if __name__ == "__main__":
    main()
