Skip to content
| Marketplace
Sign in
Visual Studio Code>Debuggers>Fortran ifx DebugNew to Visual Studio Code? Get it now.
Fortran ifx Debug

Fortran ifx Debug

PbsandtDev

|
3 installs
| (0) | Free
VS Code debugger adapter for Intel ifx Fortran with Fortran-aware evaluate and breakpoint fallbacks.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

Fortran ifx Debug

Fortran ifx Debug is a VS Code debugger extension that wraps the Microsoft C/C++ debugger backends with a Fortran-aware proxy.

It adds a custom fortran-ifx debug type and focuses on the cases where plain cppvsdbg or cppdbg are weak for Fortran:

  • module-variable lookup through Intel ifx mangled symbols
  • better watch / hover / debug-console evaluation
  • synthetic array and array-section browsing
  • derived-type field recovery
  • conditional breakpoint rewriting for common Fortran syntax

On Windows, the extension uses cppvsdbg for native ifx debugging. On Linux/macOS, it routes through cppdbg with GDB or LLDB.

What The Extension Actually Adds

  • A fortran-ifx debug type contributed to VS Code.
  • A proxy debug adapter that sits in front of the cpptools backend.
  • Evaluate fallback for Fortran symbols and expressions:
    • symbol-map based mangling through fortran_symbols.json
    • scope-walk fallback for module and USE-associated variables
    • inquiry fallback for allocated, associated, size, shape, lbound, and ubound
  • Synthetic array views for allocatable arrays and array sections:
    • multidimensional arrays
    • strided sections
    • empty sections
    • open-bound sections when bounds can be inferred
  • Synthetic derived-type object expansion using source-parsed field lists.
  • Breakpoint-condition rewriting for common Fortran syntax:
    • .and., .or., .eq., /=, and similar operators
    • single-quoted string literals
    • Fortran array subscripts
  • Automatic generation of fortran_symbols.json at launch, when the helper script and its prerequisites are available.

Current Scope And Limits

  • launch is the contributed workflow today. attach is not exposed in the current debugger contribution yet.
  • The extension depends on the Microsoft C/C++ extension (ms-vscode.cpptools) because it delegates actual debugging to cpptools.
  • The most advanced Fortran recovery features are source-assisted. They work best when your project layout looks like this:
    • symbols under build/debug/fortran_symbols.json
    • Fortran sources under src/
  • Native Windows PDB/DbgEng inspection is not implemented yet. The extension still relies on cpptools plus symbol/source fallbacks.

Requirements

  • VS Code 1.101 or newer
  • Microsoft C/C++ extension (ms-vscode.cpptools)
  • A debug build of your Fortran program with symbols
    • Windows ifx: PDB symbols
    • Linux/macOS: DWARF symbols for GDB/LLDB
  • Optional, for automatic symbol-map generation:
    • Python 3
    • llvm-nm or nm on PATH

Install

If you are using a packaged build of the extension:

  1. Open VS Code.
  2. Run Extensions: Install from VSIX....
  3. Select the .vsix file for this extension.
  4. Make sure ms-vscode.cpptools is also installed.

Quick Start

  1. Build your Fortran project in a debug configuration.
  2. Open the workspace in VS Code.
  3. Add a launch configuration with "type": "fortran-ifx".
  4. Start debugging as usual from the Run and Debug view.

No shared CMakeLists.txt changes are required for this workflow.

Commands

The extension provides these commands, all under the Fortran ifx category in the command palette:

Command Purpose
Create Windows Launch Config Add or update a Windows ifx entry in launch.json
Open Proxy Log Open the proxy adapter's diagnostics log
Refresh Symbol Map Regenerate fortran_symbols.json without restarting
Break Where A Variable Is Assigned Place conditional breakpoints at every line that writes a variable
Show Tamandua Facts Status Report which facts file is in use and what it can answer
Write MCP Server Config Point .vscode/mcp.json at the extension's MCP server

