Segmentation fault and recursive panic when calling a DLL function

I’m encountering an issue when calling a function from the Wintun library using Zig.

My code:

const std = @import("std");

pub fn main() !void {
    var wintun = try std.DynLib.open("wintun.dll");
    defer wintun.close();
    const deleteDriver = wintun.lookup(*const fn () callconv(.winapi) std.os.windows.BOOL, "WintunDeleteDriver").?;
    const success = deleteDriver();
    std.debug.print("success: {}\n", .{success});
}

Here’s the function signature from wintun.h:

typedef _Return_type_success_(return != FALSE)
BOOL(WINAPI WINTUN_DELETE_DRIVER_FUNC)(VOID);

When I run zig run main.zig, I get:

Segmentation fault at address 0x10
???:?:?: 0x7ffc8613df65 in ??? (drvsetup.dll)
???:?:?: 0x7ffc86148621 in ??? (drvsetup.dll)
???:?:?: 0x7ffc8613aebc in ??? (drvsetup.dll)
???:?:?: 0x7ffc8613b1eb in ??? (drvsetup.dll)
???:?:?: 0x7ffcd04b11f6 in ??? (SETUPAPI.dll)
???:?:?: 0x7ffcd04b0ebf in ??? (SETUPAPI.dll)
???:?:?: 0x7ffcb62552e9 in ??? (wintun.dll)
C:\repro\main.zig:7:33: 0x7ff6ca71fb00 in main (main.exe.obj)
    const success = deleteDriver();
                                ^
C:\bin\zig-windows-x86_64-0.15.0-dev.551+518105471\lib\std\start.zig:490:53: 0x7ff6ca71fe2f in WinStartup (main.exe.obj)
    std.os.windows.ntdll.RtlExitUserProcess(callMain());
                                                    ^
???:?:?: 0x7ffcd1437373 in ??? (KERNEL32.DLL)
???:?:?: 0x7ffcd171cc90 in ??? (ntdll.dll)
aborting due to recursive panic
success: 1

The program does not crash, and the function succeeds, but the output is pretty scary.

I’ve tried the following:

  • Calling std.os.windows.LoadLibraryW and std.os.windows.kernel32.GetProcAddress directly (didn’t help)
  • Writing a .c file (code below) and then using @cImport / extern fn deleteDriver() callconv(.c) std.os.windows.BOOL (didn’t help)
#include <windows.h>

typedef BOOL(WINAPI WINTUN_DELETE_DRIVER_FUNC)(VOID);

BOOL deleteDriver(void)
{
   BOOL success = FALSE;

   HMODULE wintun = LoadLibraryA("wintun.dll");
   if (!wintun) goto cleanup;

   WINTUN_DELETE_DRIVER_FUNC* WintunDeleteDriver = (WINTUN_DELETE_DRIVER_FUNC*)GetProcAddress(wintun, "WintunDeleteDriver");
   if (!WintunDeleteDriver) goto cleanup;

   success = WintunDeleteDriver();

cleanup:
   if (wintun) FreeLibrary(wintun);
   return success;
}
  • Replacing pub fn main() !void with pub export fn main() void and running it with zig run main.zig -lc did help (no segfault and panic in output). However, I don’t understand why, and I’d like to avoid that if possible.

ReleaseFast and ReleaseSmall builds also don’t output segfault and panic. ReleaseSafe only outputs the segfault and then crashes.

If the driver is already uninstalled, the function call also doesn’t result in segfault and panic.

Am I doing something wrong, or is it a bug in Zig?