Memory placement in linker script seems to be ignored

src/s.ld [main() in main.zig has linksection(“.text.main”)]

SECTIONS {
	.text : {
		*(.text.main)
		*(.text)
		*(.rodata .rodata.*)
	} = 0x80000000
}

build.zig

const std = @import("std");

pub fn build(b: *std.Build) !void {
	const q = std.Target.Query {
		.cpu_arch = .riscv32,
		.cpu_model = .{ .explicit = .generic(.riscv32) },
		.os_tag = .freestanding,
	};

	const mod = b.addModule("main", .{
		.root_source_file = b.path("src/main.zig"),
		.optimize = .ReleaseSmall,
		.target = b.resolveTargetQuery(q),
	});

	const obj = b.addObject(.{
		.name = "main",
		.root_module = mod,
	});

	obj.setLinkerScript(b.path("src/s.ld"));

	const oc = obj.addObjCopy(.{
		.format = .bin,
		.only_section = ".text",
	});

	b.default_step = &b.addInstallBinFile(oc.getOutput(), "out.bin").step;
	b.default_step.dependOn(&b.addInstallBinFile(obj.getEmittedBin(), "out.o").step);
}

All of this seems to be correct, however objdump and the disassembled code show address 0 being used instead of the proper 0x80000000. Is this an issue with my build/linker scripts, or something else?

Just a guess, but I believe addObject does not run the linker? Maybe b.addExecutable would do the trick?

The linker is being run, as the sections are being combined correctly.

I’m 99% sure @fsgr is correct, linker scripts are utilized when combining 1 or more objects into an executable or library not when producing an object file.

1 Like

The object is being linked using the script, there is no other reasonable way that .rodata would end up combined into .text, as appears in the outputted object file.

Yes, but the address in object files is emitted as 0 as it is when all objects are combined into a executable/library that section load addresses are determined (or at runtime when dynamic linking).

What should happen if two objects specify completely different addresses for their text sections? they need to be combined into a single text section in the output binary, that is where the addresses are determined by the executables/libraries linker script.

1 Like

You’re creating a relocatable object file, not a binary. Object files don’t have load addresses.

1 Like

Then how the hell do i get it to stop emitting 0 as the address for everything?

You need to emit an executable.

And how would that ELF be any different than this ELF?

Well, it’d have a load address for a start.

2 Likes

As @fsgr said, use b.addExecutable instead of b.addObject. You can take a look at my build script, which is for ARM, but otherwise the same idea: https://codeberg.org/spiffyk/zig-sam-e5x-d5x/src/branch/main/build.zig

1 Like

More specifically, ELF objects (type ET_REL rather than ET_EXEC) don’t contain program headers which are where the load addresses are stored.

If you run readelf -h on the object file, you’ll see that there are no program headers.

1 Like

Now it’s still ignoring my start address, but using 0x10040 instead of 0

FWIW, I can get it to behave if I specify the address as a memory region:

MEMORY {
	ram (rxw) : ORIGIN = 0x80000000, LENGTH = 1024
}

SECTIONS {
	.text : {
		*(.text.main)
		*(.text)
		*(.rodata .rodata.*)
	} >ram
}

Not sure why yours doesn’t work.

Oh I know why! The address goes after the section name:

SECTIONS {
	.text 0x80000000 : {
		*(.text.main)
		*(.text)
		*(.rodata .rodata.*)
	}
}

What you specified was an Output Section Fill

(Linker script syntax is weird as hell)

3 Likes