Linker Script Studio
A VS Code extension that opens GNU ld linker scripts (.ld, .lds) as a
custom editor:
- Studio: memory regions as an always-editable table with a proportional
address-space bar, a Stack & Heap section that edits the size however
the script actually expresses it (a top-level symbol with a
DEFINED()
override on Xilinx Vitis/SDK, or a literal directly in the .stack/
.heap section body on Microchip SoftConsole/PolarFire SoC -- detected
automatically, with a small proportional bar), a "Section to Memory
Region Mapping" table (each output section next to a dropdown of
available regions, reassignable in one click), and an output-section
tree with the full detail (input-section patterns, placement, symbols).
Entry point, OUTPUT_FORMAT/OUTPUT_ARCH, and INCLUDEd files are
summarized at the top. Add/edit/delete memory regions and output sections
here, including a "+ Add shared memory" preset that adds a region plus a
paired (NOLOAD) reserved section for a cross-core shared buffer.
- Text: a toggle within the same panel (not a second tab/file) showing
the raw source, for anything Studio can't yet represent. Both views read
from and write to the same local draft buffer, so switching back and
forth never loses an edit either way.
- Explicit Save, with undo/redo: Studio edits build up in a local draft
buffer, not applied to the real file on every keystroke -- nothing is
written to the document (or disk) until you hit Save (button, or
Ctrl+S) or the status bar's "Unsaved changes" indicator. Undo/Redo
buttons (and Ctrl+Z/Ctrl+Shift+Z) step back and forth through Studio
edits one at a time; the Text view's textarea additionally gets the
browser's native per-keystroke undo while it's focused. A command palette
escape hatch (
linkerScriptStudio.openAsText / ...openAsStudio) is
still available for anyone who wants the file in a genuinely separate
native-editor tab instead.

