File size: 5,906 Bytes
efc2358
0f74380
 
efc2358
 
 
0f74380
 
 
 
 
 
 
 
 
 
 
 
efc2358
 
 
 
 
 
 
 
 
 
 
 
 
 
0f74380
efc2358
 
c229485
 
 
 
0f74380
c229485
 
 
 
 
 
bd11be5
 
 
 
dd17a03
 
 
 
 
bd11be5
0f74380
 
 
efc2358
 
 
 
 
 
 
 
 
 
 
 
0f74380
 
fb223ca
0f74380
efc2358
0f74380
 
1909c69
0f74380
 
c229485
fb223ca
 
c229485
 
 
 
 
 
 
 
 
fb223ca
 
0f74380
 
efc2358
 
 
1909c69
 
0f74380
fb223ca
1909c69
c229485
 
1909c69
c229485
1909c69
 
 
 
 
 
 
bd11be5
 
 
dd17a03
bd11be5
 
 
 
 
1909c69
c229485
17ac6a1
 
 
0f74380
 
 
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import platform
import shlex
import subprocess
import tarfile
import tempfile
import urllib.request
from pathlib import Path

import gradio as gr
import nvidia


def find_tool(name):
    path = next(p for d in nvidia.__path__ if (p := Path(d) / "cu13/bin" / name).exists())
    path.chmod(0o755)
    return str(path)


def fetch_legacy_nvdisasm(version):
    arch = "linux-sbsa" if platform.machine() == "aarch64" else "linux-x86_64"
    name = f"cuda_nvdisasm-{arch}-{version}-archive"
    path = Path(tempfile.gettempdir()) / name / "nvdisasm"
    if not path.exists():
        url = f"https://developer.download.nvidia.com/compute/cuda/redist/cuda_nvdisasm/{arch}/{name}.tar.xz"
        with urllib.request.urlopen(url) as r, tarfile.open(fileobj=r, mode="r|xz") as tar:
            member = next(m for m in tar if m.name == f"{name}/bin/nvdisasm")
            path.parent.mkdir(exist_ok=True)
            path.write_bytes(tar.extractfile(member).read())
        path.chmod(0o755)
    return str(path)


TOOLS = {name: find_tool(name) for name in ("cuobjdump", "nvdisasm")}
# nvdisasm 13.x can't run flow analysis (CFGs) on old-format CUDA ELF (OSABI 0x33, pre-CUDA 13); 12.9 can.
LEGACY_NVDISASM = fetch_legacy_nvdisasm("12.9.88")
OUTPUTS = {
    "cuobjdump": ["--dump-sass", "--dump-ptx", "--dump-elf", "--dump-resource-usage", "--dump-elf-symbols",
                  "--list-elf", "--list-ptx"],
    "nvdisasm": ["--print-code", "--output-control-flow-graph", "--output-control-flow-graph-with-basic-blocks"],
}
MODIFIERS = {
    "cuobjdump": [],
    "nvdisasm": ["--print-instruction-encoding", "--print-life-ranges", "--print-raw", "--print-line-info",
                 "--print-line-info-inline"],
}
EXTRA_EXAMPLE = {"cuobjdump": "--function vecadd", "nvdisasm": "--emit-json"}
GRAPHVIZ_HEAD = "".join(f'<script src="https://cdn.jsdelivr.net/npm/{lib}"></script>' for lib in (
    "d3@7.9.0/dist/d3.min.js", "@hpcc-js/wasm@2.20.0/dist/graphviz.umd.js", "d3-graphviz@5.6.0/build/d3-graphviz.min.js"))
GRAPHVIZ_JS = """
const graph = element.querySelector('.graph');
d3.select(graph).graphviz({useWorker: false, zoom: true}).renderDot(props.value, () => {
  const svg = graph.querySelector('svg');
  svg.setAttribute('width', '100%');
  svg.setAttribute('height', Math.min(svg.viewBox.baseVal.height * 4 / 3, 600));
});
"""
EXAMPLES = sorted(str(p) for p in Path(__file__).with_name("examples").glob("*.cubin"))


