Linux-musl.so: How do I link musl properly / statically?

I was trying to build a dynamic library for aarch64-linux-musl:

// build.zig
// ...
const lib = b.addSharedLibrary(.{
    .name = "name",
    .root_source_file = b.path("src/root.zig"),
    .target = b.standardTargetOptions(.{}),
    .optimize = b.standardOptimizeOption(.{}),
});
lib.linkLibC();
lib.linkSystemLibrary("m");

But after $ zig build --release=fast -Dtarget=aarch64-linux-musl and a user of my library trying to dlopen it, they get this: dlopen failed: cannot locate symbol "cosf" referenced by "./libname.so".

Here’s other undefined symbols in the .so I was able to find out with readelf:

$ readelf --syms --dyn-syms zig-out/aarch64-linux-musl/libname.so | grep -Fw UND
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND cosf
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND sinf
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND posix_memalign
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND malloc_usable_size
     5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND free
     6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memcpy
     7: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND write
     8: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __errno_location
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
   247: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND cosf
   248: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND sinf
   261: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND posix_memalign
   262: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND malloc_usable_size
   263: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND free
   264: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memcpy
   265: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND write
   266: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __errno_location

The question is: how do I link musl properly and make the errors go away? I noticed the Zig toolchain distro contains source code of musl, including the implementation of the cosf function, but how do I instruct zig to use it?

I tried even:

-lib.linkLibC();
-lib.linkSystemLibrary("m");
+lib.root_module.linkSystemLibrary("c", .{ .preferred_link_mode = .static });
+lib.root_module.linkSystemLibrary("m", .{ .preferred_link_mode = .static });

, but to no avail.

Hello @aikusuuta

I am guessing that lib.linkSystemLibrary("m"); is causing the problem. There is no libm in musl (the contents are in libc). Your system libm might be the gnu one that is missing from the target system. i.e. use only lib.linkLibC(); without linking to libm.

--release=fast does not work with zig build;
zig build -Doptimize=ReleaseFast -Dtarget=aarch64-linux-musl is the correct invocation.

Welcome to ziggit :slight_smile:

I didn’t mention, not linking m is what I tried in the very beginning. But the error about cosf made me try to link m as well.

The --release=fast flag works on my 0.14.0-dev.2371+c013f45ad just fine. Did you mean it was deprecated or something?

I am mistaken. standardOptimizeOption uses -Doptimize first and then falls back to --release

cosf for zig is implemented in compiler_rt.
How do you call cosf?

I tried to call it both as @cos(angle) and c.cosf(angle); with no effect on the error.

And speaking of other undefined symbols: posix_memalign and free, my code uses the c allocator in the following way: std.heap.ArenaAllocator.init(std.heap.c_allocator).

I doubt a way of calling libc functions matters here.

  • What is your zig version?
  • Can you find a minimal zig source that reproduces the problem?

I tried:

const std = @import("std");
const c = @cImport(
    @cInclude("math.h"),
);

pub fn main() !void {
    const foo: f32 = 0;
    const bar = c.cosf(foo);
    std.debug.print("{}\n", .{bar});
}

I ran it with: zig run test.zig -OReleaseFast -lc -target aarch64-linux-musl
It prints: 1e0

// test.zig
const std = @import("std");
const c = @cImport(
    @cInclude("math.h"),
);

export fn main(foo: f32) void {
    const bar = c.cosf(foo);
    std.debug.print("{}\n", .{bar});
}
$ zig build-lib test.zig -dynamic -OReleaseFast -lc -target aarch64-linux-musl
$ readelf --syms --dyn-syms libtest.so | grep -Fw UND
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND cosf
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memset
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND write
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __errno_location
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
    43: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND cosf
    44: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND memset
    45: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND write
    46: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __errno_location

By comparison:

// no-libc.zig
const std = @import("std");

export fn main(foo: f32) void {
    const bar = @cos(foo);
    std.debug.print("{}\n", .{bar});
}
$ zig build-lib no-libc.zig -dynamic -OReleaseFast -target aarch64-linux-musl
$ readelf --syms --dyn-syms libno-libc.so | grep -Fw UND
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND

There’d be no errors if someone were to try to dlopen it. I want the same for the -lc case.

-dynamic means both “build a shared object” and “link musl dynamically”.
I tried -search_static_only (the outcome of .preferred_link_mode = .static) without any effect.
I am not sure if this is a musl or a zig limitation.

I made a post yesterday, but it had issues and then I got busy so I deleted it. Unfortunately even in the case of musl, linking libc isn’t as simple as just linking a static library. Libc has a global internal state that needs to be initialized by it’s corresponding C runtime. I believe that using a shared object that is simply statically linked with libc.a can run into UB issues with unitialized state depending on what parts of libc the shared object makes use of.

I was able to get some very simple experiments to work by manually linking libc.a into a shared object, but as stated above this is not an intended use case and it’s not supported for a good reason.

1 Like

Please find or file a bug in the Zig issue tracker. Zig supports dynamically linking musl libc and clearly it is not working correctly as demonstrated in your original post.

Edit, sorry I misread your title as “how do I link musl dynamically?” If you are making a shared library then dynamic linking libc is very likely what you want. If you are trying to make a dynamic library using static musl, I’m not convinced that’s something zig needs to explicitly help you accomplish.

1 Like

Btw, does the Zig’s std have a similar state requiring a hidden initialization?

No, it does not.

1 Like

That was perhaps because I did wrong: I tried to use the produced .so in an app on Android, which is bionic-based rather than musl-based, which the dynamic linking of musl assumes, I guess.

Originally, I built it for the aarch64-linux-android triple and did successfully. But then tried to also .linkLibC it, to see if I’d be able to adopt existing C code more easily. Zig responded with this:

error: error: unable to find or provide libc for target 'aarch64-linux.4.19...6.11.5-android.2.28'
info: zig can provide libc for related target aarch64-linux-gnu.2.17
info: zig can provide libc for related target aarch64-linux-musl

Which I interpreted as: “you can link libc for your Android target if only you change your triple a bit”. I didn’t want to mess with installing Android NDKs and, recalling that musl can static link nicely, I followed the Zig suggestion (as I interpreted it) and changed the triple to: aarch64-linux-musl. Unfortunately in reality things turned out to not be that simple.

At the moment you do indeed have to install the Android NDK and inform Zig about it through a libc.txt or similar (see zig libc). It’s possible Zig will have native support for bionic in the future.

4 Likes