Skip to content

System Libraries Passed Before Application Objects in Linker Args #24556

@haydenridd

Description

@haydenridd

Zig Version

0.14.1

Steps to Reproduce and Observed Behavior

You have the following files that will be built into static libraries:

first.c -> libfirst.a

int someFunction(int in)
{
    return in + 4;
}

second.c -> libsecond.a

int someFunction(int in);

int anotherOne(int a)
{
    return 70 + someFunction(a);
}

You have the following files that get built into an application:

other.h

#pragma once

int someFunction(int val);

other.c

#include "other.h"

int someFunction(int val)
{
    return val * 99;
}

main.c

#include "other.h"

#include <stdio.h>

int main(void)
{
    int v = someFunction(1);
    printf("SomeFunction: %d\n", v);
    return 0;
}

Then, finally, a build.zig that compiles said static libraries, compiles the application, and links in the two compiled static libraries as "system libraries":

const std = @import("std");

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

    // Build libfirst.a and libsecond.a

    const libfirst_mod = b.createModule(.{
        .target = target,
        .optimize = optimize,
    });
    libfirst_mod.addCSourceFiles(.{
        .files = &.{"lib/first.c"},
        .flags = &.{},
    });
    const libfirst_lib = b.addLibrary(.{
        .name = "first",
        .linkage = .static,
        .root_module = libfirst_mod,
    });
    b.installArtifact(libfirst_lib);

    const libsecond_mod = b.createModule(.{
        .target = target,
        .optimize = optimize,
    });
    libsecond_mod.addCSourceFiles(.{
        .files = &.{"lib/second.c"},
        .flags = &.{},
    });
    const libsecond_lib = b.addLibrary(.{
        .name = "second",
        .linkage = .static,
        .root_module = libsecond_mod,
    });
    b.installArtifact(libsecond_lib);

    const exe_mod = b.createModule(.{
        .target = target,
        .optimize = optimize,
        .link_libc = true,
    });
    exe_mod.addCSourceFiles(.{
        .files = &.{ "src/main.c", "src/other.c" },
        .flags = &.{},
    });

    exe_mod.addLibraryPath(b.path("zig-out/lib"));
    exe_mod.linkSystemLibrary("first", .{
        .needed = true,
        .preferred_link_mode = .static,
        .use_pkg_config = .no,
    });
    exe_mod.linkSystemLibrary("second", .{
        .needed = true,
        .preferred_link_mode = .static,
        .use_pkg_config = .no,
    });

    const exe = b.addExecutable(.{
        .name = "system_lib_bug",
        .root_module = exe_mod,
    });

    exe.step.dependOn(&libfirst_lib.step);
    exe.step.dependOn(&libsecond_lib.step);
    exe.verbose_link = true;

    b.installArtifact(exe);
}

You will then observe after a zig build you get a duplicate symbol error from the linker:

error: ld.lld: duplicate symbol: someFunction
    note: defined at first.c:2 (lib/first.c:2)
    note:            /home/hayden/Documents/zig/system-lib-bug/.zig-cache/o/69cb02e8173c3d8f38a48464beadae4e/first.o:(someFunction) in archive /home/hayden/Documents/zig/system-lib-bug/zig-out/lib/libfirst.a
    note: defined at other.c:4 (src/other.c:4)
    note:            /home/hayden/Documents/zig/system-lib-bug/.zig-cache/o/d8b8c19561fd3c4a8ee0ccc2ed542ebd/other.o:(.text+0x0)

Examining the call to the ld.lld linker, we see that the static library files are listed before the application object files:

ld.lld --error-limit=0 -mllvm -float-abi=hard --entry _start -z stack-size=16777216 --build-id=none --image-base=16777216 --eh-frame-hdr -znow -m elf_x86_64 -o /home/hayden/Documents/zig/system-lib-bug/.zig-cache/o/6b4482bbff8fb40762452bcb96604ef7/system_lib_bug /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crti.o -rpath /home/hayden/Documents/zig/system-lib-bug/zig-out/lib -L /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 /home/hayden/Documents/zig/system-lib-bug/zig-out/lib/libfirst.a /home/hayden/Documents/zig/system-lib-bug/zig-out/lib/libsecond.a /home/hayden/Documents/zig/system-lib-bug/.zig-cache/o/76131da7b5c1ad982a5a2281b4b71714/main.o /home/hayden/Documents/zig/system-lib-bug/.zig-cache/o/d8b8c19561fd3c4a8ee0ccc2ed542ebd/other.o /home/hayden/.cache/zig/o/ee9c7c2323795433f5cfe8074ac46f67/libubsan_rt.a --as-needed -lm -lpthread -lc -ldl -lrt -lutil /home/hayden/.cache/zig/o/198003ee6be6fa4a77cf81686a4c740a/libcompiler_rt.a /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crtn.o

Changing the order so that these libraries instead come after the application objects (like the other system libraries -lc, -ldl -lrt ...):

zig ld.lld --error-limit=0 -mllvm -float-abi=hard --entry _start -z stack-size=16777216 --build-id=none --image-base=16777216 --eh-frame-hdr -znow -m elf_x86_64 -o /home/hayden/Documents/zig/system-lib-bug/.zig-cache/o/6b4482bbff8fb40762452bcb96604ef7/system_lib_bug /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crt1.o /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crti.o -rpath /home/hayden/Documents/zig/system-lib-bug/zig-out/lib -L /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 /home/hayden/Documents/zig/system-lib-bug/.zig-cache/o/76131da7b5c1ad982a5a2281b4b71714/main.o /home/hayden/Documents/zig/system-lib-bug/.zig-cache/o/d8b8c19561fd3c4a8ee0ccc2ed542ebd/other.o /home/hayden/.cache/zig/o/ee9c7c2323795433f5cfe8074ac46f67/libubsan_rt.a /home/hayden/Documents/zig/system-lib-bug/zig-out/lib/libfirst.a /home/hayden/Documents/zig/system-lib-bug/zig-out/lib/libsecond.a --as-needed -lm -lpthread -lc -ldl -lrt -lutil /home/hayden/.cache/zig/o/198003ee6be6fa4a77cf81686a4c740a/libcompiler_rt.a /usr/lib/gcc/x86_64-redhat-linux/14/../../../../lib64/crtn.o

Link is successful, and the expected behavior of the application's implementation of someFunction effectively "overriding" the system libraries implementation holds true:

/home/hayden/Documents/zig/system-lib-bug/.zig-cache/o/6b4482bbff8fb40762452bcb96604ef7/system_lib_bug
SomeFunction: 99

Expected Behavior

I would expect "system" library archives to come after application object files in the linker invocation. This article goes into pretty good detail about linker behavior in regards to ordering static libraries.

For context, this came about because of the following situation:

  • I'm building for a freestanding Cortex-M target
  • I'm porting to Zig's compiler from the arm-none-eabi-gcc compiler
  • This compiler ships with pre-compiled .a files for the C standard library for any given target
  • I'm now using Zig (LLVM backend) to compile this project, however I'm linking in this pre-compiled C standard library
  • I'm running into a duplicate symbol error, since application code provides an implementation of _sbrk
  • However, providing your own implementation for a standard library function and relying on link order to "take your implementation first" is a valid use case and relatively common in my world where the C standard library is exclusively statically linked into the binary. This isn't just me trying something hacky either, this comes from the chip vendor's HAL code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugObserved behavior contradicts documented or intended behavior

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions