# Using GDB and LLDB

### Using GDB and LLDB

GDB and LLDB are debuggers.

A debugger lets you run a program under inspection. You can stop the program, move through it line by line, inspect variables, and see the call stack.

For Zig beginners, debuggers are useful when `std.debug.print` is no longer enough.

#### Build with Debug Information

First, compile normally:

```bash
zig build-exe main.zig
```

Debug information is included in normal debug builds.

Then run the program with a debugger.

With LLDB:

```bash
lldb ./main
```

With GDB:

```bash
gdb ./main
```

On macOS, LLDB is usually the better default. On Linux, both GDB and LLDB are common.

#### A Small Program to Debug

Save this as `main.zig`:

```zig
const std = @import("std");

fn add(a: i32, b: i32) i32 {
    const result = a + b;
    return result;
}

pub fn main() void {
    const x = add(10, 20);
    std.debug.print("x = {}\n", .{x});
}
```

Build it:

```bash
zig build-exe main.zig
```

Start LLDB:

```bash
lldb ./main
```

Inside LLDB, run:

```text
breakpoint set --name main
run
```

The program stops at `main`.

#### Common LLDB Commands

| Command | Meaning |
|---|---|
| `run` | start the program |
| `breakpoint set --name main` | stop when `main` starts |
| `breakpoint set --file main.zig --line 8` | stop at a specific line |
| `next` | run the next line without entering function calls |
| `step` | enter the function call on this line |
| `finish` | finish the current function and return to the caller |
| `frame variable` | show local variables |
| `bt` | show the call stack |
| `continue` | continue running |
| `quit` | exit the debugger |

Example session:

```text
(lldb) breakpoint set --name main
(lldb) run
(lldb) next
(lldb) frame variable
(lldb) continue
```

#### Common GDB Commands

| Command | Meaning |
|---|---|
| `run` | start the program |
| `break main` | stop when `main` starts |
| `break main.zig:8` | stop at a specific line |
| `next` | run the next line without entering function calls |
| `step` | enter the function call on this line |
| `finish` | finish the current function and return to the caller |
| `info locals` | show local variables |
| `backtrace` | show the call stack |
| `continue` | continue running |
| `quit` | exit the debugger |

Example session:

```text
(gdb) break main
(gdb) run
(gdb) next
(gdb) info locals
(gdb) continue
```

#### Breakpoints

A breakpoint tells the debugger:

Stop here.

For example, in LLDB:

```text
breakpoint set --file main.zig --line 5
```

In GDB:

```text
break main.zig:5
```

When the program reaches that line, it pauses.

This lets you inspect the program before it continues.

#### Stepping Through Code

After the program stops, you can move through it one line at a time.

`next` runs the next line but does not enter function calls.

`step` enters a function call.

Example:

```zig
const x = add(10, 20);
```

If you use `next`, the debugger runs the whole call to `add`.

If you use `step`, the debugger enters `add` so you can inspect it.

Use `next` when the function is not interesting.

Use `step` when the function may contain the bug.

#### Inspecting Variables

Inside a stopped program, inspect local variables.

In LLDB:

```text
frame variable
```

In GDB:

```text
info locals
```

For a specific variable:

```text
(lldb) frame variable x
```

or:

```text
(gdb) print x
```

This is useful when a value is wrong but you do not know where it first became wrong.

#### Reading the Call Stack

The call stack shows which functions are active.

In LLDB:

```text
bt
```

In GDB:

```text
backtrace
```

Example:

```text
main
parseConfig
parseNumber
```

Read this as:

`main` called `parseConfig`.

`parseConfig` called `parseNumber`.

The program is currently inside `parseNumber`.

The call stack helps you find the route to the bug.

#### Debugging a Failing Test

You can also debug tests.

Suppose this file is `main.zig`:

```zig
const std = @import("std");

fn divide(a: i32, b: i32) i32 {
    return @divTrunc(a, b);
}

test "divide works" {
    try std.testing.expectEqual(@as(i32, 5), divide(10, 0));
}
```

Build and run the test normally first:

```bash
zig test main.zig
```

It fails because division by zero is invalid.

To debug test code, you can build the test executable:

```bash
zig test main.zig -femit-bin=test-main
```

Then run it under LLDB:

```bash
lldb ./test-main
```

or GDB:

```bash
gdb ./test-main
```

Now you can set breakpoints and inspect variables like a normal executable.

#### Debuggers and Optimized Builds

Debug optimized code only when you have to.

In optimized builds, the compiler may inline functions, remove variables, reorder instructions, or combine operations. That can make debugging confusing.

For normal debugging, use a debug build:

```bash
zig build-exe main.zig
```

Avoid starting with:

```bash
zig build-exe main.zig -O ReleaseFast
```

First reproduce the bug in debug mode. Then use optimized builds later for performance testing.

#### Debugging Memory Problems

For memory problems, start with tests and the testing allocator.

Example:

```zig
const std = @import("std");

test "allocate buffer" {
    const allocator = std.testing.allocator;

    const buf = try allocator.alloc(u8, 100);
    defer allocator.free(buf);

    buf[0] = 42;

    try std.testing.expectEqual(@as(u8, 42), buf[0]);
}
```

If the program crashes, use the debugger to inspect:

the pointer

the length

the index

the caller

Most beginner memory bugs are caused by one of these:

using the wrong length

using an invalid index

freeing memory too early

forgetting who owns the memory

returning a pointer to temporary data

#### Debugger or Print?

Use `std.debug.print` when the question is simple:

```zig
std.debug.print("len = {}\n", .{items.len});
```

Use a debugger when the question needs repeated inspection:

Where did this value first change?

Which branch did the program take?

What is the full call stack?

What are several variables at this point?

Print debugging is faster to start. A debugger gives you deeper control.

#### A Practical Debugging Loop

A good workflow is:

1. Reproduce the bug.
2. Run the smallest failing example.
3. Set a breakpoint near the failure.
4. Inspect variables.
5. Step backward mentally through the call stack.
6. Fix the wrong assumption.
7. Add a test for the bug.

The last step matters. After you fix a bug, keep a test that would have caught it.

#### The Main Idea

GDB and LLDB let you pause a Zig program and inspect it while it runs.

You do not need them for every bug. Many bugs can be solved with tests, stack traces, and debug prints. But when a value changes in a surprising way, or when a crash happens deep inside several function calls, a debugger gives you a controlled way to look inside the program.

