Debugging Zig (with a debugger)

Debugging Zig code is a bit of a dark art at present:. There’s no official documentation on the subject, and the lore involved is scattered among blog posts, video, Ziggit questions, the Discord, and so on.

So I thought I’d start a topic as a place to share knowledge of the dark art. I also want to improve my own workflow here, so I’ll start with what I have working.

VSCode and CodeLLDB

I do most of my development in Neovim now (I use Neovim btw), but not debugging… yet.

For that I’m using VSCode with a great plugin, Zig language extras, which adds some code actions, specifically to tests: it has run test and debug test. That second one is what I want to reproduce: writing a test which produces the defect I need to address, and running it straight from the file, is basically ideal: you get a little custom program focused on the problem, and when it’s fixed, you have a regression test.

Once I managed to get the LLDB pretty printers from the Zig repo wired up to CodeLLDB, this is a generally pleasant workflow. I don’t remember how I did that well enough to specify it in detail, but fortunately I don’t have to because I started a thread asking for help at the time. :slight_smile:

I’d probably just stick with what works, but there’s a significant limitation in the plugin: it uses the older zig test system, which doesn’t integrate with the build system. So it can’t launch debugging sessions for any code which relies on an import. As time goes by that describes less and less of my projects.

I haven’t been able to figure out how to debug straight from a test built with zig build test, in VSCode or elsewhere. I’d rather be using DAP anyhow, but even setting that up to work with Zig is not something I’ve found docs about (they all cover Go for some reason).

So I’m putting it out there to the crowd: how do you debug Zig code? What’s your setup like, how did you get it running? What are the critical yet esoteric details someone setting this up would stumble on?

I put this in Explain rather than Help, because there are a bunch of operating systems and tools to do this, and I want to hear about all of them!

8 Likes

The entire thread is very useful: Zig Debugging with LLDB - #7 by dimdin

1 Like

Very useful!

Adding an lldb step based on the test step is a key insight here, that might be enough for me to get the needle threaded with nvim-dap.

vscode:

You can debug separated zig files - one problem tested should be without dependencies - exactly your problem

My solution was - instead of zig test , use zig build install:
changed tasks.json

        {
            "label": "build_test",
            "type": "shell",
            "command": "zig",
            "args": [
                 "build",
                "install",
                "--summary",
                "all"
            ],
            "group": "test"
        },

changed launch.json:

    {
      "type": "lldb",
      "request": "launch",
      "name": "Debug Test",
      "program": "${workspaceFolder}/zig-out/bin/test",
      "preLaunchTask": "build_test",
      "args": [],
      "cwd": "${workspaceFolder}",
      "console": "integratedTerminal"
    },

build.zig:

    // Creates a step for unit testing. This only builds the test executable
    // but does not run it.
    const lib_unit_tests = b.addTest(.{
        .root_source_file = b.path("src/root_tests.zig"),
        .target = target,
        .optimize = optimize,
    });

    lib_unit_tests.root_module.addImport("network", zig_network.module("network"));
    lib_unit_tests.root_module.addImport("zig-datetime", zig_datetime.module("zig-datetime"));
    lib_unit_tests.root_module.addImport("mailbox", mailbox.module("mailbox"));
    b.installArtifact(lib_unit_tests);

Chain:

  • Set “Debug Test” configuration and breakpoints
  • Start debugging (F5)
  • zig build install - creates zig-out/bin/test
  • test runs under lldb

Still you can not debug separated file

see repo

Simultaneously i am straggling with JetBrains Raider and RustRover

Both free for non-commercial use and with Zig Brains plugin support Zig.

But debugging supports only RustRover - see debug configuration

Problem of dependencies i solved via second repo with submodules

3 Likes

I have been using nvim with the nvim-dap and nvim-dap-ui plugins for a while, using codelldb. You can find my config here

			dap.configurations.zig= {
				{
					name = "Run Program",
					type = "codelldb",
					request = "launch",
					program = function()
						co = coroutine.running()
						if co then
							cb = function(item)
								coroutine.resume(co, item)
							end
						end
						cb = vim.schedule_wrap(cb)
						vim.ui.select(vim.fn.glob(vim.fn.getcwd() .. '**/zig-out/**/*', false, true), {
							prompt = "Select executable",
							kind="file",
						}, 
						cb)
						return coroutine.yield()
					end,
					cwd = "${workspaceFolder}",
					stopOnEntry = false,
					args = function()
						return splitStr(vim.fn.input('Args: '))
					end,
				}
			}

The key thing for me was to add a function to the program attribute that just lets me select the binary i want to test. I use watchexec (will switch to using the zib build watch once 0.14 is released), to constantly build my project, so that means i pretty much always have current version ready to build. For tests I will find where the file is being made and cp it or just link it into the bin directory (I’ve found that test builds often don’t change their name).

4 Likes

When I want to debug a unit test app, I often generate the test artifact at zig-out/test/<ARTIFACT NAME>.

build.zig

    const unit_test = b.addTest(.{
        ....
    });

    const run_unit_tests = b.addRunArtifact(unit_test);
    const test_step = b.step("test", "Run unit tests");
    test_step.dependOn(&run_unit_tests.step);
    // Add following
    test_step.dependOn(&b.addInstallArtifact(unit_test, .{.dest_sub_path = "../test/tsome-test"}).step);
