Skip to content

Commit

Permalink
Merge pull request #121 from kubkon/elf-aarch64
Browse files Browse the repository at this point in the history
elf+aarch64: implement basic thunk support
  • Loading branch information
kubkon authored Feb 28, 2024
2 parents 67e8f44 + 7be87fc commit 01b8c56
Show file tree
Hide file tree
Showing 8 changed files with 539 additions and 81 deletions.
213 changes: 146 additions & 67 deletions src/Elf.zig

Large diffs are not rendered by default.

21 changes: 14 additions & 7 deletions src/Elf/Atom.zig
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ rel_num: u32 = 0,
/// Index of this atom in the linker's atoms table.
atom_index: Index = 0,

/// Index of the thunk for this atom.
thunk_index: Thunk.Index = 0,

flags: Flags = .{},

/// Start index of FDEs referencing this atom.
Expand Down Expand Up @@ -100,6 +103,10 @@ pub fn getRelocs(self: Atom, elf_file: *Elf) []const elf.Elf64_Rela {
return object.relocs.items[self.rel_index..][0..self.rel_num];
}

pub fn getThunk(self: Atom, elf_file: *Elf) *Thunk {
return elf_file.getThunk(self.thunk_index);
}

pub fn writeRelocs(self: Atom, elf_file: *Elf, out_relocs: *std.ArrayList(elf.Elf64_Rela)) !void {
const tracy = trace(@src());
defer tracy.end();
Expand Down Expand Up @@ -1312,6 +1319,7 @@ const aarch64 = struct {
try stream.seekTo(rel.r_offset);
const cwriter = stream.writer();
const code = code_buffer[rel.r_offset..][0..4];
const object = atom.getObject(elf_file);

const P, const A, const S, const GOT, const G, const TP, const DTP = args;
_ = DTP;
Expand All @@ -1336,13 +1344,11 @@ const aarch64 = struct {
.CALL26,
.JUMP26,
=> {
// TODO: add thunk support
const disp: i28 = math.cast(i28, S + A - P) orelse {
elf_file.base.fatal("{s}: {x}: TODO relocation target exceeds max jump distance", .{
atom.getName(elf_file),
rel.r_offset,
});
return;
const disp: i28 = math.cast(i28, S + A - P) orelse blk: {
const thunk = atom.getThunk(elf_file);
const target_index = object.symbols.items[rel.r_sym()];
const S_: i64 = @intCast(thunk.getTargetAddress(target_index, elf_file));
break :blk math.cast(i28, S_ + A - P) orelse return error.Overflow;
};
aarch64_util.writeBranchImm(disp, code);
},
Expand Down Expand Up @@ -1817,3 +1823,4 @@ const Fde = @import("eh_frame.zig").Fde;
const File = @import("file.zig").File;
const Object = @import("Object.zig");
const Symbol = @import("Symbol.zig");
const Thunk = @import("thunks.zig").Thunk;
31 changes: 27 additions & 4 deletions src/Elf/Options.zig
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ static: bool = false,
relax: bool = true,
export_dynamic: bool = false,
image_base: u64 = 0x200000,
page_size: ?u16 = null,
page_size: ?u64 = null,
pie: bool = false,
pic: bool = false,
warn_common: bool = false,
build_id: ?BuildId = null,
hash_style: ?HashStyle = null,
apply_dynamic_relocs: bool = true,
soname: ?[]const u8 = null,
section_start: std.StringHashMapUnmanaged(u64) = .{},
/// -z flags
/// Overrides default stack size.
z_stack_size: ?u64 = null,
Expand Down Expand Up @@ -251,6 +252,17 @@ pub fn parse(arena: Allocator, args: []const []const u8, ctx: anytype) !Options
opts.z_relro = false;
} else if (p.flagZ("muldefs")) {
opts.allow_multiple_definition = true;
} else if (p.argAny("section-start")) |pair| {
var pair_it = std.mem.split(u8, pair, "=");
const name = pair_it.next() orelse ctx.fatal("expected section=address mapping", .{});
const value = pair_it.next() orelse ctx.fatal("expected section=address mapping", .{});
try opts.parseSectionStart(arena, name, value, ctx);
} else if (p.arg1("Tbss")) |value| {
try opts.parseSectionStart(arena, ".bss", value, ctx);
} else if (p.arg1("Ttext")) |value| {
try opts.parseSectionStart(arena, ".text", value, ctx);
} else if (p.arg1("Tdata")) |value| {
try opts.parseSectionStart(arena, ".data", value, ctx);
} else {
try positionals.append(.{ .tag = .path, .path = p.arg });
}
Expand Down Expand Up @@ -397,11 +409,16 @@ const usage =
\\--relax Optimize instructions (default)
\\ --no-relax
\\--rpath=[value], -R [value] Specify runtime path
\\--section-start section=address
\\ Set address of named section
\\--shared Create dynamic library
\\--soname=[value], -h [value] Set shared library name
\\--start-group Ignored for compatibility with GNU
\\--strip-all, -s Strip all symbols. Implies --strip-debug
\\--strip-debug, -S Strip .debug_ sections
\\-Tbss Set address of .bss section
\\-Tdata Set address of .data section
\\-Ttext Set address of .text section
\\--warn-common Warn about duplicate common symbols
\\-z Set linker extension flags
\\ execstack Require executable stack
Expand Down Expand Up @@ -438,9 +455,9 @@ fn cpuArchToElfEmulation(cpu_arch: std.Target.Cpu.Arch) []const u8 {
};
}

const supported_emulations = [_]struct { std.Target.Cpu.Arch, u16 }{
const supported_emulations = [_]struct { std.Target.Cpu.Arch, u64 }{
.{ .x86_64, 0x1000 },
.{ .aarch64, 0x1000 },
.{ .aarch64, 0x10000 },
.{ .riscv64, 0x1000 },
};

Expand All @@ -453,13 +470,19 @@ fn cpuArchFromElfEmulation(value: []const u8) ?std.Target.Cpu.Arch {
return null;
}

pub fn defaultPageSize(cpu_arch: std.Target.Cpu.Arch) ?u16 {
pub fn defaultPageSize(cpu_arch: std.Target.Cpu.Arch) ?u64 {
inline for (supported_emulations) |emulation| {
if (cpu_arch == emulation[0]) return emulation[1];
}
return null;
}

fn parseSectionStart(opts: *Options, arena: Allocator, name: []const u8, value: []const u8, ctx: anytype) !void {
const start = std.fmt.parseInt(u64, value, 0) catch
ctx.fatal("Could not parse value '{s}' into integer\n", .{value});
_ = try opts.section_start.put(arena, try arena.dupe(u8, name), start);
}

const cmd = "ld.zld";

pub const BuildId = enum {
Expand Down
2 changes: 1 addition & 1 deletion src/Elf/Symbol.zig
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ pub fn setOutputSym(symbol: Symbol, elf_file: *Elf, out: *elf.Elf64_Sym) void {
break :blk 0;
}
if (st_shndx == elf.SHN_ABS or st_shndx == elf.SHN_COMMON) break :blk symbol.getAddress(.{ .plt = false }, elf_file);
const shdr = &elf_file.sections.items(.shdr)[st_shndx];
const shdr = elf_file.sections.items(.shdr)[st_shndx];
if (Elf.shdrIsTls(shdr)) break :blk symbol.getAddress(.{ .plt = false }, elf_file) - elf_file.getTlsAddress();
break :blk symbol.getAddress(.{ .plt = false }, elf_file);
};
Expand Down
2 changes: 1 addition & 1 deletion src/Elf/relocatable.zig
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ fn allocateSections(elf_file: *Elf, base_offset: u64) void {
const shdrs = elf_file.sections.slice().items(.shdr)[1..];
var offset = base_offset;
for (shdrs) |*shdr| {
if (Elf.shdrIsZerofill(shdr)) continue;
if (Elf.shdrIsZerofill(shdr.*)) continue;
shdr.sh_offset = mem.alignForward(u64, offset, shdr.sh_addralign);
offset = shdr.sh_offset + shdr.sh_size;
}
Expand Down
240 changes: 240 additions & 0 deletions src/Elf/thunks.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
pub fn createThunks(shndx: u32, elf_file: *Elf) !void {
const gpa = elf_file.base.allocator;
const cpu_arch = elf_file.options.cpu_arch.?;
const slice = elf_file.sections.slice();
const shdr = &slice.items(.shdr)[shndx];
const atoms = slice.items(.atoms)[shndx].items;
assert(atoms.len > 0);

for (atoms) |atom_index| {
elf_file.getAtom(atom_index).?.value = @bitCast(@as(i64, -1));
}

var i: usize = 0;
while (i < atoms.len) {
const start = i;
const start_atom = elf_file.getAtom(atoms[start]).?;
assert(start_atom.flags.alive);
start_atom.value = try advance(shdr, start_atom.size, start_atom.alignment);
i += 1;

while (i < atoms.len and
shdr.sh_size - start_atom.value < maxAllowedDistance(cpu_arch)) : (i += 1)
{
const atom_index = atoms[i];
const atom = elf_file.getAtom(atom_index).?;
assert(atom.flags.alive);
atom.value = try advance(shdr, atom.size, atom.alignment);
}

// Insert a thunk at the group end
const thunk_index = try elf_file.addThunk();
const thunk = elf_file.getThunk(thunk_index);
thunk.out_shndx = shndx;

// Scan relocs in the group and create trampolines for any unreachable callsite
for (atoms[start..i]) |atom_index| {
const atom = elf_file.getAtom(atom_index).?;
const object = atom.getObject(elf_file);
log.debug("atom({d}) {s}", .{ atom_index, atom.getName(elf_file) });
for (atom.getRelocs(elf_file)) |rel| {
const is_reachable = switch (cpu_arch) {
.aarch64 => aarch64.isReachable(atom, rel, elf_file),
.x86_64, .riscv64 => unreachable,
else => @panic("unsupported arch"),
};
if (is_reachable) continue;
const target = object.symbols.items[rel.r_sym()];
try thunk.symbols.put(gpa, target, {});
}
atom.thunk_index = thunk_index;
}

thunk.value = try advance(shdr, thunk.size(elf_file), 2);

log.debug("thunk({d}) : {}", .{ thunk_index, thunk.fmt(elf_file) });
}
}

fn advance(shdr: *elf.Elf64_Shdr, size: u64, pow2_align: u8) !u64 {
const alignment = try math.powi(u32, 2, pow2_align);
const offset = mem.alignForward(u64, shdr.sh_size, alignment);
const padding = offset - shdr.sh_size;
shdr.sh_size += padding + size;
shdr.sh_addralign = @max(shdr.sh_addralign, alignment);
return offset;
}

/// A branch will need an extender if its target is larger than
/// `2^(jump_bits - 1) - margin` where margin is some arbitrary number.
fn maxAllowedDistance(cpu_arch: std.Target.Cpu.Arch) u32 {
return switch (cpu_arch) {
.aarch64 => 0x500_000,
.x86_64, .riscv64 => unreachable,
else => @panic("unhandled arch"),
};
}

pub const Thunk = struct {
value: u64 = 0,
out_shndx: u32 = 0,
symbols: std.AutoArrayHashMapUnmanaged(Symbol.Index, void) = .{},
output_symtab_ctx: Elf.SymtabCtx = .{},

pub fn deinit(thunk: *Thunk, allocator: Allocator) void {
thunk.symbols.deinit(allocator);
}

pub fn size(thunk: Thunk, elf_file: *Elf) usize {
const cpu_arch = elf_file.options.cpu_arch.?;
return thunk.symbols.keys().len * trampolineSize(cpu_arch);
}

pub fn getAddress(thunk: Thunk, elf_file: *Elf) u64 {
const shdr = elf_file.sections.items(.shdr)[thunk.out_shndx];
return shdr.sh_addr + thunk.value;
}

pub fn getTargetAddress(thunk: Thunk, sym_index: Symbol.Index, elf_file: *Elf) u64 {
const cpu_arch = elf_file.options.cpu_arch.?;
return thunk.getAddress(elf_file) + thunk.symbols.getIndex(sym_index).? * trampolineSize(cpu_arch);
}

pub fn write(thunk: Thunk, elf_file: *Elf, writer: anytype) !void {
switch (elf_file.options.cpu_arch.?) {
.aarch64 => try aarch64.write(thunk, elf_file, writer),
.x86_64, .riscv64 => unreachable,
else => @panic("unhandled arch"),
}
}

pub fn calcSymtabSize(thunk: *Thunk, elf_file: *Elf) void {
if (elf_file.options.strip_all) return;

thunk.output_symtab_ctx.nlocals = @as(u32, @intCast(thunk.symbols.keys().len));
for (thunk.symbols.keys()) |sym_index| {
const sym = elf_file.getSymbol(sym_index);
thunk.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(elf_file).len + "$thunk".len + 1));
}
}

pub fn writeSymtab(thunk: Thunk, elf_file: *Elf) void {
if (elf_file.options.strip_all) return;
const cpu_arch = elf_file.options.cpu_arch.?;

for (thunk.symbols.keys(), thunk.output_symtab_ctx.ilocal..) |sym_index, ilocal| {
const sym = elf_file.getSymbol(sym_index);
const st_name = @as(u32, @intCast(elf_file.strtab.items.len));
elf_file.strtab.appendSliceAssumeCapacity(sym.getName(elf_file));
elf_file.strtab.appendSliceAssumeCapacity("$thunk");
elf_file.strtab.appendAssumeCapacity(0);
elf_file.symtab.items[ilocal] = .{
.st_name = st_name,
.st_info = elf.STT_FUNC,
.st_other = 0,
.st_shndx = @intCast(thunk.out_shndx),
.st_value = thunk.getTargetAddress(sym_index, elf_file),
.st_size = trampolineSize(cpu_arch),
};
}
}

fn trampolineSize(cpu_arch: std.Target.Cpu.Arch) usize {
return switch (cpu_arch) {
.aarch64 => aarch64.trampoline_size,
.x86_64, .riscv64 => unreachable,
else => @panic("unhandled arch"),
};
}

pub fn format(
thunk: Thunk,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = thunk;
_ = unused_fmt_string;
_ = options;
_ = writer;
@compileError("do not format Thunk directly");
}

pub fn fmt(thunk: Thunk, elf_file: *Elf) std.fmt.Formatter(format2) {
return .{ .data = .{
.thunk = thunk,
.elf_file = elf_file,
} };
}

const FormatContext = struct {
thunk: Thunk,
elf_file: *Elf,
};

fn format2(
ctx: FormatContext,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = options;
_ = unused_fmt_string;
const thunk = ctx.thunk;
const elf_file = ctx.elf_file;
try writer.print("@{x} : size({x})\n", .{ thunk.value, thunk.size(elf_file) });
for (thunk.symbols.keys()) |index| {
const sym = elf_file.getSymbol(index);
try writer.print(" %{d} : {s} : @{x}\n", .{ index, sym.getName(elf_file), sym.value });
}
}

pub const Index = u32;
};

const aarch64 = struct {
fn isReachable(atom: *const Atom, rel: elf.Elf64_Rela, elf_file: *Elf) bool {
const r_type: elf.R_AARCH64 = @enumFromInt(rel.r_type());
if (r_type != .CALL26 and r_type != .JUMP26) return true;
const object = atom.getObject(elf_file);
const target = object.getSymbol(rel.r_sym(), elf_file);
if (target.flags.plt) return false;
if (atom.out_shndx != target.shndx) return false;
const target_atom = target.getAtom(elf_file).?;
if (target_atom.value == @as(u64, @bitCast(@as(i64, -1)))) return false;
const saddr = @as(i64, @intCast(atom.getAddress(elf_file) + rel.r_offset));
const taddr: i64 = @intCast(target.getAddress(.{}, elf_file));
_ = math.cast(i28, taddr + rel.r_addend - saddr) orelse return false;
return true;
}

fn write(thunk: Thunk, elf_file: *Elf, writer: anytype) !void {
for (thunk.symbols.keys(), 0..) |sym_index, i| {
const sym = elf_file.getSymbol(sym_index);
const saddr = thunk.getAddress(elf_file) + i * trampoline_size;
const taddr = sym.getAddress(.{}, elf_file);
const pages = try util.calcNumberOfPages(saddr, taddr);
try writer.writeInt(u32, Instruction.adrp(.x16, pages).toU32(), .little);
const off: u12 = @truncate(taddr);
try writer.writeInt(u32, Instruction.add(.x16, .x16, off, false).toU32(), .little);
try writer.writeInt(u32, Instruction.br(.x16).toU32(), .little);
}
}

const trampoline_size = 3 * @sizeOf(u32);

const util = @import("../aarch64.zig");
const Instruction = util.Instruction;
};

const assert = std.debug.assert;
const elf = std.elf;
const log = std.log.scoped(.elf);
const math = std.math;
const mem = std.mem;
const std = @import("std");

const Allocator = mem.Allocator;
const Atom = @import("Atom.zig");
const Elf = @import("../Elf.zig");
const Symbol = @import("Symbol.zig");
Loading

0 comments on commit 01b8c56

Please sign in to comment.