r2fas 0.2.1

radare2 core plugin that loads FASM -s symbolic dumps for named labels, source lines, and comments
# r2fas

radare2 core plugin that loads [FASM](https://flatassembler.net/) symbolic dumps (`fasm -s`) so disassembly and debugging look like a DWARF build: named labels, source file:line, comments, and functions.

After install, r2 loads the plugin from its plugin directory and autoloads a sibling dump when you open a binary.

The repository is a Cargo workspace: `crates/radare2` owns reusable radare2 ABI, core, plugin-session, configuration, and IO-URI helpers; `crates/r2fas` owns FAS parsing and the `core_fas` plugin.

**Caution:** this project is totally vibecoded. Use it with caution. Read the code before you trust the flags, addresses, or debugger behaviour.

## Install

Needs radare2 **6.x** development files (`pkg-config r_core`) and a Rust toolchain (1.85+, edition 2024).

With radare2's package manager (recommended once `r2fas` is accepted into the official database):

```text
r2pm -U
r2pm -i r2fas
```

The package definition is maintained at `r2pm/r2fas`. It compiles against the user's currently installed radare2 headers and libraries through `pkg-config r_core`, and installs into `R2PM_PLUGDIR`.

Arch Linux:

```text
yay -S r2fas
```

From source:

```text
make
make install
```

`make install` copies `core_fas.so` to `~/.local/share/radare2/plugins`, which r2 already searches.

Confirm:

```text
r2 -qc 'L~fas' -
```

## Use

Assemble with a sidecar dump named `*.fas` next to the binary.

```text
fasm hello.asm -s hello.fas
r2 ./hello
# or
r2 -d ./hello
```

You should see a one-line `fas: loaded N symbols, M lines, F functions, X xrefs from …` message. Then:

```text
pd 20 @ start
pdf @ start
db msg
CL @ start
```

If FASM wrote no dump, the plugin stays quiet.

### Manual commands (optional)

Autoload is the normal path. These exist if you need them:

| Command | Meaning |
| --- | --- |
| `fas` / `fas.` | Search and load a sibling dump |
| `fas load [path]` | Load an explicit `.fas` |
| `fas info` | Stats for the last load |
| `fas unload` | Remove owned symbols, functions, xrefs, and flags; restore prior source lines |
| `fas?` | Help |

### Eval knobs

| Key | Default | Meaning |
| --- | --- | --- |
| `fas.autoload` | `true` | Load on `cmd.load` (binary open) |
| `fas.comments` | `true` | Source text as `CCu` comments |
| `fas.analyze` | `true` | `af` at uncovered code labels |

## How autoload finds the dump

Given an opened file `/path/hello`:

1. `/path/hello.fas`
2. `/path/hello` with a `.fas` extension (same as 1 if there was no suffix)

A candidate is used only if it is a valid FAS file whose header output name matches the opened basename (so a leftover dump from another program is ignored).

This mirrors r2 PDB sidecar search (`pdb.autoload`) and r2 DWARF “just works on open”, not the x64dbg FAS plugins that require a file-picker.

`cmd.load`, `cmd.open`, and `cmd.prompt` are wrapped with `fas.` on plugin init. Apply waits until an IO map covers dump addresses (or the IO is `ptrace://`), so symbols are not planted at file offsets. `cmd.prompt` retries after `r2 -d` attaches. If you already set those hooks, the previous value is still run after `fas.`.

## What gets applied

Parsed from [FAS.TXT](https://github.com/tgrysztar/fasm/blob/master/TOOLS/FAS.TXT):

- **Labels** (defined, not `=` variables, not markers, not `?` internals) → native `RBinSymbol` entries visible in `is`/`isj`, plus flags in the `fas` flagspace
- **Sized data** → native `OBJ` symbols and `Cd`
- **Assembly dump rows** → direct native addrline-store rows, including source paths with spaces; `CL`/`CLj` and `pd` use them
- **Source text** → comments for macro-generated rows, walking macro invocations back to the originating line
- **Functions** → conservative emitted code labels on executable maps are analyzed with radare2's native function engine and appear in `afl`/`aflj`
- **References** → FAS symbol-use records become conservative `DATA` xrefs in `axl`/`axlj`; r2fas does not guess call/jump semantics that FAS does not encode
- **PIE / `r2 -d`** → each runtime address is resolved through active paddr/vaddr IO maps, with a guarded image-base fallback only when FAS lacks a paddr
- **Unload** → removes only plugin-owned symbols/functions/xrefs/flags and restores the exact addrline rows that existed before loading; rollback is refused if external mutations make ownership ambiguous

Some radare2 versions' `id`/`idj` command bypasses addrline rows populated by an external core plugin, so it can remain empty even though `CLj`, disassembly annotations, and source lookup work. r2fas does not patch radare2, fabricate DWARF, or mutate a bin-plugin vtable; it builds against and uses the radare2 version installed on the user's machine.

## License

MIT