How to debug Zig with LLDB on Windows

New user here.

Quick question: I am running Zig 0.15.2 on Windows 11, and I’ve been trying to debug my code. I suspect I won’t go too far, even if I knew how to use LLDB on a terminal (hopefully, soon I’ll be able to fairly easily).

My build.zig is here (it’s a specific commit ID, so things may look slightly different, but I’m otherwise stuck there).

So far, the executables that I get from the install artefacts steps don’t seem to be interacting with LLDB or even CppVsDbg(?) at all, which I confirmed from both JetBrains CLion/Zigbrains (that defaults to CppVsDbg or the Microsoft C++ debugger) and VS Code/CodeLLDB. I simply get “All 0 tests passed”. For single-file tests, I get the correct outputs, but I haven’t figured out how to debug them, so I suspect that the build process is going wrong here.

EDIT: the question: Is it?

No. I compiled your program (after fixing a few minor bugs) and added a @brakpoint to the main function. Then I started it with lldb ./zig-out/bin/ziglox and after typing r I got this display:

Process 4882 launched: '/home/chris/puffer/zig/chaka/ziglox/zig-out/bin/ziglox' (x86_64)
Process 4882 stopped
* thread #1, name = 'ziglox', stop reason = signal SIGTRAP
    frame #0: 0x000000000113d36c ziglox`main.main at main.zig:9:27
   6   	   // const alloc = lox.global_alloc_init();
   7   	   // defer lox.global_alloc_deinit();
   8   	
-> 9   	   const vm = lox.VM.init();
   10  	   // const vm = lox.VM.init(alloc);
   11  	   defer vm.deinit();
   12  	}
(lldb) 

I’m completely clueless, I haven’t tested this on my WSL2 environment or a separate Linux setup.

I’m trying to debug tests.

@vivraan, please stop hoping from topics to post the same question, it will be considered spam if it continues. Since we are here now, could you please walk through the steps you are taking (actual commands you are issuing) to see if we can get a better idea of where things might be going wrong?

2 Likes

Sorry, I suspected I was being too erratic with my posting. I have no clue where to start with properly debugging my program, and I won’t lie, not getting this to work has hurt my morale, which may have contributed to my posting everywhere.

@chrboesch took precious time to run my entire codebase, which wasn’t necessary at all; only the build file is sufficient: link to GitLab – build.zig

With this, the following directory structure works:

root/
 |-- src/
   |-- main.zig
   |-- root.zig
 |-- .zig-cache/
 |-- zig-out/

Assuming that the root file and the main file have tests, and based on the provided build.zig, I would want to probably execute (I’m new to debugging on the terminal):

zig build test_root
lldb ./test_root/test.exe -- # unsure of arguments

Assuming that I have a setup similar to @chrboesch’s: @breakpoint() in one of the tests, I’d like to be able to have SIGTRAP fired, and some debugger (CLI/GUI).

At the moment, none of those happens. The process exits normally, and prints “All 0 tests passed” instead of some maybe non-zero number.

In my case, I have been issuing commands through VS Code or CLion GUIs, I don’t yet know how to use lldb through the terminal properly.

Oof, are you sure your tests are running? This is a common issue, zig is lazy compiled, and that means if something is never referenced, then it won’t be used. This crops up a lot where tests that we think should be running don’t actually run. Something I’d try is purposely making the test fail to see if it is even being run.

If it isn’t being run you’ll need to make sure the container where the tests live is being referenced by your program.

Sorry if I came across as too harsh, I get wanting to get the issue fixed. Hopefully we are on to something here.

Yeah, single-file tests with zig test seems to work, but they can’t be debugged as such. Also, yes, the tests don’t have any assertions/expects statements, I just print stuff using std.debug.print to the console, so they should autosucceed, whereas they don’t when zig building.

I think I have referenced the relevant containers in the root.zig file, since running tests now (not on remote yet) on root.zig runs all ref’d tests.

That’s alright, I should have been more discerning.

zig test unfortunately behaves a little differently than zig build test. zig test foo.zig treats foo.zig as the root source file, and any test decl in a root source file will be analyzed automatically. With zig build test your root file (root.zig in this case) is the root file, and the test runner will start looking for test decls there.

Try changing your root to say:

test {
    _ = @import("debug.zig");
}

EDIT: My tests didn’t quite cover your case. Very strange. The comptime block, should make sure that your tests in debug.zig are being analyzed.

Since I don’t know what your level of knowledge is (I suspect something with C, since you use underscores by the variables), I don’t know where to start. Therefore, the simplest thing to do is to create an empty directory, call zig init, add a breakpoint directly after pub fn main() in main.zig, and compile everything with zig build. Then you start it with lldb ./zig-out/bin… and see if that works. If not, get in touch and tell me exactly what happened. Everything else is just guesswork.

I’ve actually done this, but it doesn’t work.
I just pushed changes to my repo, so this can be tested. I’ll respond shortly after I try to edit the code more.

This seems like a perfect minimum reproduction, I’ll do this and check if something changes.

EDIT:
I’ll provide two reprods, one for this, and another for the case I mentioned with at least a root and a main container.

@vivraan please just start your own topic, possibly linking, referring or quoting other topics, in the future, here I moved this conversation to its own help topic for you. That makes it easier for everyone to follow what is going on and request for help generally don’t belong in hijacked pre-existing topics, that happen to be a bit related. When in doubt create your own topic, possibly mentioning other topics if you feel it is relevant.