def version(exe):
    return subprocess.run([exe, "--version"], capture_output=True, text=True).stdout.split("release ")[-1].splitlines()[0]


def is_old_elf(file):
    with open(file, "rb") as f:
        head = f.read(8)
    return head[:4] == b"\x7fELF" and head[7] == 0x33


def executable(tool, file):
    return LEGACY_NVDISASM if tool == "nvdisasm" and is_old_elf(file) else TOOLS[tool]


def run_one(tool, args, file):
    try:
        r = subprocess.run([executable(tool, file), *args, file], capture_output=True, text=True, timeout=60)
    except subprocess.TimeoutExpired:
        return "Timed out after 60s."
    return (r.stdout + r.stderr or "(no output)") + (f"\n[exit code {r.returncode}]" if r.returncode else "")


def run(file, tool, outputs, modifiers, extra):
    if file is None:
        return {"error": "Upload a cubin first."}
    common = [*modifiers, *shlex.split(extra or "")]
    return {" ".join(args): run_one(tool, args, file) for args in ([o, *common] for o in outputs)} or \
           {" ".join(common) or tool: run_one(tool, common, file)}


def select_tool(tool):
    return (gr.CheckboxGroup(OUTPUTS[tool], value=OUTPUTS[tool]),
            gr.CheckboxGroup(MODIFIERS[tool], value=[], visible=bool(MODIFIERS[tool])),
            gr.Textbox(placeholder=EXTRA_EXAMPLE[tool]))


with gr.Blocks(title="cuobjdump / nvdisasm") as demo:
    gr.Markdown("# cuobjdump / nvdisasm\n"
                "Disassemble CUDA cubins, fatbins and ELF objects with NVIDIA's tools ("
                + ", ".join(f"`{t}` {version(exe)}" for t, exe in TOOLS.items())
                + f"; old-format CUDA ELF goes to `nvdisasm` {version(LEGACY_NVDISASM)}).")
    with gr.Row(equal_height=True):
        with gr.Column(scale=1):
            file = gr.File(label="cubin / fatbin / CUDA ELF", type="filepath")
        with gr.Column(scale=2):
            tool = gr.Radio(list(TOOLS), value="cuobjdump", label="Tool")
            outputs = gr.CheckboxGroup(OUTPUTS["cuobjdump"], value=OUTPUTS["cuobjdump"], label="Outputs (each runs separately)")
            modifiers = gr.CheckboxGroup(MODIFIERS["cuobjdump"], label="Display options (applied to every run)", visible=False)
            with gr.Row(equal_height=True):
                extra = gr.Textbox(label="Extra arguments", placeholder=EXTRA_EXAMPLE["cuobjdump"], scale=4)
                btn = gr.Button("Run", variant="primary", scale=1)
    gr.Examples(EXAMPLES, file)
    results = gr.State({})

    @gr.render(inputs=results)
    def show(results):
        for label, output in results.items():
            if output.startswith("digraph"):
                gr.Markdown(f"`{label}` (scroll to zoom, drag to pan)")
                gr.HTML(output.replace("\t", "    "), head=GRAPHVIZ_HEAD, js_on_load=GRAPHVIZ_JS,
                        html_template='<div class="graph" style="background:white;'
                                      'border:1px solid var(--border-color-primary)"></div>')
                with gr.Accordion(f"{label} (DOT source)", open=False):
                    gr.Code(output, max_lines=60)
            else:
                gr.Code(output, label=label, max_lines=60)

    tool.change(select_tool, tool, [outputs, modifiers, extra])
    (btn.click(lambda: gr.Button("Running…", interactive=False), None, btn)
        .then(run, [file, tool, outputs, modifiers, extra], results)
        .then(lambda: gr.Button("Run", interactive=True), None, btn))

if __name__ == "__main__":
    demo.launch()