You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
HRX: libhip_intercept wraps only 331 of 540 HIP symbols (rest silently unobservable) #410
HRX: libhip_intercept wraps only 331 of 540 HIP symbols, so the rest are silently unobservable
libhip_intercept.so is the drop-in proxy that forwards HIP calls to the real backend and
optionally routes them through a logging/tracing interceptor. It exports 331 of the 540 symbols that HRX's libamdhip64.so exports, so 215 calls are never intercepted.
This does not break those calls — the proxy loads the backend with RTLD_NOW | RTLD_GLOBAL, so unwrapped functions resolve directly against the backend. The
effect is that any tool relying on the proxy for visibility (logging, tracing, buffer
inspection) sees nothing at all for those 215 functions.
Repro
Compare the two export tables:
nm -D --defined-only libamdhip64.so.7 | awk '{print $3}'| sed 's/@.*//'| sort -u > A # 540
nm -D --defined-only libhip_intercept.so | awk '{print $3}'| sed 's/@.*//'| sort -u > B # 331
comm -23 A B | wc -l # 215
215 symbols present in the backend and absent from the proxy. 166 of those 215 have real
implementations in HRX (the remainder are the hipErrorNotSupported stubs).
Confirmed at runtime that the calls still work
hipDeviceGetExecutionCtx (HIP 7.14, a real implementation, absent from the proxy's table):
direct backend hipDeviceGetExecutionCtx -> 0 (valid ctx handle)
via libhip_intercept hipDeviceGetExecutionCtx -> 0 (valid ctx handle)
So the gap costs observability, not correctness. Filed as a visibility defect.
Examples of the 166 real functions that are not intercepted
The proxy also exports six symbols that HRX's libamdhip64.so does not export at all: __hipRegisterSurface, hipGLGetDevices, hipGraphicsGLRegisterBuffer, hipGraphicsGLRegisterImage, plus its own hip_passthrough_get_active_table / hip_passthrough_get_real_table.
That strongly suggests the proxy's symbol list was generated against an older or upstream HIP
export set and has not been regenerated against the current HRX ABI. Regenerating it from nm -D of the built libamdhip64.so should close the gap.
Follow-up: two additional defects found while re-verifying this report
Re-testing this issue with the proxy correctly preloaded (LD_PRELOAD=<...>/libhip_intercept.so
— my original test had it only on LD_LIBRARY_PATH, so it was never loaded) confirmed the
coverage gap above and surfaced two more problems.
Confirmed: the coverage gap is real
hipGetDeviceCount (present in the proxy's export table) is logged: hipGetDeviceCount() -> count=1, ret=0
hipDeviceGetExecutionCtx (absent from the proxy's export table) is not logged at all, yet
the call succeeds. Consistent with the original analysis: missing wrapper ⇒ not intercepted, not
broken.
New defect 1 — the documented interceptor variable does not exist in the code
Variable
libhrx/src/passthrough/hip_intercept.creads
HIP_INTERCEPTION_LIBRARY
libhrx/src/passthrough/README.mddocuments
HIP_PASSTHROUGH_INTERCEPTOR
The README's name is never read. A user following the documentation gets a silently
un-instrumented run with no warning — which is exactly how I first misdiagnosed the logging
interceptor as broken. Suggest reading both names (documented one taking precedence) or fixing
the README.
Full env surface read by hip_intercept.c: HIP_PASSTHROUGH_BACKEND_LIB, HIP_INTERCEPTION_LIBRARY, HIP_LOG_FILE, HIP_LOG_LEVEL.
early_init calls intercept_init() directly from an early constructor while every public
entry point goes through the pthread_once-guarded ensure_init(). Initialisation therefore
runs from the constructor and through the guard.
Observed consequence: HIP Logging initialized printed three times in one short run, with a
matching HIP Logging shutting down each time. Because hip_interceptor_init opens the log with fopen(path, "w"), a re-init truncates the log earlier calls are still writing to — my
captured log contained the mid-line fragment nt=0, ret=3, the tail of a line whose start had
been truncated away.
Fix: have early_init call ensure_init() so both routes share the guard, and make hip_interceptor_init idempotent.
Correction to my earlier comment
The hip_logging "writes no output" report I filed separately was my error and has been
closed. It works — including HIP_LOG_FILE — when the proxy is actually preloaded and the
process is not run under an environment where the target file is unwritable. Only the two
defects above are genuine.
Measured directly: of the 10 hipExecutionCtx* / hipDevResource* symbols HRX exports,
the proxy covers 0.
So the 215-symbol gap is a generation-version boundary, not a random omission: the stub file
was produced against a ROCm libamdhip64.so that predates HIP 7.14, and has not been regenerated
since.
Fix
Regenerate stubs_generated.c against the current HRX libamdhip64.so (or a current ROCm
build), so the node set includes hip_7.14. The generator's own convention already does the
right thing — .symver per symbol with the node taken from the source library — so this is a
regeneration, not a redesign.
A cheap guard would be to assert during generation that the produced node set equals the node set
in HRX's own amdhip64.map, which fails loudly the next time a version node is added upstream
rather than silently leaving a gap.
All three defects fixed (fork), and the fix is bigger than the report implies
Fixed on 1bit-MONSTER/hrx-system1bit/main — PR 1bit-MONSTER/hrx-system#3, branch 410-libhip-intercept-coverage.
Coverage: 331 → 544 exports; comm -23 against the backend's table is now 0. Version nodes now match amdhip64.map exactly, hip_7.14 included. hipDeviceGetExecutionCtx is intercepted and logged through LD_PRELOAD of the rebuilt proxy over a stock ROCm 7.16 backend (no HRX device needed for that check).
Correction 1 — regenerating stubs_generated.c alone would have changed nothing
The file was not in the build graph: libhrx/src/passthrough/CMakeLists.txt listed only hip_intercept.c, and the stubs it defines (__hipGetPCH, …) are absent from the shipped .so. So the reported fix ("regenerate the stub file") was necessary but not sufficient — the regenerated file had to be regenerated and linked in. That is why the gap could sit there looking generated-and-correct.
Correction 2 — there was no usable generator, and now there is
add_logging.py is not the stub generator (it rewrites hip_intercept.c, and its __main__ hardcodes a personal path); the map/stubs were produced ad hoc. The PR commits libhrx/src/passthrough/generate_stubs.py with a node-set assertion against amdhip64.map (tested: a stray hip_9.9 node exits 1) and a --check mode, documented in the passthrough README. This is the difference between fixing the gap once and leaving it to reopen at the next HIP version node.
Correction 3 — the double init prints twice, not three times
early_init() now goes through ensure_init(), and hip_interceptor_init() is idempotent. Measured on stderr, a marker written between two init calls survives; before, it was truncated. The report's "three times" reading came from the log file itself being re-fopened with "w" mid-run — the truncation is the defect that hid its own count.
What this does not do
Same shipping caveat as the rest of the fork line: the engine pins third_party/hrx-system at 98d05d94 (held by config/hrx-system-hold.config), so this is not yet in engine builds.
Evidence: team/evidence/410-evidence.md plus 410-symbols.txt, 410-runtime-{before,after}.txt, 410-reinit.txt, probe sources and build script.
All three defects are fixed and in engine builds: PR #423 moved third_party/hrx-system to 1bit/held-98d05d94-3fixes (3319e5361, merged as 7d44c99).
Route, for the record: the fix landed on 1bit/main (PR #3) but the engine holds hrx-system at 98d05d94, so the owner chose a backport branch off that same commit. The only hand-written code in it is the +61/−0 api.h launch-config port that the regenerated stubs need.
Verified at the pinned line (gfx1151, -DONEBIT_HRX=ON build exit 0):
comm -23 between the backend's 540 HIP exports and libhip_intercept's 544 defined symbols = 0 (the issue measured 331 of 540 covered).
Op gate test-backend-ops -b HRX0: 1141/1141 passed, 0 failures.
tests/hrx_long_prompt.py passes against a build at this pin, which is what the pin's validated-pin record rests on.
From the fix comment, the three corrections that matter: the stub file was never in the build graph (regenerating it alone would have changed nothing, so it is regenerated and linked), there was no usable generator (there now is, committed, with a node-set assertion), and the double init prints twice, not three times.
One loose end is tracked as a follow-up task rather than left implicit: the committed generator's --check mode reports the checked-in stubs "out of date" at this pin even though coverage is correct, so it is being reproduced and either fixed or scoped down before anything wires it into CI.
Activity
bong-water-water-bong commentedon Oct 13, 2026
Follow-up: two additional defects found while re-verifying this report
Re-testing this issue with the proxy correctly preloaded (
LD_PRELOAD=<...>/libhip_intercept.so— my original test had it only on
LD_LIBRARY_PATH, so it was never loaded) confirmed thecoverage gap above and surfaced two more problems.
Confirmed: the coverage gap is real
hipGetDeviceCount(present in the proxy's export table) is logged:hipGetDeviceCount() -> count=1, ret=0hipDeviceGetExecutionCtx(absent from the proxy's export table) is not logged at all, yetthe call succeeds. Consistent with the original analysis: missing wrapper ⇒ not intercepted, not
broken.
New defect 1 — the documented interceptor variable does not exist in the code
libhrx/src/passthrough/hip_intercept.creadsHIP_INTERCEPTION_LIBRARYlibhrx/src/passthrough/README.mddocumentsHIP_PASSTHROUGH_INTERCEPTORThe README's name is never read. A user following the documentation gets a silently
un-instrumented run with no warning — which is exactly how I first misdiagnosed the logging
interceptor as broken. Suggest reading both names (documented one taking precedence) or fixing
the README.
Full env surface read by
hip_intercept.c:HIP_PASSTHROUGH_BACKEND_LIB,HIP_INTERCEPTION_LIBRARY,HIP_LOG_FILE,HIP_LOG_LEVEL.New defect 2 — the proxy initializes twice
early_initcallsintercept_init()directly from an early constructor while every publicentry point goes through the
pthread_once-guardedensure_init(). Initialisation thereforeruns from the constructor and through the guard.
Observed consequence:
HIP Logging initializedprinted three times in one short run, with amatching
HIP Logging shutting downeach time. Becausehip_interceptor_initopens the log withfopen(path, "w"), a re-init truncates the log earlier calls are still writing to — mycaptured log contained the mid-line fragment
nt=0, ret=3, the tail of a line whose start hadbeen truncated away.
Fix: have
early_initcallensure_init()so both routes share the guard, and makehip_interceptor_initidempotent.Correction to my earlier comment
The
hip_logging"writes no output" report I filed separately was my error and has beenclosed. It works — including
HIP_LOG_FILE— when the proxy is actually preloaded and theprocess is not run under an environment where the target file is unwritable. Only the two
defects above are genuine.
bong-water-water-bong commentedon Oct 13, 2026
Root cause found: the proxy was generated before HIP 7.14
The coverage gap is not arbitrary — it has a precise cause.
libhrx/src/passthrough/stubs_generated.cstates its own provenance:Every forwarded symbol carries an explicit versioned-symbol directive:
__asm__(".symver __hipGetPCH, __hipGetPCH@hip_4.2");Counting those directives in the checked-in file:
hip_4.2 4.3 4.4 4.5 5.0 5.1 5.2 5.3 5.5 5.6 6.0 6.1 6.2 6.4 6.5 7.1 7.2hip_7.14hip_7.14directives in the fileMeanwhile the HRX library it is meant to proxy exports 18 nodes — the same 17 plus
hip_7.14.The gap is exactly the 7.14 boundary
The symbols this issue lists as unreachable are predominantly the HIP 7.14 additions:
hipExecutionCtxCreate,hipExecutionCtxDestroy,hipExecutionCtxGetDevice,hipExecutionCtxGetId,hipExecutionCtxRecordEvent,hipExecutionCtxStreamCreate,hipExecutionCtxSynchronize,hipExecutionCtxWaitEvent,hipDeviceGetDevResource,hipDevSmResourceSplit,hipDevSmResourceSplitByCount,hipDevResourceGenerateDesc,hipStreamGetDevResource,hipGreenCtxCreateMeasured directly: of the 10
hipExecutionCtx*/hipDevResource*symbols HRX exports,the proxy covers 0.
So the 215-symbol gap is a generation-version boundary, not a random omission: the stub file
was produced against a ROCm
libamdhip64.sothat predates HIP 7.14, and has not been regeneratedsince.
Fix
Regenerate
stubs_generated.cagainst the current HRXlibamdhip64.so(or a current ROCmbuild), so the node set includes
hip_7.14. The generator's own convention already does theright thing —
.symverper symbol with the node taken from the source library — so this is aregeneration, not a redesign.
A cheap guard would be to assert during generation that the produced node set equals the node set
in HRX's own
amdhip64.map, which fails loudly the next time a version node is added upstreamrather than silently leaving a gap.
bong-water-water-bong commentedon Oct 16, 2026
All three defects fixed (fork), and the fix is bigger than the report implies
Fixed on
1bit-MONSTER/hrx-system1bit/main— PR 1bit-MONSTER/hrx-system#3, branch410-libhip-intercept-coverage.Coverage: 331 → 544 exports;
comm -23against the backend's table is now 0. Version nodes now matchamdhip64.mapexactly,hip_7.14included.hipDeviceGetExecutionCtxis intercepted and logged throughLD_PRELOADof the rebuilt proxy over a stock ROCm 7.16 backend (no HRX device needed for that check).Correction 1 — regenerating
stubs_generated.calone would have changed nothingThe file was not in the build graph:
libhrx/src/passthrough/CMakeLists.txtlisted onlyhip_intercept.c, and the stubs it defines (__hipGetPCH, …) are absent from the shipped.so. So the reported fix ("regenerate the stub file") was necessary but not sufficient — the regenerated file had to be regenerated and linked in. That is why the gap could sit there looking generated-and-correct.Correction 2 — there was no usable generator, and now there is
add_logging.pyis not the stub generator (it rewriteship_intercept.c, and its__main__hardcodes a personal path); the map/stubs were produced ad hoc. The PR commitslibhrx/src/passthrough/generate_stubs.pywith a node-set assertion againstamdhip64.map(tested: a strayhip_9.9node exits 1) and a--checkmode, documented in the passthrough README. This is the difference between fixing the gap once and leaving it to reopen at the next HIP version node.Correction 3 — the double init prints twice, not three times
early_init()now goes throughensure_init(), andhip_interceptor_init()is idempotent. Measured on stderr, a marker written between two init calls survives; before, it was truncated. The report's "three times" reading came from the log file itself being re-fopened with"w"mid-run — the truncation is the defect that hid its own count.What this does not do
Same shipping caveat as the rest of the fork line: the engine pins
third_party/hrx-systemat98d05d94(held byconfig/hrx-system-hold.config), so this is not yet in engine builds.Evidence:
team/evidence/410-evidence.mdplus410-symbols.txt,410-runtime-{before,after}.txt,410-reinit.txt, probe sources and build script.HRX: pin hrx-system to the held-pin backport carrying #409, #410 and #…
bong-water-water-bong commentedon Oct 16, 2026
Shipped: the engine now pins the fix
All three defects are fixed and in engine builds: PR #423 moved
third_party/hrx-systemto1bit/held-98d05d94-3fixes(3319e5361, merged as7d44c99).Route, for the record: the fix landed on
1bit/main(PR #3) but the engine holdshrx-systemat98d05d94, so the owner chose a backport branch off that same commit. The only hand-written code in it is the +61/−0api.hlaunch-config port that the regenerated stubs need.Verified at the pinned line (gfx1151,
-DONEBIT_HRX=ONbuild exit 0):comm -23between the backend's 540 HIP exports andlibhip_intercept's 544 defined symbols = 0 (the issue measured 331 of 540 covered).test-backend-ops -b HRX0: 1141/1141 passed, 0 failures.tests/hrx_long_prompt.pypasses against a build at this pin, which is what the pin's validated-pin record rests on.From the fix comment, the three corrections that matter: the stub file was never in the build graph (regenerating it alone would have changed nothing, so it is regenerated and linked), there was no usable generator (there now is, committed, with a node-set assertion), and the double init prints twice, not three times.
One loose end is tracked as a follow-up task rather than left implicit: the committed generator's
--checkmode reports the checked-in stubs "out of date" at this pin even though coverage is correct, so it is being reproduced and either fixed or scoped down before anything wires it into CI.Closing as completed.
Move HRX pins: hrx-system to AMD's e3e6a976 line, llama.cpp 8d9ce2c1a (…