Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/816.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**`get_stack_trace` explains source-less frames in the adapter's own terms** — the note for frames flagged `unresolvedSource: true` always ended with js-debug's remedy ("attach with `adapterConfig.sourceMaps: false` to see the generated .js paths"), on every adapter: a C/C++ attach showed it under nine native frames (`@NtWaitForSingleObject`, `@BaseThreadInitThunk`, …) that CodeLLDB names by symbol because they have no debug info — nothing was source-mapped and the key it named is one the C/C++ adapter does not know. The note is now adapter-neutral ("N frame(s) have no source file on this host … their file is a label, not an openable path; do not pass it to get_source_context") and the session's policy adds what such a frame is under its debugger through a new optional `describeUnresolvedSource` hook: js-debug names the unshipped source map and spells the switch for the session's mode (`adapterConfig.sourceMaps: false` on attach, `adapterLaunchConfig.sourceMaps: false` on launch); the CodeLLDB policies (C/C++, Rust, COBOL) say the frames are native code without debug info and that the program's own frames are the ones with a path; adapters with nothing to add leave the neutral sentence alone (#816)
2 changes: 1 addition & 1 deletion docs/agent-debugging-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ evaluate_expression(sessionId=session_id, expression="a + b") # Returns: "3"
## Common Issues and Solutions

### Issue: JavaScript shows Node.js internals in stack trace
**Solution:** Use `continue_execution` to move past internal frames. Stack trace filtering hides Node internals, `node_modules` dependency frames, and async separators by default; `includeInternals: true` shows them. A frame marked `unresolvedSource: true` is a source-map label, not a file you can open.
**Solution:** Use `continue_execution` to move past internal frames. Stack trace filtering hides Node internals, `node_modules` dependency frames, and async separators by default; `includeInternals: true` shows them. A frame marked `unresolvedSource: true` has no file path you can open (for JavaScript, a source-map label for a file the package did not ship; for C/C++/Rust/COBOL, a native frame without debug info; for Java, a class the JDI bridge names by package path) — the `note` says which.

