Code reloading error General protection exception (no address available)

Update 3:
But if I try to do anything inside the function if will segfault, this for example result in a Segmentation fault at address 0x0

pub export fn gameInit() void {
    std.debug.print("Init done", .{});
}

Update 2:
Looks to have to do with the return of gameInit, if I make it return void it works, however I would need it to not be void.
var gameInit: *const fn () void = undefined;

Update:
Code is now avaliable at Stuck with error · favetelinguis/ztuig@f5ba3fa · GitHub

I am currently playing around with Zig and hot code where I get an error I do not understand at all. Given the following code.

main.zig

const std = @import("std");
const debug = std.debug;

const game_lib = @import("game.zig");

const gameInit_t = @TypeOf(game_lib.gameInit);
var gameInit: *gameInit_t = undefined;
pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    loadGameDll() catch @panic("Failed to load game.so");
    _ = gameInit();

    var count: u32 = 1;

    while (true) {
        if (count % 5 == 0) { // reload every 5 seconds
            unloadGameDll() catch unreachable;
            recompileGameDll(allocator) catch {
                std.debug.print("Failed to recompile game.dll\n", .{});
            };
            loadGameDll() catch @panic("Failed to load game.dll");
        }
        std.time.sleep(1_000_000_000);
        count += 1;
    }
}

var game_dyn_lib: ?std.DynLib = null;
fn loadGameDll() !void {
    if (game_dyn_lib != null) return error.AlreadyLoaded;
    var dyn_lib = std.DynLib.open("zig-out/lib/libgame.so") catch {
        return error.OpenFail;
    };
    game_dyn_lib = dyn_lib;
    gameInit = dyn_lib.lookup(*gameInit_t, "gameInit") orelse return error.LookupFail;
    std.debug.print("Loaded libgame.so\n", .{});
}

fn unloadGameDll() !void {
    if (game_dyn_lib) |*dyn_lib| {
        dyn_lib.close();
        game_dyn_lib = null;
    } else {
        return error.AlreadyUnloaded;
    }
}

fn recompileGameDll(allocator: std.mem.Allocator) !void {
    const process_args = [_][]const u8{
        "zig",
        "build",
        "-Dgame_only=true",
    };
    var build_process = std.process.Child.init(&process_args, allocator);
    try build_process.spawn();
    const term = try build_process.wait();
    switch (term) {
        .Exited => |exited| {
            if (exited == 2) return error.RecompileFail;
        },
        else => return,
    }
}

game.zig

const std = @import("std");

pub const GameState = struct {};

pub export fn gameInit() *GameState {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();
    return allocator.create(GameState) catch @panic("out of memory");
}


I get the following error when trying to run it. It looks like it loads the dynamic lib ok but then fail to run gameInit(). Any ideas on how to make progress on fixing the issue and understanding the root of the error.

$ zig build run2 -Dgame_only=false
Loaded libgame.so
General protection exception (no address available)
???:?:?: 0x7f7e21bddd90 in ??? (???)
Unwind information for `???:0x7f7e21bddd90` was not available, trace may be incomplete

/home/h/repos/zigs/ztuig/src/main2.zig:23:17: 0x103c0f1 in main (hotr)
    _ = gameInit();
                ^
/nix/store/j9sp79vdhz4l6lfnvzhvcprhar803zr3-zig-0.13.0/lib/zig/std/start.zig:524:37: 0x103ae14 in posixCallMainAndExit (hotr)
            const result = root.main() catch |err| {
                                    ^
/nix/store/j9sp79vdhz4l6lfnvzhvcprhar803zr3-zig-0.13.0/lib/zig/std/start.zig:266:5: 0x103a951 in _start (hotr)
    asm volatile (switch (native_arch) {
    ^
run2
└─ run hotr failure
error: the following command terminated unexpectedly:
/home/h/repos/zigs/ztuig/zig-out/bin/hotr
Build Summary: 7/9 steps succeeded; 1 failed (disable with --summary none)
run2 transitive failure
└─ run hotr failure
error: the following build command failed with exit code 1:
/home/h/repos/zigs/ztuig/.zig-cache/o/b99ecefa070148799c71cbf52f917711/build /nix/store/j9sp79vdhz4l6lfnvzhvcprhar803zr3-zig-0.13.0/bin/zig /home/h/repos/zigs/ztuig /home/h/repos/zigs/ztuig/.zig-cache /home/h/.cache/zig --seed 0x7f4b92cb -Z4de9b50e3d3bb79a run2 -Dgame_only=false
error: Recipe `rerun` failed on line 2 with exit code 1

The code is very much taken from hotreload/src/main.zig at master · samhattangady/hotreload · GitHub
and
Hot-reloading with Raylib - Zig NEWS

Try to change the type in lookup to:

gameInit = dyn_lib.lookup(*const fn () callconv(.C) *GameState, "gameInit") orelse return error.LookupFail;

That did not change anything, still the same error. I have updated the OP with a link to a git repo containing the code, it is however very messy atm but not much code so should be ok.

I was able to get it to work by adding .link_libc = true to the options of exe2 in your build.zig.

Based on some debugging I did, it looked like it was failing on a call to memcpy from within your libgame.so shared library, which is what made me initially think it might be related to linking libc. However, it looks like the real difference may be here: zig/lib/std/dynamic_library.zig at 0bf44c30934bced6fc8f6451cf418ae40db665e6 · ziglang/zig · GitHub There is a different implementation used for std.DynLib when libc is not being linked, or musl is used with a static binary. In fact, the crash returns even when linking libc when using -Dtarget=x86_64-linux-musl to hit the other part of that condition, which makes me suspect that’s the real root cause. I don’t know enough about dynamic linking to know whether this is a bug in ElfDynLib or whether something else is missing in the compilation of the shared library or main executable which would make it work.

Regardless, hopefully just adding that option for now will allow you to keep working on your project :smile:

Edit: after writing my suspicion above, I searched the Zig issue tracker, and it seems this is indeed a known issue with ElfDynLib:

4 Likes

Awsome that did the trick. I wonder if this is a new issue since following some online examples no one mention that you need to link with libc.

What is the state of the built in hot reload feature in Zig as mentioned:

1 Like

Here’s the issue tracking that: hot code swapping · Issue #68 · ziglang/zig · GitHub

It looks like what you linked was an early proof-of-concept of what the feature could be, but from what I understand, more progress towards a generally usable implementation is dependent on improvements in incremental compilation and linking, which are things the core team is actively working on. One of the comments on the linked issue also mentions that progress on the self-hosted x86_64 backend is important to making hot code swapping generally usable, which is also something that’s been making a lot of progress recently.

1 Like