aboutsummaryrefslogtreecommitdiffstats
path: root/experiments/profile_remote.py
blob: fbf17dc8b03f1f75a6ca47a694aded55f6039704 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
"""Profile persistent ida-tui operations inside the IDA process.

The profiler itself is a typed ``RemoteModule`` function in ``remote_tools.py``;
this file contains no generated Python source or knowledge of remote module names.

Usage::

    PYTHONPATH=. python experiments/profile_remote.py [BINARY]
    PYTHONPATH=. python experiments/profile_remote.py --op decompile
"""

from __future__ import annotations

import argparse
import os

from idatui import remote_ops
from idatui.nexus_client import NexusClient

CALLS = {
    "heads": ("heads", {"count": 500, "annotate": True}),
    "heads_plain": ("heads", {"count": 500, "annotate": False}),
    "heads_skeleton": (
        "heads",
        {"count": 500, "annotate": True, "text": False},
    ),
    "decompile": ("decompile", {}),
}


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("binary", nargs="?", default="targets/bash")
    parser.add_argument("--op", default="heads", choices=sorted(CALLS))
    parser.add_argument("--reps", type=int, default=20)
    parser.add_argument("--addr", default=None, help="default: the .text start")
    args = parser.parse_args()

    client = NexusClient(os.path.abspath(args.binary)).connect()
    addr = args.addr
    if addr is None:
        regions = client.call(remote_ops.file_regions)
        rows = regions.get("regions") or regions.get("result") or []
        text = next(
            (row for row in rows if ".text" in str(row.get("name", ""))),
            None,
        )
        addr = (text or rows[0])["start"] if rows else "0x0"

    operation, call_args = CALLS[args.op]
    call_args = {"addr": addr, **call_args}
    print(
        f"# {os.path.basename(args.binary)}  op={args.op}  "
        f"addr={addr}  reps={args.reps}"
    )

    # Install the persistent tool module and warm its caches before profiling.
    client.call(remote_ops.heads, addr=addr, count=500, annotate=True)
    out = client.call(
        remote_ops.profile_remote,
        operation=operation,
        args=call_args,
        reps=args.reps,
    )
    per = out["total"] / out["reps"] * 1000
    print(f"# {out['total'] * 1000:.0f}ms total, {per:.1f}ms per call\n")
    print(out["stats"])
    client.close()
    return 0


if __name__ == "__main__":
    raise SystemExit(main())