From f0ac706f389dadc4d831059f88b0101a4301f612 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 8 Sep 2024 06:58:00 +0200 Subject: [PATCH 1/2] update to latest zig changes --- build.zig.zon | 8 +- src/Coff.zig | 21 ++- src/Coff/Symbol.zig | 2 +- src/Elf.zig | 103 +++++++++++++-- src/Elf/Atom.zig | 4 +- src/Elf/LdScript.zig | 2 +- src/Elf/Symbol.zig | 2 +- src/Elf/Thunk.zig | 180 ++++++++++++++++++++++++++ src/Elf/thunks.zig | 241 ----------------------------------- src/MachO.zig | 93 +++++++++++++- src/MachO/Atom.zig | 4 +- src/MachO/Dylib.zig | 8 +- src/MachO/InternalObject.zig | 16 +-- src/MachO/Object.zig | 16 +-- src/MachO/Symbol.zig | 2 +- src/MachO/Thunk.zig | 107 ++++++++++++++++ src/MachO/thunks.zig | 190 --------------------------- src/Wasm/Object.zig | 4 +- src/riscv.zig | 2 +- 19 files changed, 515 insertions(+), 490 deletions(-) create mode 100644 src/Elf/Thunk.zig delete mode 100644 src/Elf/thunks.zig create mode 100644 src/MachO/Thunk.zig delete mode 100644 src/MachO/thunks.zig diff --git a/build.zig.zon b/build.zig.zon index 3419dc5b..c4ce7435 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -4,12 +4,12 @@ .dependencies = .{ .@"zig-yaml" = .{ - .url = "https://github.com/kubkon/zig-yaml/archive/325dbdd276604dccf184c32fef9600b0ac48343d.tar.gz", - .hash = "1220e8870ca83e47b98807e89b5b636072413f6c09f9b26037e4c98c55e4960ac55a", + .url = "https://github.com/kubkon/zig-yaml/archive/a551d00ab041f4799420ab224cdc3efdf978092c.tar.gz", + .hash = "12208398e1393f83a39d03f3ef4893607650b6227dc7f1eee3db4d163fbc2c0c37ca", }, .@"zig-dis-x86_64" = .{ - .url = "https://github.com/kubkon/zig-dis-x86_64/archive/5203b9affc5045e000ae7963d988e155e98e396d.tar.gz", - .hash = "12207252f0592e53e8794d5a41409791d5c8c70e0de67bfba48844406619847cc971", + .url = "https://github.com/kubkon/zig-dis-x86_64/archive/53b2e2dae9e824d7b4994e767c2cbb4f39e443a9.tar.gz", + .hash = "1220f2b9588352067d5b6f3b68d5461cea52f4425c9144f6757db775cf2f19cb5d26", }, }, diff --git a/src/Coff.zig b/src/Coff.zig index 161c47dd..8ee69be8 100644 --- a/src/Coff.zig +++ b/src/Coff.zig @@ -367,7 +367,7 @@ fn parseArchive(self: *Coff, obj: LinkObject, queue: anytype) ParseError!bool { var has_parse_error = false; for (archive.members.items) |member| { - const member_cpu_arch = member.machine.toTargetCpuArch() orelse { + const member_cpu_arch = cpuArchFromCoffMachineType(member.machine) orelse { extra_log.debug("{s}({s}): TODO unhandled machine type {}", .{ obj.path, member.name, member.machine }); continue; }; @@ -1395,6 +1395,15 @@ pub fn getImageBase(self: Coff) u64 { }; } +/// TODO convert from std.Target.Cpu.Arch into std.coff.MachineType and remove this. +fn cpuArchFromCoffMachineType(em: std.coff.MachineType) ?std.Target.Cpu.Arch { + return switch (em) { + .ARM64 => .aarch64, + .X64 => .x86_64, + else => null, + }; +} + pub fn addAlternateName(self: *Coff, from: []const u8, to: []const u8) !void { const gpa = self.base.allocator; const from_index = blk: { @@ -1493,14 +1502,14 @@ pub fn getSymbol(self: *Coff, index: Symbol.Index) *Symbol { } pub fn addSymbolExtra(self: *Coff, extra: Symbol.Extra) !u32 { - const fields = @typeInfo(Symbol.Extra).Struct.fields; + const fields = @typeInfo(Symbol.Extra).@"struct".fields; try self.symbols_extra.ensureUnusedCapacity(self.base.allocator, fields.len); return self.addSymbolExtraAssumeCapacity(extra); } pub fn addSymbolExtraAssumeCapacity(self: *Coff, extra: Symbol.Extra) u32 { const index = @as(u32, @intCast(self.symbols_extra.items.len)); - const fields = @typeInfo(Symbol.Extra).Struct.fields; + const fields = @typeInfo(Symbol.Extra).@"struct".fields; inline for (fields) |field| { self.symbols_extra.appendAssumeCapacity(switch (field.type) { u32 => @field(extra, field.name), @@ -1512,7 +1521,7 @@ pub fn addSymbolExtraAssumeCapacity(self: *Coff, extra: Symbol.Extra) u32 { pub fn getSymbolExtra(self: Coff, index: u32) ?Symbol.Extra { if (index == 0) return null; - const fields = @typeInfo(Symbol.Extra).Struct.fields; + const fields = @typeInfo(Symbol.Extra).@"struct".fields; var i: usize = index; var result: Symbol.Extra = undefined; inline for (fields) |field| { @@ -1527,7 +1536,7 @@ pub fn getSymbolExtra(self: Coff, index: u32) ?Symbol.Extra { pub fn setSymbolExtra(self: *Coff, index: u32, extra: Symbol.Extra) void { assert(index > 0); - const fields = @typeInfo(Symbol.Extra).Struct.fields; + const fields = @typeInfo(Symbol.Extra).@"struct".fields; inline for (fields, 0..) |field, i| { self.symbols_extra.items[index + i] = switch (field.type) { u32 => @field(extra, field.name), @@ -1636,7 +1645,7 @@ fn formatSectionFlags( ) !void { _ = options; _ = unused_fmt_string; - inline for (@typeInfo(coff.SectionHeaderFlags).Struct.fields) |field| { + inline for (@typeInfo(coff.SectionHeaderFlags).@"struct".fields) |field| { if (@field(flags, field.name) == 0b1) { try writer.writeAll(field.name ++ " "); } diff --git a/src/Coff/Symbol.zig b/src/Coff/Symbol.zig index a87bccc7..4d392f57 100644 --- a/src/Coff/Symbol.zig +++ b/src/Coff/Symbol.zig @@ -119,7 +119,7 @@ pub fn addExtra(symbol: *Symbol, opts: AddExtraOpts, coff_file: *Coff) !void { symbol.extra = try coff_file.addSymbolExtra(.{}); } var extra = symbol.getExtra(coff_file).?; - inline for (@typeInfo(@TypeOf(opts)).Struct.fields) |field| { + inline for (@typeInfo(@TypeOf(opts)).@"struct".fields) |field| { if (@field(opts, field.name)) |x| { @field(extra, field.name) = x; } diff --git a/src/Elf.zig b/src/Elf.zig index c8df44fd..8e1e8643 100644 --- a/src/Elf.zig +++ b/src/Elf.zig @@ -866,7 +866,7 @@ fn calcSectionSizes(self: *Elf) !void { if (atoms.items.len == 0) continue; // Create jump/branch range extenders if needed. - try thunks.createThunks(@intCast(i), self); + try self.createThunks(@intCast(i)); } } @@ -1833,7 +1833,7 @@ fn parseObject(self: *Elf, obj: LinkObject) !bool { try file.seekTo(0); if (!Object.isValidHeader(&header)) return false; - const obj_arch = header.e_machine.toTargetCpuArch().?; + const obj_arch = cpuArchFromElfMachine(header.e_machine); try self.validateOrSetCpuArch(obj.path, obj_arch); try self.validateEFlags(obj.path, header.e_flags); @@ -1899,7 +1899,8 @@ fn parseShared(self: *Elf, obj: LinkObject) !bool { try file.seekTo(0); if (!SharedObject.isValidHeader(&header)) return false; - try self.validateOrSetCpuArch(obj.path, header.e_machine.toTargetCpuArch().?); + const cpu_arch = cpuArchFromElfMachine(header.e_machine); + try self.validateOrSetCpuArch(obj.path, cpu_arch); const index = @as(File.Index, @intCast(try self.files.addOne(gpa))); self.files.set(index, .{ .shared = .{ @@ -1971,6 +1972,16 @@ fn validateOrSetCpuArch(self: *Elf, name: []const u8, cpu_arch: std.Target.Cpu.A } } +/// TODO convert from std.Target.Cpu.Arch into std.elf.EM and remove this. +fn cpuArchFromElfMachine(em: std.elf.EM) std.Target.Cpu.Arch { + return switch (em) { + .AARCH64 => .aarch64, + .X86_64 => .x86_64, + .RISCV => .riscv64, + else => @panic("unhandled e_machine value"), + }; +} + fn validateEFlags(self: *Elf, name: []const u8, e_flags: elf.Elf64_Word) !void { // validateOrSetCpuArch should be called before this. const self_cpu_arch = self.options.cpu_arch.?; @@ -2767,14 +2778,14 @@ pub fn getAtom(self: Elf, atom_index: Atom.Index) ?*Atom { } pub fn addAtomExtra(self: *Elf, extra: Atom.Extra) !u32 { - const fields = @typeInfo(Atom.Extra).Struct.fields; + const fields = @typeInfo(Atom.Extra).@"struct".fields; try self.atoms_extra.ensureUnusedCapacity(self.base.allocator, fields.len); return self.addAtomExtraAssumeCapacity(extra); } pub fn addAtomExtraAssumeCapacity(self: *Elf, extra: Atom.Extra) u32 { const index = @as(u32, @intCast(self.atoms_extra.items.len)); - const fields = @typeInfo(Atom.Extra).Struct.fields; + const fields = @typeInfo(Atom.Extra).@"struct".fields; inline for (fields) |field| { self.atoms_extra.appendAssumeCapacity(switch (field.type) { u32 => @field(extra, field.name), @@ -2786,7 +2797,7 @@ pub fn addAtomExtraAssumeCapacity(self: *Elf, extra: Atom.Extra) u32 { pub fn getAtomExtra(self: *Elf, index: u32) ?Atom.Extra { if (index == 0) return null; - const fields = @typeInfo(Atom.Extra).Struct.fields; + const fields = @typeInfo(Atom.Extra).@"struct".fields; var i: usize = index; var result: Atom.Extra = undefined; inline for (fields) |field| { @@ -2801,7 +2812,7 @@ pub fn getAtomExtra(self: *Elf, index: u32) ?Atom.Extra { pub fn setAtomExtra(self: *Elf, index: u32, extra: Atom.Extra) void { assert(index > 0); - const fields = @typeInfo(Atom.Extra).Struct.fields; + const fields = @typeInfo(Atom.Extra).@"struct".fields; inline for (fields, 0..) |field, i| { self.atoms_extra.items[index + i] = switch (field.type) { u32 => @field(extra, field.name), @@ -2810,7 +2821,7 @@ pub fn setAtomExtra(self: *Elf, index: u32, extra: Atom.Extra) void { } } -pub fn addThunk(self: *Elf) !Thunk.Index { +fn addThunk(self: *Elf) !Thunk.Index { const index = @as(Thunk.Index, @intCast(self.thunks.items.len)); const thunk = try self.thunks.addOne(self.base.allocator); thunk.* = .{}; @@ -2822,6 +2833,71 @@ pub fn getThunk(self: *Elf, index: Thunk.Index) *Thunk { return &self.thunks.items[index]; } +fn createThunks(self: *Elf, shndx: u32) !void { + const advance = struct { + fn advance(shdr: *elf.Elf64_Shdr, size: u64, pow2_align: u8) !i64 { + 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 @intCast(offset); + } + }.advance; + + const gpa = self.base.allocator; + const cpu_arch = self.options.cpu_arch.?; + const slice = self.sections.slice(); + const shdr = &slice.items(.shdr)[shndx]; + const atoms = slice.items(.atoms)[shndx].items; + assert(atoms.len > 0); + + for (atoms) |atom_index| { + self.getAtom(atom_index).?.value = -1; + } + + var i: usize = 0; + while (i < atoms.len) { + const start = i; + const start_atom = self.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) : (i += 1) { + const atom_index = atoms[i]; + const atom = self.getAtom(atom_index).?; + assert(atom.flags.alive); + const alignment = try math.powi(u32, 2, atom.alignment); + if (@as(i64, @intCast(mem.alignForward(u64, shdr.sh_size, alignment))) - start_atom.value >= Thunk.maxAllowedDistance(cpu_arch)) break; + atom.value = try advance(shdr, atom.size, atom.alignment); + } + + // Insert a thunk at the group end + const thunk_index = try self.addThunk(); + const thunk = self.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 = self.getAtom(atom_index).?; + const object = atom.getObject(self); + log.debug("atom({d}) {s}", .{ atom_index, atom.getName(self) }); + for (atom.getRelocs(self)) |rel| { + if (Thunk.isReachable(atom, rel, self)) continue; + const target = object.symbols.items[rel.r_sym()]; + try thunk.symbols.put(gpa, target, {}); + } + try atom.addExtra(.{ .thunk = thunk_index }, self); + atom.flags.thunk = true; + } + + thunk.value = try advance(shdr, thunk.size(self), 2); + + log.debug("thunk({d}) : {}", .{ thunk_index, thunk.fmt(self) }); + } +} + pub fn addSymbol(self: *Elf) !Symbol.Index { const index = @as(Symbol.Index, @intCast(self.symbols.items.len)); const symbol = try self.symbols.addOne(self.base.allocator); @@ -2835,14 +2911,14 @@ pub fn getSymbol(self: *Elf, index: Symbol.Index) *Symbol { } pub fn addSymbolExtra(self: *Elf, extra: Symbol.Extra) !u32 { - const fields = @typeInfo(Symbol.Extra).Struct.fields; + const fields = @typeInfo(Symbol.Extra).@"struct".fields; try self.symbols_extra.ensureUnusedCapacity(self.base.allocator, fields.len); return self.addSymbolExtraAssumeCapacity(extra); } pub fn addSymbolExtraAssumeCapacity(self: *Elf, extra: Symbol.Extra) u32 { const index = @as(u32, @intCast(self.symbols_extra.items.len)); - const fields = @typeInfo(Symbol.Extra).Struct.fields; + const fields = @typeInfo(Symbol.Extra).@"struct".fields; inline for (fields) |field| { self.symbols_extra.appendAssumeCapacity(switch (field.type) { u32 => @field(extra, field.name), @@ -2854,7 +2930,7 @@ pub fn addSymbolExtraAssumeCapacity(self: *Elf, extra: Symbol.Extra) u32 { pub fn getSymbolExtra(self: *Elf, index: u32) ?Symbol.Extra { if (index == 0) return null; - const fields = @typeInfo(Symbol.Extra).Struct.fields; + const fields = @typeInfo(Symbol.Extra).@"struct".fields; var i: usize = index; var result: Symbol.Extra = undefined; inline for (fields) |field| { @@ -2869,7 +2945,7 @@ pub fn getSymbolExtra(self: *Elf, index: u32) ?Symbol.Extra { pub fn setSymbolExtra(self: *Elf, index: u32, extra: Symbol.Extra) void { assert(index > 0); - const fields = @typeInfo(Symbol.Extra).Struct.fields; + const fields = @typeInfo(Symbol.Extra).@"struct".fields; inline for (fields, 0..) |field, i| { self.symbols_extra.items[index + i] = switch (field.type) { u32 => @field(extra, field.name), @@ -3312,7 +3388,6 @@ const relocatable = @import("Elf/relocatable.zig"); const relocation = @import("Elf/relocation.zig"); const state_log = std.log.scoped(.state); const synthetic = @import("Elf/synthetic.zig"); -const thunks = @import("Elf/thunks.zig"); const trace = @import("tracy.zig").trace; const riscv = @import("riscv.zig"); @@ -3342,6 +3417,6 @@ const SharedObject = @import("Elf/SharedObject.zig"); const StringTable = @import("StringTable.zig"); const Symbol = @import("Elf/Symbol.zig"); const ThreadPool = std.Thread.Pool; -const Thunk = @import("Elf/thunks.zig").Thunk; +const Thunk = @import("Elf/Thunk.zig"); const VerneedSection = synthetic.VerneedSection; const Zld = @import("Zld.zig"); diff --git a/src/Elf/Atom.zig b/src/Elf/Atom.zig index 087d76ac..0fdbf406 100644 --- a/src/Elf/Atom.zig +++ b/src/Elf/Atom.zig @@ -123,7 +123,7 @@ pub fn addExtra(atom: *Atom, opts: AddExtraOpts, elf_file: *Elf) !void { atom.extra = try elf_file.addAtomExtra(.{}); } var extra = atom.getExtra(elf_file).?; - inline for (@typeInfo(@TypeOf(opts)).Struct.fields) |field| { + inline for (@typeInfo(@TypeOf(opts)).@"struct".fields) |field| { if (@field(opts, field.name)) |x| { @field(extra, field.name) = x; } @@ -1890,4 +1890,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; +const Thunk = @import("Thunk.zig"); diff --git a/src/Elf/LdScript.zig b/src/Elf/LdScript.zig index ca95ad27..12baeb65 100644 --- a/src/Elf/LdScript.zig +++ b/src/Elf/LdScript.zig @@ -105,7 +105,7 @@ const Command = enum { as_needed, fn fromString(s: []const u8) ?Command { - inline for (@typeInfo(Command).Enum.fields) |field| { + inline for (@typeInfo(Command).@"enum".fields) |field| { const upper_name = n: { comptime var buf: [field.name.len]u8 = undefined; inline for (field.name, 0..) |c, i| { diff --git a/src/Elf/Symbol.zig b/src/Elf/Symbol.zig index 1c340f92..2c27f2fd 100644 --- a/src/Elf/Symbol.zig +++ b/src/Elf/Symbol.zig @@ -235,7 +235,7 @@ pub fn addExtra(symbol: *Symbol, opts: AddExtraOpts, elf_file: *Elf) !void { symbol.extra = try elf_file.addSymbolExtra(.{}); } var extra = symbol.getExtra(elf_file).?; - inline for (@typeInfo(@TypeOf(opts)).Struct.fields) |field| { + inline for (@typeInfo(@TypeOf(opts)).@"struct".fields) |field| { if (@field(opts, field.name)) |x| { @field(extra, field.name) = x; } diff --git a/src/Elf/Thunk.zig b/src/Elf/Thunk.zig new file mode 100644 index 00000000..380dd202 --- /dev/null +++ b/src/Elf/Thunk.zig @@ -0,0 +1,180 @@ +value: i64 = 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) i64 { + const shdr = elf_file.sections.items(.shdr)[thunk.out_shndx]; + return @as(i64, @intCast(shdr.sh_addr)) + thunk.value; +} + +pub fn getTargetAddress(thunk: Thunk, sym_index: Symbol.Index, elf_file: *Elf) i64 { + const cpu_arch = elf_file.options.cpu_arch.?; + return thunk.getAddress(elf_file) + @as(i64, @intCast(thunk.symbols.getIndex(sym_index).? * trampolineSize(cpu_arch))); +} + +pub fn isReachable(atom: *const Atom, rel: elf.Elf64_Rela, elf_file: *Elf) bool { + return switch (elf_file.options.cpu_arch.?) { + .aarch64 => Thunk.aarch64.isReachable(atom, rel, elf_file), + .x86_64, .riscv64 => unreachable, + else => @panic("unsupported arch"), + }; +} + +/// A branch will need an extender if its target is larger than +/// `2^(jump_bits - 1) - margin` where margin is some arbitrary number. +pub 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 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 = @intCast(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 == -1) return false; + const saddr = atom.getAddress(elf_file) + @as(i64, @intCast(rel.r_offset)); + const taddr = 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) + @as(i64, @intCast(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(@as(u64, @bitCast(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"); +const Thunk = @This(); diff --git a/src/Elf/thunks.zig b/src/Elf/thunks.zig deleted file mode 100644 index e389d35f..00000000 --- a/src/Elf/thunks.zig +++ /dev/null @@ -1,241 +0,0 @@ -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 = -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) : (i += 1) { - const atom_index = atoms[i]; - const atom = elf_file.getAtom(atom_index).?; - assert(atom.flags.alive); - const alignment = try math.powi(u32, 2, atom.alignment); - if (@as(i64, @intCast(mem.alignForward(u64, shdr.sh_size, alignment))) - start_atom.value >= maxAllowedDistance(cpu_arch)) break; - 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, {}); - } - try atom.addExtra(.{ .thunk = thunk_index }, elf_file); - atom.flags.thunk = true; - } - - 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) !i64 { - 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 @intCast(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: i64 = 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) i64 { - const shdr = elf_file.sections.items(.shdr)[thunk.out_shndx]; - return @as(i64, @intCast(shdr.sh_addr)) + thunk.value; - } - - pub fn getTargetAddress(thunk: Thunk, sym_index: Symbol.Index, elf_file: *Elf) i64 { - const cpu_arch = elf_file.options.cpu_arch.?; - return thunk.getAddress(elf_file) + @as(i64, @intCast(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 = @intCast(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 == -1) return false; - const saddr = atom.getAddress(elf_file) + @as(i64, @intCast(rel.r_offset)); - const taddr = 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) + @as(i64, @intCast(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(@as(u64, @bitCast(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"); diff --git a/src/MachO.zig b/src/MachO.zig index 7f8f56ac..ad235fb1 100644 --- a/src/MachO.zig +++ b/src/MachO.zig @@ -1727,7 +1727,7 @@ fn calcSectionSizeWorker(self: *MachO, sect_id: u8) void { fn createThunksWorker(self: *MachO, sect_id: u8) void { const tracy = trace(@src()); defer tracy.end(); - thunks.createThunks(sect_id, self) catch |err| { + self.createThunks(sect_id) catch |err| { const header = self.sections.items(.header)[sect_id]; self.base.fatal("failed to create thunks and calculate size of section '{s},{s}': {s}", .{ header.segName(), @@ -2741,7 +2741,7 @@ pub fn getFileHandle(self: MachO, index: File.HandleIndex) File.Handle { return self.file_handles.items[index]; } -pub fn addThunk(self: *MachO) !Thunk.Index { +fn addThunk(self: *MachO) !Thunk.Index { const index = @as(Thunk.Index, @intCast(self.thunks.items.len)); const thunk = try self.thunks.addOne(self.base.allocator); thunk.* = .{}; @@ -2753,6 +2753,92 @@ pub fn getThunk(self: *MachO, index: Thunk.Index) *Thunk { return &self.thunks.items[index]; } +fn createThunks(self: *MachO, sect_id: u8) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const advance = struct { + fn advance(sect: *macho.section_64, size: u64, pow2_align: u32) !u64 { + const alignment = try math.powi(u32, 2, pow2_align); + const offset = mem.alignForward(u64, sect.size, alignment); + const padding = offset - sect.size; + sect.size += padding + size; + sect.@"align" = @max(sect.@"align", pow2_align); + return offset; + } + }.advance; + + const scanRelocations = struct { + fn scanRelocations(thunk_index: Thunk.Index, gpa: Allocator, atoms: []const MachO.Ref, mf: *MachO) !void { + const tr = trace(@src()); + defer tr.end(); + + const thunk = mf.getThunk(thunk_index); + + for (atoms) |ref| { + const atom = ref.getAtom(mf).?; + log.debug("atom({d}) {s}", .{ atom.atom_index, atom.getName(mf) }); + for (atom.getRelocs(mf)) |rel| { + if (rel.type != .branch) continue; + if (Thunk.isReachable(atom, rel, mf)) continue; + try thunk.symbols.put(gpa, rel.getTargetSymbolRef(atom.*, mf), {}); + } + atom.addExtra(.{ .thunk = thunk_index }, mf); + } + } + }.scanRelocations; + + // Branch instruction has 26 bits immediate but is 4 byte aligned. + const jump_bits = @bitSizeOf(i28); + const max_distance = (1 << (jump_bits - 1)); + + // A branch will need an extender if its target is larger than + // `2^(jump_bits - 1) - margin` where margin is some arbitrary number. + // mold uses 5MiB margin, while ld64 uses 4MiB margin. We will follow mold + // and assume margin to be 5MiB. + const max_allowed_distance = max_distance - 0x500_000; + + const gpa = self.base.allocator; + const slice = self.sections.slice(); + const header = &slice.items(.header)[sect_id]; + const thnks = &slice.items(.thunks)[sect_id]; + const atoms = slice.items(.atoms)[sect_id].items; + assert(atoms.len > 0); + + for (atoms) |ref| { + ref.getAtom(self).?.value = @bitCast(@as(i64, -1)); + } + + var i: usize = 0; + while (i < atoms.len) { + const start = i; + const start_atom = atoms[start].getAtom(self).?; + assert(start_atom.alive.load(.seq_cst)); + start_atom.value = try advance(header, start_atom.size, start_atom.alignment); + i += 1; + + while (i < atoms.len and + header.size - start_atom.value < max_allowed_distance) : (i += 1) + { + const atom = atoms[i].getAtom(self).?; + assert(atom.alive.load(.seq_cst)); + atom.value = try advance(header, atom.size, atom.alignment); + } + + // Insert a thunk at the group end + const thunk_index = try self.addThunk(); + const thunk = self.getThunk(thunk_index); + thunk.out_n_sect = sect_id; + try thnks.append(gpa, thunk_index); + + // Scan relocs in the group and create trampolines for any unreachable callsite + try scanRelocations(thunk_index, gpa, atoms[start..i], self); + thunk.value = try advance(header, thunk.size(), 2); + + log.debug("thunk({d}) : {}", .{ thunk_index, thunk.fmt(self) }); + } +} + pub fn eatPrefix(path: []const u8, prefix: []const u8) ?[]const u8 { if (mem.startsWith(u8, path, prefix)) return path[prefix.len..]; return null; @@ -3225,7 +3311,6 @@ const relocatable = @import("MachO/relocatable.zig"); const synthetic = @import("MachO/synthetic.zig"); const state_log = std.log.scoped(.state); const std = @import("std"); -const thunks = @import("MachO/thunks.zig"); const trace = @import("tracy.zig").trace; const Allocator = mem.Allocator; @@ -3256,7 +3341,7 @@ const Symbol = @import("MachO/Symbol.zig"); const StringTable = @import("StringTable.zig"); const StubsSection = synthetic.StubsSection; const StubsHelperSection = synthetic.StubsHelperSection; -const Thunk = thunks.Thunk; +const Thunk = @import("MachO/Thunk.zig"); const ThreadPool = std.Thread.Pool; const TlvPtrSection = synthetic.TlvPtrSection; const UnwindInfo = @import("MachO/UnwindInfo.zig"); diff --git a/src/MachO/Atom.zig b/src/MachO/Atom.zig index 9b428b8d..4e8aaea8 100644 --- a/src/MachO/Atom.zig +++ b/src/MachO/Atom.zig @@ -116,7 +116,7 @@ const AddExtraOpts = struct { pub fn addExtra(atom: *Atom, opts: AddExtraOpts, macho_file: *MachO) void { const file = atom.getFile(macho_file); var extra = file.getAtomExtra(atom.extra); - inline for (@typeInfo(@TypeOf(opts)).Struct.fields) |field| { + inline for (@typeInfo(@TypeOf(opts)).@"struct".fields) |field| { if (@field(opts, field.name)) |x| { @field(extra, field.name) = x; } @@ -913,5 +913,5 @@ const MachO = @import("../MachO.zig"); const Object = @import("Object.zig"); const Relocation = @import("Relocation.zig"); const Symbol = @import("Symbol.zig"); -const Thunk = @import("thunks.zig").Thunk; +const Thunk = @import("Thunk.zig"); const UnwindInfo = @import("UnwindInfo.zig"); diff --git a/src/MachO/Dylib.zig b/src/MachO/Dylib.zig index 0dcb82b7..c3678486 100644 --- a/src/MachO/Dylib.zig +++ b/src/MachO/Dylib.zig @@ -695,14 +695,14 @@ pub fn getSymbolRef(self: Dylib, index: Symbol.Index, macho_file: *MachO) MachO. } pub fn addSymbolExtra(self: *Dylib, allocator: Allocator, extra: Symbol.Extra) !u32 { - const fields = @typeInfo(Symbol.Extra).Struct.fields; + const fields = @typeInfo(Symbol.Extra).@"struct".fields; try self.symbols_extra.ensureUnusedCapacity(allocator, fields.len); return self.addSymbolExtraAssumeCapacity(extra); } fn addSymbolExtraAssumeCapacity(self: *Dylib, extra: Symbol.Extra) u32 { const index = @as(u32, @intCast(self.symbols_extra.items.len)); - const fields = @typeInfo(Symbol.Extra).Struct.fields; + const fields = @typeInfo(Symbol.Extra).@"struct".fields; inline for (fields) |field| { self.symbols_extra.appendAssumeCapacity(switch (field.type) { u32 => @field(extra, field.name), @@ -713,7 +713,7 @@ fn addSymbolExtraAssumeCapacity(self: *Dylib, extra: Symbol.Extra) u32 { } pub fn getSymbolExtra(self: Dylib, index: u32) Symbol.Extra { - const fields = @typeInfo(Symbol.Extra).Struct.fields; + const fields = @typeInfo(Symbol.Extra).@"struct".fields; var i: usize = index; var result: Symbol.Extra = undefined; inline for (fields) |field| { @@ -727,7 +727,7 @@ pub fn getSymbolExtra(self: Dylib, index: u32) Symbol.Extra { } pub fn setSymbolExtra(self: *Dylib, index: u32, extra: Symbol.Extra) void { - const fields = @typeInfo(Symbol.Extra).Struct.fields; + const fields = @typeInfo(Symbol.Extra).@"struct".fields; inline for (fields, 0..) |field, i| { self.symbols_extra.items[index + i] = switch (field.type) { u32 => @field(extra, field.name), diff --git a/src/MachO/InternalObject.zig b/src/MachO/InternalObject.zig index 4b6bbaa5..1e1401f3 100644 --- a/src/MachO/InternalObject.zig +++ b/src/MachO/InternalObject.zig @@ -665,14 +665,14 @@ pub fn getAtoms(self: InternalObject) []const Atom.Index { } fn addAtomExtra(self: *InternalObject, allocator: Allocator, extra: Atom.Extra) !u32 { - const fields = @typeInfo(Atom.Extra).Struct.fields; + const fields = @typeInfo(Atom.Extra).@"struct".fields; try self.atoms_extra.ensureUnusedCapacity(allocator, fields.len); return self.addAtomExtraAssumeCapacity(extra); } fn addAtomExtraAssumeCapacity(self: *InternalObject, extra: Atom.Extra) u32 { const index = @as(u32, @intCast(self.atoms_extra.items.len)); - const fields = @typeInfo(Atom.Extra).Struct.fields; + const fields = @typeInfo(Atom.Extra).@"struct".fields; inline for (fields) |field| { self.atoms_extra.appendAssumeCapacity(switch (field.type) { u32 => @field(extra, field.name), @@ -683,7 +683,7 @@ fn addAtomExtraAssumeCapacity(self: *InternalObject, extra: Atom.Extra) u32 { } pub fn getAtomExtra(self: InternalObject, index: u32) Atom.Extra { - const fields = @typeInfo(Atom.Extra).Struct.fields; + const fields = @typeInfo(Atom.Extra).@"struct".fields; var i: usize = index; var result: Atom.Extra = undefined; inline for (fields) |field| { @@ -698,7 +698,7 @@ pub fn getAtomExtra(self: InternalObject, index: u32) Atom.Extra { pub fn setAtomExtra(self: *InternalObject, index: u32, extra: Atom.Extra) void { assert(index > 0); - const fields = @typeInfo(Atom.Extra).Struct.fields; + const fields = @typeInfo(Atom.Extra).@"struct".fields; inline for (fields, 0..) |field, i| { self.atoms_extra.items[index + i] = switch (field.type) { u32 => @field(extra, field.name), @@ -746,14 +746,14 @@ pub fn getSymbolRef(self: InternalObject, index: Symbol.Index, macho_file: *Mach } pub fn addSymbolExtra(self: *InternalObject, allocator: Allocator, extra: Symbol.Extra) !u32 { - const fields = @typeInfo(Symbol.Extra).Struct.fields; + const fields = @typeInfo(Symbol.Extra).@"struct".fields; try self.symbols_extra.ensureUnusedCapacity(allocator, fields.len); return self.addSymbolExtraAssumeCapacity(extra); } fn addSymbolExtraAssumeCapacity(self: *InternalObject, extra: Symbol.Extra) u32 { const index = @as(u32, @intCast(self.symbols_extra.items.len)); - const fields = @typeInfo(Symbol.Extra).Struct.fields; + const fields = @typeInfo(Symbol.Extra).@"struct".fields; inline for (fields) |field| { self.symbols_extra.appendAssumeCapacity(switch (field.type) { u32 => @field(extra, field.name), @@ -764,7 +764,7 @@ fn addSymbolExtraAssumeCapacity(self: *InternalObject, extra: Symbol.Extra) u32 } pub fn getSymbolExtra(self: InternalObject, index: u32) Symbol.Extra { - const fields = @typeInfo(Symbol.Extra).Struct.fields; + const fields = @typeInfo(Symbol.Extra).@"struct".fields; var i: usize = index; var result: Symbol.Extra = undefined; inline for (fields) |field| { @@ -778,7 +778,7 @@ pub fn getSymbolExtra(self: InternalObject, index: u32) Symbol.Extra { } pub fn setSymbolExtra(self: *InternalObject, index: u32, extra: Symbol.Extra) void { - const fields = @typeInfo(Symbol.Extra).Struct.fields; + const fields = @typeInfo(Symbol.Extra).@"struct".fields; inline for (fields, 0..) |field, i| { self.symbols_extra.items[index + i] = switch (field.type) { u32 => @field(extra, field.name), diff --git a/src/MachO/Object.zig b/src/MachO/Object.zig index 263e247e..221b61d6 100644 --- a/src/MachO/Object.zig +++ b/src/MachO/Object.zig @@ -2312,14 +2312,14 @@ pub fn getAtoms(self: *Object) []const Atom.Index { } fn addAtomExtra(self: *Object, allocator: Allocator, extra: Atom.Extra) !u32 { - const fields = @typeInfo(Atom.Extra).Struct.fields; + const fields = @typeInfo(Atom.Extra).@"struct".fields; try self.atoms_extra.ensureUnusedCapacity(allocator, fields.len); return self.addAtomExtraAssumeCapacity(extra); } fn addAtomExtraAssumeCapacity(self: *Object, extra: Atom.Extra) u32 { const index = @as(u32, @intCast(self.atoms_extra.items.len)); - const fields = @typeInfo(Atom.Extra).Struct.fields; + const fields = @typeInfo(Atom.Extra).@"struct".fields; inline for (fields) |field| { self.atoms_extra.appendAssumeCapacity(switch (field.type) { u32 => @field(extra, field.name), @@ -2330,7 +2330,7 @@ fn addAtomExtraAssumeCapacity(self: *Object, extra: Atom.Extra) u32 { } pub fn getAtomExtra(self: Object, index: u32) Atom.Extra { - const fields = @typeInfo(Atom.Extra).Struct.fields; + const fields = @typeInfo(Atom.Extra).@"struct".fields; var i: usize = index; var result: Atom.Extra = undefined; inline for (fields) |field| { @@ -2345,7 +2345,7 @@ pub fn getAtomExtra(self: Object, index: u32) Atom.Extra { pub fn setAtomExtra(self: *Object, index: u32, extra: Atom.Extra) void { assert(index > 0); - const fields = @typeInfo(Atom.Extra).Struct.fields; + const fields = @typeInfo(Atom.Extra).@"struct".fields; inline for (fields, 0..) |field, i| { self.atoms_extra.items[index + i] = switch (field.type) { u32 => @field(extra, field.name), @@ -2373,14 +2373,14 @@ pub fn getSymbolRef(self: Object, index: Symbol.Index, macho_file: *MachO) MachO } pub fn addSymbolExtra(self: *Object, allocator: Allocator, extra: Symbol.Extra) !u32 { - const fields = @typeInfo(Symbol.Extra).Struct.fields; + const fields = @typeInfo(Symbol.Extra).@"struct".fields; try self.symbols_extra.ensureUnusedCapacity(allocator, fields.len); return self.addSymbolExtraAssumeCapacity(extra); } fn addSymbolExtraAssumeCapacity(self: *Object, extra: Symbol.Extra) u32 { const index = @as(u32, @intCast(self.symbols_extra.items.len)); - const fields = @typeInfo(Symbol.Extra).Struct.fields; + const fields = @typeInfo(Symbol.Extra).@"struct".fields; inline for (fields) |field| { self.symbols_extra.appendAssumeCapacity(switch (field.type) { u32 => @field(extra, field.name), @@ -2391,7 +2391,7 @@ fn addSymbolExtraAssumeCapacity(self: *Object, extra: Symbol.Extra) u32 { } pub fn getSymbolExtra(self: Object, index: u32) Symbol.Extra { - const fields = @typeInfo(Symbol.Extra).Struct.fields; + const fields = @typeInfo(Symbol.Extra).@"struct".fields; var i: usize = index; var result: Symbol.Extra = undefined; inline for (fields) |field| { @@ -2405,7 +2405,7 @@ pub fn getSymbolExtra(self: Object, index: u32) Symbol.Extra { } pub fn setSymbolExtra(self: *Object, index: u32, extra: Symbol.Extra) void { - const fields = @typeInfo(Symbol.Extra).Struct.fields; + const fields = @typeInfo(Symbol.Extra).@"struct".fields; inline for (fields, 0..) |field, i| { self.symbols_extra.items[index + i] = switch (field.type) { u32 => @field(extra, field.name), diff --git a/src/MachO/Symbol.zig b/src/MachO/Symbol.zig index e06acd43..6646da3b 100644 --- a/src/MachO/Symbol.zig +++ b/src/MachO/Symbol.zig @@ -198,7 +198,7 @@ const AddExtraOpts = struct { pub fn addExtra(symbol: *Symbol, opts: AddExtraOpts, macho_file: *MachO) void { var extra = symbol.getExtra(macho_file); - inline for (@typeInfo(@TypeOf(opts)).Struct.fields) |field| { + inline for (@typeInfo(@TypeOf(opts)).@"struct".fields) |field| { if (@field(opts, field.name)) |x| { @field(extra, field.name) = x; } diff --git a/src/MachO/Thunk.zig b/src/MachO/Thunk.zig new file mode 100644 index 00000000..26c9790b --- /dev/null +++ b/src/MachO/Thunk.zig @@ -0,0 +1,107 @@ +value: u64 = 0, +out_n_sect: u8 = 0, +symbols: std.AutoArrayHashMapUnmanaged(MachO.Ref, void) = .{}, + +pub fn deinit(thunk: *Thunk, allocator: Allocator) void { + thunk.symbols.deinit(allocator); +} + +pub fn size(thunk: Thunk) usize { + return thunk.symbols.keys().len * trampoline_size; +} + +pub fn getAddress(thunk: Thunk, macho_file: *MachO) u64 { + const header = macho_file.sections.items(.header)[thunk.out_n_sect]; + return header.addr + thunk.value; +} + +pub fn getTargetAddress(thunk: Thunk, ref: MachO.Ref, macho_file: *MachO) u64 { + return thunk.getAddress(macho_file) + thunk.symbols.getIndex(ref).? * trampoline_size; +} + +pub fn write(thunk: Thunk, macho_file: *MachO, writer: anytype) !void { + for (thunk.symbols.keys(), 0..) |ref, i| { + const sym = ref.getSymbol(macho_file).?; + const saddr = thunk.getAddress(macho_file) + i * trampoline_size; + const taddr = sym.getAddress(.{}, macho_file); + const pages = try aarch64.calcNumberOfPages(@intCast(saddr), @intCast(taddr)); + try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little); + const off: u12 = @truncate(taddr); + try writer.writeInt(u32, aarch64.Instruction.add(.x16, .x16, off, false).toU32(), .little); + try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little); + } +} + +pub fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool { + const target = rel.getTargetSymbol(atom.*, macho_file); + if (target.getSectionFlags().stubs or target.getSectionFlags().objc_stubs) return false; + if (atom.out_n_sect != target.getOutputSectionIndex(macho_file)) return false; + const target_atom = target.getAtom(macho_file).?; + if (target_atom.value == @as(u64, @bitCast(@as(i64, -1)))) return false; + const saddr = @as(i64, @intCast(atom.getAddress(macho_file))) + @as(i64, @intCast(rel.offset - atom.off)); + const taddr: i64 = @intCast(rel.getTargetAddress(atom.*, macho_file)); + _ = math.cast(i28, taddr + rel.addend - saddr) orelse return false; + return true; +} + +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, macho_file: *MachO) std.fmt.Formatter(format2) { + return .{ .data = .{ + .thunk = thunk, + .macho_file = macho_file, + } }; +} + +const FormatContext = struct { + thunk: Thunk, + macho_file: *MachO, +}; + +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 macho_file = ctx.macho_file; + try writer.print("@{x} : size({x})\n", .{ thunk.value, thunk.size() }); + for (thunk.symbols.keys()) |ref| { + const sym = ref.getSymbol(macho_file).?; + try writer.print(" {} : {s} : @{x}\n", .{ ref, sym.getName(macho_file), sym.value }); + } +} + +const trampoline_size = 3 * @sizeOf(u32); + +pub const Index = u32; + +const aarch64 = @import("../aarch64.zig"); +const assert = std.debug.assert; +const log = std.log.scoped(.link); +const macho = std.macho; +const math = std.math; +const mem = std.mem; +const std = @import("std"); +const trace = @import("../tracy.zig").trace; + +const Allocator = mem.Allocator; +const Atom = @import("Atom.zig"); +const MachO = @import("../MachO.zig"); +const Relocation = @import("Relocation.zig"); +const Symbol = @import("Symbol.zig"); +const Thunk = @This(); diff --git a/src/MachO/thunks.zig b/src/MachO/thunks.zig deleted file mode 100644 index 390f0fff..00000000 --- a/src/MachO/thunks.zig +++ /dev/null @@ -1,190 +0,0 @@ -pub fn createThunks(sect_id: u8, macho_file: *MachO) !void { - const tracy = trace(@src()); - defer tracy.end(); - - const gpa = macho_file.base.allocator; - const slice = macho_file.sections.slice(); - const header = &slice.items(.header)[sect_id]; - const thnks = &slice.items(.thunks)[sect_id]; - const atoms = slice.items(.atoms)[sect_id].items; - assert(atoms.len > 0); - - for (atoms) |ref| { - ref.getAtom(macho_file).?.value = @bitCast(@as(i64, -1)); - } - - var i: usize = 0; - while (i < atoms.len) { - const start = i; - const start_atom = atoms[start].getAtom(macho_file).?; - assert(start_atom.alive.load(.seq_cst)); - start_atom.value = try advance(header, start_atom.size, start_atom.alignment); - i += 1; - - while (i < atoms.len and - header.size - start_atom.value < max_allowed_distance) : (i += 1) - { - const atom = atoms[i].getAtom(macho_file).?; - assert(atom.alive.load(.seq_cst)); - atom.value = try advance(header, atom.size, atom.alignment); - } - - // Insert a thunk at the group end - const thunk_index = try macho_file.addThunk(); - const thunk = macho_file.getThunk(thunk_index); - thunk.out_n_sect = sect_id; - try thnks.append(gpa, thunk_index); - - // Scan relocs in the group and create trampolines for any unreachable callsite - try scanRelocs(thunk_index, gpa, atoms[start..i], macho_file); - thunk.value = try advance(header, thunk.size(), 2); - - log.debug("thunk({d}) : {}", .{ thunk_index, thunk.fmt(macho_file) }); - } -} - -fn advance(sect: *macho.section_64, size: u64, pow2_align: u32) !u64 { - const alignment = try math.powi(u32, 2, pow2_align); - const offset = mem.alignForward(u64, sect.size, alignment); - const padding = offset - sect.size; - sect.size += padding + size; - sect.@"align" = @max(sect.@"align", pow2_align); - return offset; -} - -fn scanRelocs(thunk_index: Thunk.Index, gpa: Allocator, atoms: []const MachO.Ref, macho_file: *MachO) !void { - const tracy = trace(@src()); - defer tracy.end(); - - const thunk = macho_file.getThunk(thunk_index); - - for (atoms) |ref| { - const atom = ref.getAtom(macho_file).?; - log.debug("atom({d}) {s}", .{ atom.atom_index, atom.getName(macho_file) }); - for (atom.getRelocs(macho_file)) |rel| { - if (rel.type != .branch) continue; - if (isReachable(atom, rel, macho_file)) continue; - try thunk.symbols.put(gpa, rel.getTargetSymbolRef(atom.*, macho_file), {}); - } - atom.addExtra(.{ .thunk = thunk_index }, macho_file); - } -} - -fn isReachable(atom: *const Atom, rel: Relocation, macho_file: *MachO) bool { - const target = rel.getTargetSymbol(atom.*, macho_file); - if (target.getSectionFlags().stubs or target.getSectionFlags().objc_stubs) return false; - if (atom.out_n_sect != target.getOutputSectionIndex(macho_file)) return false; - const target_atom = target.getAtom(macho_file).?; - if (target_atom.value == @as(u64, @bitCast(@as(i64, -1)))) return false; - const saddr = @as(i64, @intCast(atom.getAddress(macho_file))) + @as(i64, @intCast(rel.offset - atom.off)); - const taddr: i64 = @intCast(rel.getTargetAddress(atom.*, macho_file)); - _ = math.cast(i28, taddr + rel.addend - saddr) orelse return false; - return true; -} - -pub const Thunk = struct { - value: u64 = 0, - out_n_sect: u8 = 0, - symbols: std.AutoArrayHashMapUnmanaged(MachO.Ref, void) = .{}, - - pub fn deinit(thunk: *Thunk, allocator: Allocator) void { - thunk.symbols.deinit(allocator); - } - - pub fn size(thunk: Thunk) usize { - return thunk.symbols.keys().len * trampoline_size; - } - - pub fn getAddress(thunk: Thunk, macho_file: *MachO) u64 { - const header = macho_file.sections.items(.header)[thunk.out_n_sect]; - return header.addr + thunk.value; - } - - pub fn getTargetAddress(thunk: Thunk, ref: MachO.Ref, macho_file: *MachO) u64 { - return thunk.getAddress(macho_file) + thunk.symbols.getIndex(ref).? * trampoline_size; - } - - pub fn write(thunk: Thunk, macho_file: *MachO, writer: anytype) !void { - for (thunk.symbols.keys(), 0..) |ref, i| { - const sym = ref.getSymbol(macho_file).?; - const saddr = thunk.getAddress(macho_file) + i * trampoline_size; - const taddr = sym.getAddress(.{}, macho_file); - const pages = try aarch64.calcNumberOfPages(@intCast(saddr), @intCast(taddr)); - try writer.writeInt(u32, aarch64.Instruction.adrp(.x16, pages).toU32(), .little); - const off: u12 = @truncate(taddr); - try writer.writeInt(u32, aarch64.Instruction.add(.x16, .x16, off, false).toU32(), .little); - try writer.writeInt(u32, aarch64.Instruction.br(.x16).toU32(), .little); - } - } - - 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, macho_file: *MachO) std.fmt.Formatter(format2) { - return .{ .data = .{ - .thunk = thunk, - .macho_file = macho_file, - } }; - } - - const FormatContext = struct { - thunk: Thunk, - macho_file: *MachO, - }; - - 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 macho_file = ctx.macho_file; - try writer.print("@{x} : size({x})\n", .{ thunk.value, thunk.size() }); - for (thunk.symbols.keys()) |ref| { - const sym = ref.getSymbol(macho_file).?; - try writer.print(" {} : {s} : @{x}\n", .{ ref, sym.getName(macho_file), sym.value }); - } - } - - const trampoline_size = 3 * @sizeOf(u32); - - pub const Index = u32; -}; - -/// Branch instruction has 26 bits immediate but is 4 byte aligned. -const jump_bits = @bitSizeOf(i28); -const max_distance = (1 << (jump_bits - 1)); - -/// A branch will need an extender if its target is larger than -/// `2^(jump_bits - 1) - margin` where margin is some arbitrary number. -/// mold uses 5MiB margin, while ld64 uses 4MiB margin. We will follow mold -/// and assume margin to be 5MiB. -const max_allowed_distance = max_distance - 0x500_000; - -const aarch64 = @import("../aarch64.zig"); -const assert = std.debug.assert; -const log = std.log.scoped(.link); -const macho = std.macho; -const math = std.math; -const mem = std.mem; -const std = @import("std"); -const trace = @import("../tracy.zig").trace; - -const Allocator = mem.Allocator; -const Atom = @import("Atom.zig"); -const MachO = @import("../MachO.zig"); -const Relocation = @import("Relocation.zig"); -const Symbol = @import("Symbol.zig"); diff --git a/src/Wasm/Object.zig b/src/Wasm/Object.zig index 8a256ea5..439b449f 100644 --- a/src/Wasm/Object.zig +++ b/src/Wasm/Object.zig @@ -867,7 +867,7 @@ fn ElementType(comptime ptr: type) type { /// signedness of the given type `T`. /// Asserts `T` is an integer. fn readLeb(comptime T: type, reader: anytype) !T { - return switch (@typeInfo(T).Int.signedness) { + return switch (@typeInfo(T).int.signedness) { .signed => try leb.readILEB128(T, reader), .unsigned => try leb.readULEB128(T, reader), }; @@ -877,7 +877,7 @@ fn readLeb(comptime T: type, reader: anytype) !T { /// Asserts `T` is an enum fn readEnum(comptime T: type, reader: anytype) !T { switch (@typeInfo(T)) { - .Enum => |enum_type| return @as(T, @enumFromInt(try readLeb(enum_type.tag_type, reader))), + .@"enum" => |enum_type| return @as(T, @enumFromInt(try readLeb(enum_type.tag_type, reader))), else => @compileError("T must be an enum. Instead was given type " ++ @typeName(T)), } } diff --git a/src/riscv.zig b/src/riscv.zig index ebf0351c..0923a25a 100644 --- a/src/riscv.zig +++ b/src/riscv.zig @@ -419,7 +419,7 @@ pub fn writeSetSub6(comptime op: enum { set, sub }, code: *[1]u8, addend: anytyp pub fn writeAddend( comptime Int: type, comptime op: enum { add, sub }, - code: *[@typeInfo(Int).Int.bits / 8]u8, + code: *[@typeInfo(Int).int.bits / 8]u8, value: anytype, ) void { var V: Int = mem.readInt(Int, code, .little); From 4bedccc94646a06f4ee92882728154c4d871d56e Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 9 Sep 2024 05:52:04 +0200 Subject: [PATCH 2/2] macho: update reexports-zig test to latest language changes --- test/macho.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/macho.zig b/test/macho.zig index 60b57bbe..989b77ff 100644 --- a/test/macho.zig +++ b/test/macho.zig @@ -2516,7 +2516,7 @@ fn testReexportsZig(b: *Build, opts: Options) *Step { \\ return x; \\} \\comptime { - \\ @export(foo, .{ .name = "bar", .linkage = .strong }); + \\ @export(&foo, .{ .name = "bar", .linkage = .strong }); \\} );