I have a relatively simple build.zig
file for a freestanding (bare metal) 32 bit ARM target here:
Summary
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.resolveTargetQuery(.{
.cpu_arch = .thumb,
.os_tag = .freestanding,
.abi = .eabihf,
.cpu_model = std.zig.CrossTarget.CpuModel{ .explicit = &std.Target.arm.cpu.cortex_m7 },
.cpu_features_add = std.Target.arm.featureSet(&[_]std.Target.arm.Feature{std.Target.arm.Feature.fp_armv8d16sp}),
});
const arm_gcc_version = "13.2.1";
const project_name = "blinky";
b.verbose_cc = true;
b.verbose_link = true;
const optimize = b.standardOptimizeOption(.{});
const blinky_exe = b.addExecutable(.{
.name = project_name ++ ".elf",
.target = target,
.optimize = optimize,
.link_libc = false,
.linkage = .static,
.single_threaded = true,
});
// Manually including libraries bundled with arm-none-eabi-gcc
const arm_gcc_path = b.option([]const u8, "armgcc", "Path to arm-none-eabi-gcc compiler") orelse unreachable;
blinky_exe.addLibraryPath(.{ .path = b.fmt("{s}/arm-none-eabi/lib/thumb/v7e-m+fp/hard", .{arm_gcc_path}) });
blinky_exe.addLibraryPath(.{ .path = b.fmt("{s}/lib/gcc/arm-none-eabi/" ++ arm_gcc_version ++ "/thumb/v7e-m+fp/hard", .{arm_gcc_path}) });
blinky_exe.addSystemIncludePath(.{ .path = b.fmt("{s}/arm-none-eabi/include", .{arm_gcc_path}) });
blinky_exe.linkSystemLibrary("c_nano");
blinky_exe.linkSystemLibrary("m");
blinky_exe.linkSystemLibrary("nosys");
// Manually include C runtime objects bundled with arm-none-eabi-gcc
blinky_exe.addObjectFile(.{ .path = b.fmt("{s}/arm-none-eabi/lib/thumb/v7e-m+fp/hard/crt0.o", .{arm_gcc_path}) });
blinky_exe.addObjectFile(.{ .path = b.fmt("{s}/lib/gcc/arm-none-eabi/" ++ arm_gcc_version ++ "/thumb/v7e-m+fp/hard/crti.o", .{arm_gcc_path}) });
blinky_exe.addObjectFile(.{ .path = b.fmt("{s}/lib/gcc/arm-none-eabi/" ++ arm_gcc_version ++ "/thumb/v7e-m+fp/hard/crtbegin.o", .{arm_gcc_path}) });
blinky_exe.addObjectFile(.{ .path = b.fmt("{s}/lib/gcc/arm-none-eabi/" ++ arm_gcc_version ++ "/thumb/v7e-m+fp/hard/crtend.o", .{arm_gcc_path}) });
blinky_exe.addObjectFile(.{ .path = b.fmt("{s}/lib/gcc/arm-none-eabi/" ++ arm_gcc_version ++ "/thumb/v7e-m+fp/hard/crtn.o", .{arm_gcc_path}) });
// Startup file
blinky_exe.addAssemblyFile(b.path("src/startup_stm32f750xx.s"));
// Source files
blinky_exe.addCSourceFiles(.{
.files = &.{
"src/main.c",
},
.flags = &.{ "-Og", "-std=c11", "-DUSE_HAL_DRIVER", "-DSTM32F750xx" },
});
blinky_exe.link_gc_sections = true;
blinky_exe.link_data_sections = true;
blinky_exe.link_function_sections = true;
blinky_exe.setLinkerScriptPath(.{ .path = "./STM32F750N8Hx_FLASH.ld" });
b.installArtifact(blinky_exe);
}
I was examining the linking calls to ld.lld
for a different reason, but disconcertingly noticed that despite setting everything up correctly as a freestanding target (as far as I’m aware), zig build
ends up passing the arg -m armelf_linux_eabi
(“Set target emulation”) to its ld.lld
call. I believe this is incorrect right? Shouldn’t it be something to the effect of -m armelf_freestanding_eabi
given this is a freestanding target?
For what it’s worth, some test applications compiled with this “work” on chip, so this may be nothing but wanted some clarification.