Create Windows Launch Config adds or updates a Ugs Debug (Windows IFX) entry in .vscode/launch.json for the active workspace folder, using the generic ${command:cmake.launchTargetPath} program pattern. It writes through the VS Code configuration API, which rewrites the configurations block, so comments in launch.json are not preserved.

Open Proxy Log opens the proxy adapter's log file. The proxy runs as a separate process and cannot write to the extension's output channel, so its diagnostics — evaluate fallbacks, allocate-index builds, scope-walk stats — only exist there. That log is the first place to look when a variable does not resolve.

Refresh Symbol Map regenerates build/debug/fortran_symbols.json for the active workspace folder without restarting the debug session, for when the map has gone stale after a rebuild. It requires Python 3 on PATH.

Breakpoints From A Variable Name

Setting a useful Fortran breakpoint means knowing three things the debugger cannot tell you: which lines assign the variable, what loop encloses each of them, and which index variable pins one iteration. Those are static facts, and Tamandua indexes them.

Point the extension at a Tamandua facts file and run Fortran ifx: Break Where A Variable Is Assigned. Give it aqu_d%rchrg, and it finds every write, offers the index variables in scope at each, and places a breakpoint conditioned on the iteration you name.

The facts file is looked for in this order:

  1. fortranIfx.factsFile, if set
  2. swatplus-facts.json in the workspace folder
  3. build/swatplus-facts.json
  4. build/debug/swatplus-facts.json
  5. .tamandua/swatplus-facts.json

Build one with swatplus-build from a Tamandua checkout. Without one, this command reports that it has no facts and everything else in the extension works as before -- the feature is opt-in and nothing depends on it.

What it will not do is guess. Where Tamandua could not resolve a file's loop nesting it places the breakpoint without a condition and says so, because a condition pinned to the wrong index variable costs a whole compile-and-run cycle to discover. Where a bare module-variable name is declared in more than one module, it narrows the candidates by what the file imports -- a breakpoint carries no stack frame, so there is no running scope to consult -- and reports the ambiguity rather than picking one.

Trying It Without A Real Index

test_program/ ships a hand-written swatplus-facts.json describing its own src/main.f90, so the feature can be exercised with no SWAT+ checkout and no Tamandua install. Open test_program/ as the workspace folder, run Fortran ifx: Break Where A Variable Is Assigned, enter total_ke, and answer 2 when it asks for an iteration. It places two breakpoints:

main.f90:108 in run_demo  when step == 2
main.f90:111 in run_demo  when k == 2

Two different conditions for one variable, because only the inner loop encloses the second write. That is the behaviour worth checking -- a single condition applied to both lines would be wrong at one of them.

Other things to try in that fixture: grid (nested loops in fill_grid), p%position%x (a routine with no loop of its own, so the index comes from the caller), and pi (a parameter, which nothing assigns).

Letting An Assistant Set Them

A VS Code breakpoint is not a file. Breakpoints live in the editor's workspace storage, so an AI assistant that edits files and runs shell commands has nothing to write, and only an extension can call vscode.debug.addBreakpoints.

So the extension runs a small MCP server on loopback and registers it with the editor, which makes these tools available to agent mode:

Tool What it does
break_at Place a breakpoint at a file and line, optionally conditional
set_fortran_breakpoint Place conditional breakpoints for a variable
plan_fortran_breakpoint Report where to break and on what, changing nothing
clear_fortran_breakpoints Remove every breakpoint in the workspace
start_fortran_debug Run until a breakpoint hits, then report the stop
debug_variables Every variable in scope at the stop, or in a caller's frame
debug_evaluate Evaluate a Fortran expression, with a derived type's components
debug_continue Run to the next hit
debug_step Step over, in, or out
debug_output What the program has printed, most recent last
debug_crash_report Name the failing subroutine when a crash never trapped
stop_fortran_debug Terminate the session

The six debugging tools need 0.5.0 or newer, debug_continue needs 0.6.0 to survive more than one call, debug_evaluate needs 0.7.0 to report a derived type's components rather than {...}, break_at arrived in 0.8.0, debug_output needs 0.9.1 -- 0.9.0 captured cppvsdbg's own telemetry alongside the program's real output, which crowded it out of the buffer -- and debug_crash_report arrived in 0.10.0. An agent that lists only the three breakpoint tools is running an older build: check the version in the Extensions panel rather than the tool names, since 0.4.0 shipped twice with different tool sets.