2 Likes

Your test_root step add tests using addTest from the source file src/root.zig.
Add there the following test case:

test {
    try std.testing.expect(false);
}

After that, when running tests, the runner must detect the test case and display 1/1 test.test_0...FAIL (TestUnexpectedResult).

Please confirm that this happens.

1 Like

Thanks for the assist Sze, sorry for the trouble.

1 Like

Repro stuff

Okay, for your request, I ran zig init, went to main.zig, and changed it to look like this:

const std = @import("std");
const ZigTestRepro = @import("ZigTestRepro");

pub fn main() !void {
    @breakpoint();
}

When I ran CodeLLDB via VSCode on .\zig-out\bin\ZigTestRepro.exe, I got

Console is in 'commands' mode, prefix expressions with '?'.
Launching: O:\ZigTestRepro\zig-out\bin\ZigTestRepro.exe
Launched process 36708 from 'O:\ZigTestRepro\zig-out\bin\ZigTestRepro.exe'
Stop reason: Exception 0x80000003 encountered at address 0x7ff72154e324

This means I was able to make a breakpoint execute intentionally in the main function.

I also went ahead and tried a test-based breakpoint:

const std = @import("std");
const ZigTestRepro = @import("ZigTestRepro");

pub fn main() !void {}

test {
    @breakpoint();
}

But this version didn’t stop at the breakpoint. I think maybe breakpoints don’t trigger in tests?

the main project

It currently exists as

const std = @import("std");
pub const Chunk = @import("Chunk.zig");
pub const VM = @import("VM.zig");
pub const debug = @import("debug.zig");

// Allocator logic is exported selectively
const memory = @import("memory.zig");
pub const global_alloc_init = memory.global_alloc_init;
pub const global_alloc_deinit = memory.global_alloc_deinit;

test {
    std.testing.refAllDecls(@This());
}

Without refAllDecls, but with try std.testing.expect(false);, I get

$ zig build test_root
test_root
└─ run test 0/1 passed, 1 failed
error: 'root.test_0' failed: C:\Users\shiva\AppData\Roaming\Code\User\globalStorage\ziglang.vscode-zig\zig\x86_64-windows-0.15.2\lib\std\testing.zig:607:14: 0x7ff7eaba102f in expect (test_zcu.obj)
    if (!ok) return error.TestUnexpectedResult;
             ^
O:\CraftingInterpreters\ziglox\src\root.zig:12:5: 0x7ff7eaba10c7 in test_0 (test_zcu.obj)
    try std.testing.expect(false);
    ^
error: while executing test 'root.test_0', the following test command failed:
"O:\\CraftingInterpreters\\ziglox\\zig-out\\test_root\\test.exe" "--cache-dir=.\\.zig-cache" --seed=0x6df3c409 --listen=-

Build Summary: 2/4 steps succeeded; 1 failed; 0/1 tests passed; 1 failed
test_root transitive failure
└─ run test 0/1 passed, 1 failed

error: the following build command failed with exit code 1:
.zig-cache\o\c2129b686444549411bca77536cb2187\build.exe C:\Users\shiva\AppData\Roaming\Code\User\globalStorage\ziglang.vscode-zig\zig\x86_64-windows-0.15.2\zig.exe C:\Users\shiva\AppData\Roaming\Code\User\globalStorage\ziglang.vscode-zig\zig\x86_64-windows-0.15.2\lib O:\CraftingInterpreters\ziglox .zig-cache C:\Users\shiva\AppData\Local\zig --seed 0x6df3c409 -Zf10d49cdddc2ca78 test_root

This should be good news. I only need to have actual expects statements in the tests? The other tests don’t have any.

EDIT:

I also tried putting a breakpoint clause in there so it reads:

test {
    @breakpoint();
    try std.testing.expect(false);
}

And this successfully breaks at the expect when I debug it.

1 Like

I think the above answers one part of my headache, but the next part is regarding how I would see this behaviour in a specific IDE (Jetbrains/Zigbrains plugged in), so perhaps this isn’t the best place to have that discussion.

Yes, the most useful is std.testing.expectEqual to test that you have the expected result. For example try std.testing.expectEqual(42, theAnswer());

Since your test_root installs the test executable, you can run it directly.
Try it; normally it is named `zig-out\bin\test_root.exe.
The executable contains the test runner that runs your test code.
Simply use this executable as the running target for your IDE debugger.

I intend to report on this in a couple minutes after I refactor my tests.

EDIT: maybe around a day, because I got into the weeds

Update: haven’t yet gotten around to build testing this as I’m wrestling with unrelated Zig learning curve obstacles. Once I fix all that, I’ll post a solution or mark a solution here.

EDIT: Here we go.

Here’s the commit that fixes test debugging for me, and another one to complete the implementation hole.

I was originally missing actual calls to the std.testing.expect* functions for testing my code, so it’s likely that while zig test was working fine with the test bodies, the build produced by the equivalent zig build … operation/step-edge involving adding a test by module and installing it may have just been giving me no-ops.

This is also seemingly the case when running the test binary directly, as it no-opped in the terminal when I ran it, but debugging the same executable worked.

I might continue tracking this thread, but I think this is a great place to mark this thread solved.

PS: The commits also show a hodgepodge of other decisions I made in the testing process relating to streaming outputs that I would usually handle with stdout or stderr redirection.