2 Likes

A Dark Art! That is why debugging does not do anything here!
I am used to the almost perfect debugging in C# with MS Visual Studio…
My debugging consists entirely of std.debug.print currently.
I must say that debugging in Rust was also close to unusable.

(BTW: Which crazy person made the names of visual studio code and microsoft visual studio almost the same, so that you can never find on the internet what you want?)

1 Like

It can be done! But learning how to set it up, get acceptable pretty printing etc, is lore, scattered around various sources. That’s why I started this thread, as a way to gather the lore. At some point Ziggit should have a doc in the wiki about this, but with so many approaches and operating systems, it might take awhile to do a thorough job of it.

Always respectable IMHO, sometimes print / log debugging is optimal. Good to have the tool ready for when breakpoint debugging is the better choice though.

You can get a pretty good start on it with VSCode and the Zig plugin, plus some lines configuring CodeLLDB to use the pretty printers from a local copy of the Zig repo. Various breadcrumbs on this path are scattered about this thread.

2 Likes

The beginning was laid by JavaScript and Java

What would be really really good in my opinion is an IDE which is dedicated and specialized for the language. Like Delphi and C# have (the only other languages I really mastered).

Perhaps there is some cool gains that would warrant creating some kind of “extension” of lldb/gdb that would support hotloading a new executable while maintaining external state after incremental compilation is real. Or perhaps the compiler becomes a server and claims debugging responsibility as well. I’ve read ppl say ZLS will one day be consumed and negated by the compiler… idk what I’m talking about.

Regardless I hope whatever is done supports any and every ide.

1 Like

Are folks using CodeLLDB with vscode developing only on Linux?

I’m on Windows, and while the cppvsdbg dap works, it has all the problems of a vanilla lldb without the Zig pretty printers, so debugging often means mangling the code to stash local pointers in some format that the watch window will be able to understand. It’d be nice to get those pretty printers.

I can technically start a debug session with CodeLLDB but it doesn’t symbolicate at all, and attempting to manually add the pdb results in a crash inside lldb’s source:

target symbols add C:\Users\micha\dev\myzigprogram\zig-out\bin\myzigprogram.pdb

symbol file 'C:\Users\micha\dev\myzigprogram\zig-out\bin\myzigprogram.pdb' has been added to 'C:\Users\micha\dev\myzigprogram\zig-out\bin\myzigprogram.exe'

error: Assertion failed: (obj_load_address && obj_load_address != LLDB_INVALID_ADDRESS), function SymbolFilePDB::InitializeObject, file D:\a\1\s\llvm-project\lldb\source\Plugins\SymbolFile\PDB\SymbolFilePDB.cpp, line 204

Please file a bug report against lldb reporting this failure log, and as many details as possible

I remember trying this out a few years ago and couldn’t get anything except the cppvsdbg adapter so I’ve been jumping between that, rad, remedy, windbg classic, windbg preview, and VS proper periodically to see if the situation has improved anywhere. It really hasn’t, unfortunately (unless I’m just missing some key step that will make everything work amazing, in which case lmk!).

[EDIT] Setting LLDB_USE_NATIVE_PDB_READER=1 at least lets you step through source if you manually add the PDB with the above command, but still can’t show any variables in the watch window and can’t show the names of most zig functions in the callstack, so it’s a worse experience still compared to cppvsdbg.

1 Like

Both Visual Studio and Code can have custom visualizers in an natvis XML file.

Natvis documentation:

EDIT: Zig natvis support:

2 Likes

What is this dedicated IDE for C# that you speak of? Up to now I haven’t heard of one.

I’m familiar with natvis files, but last time I went looking around to make it work I found some messages from SpexGuy saying there were some fundamental issues with making natvis pattern match Zig slice type names properly:

I tried to get this to work a while back, but I think there’s something special about how natvis parses wildcards. I’m pretty sure it needs type names that match how C++ type names work, so I haven’t found a way to do this.
[…]
Yeah something like that. The * needs some sort of delimiters and natvis probably uses the C++ ones.

-Discord

And the linked draft PR there mentions the same problem:

There is no way to add visualizers for slices as they have square brackets in their type name, which isn’t allowed for C++ types. This is an issue the Rust devs ran into and AFAIK still hasn’t been fixed. Note that it is still possible to do (char*)buf.ptr,[buf.len]s8 for a []u8 slice when in the debugger.

Some types like packed structs/comptime known enums are lowered to their integer representation when debugging, and these base types cannot be selected for visualization.

-Link: Add natvis file support by The-King-of-Toasters · Pull Request #19873 · ziglang/zig · GitHub

Seems like the ideal in the long-run would be that we are able to use lldb to debug Zig artifacts on Windows.

1 Like

Ideal in the long run is a Zig debugger written in Zig, that understands Zig types, etc.

4 Likes

Well, I thought that was MS Visual Studio…

Clarification for Windows

I disagree. To me, one of the selling points of Zig is its interoperability with other Languages, especially C.

In practice, reuse across programming languages is an important usecase. Debuggers therefore already need to support several languages simultaneously within the same target binary.

A new Debugger written in Zig then also needs support for other languages. Extending existing debuggers to support Zig seems more practical to me.

1 Like

Fair take. That fork of lldb perhaps.