Calling any `sndfile` (C library) function segfaults :<

Hello everyone,

I am new to Zig and have some C experience.
To get to know the language I have started rewriting one of my projects from C17 to Zig, instead of C23.

This project is interacting with the sndfile library.
I can declare a structure, calculate it’s size and so on, but the moment that I call any sndfile’s function my program segfaults.

Here is the code:

const std = @import("std");
const sf = @cImport({
    @cInclude("sndfile.h");
});

pub fn main() !void {
    // hello world
    const stdout = std.io.getStdOut().writer();
    try stdout.print("hello world!\n", .{});

    // example libsndfile use
    const version: [*c]const u8 = sf.sf_version_string();
    std.debug.print("{s}\n", .{version});
}

It compiles fine but when I execute it I get

$ zig build
$ ./zig-out/bin/zig
hello world!
Segmentation fault at address 0x0
???:?:?: 0x0 in ??? (???)
Aborted

Also I have added exe.linkSystemLibrary("sndfile"); to my default build.zig.

Do any of you know why this might be happening?
I am using zig 0.14 and libsndfile 1.2.2.

Looking at sf_version_string, it references the identifiers PACKAGE_NAME and PACKAGE_STRING.

It looks like these are set by cmake somehow, so it’s possible that not using cmake causes these identifiers to never be defined. It is strange however if that doesn’t cause a compile error and just evaluates to 0/null.

To test this, maybe try adding definitions in your cImport like so:

const sf = @cImport({
    @cDefine("PACKAGE_NAME", "libsndfile");
    @cDefine("PACKAGE_VERSION", "1.2.2");
    @cInclude("sndfile.h");
});

This unfortunately did not change anything.

My end goal is to copy the audio file contents and I have encountered this with sf_open but scaled down to a simple version function to debug.

Are you linking libc?

2 Likes
const char *
sf_version_string (void)
{
#if	ENABLE_EXPERIMENTAL_CODE
	return PACKAGE_NAME "-" PACKAGE_VERSION "-exp" ;
#else
	return PACKAGE_NAME "-" PACKAGE_VERSION ;
#endif
}

Even if no macros were defined, he should get a “-” string. Also, if he is using linkSystemLibrary, he probably compiled it by normal means, with CMake, and is just linking to it.
Are you linking to it statically or dinamically? Dynamic linking on Linux had some problems related to relocation, I don’t know if that’s fixed.

Adding exe.linkLibC(); to build.zig fixed it!

I do not know… where should I check it?

here’s the build.zig

const std = @import("std");

// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
pub fn build(b: *std.Build) void {
    // Standard target options allows the person running `zig build` to choose
    // what target to build for. Here we do not override the defaults, which
    // means any target is allowed, and the default is native. Other options
    // for restricting supported target set are available.
    const target = b.standardTargetOptions(.{});

    // Standard optimization options allow the person running `zig build` to select
    // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
    // set a preferred release mode, allowing the user to decide how to optimize.
    const optimize = b.standardOptimizeOption(.{});

    // We will also create a module for our other entry point, 'main.zig'.
    const exe_mod = b.createModule(.{
        // `root_source_file` is the Zig "entry point" of the module. If a module
        // only contains e.g. external object files, you can make this `null`.
        // In this case the main source file is merely a path, however, in more
        // complicated build scripts, this could be a generated file.
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    // This creates another `std.Build.Step.Compile`, but this one builds an executable
    // rather than a static library.
    const exe = b.addExecutable(.{
        .name = "zig",
        .root_module = exe_mod,
    });
    // link
    exe.linkLibC();
    exe.linkSystemLibrary("sndfile");

    // This declares intent for the executable to be installed into the
    // standard location when the user invokes the "install" step (the default
    // step when running `zig build`).
    b.installArtifact(exe);

    // This *creates* a Run step in the build graph, to be executed when another
    // step is evaluated that depends on it. The next line below will establish
    // such a dependency.
    const run_cmd = b.addRunArtifact(exe);

    // By making the run step depend on the install step, it will be run from the
    // installation directory rather than directly from within the cache directory.
    // This is not necessary, however, if the application depends on other installed
    // files, this ensures they will be present and in the expected location.
    run_cmd.step.dependOn(b.getInstallStep());

    // This allows the user to pass arguments to the application in the build
    // command itself, like this: `zig build run -- arg1 arg2 etc`
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }

    // This creates a build step. It will be visible in the `zig build --help` menu,
    // and can be selected like this: `zig build run`
    // This will evaluate the `run` step rather than the default, which is "install".
    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);

    const exe_unit_tests = b.addTest(.{
        .root_module = exe_mod,
    });

    const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);

    // Similar to creating the run step earlier, this exposes a `test` step to
    // the `zig build --help` menu, providing a way for the user to request
    // running the unit tests.
    const test_step = b.step("test", "Run unit tests");
    test_step.dependOn(&run_exe_unit_tests.step);
}

You would have to know if the library installed in your system is static or dynamic. But it looks like it’s dynamic.
On Linux, when you use a dynamic library and don’t link with libc, Zig will use its own implementation of dynamic library loader, which is bugged and doesn’t handle relocations. Addresses intended for relocation are filled with 0, and are supposed to be corrected during relocation. Since Zig doesn’t do it, you get 0 instead of the correct address, which is what you were getting. When you link with libc, Zig will instead use the system’s dynamic library loader, which is why linking with libc corrected your problem.

2 Likes

Yes it is dynamic.

In that case should I mark this as a bug?
Also does that man that I have added the build-system tag correctly? :smiley:

It’s been reported #20980.

I guess so.

1 Like

I meant internally.

Thank you all for the help!