# Debugging

> Debug builds, brine debug, breaking at file.br:line and at Brine functions in gdb and lldb, the gdb pretty-printers, the C fallback, and BRINE_LEAKCHECK.

Source: https://docs.brinelang.org/tooling/debugging/

`brine debug` makes a debug build and starts gdb (or lldb) on it. On x86-64 Windows and Linux and on AArch64 Linux, debug builds are native code with DWARF debug info: you break at `file.br:line` or at a Brine function such as `Point.norm`, step through Brine lines, and see every local by its Brine name and type. With gdb, values print the way Brine prints them (`"origin"`, `[1, 2, 3]`, `Shape.Rect(w=2, h=3)`).

## A session

[Section titled “A session”](#a-session)

shapes.br

```brine
struct Point:
    x: float
    y: float


    fn norm(self) -> float:
        return sqrt(self.x * self.x + self.y * self.y)


fn main():
    let name = "origin"
    let xs = [1, 2, 3]
    let p = Point(x=3.0, y=4.0)
    let n = p.norm()
    print(name, xs, n)
```

```sh
brine debug shapes.br
```

A session on Windows with gdb 17.2, with the commands typed after each `(gdb)` prompt (gdb’s `[New Thread ...]` lines are left out):

```text
     Built shapes [debug] in 0.10s
Reading symbols from C:\Users\you\AppData\Local\Temp\brine-cache\d708f6e0b6b1cd6e\debug\shapes.exe...
(gdb) break shapes.br:13
Breakpoint 1 at 0x1400019e4: file shapes.br, line 13.
(gdb) run
Starting program: C:\Users\you\AppData\Local\Temp\brine-cache\d708f6e0b6b1cd6e\debug\shapes.exe


Thread 1 hit Breakpoint 1, main () at shapes.br:13
13      print(name, xs, n)
(gdb) info locals
n = 5
p = {x = 3, y = 4}
xs = [1, 2, 3]
name = "origin"
```

Methods are named `Type.method`, so you can break on them directly:

```text
(gdb) break Point.norm
Breakpoint 2 at 0x14000176f: file shapes.br, line 6.
(gdb) run
Thread 1 hit Breakpoint 2, Point.norm (self=...) at shapes.br:6
6          return sqrt(self.x * self.x + self.y * self.y)
(gdb) bt
#0  Point.norm (self=...) at shapes.br:6
#1  0x00007ff70c6419d4 in main () at shapes.br:12
#2  0x00007ff70c6418f5 in main ()
#3  0x00007ff70c6413ba in __tmainCRTStartup ()
#4  0x00007ff70c641080 in mainCRTStartup ()
(gdb) info args
self = {x = 3, y = 4}
(gdb) finish
0x00007ff70c6419d4 in main () at shapes.br:12
12      let n = p.norm()
Value returned is $1 = 5
```

Frame `#1 main` is your `fn main`; the second `main` below it is the runtime’s C entry point, which starts the thread pool and calls yours.

## brine debug

[Section titled “brine debug”](#brine-debug)

```sh
brine debug                      # the current project
brine debug shapes.br            # a single file
brine debug shapes.br -- a b c   # pass arguments to the program
```

`brine debug` builds with the debug profile (the same build as `brine build --debug`), then starts:

| Debugger found     | Command Brine runs                                                                                                                                     |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| gdb with Python    | `gdb -q -x <dir>/brine-gdb.py --args <exe> [args]`: loads the [pretty-printers](#the-gdb-pretty-printers), which also skip the C runtime when stepping |
| gdb without Python | `gdb -q -ex "skip -gfi */brine_rt.c" -ex "skip -gfi */brine_rt.h" --args <exe> [args]`: steps skip the runtime, values print as their C layout         |
| lldb (no gdb)      | `lldb -O "settings set target.process.thread.step-avoid-regexp ^(brine_\|br_)" -- <exe> [args]`                                                        |

It looks for `gdb` first, then `lldb`, on your `PATH`. Without either it stops after the build and says where the executable is. `brine doctor` shows which debugger it found.

## Debug builds

[Section titled “Debug builds”](#debug-builds)

```sh
brine build --debug
```

```text
     Built hello v0.1.0 [debug] in 0.97s
    Output target/debug/hello.exe
```

| Platform                                  | Debug build                                                                 |
| ----------------------------------------- | --------------------------------------------------------------------------- |
| x86-64 Windows (COFF), x86-64 Linux (ELF) | native code from the x86-64 backend, with DWARF                             |
| AArch64 Linux (ELF)                       | native code from the AArch64 backend, with DWARF                            |
| macOS, other platforms                    | C at `-O0 -g` with `#line` directives ([below](#through-c-line-directives)) |

A native debug build carries:

* **Lines**: every Brine line has an address, so `break file.br:line`, `step`, `next` and `finish` work in Brine terms.
* **Functions** under their Brine names: `main`, `area`, `Point.norm`, `Shape.area`. Functions from imported modules keep their plain names (`area` from `geometry.br` is `area`, at `geometry.br:2`). Each instance of a generic function has the generic’s name (`largest`).
* **Variables**: every local and parameter by its Brine name and type, in the scope where it’s declared.
* **Unwind info**, so `bt` and profilers walk through Brine frames.

Debug code skips the optimizations and keeps each value in its stack slot (no register allocation, no cached values), so what `print` shows is always the variable’s current value. Checks still run: a debug build panics exactly where a dev or release build would.

## Breakpoints

[Section titled “Breakpoints”](#breakpoints)

| To stop                              | gdb                        | lldb                           |
| ------------------------------------ | -------------------------- | ------------------------------ |
| at a line                            | `break shapes.br:13`       | `b shapes.br:13`               |
| in a function                        | `break main`, `break area` | `b main`                       |
| in a method                          | `break Point.norm`         | `b Point.norm`                 |
| in every function matching a pattern | `rbreak ^Point\.`          | `breakpoint set -r '^Point\.'` |
| on any panic                         | `break brine_panic_at`     | `b brine_panic_at`             |

Every panic (overflow, a bad index, a failed `assert`, `panic(...)`, `unwrap()` on `None`) goes through the runtime function `brine_panic_at`, which receives the Brine location and the message:

```text
(gdb) break brine_panic_at
(gdb) run
Thread 1 hit Breakpoint 1, brine_panic_at (loc=0x7ff6a6ee306c <br_ccd1c7eaaac8cf230> "panic.br:4:11", msg=0x5ff920 "index 5 is out of bounds for an array of length 3") at C:\Users\you\AppData\Local\Temp\brine-cache\rt-2fdf516b1c19bd8c\brine_rt.c:142
142      br_lock(&br_io_mu);
(gdb) frame 3
#3  0x00007ff6a6ec1902 in main () at panic.br:4
4      print(xs[i + 5])
```

Note

In Brine 0.4.0, once a panic has started, the panicking Brine frame’s locals aren’t visible (`No symbol "xs" in current context`). To look at them, break on the line itself (`break panic.br:4`) and run again.

## The gdb pretty-printers

[Section titled “The gdb pretty-printers”](#the-gdb-pretty-printers)

`runtime/brine_gdb.py` teaches gdb to print Brine values the way `print` does. `brine debug` writes it next to the debug build as `brine-gdb.py` and loads it when gdb has Python support.

| Type          | Without the printers                    | With them                |
| ------------- | --------------------------------------- | ------------------------ |
| `str`         | `0x7ff70c661010 <br_sb9eae4fe785a14cf>` | `"origin"`               |
| `[int]`       | `0x80000040`                            | `[1, 2, 3]`              |
| `{str: int}`  | a pointer                               | `{"ann": 31, "bob": 27}` |
| tuple         | a C struct                              | `(1, "two")`             |
| enum          | a C struct                              | `Shape.Rect(w=2, h=3)`   |
| `Option[int]` | a C struct                              | `Some(1)`                |

```text
(gdb) info locals
pair = (1, "two")
ages = {"ann": 31, "bob": 27}
found = Some(1)
s = Shape.Rect(w=2, h=3)
```

Structs keep gdb’s own layout (`{x = 3, y = 4}`). At most 200 elements or bytes of a value are shown.

To use the printers with gdb started by hand (or from an IDE), load the file from the Brine source tree, or the copy `brine debug` wrote:

```sh
gdb -x path/to/brine_gdb.py --args target/debug/hello
```

or, inside gdb, `source path/to/brine_gdb.py`. They also make `step` skip the runtime’s C functions.

## lldb

[Section titled “lldb”](#lldb)

lldb reads the same DWARF, so lines, functions and variables work as in gdb:

```sh
lldb target/debug/hello
```

```text
(lldb) b shapes.br:13
(lldb) run
(lldb) frame variable
(lldb) b Point.norm
(lldb) thread step-over
```

There are no lldb formatters yet: strings, arrays, maps and enums show as their C layout (pointers and structs). `brine debug` sets `step-avoid-regexp` so stepping doesn’t enter runtime functions (`brine_*`, `br_*`).

## Through C: #line directives

[Section titled “Through C: #line directives”](#through-c-line-directives)

Where there is no native backend (macOS, and any other platform that builds through C), and whenever `BRINE_BACKEND=c` is set, a debug build is C compiled at `-O0 -g`, with `#line` directives that point back at the Brine source. Breakpoints by file and line, stepping and backtraces still work, but:

* variables show under their C names: the Brine name and a number, like `name_0`, `xs_1`, `p_3`, `n_4`, next to the compiler’s temporaries (`v0`, `v1`, …);
* values don’t always match the Brine variable at a given line, because the C code keeps them in temporaries;
* the pretty-printers don’t apply to those C variables.

```sh
BRINE_BACKEND=c brine build --debug shapes.br -o shapes_c
```

Prefer a native debug build wherever there is one.

## Leak checking

[Section titled “Leak checking”](#leak-checking)

`BRINE_LEAKCHECK=1` makes a program print how many strings and arrays are still allocated when it exits. Memory is reference counted and freed at scope exit, so the answer should always be 0:

```sh
BRINE_LEAKCHECK=1 target/dev/hello
```

```text
Hello, brine!
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
brine: 0 live objects at exit
```

Brine’s own test suite requires 0. A non-zero count is a bug in Brine, not in your program; please report it with the program.

Tip

Set it on the program, not on `brine run`: the compiler is a Brine program too, so `BRINE_LEAKCHECK=1 brine run` prints a second line for the compiler itself.

## Related

[Section titled “Related”](#related)

* [The brine command](/tooling/cli/#brine-debug)
* [Builds, backends and performance](/tooling/builds-and-backends/)
* [Environment variables](/tooling/environment-variables/)
* [Runtime checks](/proofs/runtime-checks/)
