# Zig Debugging with LLDB

**URL:** <https://ziggit.dev/t/zig-debugging-with-lldb/3931>\
**Category:** Explain\
**Created:** [April 12, 2024, 6:22pm UTC](https://ziggit.dev/t/zig-debugging-with-lldb/3931 "2024-04-12T18:22:56Z")\
**Posts on this page:** 17\
**Page:** 1

<div class="post-metadata">

**Author:** ![chrboesch](https://ziggit.dev/user_avatar/ziggit.dev/chrboesch/32/438_2.png) [@chrboesch](https://ziggit.dev/u/chrboesch)\
**Post date:** [April 12, 2024, 6:22pm UTC](https://ziggit.dev/t/zig-debugging-with-lldb/3931/1 "2024-04-12T18:22:56Z")

</div>

Short example of degugging with LLDB.  
As an example, I will use a small program that I wrote for Rosetta Code:

```zig
const std = @import("std");
const stdout = @import("std").io.getStdOut().writer();

pub fn rot(txt: []u8, key: u8) void {
    for (txt, 0..txt.len) |c, i| {
        if (std.ascii.isLower(c)) {
            txt[i] = (c - 'a' + key) % 26 + 'a';
        } else if (std.ascii.isUpper(c)) {
            txt[i] = (c - 'A' + key) % 26 + 'A';
        }
    }
}

pub fn main() !void {
    const key = 3;
    var txt = "The five boxing wizards jump quickly".*;

    try stdout.print("Original: {s}\n", .{txt});
    rot(&txt, key);
    try stdout.print("Encrypted: {s}\n", .{txt});
    rot(&txt, 26 - key);
    try stdout.print("Decrypted: {s}\n", .{txt});
}

```

Now let’s compile:

```zig
zig build-exe caesar.zig

```

Start debugging:

```zig
lldb caesar 

```

```zig
(lldb) target create "caesar"
Current executable set to '/home/chris/workspace/zig/rosetta/caesar' (x86_64).
(lldb) █

```

Now we set a breakpoint at main function:

```zig
(lldb) b main
Breakpoint 1: where = caesar`caesar.main + 18 at caesar.zig:16:5, address = 0x0000000001033c22
(lldb) █

```

And then run the program:

```zig
(lldb) r
Process 24175 launched: '/home/chris/workspace/zig/chris/usb/caesar' (x86_64)
Process 24175 stopped
* thread #1, name = 'caesar', stop reason = breakpoint 1.1
    frame #0: 0x0000000001033c22 caesar`caesar.main at caesar.zig:16:5
   13  	
   14 pub fn main() !void {
   15 const key = 3;
-> 16 var txt = "The five boxing wizards jump quickly".*;
   17  	
   18 try stdout.print("Original: {s}\n", .{txt});
   19 rot(&txt, key);
(lldb) █

```

We can step forward with next:

```zig
(lldb) n
Process 24175 stopped
* thread #1, name = 'caesar', stop reason = step over
    frame #0: 0x0000000001033c43 caesar`caesar.main at caesar.zig:18:21
   15 const key = 3;
   16 var txt = "The five boxing wizards jump quickly".*;
   17  	
-> 18 try stdout.print("Original: {s}\n", .{txt});
   19 rot(&txt, key);
   20 try stdout.print("Encrypted: {s}\n", .{txt});
   21 rot(&txt, 26 - key);
(lldb) █

```

And so on. A very nice feature is switching to GUI mode:

```zig
(lldb) gui

```

```zig
| LLDB (F1) | Target (F2) | Process (F3) | Thread (F4) | View (F5) | Help (F6) |                                                                                                                                                                                                                                               
┌──<Sources>──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐┌──<Threads>───────────────────────────────────────────────────┐
│ caesar`caesar.main ││ ◆─process 24175 │
│ 1 │ const std = @import("std"); ││ └─◆─thread #1: tid = 0x5e6f, stop reason = step over │
│ 2 │ const stdout = @import("std").io.getStdOut().writer(); ││ ├─#0: caesar.main + 51 │
│ 3 │ ││ ├─#1: start.posixCallMainAndExit [inlined] start.callMain +│
│ 4 │ pub fn rot(txt: []u8, key: u8) void { ││ ├─#2: start.posixCallMainAndExit + 93 │
│ 5 │ for (txt, 0..txt.len) |c, i| { ││ ├─#3: start.posixCallMainAndExit + 1133 │
│ 6 │ if (std.ascii.isLower(c)) { ││ └─#4: start._start + 18 │
│ 7 │ txt[i] = (c - 'a' + key) % 26 + 'a'; ││ │
│ 8 │ } else if (std.ascii.isUpper(c)) { ││ │
│ 9 │ txt[i] = (c - 'A' + key) % 26 + 'A'; ││ │
│ 10 │ } ││ │
│ 11 │ } ││ │
│ 12 │ } ││ │
│ 13 │ ││ │
│ 14 │ pub fn main() !void { ││ │
│ 15 │ const key = 3; ││ │
│ 16 │ var txt = "The five boxing wizards jump quickly".*; ││ │
│ 17 │ ││ │
│ 18 │◆ try stdout.print("Original: {s}\n", .{txt}); <<< Thread 1: step over││ │
│ 19 │ rot(&txt, key); ││ │
│ 20 │ try stdout.print("Encrypted: {s}\n", .{txt}); ││ │
│ 21 │ rot(&txt, 26 - key); ││ │
│ 22 │ try stdout.print("Decrypted: {s}\n", .{txt}); ││ │
│ 23 │ } ││ │
│ 24 │ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘│ │
┌──<Variables>────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐│ │
│ ◆─(unsigned char[36]) txt "The five boxing wizards jump quickly" ││ │
│ ◆─(elf.Elf64_Dyn) _DYNAMIC ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
│ ││ │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────────┘
Process: 24175 stopped Thread: 24175 Frame: 0 PC = 0x0000000001033c43                                                                                                                                                                                                                                

```

You can set breakpoints there by navigating to a line and pressing ‘b’. To exit the GUI, press Esc.  
The other commands can be found in the help.

---

<div class="post-metadata">

**Author:** ![jaime](https://ziggit.dev/user_avatar/ziggit.dev/jaime/32/1842_2.png) [@jaime](https://ziggit.dev/u/jaime)\
**Post date:** [April 12, 2024, 6:46pm UTC](https://ziggit.dev/t/zig-debugging-with-lldb/3931/2 "2024-04-12T18:46:34Z")

</div>

That was cool! Let me tell you that I enjoyed your ziglings exercices (and sense of humor you put in it) 😀

---

<div class="post-metadata">

**Author:** ![chrboesch](https://ziggit.dev/user_avatar/ziggit.dev/chrboesch/32/438_2.png) [@chrboesch](https://ziggit.dev/u/chrboesch)\
**Post date:** [September 27, 2024, 9:26pm UTC](https://ziggit.dev/t/zig-debugging-with-lldb/3931/3 "2024-09-27T21:26:13Z")

</div>

Update:  
A great [video-tutorial on how to debug with lldb](https://www.youtube.com/watch?v=2GV0K9Y2MKA)!

---

<div class="post-metadata">

**Author:** ![andrewrk](https://ziggit.dev/user_avatar/ziggit.dev/andrewrk/32/7011_2.png) [@andrewrk](https://ziggit.dev/u/andrewrk)\
**Post date:** [September 27, 2024, 9:44pm UTC](https://ziggit.dev/t/zig-debugging-with-lldb/3931/4 "2024-09-27T21:44:32Z")

</div>

This is a bit advanced but [Jacob Young has a fork of LLDB](https://github.com/jacobly0/llvm-project/tree/lldb-zig) that adds quite sophisticated Zig support including standard library integration, which is meant to pair with the self-hosted x86 backend (`-fno-llvm -fno-lld`).

This is a way better debugging experience if you are using that backend.

---

<div class="post-metadata">

**Author:** ![wrapitup](https://ziggit.dev/user_avatar/ziggit.dev/wrapitup/32/1246_2.png) [@wrapitup](https://ziggit.dev/u/wrapitup)\
**Post date:** [October 18, 2024, 3:06pm UTC](https://ziggit.dev/t/zig-debugging-with-lldb/3931/5 "2024-10-18T15:06:41Z")

</div>

I’d like to add an extension to this thread: using `lldb` after a test.

Sometimes you have a failing test and you would like to use `lldb` to set breakpoints and observe the behavior of a smaller part of your function without having to run the entire program. This is possible to do with tests in Zig!

```zig
# filename: cumulative.zig
const std = @import("std");

fn cumulative_sum(slice: []u16) void {
    const l = slice.len - 1;
    for (slice[0..][0..l], slice[1..][0..l]) |*left, *right| {
        right.* = left.*; // the bug is here, I should use a +=
    }
}

test cumulative_sum {
    var slice = [_]u16{ 10, 20, 30, 40 };
    cumulative_sum(&slice);
    try std.testing.expectEqualSlices(u16, &[_]u16{ 10, 30, 60, 100 }, &slice);
}

```

now you can test

```zig
zig test cumulative.zig

```

You should see an error and at the very bottom you’ll find the path to the test object file:

```zig
0 passed; 0 skipped; 1 failed.
error: the following test command failed with exit code 1:
/path-to-zig-cache/.zig-cache/o/ead8187be37f01c571e970ede773ed12/test

```

Depending on if you are working in a project after using `zig init` or if you’re just working on a file somewhere else, the path may be to your project `.zig-cache` or to a global zig cache.

You can now debug that object!

```bash
lldb /path-to-zig-cache/.zig-cache/o/ead8187be37f01c571e970ede773ed12/test

```

You can now set breakpoints as mentioned above and then run the process.

```zig
(lldb) breakpoint set -n cumulative.cumulative_sum
(lldb) r

```

If you want to see the contents of a slice, you can pass `slice.len` into the `parray` command. For example, in the `cumulative_sum()` function above, once we have set a breakpoint

```lldb
(lldb) parray `slice.len` slice.ptr

```

### Explicit @breakpoint()

If you want to explicitly add in a breakpoint in the code, just use `@breakpoint()` and then run the test. The test should immediately crash and then you can load the object with lldb and just enter `r` to launch the process. You should stop at every `@breakpoint()` that you set.

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

fn cumulative_sum(slice: []u16) void {
    const l = slice.len - 1;
    // now lldb will stop at this breakpoint without having to set it in the lldb console
    @breakpoint();
    for (slice[0..][0..l], slice[1..][0..l]) |*left, *right| {
        @breakpoint();
        right.* = left.*;
    }
}

test cumulative_sum {
    var slice = [_]u16{ 10, 20, 30, 40 };
    cumulative_sum(&slice);
    try std.testing.expectEqualSlices(u16, &[_]u16{ 10, 30, 60, 100 }, &slice);
}

```

---

<div class="post-metadata">

**Author:** ![ajoino](https://ziggit.dev/user_avatar/ziggit.dev/ajoino/32/3120_2.png) [@ajoino](https://ziggit.dev/u/ajoino)\
**Post date:** [October 30, 2024, 6:49pm UTC](https://ziggit.dev/t/zig-debugging-with-lldb/3931/6 "2024-10-30T18:49:08Z")

</div>

Is there a way to use lldb when running your tests with `zig build test`? The tutorial and resources I could find just talk about running lldb with executables.

---

<div class="post-metadata">

**Author:** ![dimdin](https://ziggit.dev/user_avatar/ziggit.dev/dimdin/32/1457_2.png) [@dimdin](https://ziggit.dev/u/dimdin)\
**Post date:** [October 30, 2024, 7:19pm UTC](https://ziggit.dev/t/zig-debugging-with-lldb/3931/7 "2024-10-30T19:19:15Z")

</div>

Yes, there are ways to debug tests.

`zig build test` actually produces an executable and then runs the executable.

Calling [`addTest`](https://ziglang.org/documentation/master/std/#std.Build.addTest), without calling [`addRunArtifact`](https://ziglang.org/documentation/master/std/#std.Build.addRunArtifact) the test executable is produced but it is not run.

You can run `lldb` using the [`addSystemCommand`](https://ziglang.org/documentation/master/std/#std.Build.addSystemCommand):

```zig
    const unit_tests = b.addTest({
        // TODO add here the test options
    });

    const lldb = b.addSystemCommand(&.{
        "lldb",
        // add lldb flags before --
        "--",
    });
    // appends the unit_tests executable path to the lldb command line
    lldb.addArtifactArg(unit_tests);
    // lldb.addArg can add arguments after the executable path

    const lldb_step = b.step("debug", "run the tests under lldb");
    lldb_step.dependOn(&lldb.step);

```

---

<div class="post-metadata">

**Author:** ![ajoino](https://ziggit.dev/user_avatar/ziggit.dev/ajoino/32/3120_2.png) [@ajoino](https://ziggit.dev/u/ajoino)\
**Post date:** [October 30, 2024, 7:46pm UTC](https://ziggit.dev/t/zig-debugging-with-lldb/3931/8 "2024-10-30T19:46:19Z")

</div>

That’s very cool, thanks a lot!

---

<div class="post-metadata">

**Author:** ![chrboesch](https://ziggit.dev/user_avatar/ziggit.dev/chrboesch/32/438_2.png) [@chrboesch](https://ziggit.dev/u/chrboesch)\
**Post date:** [October 30, 2024, 7:58pm UTC](https://ziggit.dev/t/zig-debugging-with-lldb/3931/9 "2024-10-30T19:58:25Z")

</div>

Please don’t quote the entire previous entry. That makes it unreadable. Just press the heart if you like it.

---

<div class="post-metadata">

**Author:** ![andrewrk](https://ziggit.dev/user_avatar/ziggit.dev/andrewrk/32/7011_2.png) [@andrewrk](https://ziggit.dev/u/andrewrk)\
**Post date:** [October 30, 2024, 9:17pm UTC](https://ziggit.dev/t/zig-debugging-with-lldb/3931/10 "2024-10-30T21:17:12Z")

</div>

Handy tip when trying to debug tests: if the test binary crashes it will print the command that you could paste into a debugger. And one way to make it crash is to use `@breakpoint()` somewhere. Then you kill 2 birds with 1 stone because the debugger will stop at your breakpoint.

---

<div class="post-metadata">

**Author:** ![joern](https://ziggit.dev/letter_avatar_proxy/v4/letter/j/dc4da7/32.png) [@joern](https://ziggit.dev/u/joern)\
**Post date:** [November 29, 2025, 8:31am UTC](https://ziggit.dev/t/zig-debugging-with-lldb/3931/11 "2025-11-29T08:31:09Z")

</div>

It might be necessary to add **.use\_llvm = true** to const exe = b.addExecutable to see symbols when using a newer Zig version.

---

<div class="post-metadata">

**Author:** ![vulpesx](https://ziggit.dev/user_avatar/ziggit.dev/vulpesx/32/3989_2.png) [@vulpesx](https://ziggit.dev/u/vulpesx)\
**Post date:** [November 29, 2025, 8:40am UTC](https://ziggit.dev/t/zig-debugging-with-lldb/3931/12 "2025-11-29T08:40:43Z")

</div>

Yes, only when building for x86\_64 Linux atm, but that will change in the future.

There is also the [LLB fork](https://github.com/jacobly0/llvm-project) that works with the custom backends, I think it has a better experience, but you need to build from source.

Normal LLB should support zig in the future, atm zig pretends to be CPP when using LLVM. In addition, the custom backends are using experimental debug information that isn’t supported yet either, but that will also change.

---

<div class="post-metadata">

**Author:** ![Sze](https://ziggit.dev/user_avatar/ziggit.dev/sze/32/496_2.png) [@Sze](https://ziggit.dev/u/Sze)\
**Post date:** [January 8, 2026, 3:15pm UTC](https://ziggit.dev/t/zig-debugging-with-lldb/3931/13 "2026-01-08T15:15:39Z")

</div>

10 posts were split to a new topic: [How to debug Zig with LLDB on Windows](https://ziggit.dev/t/how-to-debug-zig-with-lldb-on-windows/13862)

---

<div class="post-metadata">

**Author:** ![dimdin](https://ziggit.dev/user_avatar/ziggit.dev/dimdin/32/1457_2.png) [@dimdin](https://ziggit.dev/u/dimdin)\
**Post date:** [January 8, 2026, 3:23pm UTC](https://ziggit.dev/t/zig-debugging-with-lldb/3931/14 "2026-01-08T15:23:18Z")

</div>

A post was merged into an existing topic: [How to debug Zig with LLDB on Windows](https://ziggit.dev/t/how-to-debug-zig-with-lldb-on-windows/13862/12)

---

<div class="post-metadata">

**Author:** ![yf13](https://ziggit.dev/letter_avatar_proxy/v4/letter/y/848f3c/32.png) [@yf13](https://ziggit.dev/u/yf13)\
**Post date:** [February 16, 2026, 1:09am UTC](https://ziggit.dev/t/zig-debugging-with-lldb/3931/15 "2026-02-16T01:09:04Z")

</div>

I am not able to set breakpoint when used zig build like below on Ubuntu 22.04:

```zig
$ mkdir /tmp/hello
$ cd /tmp/hello
$ zig version
0.16.0-dev.2193+fc517bd01
$ zig init
$ cd /tmp/aaa
$ zig build --build-file ../hello/build.zig --global-cache-dir /tmp/.cache --cache-dir .cache -p . -j4
$ file bin/hello
bin/hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, with debug_info, not stripped
$ lldb -v
lldb version 21.1.8 (https://github.com/llvm/llvm-project revision 2078da43e25a4623cab2d0d60decddf709aaea28)
$ lldb bin/hello
(lldb) target create "bin/hello"
Current executable set to '/tmp/aaa/bin/hello' (x86_64).
(lldb) b main
Breakpoint 1: no locations (pending).
WARNING: Unable to resolve breakpoint to any actual locations.
(lldb) q
$ bin/hello 
All your codebase are belong to us.
info: arg: bin/hello
Run `zig build test` to run the tests.

```

Maybe my out-of-tree build usage is incorrect?

Update: maybe the `lldb` I am using is weird as it doesn’t work even for in-tree build:

```zig
`$ cd /tmp/aaa`
`$ zig init`
`$ zig build —build-file ./build.zig –global-cache-dir /tmp/.cache –cache-dir .cache -p . -j4`
`$ file bin/aaa`
`bin/aaa: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, with debug_info, not stripped`
`$ lldb bin/aaa`
`lldb) target create “bin/aaa”`
`Current executable set to ‘/tmp/aaa/bin/aaa’ (x86_64).`
`(lldb) b main`
`Breakpoint 1: no locations (pending).`
`WARNING: Unable to resolve breakpoint to any actual locations.`
`(lldb) q`

```

Fortunately the stock `gdb (Ubuntu 12.1-0ubuntu1~22.04.2) 12.1` seems working so I can use it for now.

---

<div class="post-metadata">

**Author:** ![Calder-Ty](https://ziggit.dev/user_avatar/ziggit.dev/calder-ty/32/10237_2.png) [@Calder-Ty](https://ziggit.dev/u/Calder-Ty)\
**Post date:** [February 23, 2026, 9:29am UTC](https://ziggit.dev/t/zig-debugging-with-lldb/3931/16 "2026-02-23T09:29:59Z")

</div>

In this thread @vulpesx has a good explanation for why you are getting errors and how to get around it

> [@Debugging Zig (with a debugger)](https://ziggit.dev/t/debugging-zig-with-a-debugger/7160/32):
>
> if you are using zig 0.15+ on x86\_64 linux, then the custom backend is enabled by default in debug mode. You can force the use of llvm with .use\_llvm = true for your compile options, or -fllvm if you’re not using the build system.

---

<div class="post-metadata">

**Author:** ![yf13](https://ziggit.dev/letter_avatar_proxy/v4/letter/y/848f3c/32.png) [@yf13](https://ziggit.dev/u/yf13)\
**Post date:** [February 24, 2026, 12:26am UTC](https://ziggit.dev/t/zig-debugging-with-lldb/3931/17 "2026-02-24T00:26:28Z")

</div>

Thanks for the explanation. I will give zig forked lldb a try later.
