Release v0.2.2#95
Merged
Merged
Conversation
Replace the broken GDB expression evaluation path for variable forcing with a cross-platform IPC command pipe. The debug binary now accepts a --cmd-pipe argument to start a command server thread that listens for REPL commands from the VSCode extension. Architecture: - Extract process_command() from the REPL loop into a shared function that both the interactive REPL and the command server call — single source of truth for force/unforce/get/set commands. - New iec_command_server.hpp: listener thread using Unix domain socket (Linux/macOS) or Win32 Named Pipe (Windows), newline-delimited text protocol with the same command format as the interactive REPL. - New repl-client.ts: TypeScript IPC client using Node.js net module (handles both Unix sockets and Win32 named pipes transparently). - Force/unforce commands now work while the program is running, not just when paused at a breakpoint. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The resolveDebugConfiguration() path in debug-config-provider.ts was not passing cmdPipePath to buildDebugConfig(), so the debug binary was launched without --cmd-pipe and the command server never started. Also increase the connect delay to 1s and show a warning toast if the connection fails. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add diagnostic logging to trace exactly where the pipe connection breaks. Logs go to the "STruC++ Debug" output channel and console: - commands.ts: logs generated pipe path - debug-config-provider.ts: logs debugState.cmdPipePath - debug-config-builder.ts: logs args passed to binary - extension.ts: logs session config, connect attempt, success/failure - force-variable.ts: logs replClient state when force is invoked Check Output > "STruC++ Debug" panel after launching a debug session to see the full trace. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lient Two bugs in the IPC connection: 1. ReplClient: net.Socket.setTimeout(5000) fires on idle data, not just during connection. Once connected, the socket gets destroyed after 5s of no commands. Fix: disable timeout after successful connection with sock.setTimeout(0). 2. CommandServer: handle_client() read() blocks indefinitely if client disconnects without closing cleanly. Fix: set SO_RCVTIMEO on client socket so read() returns EAGAIN on timeout, allowing the loop to check running_ and accept new connections. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ence Log each retry attempt with whether the socket file exists on disk. This will reveal whether the binary hasn't created the socket yet, or whether the socket exists but connection fails. Also increase retries to 8 and base delay to 250ms for more total wait time (~64s max). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GDB's all-stop mode freezes all threads (including the command server) when any thread hits a breakpoint. Connecting at session start races with GDB pausing the binary during startup, causing ECONNREFUSED. Fix: defer the connection to the first force/unforce command via ensureConnected(). By the time the user right-clicks a variable, the program has been running long enough for the command server to be active. If the program is paused at a breakpoint when the user tries to force, the connection will fail because GDB has frozen the server thread. The error message now tells the user to resume execution first. Also found: cppdbg provides evaluateName as GDB cast expressions like ((strucpp::TYPE *)0xaddr)->VAR, not clean paths. This needs separate handling (tracked for follow-up). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the command server IPC approach with direct GDB memory writes for variable forcing. When stopped at a breakpoint, the extension sets the IECVar internal fields directly via DAP evaluate: evaluateName.forced_ = true evaluateName.forced_value_ = <value> evaluateName.value_ = <value> GDB ignores C++ access specifiers and can write private struct fields. This works reliably when stopped at a breakpoint — the primary use case since the Variables pane is only populated when stopped. The command server (iec_command_server.hpp) and ReplClient are kept in the codebase for future use (Option D hybrid: GDB when stopped, pipe when running), but the force commands no longer depend on them. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The DAP evaluate request requires a frameId to specify which stack frame the expression should be evaluated in. Without it, cppdbg returns "Cannot evaluate expression on the specified stack frame". Fix: before evaluating, fetch the active thread's topmost stack frame and pass its ID to the evaluate request. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The debug adapter tracker transforms evaluate requests with context "repl" or "watch" by uppercasing identifiers (ST is case-insensitive). This breaks force field writes because C++ namespaces are case- sensitive: strucpp:: becomes STRUCPP:: which LLDB can't resolve. Fix: use context "variables" for force/unforce evaluate requests. The tracker only transforms "repl" and "watch" contexts, so "variables" passes through untouched. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove console.log statements and verify read-back that were added during debugging. The force mechanism is confirmed working. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When GDB pauses the program at a breakpoint, wall clock time advances while the cyclic loop is frozen. On resume, the sleep_until target (next_tick) is in the past, so the loop runs at full speed without sleeping — executing hundreds of cycles instantly. This causes timers (TON, TOF, TP) to fire almost immediately instead of respecting their configured delay. Fix: after each cycle, check if next_tick has fallen behind the current wall clock. If so, reset it to now. This prevents catch-up bursts while still maintaining accurate timing during normal execution. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three fixes for the debug variable interaction: 1. After forcing a variable, issue a dummy evaluate in "repl" context to trigger cppdbg's variable cache invalidation. This makes the forced value visible immediately in the Variables pane without needing to step. 2. Intercept setExpression DAP requests (triggered by "Set Value" in the Watch pane) and rewrite them as evaluate requests that assign to .value_ directly. This avoids the "expression could not be evaluated" error caused by cppdbg trying to assign to the IECVar wrapper type. 3. Note: "Set Value" in the Variables pane sends setVariable (not setExpression). This is harder to intercept since it uses variablesReference IDs rather than expression paths. For now, users should use "STruC++: Force Variable" from the context menu for the Variables pane. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Log every request passing through the debug adapter tracker to see exactly what VSCode sends for Set Value (setVariable vs setExpression), and what the refresh evaluate returns after forcing. Check the "STruC++ Debug" output channel for [tracker] lines and Developer Tools Console for [strucpp:force] lines. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three fixes based on DAP trace analysis: 1. Variable refresh after force: the "repl" context evaluate failed because it had no frameId. Replace with a read-back of the forced value via debugEvaluate() which always includes a frameId. This also uses "variables" context to avoid the ST uppercasing. 2. setExpression rewrite (Watch pane "Set Value"): the expression wasn't being uppercased, so LLDB couldn't find the ST variable name. Now applies transformStExpression() before rewriting. 3. setVariable (Variables pane "Set Value"): logged but left to cppdbg — it uses variablesReference IDs which we can't map to evaluateNames without caching the variable tree. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Variables pane "Set Value" sends a setVariable DAP request with only variablesReference + name (no evaluateName). To rewrite this as an evaluate that sets .value_ directly, cache the evaluateName for each variable when the variables response passes through. Implementation: - Track pending variables requests (seq → variablesReference) - On variables response, cache (variablesReference, name) → evaluateName - On setVariable, look up the cached evaluateName and rewrite to an evaluate request: evaluateName.value_ = <value> - Cache lastFrameId from stackTrace responses to provide frame context Also applies to setExpression (Watch pane Set Value) which now correctly uppercases the ST expression. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ging 1. Remove duplicate JSDoc comment on varEvalNameCache. 2. Remove debug logging: per-request DAP log in tracker, console.log in debug-config-builder and debug-config-provider. 3. Clear varEvalNameCache and pendingVarRequests on DAP "continued" event (when execution resumes, cached references become stale). 4. Remove dead IPC infrastructure that was built for Option D (pipe when running) but never used — forcing only works via GDB when stopped at a breakpoint: - Delete iec_command_server.hpp (C++ socket server) - Delete repl-client.ts (TypeScript socket client) - Remove --cmd-pipe arg parsing from generated main() - Remove cmdPipePath from DebugBuildState and DebugBuildInfo - Remove __cmdPipePath from debug configurations Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rcing feat: variable forcing via GDB field writes, IPC command server, timer fix
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Merge development into main for v0.2.2 release.
See PR #94 for full changelog: variable forcing via GDB field writes, Set Value interception, REPL refactoring, debugger timer fix.
🤖 Generated with Claude Code