Static linking newlibc as libc, freestanding on ARM, zig + c libraries

Hi I’m trying to build a C project with build.zig
The project is for an embedded platform on ARMv7-M (thumb) specifically for an STM32 CPU. The vendor provides hardware abstraction layer (HAL c-sources ) libraries, startup-code (assembly) and main file.
I successfully included paths to header and sources (c+asm) for the HAL and linker script.
The constraint is that I have to link against newlib as the libc standard library. I added the references to the newlib library object files for my target (ARM embedded cpus have multiple pre-build binaries compiled by the arm-none-eabi-gcc project like crt0.o crtbegin.o, newlibc.o etc…) but I’m unable to compile with zig build because it fails with no significant messages on the linking step. I suspect that it has something to do with how I link to newlib.

I read the startup assembly script for my CPU, and it is the first code that runs after a cpu reset. It does most of the initialization that a normal crt0.s code does a part from calling _start. In fact later on it calls __libc_init_array that calls __init an probably later __start but the call to main is done in the startup assembly (startup_stm32f446xx.s).
I don’t know how to debug my linking error and how to generate a build.
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 {
    //b.verbose_cc = true;
    b.verbose_link = true;
    //b.verbose_air = true;
    //b.verbose = true;

    const prj_name = "test";

    const target = .{
        .cpu_arch = .thumb, // ARMv7
        .cpu_model = .{ .explicit = &std.Target.arm.cpu.cortex_m4 }, // STM32F446RE
        .os_tag = .freestanding, // running in bare metal
        .abi = .eabihf, // no libc (noneabi) with hardware floating point (hf)
    };
    const asm_sources = [_][]const u8{"startup_stm32f446xx.s"};

    //const asm_flags = [_][]const u8{
    //    "-fdata-sections",
    //    "-ffunction-sections",
    //    "-Wall",
    //    "-Wextra",
    //    "-Werror",
    //    "-pedantic",
    //    "-fstack-usage"
    //};

    const c_includes = [_][]const u8{
        "./Core/Inc",
        "./Drivers/STM32F4xx_HAL_Driver/Inc",
        "./Drivers/STM32F4xx_HAL_Driver/Inc/Legacy",
        "./Drivers/CMSIS/Device/ST/STM32F4xx/Include",
        "./Drivers/CMSIS/Include",
    };

    const c_sources_drivers = [_][]const u8{
        "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim.c",
        "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim_ex.c",
        "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_uart.c",
        "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc.c",
        "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc_ex.c",
        "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash.c",
        "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash_ex.c",
        "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash_ramfunc.c",
        "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_gpio.c",
        "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma_ex.c",
        "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma.c",
        "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pwr.c",
        "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pwr_ex.c",
        "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_cortex.c",
        "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal.c",
        "Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_exti.c",
    };
    // "-Wno-unused-parameter",
    const c_sources_drivers_compile_flags = [_][]const u8{
        "-std=gnu11",
        "-DUSE_HAL_DRIVER",
        "-DSTM32F446xx",
        "-ffunction-sections",
        "-fdata-sections",
        "-nostdlib",
        "-nostdinc",
        "-Wall",
        //"-Werror",
        "-Wextra",
        "-pedantic",
        "-fstack-usage",
        "-mthumb",
    };

    const c_sources_core = [_][]const u8{
        "./Core/Src/system_stm32f4xx.c",
        "./Core/Src/stm32f4xx_it.c",
        "./Core/Src/stm32f4xx_hal_msp.c",
    };
    const c_sources_app = [_][]const u8{
        "./Core/Src/main.c",
        "./Core/Src/MedianFilter.c",
    };

    //const c_compile_flags = [_][]const u8{ "-DUSE_HAL_DRIVER", "-DSTM32F446xx", "-std=gnu11", "-Wall", "-Werror", "-Wextra", "-pedantic", "-fstack-usage", "-fdata-sections", "-ffunction-sections" };
    const c_compile_flags = [_][]const u8{ "-DUSE_HAL_DRIVER", "-DSTM32F446xx", "-std=gnu11", "-Wall", "-Wextra", "-pedantic", "-fstack-usage", "-fdata-sections", "-ffunction-sections" };
    //const c_compile_flags = [_][]const u8{ "-DUSE_HAL_DRIVER", "-DSTM32F446xx", "-fstack-usage", "-fdata-sections", "-ffunction-sections" };

    // 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(.{});

    const elf = b.addExecutable(.{
        .name = prj_name ++ ".elf",
        .target = target,
        .optimize = optimize, // use optimization given by user
        //.strip = false, // do not strip debug symbols
        .linkage = .static, // static linking
        .link_libc = false, // will link against newlib_nano
        .single_threaded = true, // single core cpu
    });

    // Add necessary libraries and link to them
    elf.addIncludePath(.{ .path = "/usr/arm-none-eabi/include" });
    elf.addIncludePath(.{ .path = "/usr/arm-none-eabi/include/newlib-nano" });
    elf.addObjectFile(.{ .path = "/usr/arm-none-eabi/lib/thumb/v7e-m+fp/hard/libnosys.a" });
    elf.addObjectFile(.{ .path = "/usr/arm-none-eabi/lib/thumb/v7e-m+fp/hard/libc_nano.a" });
    elf.addObjectFile(.{ .path = "/usr/arm-none-eabi/lib/thumb/v7e-m+fp/hard/libm.a" });
    //elf.addObjectFile(.{ .path = "/usr/arm-none-eabi/lib/thumb/v7e-m+fp/hard/libgcc.a" });
    //elf.addObjectFile(.{ .path = "/usr/arm-none-eabi/lib/thumb/v7e-m+fp/hard/crti.o" });
    //elf.addObjectFile(.{ .path = "/usr/arm-none-eabi/lib/thumb/v7e-m+fp/hard/crtbegin.o" });

    // C runtime 0 contains _start function, initializes stack and libc
    // runtime, then calls _main
    // Cruntime 0: This object is expected to contain the _start symbol which
    // takes care of bootstrapping the initial execution of the program.
    // this object initializes very early ABI requirements
    // (like the stack or frame pointer), setting up the argc/argv/env values, and
    // then passing pointers to the init/fini/main funcs to the internal libc main
    // which in turn does more general bootstrapping before finally calling the real
    // main function.
    elf.addObjectFile(.{ .path = "/usr/arm-none-eabi/lib/thumb/v7e-m+fp/hard/crt0.o" });

    // crti (cruntime init) Defines the function prologs for the .init and
    // .fini sections (with the _init and _fini symbols respectively).  This
    // way they can be called directly. They contain constructors and destructors
    // for global structs
    elf.addObjectFile(.{ .path = "/usr/lib/gcc/arm-none-eabi/13.2.0/thumb/v7e-m+fp/hard/crti.o" });
    //  Defines the function epilogs for the .init/.fini sections
    //elf.addObjectFile(.{ .path = "/usr/lib/gcc/arm-none-eabi/13.2.0/thumb/v7e-m+fp/hard/crtn.o" });

    // GCC uses this to find the start of the constructors
    elf.addObjectFile(.{ .path = "/usr/lib/gcc/arm-none-eabi/13.2.0/thumb/v7e-m+fp/hard/crtbegin.o" });
    // GCC uses this to find the start of the destructors.
    //elf.addObjectFile(.{ .path = "/usr/lib/gcc/arm-none-eabi/13.2.0/thumb/v7e-m+fp/hard/crtend.o" });

    // Add C source files
    elf.addCSourceFiles(&c_sources_drivers, &c_sources_drivers_compile_flags);
    elf.addCSourceFiles(&c_sources_core, &c_compile_flags);
    elf.addCSourceFiles(&c_sources_app, &c_compile_flags);

    // Add Assembly sources
    for (asm_sources) |path| {
        elf.addAssemblyFile(.{ .path = path });
    }
    // Add C headers include dirs
    for (c_includes) |path| {
        elf.addIncludePath(.{ .path = path });
    }

    // Set Entry Point of the firmware
    elf.entry_symbol_name = "Reset_Handler";

    // Set linker script file
    elf.setLinkerScriptPath(.{ .path = "./STM32F446RETx_FLASH.ld" });
    elf.setVerboseLink(true);

    b.default_step.dependOn(&elf.step);
    b.installArtifact(elf);
}

