Segmentation fault at address 0x3 in shared library

I am building a library to link with two types of program.

  1. a zig wrapper executable linked with this library dynamically, to work as a CLI application.
  2. a shared library produced by statically linking this library with C(.c/.h) wrapper. This then will be used on a java application through Java Native Interface.

I just said two types but this issue is focused on program type 1.
The latter is there to reason why things are exported as such / allocator is declared there, in my code below.

So while testing out a file-read on this library, I faced a segfault, and am having a hard time debugging it.
I am currently on WSL2 Ubuntu with zig 0.13.0 compiling natively(x86_64-linux-gnu), and these are the 4 files of code I am currently testing on.

src/lib.zig

const std = @import("std");

export fn read(s: [*:0]const u8) callconv(.C) [*:0]const u8 {
    // GPA
    var gpa = std.heap.GeneralPurposeAllocator(.{ .never_unmap = true, .retain_metadata = true, .verbose_log = true }){};
    const allocator = gpa.allocator();

    // FILE
    const path: []const u8 = std.mem.span(s);
    const file = std.fs.cwd().openFile(path, .{}) catch @panic("oh nyo");
    std.log.info("opening file: {s}", .{path});
    defer file.close();

    // READ
    const reader = file.reader();
    // will expose a release to caller later
    const buff: []u8 = allocator.alloc(u8, 256) catch @panic("oh nyoo"); 
    // ERR: readAll fails with "Segmentation fault at address 0x3"
    std.log.info("the line under throws segfault @ 0x3 in x86_64-linux-gnu built library", .{});
    // tried readUntilDelimiterOrEofAlloc first, pulled allocator away for clearance
    const read_len = reader.readAll(buff) catch @panic("oh nyooo"); 
    std.log.info("unreachable by segfault", .{});
    buff[read_len] = 0;

    // content
    const result: [*:0]u8 = @ptrCast(buff);
    return result;
}

test {
    const string = @as([:0]const u8, "file.txt").ptr;
    const result = read(string);
    const span: []const u8 = std.mem.span(result);
    try std.testing.expect(std.mem.eql(u8, span, "wow hello text"));
}

src/main.zig

const std = @import("std");

extern fn read(_s: [*:0]const u8) [*:0]const u8;

pub fn main() !void {
    const filename_c = @as([:0]const u8, "file.txt").ptr;
    const content_c = read(filename_c);
    const content = std.mem.span(content_c);
    std.log.info("file contains: {s}\n", .{content});
}

file.txt

wow hello text

build.zig

const std = @import("std");

pub fn build(b: *std.Build) !void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const lib_core = b.addSharedLibrary(.{ 
        .name = "core_zzg", 
        .root_source_file = b.path("src/lib.zig"), 
        .target = target, 
        .optimize = optimize, 
        .link_libc = true 
    });
    const exe = b.addExecutable(.{ 
        .name = "zzg", 
        .root_source_file = b.path("src/main.zig"), 
        .target = target, 
        .optimize = optimize, 
        .link_libc = true 
    });
    exe.linkLibrary(lib_core);
    b.installArtifact(lib_core);
    b.installArtifact(exe);
}

build.zig.zon is completely same as the one produced by zig init.

and… that’s it. If I run zig test src/lib.zig, it works without any problem.
But if I execute the output of zig build, the segfault is thrown with a complete output below.

info: opening file: file.txt
info(gpa): small alloc 1024 bytes at u8@7f07124c1000
info: the line under throws segfault @ 0x3 in x86_64-linux-gnu built library
Segmentation fault at address 0x3
/snap/zig/11625/lib/std/mem.zig:1062:48: 0x7f0712504138 in indexOfSentinel__anon_3949 (core_zigzag)
Segmentation fault (core dumped)

I would like to know what’s the reason behind this behavior and grasp any possible resolution.
Other tips regarding how I should have done something in some other way considering c-zig linking is also very welcome.
I am greeting awkwardly with this language since just a week ago, and would much appreciate if anyone could lead me to a better understanding :slight_smile:

It seems like linking libC causes your read function to be overwriten by libC’s read function. I reproduced setup and setting link_libc = false works fine while .link_libc = true does not.

2 Likes

you are absolutely right!
I just renamed the function read to something else instead of unlinking libc and it worked like a charm!
thank you very much for the help :laughing:

One other thing you can drop callconv(.C) if function is extern/export since extern functions are using C callconv by default.

extern fn read(_s: [*:0]const u8) [*:0]const u8;
export fn read(s: [*:0]const u8)  [*:0]const u8 {
    // ...
}
1 Like

that is great to know too. I was convinced by a reddit post where they explained with a nuance that it may not be consistent so it’s best to rely on call convention…
(can’t find it now though)
anyways, I deeply appreciate it!

Maybe it was the case at some point but docs say this at least starting from zig 0.7.0

2 Likes