### Issue: Python shows "special variables" instead of actual variables
**Solution:** This is normal hierarchical organization. Use the `variablesReference` to expand:
Expand Down
2 changes: 2 additions & 0 deletions docs/architecture/adapter-policy-pattern.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ export interface AdapterPolicy {
// === Stack frame filtering (optional) ===
filterStackFrames?(frames: StackFrame[], includeInternals: boolean): StackFrame[];
isInternalFrame?(frame: StackFrame): boolean;
// What a frame flagged unresolvedSource IS under this debugger, for the get_stack_trace note (issue #816)
describeUnresolvedSource?(info: { count: number; files: readonly string[]; attachMode: boolean }): string | undefined;

// === Variable extraction (optional) ===
extractLocalVariables?(stackFrames, scopes, variables, includeSpecial?): LocalVariableExtraction;
Expand Down
2 changes: 1 addition & 1 deletion docs/cobol/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ attach_to_process sessionId=... processId=<pid> adapterConfig={"manifestDirs": [
- `manifestDirs` supplies ready `*.cobol-symbols.json` files instead (an earlier source launch's artifact directory, or a `cobc -C` run of your own); a manifest regenerated from `sources` outranks a `manifestDirs` entry for the same program. With neither, the session shows the engine's C view and stepping lands on any `.cob`/`.cpy` line.
- cobc is not needed to attach with `manifestDirs`: the shim and the vendored CodeLLDB are all that runs.
- On Windows the attach stop is reported on the break thread the OS injects (exception `0x80000003`), a pause may land on a runtime worker thread: when the reported thread has no COBOL frame, the shim reports the stop on the thread that is inside the COBOL program (the description names both), so `get_stack_trace` shows the program on first contact. Breakpoint, step, entry and runtime-error stops are always on the right thread and are left alone.
- A batch job is usually paused inside libcob or a `C$SLEEP`/I/O call, a frame with no COBOL source: `get_scopes`, `get_local_variables` and `evaluate_expression` then serve the nearest COBOL program up the stack (the scope names say whose and how far up, `WORKING-STORAGE of PAYROLL (0000-MAIN, 3 frames up)`), so the data division is visible on first contact; `get_stack_trace` shows the COBOL frame's paragraph.
- A batch job is usually paused inside libcob or a `C$SLEEP`/I/O call, a frame with no COBOL source: `get_scopes`, `get_local_variables` and `evaluate_expression` then serve the nearest COBOL program up the stack (the scope names say whose and how far up, `WORKING-STORAGE of PAYROLL (0000-MAIN, 3 frames up)`), so the data division is visible on first contact; `get_stack_trace` shows the COBOL frame's paragraph. Frames CodeLLDB names by symbol (`@…` — libcob, the C runtime or the OS without debug info) carry `unresolvedSource: true` and the `note` says they are native frames, not source maps (issue #816; the mechanism is in the C/C++ guide's inspect step).
- When the client that started the session goes away (the server or the proxy dies), the shim detaches: an attached process is never terminated the way a launched one is.
- The target is held paused after attach (`stopOnEntry` is a top-level `attach_to_process` parameter and defaults to `true`; pass `false` to resume immediately). That initial stop is reported as `lastStop.reason: "pause"`; on Windows the adapter's own words stay beside it as `rawReason: "exception"` and the `0x80000003` description (issue #817; the mechanism is in the C/C++ guide's attach section).
- Recognised `adapterConfig` keys: `processId`/`pid`, `program` (CodeLLDB's explicit-binary hint; a relative path is resolved against `cwd` before it reaches CodeLLDB), `cwd`, `waitFor`, `manifestDirs`, `engineScopes`, `sources`, `dialect`, `format`, `copybookDirs`, `cobcFlags`, `runtimeChecks`, `forceRebuild`, `initCommands`, `preRunCommands`, `postRunCommands`, `exitCommands`, `targetCreateCommands`, `processCreateCommands`, `expressions`, `sourceMap`, `sourceLanguages`, `relativePathBase`, `breakpointMode`. The manifest keys (`sources` … `forceRebuild`, `manifestDirs`, `engineScopes`, `cwd`) are consumed by the adapter and never appear in the attach request, and are not reported as ignored. Unlisted keys are still forwarded, with a warning naming them.
Expand Down
2 changes: 1 addition & 1 deletion docs/cpp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Data breakpoints (hardware watchpoints), disassembly view, instruction stepping,
3. `set_breakpoint` on the source file (or `function: "name"` for function breakpoints — a bare `main` works fine in C/C++)
4. `start_debugging` with the executable (or source) path
5. Step (`step_over`/`step_into`/`step_out`), `continue_execution`, `pause_execution`
6. Inspect: `get_stack_trace`, `get_local_variables`, `evaluate_expression` (LLDB expressions, e.g. `ptr->field`, `vec.size()`)
6. Inspect: `get_stack_trace`, `get_local_variables`, `evaluate_expression` (LLDB expressions, e.g. `ptr->field`, `vec.size()`). Frames CodeLLDB names by symbol (`@NtWaitForSingleObject`, `@BaseThreadInitThunk`, `@__tmainCRTStartup`) are native code without debug info — system libraries and CRT start-up; they carry `unresolvedSource: true` and the `note` says so. The program's own frames are the ones with a file path (issue #816)
7. `get_output` for captured stdout/stderr (Windows: forwarded via adapter stdio; POSIX: CodeLLDB output events)
8. `close_debug_session`

Expand Down
2 changes: 1 addition & 1 deletion docs/stack-trace-filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ The filtering is implemented using the existing `AdapterPolicy` system:
- **All frames internal**: The filtered stack is never empty when the adapter reported frames. `FrameAnchorResolver` keeps the top (unfiltered) frame and sets `allFramesInternal`, so `get_scopes` and `evaluate_expression` always have a valid `frameId`; the `note` says so and points at `includeInternals: true` (issue #346). This guarantee is central and applies to every language, Go and .NET included. One policy additionally softens the result itself — Java returns the full unfiltered array (so a thread parked deep in JDK code still shows its stack)
- **Paused frame internal, ancestors visible** (issue #672): when the stopped thread's top frame is filtered while others survive, the resolver reports it as `pausedFrame` and keeps it as `frames[0]` on a breakpoint or step stop, or when the policy's `isAsyncBoundaryFrame` finds a boundary between it and the first visible frame; otherwise it stays hidden with `kept: false`. The decision is made only for the thread the stop event named (or the current thread when the event named none) and only while that stop is still the current one — a new stop landing mid-request falls back to plain filtering. A kept frame whose `file` is a relative label rather than a path (the JDI bridge's `java/io/PrintStream.java`) carries `unresolvedSource: true`
- **No frames**: Returns empty array as before
- **Unresolvable source-mapped frames** (issue #655): when the adapter reports a frame's source as not-a-file-on-this-host (DAP `sourceReference != 0` with a real-looking path — js-debug does this for a source map's `../src/x.ts` that the package never shipped), the frame carries `unresolvedSource: true` and the `note` says its `file` is a label, not an openable path. These frames are the debuggee's own code and are never hidden. On js attach this is rare now: `resolveSourceMapLocations` excludes `node_modules` by default (so dependency maps are not applied and those frames report their real `.js` path) and `cwd` is defaulted so the debuggee's own relative map sources resolve
- **Frames without an openable source** (issues #655/#816): when the adapter reports a frame's source as not-a-file-on-this-host (DAP `sourceReference != 0` and no openable path), the frame carries `unresolvedSource: true` and the `note` says its `file` is a label, not an openable path, then adds the policy's account of it (`describeUnresolvedSource`). What the flag means differs by debugger: js-debug reports a source map's `../src/x.ts` that the package never shipped — those frames are the debuggee's own code and are never hidden, and the remedy is `sourceMaps: false` (spelled for the session's mode); CodeLLDB reports native frames without debug info as `@symbol` labels with a `sourceReference` (`@NtWaitForSingleObject`, `@BaseThreadInitThunk`) — runtime plumbing, where the program's own frames are the ones with a path; the JDI bridge names a class no breakpoint was set in by its package path (the #672 kept-frame case above). The hook receives the flagged frames' `file` labels, so a policy can tell its cases apart (an LLDB frame with a relative glibc path is a runtime built elsewhere, not a symbol-only frame). On js attach the flag is rare now: `resolveSourceMapLocations` excludes `node_modules` by default (so dependency maps are not applied and those frames report their real `.js` path) and `cwd` is defaulted so the debuggee's own relative map sources resolve
- **Python**: No filtering applied (Python's AdapterPolicy does not implement `filterStackFrames`)
- **Other languages**: Any language whose AdapterPolicy implements `filterStackFrames` has filtering applied

Expand Down
2 changes: 1 addition & 1 deletion docs/tool-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,7 @@ Gets the current call stack.
- `stopReason` and `lastStop` describe the pause the frames were read at, so they are omitted whenever the session is not paused; they are taken from the state at the start of the call, so a `continue_execution` racing the fetch does not strip them from an answer that really was read at a stop.
- Frame IDs are used with `get_scopes`
- Internal/runtime frames (e.g. Node.js internals and `node_modules` dependencies, Go `/runtime/`, `System.*`) are filtered out by default; pass `includeInternals: true` to see them. When any frames were hidden, the response additionally carries `hiddenFrames` (count) and a `note` explaining how to reveal them.
- A frame whose source the adapter could not find on this host (js-debug: a source-mapped `.ts` the package did not ship) carries `unresolvedSource: true`, and the `note` says its `file` is a label rather than an openable path — do not pass it to `get_source_context`.
- A frame with no openable source path on this host carries `unresolvedSource: true`, and the `note` says its `file` is a label rather than a path — do not pass it to `get_source_context` — then adds what such a frame is under the session's adapter (issue #816): js-debug — a source-mapped `.ts` the package did not ship, with the switch that shows the generated `.js` paths instead (`adapterConfig.sourceMaps: false` on attach, `adapterLaunchConfig.sourceMaps: false` on launch); CodeLLDB (C/C++, Rust, COBOL) — a native frame without debug info (system libraries, CRT start-up) that the debugger names by symbol (`@NtWaitForSingleObject`), or a runtime built elsewhere whose relative path is not present here, where the program's own frames are the ones with a full path; Java — a class the JDI bridge names by package path (`java/io/PrintStream.java`) because no breakpoint has taught it the file's location. Adapters with nothing to add leave the neutral sentence alone.
- The filtered stack is never empty when the adapter reported frames: if *every* frame is internal (e.g. a goroutine paused inside the Go runtime), the top internal frame is kept so `get_scopes`/`evaluate_expression` still have a valid `frameId`, and the `note` says so.
- When the frame the debuggee is paused in is itself internal (a breakpoint or step that landed inside a `node_modules` dependency, say) while user frames survive further down, the response carries `pausedFrame` (`id`, `name`, `file`, `line`, and `kept`). On a breakpoint or step stop (a `debugger;` statement counts), or when an async boundary separates the paused frame from the first visible one (nothing below it can be evaluated), the paused frame is kept as `stackFrames[0]` so locals and evaluation anchor where the program actually stopped; `hiddenFrames` counts only the frames that stayed hidden. After a `pause` or an exception with a synchronous caller visible — stops that routinely land in runtime frames where the first user frame is the useful one — it stays hidden (`kept: false`) and the `note` names it; use `pausedFrame.id` as the `frameId` to inspect it (issue #672).
- When an explicit thread reports no frames, the response remains anchored to that thread and its `note` suggests a frame-bearing alternative when one is available.
Expand Down
2 changes: 1 addition & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ You can also evaluate arbitrary expressions in the current debug context:
- Narrow the request to escape the cap: pass `names: ["a", "b"]` to `get_variables` or `get_local_variables` to fetch specific variables in full. Requested names that were not found are listed in the response's `notFound`

### Stack Trace Filtering
- `get_stack_trace` filters internal/runtime frames by default (for JavaScript: Node internals, `node_modules` dependencies, and async separators). When any are hidden the response carries `hiddenFrames` (the count) and a `note` saying so; pass `includeInternals: true` to get the full stack. A frame with `unresolvedSource: true` has a `file` that is a label, not an openable path
- `get_stack_trace` filters internal/runtime frames by default (for JavaScript: Node internals, `node_modules` dependencies, and async separators). When any are hidden the response carries `hiddenFrames` (the count) and a `note` saying so; pass `includeInternals: true` to get the full stack. A frame with `unresolvedSource: true` has a `file` that is a label, not an openable path, and the `note` says what it is under that adapter (a js-debug source map the package did not ship, a CodeLLDB native frame without debug info, a class the JDI bridge names by package path)

### Breakpoint Behavior
- Breakpoints initially show `"verified": false` because verification happens asynchronously by the debug adapter once the module is loaded (e.g., debugpy verifies after the script starts)
Expand Down
4 changes: 4 additions & 0 deletions packages/shared/src/interfaces/adapter-policy-cobol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
getLldbDapClientBehavior,
getLldbAttachBehavior,
isLldbInternalFrame,
describeLldbUnresolvedSource,
lldbShouldSuppressOutputEvent
} from './lldb-policy-shared.js';

Expand Down Expand Up @@ -163,6 +164,9 @@ export const CobolAdapterPolicy = {

filterStackFrames: filterCobolStackFrames,
isInternalFrame: isCobolInternalFrame,
// The engine is CodeLLDB: a `@symbol` frame is native code without debug
// info, not a source map (issue #816).
describeUnresolvedSource: describeLldbUnresolvedSource,

extractLocalVariables: extractCobolLocalVariables,

Expand Down
4 changes: 4 additions & 0 deletions packages/shared/src/interfaces/adapter-policy-cpp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
getLldbAttachBehavior,
isLldbInternalFrame,
filterLldbStackFrames,
describeLldbUnresolvedSource,
lldbShouldSuppressOutputEvent
} from './lldb-policy-shared.js';

Expand Down Expand Up @@ -63,6 +64,9 @@ export const CppAdapterPolicy = {
*/
filterStackFrames: filterLldbStackFrames,
isInternalFrame: isLldbInternalFrame,
// A `@symbol` frame is native code without debug info, not a source map
// (issue #816).
describeUnresolvedSource: describeLldbUnresolvedSource,

extractLocalVariables: extractLldbLocalVariables,

Expand Down
18 changes: 17 additions & 1 deletion packages/shared/src/interfaces/adapter-policy-java.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* "initialized" during the initialize handshake, before the launch request.
*/
import type { DebugProtocol } from '@vscode/debugprotocol';
import type { AdapterPolicy, AdapterSpecificState, CommandHandling, LocalVariableExtraction } from './adapter-policy.js';
import type { AdapterPolicy, AdapterSpecificState, CommandHandling, LocalVariableExtraction, UnresolvedSourceContext } from './adapter-policy.js';
import { emptyLocalVariableExtraction, extractionFromScope } from './adapter-policy.js';
import { SessionState } from '@debugmcp/shared';
import type { StackFrame, Variable } from '../models/index.js';
Expand Down Expand Up @@ -234,6 +234,22 @@ export const JavaAdapterPolicy = {
return filtered.length > 0 ? filtered : frames;
},

/**
* An unresolvedSource frame under the JDI bridge (issues #672/#816) is a
* class the bridge has no file path for: `resolveSourcePath` knows a path
* only for classes a breakpoint was set in, and names every other one by
* package path (`java/io/PrintStream.java`, `com/example/Foo.java`). The
* class may well have source — it is the path the bridge lacks.
*/
describeUnresolvedSource: (info: UnresolvedSourceContext): string => {
const example = info.files[0] ? ` (${info.files[0]})` : '';
return (
`${info.count === 1 ? 'It is a class' : 'They are classes'} the JDI bridge names by package path${example} ` +
'because it has no file path for them: a breakpoint set in a class\'s source file teaches the bridge its ' +
"path, and the JDK's own classes have no source on this host."
);
},

isInternalFrame: (frame: StackFrame): boolean => {
const frameName = frame.name || '';
const filePath = frame.file || '';
Expand Down
Loading
Loading