Here my linking error result after zig build:

zig build-exe test.elf Debug thumb-freestanding-eabihf: error: ld.lld --error-limit=0 -O0 --entry Reset_Handler -z stack-size=16777216 -T /home/simone/Documents/test/stm32f446re/STM32F446RETx_FLASH.ld --gc-sections -znow -m armelf_linux_eabi -Bstatic -o /home/simone/Documents/test/stm32f446re/zig-cache/o/2926eb9bebe0b6086adb9408f974c605/test.elf /usr/arm-none-eabi/lib/thumb/v7e-m+fp/hard/libnosys.a /usr/arm-none-eabi/lib/thumb/v7e-m+fp/hard/libc_nano.a /usr/arm-none-eabi/lib/thumb/v7e-m+fp/hard/libm.a /usr/arm-none-eabi/lib/thumb/v7e-m+fp/hard/crt0.o /usr/lib/gcc/arm-none-eabi/13.2.0/thumb/v7e-m+fp/hard/crti.o /usr/lib/gcc/arm-none-eabi/13.2.0/thumb/v7e-m+fp/hard/crtbegin.o /home/simone/Documents/test/stm32f446re/zig-cache/o/99288ce3e59a0a256c3a994803be2af0/stm32f4xx_hal_tim.o /home/simone/Documents/test/stm32f446re/zig-cache/o/3392d5d156564ae16d92e6e1e61287dd/stm32f4xx_hal_tim_ex.o /home/simone/Documents/test/stm32f446re/zig-cache/o/f45f1e22a18dfeb915af7d502fe65480/stm32f4xx_hal_uart.o /home/simone/Documents/test/stm32f446re/zig-cache/o/dfc9ef3cafd45a08e1748e5daf7d89c1/stm32f4xx_hal_rcc.o /home/simone/Documents/test/stm32f446re/zig-cache/o/dd46e027c458622b9f55f8559f13606c/stm32f4xx_hal_rcc_ex.o /home/simone/Documents/test/stm32f446re/zig-cache/o/7ec7a26337c091e4b5b68b21fca20066/stm32f4xx_hal_flash.o /home/simone/Documents/test/stm32f446re/zig-cache/o/efd3655fa5f8371e61870ae4070e0039/stm32f4xx_hal_flash_ex.o /home/simone/Documents/test/stm32f446re/zig-cache/o/e575702641443b72ac5f71bcd076bb7e/stm32f4xx_hal_flash_ramfunc.o /home/simone/Documents/test/stm32f446re/zig-cache/o/8bd73bf66e49c02b4de785fa40745d3d/stm32f4xx_hal_gpio.o /home/simone/Documents/test/stm32f446re/zig-cache/o/320697216849f57288f06ee30b883927/stm32f4xx_hal_dma_ex.o /home/simone/Documents/test/stm32f446re/zig-cache/o/d8005e7182f54a18ef4f67f4611b352e/stm32f4xx_hal_dma.o /home/simone/Documents/test/stm32f446re/zig-cache/o/b47ab7db75a3fa4f15862b8bf641f383/stm32f4xx_hal_pwr.o /home/simone/Documents/test/stm32f446re/zig-cache/o/01e7e5906a3cfce5eb6706ad53b571b8/stm32f4xx_hal_pwr_ex.o /home/simone/Documents/test/stm32f446re/zig-cache/o/e5b65b2fa6b1256cab359bf320787ca0/stm32f4xx_hal_cortex.o /home/simone/Documents/test/stm32f446re/zig-cache/o/1696de14f92d2c979677719783df1848/stm32f4xx_hal.o /home/simone/Documents/test/stm32f446re/zig-cache/o/47c1c46543fadda6325e01461f19c71c/stm32f4xx_hal_exti.o /home/simone/Documents/test/stm32f446re/zig-cache/o/458200b72dc662e8df51c6bf2e572a28/system_stm32f4xx.o /home/simone/Documents/test/stm32f446re/zig-cache/o/8556142a4e8cb3a64acad0a21c600e20/stm32f4xx_it.o /home/simone/Documents/test/stm32f446re/zig-cache/o/88e82eefb964b9cc17f8339c485391dd/stm32f4xx_hal_msp.o /home/simone/Documents/test/stm32f446re/zig-cache/o/9e04bf4154200a9ad229a98adf1ef133/main.o /home/simone/Documents/test/stm32f446re/zig-cache/o/608588b52ad424e39fbe7b754bc2d13b/MedianFilter.o /home/simone/Documents/test/stm32f446re/zig-cache/o/34deb1aa406cbf5da07069ba8d251c04/startup_stm32f446xx.o /home/simone/.cache/zig/o/c0e0857fbf17c71bbc9eeea5e5debd97/libc.a --as-needed /home/simone/.cache/zig/o/5201866f2b3579a8a2d793a3128633bc/libcompiler_rt.a --allow-shlib-undefined

You can find the repo and other files here GitHub - simoneruffini/test
The linking of newlib is hardcoded because provided by the arm-none-eabi-newlib package in archlinux, but other linux distributions have the same package.
The project works with the provided makefile that I used as the base to create the build.zig. You can see the verbose output of the compilation with makefile in this file for understanding how and which libraries are linked: test/make_v.log at master · simoneruffini/test · GitHub
Thank you, I don’t know how to proceed more then this.

For whatever reason even with that error from ld.lld the binary output is still generated but its size is Huge with resepect to the gcc build one… I suspect the linker adds stuff I’m not using (for example _malloc_r: my codes doesn’t use mallocs anywhere), but I don’t know how to compare correctly the elf files since the symbols are saved in different memory addresses. How can I proceed?