Re-ran analysis/frida/mx-nmx-trace.js (with the F46 hooks for
LmxProxy.dll!CLMXProxyServer.Suspend / .Activate) against
MxTraceHarness on the local AVEVA install. Two captures landed:
- captures/123-frida-suspend-advised-instrumented/
Scenario: --scenario=suspend-advised --tag=TestChildObject.ScanState
After mx.suspend.begin/end at 17:23:51.949Z, NMX PutRequest fires
~140ms later with body:
2d 01 00 command 0x2D, version 0x0001
cd 2a ee ec b2 76 06 4f b4 58 5c a0 2d f7 a8 93 16-byte correlation_id (matches the prior AdviseSupervisory)
01 00 05 00 01 00 02 00 01 00 69 00 0a 00 engine + handle + attribute / property ids
47 92 00 00 03 00 00 00 trailer
TransferData wraps it; HRESULT 0 returned; ProcessDataReceived
callback delivers a 50-byte op-status frame; LMX surfaces it
through CUserConnectionCallback.OperationComplete. Suspend is
unambiguously server-side wire op 0x2D.
- captures/124-frida-activate-advised-instrumented/
Scenario: --scenario=activate-advised --tag=TestChildObject.ScanState
Activate fires at 17:26:02.982Z and returns Success synchronously
with no NMX traffic. The next NMX activity is 7+ seconds later
(harness teardown). Activate against a non-suspended item is
client-side only on this build.
The harness's activate-advised scenario doesn't sequence
Suspend-then-Activate, so we don't have direct evidence for
Activate-after-Suspend. Circumstantial reasoning: since Suspend
goes server-side with a state change, Activate likely also does to
revert. If direct evidence becomes needed, add a new
suspend-then-activate scenario to MxTraceHarness/Program.cs and
re-run.
design/70-risks-and-open-questions.md R5 moves to "settled —
Suspend is wire op 0x2D, Activate behaviour is conditional",
severity downgraded P2 -> P3 (no public Session::suspend /
Session::activate API exists today; if added later, 0x2D is the
encoder target).
design/followups.md F50 marked resolved.
docs/F50-suspend-activate-evidence.md: per-capture byte-level
evidence + repro recipe.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5.3 KiB
F50 Suspend / Activate live evidence — 2026-05-06
Live re-run of analysis/frida/mx-nmx-trace.js (with the F46 hook additions for LmxProxy.dll!CLMXProxyServer.Suspend / .Activate) against MxTraceHarness.exe on the local AVEVA install. Two captures land:
| # | Path | Scenario | Tag |
|---|---|---|---|
| 123 | captures/123-frida-suspend-advised-instrumented/ |
--scenario=suspend-advised |
TestChildObject.ScanState |
| 124 | captures/124-frida-activate-advised-instrumented/ |
--scenario=activate-advised |
TestChildObject.ScanState |
Capture 123 — Suspend IS server-side
After mx.suspend.begin fires at 17:23:51.949Z, NMX wire traffic appears within ~140ms:
17:23:51.949Z mx.suspend.begin (CLMXProxyServer.Suspend, serverHandle=1, itemHandle=1)
17:23:51.949Z mx.suspend.end (Status: Success=-1 / MxCategoryPending / MxSourceRequestingLmx / Detail=0)
17:23:52.089Z nmx.enter PutRequest body=
2d 01 00 ← command 0x2D, version 0x0001
cd 2a ee ee b2 76 06 4f b4 58 5c a0 2d f7 a8 93 ← 16-byte correlation_id (matches the prior AdviseSupervisory)
01 00 05 00 01 00 02 00 01 00 ← reserved / engine + handle context
69 00 0a 00 47 92 00 00 ← attribute / property ids
03 00 00 00 ← trailer
17:23:52.089Z nmx.enter TransferData (envelope wraps the above 41-byte body, target_galaxy=1, target_platform=1, target_engine=2)
17:23:52.090Z nmx.leave TransferData (HRESULT 0 = success)
17:23:52.090Z nmx.leave PutRequest (HRESULT 0 = success)
17:23:52.123Z nmx.enter ProcessDataReceived (50-byte op-status frame back from engine)
17:23:52.183Z call.enter CUserConnectionCallback.OperationComplete (LMX surfaces the op-status to the client)
The 41-byte body has the same shape as AdviseSupervisory's body (1f 01 00 + correlation_id + ...) — same family of INmxService2 item-control ops. The opcode 0x2D is what LmxProxy.dll!CLMXProxyServer.Suspend puts on the wire.
Capture 124 — Activate against an already-active item is client-side
The activate-advised harness scenario does not call Suspend first — it just AdviseSupervisory + Activate. So the Activate is invoked on an already-active item.
After mx.activate.begin fires at 17:26:02.982Z, the next NMX traffic is at 17:26:10.20Z (7+ seconds later — that's the harness's UnAdvise / Unregister at scenario teardown). No wire op fires for the Activate itself.
17:26:02.982Z mx.activate.begin (CLMXProxyServer.Activate, serverHandle=1, itemHandle=1)
17:26:02.982Z mx.activate.end (Status: Success=-1 / category=175 / Detail=0) ← returns instantly client-side
17:26:10.206Z nmx.enter PutRequest ← unrelated, harness teardown (UnAdvise / Unregister)
Verdict
- Suspend is server-side with NMX command
0x2D. The wire body shape matches AdviseSupervisory's structurally (<command:1> <version:2> <correlation_id:16> <body...>). The full body decode (engine / handle / attribute id meanings of bytes 19–40) is left for a future codec port — the M6 F50 deliverable is the existence + opnum + correlation-id evidence. - Activate (against a non-suspended item) is client-side only in this scenario — the LMX proxy returns success without emitting wire traffic. We don't have direct evidence for Activate-after-Suspend (the harness's
activate-advisedscenario doesn't sequence them); circumstantial reasoning is that since Suspend goes server-side, Activate likely also does when it has a suspension to revert. If a future capture is needed, add asuspend-then-activatescenario toMxTraceHarness/Program.cs.
What this changes
- R5 in
design/70-risks-and-open-questions.mdmoves to "settled — Suspend is wire op0x2D, Activate behaviour is conditional." - A future codec follow-up could port the
0x2Dbody shape into a typed encoder/decoder undercrates/mxaccess-codec/src/. Not blocking M6 / V1 —Session::suspend/Session::activatearen't part of the public API today; they'd be additions. analysis/proxy/nmxsvcps-procedures.tsvcould grow a row for opnum0x2Donce someone correlates the Frida capture against theINmxService2MIDL. Out of scope for F50.
Reproducing
$frida = "C:\Users\dohertj2\AppData\Local\Programs\Python\Python312\Scripts\frida.exe"
$harness = "C:\Users\dohertj2\Desktop\mxaccess\src\MxTraceHarness\bin\Release\net481\MxTraceHarness.exe"
$script = "C:\Users\dohertj2\Desktop\mxaccess\analysis\frida\mx-nmx-trace.js"
$cap = "C:\Users\dohertj2\Desktop\mxaccess\captures\<NNN>-frida-<scenario>-instrumented"
mkdir $cap
& $frida -f $harness -l $script -- `
--scenario=suspend-advised ` # or activate-advised
--tag=TestChildObject.ScanState `
--duration=8 `
--log="$cap\harness.log" `
--client="MxFridaTrace-<NNN>" `
> "$cap\frida.stdout.jsonl" 2> "$cap\frida.stderr.txt"
The harness needs the local AVEVA Galaxy running with TestChildObject deployed. Frida 17.x; Python 3.12. The MxTraceHarness.exe is the x86 / net481 build at bin/Release/net481/ — dotnet build src/MxTraceHarness/MxTraceHarness.csproj /p:Configuration=Release produces it.