A self-contained debug adapter that reproduces the VS Code Disassembly View source resolution bugs without a real debugger, a real target, or a remote window.
No build step and no dependencies — plain CommonJS, loaded straight from source.
Everything is counted, nothing is realistic, so that wrong output is obvious at a glance:
instruction n -> address 0x1000 + n, mnemonic `in`
100 instructions over the 60 lines of demo.c, in two halves.
Lines 1–40 map one to one, two instructions each, with no endLine:
source line L -> instructions i(2L-2) and i(2L-1)
/* line 01 -> i0 i1 */
/* line 02 -> i2 i3 */
...
/* line 40 -> i78 i79 */
Lines 41–60 are covered by ranges, so that one row has to reserve several source lines. A block spanning N lines carries N instructions, and the blocks widen from two lines to six:
/* line 41 -> range 41-42 -> i80 i81 */
/* line 43 -> range 43-45 -> i82 i83 i84 */
/* line 46 -> range 46-49 -> i85 i86 i87 i88 */
/* line 50 -> range 50-54 -> i89 i90 i91 i92 i93 */
/* line 55 -> range 55-60 -> i94 i95 i96 i97 i98 i99 */
Only the first instruction of a block shows the source, so a block renders as one tall source cell followed by bare instruction rows — which is exactly the shape that has to stay right when the source cannot be loaded.
So a correct view reads i0, i1, i2, … downwards with no gaps and no repeats, and every row
sits under the source line or range that names it. Any repeat, any backwards step, any row under
the wrong line is the bug — the row is displaying the instruction of whatever element the
recycled row template was previously bound to. Instructions outside i0–i99 carry no source
location, which is legal and keeps the mapping honest.
The session starts at i78 (address 0x104E, line 40), the last one-to-one row, so both halves
are on screen at once.
demo.c is generated from MULTI_LINE_BLOCKS in extension.js; changing the blocks means
rewriting the matching comments.
code --extensionDevelopmentPath=<this-folder> <this-folder>
Works the same for stock VS Code and for a self-built OSS build (scripts/code.bat /
scripts/code.sh instead of code). Then:
- Pick one of the four configurations in the Run and Debug dropdown, press F5.
- The session stops immediately.
- Right-click the frame in the Call Stack view → Open Disassembly View.
(Right-clicking inside
demo.cworks too, since the debugger contribution claimsc.) - Scroll the Disassembly View up and down a few screens. Row recycling is what makes the
render-order bug visible, so scrolling matters. Scroll down past
i79for the multi-line blocks, which recycle tall rows into short ones and back.
The Mock Disassembly Adapter output channel logs the scenario and every request.
The adapter puts the same Source object in its stackTrace and disassemble responses, so
any difference between how the editor and the Disassembly View behave is VS Code's alone.
Content served over a source request is tagged with /* from adapter */ on every line. That is
the tell: if you see the tag, the view fetched over DAP; if you do not, it read the file from disk.
sourceReference |
reported path | reads content from | |
|---|---|---|---|
| A | 0 | real demo.c |
— |
| B | 0 | /nonexistent/build-server/demo.c |
— |
| C | 1234 | real demo.c |
demo.c |
| D | 1234 | /nonexistent/build-server/demo.c |
demo.c |
The two fixes land in separate PRs, so the middle column is what the render-order fix alone buys.
| stock VS Code | + render-order fix | + sourceReference fix |
|
|---|---|---|---|
| A | source shown, untagged (from disk) — correct | unchanged | unchanged |
| B | mnemonics out of order / repeated, no source | i0, i1, i2, … in order, every reserved source line reads (… unavailable) |
unchanged |
| C | source shown untagged — wrong, the reference was ignored | unchanged | source shown tagged — fetched from the adapter |
| D | mnemonics out of order / repeated, no source | in order, with the (… unavailable) placeholder |
source shown tagged, mnemonics in order |
In B, the multi-line blocks are where the placeholder earns its keep: the row height is fixed before the source is resolved and cannot shrink afterwards, so a six-line block reserves six source lines whether or not anything can be loaded into them. Stock VS Code leaves all six blank under a mnemonic; the fix names every one of them:
55: (/nonexistent/build-server/demo.c unavailable)
56: (/nonexistent/build-server/demo.c unavailable)
57: (/nonexistent/build-server/demo.c unavailable)
58: (/nonexistent/build-server/demo.c unavailable)
59: (/nonexistent/build-server/demo.c unavailable)
60: (/nonexistent/build-server/demo.c unavailable)
i94
A is the control. Everything works, in every build.
B shows the render-order bug on its own: sourceReference is 0, so VS Code builds exactly the
URI it should, and the source genuinely is not there. The correct outcome is a row with no source
code. Instead the row silently keeps the previous row's address and mnemonic. This needs no remote
and no unusual adapter — it is what anyone gets when debugging a binary whose DWARF paths point at
a build server.
C shows the wrong-URI bug with nothing failing. The path resolves, so the view happily renders
source — but it renders the file from disk rather than the content the adapter is holding,
which is what a non-zero sourceReference requires. Compare against the editor, which opens the
tagged adapter copy from the same Source object. Two views, one source, two answers.
D is C and B together, and is the WSL case reproduced locally: a non-zero sourceReference
plus a path the machine running the UI cannot resolve. The editor opens the file correctly over
DAP; the Disassembly View goes to the file system, fails, and corrupts.
- The adapter never really runs. Continue and step just move the program counter by one instruction and stop again, which is enough to force the view to re-render.
- To try other paths without editing the code, set
missingPathin the launch configuration.