authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-08-26 07:36:13+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-08-29 11:39:34+02:00
logf29d9ec61cf9d533035749fe614f5071e80bc3d0
tree23c6b9b91a677c35a20a5135774db98b5c49d0c1
parentb2af2dc8b751e3a784b10f67314303b4e6514bdc

macho: unify entry point handling


1 files changed, 249 insertions(+), 205 deletions(-)

src/link/MachO.zig+249-205
......@@ -1,99 +1,3 @@
1const MachO = @This();
2
3const std = @import("std");
4const build_options = @import("build_options");
5const builtin = @import("builtin");
6const assert = std.debug.assert;
7const dwarf = std.dwarf;
8const fs = std.fs;
9const log = std.log.scoped(.link);
10const macho = std.macho;
11const math = std.math;
12const mem = std.mem;
13const meta = std.meta;
14
15const aarch64 = @import("../arch/aarch64/bits.zig");
16const calcUuid = @import("MachO/uuid.zig").calcUuid;
17const codegen = @import("../codegen.zig");
18const dead_strip = @import("MachO/dead_strip.zig");
19const fat = @import("MachO/fat.zig");
20const link = @import("../link.zig");
21const llvm_backend = @import("../codegen/llvm.zig");
22const load_commands = @import("MachO/load_commands.zig");
23const stubs = @import("MachO/stubs.zig");
24const target_util = @import("../target.zig");
25const trace = @import("../tracy.zig").trace;
26const zld = @import("MachO/zld.zig");
27
28const Air = @import("../Air.zig");
29const Allocator = mem.Allocator;
30const Archive = @import("MachO/Archive.zig");
31pub const Atom = @import("MachO/Atom.zig");
32const Cache = std.Build.Cache;
33const CodeSignature = @import("MachO/CodeSignature.zig");
34const Compilation = @import("../Compilation.zig");
35const Dwarf = File.Dwarf;
36const Dylib = @import("MachO/Dylib.zig");
37const File = link.File;
38const Object = @import("MachO/Object.zig");
39const LibStub = @import("tapi.zig").LibStub;
40const Liveness = @import("../Liveness.zig");
41const LlvmObject = @import("../codegen/llvm.zig").Object;
42const Md5 = std.crypto.hash.Md5;
43const Module = @import("../Module.zig");
44const InternPool = @import("../InternPool.zig");
45const Relocation = @import("MachO/Relocation.zig");
46const StringTable = @import("strtab.zig").StringTable;
47const TableSection = @import("table_section.zig").TableSection;
48const Trie = @import("MachO/Trie.zig");
49const Type = @import("../type.zig").Type;
50const TypedValue = @import("../TypedValue.zig");
51const Value = @import("../value.zig").Value;
52
53pub const DebugSymbols = @import("MachO/DebugSymbols.zig");
54
55const Bind = @import("MachO/dyld_info/bind.zig").Bind(*const MachO, SymbolWithLoc);
56const LazyBind = @import("MachO/dyld_info/bind.zig").LazyBind(*const MachO, SymbolWithLoc);
57const Rebase = @import("MachO/dyld_info/Rebase.zig");
58
59pub const base_tag: File.Tag = File.Tag.macho;
60pub const N_DEAD: u16 = @as(u16, @bitCast(@as(i16, -1)));
61
62/// Mode of operation of the linker.
63pub const Mode = enum {
64 /// Incremental mode will preallocate segments/sections and is compatible with
65 /// watch and HCS modes of operation.
66 incremental,
67 /// Zld mode will link relocatables in a traditional, one-shot
68 /// fashion (default for LLVM backend). It acts as a drop-in replacement for
69 /// LLD.
70 zld,
71};
72
73pub const Section = struct {
74 header: macho.section_64,
75 segment_index: u8,
76 first_atom_index: ?Atom.Index = null,
77 last_atom_index: ?Atom.Index = null,
78
79 /// A list of atoms that have surplus capacity. This list can have false
80 /// positives, as functions grow and shrink over time, only sometimes being added
81 /// or removed from the freelist.
82 ///
83 /// An atom has surplus capacity when its overcapacity value is greater than
84 /// padToIdeal(minimum_atom_size). That is, when it has so
85 /// much extra capacity, that we could fit a small new symbol in it, itself with
86 /// ideal_capacity or more.
87 ///
88 /// Ideal capacity is defined by size + (size / ideal_factor).
89 ///
90 /// Overcapacity is measured by actual_capacity - ideal_capacity. Note that
91 /// overcapacity can be negative. A simple way to have negative overcapacity is to
92 /// allocate a fresh atom, which will have ideal capacity, and then grow it
93 /// by 1 byte. It will then have -1 overcapacity.
94 free_list: std.ArrayListUnmanaged(Atom.Index) = .{},
95};
96
971base: File,
982
993/// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
......@@ -153,6 +57,10 @@ strtab: StringTable(.strtab) = .{},
15357
15458got_table: TableSection(SymbolWithLoc) = .{},
15559stub_table: TableSection(SymbolWithLoc) = .{},
60tlv_ptr_table: TableSection(SymbolWithLoc) = .{},
61
62thunk_table: std.AutoHashMapUnmanaged(Atom.Index, thunks.Thunk.Index) = .{},
63thunks: std.ArrayListUnmanaged(thunks.Thunk) = .{},
15664
15765error_flags: File.ErrorFlags = File.ErrorFlags{},
15866misc_errors: std.ArrayListUnmanaged(File.ErrorMsg) = .{},
......@@ -225,101 +133,6 @@ tlv_table: TlvSymbolTable = .{},
225133/// Hot-code swapping state.
226134hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{},
227135
228const is_hot_update_compatible = switch (builtin.target.os.tag) {
229 .macos => true,
230 else => false,
231};
232
233const LazySymbolTable = std.AutoArrayHashMapUnmanaged(Module.Decl.OptionalIndex, LazySymbolMetadata);
234
235const LazySymbolMetadata = struct {
236 const State = enum { unused, pending_flush, flushed };
237 text_atom: Atom.Index = undefined,
238 data_const_atom: Atom.Index = undefined,
239 text_state: State = .unused,
240 data_const_state: State = .unused,
241};
242
243const TlvSymbolTable = std.AutoArrayHashMapUnmanaged(SymbolWithLoc, Atom.Index);
244
245const DeclMetadata = struct {
246 atom: Atom.Index,
247 section: u8,
248 /// A list of all exports aliases of this Decl.
249 /// TODO do we actually need this at all?
250 exports: std.ArrayListUnmanaged(u32) = .{},
251
252 fn getExport(m: DeclMetadata, macho_file: *const MachO, name: []const u8) ?u32 {
253 for (m.exports.items) |exp| {
254 if (mem.eql(u8, name, macho_file.getSymbolName(.{ .sym_index = exp }))) return exp;
255 }
256 return null;
257 }
258
259 fn getExportPtr(m: *DeclMetadata, macho_file: *MachO, name: []const u8) ?*u32 {
260 for (m.exports.items) |*exp| {
261 if (mem.eql(u8, name, macho_file.getSymbolName(.{ .sym_index = exp.* }))) return exp;
262 }
263 return null;
264 }
265};
266
267const BindingTable = std.AutoArrayHashMapUnmanaged(Atom.Index, std.ArrayListUnmanaged(Atom.Binding));
268const UnnamedConstTable = std.AutoArrayHashMapUnmanaged(Module.Decl.Index, std.ArrayListUnmanaged(Atom.Index));
269const RebaseTable = std.AutoArrayHashMapUnmanaged(Atom.Index, std.ArrayListUnmanaged(u32));
270const RelocationTable = std.AutoArrayHashMapUnmanaged(Atom.Index, std.ArrayListUnmanaged(Relocation));
271
272const ResolveAction = struct {
273 kind: Kind,
274 target: SymbolWithLoc,
275
276 const Kind = enum {
277 none,
278 add_got,
279 add_stub,
280 };
281};
282
283pub const SymbolWithLoc = extern struct {
284 // Index into the respective symbol table.
285 sym_index: u32,
286
287 // 0 means it's a synthetic global.
288 file: u32 = 0,
289
290 pub fn getFile(self: SymbolWithLoc) ?u32 {
291 if (self.file == 0) return null;
292 return self.file - 1;
293 }
294
295 pub fn eql(self: SymbolWithLoc, other: SymbolWithLoc) bool {
296 return self.file == other.file and self.sym_index == other.sym_index;
297 }
298};
299
300const HotUpdateState = struct {
301 mach_task: ?std.os.darwin.MachTask = null,
302};
303
304/// When allocating, the ideal_capacity is calculated by
305/// actual_capacity + (actual_capacity / ideal_factor)
306const ideal_factor = 3;
307
308/// In order for a slice of bytes to be considered eligible to keep metadata pointing at
309/// it as a possible place to put new symbols, it must have enough room for this many bytes
310/// (plus extra for reserved capacity).
311const minimum_text_block_size = 64;
312pub const min_text_capacity = padToIdeal(minimum_text_block_size);
313
314/// Default virtual memory offset corresponds to the size of __PAGEZERO segment and
315/// start of __TEXT segment.
316pub const default_pagezero_vmsize: u64 = 0x100000000;
317
318/// We commit 0x1000 = 4096 bytes of space to the header and
319/// the table of load commands. This should be plenty for any
320/// potential future extensions.
321pub const default_headerpad_size: u32 = 0x1000;
322
323136pub fn openPath(allocator: Allocator, options: link.Options) !*MachO {
324137 assert(options.target.ofmt == .macho);
325138
......@@ -622,6 +435,10 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
622435 defer actions.deinit();
623436 try self.resolveSymbolsInDylibs(&actions);
624437
438 if (self.getEntryPoint() == null) {
439 self.error_flags.no_entry_point_found = true;
440 }
441
625442 if (self.unresolved.count() > 0) {
626443 for (self.unresolved.keys()) |index| {
627444 // TODO: convert into compiler errors.
......@@ -641,6 +458,16 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
641458 try self.createDyldPrivateAtom();
642459 try self.writeStubHelperPreamble();
643460
461 if (self.base.options.output_mode == .Exe and self.getEntryPoint() != null) {
462 const global = self.getEntryPoint().?;
463 if (self.getSymbol(global).undf()) {
464 // We do one additional check here in case the entry point was found in one of the dylibs.
465 // (I actually have no idea what this would imply but it is a possible outcome and so we
466 // support it.)
467 try self.addStubEntry(global);
468 }
469 }
470
644471 try self.allocateSpecialSymbols();
645472
646473 for (self.relocs.keys()) |atom_index| {
......@@ -732,16 +559,18 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
732559 .Exe => blk: {
733560 const seg_id = self.header_segment_cmd_index.?;
734561 const seg = self.segments.items[seg_id];
735 const global = self.getEntryPoint() catch |err| switch (err) {
736 error.MissingMainEntrypoint => {
737 self.error_flags.no_entry_point_found = true;
738 break :blk;
739 },
740 else => |e| return e,
741 };
562 const global = self.getEntryPoint() orelse break :blk;
742563 const sym = self.getSymbol(global);
564
565 const addr: u64 = if (sym.undf())
566 // In this case, the symbol has been resolved in one of dylibs and so we point
567 // to the stub as its vmaddr value.
568 self.getStubsEntryAddress(global).?
569 else
570 sym.n_value;
571
743572 try lc_writer.writeStruct(macho.entry_point_command{
744 .entryoff = @as(u32, @intCast(sym.n_value - seg.vmaddr)),
573 .entryoff = @as(u32, @intCast(addr - seg.vmaddr)),
745574 .stacksize = self.base.options.stack_size_override orelse 0,
746575 });
747576 },
......@@ -1796,6 +1625,14 @@ pub fn deinit(self: *MachO) void {
17961625
17971626 self.got_table.deinit(gpa);
17981627 self.stub_table.deinit(gpa);
1628 self.tlv_ptr_table.deinit(gpa);
1629 self.thunk_table.deinit(gpa);
1630
1631 for (self.thunks.items) |*thunk| {
1632 thunk.deinit(gpa);
1633 }
1634 self.thunks.deinit(gpa);
1635
17991636 self.strtab.deinit(gpa);
18001637
18011638 self.locals.deinit(gpa);
......@@ -4019,14 +3856,29 @@ pub fn getAtomIndexForSymbol(self: *MachO, sym_with_loc: SymbolWithLoc) ?Atom.In
40193856 return self.atom_by_index_table.get(sym_with_loc.sym_index);
40203857}
40213858
4022/// Returns symbol location corresponding to the set entrypoint.
3859pub fn getGotEntryAddress(self: *MachO, sym_with_loc: SymbolWithLoc) ?u64 {
3860 const index = self.got_table.lookup.get(sym_with_loc) orelse return null;
3861 const header = self.sections.items(.header)[self.got_section_index.?];
3862 return header.addr + @sizeOf(u64) * index;
3863}
3864
3865pub fn getTlvPtrEntryAddress(self: *MachO, sym_with_loc: SymbolWithLoc) ?u64 {
3866 const index = self.tlv_ptr_table.lookup.get(sym_with_loc) orelse return null;
3867 const header = self.sections.items(.header)[self.tlv_ptr_section_index.?];
3868 return header.addr + @sizeOf(u64) * index;
3869}
3870
3871pub fn getStubsEntryAddress(self: *MachO, sym_with_loc: SymbolWithLoc) ?u64 {
3872 const index = self.stub_table.lookup.get(sym_with_loc) orelse return null;
3873 const header = self.sections.items(.header)[self.stubs_section_index.?];
3874 return header.addr + stubs.stubSize(self.base.options.target.cpu.arch) * index;
3875}
3876
3877/// Returns symbol location corresponding to the set entrypoint if any.
40233878/// Asserts output mode is executable.
4024pub fn getEntryPoint(self: MachO) error{MissingMainEntrypoint}!SymbolWithLoc {
3879pub fn getEntryPoint(self: MachO) ?SymbolWithLoc {
40253880 const entry_name = self.base.options.entry orelse load_commands.default_entry_point;
4026 const global = self.getGlobal(entry_name) orelse {
4027 log.err("entrypoint '{s}' not found", .{entry_name});
4028 return error.MissingMainEntrypoint;
4029 };
3881 const global = self.getGlobal(entry_name) orelse return null;
40303882 return global;
40313883}
40323884
......@@ -4258,3 +4110,195 @@ pub fn logAtom(self: *MachO, atom_index: Atom.Index) void {
42584110 sym.n_sect + 1,
42594111 });
42604112}
4113
4114const MachO = @This();
4115
4116const std = @import("std");
4117const build_options = @import("build_options");
4118const builtin = @import("builtin");
4119const assert = std.debug.assert;
4120const dwarf = std.dwarf;
4121const fs = std.fs;
4122const log = std.log.scoped(.link);
4123const macho = std.macho;
4124const math = std.math;
4125const mem = std.mem;
4126const meta = std.meta;
4127
4128const aarch64 = @import("../arch/aarch64/bits.zig");
4129const calcUuid = @import("MachO/uuid.zig").calcUuid;
4130const codegen = @import("../codegen.zig");
4131const dead_strip = @import("MachO/dead_strip.zig");
4132const fat = @import("MachO/fat.zig");
4133const link = @import("../link.zig");
4134const llvm_backend = @import("../codegen/llvm.zig");
4135const load_commands = @import("MachO/load_commands.zig");
4136const stubs = @import("MachO/stubs.zig");
4137const target_util = @import("../target.zig");
4138const thunks = @import("MachO/thunks.zig");
4139const trace = @import("../tracy.zig").trace;
4140const zld = @import("MachO/zld.zig");
4141
4142const Air = @import("../Air.zig");
4143const Allocator = mem.Allocator;
4144const Archive = @import("MachO/Archive.zig");
4145pub const Atom = @import("MachO/Atom.zig");
4146const Cache = std.Build.Cache;
4147const CodeSignature = @import("MachO/CodeSignature.zig");
4148const Compilation = @import("../Compilation.zig");
4149const Dwarf = File.Dwarf;
4150const Dylib = @import("MachO/Dylib.zig");
4151const File = link.File;
4152const Object = @import("MachO/Object.zig");
4153const LibStub = @import("tapi.zig").LibStub;
4154const Liveness = @import("../Liveness.zig");
4155const LlvmObject = @import("../codegen/llvm.zig").Object;
4156const Md5 = std.crypto.hash.Md5;
4157const Module = @import("../Module.zig");
4158const InternPool = @import("../InternPool.zig");
4159const Relocation = @import("MachO/Relocation.zig");
4160const StringTable = @import("strtab.zig").StringTable;
4161const TableSection = @import("table_section.zig").TableSection;
4162const Trie = @import("MachO/Trie.zig");
4163const Type = @import("../type.zig").Type;
4164const TypedValue = @import("../TypedValue.zig");
4165const Value = @import("../value.zig").Value;
4166
4167pub const DebugSymbols = @import("MachO/DebugSymbols.zig");
4168
4169const Bind = @import("MachO/dyld_info/bind.zig").Bind(*const MachO, SymbolWithLoc);
4170const LazyBind = @import("MachO/dyld_info/bind.zig").LazyBind(*const MachO, SymbolWithLoc);
4171const Rebase = @import("MachO/dyld_info/Rebase.zig");
4172
4173pub const base_tag: File.Tag = File.Tag.macho;
4174pub const N_DEAD: u16 = @as(u16, @bitCast(@as(i16, -1)));
4175
4176/// Mode of operation of the linker.
4177pub const Mode = enum {
4178 /// Incremental mode will preallocate segments/sections and is compatible with
4179 /// watch and HCS modes of operation.
4180 incremental,
4181 /// Zld mode will link relocatables in a traditional, one-shot
4182 /// fashion (default for LLVM backend). It acts as a drop-in replacement for
4183 /// LLD.
4184 zld,
4185};
4186
4187pub const Section = struct {
4188 header: macho.section_64,
4189 segment_index: u8,
4190 first_atom_index: ?Atom.Index = null,
4191 last_atom_index: ?Atom.Index = null,
4192
4193 /// A list of atoms that have surplus capacity. This list can have false
4194 /// positives, as functions grow and shrink over time, only sometimes being added
4195 /// or removed from the freelist.
4196 ///
4197 /// An atom has surplus capacity when its overcapacity value is greater than
4198 /// padToIdeal(minimum_atom_size). That is, when it has so
4199 /// much extra capacity, that we could fit a small new symbol in it, itself with
4200 /// ideal_capacity or more.
4201 ///
4202 /// Ideal capacity is defined by size + (size / ideal_factor).
4203 ///
4204 /// Overcapacity is measured by actual_capacity - ideal_capacity. Note that
4205 /// overcapacity can be negative. A simple way to have negative overcapacity is to
4206 /// allocate a fresh atom, which will have ideal capacity, and then grow it
4207 /// by 1 byte. It will then have -1 overcapacity.
4208 free_list: std.ArrayListUnmanaged(Atom.Index) = .{},
4209};
4210
4211const is_hot_update_compatible = switch (builtin.target.os.tag) {
4212 .macos => true,
4213 else => false,
4214};
4215
4216const LazySymbolTable = std.AutoArrayHashMapUnmanaged(Module.Decl.OptionalIndex, LazySymbolMetadata);
4217
4218const LazySymbolMetadata = struct {
4219 const State = enum { unused, pending_flush, flushed };
4220 text_atom: Atom.Index = undefined,
4221 data_const_atom: Atom.Index = undefined,
4222 text_state: State = .unused,
4223 data_const_state: State = .unused,
4224};
4225
4226const TlvSymbolTable = std.AutoArrayHashMapUnmanaged(SymbolWithLoc, Atom.Index);
4227
4228const DeclMetadata = struct {
4229 atom: Atom.Index,
4230 section: u8,
4231 /// A list of all exports aliases of this Decl.
4232 /// TODO do we actually need this at all?
4233 exports: std.ArrayListUnmanaged(u32) = .{},
4234
4235 fn getExport(m: DeclMetadata, macho_file: *const MachO, name: []const u8) ?u32 {
4236 for (m.exports.items) |exp| {
4237 if (mem.eql(u8, name, macho_file.getSymbolName(.{ .sym_index = exp }))) return exp;
4238 }
4239 return null;
4240 }
4241
4242 fn getExportPtr(m: *DeclMetadata, macho_file: *MachO, name: []const u8) ?*u32 {
4243 for (m.exports.items) |*exp| {
4244 if (mem.eql(u8, name, macho_file.getSymbolName(.{ .sym_index = exp.* }))) return exp;
4245 }
4246 return null;
4247 }
4248};
4249
4250const BindingTable = std.AutoArrayHashMapUnmanaged(Atom.Index, std.ArrayListUnmanaged(Atom.Binding));
4251const UnnamedConstTable = std.AutoArrayHashMapUnmanaged(Module.Decl.Index, std.ArrayListUnmanaged(Atom.Index));
4252const RebaseTable = std.AutoArrayHashMapUnmanaged(Atom.Index, std.ArrayListUnmanaged(u32));
4253const RelocationTable = std.AutoArrayHashMapUnmanaged(Atom.Index, std.ArrayListUnmanaged(Relocation));
4254
4255const ResolveAction = struct {
4256 kind: Kind,
4257 target: SymbolWithLoc,
4258
4259 const Kind = enum {
4260 none,
4261 add_got,
4262 add_stub,
4263 };
4264};
4265
4266pub const SymbolWithLoc = extern struct {
4267 // Index into the respective symbol table.
4268 sym_index: u32,
4269
4270 // 0 means it's a synthetic global.
4271 file: u32 = 0,
4272
4273 pub fn getFile(self: SymbolWithLoc) ?u32 {
4274 if (self.file == 0) return null;
4275 return self.file - 1;
4276 }
4277
4278 pub fn eql(self: SymbolWithLoc, other: SymbolWithLoc) bool {
4279 return self.file == other.file and self.sym_index == other.sym_index;
4280 }
4281};
4282
4283const HotUpdateState = struct {
4284 mach_task: ?std.os.darwin.MachTask = null,
4285};
4286
4287/// When allocating, the ideal_capacity is calculated by
4288/// actual_capacity + (actual_capacity / ideal_factor)
4289const ideal_factor = 3;
4290
4291/// In order for a slice of bytes to be considered eligible to keep metadata pointing at
4292/// it as a possible place to put new symbols, it must have enough room for this many bytes
4293/// (plus extra for reserved capacity).
4294const minimum_text_block_size = 64;
4295pub const min_text_capacity = padToIdeal(minimum_text_block_size);
4296
4297/// Default virtual memory offset corresponds to the size of __PAGEZERO segment and
4298/// start of __TEXT segment.
4299pub const default_pagezero_vmsize: u64 = 0x100000000;
4300
4301/// We commit 0x1000 = 4096 bytes of space to the header and
4302/// the table of load commands. This should be plenty for any
4303/// potential future extensions.
4304pub const default_headerpad_size: u32 = 0x1000;