Can I call my debugging Zig functions inside lldb?

I’ve come to know that we can’t have nice things such as printing slices or slice expressions and much more in a debugger in Zig, or in low-level programming languages like C in general.

At least, that’s how it feels to me. I could very well be horribly wrong and these could just be skill issues of looking up an info.

Anyways, these are the words of someone who is used to taking Python REPL’s niceties for granted. So, to ease the pain, I think there should at least be a workaround to call my Zig functions inside lldb.

For example, this will satisfy my greedy needs:
debug_tools.zig:

pub fn asSlice(input: []u8, start: usize, end: usize) []const u8 {
    if (end == 0)
        return input[start..input.len]
    else
        return input[start .. end - 1];
}

pub fn asSliceToEnd(input: []u8, start: usize) []const u8 {
    return asSlice(input, start, 0);
}

combining with lldb_pretty_printers.py.

But I can’t seem to call these functions:

* thread #1, name = 'zigxatolik', stop reason = step over
    frame #0: 0x0000000001a68dc5 zigxatolik`preprocessor.unfreeze(queue_item=0x00007fffff5e97fc) at preprocessor.zig:189:43
   186      for (0..queue_item.entity_count) |i| {
   187          const needle = try std.fmt.bufPrint(&needle_buf, "\x01{d}\x01", .{i});
   188          const needle_pos = std.mem.indexOf(u8, &queue_item.string, needle).?;
-> 189          const entity_len = std.mem.indexOf(u8, &queue_item.entities[i], "\x00").?;
   190          std.mem.copyForwards(u8, output_buf[index_buf..], queue_item.string[index..needle_pos]);
   191          index += needle_pos + needle.len;
   192          if (i == 0)
(lldb) expression asSliceToEnd(needle, 0)
error: <user expression 0>:1:1: use of undeclared identifier 'asSliceToEnd'
    1 | asSliceToEnd(needle, 0)
      | ^
(lldb) p asSliceToEnd(needle, 0)
error: <user expression 1>:1:1: use of undeclared identifier 'asSliceToEnd'
    1 | asSliceToEnd(needle, 0)
      | ^

I’ll get down on my knees if someone can point me in the right direction :face_holding_back_tears:

Use -- between expression and the function call.
lldb can call zig exported functions:

❯ cat test.zig
const std = @import("std");

export fn foo() isize {
    return 42;
}

pub fn main() !void {
    std.debug.print("Hello, World\n", .{});
}

❯ lldb test
(lldb) target create "test"
Current executable set to '/Users/din/test' (arm64).
(lldb) b test.main
Breakpoint 1: where = test`test.main + 8 at test.zig:8:20, address = 0x000000010009e74c
(lldb) r
Process 39920 launched: '/Users/din/test' (arm64)
Process 39920 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x000000010009e74c test`test.main at test.zig:8:20
   5    }
   6
   7    pub fn main() !void {
-> 8        std.debug.print("Hello, World\n", .{});
   9    }
(lldb) expr -- foo()
(long) $0 = 42
(lldb)
2 Likes

Dang it… I don’t know how to fix this yet…

src/main.zig:5:19: error: parameter of type '[]u8' not allowed in function with calling convention 'C'
export fn asSlice(input: []u8, start: usize, end: usize) []const u8 {
                  ^~~~~~~~~~~
src/main.zig:5:19: note: slices have no guaranteed in-memory representation
src/main.zig:12:24: error: parameter of type '[]u8' not allowed in function with calling convention 'C'
export fn asSliceToEnd(input: []u8, start: usize) []const u8 {
                       ^~~~~~~~~~~
src/main.zig:12:24: note: slices have no guaranteed in-memory representation

The debugger doesn’t know what a slice is, so decompose the slice to it’s pointer and length. (foo.ptr, foo.len)

2 Likes

In this case, the problem is that C doesn’t know what a slice is, since it’s an export function. They can’t return one either, which is tricky.

I bodged something together to do this with Python, I might be able to find it this evening. That’s probably the best bet here.

2 Likes

Thank you all. I’ll try to gather information. I’ve seen some official webpages mentioning an lldb fork. Might as well look into that. Also, I didn’t try to understand the lldb_pretty_printers.py python script at all, just skimmed through some. Maybe that’s where I should begin. Gotta master the tools I make software with.

I think you can use parray on the slice. You just need to access the actual pointer and len parts of the slice explicitly.

(lldb) help parray
parray <COUNT> <EXPRESSION> -- lldb will evaluate EXPRESSION to get a typed-pointer-to-an-array in memory, and will display COUNT elements of
that type from the array.  Expects 'raw' input (see 'help raw-input'.)

Syntax: parray <expr>
  parray <expr>
'parray' is an abbreviation for 'expression -Z %1   --'
(lldb) parray needle.len needle.ptr
1 Like

It would be really neat if you find it, I’ve been struggling with export functions and slice return type too :slight_smile:

Found a temporary solution that fits my needs:

dap> p *(uint8_t (*)[`needle_pos - index`])(queue_item.string + index)
(uint8_t[8]) "лмао"
dap> p *(uint8_t (*)[`needle.len`])(needle.ptr)
(uint8_t[3]) "\U000000011\U00000001"
dap> 

Printing a select substring from [N]u8 (fixed-size array of u8)

This p *(uint8_t (*)[`needle_pos - index`])(queue_item.string + index) does casting. It casts the array to an N-sized array, i.e., cuts off the array. The (queue_item.string + index) part tells where to start the array.

This is not a great explanation.

:warning: Without backticks, it can’t evaluate needle_pos - index

So, basically, it’s array_u8[start_usize .. end_usize], but super ugly.

Printing a select substring from [ ]u8 (slice structs)

Just access the slice’s ptr – address of the actual u8 array – with dot notation.

Don’t forget to import lldb_pretty_printers.py

Here’s my related nvim settings.

I think we can create a custom lldb syntax using Python scripting, so we can just do array_u8[start_usize .. end_usize] inside lldb. But I don’t have time to do that right now.

3 Likes

You gotta wrap backticks around needle.len to make it work, otherwise it says:
error: invalid element count 'needle.len'.

dap> parray `needle.len` needle.ptr
(unsigned char *) $14 = 0x00007fffff5e539c "\U000000010\U00000001\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\U00000001" {
  [0] = '\x01'
  [1] = '0'
  [2] = '\x01'
}
dap> 

Thank you. Though not exactly what I wanted, it may come handy someday.