A crash that never reached the debugger

A severe Fortran runtime error arrives in one of two ways, and they need different things.

When the debug build traps it, the debugger stops and cppvsdbg hands over a real PDB-resolved stack. The top frame or two sit inside libifcoremdd.dll, which ships no PDB, but the frame below that is the user subroutine and line. That case needs nothing.

When it does not trap -- a release build, or any configuration where the runtime just exits -- there is no stop and no stack. All that survives is what forrtl printed on the way out:

forrtl: severe (408): Subscript [#1](https://github.com/tugraskan/vsc_ifx_debug/issues/1) of the array OB has value 0 which is less than the lower bound of 1
Image              PC                Routine            Line        Source
swatplus-62.0.0-9  00007FF637696D74  Unknown               Unknown  Unknown

Every routine says Unknown, because Intel's own unwinder could not read the very PDB cppvsdbg reads successfully. The names are gone; the addresses are not. debug_crash_report works from those.

The arithmetic it undoes is why those addresses look useless. A printed program counter is a loaded address, and Windows relocates the image, so it matches nothing in the PDB and differs on every run:

rva    = pc - load base
lookup = preferred base + rva

The load base comes from the adapter's module events, recorded while the process is alive -- by the time the traceback prints the process is going away and nothing can be asked of it, so it is collected up front for a crash that may never come. The preferred base is read out of the PE header rather than assumed, because a link that set /BASE would make every resolved line wrong by the difference. Each frame prints both addresses, so a wrong base shows up rather than hiding behind a plausible source line.

Resolution needs a tool that reads PDBs. Intel ships llvm-symbolizer with oneAPI, so it is usually already on the machine that compiled the program; set fortranIfx.symbolizerPath if it lives elsewhere. Without one, the report still gives the condition, the array, the index and the bound, and names every path it searched.

The last section of a report -- what the static index knows about the named array -- is the only part that is not evidence, and says so. A subscript violation happens at a read, and the index records assignments, so those sites are worth looking at and are not the failing line.

Reading what the program said, not just where it stopped

Not every failure is a stop. A SWAT+ input error -- a malformed line in file.cio, a missing value -- is usually a clean Fortran stop after a printed message, not a trapped exception. There is no frame to inspect and no breakpoint to hit, because the failure happens before any breakpoint in your own code is ever reached. The printed message is the whole diagnostic, and until 0.9.0 nothing here could retrieve it: start_fortran_debug would only report "the program exited before stopping" and point at the Debug Console, which a caller without eyes on the editor cannot read.

debug_output reads what the program printed, and the termination error itself now folds in the last part of it directly -- the common case needs no second call. It works during a run, at a stop, or after the session has already exited; the buffer is cleared only when a new session starts, so a crash is still readable after the fact.

An exception that is trapped (an access violation, a runtime bounds check) still produces a stop, and start_fortran_debug and debug_continue now report its detail -- the debugger's own description of what happened, since an exception has no breakpoint condition to echo in its place.

Conditions

set_fortran_breakpoint takes either index_value or condition:

  • index_value pins the innermost counted loop at the breakpoint. Use it when the iteration you want is the loop right there in the routine.
  • condition is Fortran, used instead. Use it when the index that selects the object is set by the caller, which no loop in the routine carries.

hru_control is the case that needs the second form. It runs once per HRU, and j = ihru arrives from command, so the innermost loop there is over soil layers -- index_value: 3 means layer 3, while HRU 3 is condition: "j == 3". The placement report echoes the condition, so which one you got is visible rather than assumed.

A loop index itself is never a breakpoint target: a DO index has no assignment statement, so j1 has no site of its own. The planner says so and names the loop it drives, rather than reporting the variable as unknown.

The last six are what a print statement cannot give you. With prints, the variables to log have to be chosen before rebuilding, and a wrong guess costs another full SWAT+ build. At a breakpoint everything in scope is already there, so an assistant can ask twenty follow-up questions for free, and step out to the caller to find where an index came from.

debug_evaluate deliberately goes through the session rather than around it: the request passes the proxy, so it inherits the Fortran fallbacks the proxy already implements -- mangled module symbols, allocatable arrays, derived-type expansion -- instead of reimplementing them worse.

Stopping on one object

break_at is the plain operation and usually the right one. Stopping on HRU 3 is not the same as reading soil(3) from a stop on HRU 1: the editor's Locals and Watch panes show the frame the program is actually parked in, a routine's per-object locals do not exist yet for an object it has not reached, and a value read mid-run is whatever it was before that object's update.

break_at  file=hru_control.f90  line=102  condition="j == 3"

hru_control runs once per HRU with j = ihru arriving from command, so the index that selects the object is not carried by any loop in the routine -- which is why index_value cannot express this and a condition can. The innermost loop there is over soil layers, so index_value: 3 would mean layer 3.

It reports which procedure the line is in and which loop indexes are live there, so the position can be confirmed before a run. It does not move the breakpoint: if a line has no code of its own -- a declaration, a comment, a loop header the compiler folded -- the breakpoint stays hollow when the run starts, and the next executable line is the answer.

set_fortran_breakpoint remains the way in when the line is the unknown: give it a variable and it finds every site that assigns it.

Seeing the rest of a type once stopped

At a stop, debug_evaluate reports a derived type's components rather than {...}:

debug_evaluate  soil(3)              -> the profile, component by component
debug_evaluate  soil(3)%phys(2)  depth=2

depth defaults to 1 and is capped at 3. A profile nests components inside arrays of layers, so an uncapped read would bury a whole context in one call; children per value and total nodes are bounded too. Every cut is marked with a + or a count, never silent -- an omitted interior that says nothing reads as a leaf, and the next question then assumes the data is not there.

Because module-level arrays cover every object, a neighbour is also readable from where you are -- soil(4) while parked on HRU 3, no resume. Useful for a comparison; not a substitute for stopping on the object you care about.

Prints remain better for breadth over time: 365 days of a value in a file beats one frame. The two are complementary, and Tamandua improves both, since most of what makes print debugging slow is not knowing which line to print at.

The server binds 127.0.0.1 only. Its tools change editor state, so nothing off-box may reach it. Set fortranIfx.mcpServer.enabled to false to switch it off.

How a client reaches it depends on whose MCP registry it reads.

VS Code's own agent mode is registered with automatically, through vscode.lm.registerMcpServerDefinitionProvider. Nothing to configure, and the per-session port is handled for you. Fortran ifx: Write MCP Server Config additionally records the address in .vscode/mcp.json.

Claude Code, and any other external client, does not read VS Code's registry -- it keeps its own MCP configuration -- so it has to be pointed at the URL, and it stores that URL rather than re-reading it. Pin the port first, or the address will be wrong after the next reload:

// .vscode/settings.json
{ "fortranIfx.mcpServer.port": 39271 }

Then add the server to that client. For Claude Code:

claude mcp add --transport http fortran-ifx http://127.0.0.1:39271

Reload the window after pinning the port, and check the "Fortran ifx Debug" output channel for mcp server listening on http://127.0.0.1:39271. If the port is already taken the extension says so rather than failing quietly.

Launch Configuration

Windows ifx

Use cppvsdbg implicitly by omitting MIMode:

{
  "name": "Ugs Debug (Windows IFX)",
  "type": "fortran-ifx",
  "request": "launch",
  "program": "${command:cmake.launchTargetPath}",
  "args": [],
  "cwd": "${workspaceFolder}",
  "stopAtEntry": false,
  "environment": [],
  "externalConsole": false
}

Linux Or macOS With GDB

{
  "name": "Debug Fortran (GDB)",
  "type": "fortran-ifx",
  "request": "launch",
  "program": "${command:cmake.launchTargetPath}",
  "args": [],
  "cwd": "${workspaceFolder}",
  "stopAtEntry": false,
  "MIMode": "gdb"
}

Linux Or macOS With LLDB

{
  "name": "Debug Fortran (LLDB)",
  "type": "fortran-ifx",
  "request": "launch",
  "program": "${command:cmake.launchTargetPath}",
  "args": [],
  "cwd": "${workspaceFolder}",
  "stopAtEntry": false,
  "MIMode": "lldb"
}

Launch Properties

The extension contributes these Fortran-specific launch properties:

  • fortranIfxSymbolFile
    • Explicit path to fortran_symbols.json
  • fortranIfxScopeWalkEnabled
    • Override scope-walk fallback for this launch
  • fortranIfxScopeWalkMaxDepth
    • Override maximum scope-walk depth for this launch

Standard cpptools launch fields such as program, args, cwd, environment, externalConsole, MIMode, miDebuggerPath, and setupCommands are passed through as expected.

Recommended Workflow For Shared Repos

If your project uses a shared CMakeLists.txt, the recommended setup is to leave CMake alone and keep the debugger integration user-local:

  1. Build the project normally.
  2. Install this extension and ms-vscode.cpptools.
  3. Open the workspace in VS Code.
  4. Run Fortran ifx: Create Windows Launch Config.

That keeps the build system independent of editor tooling and avoids adding extension-specific logic for users who will never need it.

Workspace Settings

The extension contributes these settings:

  • fortranIfx.scopeWalk.enabled (default true)
  • fortranIfx.scopeWalk.maxDepth (default 4, range 1–10)
  • fortranIfx.factsFile (default empty — search the usual paths)
  • fortranIfx.mcpServer.enabled (default true)

These control whether scope-walk fallback is used and how deep it searches when direct expression evaluation fails. They are read when a launch configuration is resolved, so a change takes effect on the next debug session. The matching fortranIfxScopeWalkEnabled / fortranIfxScopeWalkMaxDepth launch properties override them for a single configuration.

Symbol Map Behavior

The proxy looks for fortran_symbols.json in this order:

  1. fortranIfxSymbolFile from the launch configuration, if set
  2. <cwd>/build/debug/fortran_symbols.json
  3. next to the program
  4. one directory above the program
  5. two directories above the program

If no symbol map is found at any of those paths, the proxy makes a single attempt per session to run tools/generate_fortran_symbols.py. That attempt starts when the session launches, alongside the backend starting your program, so it does not stall the first expression you evaluate. Generation needs Python 3 and llvm-nm or nm on PATH; when either is missing the proxy logs the failure and carries on with its other fallbacks.

Run Fortran ifx: Refresh Symbol Map to regenerate the map mid-session after a rebuild, or generate it yourself:

python tools/generate_fortran_symbols.py <search-dir> --output build/debug/fortran_symbols.json

What To Expect During Debugging

When you debug with fortran-ifx, the proxy sits between VS Code and the cpptools backend and patches the main Fortran pain points:

  • evaluate
    • direct backend evaluation first
    • inquiry fallback for allocated, associated, size, shape, lbound, and ubound
    • symbol-map mangling fallback
    • resolved-index and cast fallback for arrays
    • synthetic array fallback for allocatable arrays and sections
    • scope-walk fallback
  • variables
    • repair unreadable top-level or child variables when possible
    • format Fortran CHARACTER values more readably
    • expose synthetic arrays and synthetic derived-type objects
  • setBreakpoints
    • rewrite common Fortran conditional-breakpoint syntax to forms cpptools can evaluate

Source Caching And Editing During A Session

Several recovery paths read your Fortran sources: array bounds come from the allocate statement that created the array, and derived-type fields come from the type definition. To keep that off the per-step path, the proxy indexes every allocate statement in the source tree once per session and caches the source-file listing alongside it.

Consequences worth knowing:

  • The index is built when the session launches, not on first use, so the cost is paid while your program is starting.
  • Editing sources mid-session invalidates the index through a recursive filesystem watcher. Recursive watching is not available on every platform (notably Linux), and where it is missing the index simply lives for the rest of the session — rebuild and restart the session to pick up changes.
  • Bounds resolved against a stopped frame's live variables are not cached across steps; only the source facts are. Stepping always re-resolves values.

Diagnostics

The proxy runs as a separate process and cannot write to the extension's output channel, so its diagnostics go to a log file. Open it with Fortran ifx: Open Proxy Log, or find it at fortran-inspect-proxy.log in your temp directory (%TEMP% on Windows).

The extension's own output channel ("Fortran ifx Debug") carries only activation, configuration resolution, and command results.

When an expression does not resolve, the proxy log shows which fallback stage gave up. Lines worth knowing:

Log line What it tells you
cache priming settled elapsed= Symbol map and allocate index are ready
symbol map loaded path= entries= The map was found and how many symbols it has
symbol map generation failed The generator ran but did not succeed
allocate index built mode= files= statements= Source index is ready; mode=sync means something needed it before priming finished
evaluate direct success The backend resolved it; no fallback needed
evaluate fallback expr= Direct evaluation failed; the fallback chain starts
array bounds source scan expr= candidates= matches= How many allocate statements matched an array
scope walk expr= reads= skipped= timedOut= Containers opened, repeats skipped, and whether the walk hit its budget

A scope walk is bounded to 750ms; timedOut=true on a lookup you expect to succeed means the walk ran out of budget rather than the variable being absent.

Project Layout Notes

Source-assisted features such as array-bound recovery, derived-type field recovery, and some inquiry fallbacks work best when the proxy can infer a source tree from the symbol-map location. In the current implementation, the best-supported layout is:

<workspace>/
  src/
  build/
    debug/
      fortran_symbols.json

If your layout differs, the extension can still work, but some source-parsed recovery paths may be weaker unless you provide an explicit symbol-file path.

Development

npm install
npm run compile
npm run watch
npm run lint
npm test

Press F5 in VS Code to launch an Extension Development Host and test the extension from source.

Layout

The extension host and the proxy are separate processes and do not share state. Only the extension-host side may import vscode.

src/
  extension.ts                 activation, command registration
  fortranDebugProvider.ts      launch-config resolution and defaults
  fortranDebugAdapterFactory.ts  backend selection, spawns the proxy
  logger.ts                    extension-host output channel

  proxyAdapter.ts              the proxy: DAP interception and fallbacks
  dapFraming.ts                DAP wire framing
  dapTypes.ts                  shared type definitions
  proxyConstants.ts            tunables (budgets, limits, feature flags)

  allocateIndex.ts             session index of `allocate` statements
  scopeWalkGuard.ts            scope-walk budget and visited set
  asyncPool.ts                 bounded-concurrency file reads
  expressionHelpers.ts         Fortran expression parsing and formatting
  fortranSourceParser.ts       source line/comment handling
  fortranDeclarationHelpers.ts declaration parsing
  arraySectionHelpers.ts       array-section arithmetic
  evaluateHelpers.ts           DAP value heuristics
  breakpointRewriter.ts        Fortran condition rewriting

  tamanduaFacts.ts             reader for a Tamandua facts file
  breakpointPlanner.ts         variable -> conditional breakpoint sites
  breakpointService.ts         places them via the VS Code debug API
  debugSession.ts              drives a session; reads frames and variables
  debugFormat.ts               compact rendering of a stopped frame
  mcpServer.ts                 loopback MCP server (JSON-RPC over HTTP)
  startupHelpers.ts            argument parsing, launch settings
  symbolMapTools.ts            symbol-map generation
  runtimePaths.ts              log and symbol-map paths
  workspaceLaunchSeed.ts       launch-config discovery (not yet wired up)

proxyAdapter.ts holds the stateful DAP machinery. Pure logic lives in the smaller modules so it can be unit tested — test/ runs against the compiled output in out/ with no test framework, via node test/run-tests.js. A test module either asserts at require time or exports a promise the runner awaits. Adding a test means creating test/<name>.test.js and listing it in test/run-tests.js.

Tunables live in proxyConstants.ts: scope-walk and variable-repair budgets, indexed page size, search depths, and flags to disable variable repair or the watch-evaluate fallback when isolating a problem.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft