Help needed to port `Makefile` to `build.zig` for arm-v7a RTOS

I’m trying to port a build system written in Makefile to build.zig.

The reference Makefile script is here:

Navilos/build/rvpb/Makefile at master · navilera/Navilos (github.com)

And this is the build.zig I written so far:
(And the RTOS build with it works to some extent, but not perfectly…)

const std = @import("std");

const Cpu = std.Target.Cpu;
const Os = std.Target.Os;
const arm = std.Target.arm;

// Target Architecture = ARMv7-a
// Target CPU = Cortex-A8

pub fn build(b: *std.Build) void {
    const compilationTarget = std.Target{
        .cpu = arm.cpu.cortex_a8.toCpu(Cpu.Arch.arm),
        .os = Os{
            .tag = std.Target.Os.Tag.freestanding,
            .version_range = Os.VersionRange.default(Os.Tag.freestanding, Cpu.Arch.arm),
        },
        .abi = std.Target.Abi.eabi,
        .ofmt = std.Target.ObjectFormat.elf,
    };
    const crossTarget = std.zig.CrossTarget.fromTarget(compilationTarget);

    const target = b.standardTargetOptions(.{
        .default_target = crossTarget,
    });

    const optimize = b.standardOptimizeOption(.{});

    const navilos = b.addExecutable(.{
        .name = "navilos",
        .target = target,
        .optimize = optimize,
    });
    navilos.addAssemblyFile("boot/Entry.S");
    navilos.addCSourceFiles(
        &.{
            "boot/Handler.c",
            "boot/Main.c",

            "kernel/Kernel.c",
            "kernel/task.c",
            "kernel/event.c",

            "lib/armcpu.c",
            "lib/stdio.c",
            "lib/stdlib.c",
            "lib/switch.c",

            "hal/rvpb/Uart.c",
            "hal/rvpb/Timer.c",
            "hal/rvpb/Regs.c",
            "hal/rvpb/Interrupt.c",
        },
        &.{
            "-c",
            "-g",
            "-std=c11",
            // "-mthumb-interwork",
        },
    );

    navilos.addIncludePath("include");
    navilos.addIncludePath("hal");
    navilos.addIncludePath("lib");
    navilos.addIncludePath("kernel");

    navilos.setLinkerScriptPath(std.Build.FileSource{
        .path = "navilos.ld",
    });

    b.installArtifact(navilos);

    const run_navilos = b.addSystemCommand(&[_][]const u8{
        "qemu-system-arm",
        "-M",
        "realview-pb-a8",
        "-kernel",
        "./zig-out/bin/navilos",
        "-nographic",
    });
    run_navilos.step.dependOn(&navilos.step);

    const run_step = b.step("run", "Run navilos on qemu-system-arm");
    run_step.dependOn(&run_navilos.step);
}

I’m struggling to find out:

  1. how to pass arguments/flags to the linker.
    The linker should be passed following arguments:

    -n -T $(LINKER_SCRIPT)
    ...
     -Wl,-Map=$(MAP_FILE) -nostartfiles -nostdlib -nodefaultlibs -static -lgcc
    
  2. how to turn on -mthumb-interwork option for the compiler

    As long as I figured out, this option is not present in clang which is used by zig build process.

-n

This is documented by ld as

   -n, --nmagic                Do not page align data

Zig has no option for this yet.

-T $(LINKER_SCRIPT)

It looks like you have already accomplished this with

    navilos.setLinkerScriptPath(std.Build.FileSource{
        .path = "navilos.ld",
    });
-Wl,-Map=$(MAP_FILE)

-Map is documented as:

  -Map FILE/DIR               Write a linker map to FILE or DIR/<outputname>.map

This is not something supported by Zig’s linker yet.

-nostartfiles -nostdlib -nodefaultlibs

None of these are needed when using Zig and targeting freestanding.

-static

Zig does static by default already, no need.

-lgcc

No need, zig will produce compiler_rt and link in only the needed functions.

Something else I noticed, your run_navilos step should use addArtifactArg(navilos) instead of hard-coding ./zig-out/bin/navilos.

how to turn on -mthumb-interwork option for the compiler

Is this perhaps equivalent to the target CPU feature thumb_mode? If so you can include that in the CrossTarget.

3 Likes

Thank you for your reply.

I have one more question: is there any work ongoing to support producing a linker map file (-Map=FILE/DIR)?

There is no work ongoing. I only learned about this feature yesterday when I read your post. Would you mind sharing a little bit more about what that does?

Nothing much I can share at the moment, because I don’t know the file is about in detail. My purpose was following the instructions to compile the provided project, and port it to zig build system, reading this book - 임베디드 OS 개발 프로젝트 - YES24, sorry that it has no english version)
Only I can tell is that the file contains all the decisions made during the linking process (where objects are relocated, where symbols are from, where the entry point is at, etc), which is useful for debugging purpose.

It sounds optional.

I’ll chime in on a couple of use cases where having the linker map is most convenient. For those of us building micro-controller systems, we live in the world of physical addresses and the link map can provide help in how memory is allocated and placed. For example, in the ARM v7-M architecture, the Memory Protection Unit (MPU) has the characteristic that memory segments must be placed on the boundaries of the programmed segment size. Larger memory segments have fewer places in memory where they can be placed. When separating privileged from unprivileged execution, it can be quite a juggle and the link map can be useful in designing how the placement of the various linker sections can be accomplished. The link map is not essential, but sometimes it easier to verify the drawing of memory layout I usually make if it is available.

3 Likes