Repository navigation
docs(hrr): document what a capture archive contains and that replay runs its code #13625
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
andraghetti
merged 5 commits into
develop
from
users/andraghetti/hrr-capture-privacy-page
Oct 19, 2026
Merged
Changes from 2 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
c597948
docs(hrr): document what a capture archive contains and that replay r…
andraghetti cdb45b0
docs(hrr): correct the privacy page's pointer, snapshot, marker and s…
andraghetti 30cb8f6
docs(hrr): list linker input buffers in the privacy page's inventory
andraghetti 0da567c
docs(hrr): list the input structures capture copies into events.bin
andraghetti f19e95b
docs(hrr): say replay passes a non-address pointer argument through
andraghetti File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| # What an HRR archive contains, and what replaying one means | ||
|
|
||
| This page states what a capture archive can and cannot contain, and what a person who | ||
| replays one is trusting. It is the customer-facing text of | ||
| [AIRUNTIME-2733](https://amd-hub.atlassian.net/browse/AIRUNTIME-2733) (capture privacy and | ||
| content control). The engineering threat model is | ||
| [AIRUNTIME-2853](https://amd-hub.atlassian.net/browse/AIRUNTIME-2853). | ||
|
|
||
| Every task under AIRUNTIME-2733 that changes what an archive holds updates this page in the | ||
| same change. `tools/tests/test_privacy_doc.py`, run by the ctest `hrr-generator-rules`, | ||
| fails when the manifest fields written by capture and the table below disagree. | ||
|
|
||
| ## Replay runs GPU code taken from the archive | ||
|
|
||
| An archive is untrusted executable input for the tool that replays it. `hrr-playback` loads | ||
| the code objects stored in the archive and launches their kernels on the GPU. That code runs | ||
| with the replaying process's access to device memory and to any host memory mapped for the | ||
| GPU, and nothing in the replay tool can check what it does. The tool refuses malformed | ||
| records and caps memory, events and run time, but it cannot make the code itself safe. | ||
| Replay maps recorded pointers to the buffers it allocated so that the run can be rebuilt; | ||
| this is reconstruction, not a safety boundary. Only a value that matches a recorded | ||
| allocation is translated: a pointer argument that matches none is passed as null, and every | ||
| other argument byte, including a pointer-sized word inside a struct that matches none, is | ||
| passed as recorded, so a kernel can still receive addresses from the original process. | ||
|
|
||
| Replay an archive only if you trust whoever produced it, as an unprivileged user, on a | ||
| machine and GPU that hold no data you would not hand to that person. A container is a | ||
| reasonable boundary. `hrr-playback --info` reads the archive without loading any code and | ||
| needs no GPU. | ||
|
|
||
| ## Capture modes | ||
|
|
||
| Capture is disabled by default on AMD platforms at present (see the [README](README.md)); | ||
| where it is built in, it is armed by `HIP_HRR_CAPTURE_OUTPUT` alone. Today there is one | ||
| mode, and it records **contents**: everything in the tables below. | ||
|
|
||
| [AIRUNTIME-2880](https://amd-hub.atlassian.net/browse/AIRUNTIME-2880) adds a | ||
| **metadata-only** mode as the default and moves contents behind a second variable. The | ||
| "Mode" column states which mode records each item once that task has landed. Until then, | ||
| every capture is a contents capture. | ||
|
|
||
| ## What an archive contains | ||
|
|
||
| | Item | Where | Mode | Removed or reduced by | | ||
| |---|---|---|---| | ||
| | Sequence of successful HIP calls, with sizes, flags, device addresses, handles, thread ids | `events.bin` | both | — kept, replay needs it | | ||
| | Kernel names, mangled but read back with `c++filt`; they describe the model's architecture and precision | `events.bin` | both | — kept, triage names the failing kernel | | ||
| | Event timestamps, from a monotonic clock (not the date or time of day) | `events.bin` | both | — kept | | ||
| | Host buffers copied to or from the device, byte for byte, unencrypted. For a serving stack this includes prompts and generated output as token ids, which the model's public tokeniser reverses, and the model weights | `blobs/` | contents | [AIRUNTIME-2880](https://amd-hub.atlassian.net/browse/AIRUNTIME-2880) | | ||
| | Host memory registered with `hipHostRegister`: the whole range, byte for byte, as it was at registration, whether or not it is ever copied | `blobs/` | contents | [AIRUNTIME-2880](https://amd-hub.atlassian.net/browse/AIRUNTIME-2880) | | ||
| | Kernel argument bytes | `events.bin` | contents | [AIRUNTIME-2880](https://amd-hub.atlassian.net/browse/AIRUNTIME-2880) | | ||
| | Code objects the process loaded (the application's own GPU binary code) | `code_objects/`, `blobs/` | contents | [AIRUNTIME-2880](https://amd-hub.atlassian.net/browse/AIRUNTIME-2880) | | ||
| | Whole fat binary bundles, including targets HIP never loads | `blobs/` | contents | [AIRUNTIME-2858](https://amd-hub.atlassian.net/browse/AIRUNTIME-2858): only the code objects HIP loads | | ||
| | Debug and comment sections of code objects: absolute build paths, source file names, the exact compiler build | `code_objects/`, `blobs/` | contents | [AIRUNTIME-2881](https://amd-hub.atlassian.net/browse/AIRUNTIME-2881): stripped | | ||
| | Framework allocator layout, when a region producer ran beside capture | `regions/` | both | — kept, written only by an opt-in producer | | ||
| | Marker that capture is running: on Linux the boot id and the process start time in clock ticks since boot; on Windows the process creation date and time. A normal exit removes it; a process that crashes or is killed leaves it | `active` | both | — kept, region producers read it | | ||
|
|
||
| The per-process `manifest.json` also holds `pid`, `parent_pid`, `complete`, `event_count`, | ||
| `blob_count`, the HIP and comgr versions, and one block per device: | ||
|
|
||
| | Device field | Kept | Removed by | | ||
| |---|---|---| | ||
| | `name`, `gcn_arch_name`, `compute_capability` | yes | — | | ||
| | `total_global_mem`, `multi_processor_count`, `warp_size`, `max_threads_per_block` | yes | — | | ||
| | `clock_rate_khz`, `memory_clock_rate_khz`, `memory_bus_width`, `l2_cache_size` | yes | — | | ||
| | `integrated`, `managed_memory`, `memory_pools_supported`, `compute_mode` | yes | — | | ||
| | `uuid`, `pci`, `luid`, `luid_device_node_mask`: identify the individual GPU and its slot | no | [AIRUNTIME-2881](https://amd-hub.atlassian.net/browse/AIRUNTIME-2881): blanked | | ||
|
|
||
| ## Never recorded | ||
|
|
||
| Capture does not read the host name, the command line, environment variables or the user | ||
| name, and records a date and time of day only in the `active` marker on Windows. A string | ||
| reaches the archive only as a kernel name; as a string argument of a HIP call (the path | ||
| given to `hipLinkAddFile`, the name given to `hipLinkAddData`, the function name given to | ||
| `hipGetProcAddress`); as the name of a device variable the program registers; inside a host | ||
| buffer the workload copied or registered; or inside a code object. A path in such a buffer, | ||
| for example a compiler cache path under the user's home directory, is recorded with it. | ||
| Memory that never crossed a HIP call is not recorded, and neither is a failed call, apart | ||
| from the exceptions listed in [skills/enable-recording/SKILL.md](skills/enable-recording/SKILL.md). | ||
|
|
||
| ## What no mode can promise | ||
|
|
||
| A metadata-only archive with identifiers blanked still names every kernel the workload | ||
| ran, which describes the model and the serving stack, and still records every size and | ||
| address. It holds no buffer contents and no code, so it cannot reproduce a fault that | ||
| depends on values, and it cannot be replayed in full. A contents archive is as sensitive | ||
| as the data the workload handled: where it may go has to be agreed before it is sent. | ||
|
|
||
| ## How the archive is protected on disk | ||
|
|
||
| On Linux the archive is readable only by the user who captured it (directories 0700, files | ||
| 0600). [AIRUNTIME-2847](https://amd-hub.atlassian.net/browse/AIRUNTIME-2847) makes every | ||
| write go through the directory descriptors capture checked. | ||
| [AIRUNTIME-2857](https://amd-hub.atlassian.net/browse/AIRUNTIME-2857) decides whether | ||
| capture must ask for consent before it starts. | ||
| [AIRUNTIME-2878](https://amd-hub.atlassian.net/browse/AIRUNTIME-2878) keeps the crash-time | ||
| flush without displacing the application's own crash handler. None of these changes what an | ||
| archive contains. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| # Copyright (c) Advanced Micro Devices, Inc., or its affiliates. | ||
| # | ||
| # SPDX-License-Identifier: MIT | ||
| """PRIVACY.md lists what capture writes into an archive. | ||
|
|
||
| The page is the customer text for AIRUNTIME-2733. Its device table has to name | ||
| exactly the fields hip_capture_metadata.cpp writes into a manifest, so a task | ||
| that adds, removes or blanks one cannot land without updating the page. Its | ||
| inventory has to list the active marker and the hipHostRegister snapshot. | ||
|
|
||
| Run: python3 -m unittest discover -s projects/hrr/tools/tests | ||
| """ | ||
|
|
||
| import re | ||
| import unittest | ||
| from pathlib import Path | ||
|
|
||
| HRR = Path(__file__).resolve().parents[2] | ||
| PRIVACY = HRR / "PRIVACY.md" | ||
| CAPTURE_SRC = HRR.parent / "clr" / "hipamd" / "src" / "hrr" | ||
| METADATA_CPP = CAPTURE_SRC / "hip_capture_metadata.cpp" | ||
|
|
||
| # The tasks under AIRUNTIME-2733 that the page links, one per statement. | ||
| EPIC_TASKS = ( | ||
| "AIRUNTIME-2847", | ||
| "AIRUNTIME-2857", | ||
| "AIRUNTIME-2858", | ||
| "AIRUNTIME-2878", | ||
| "AIRUNTIME-2880", | ||
| "AIRUNTIME-2881", | ||
| ) | ||
|
|
||
|
|
||
| def device_fields_written() -> set: | ||
| """The keys append_prop_fields writes into each manifest device block.""" | ||
| text = METADATA_CPP.read_text(encoding="utf-8") | ||
| body = text.split("void append_prop_fields(", 1)[1].split("\n}\n", 1)[0] | ||
| return set(re.findall(r'\\"(\w+)\\": ', body)) - {"properties"} | ||
|
|
||
|
|
||
| def inventory_rows() -> list: | ||
| """The rows of the page's "What an archive contains" table.""" | ||
| text = PRIVACY.read_text(encoding="utf-8") | ||
| table = text.split("| Item | Where |", 1)[1].split("\n\n", 1)[0] | ||
| return table.splitlines()[2:] | ||
|
|
||
|
|
||
| def device_fields_documented() -> set: | ||
| """The backticked names in the first column of the page's device table.""" | ||
| text = PRIVACY.read_text(encoding="utf-8") | ||
| table = text.split("| Device field |", 1)[1].split("\n\n", 1)[0] | ||
| fields = set() | ||
| for row in table.splitlines()[2:]: | ||
| fields.update(re.findall(r"`(\w+)`", row.split("|")[1])) | ||
| return fields | ||
|
|
||
|
|
||
| class PrivacyDocTest(unittest.TestCase): | ||
| def test_device_table_matches_the_manifest_capture_writes(self): | ||
| written = device_fields_written() | ||
| self.assertIn("uuid", written) | ||
| self.assertEqual(device_fields_documented(), written) | ||
|
|
||
| def test_inventory_lists_the_active_marker_capture_writes(self): | ||
| writer = (CAPTURE_SRC / "hip_capture_writer.cpp").read_text(encoding="utf-8") | ||
| name = re.search(r'kActiveMarker = "/(\w+)"', writer).group(1) | ||
| self.assertTrue( | ||
| any(row.split("|")[2].strip() == f"`{name}`" for row in inventory_rows()) | ||
| ) | ||
|
|
||
| def test_inventory_lists_the_host_register_snapshot(self): | ||
| capture = (CAPTURE_SRC / "hip_capture.cpp").read_text(encoding="utf-8") | ||
| body = capture.split("hipError_t capture_hipHostRegister(", 1)[1] | ||
| body = body.split("\n}\n", 1)[0] | ||
| self.assertIn("write_blob(", body) | ||
| self.assertTrue( | ||
| any("`hipHostRegister`" in row and "`blobs/`" in row for row in inventory_rows()) | ||
| ) | ||
|
|
||
| def test_states_that_an_archive_is_untrusted_executable_input(self): | ||
| text = PRIVACY.read_text(encoding="utf-8") | ||
| self.assertIn("untrusted executable input", text) | ||
| self.assertIn("launches their kernels on the GPU", text) | ||
|
|
||
| def test_links_each_epic_task(self): | ||
| text = PRIVACY.read_text(encoding="utf-8") | ||
| for key in EPIC_TASKS: | ||
| self.assertIn(f"https://amd-hub.atlassian.net/browse/{key}", text) | ||
|
|
||
| def test_readme_and_recording_skill_link_the_page(self): | ||
| readme = (HRR / "README.md").read_text(encoding="utf-8") | ||
| skill = (HRR / "skills" / "enable-recording" / "SKILL.md").read_text( | ||
| encoding="utf-8" | ||
| ) | ||
| self.assertIn("](PRIVACY.md)", readme) | ||
| self.assertIn("](../../PRIVACY.md)", skill) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.