Macos system call validation failure

Testing commandline completion with zigclc on my macos M1 macbook, I ran into a run-time error:

[1]    70728 invalid system call  CLIGEN_W="zig build --watch -finc" CLIGEN_N="3" _ZIGCLC_CLIGEN_C=czsh zigclc

( the env var settings are normally used by the registered function for zig commandline completion, here I set the by hand).

Not a very helpful message, and in addition the code worked my server (Intel/Linux). Adding some debug print statements would no longer trigger this invalid system call, but removing those would trigger the run-time error again. This made it useless to bisect down onto the source code area triggering the error.

I hadn’t used a debugger for, literally, multiple decades, but searching told me to use lldb:

zig/zigclc » lldb  /Users/anthon/src/zig/zigclc/zig-out/bin/zigclc
(lldb) target create "/Users/anthon/src/zig/zigclc/zig-out/bin/zigclc"
Current executable set to '/Users/anthon/src/zig/zigclc/zig-out/bin/zigclc' (arm64).
(lldb) run
Process 69385 launched: '/Users/anthon/src/zig/zigclc/zig-out/bin/zigclc' (arm64)
>>>>>>>>>> args 0 /Users/anthon/src/zig/zigclc/zig-out/bin/zigclc
>>>>>>>>>> completing 1
Process 69385 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_SYSCALL (code=1697345648)
    frame #0: 0x00000001003dfc94 zigclc`os.linux.aarch64.syscall2(number=getcwd, arg1=6171526312, arg2=1024) at aarch64.zig:32:5
   29       arg1: syscall_arg_t,
   30       arg2: syscall_arg_t,
   31   ) u64 {
-> 32       return asm volatile ("svc #0"
   33           : [ret] "={x0}" (-> u64),
   34           : [number] "{x8}" (@intFromEnum(number)),
   35             [arg1] "{x0}" (arg1),
Target 0: (zigclc) stopped.

That is already more useful, but I use getcwd in more than one position in the source, so I used bt ( and cut down the repetitive buf content):

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_SYSCALL (code=1697345648)
  * frame #0: 0x00000001003dfc94 zigclc`os.linux.aarch64.syscall2(number=getcwd, arg1=6171526312, arg2=1024) at aarch64.zig:32:5
    frame #1: 0x00000001003dfb10 zigclc`os.linux.getcwd(buf="\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa SNIP xaa"..., size=1024) at linux.zig:1028:20
    frame #2: 0x00000001003df898 zigclc`BuildZig.cwdDepth at BuildZig.zig:471:28
    frame #3: 0x00000001003dbecc zigclc`BuildZig.find_bldzig_dir(io=Io @ 0x000000016fda3d50) at BuildZig.zig:480:27
    frame #4: 0x00000001003d49f4 zigclc`BuildZig.init(alloc=mem.Allocator @ 0x000000016fda3d40, io=Io @ 0x000000016fda3d50, releases=<unavailable>) at BuildZig.zig:47:24
    frame #5: 0x00000001004f9fb8 zigclc`cli.BuildCmdParser.parse(self=0x000000016fde63b0, gpa=mem.Allocator @ 0x000000016fdf3818, out=0x000000016fdf3960, fs=0x000000016fdf3890, args=[]const []const u8 @ 0x000000016fda3990, arg_idx=(ptr = "\U00000002", len = 2), optnl_partial=(ptr = "-finc", len = 5)) at cli.zig:5652:61
    frame #6: 0x00000001003faa18 zigclc`cli.CombinedParsers.parse(self=0x000000016fde63b0, gpa=mem.Allocator @ 0x000000016fdf3818, out=0x000000016fdf3960, fs=0x000000016fdf3890, args=[]const []const u8 @ 0x000000016fdac710, arg_idx=(ptr = "\U00000002", len = 2), optnl_partial=(ptr = "-finc", len = 5)) at cli.zig:67204:56
    frame #7: 0x00000001003f97f0 zigclc`cli.check_completion(alloc=mem.Allocator @ 0x000000016fdf3818, out=0x000000016fdf3960, env=<unavailable>, fs=0x000000016fdf3890, progname=(ptr = "zigclc", len = 6), config=0x000000016fdf5c60) at cli.zig:67374:30
    frame #8: 0x00000001003fa5c4 zigclc`cli.cligen_parser(gpa=mem.Allocator @ 0x000000016fdf3818, out=0x000000016fdf3960, env=cli.Env @ 0x000000016fdf3860, fs=0x000000016fdf3890, sys_args=[]const [:0]const u8 @ 0x000000016fdebdc8, progname=(ptr = "zigclc", len = 6), config=0x000000016fdf5c60) at cli.zig:67393:21
    frame #9: 0x0000000100103bc4 zigclc`cli.command_line_parser(init=<unavailable>, params=cli.ParserOptions @ 0x000000016fdfbce0) at cli.zig:67447:25
    frame #10: 0x000000010011e844 zigclc`main.main(init=process.Init @ 0x000000016fdfe240) at main.zig:32:43
    frame #11: 0x000000010011f1f0 zigclc`start.callMain at start.zig:790:30
    frame #12: 0x000000010011f0e4 zigclc`start.callMainWithArgs at start.zig:692:20
    frame #13: 0x000000010011efa4 zigclc`start.main(c_argc=1, c_argv=0x000000016fdff1f0, c_envp=0x000000016fdff200) at start.zig:717:28
    frame #14: 0x000000019a96eb98 dyld`start + 6076

My program is calling std.os.linux.getcwd() which has worked for some time without problems, and, as indicated above, works if I put some debug print statements in. Using up and fr v and checked that the buffer address and length are correct.

The error 1697345648 is a system call validation failure, why doesn’t this get thrown now and always? How can I prevent that from happening (apart from adding debug print statements)?

(FWIW the current directory is only 29 characters, so that should fit without problem (and hopefully a better error message), if it had not).

Are there more source code details?

If the source code is the current zigclc master, then I think the getcwd you’re calling is std.c.getcwd here.
It confuses me that it doesn’t use libc but instead runs the code in std.os.linux.

No it is not the zigclc master, this code is not commited yet.

    const maxnr: usize = 1024;
    var buf: [maxnr]u8 = undefined;
    _ = std.os.linux.getcwd(&buf, maxnr);

The code under std.os isn’t portable, so obviously std.os.linux code won’t work on macOS.
edit: This might be what you want

Obvious? That does not explain that this worked for weeks and adding a debug print would not make std.os.linux compatible with macOS.

Thanks for the link, I might try that, if nobody can provide an explanation.

Where would adding it make a difference? Is there any related source code?

Is the full source code of cwdDepth the one given above? Does its usage depend on some kind of race control flow?

Imagine some possibilities, like cwdDepth being a fallback for other solutions, which in most cases won’t even be called. Whether it’s called depends on some cross-thread caching situation; if there’s a cache, it won’t be called. At the same time, the race condition memory access here wasn’t implemented correctly, causing access to the cache to be optimized away, which then fell back to the control flow that needed to call cwdDepth, eventually triggering the issue. But after adding debug print statements, those optimizations no longer happen, the cache gets accessed, and calling cwdDepth is no longer necessary.

The above are some unrealistic speculations. If possible, I hope the current version of the source code can be uploaded as a branch so that the specific issues can be identified.

There are two options for this, one would be to open a . directory and get a path name from it, other one is std.process.currentPath() listed above. But I’d also suggest to use higher level functions than using os ones.
Functions I was referring:
std.Io.Dir.realPath()
std.Io.Dir.OpenDir()