Design mockup of the Studio view (built from the actual media/main.css design, rendered standalone against real data) -- not yet a screenshot of the extension running inside VS Code, since that still needs an F5 smoke test to confirm. The memory regions/sections are from the fixtures/softconsole/polarfire-e51.ld fixture; the Stack & Heap section instead shows examples/mpfs-ddr-loaded-by-boot-loader.ld's real multi-hart data (E51 + four U54 cores), since that's the case worth illustrating. Design pulled from the sibling binary-structure-inspector extension's own native-VS-Code styling.
Architecture
src/core/ -- pure parser/serializer/validator, zero vscode/Node
imports (enforced at build time: tsconfig.core.json excludes the
node/vscode type libs, so an accidental import fails tsc, not just
code review). Usable from plain Node (that's what the Mocha tests run
against) and reusable from the webview if ever needed.
tokenizer.ts / parser.ts -- GNU ld script grammar → LinkerScript model.
serializer.ts / edits.ts -- model mutations → text splices (see
Round-trip strategy below).
validate.ts -- semantic checks (duplicate/undefined region references).
expr.ts -- a restricted, non-executing arithmetic evaluator for
ORIGIN/LENGTH values. It folds literals and + - * / << >>; anything
else (symbol references, LENGTH(REGION)) is kept as an opaque raw
string. No code from a linker script is ever executed.
src/extension.ts, src/linkerScriptEditorProvider.ts -- the extension
host (vscode imports allowed). Registers the linkerScriptStudio.editor
custom text editor. It's a thin sync layer, not an editing layer: it
tells the webview when the document changes outside of it, and writes
the webview's draft back to the document (and disk) on an explicit Save
-- see shared/messages.ts for why.
src/webview/ -- the Studio view's UI (vanilla DOM, no framework).
Bundles src/core directly (it has zero vscode/Node imports, so it's
safe in a browser context) and does all parsing/editing locally against
its own draft text buffer; the host only hears about it on Save. Styled
with media/main.css against VS Code's own theme variables (--vscode-*)
rather than fixed colors, and uses @vscode/codicons (media/codicon/)
for icon buttons so actions look native rather than like generic HTML
form controls. The webview's localResourceRoots must cover every
directory a resource is served from (dist for the bundle, media for
the stylesheet and codicon font) -- too narrow a root doesn't error, it
silently blocks the resource, so double-check this after adding a new
webview asset directory.
- Two esbuild bundles:
dist/extension.js (platform: node) and
dist/webview/main.js (platform: browser), matching build targets to
what's actually available in each context.
Round-trip strategy (read before editing the parser)
Editing in the Studio view does not regenerate the whole file from the
model. The parser records the exact source byte-span of every top-level
construct (each MEMORY region, each output SECTION, each top-level
directive). An edit computes a small set of TextEdit splices against the
original source text and applies only those -- everything outside an
edited node's span (comments, blank-line grouping, unrelated
regions/sections, indentation elsewhere in the file) survives byte-for-byte.
The explicit tradeoff: a node that is edited has its entire span
replaced by canonical pretty-printed text, so that node's own internal
comments/formatting are not preserved, and a newly added region/section is
appended with no comment. The Text view is the escape hatch either way --
nothing is only editable through the lossy path.
Saving and external changes
Save stays pending until VS Code confirms the edit and disk write. Failures
keep the draft unsaved. If another editor changes the file, a clean Studio
view reloads it; a dirty Studio view keeps your draft and asks before
replacing the external changes. Discard draft loads the current document
without closing the panel. Ctrl/Cmd+S commits the currently focused memory
field before saving.
Drafts live in the webview. Save before closing the panel: local unsaved
changes do not reliably participate in VS Code's native dirty-file warning.
Scope
Implements the common real-world subset of the ld script grammar:
MEMORY, SECTIONS (input-section patterns, KEEP, ALIGN, symbol/
PROVIDE assignments, >/AT>/AT() placement), and the usual top-level
directives. Unrecognized constructs produce a parse-error diagnostic rather
than crashing or silently corrupting the file.
Explicitly out of scope (first pass): scripts that require the C
preprocessor before they're valid ld syntax (#include/#define/#if at
BSP-generated files sometimes do this). These are detected up front
(detectNeedsPreprocessor); the Studio view shows a notice and falls back
to the Text view, rather than producing a broken parse.
Development
npm ci
npm run check-types # tsc --noEmit against all three tsconfigs (core/host/webview)
npm run lint
npm test # Core + mocked-host tests; no extension host required
npm run test:browser # Real webview bundle in Chromium; see prerequisites below
npm run build # esbuild, both bundles (dev: sourcemaps, unminified)
npm run package # produces linker-script-studio-<version>.vsix
The browser suite requires Chromium at /usr/bin/chromium, or set
CHROMIUM_PATH to your Chromium executable. playwright-core drives that
browser without downloading one. On a trusted isolated cloud container the
suite launches Chromium with --no-sandbox; run it only against this local
test harness. The Node suite does not require Chromium. To type-check test
sources as well, run npx tsc -p tsconfig.json --noEmit.
npm run package cleans any stale .vsix, type-checks, then runs vsce package, whose own vscode:prepublish hook runs the production build
(build:prod: minified, no sourcemaps) -- don't add a separate production
build step to the package script, vsce already triggers one, and running
both just means the second (non-production) build silently wins.
Press F5 in VS Code (with the .vscode/launch.json Extension Development
Host config) to try it against fixtures/xilinx/lscript.ld or
fixtures/softconsole/polarfire-e51.ld.
The fixtures under fixtures/ are representative, hand-trimmed excerpts
reconstructed from well-known Xilinx Vitis/SDK and Microchip SoftConsole
conventions, not literal vendor files. examples/ now holds real,
unmodified vendor linker scripts (a Xilinx one and a PolarFire SoC
DDR-loaded-by-bootloader one) -- worth promoting into fixtures/ and
wiring into the fixture tests, since real files are what actually surfaces
grammar edge cases.
Release validation
The 0.2.4 development pass covers core, mocked-host, and Chromium webview
regressions. See architecture and security review for
findings and remaining limits. VSIX packaging prepares a local artifact;
it does not publish the extension.
Before Marketplace publication, run a real Extension Development Host check:
- Open this checkout in VS Code and press F5 (Run Extension).
- Open both fixtures and the real files under
examples/ in Studio. Check
that styles, codicons, region bars, and all PolarFire hart stacks render.
- Edit memory and stack/heap sizes, switch Studio/Text, and undo/redo. Save,
close, and reopen to verify the file content on disk.
- Modify the file through another editor with both clean and dirty Studio
drafts. Verify updates, overwrite confirmation, and Discard draft.
- Try a read-only file or a failed write. Verify Save reports failure and
keeps the draft unsaved. Test Ctrl/Cmd+S while a memory field has focus.
This live VS Code check has not been run in the cloud machine, which has no
VS Code executable. The browser suite exercises the webview with a simulated
bridge and does not replace this check.