diff options
| author | mlugg <mlugg@mlugg.co.uk> | 2024-08-28 18:25:14 +0100 |
|---|---|---|
| committer | mlugg <mlugg@mlugg.co.uk> | 2024-08-29 23:43:52 +0100 |
| commit | c62487da76b08a0dfb69fbf76501250ca065c140 (patch) | |
| tree | 1ef46d45f3ff9edaade3805d9ab5a9910cb8571b /src/link | |
| parent | ba8d3f69ca65738f27deea43e795f5e787a061f2 (diff) | |
| download | zig-c62487da76b08a0dfb69fbf76501250ca065c140.tar.gz zig-c62487da76b08a0dfb69fbf76501250ca065c140.zip | |
compiler: avoid field/decl name conflicts
Most of the required renames here are net wins for readaibility, I'd
say. The ones in `arch` are a little more verbose, but I think better. I
didn't bother renaming the non-conflicting functions in
`arch/arm/bits.zig` and `arch/aarch64/bits.zig`, since these backends
are pretty bit-rotted anyway AIUI.
Diffstat (limited to 'src/link')
| -rw-r--r-- | src/link/Elf.zig | 87 | ||||
| -rw-r--r-- | src/link/Elf/Atom.zig | 2 | ||||
| -rw-r--r-- | src/link/Elf/Thunk.zig | 144 | ||||
| -rw-r--r-- | src/link/Elf/thunks.zig | 234 | ||||
| -rw-r--r-- | src/link/MachO.zig | 130 | ||||
| -rw-r--r-- | src/link/MachO/Atom.zig | 2 | ||||
| -rw-r--r-- | src/link/MachO/Thunk.zig | 125 | ||||
| -rw-r--r-- | src/link/MachO/synthetic.zig | 2 | ||||
| -rw-r--r-- | src/link/MachO/thunks.zig | 218 |
9 files changed, 467 insertions, 477 deletions
diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 3dca7eace3..e577b8d45a 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -3467,7 +3467,7 @@ fn updateSectionSizes(self: *Elf) !void { if (atom_list.items.len == 0) continue; // Create jump/branch range extenders if needed. - try thunks.createThunks(shdr, @intCast(shndx), self); + try self.createThunks(shdr, @intCast(shndx)); } } @@ -5576,6 +5576,88 @@ fn defaultEntrySymbolName(cpu_arch: std.Target.Cpu.Arch) []const u8 { }; } +fn createThunks(elf_file: *Elf, shdr: *elf.Elf64_Shdr, shndx: u32) !void { + const gpa = elf_file.base.comp.gpa; + const cpu_arch = elf_file.getTarget().cpu.arch; + // A branch will need an extender if its target is larger than + // `2^(jump_bits - 1) - margin` where margin is some arbitrary number. + const max_distance = switch (cpu_arch) { + .aarch64 => 0x500_000, + .x86_64, .riscv64 => unreachable, + else => @panic("unhandled arch"), + }; + const atoms = elf_file.sections.items(.atom_list)[shndx].items; + assert(atoms.len > 0); + + for (atoms) |ref| { + elf_file.atom(ref).?.value = -1; + } + + var i: usize = 0; + while (i < atoms.len) { + const start = i; + const start_atom = elf_file.atom(atoms[start]).?; + assert(start_atom.alive); + start_atom.value = try advanceSection(shdr, start_atom.size, start_atom.alignment); + i += 1; + + while (i < atoms.len) : (i += 1) { + const atom_ptr = elf_file.atom(atoms[i]).?; + assert(atom_ptr.alive); + if (@as(i64, @intCast(atom_ptr.alignment.forward(shdr.sh_size))) - start_atom.value >= max_distance) + break; + atom_ptr.value = try advanceSection(shdr, atom_ptr.size, atom_ptr.alignment); + } + + // Insert a thunk at the group end + const thunk_index = try elf_file.addThunk(); + const thunk_ptr = elf_file.thunk(thunk_index); + thunk_ptr.output_section_index = shndx; + + // Scan relocs in the group and create trampolines for any unreachable callsite + for (atoms[start..i]) |ref| { + const atom_ptr = elf_file.atom(ref).?; + const file_ptr = atom_ptr.file(elf_file).?; + log.debug("atom({}) {s}", .{ ref, atom_ptr.name(elf_file) }); + for (atom_ptr.relocs(elf_file)) |rel| { + const is_reachable = switch (cpu_arch) { + .aarch64 => r: { + const r_type: elf.R_AARCH64 = @enumFromInt(rel.r_type()); + if (r_type != .CALL26 and r_type != .JUMP26) break :r true; + const target_ref = file_ptr.resolveSymbol(rel.r_sym(), elf_file); + const target = elf_file.symbol(target_ref).?; + if (target.flags.has_plt) break :r false; + if (atom_ptr.output_section_index != target.output_section_index) break :r false; + const target_atom = target.atom(elf_file).?; + if (target_atom.value == -1) break :r false; + const saddr = atom_ptr.address(elf_file) + @as(i64, @intCast(rel.r_offset)); + const taddr = target.address(.{}, elf_file); + _ = math.cast(i28, taddr + rel.r_addend - saddr) orelse break :r false; + break :r true; + }, + .x86_64, .riscv64 => unreachable, + else => @panic("unsupported arch"), + }; + if (is_reachable) continue; + const target = file_ptr.resolveSymbol(rel.r_sym(), elf_file); + try thunk_ptr.symbols.put(gpa, target, {}); + } + atom_ptr.addExtra(.{ .thunk = thunk_index }, elf_file); + } + + thunk_ptr.value = try advanceSection(shdr, thunk_ptr.size(elf_file), Atom.Alignment.fromNonzeroByteUnits(2)); + + log.debug("thunk({d}) : {}", .{ thunk_index, thunk_ptr.fmt(elf_file) }); + } +} +fn advanceSection(shdr: *elf.Elf64_Shdr, adv_size: u64, alignment: Atom.Alignment) !i64 { + const offset = alignment.forward(shdr.sh_size); + const padding = offset - shdr.sh_size; + shdr.sh_size += padding + adv_size; + shdr.sh_addralign = @max(shdr.sh_addralign, alignment.toByteUnits() orelse 1); + return @intCast(offset); +} + const std = @import("std"); const build_options = @import("build_options"); const builtin = @import("builtin"); @@ -5598,7 +5680,6 @@ const musl = @import("../musl.zig"); const relocatable = @import("Elf/relocatable.zig"); const relocation = @import("Elf/relocation.zig"); const target_util = @import("../target.zig"); -const thunks = @import("Elf/thunks.zig"); const trace = @import("../tracy.zig").trace; const synthetic_sections = @import("Elf/synthetic_sections.zig"); @@ -5636,7 +5717,7 @@ const PltGotSection = synthetic_sections.PltGotSection; const SharedObject = @import("Elf/SharedObject.zig"); const Symbol = @import("Elf/Symbol.zig"); const StringTable = @import("StringTable.zig"); -const Thunk = thunks.Thunk; +const Thunk = @import("Elf/Thunk.zig"); const Value = @import("../Value.zig"); const VerneedSection = synthetic_sections.VerneedSection; const ZigObject = @import("Elf/ZigObject.zig"); diff --git a/src/link/Elf/Atom.zig b/src/link/Elf/Atom.zig index 10a7aa2b79..ef2301f1cd 100644 --- a/src/link/Elf/Atom.zig +++ b/src/link/Elf/Atom.zig @@ -2251,6 +2251,6 @@ const Fde = eh_frame.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"); const ZigObject = @import("ZigObject.zig"); const dev = @import("../../dev.zig"); diff --git a/src/link/Elf/Thunk.zig b/src/link/Elf/Thunk.zig new file mode 100644 index 0000000000..389ba7ffed --- /dev/null +++ b/src/link/Elf/Thunk.zig @@ -0,0 +1,144 @@ +value: i64 = 0, +output_section_index: u32 = 0, +symbols: std.AutoArrayHashMapUnmanaged(Elf.Ref, 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.getTarget().cpu.arch; + return thunk.symbols.keys().len * trampolineSize(cpu_arch); +} + +pub fn address(thunk: Thunk, elf_file: *Elf) i64 { + const shdr = elf_file.sections.items(.shdr)[thunk.output_section_index]; + return @as(i64, @intCast(shdr.sh_addr)) + thunk.value; +} + +pub fn targetAddress(thunk: Thunk, ref: Elf.Ref, elf_file: *Elf) i64 { + const cpu_arch = elf_file.getTarget().cpu.arch; + return thunk.address(elf_file) + @as(i64, @intCast(thunk.symbols.getIndex(ref).? * trampolineSize(cpu_arch))); +} + +pub fn write(thunk: Thunk, elf_file: *Elf, writer: anytype) !void { + switch (elf_file.getTarget().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 { + thunk.output_symtab_ctx.nlocals = @as(u32, @intCast(thunk.symbols.keys().len)); + for (thunk.symbols.keys()) |ref| { + const sym = elf_file.symbol(ref).?; + thunk.output_symtab_ctx.strsize += @as(u32, @intCast(sym.name(elf_file).len + "$thunk".len + 1)); + } +} + +pub fn writeSymtab(thunk: Thunk, elf_file: *Elf) void { + const cpu_arch = elf_file.getTarget().cpu.arch; + for (thunk.symbols.keys(), thunk.output_symtab_ctx.ilocal..) |ref, ilocal| { + const sym = elf_file.symbol(ref).?; + const st_name = @as(u32, @intCast(elf_file.strtab.items.len)); + elf_file.strtab.appendSliceAssumeCapacity(sym.name(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.output_section_index), + .st_value = @intCast(thunk.targetAddress(ref, 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()) |ref| { + const sym = elf_file.symbol(ref).?; + try writer.print(" {} : {s} : @{x}\n", .{ ref, sym.name(elf_file), sym.value }); + } +} + +pub const Index = u32; + +const aarch64 = struct { + fn write(thunk: Thunk, elf_file: *Elf, writer: anytype) !void { + for (thunk.symbols.keys(), 0..) |ref, i| { + const sym = elf_file.symbol(ref).?; + const saddr = thunk.address(elf_file) + @as(i64, @intCast(i * trampoline_size)); + const taddr = sym.address(.{}, 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(.link); +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/link/Elf/thunks.zig b/src/link/Elf/thunks.zig deleted file mode 100644 index bc534639da..0000000000 --- a/src/link/Elf/thunks.zig +++ /dev/null @@ -1,234 +0,0 @@ -pub fn createThunks(shdr: *elf.Elf64_Shdr, shndx: u32, elf_file: *Elf) !void { - const gpa = elf_file.base.comp.gpa; - const cpu_arch = elf_file.getTarget().cpu.arch; - const max_distance = maxAllowedDistance(cpu_arch); - const atoms = elf_file.sections.items(.atom_list)[shndx].items; - assert(atoms.len > 0); - - for (atoms) |ref| { - elf_file.atom(ref).?.value = -1; - } - - var i: usize = 0; - while (i < atoms.len) { - const start = i; - const start_atom = elf_file.atom(atoms[start]).?; - assert(start_atom.alive); - start_atom.value = try advance(shdr, start_atom.size, start_atom.alignment); - i += 1; - - while (i < atoms.len) : (i += 1) { - const atom = elf_file.atom(atoms[i]).?; - assert(atom.alive); - if (@as(i64, @intCast(atom.alignment.forward(shdr.sh_size))) - start_atom.value >= max_distance) - 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.thunk(thunk_index); - thunk.output_section_index = shndx; - - // Scan relocs in the group and create trampolines for any unreachable callsite - for (atoms[start..i]) |ref| { - const atom = elf_file.atom(ref).?; - const file = atom.file(elf_file).?; - log.debug("atom({}) {s}", .{ ref, atom.name(elf_file) }); - for (atom.relocs(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 = file.resolveSymbol(rel.r_sym(), elf_file); - try thunk.symbols.put(gpa, target, {}); - } - atom.addExtra(.{ .thunk = thunk_index }, elf_file); - } - - thunk.value = try advance(shdr, thunk.size(elf_file), Atom.Alignment.fromNonzeroByteUnits(2)); - - log.debug("thunk({d}) : {}", .{ thunk_index, thunk.fmt(elf_file) }); - } -} - -fn advance(shdr: *elf.Elf64_Shdr, size: u64, alignment: Atom.Alignment) !i64 { - const offset = alignment.forward(shdr.sh_size); - const padding = offset - shdr.sh_size; - shdr.sh_size += padding + size; - shdr.sh_addralign = @max(shdr.sh_addralign, alignment.toByteUnits() orelse 1); - 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, - output_section_index: u32 = 0, - symbols: std.AutoArrayHashMapUnmanaged(Elf.Ref, 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.getTarget().cpu.arch; - return thunk.symbols.keys().len * trampolineSize(cpu_arch); - } - - pub fn address(thunk: Thunk, elf_file: *Elf) i64 { - const shdr = elf_file.sections.items(.shdr)[thunk.output_section_index]; - return @as(i64, @intCast(shdr.sh_addr)) + thunk.value; - } - - pub fn targetAddress(thunk: Thunk, ref: Elf.Ref, elf_file: *Elf) i64 { - const cpu_arch = elf_file.getTarget().cpu.arch; - return thunk.address(elf_file) + @as(i64, @intCast(thunk.symbols.getIndex(ref).? * trampolineSize(cpu_arch))); - } - - pub fn write(thunk: Thunk, elf_file: *Elf, writer: anytype) !void { - switch (elf_file.getTarget().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 { - thunk.output_symtab_ctx.nlocals = @as(u32, @intCast(thunk.symbols.keys().len)); - for (thunk.symbols.keys()) |ref| { - const sym = elf_file.symbol(ref).?; - thunk.output_symtab_ctx.strsize += @as(u32, @intCast(sym.name(elf_file).len + "$thunk".len + 1)); - } - } - - pub fn writeSymtab(thunk: Thunk, elf_file: *Elf) void { - const cpu_arch = elf_file.getTarget().cpu.arch; - for (thunk.symbols.keys(), thunk.output_symtab_ctx.ilocal..) |ref, ilocal| { - const sym = elf_file.symbol(ref).?; - const st_name = @as(u32, @intCast(elf_file.strtab.items.len)); - elf_file.strtab.appendSliceAssumeCapacity(sym.name(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.output_section_index), - .st_value = @intCast(thunk.targetAddress(ref, 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()) |ref| { - const sym = elf_file.symbol(ref).?; - try writer.print(" {} : {s} : @{x}\n", .{ ref, sym.name(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 file = atom.file(elf_file).?; - const target_ref = file.resolveSymbol(rel.r_sym(), elf_file); - const target = elf_file.symbol(target_ref).?; - if (target.flags.has_plt) return false; - if (atom.output_section_index != target.output_section_index) return false; - const target_atom = target.atom(elf_file).?; - if (target_atom.value == -1) return false; - const saddr = atom.address(elf_file) + @as(i64, @intCast(rel.r_offset)); - const taddr = target.address(.{}, 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..) |ref, i| { - const sym = elf_file.symbol(ref).?; - const saddr = thunk.address(elf_file) + @as(i64, @intCast(i * trampoline_size)); - const taddr = sym.address(.{}, 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(.link); -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/link/MachO.zig b/src/link/MachO.zig index fd77201739..27bfc9392e 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -64,10 +64,10 @@ stubs_helper: StubsHelperSection = .{}, objc_stubs: ObjcStubsSection = .{}, la_symbol_ptr: LaSymbolPtrSection = .{}, tlv_ptr: TlvPtrSection = .{}, -rebase: Rebase = .{}, -bind: Bind = .{}, -weak_bind: WeakBind = .{}, -lazy_bind: LazyBind = .{}, +rebase_section: Rebase = .{}, +bind_section: Bind = .{}, +weak_bind_section: WeakBind = .{}, +lazy_bind_section: LazyBind = .{}, export_trie: ExportTrie = .{}, unwind_info: UnwindInfo = .{}, data_in_code: DataInCode = .{}, @@ -324,10 +324,10 @@ pub fn deinit(self: *MachO) void { self.stubs.deinit(gpa); self.objc_stubs.deinit(gpa); self.tlv_ptr.deinit(gpa); - self.rebase.deinit(gpa); - self.bind.deinit(gpa); - self.weak_bind.deinit(gpa); - self.lazy_bind.deinit(gpa); + self.rebase_section.deinit(gpa); + self.bind_section.deinit(gpa); + self.weak_bind_section.deinit(gpa); + self.lazy_bind_section.deinit(gpa); self.export_trie.deinit(gpa); self.unwind_info.deinit(gpa); self.data_in_code.deinit(gpa); @@ -2005,7 +2005,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.reportUnexpectedError("failed to create thunks and calculate size of section '{s},{s}': {s}", .{ header.segName(), @@ -2562,7 +2562,7 @@ fn updateLazyBindSizeWorker(self: *MachO) void { defer tracy.end(); const doWork = struct { fn doWork(macho_file: *MachO) !void { - try macho_file.lazy_bind.updateSize(macho_file); + try macho_file.lazy_bind_section.updateSize(macho_file); const sect_id = macho_file.stubs_helper_sect_index.?; const out = &macho_file.sections.items(.out)[sect_id]; var stream = std.io.fixedBufferStream(out.items); @@ -2585,9 +2585,9 @@ pub fn updateLinkeditSizeWorker(self: *MachO, tag: enum { data_in_code, }) void { const res = switch (tag) { - .rebase => self.rebase.updateSize(self), - .bind => self.bind.updateSize(self), - .weak_bind => self.weak_bind.updateSize(self), + .rebase => self.rebase_section.updateSize(self), + .bind => self.bind_section.updateSize(self), + .weak_bind => self.weak_bind_section.updateSize(self), .export_trie => self.export_trie.updateSize(self), .data_in_code => self.data_in_code.updateSize(self), }; @@ -2640,13 +2640,13 @@ fn writeDyldInfo(self: *MachO) !void { var stream = std.io.fixedBufferStream(buffer); const writer = stream.writer(); - try self.rebase.write(writer); + try self.rebase_section.write(writer); try stream.seekTo(cmd.bind_off - base_off); - try self.bind.write(writer); + try self.bind_section.write(writer); try stream.seekTo(cmd.weak_bind_off - base_off); - try self.weak_bind.write(writer); + try self.weak_bind_section.write(writer); try stream.seekTo(cmd.lazy_bind_off - base_off); - try self.lazy_bind.write(writer); + try self.lazy_bind_section.write(writer); try stream.seekTo(cmd.export_off - base_off); try self.export_trie.write(writer); try self.base.file.?.pwriteAll(buffer, cmd.rebase_off); @@ -4602,7 +4602,6 @@ const load_commands = @import("MachO/load_commands.zig"); const relocatable = @import("MachO/relocatable.zig"); const tapi = @import("tapi.zig"); const target_util = @import("../target.zig"); -const thunks = @import("MachO/thunks.zig"); const trace = @import("../tracy.zig").trace; const synthetic = @import("MachO/synthetic.zig"); @@ -4641,7 +4640,7 @@ const StringTable = @import("StringTable.zig"); const StubsSection = synthetic.StubsSection; const StubsHelperSection = synthetic.StubsHelperSection; const Symbol = @import("MachO/Symbol.zig"); -const Thunk = thunks.Thunk; +const Thunk = @import("MachO/Thunk.zig"); const TlvPtrSection = synthetic.TlvPtrSection; const Value = @import("../Value.zig"); const UnwindInfo = @import("MachO/UnwindInfo.zig"); @@ -5292,3 +5291,96 @@ pub const KernE = enum(u32) { NOT_FOUND = 56, _, }; + +fn createThunks(macho_file: *MachO, sect_id: u8) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = macho_file.base.comp.gpa; + 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.isAlive()); + start_atom.value = advanceSection(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.isAlive()); + atom.value = advanceSection(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 scanThunkRelocs(thunk_index, gpa, atoms[start..i], macho_file); + thunk.value = advanceSection(header, thunk.size(), .@"4"); + + log.debug("thunk({d}) : {}", .{ thunk_index, thunk.fmt(macho_file) }); + } +} + +fn advanceSection(sect: *macho.section_64, adv_size: u64, alignment: Atom.Alignment) u64 { + const offset = alignment.forward(sect.size); + const padding = offset - sect.size; + sect.size += padding + adv_size; + sect.@"align" = @max(sect.@"align", alignment.toLog2Units()); + return offset; +} + +fn scanThunkRelocs(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; +} + +/// 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; diff --git a/src/link/MachO/Atom.zig b/src/link/MachO/Atom.zig index a0193c20be..d2a6a13497 100644 --- a/src/link/MachO/Atom.zig +++ b/src/link/MachO/Atom.zig @@ -1220,6 +1220,6 @@ 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"); const dev = @import("../../dev.zig"); diff --git a/src/link/MachO/Thunk.zig b/src/link/MachO/Thunk.zig new file mode 100644 index 0000000000..4a76a408ed --- /dev/null +++ b/src/link/MachO/Thunk.zig @@ -0,0 +1,125 @@ +value: u64 = 0, +out_n_sect: u8 = 0, +symbols: std.AutoArrayHashMapUnmanaged(MachO.Ref, void) = .{}, +output_symtab_ctx: MachO.SymtabCtx = .{}, + +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 calcSymtabSize(thunk: *Thunk, macho_file: *MachO) void { + thunk.output_symtab_ctx.nlocals = @as(u32, @intCast(thunk.symbols.keys().len)); + for (thunk.symbols.keys()) |ref| { + const sym = ref.getSymbol(macho_file).?; + thunk.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + "__thunk".len + 1)); + } +} + +pub fn writeSymtab(thunk: Thunk, macho_file: *MachO, ctx: anytype) void { + var n_strx = thunk.output_symtab_ctx.stroff; + for (thunk.symbols.keys(), thunk.output_symtab_ctx.ilocal..) |ref, ilocal| { + const sym = ref.getSymbol(macho_file).?; + const name = sym.getName(macho_file); + const out_sym = &ctx.symtab.items[ilocal]; + out_sym.n_strx = n_strx; + @memcpy(ctx.strtab.items[n_strx..][0..name.len], name); + n_strx += @intCast(name.len); + @memcpy(ctx.strtab.items[n_strx..][0.."__thunk".len], "__thunk"); + n_strx += @intCast("__thunk".len); + ctx.strtab.items[n_strx] = 0; + n_strx += 1; + out_sym.n_type = macho.N_SECT; + out_sym.n_sect = @intCast(thunk.out_n_sect + 1); + out_sym.n_value = @intCast(thunk.getTargetAddress(ref, macho_file)); + out_sym.n_desc = 0; + } +} + +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/link/MachO/synthetic.zig b/src/link/MachO/synthetic.zig index 35f9d45536..5c7ede387d 100644 --- a/src/link/MachO/synthetic.zig +++ b/src/link/MachO/synthetic.zig @@ -204,7 +204,7 @@ pub const StubsHelperSection = struct { for (macho_file.stubs.symbols.items) |ref| { const sym = ref.getSymbol(macho_file).?; if (sym.flags.weak) continue; - const offset = macho_file.lazy_bind.offsets.items[idx]; + const offset = macho_file.lazy_bind_section.offsets.items[idx]; const source: i64 = @intCast(sect.addr + preamble_size + entry_size * idx); const target: i64 = @intCast(sect.addr); switch (cpu_arch) { diff --git a/src/link/MachO/thunks.zig b/src/link/MachO/thunks.zig deleted file mode 100644 index 4248785c54..0000000000 --- a/src/link/MachO/thunks.zig +++ /dev/null @@ -1,218 +0,0 @@ -pub fn createThunks(sect_id: u8, macho_file: *MachO) !void { - const tracy = trace(@src()); - defer tracy.end(); - - const gpa = macho_file.base.comp.gpa; - 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.isAlive()); - start_atom.value = 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.isAlive()); - atom.value = 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 = advance(header, thunk.size(), .@"4"); - - log.debug("thunk({d}) : {}", .{ thunk_index, thunk.fmt(macho_file) }); - } -} - -fn advance(sect: *macho.section_64, size: u64, alignment: Atom.Alignment) u64 { - const offset = alignment.forward(sect.size); - const padding = offset - sect.size; - sect.size += padding + size; - sect.@"align" = @max(sect.@"align", alignment.toLog2Units()); - 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) = .{}, - output_symtab_ctx: MachO.SymtabCtx = .{}, - - 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 calcSymtabSize(thunk: *Thunk, macho_file: *MachO) void { - thunk.output_symtab_ctx.nlocals = @as(u32, @intCast(thunk.symbols.keys().len)); - for (thunk.symbols.keys()) |ref| { - const sym = ref.getSymbol(macho_file).?; - thunk.output_symtab_ctx.strsize += @as(u32, @intCast(sym.getName(macho_file).len + "__thunk".len + 1)); - } - } - - pub fn writeSymtab(thunk: Thunk, macho_file: *MachO, ctx: anytype) void { - var n_strx = thunk.output_symtab_ctx.stroff; - for (thunk.symbols.keys(), thunk.output_symtab_ctx.ilocal..) |ref, ilocal| { - const sym = ref.getSymbol(macho_file).?; - const name = sym.getName(macho_file); - const out_sym = &ctx.symtab.items[ilocal]; - out_sym.n_strx = n_strx; - @memcpy(ctx.strtab.items[n_strx..][0..name.len], name); - n_strx += @intCast(name.len); - @memcpy(ctx.strtab.items[n_strx..][0.."__thunk".len], "__thunk"); - n_strx += @intCast("__thunk".len); - ctx.strtab.items[n_strx] = 0; - n_strx += 1; - out_sym.n_type = macho.N_SECT; - out_sym.n_sect = @intCast(thunk.out_n_sect + 1); - out_sym.n_value = @intCast(thunk.getTargetAddress(ref, macho_file)); - out_sym.n_desc = 0; - } - } - - 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"); |
