diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2024-02-08 07:22:34 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-02-08 07:22:34 +0100 |
| commit | 9ca6cc1e2f6af1b5197f92e4b56865ab822f4040 (patch) | |
| tree | 579ce8050c21bc9baf5fc5c9268866d20b95b70c /src | |
| parent | ba8375328cd6ddf5025a400fc1d1817b88be6d53 (diff) | |
| parent | 272fc2df2e0ba79866ff56bacacece2f60dec10b (diff) | |
| download | zig-9ca6cc1e2f6af1b5197f92e4b56865ab822f4040.tar.gz zig-9ca6cc1e2f6af1b5197f92e4b56865ab822f4040.zip | |
Merge pull request #18853 from ziglang/macho-static-lib
macho: implement our own archiver
Diffstat (limited to 'src')
| -rw-r--r-- | src/link/MachO.zig | 101 | ||||
| -rw-r--r-- | src/link/MachO/Archive.zig | 290 | ||||
| -rw-r--r-- | src/link/MachO/Object.zig | 112 | ||||
| -rw-r--r-- | src/link/MachO/ZigObject.zig | 37 | ||||
| -rw-r--r-- | src/link/MachO/file.zig | 116 | ||||
| -rw-r--r-- | src/link/MachO/relocatable.zig | 294 |
6 files changed, 761 insertions, 189 deletions
diff --git a/src/link/MachO.zig b/src/link/MachO.zig index c5fe26bc57..84de97f4e9 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -379,10 +379,6 @@ pub fn deinit(self: *MachO) void { } pub fn flush(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node) link.File.FlushError!void { - // TODO: I think this is just a temp and can be removed once we can emit static archives - if (self.base.isStaticLib() and build_options.have_llvm) { - return self.base.linkAsArchive(arena, prog_node); - } try self.flushModule(arena, prog_node); } @@ -395,8 +391,6 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node if (self.llvm_object) |llvm_object| { try self.base.emitLlvmObject(arena, llvm_object, prog_node); - // TODO: I think this is just a temp and can be removed once we can emit static archives - if (self.base.isStaticLib() and build_options.have_llvm) return; } var sub_prog_node = prog_node.start("MachO Flush", 0); @@ -417,8 +411,8 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node if (comp.verbose_link) try self.dumpArgv(comp); if (self.getZigObject()) |zo| try zo.flushModule(self); - if (self.base.isStaticLib()) return self.flushStaticLib(comp, module_obj_path); - if (self.base.isObject()) return relocatable.flush(self, comp, module_obj_path); + if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp, module_obj_path); + if (self.base.isObject()) return relocatable.flushObject(self, comp, module_obj_path); var positionals = std.ArrayList(Compilation.LinkObject).init(gpa); defer positionals.deinit(); @@ -577,7 +571,7 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node }, }; - try self.markImportsAndExports(); + self.markImportsAndExports(); self.deadStripDylibs(); for (self.dylibs.items, 1..) |index, ord| { @@ -606,7 +600,9 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node self.allocateSyntheticSymbols(); try self.allocateLinkeditSegment(); - state_log.debug("{}", .{self.dumpState()}); + if (build_options.enable_logging) { + state_log.debug("{}", .{self.dumpState()}); + } try self.initDyldInfoSections(); @@ -892,16 +888,6 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { Compilation.dump_argv(argv.items); } -fn flushStaticLib(self: *MachO, comp: *Compilation, module_obj_path: ?[]const u8) link.File.FlushError!void { - _ = comp; - _ = module_obj_path; - - var err = try self.addErrorWithNotes(0); - try err.addMsg(self, "TODO implement flushStaticLib", .{}); - - return error.FlushFailure; -} - pub fn resolveLibSystem( self: *MachO, arena: Allocator, @@ -934,7 +920,7 @@ pub fn resolveLibSystem( }); } -const ParseError = error{ +pub const ParseError = error{ MalformedObject, MalformedArchive, MalformedDylib, @@ -1011,7 +997,7 @@ fn parseObject(self: *MachO, path: []const u8) ParseError!void { try object.parse(self); } -fn parseFatLibrary(self: *MachO, path: []const u8) !fat.Arch { +pub fn parseFatLibrary(self: *MachO, path: []const u8) !fat.Arch { var buffer: [2]fat.Arch = undefined; const fat_archs = try fat.parseArchs(path, &buffer); const cpu_arch = self.getTarget().cpu.arch; @@ -1525,46 +1511,11 @@ fn createObjcSections(self: *MachO) !void { } fn claimUnresolved(self: *MachO) error{OutOfMemory}!void { - const gpa = self.base.comp.gpa; - - var objects = try std.ArrayList(File.Index).initCapacity(gpa, self.objects.items.len + 1); - defer objects.deinit(); - if (self.getZigObject()) |zo| objects.appendAssumeCapacity(zo.index); - objects.appendSliceAssumeCapacity(self.objects.items); - - for (objects.items) |index| { - const file = self.getFile(index).?; - - for (file.getSymbols(), 0..) |sym_index, i| { - const nlist_idx = @as(Symbol.Index, @intCast(i)); - const nlist = switch (file) { - .object => |x| x.symtab.items(.nlist)[nlist_idx], - .zig_object => |x| x.symtab.items(.nlist)[nlist_idx], - else => unreachable, - }; - if (!nlist.ext()) continue; - if (!nlist.undf()) continue; - - const sym = self.getSymbol(sym_index); - if (sym.getFile(self) != null) continue; - - const is_import = switch (self.undefined_treatment) { - .@"error" => false, - .warn, .suppress => nlist.weakRef(), - .dynamic_lookup => true, - }; - if (is_import) { - sym.value = 0; - sym.atom = 0; - sym.nlist_idx = 0; - sym.file = self.internal_object.?; - sym.flags.weak = false; - sym.flags.weak_ref = nlist.weakRef(); - sym.flags.import = is_import; - sym.visibility = .global; - try self.getInternalObject().?.symbols.append(self.base.comp.gpa, sym_index); - } - } + if (self.getZigObject()) |zo| { + try zo.asFile().claimUnresolved(self); + } + for (self.objects.items) |index| { + try self.getFile(index).?.claimUnresolved(self); } } @@ -1590,26 +1541,12 @@ fn checkDuplicates(self: *MachO) !void { try self.reportDuplicates(dupes); } -fn markImportsAndExports(self: *MachO) error{OutOfMemory}!void { - const gpa = self.base.comp.gpa; - var objects = try std.ArrayList(File.Index).initCapacity(gpa, self.objects.items.len + 1); - defer objects.deinit(); - if (self.getZigObject()) |zo| objects.appendAssumeCapacity(zo.index); - objects.appendSliceAssumeCapacity(self.objects.items); - - for (objects.items) |index| { - for (self.getFile(index).?.getSymbols()) |sym_index| { - const sym = self.getSymbol(sym_index); - const file = sym.getFile(self) orelse continue; - if (sym.visibility != .global) continue; - if (file == .dylib and !sym.flags.abs) { - sym.flags.import = true; - continue; - } - if (file.getIndex() == index) { - sym.flags.@"export" = true; - } - } +fn markImportsAndExports(self: *MachO) void { + if (self.getZigObject()) |zo| { + zo.asFile().markImportsExports(self); + } + for (self.objects.items) |index| { + self.getFile(index).?.markImportsExports(self); } for (self.undefined_symbols.items) |index| { diff --git a/src/link/MachO/Archive.zig b/src/link/MachO/Archive.zig index fd87b8e260..aba31d1742 100644 --- a/src/link/MachO/Archive.zig +++ b/src/link/MachO/Archive.zig @@ -1,63 +1,5 @@ objects: std.ArrayListUnmanaged(Object) = .{}, -// Archive files start with the ARMAG identifying string. Then follows a -// `struct ar_hdr', and as many bytes of member file data as its `ar_size' -// member indicates, for each member file. -/// String that begins an archive file. -pub const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n"; -/// Size of that string. -pub const SARMAG: u4 = 8; - -/// String in ar_fmag at the end of each header. -const ARFMAG: *const [2:0]u8 = "`\n"; - -const ar_hdr = extern struct { - /// Member file name, sometimes / terminated. - ar_name: [16]u8, - - /// File date, decimal seconds since Epoch. - ar_date: [12]u8, - - /// User ID, in ASCII format. - ar_uid: [6]u8, - - /// Group ID, in ASCII format. - ar_gid: [6]u8, - - /// File mode, in ASCII octal. - ar_mode: [8]u8, - - /// File size, in ASCII decimal. - ar_size: [10]u8, - - /// Always contains ARFMAG. - ar_fmag: [2]u8, - - fn date(self: ar_hdr) !u64 { - const value = mem.trimRight(u8, &self.ar_date, &[_]u8{@as(u8, 0x20)}); - return std.fmt.parseInt(u64, value, 10); - } - - fn size(self: ar_hdr) !u32 { - const value = mem.trimRight(u8, &self.ar_size, &[_]u8{@as(u8, 0x20)}); - return std.fmt.parseInt(u32, value, 10); - } - - fn name(self: *const ar_hdr) ?[]const u8 { - const value = &self.ar_name; - if (mem.startsWith(u8, value, "#1/")) return null; - const sentinel = mem.indexOfScalar(u8, value, '/') orelse value.len; - return value[0..sentinel]; - } - - fn nameLength(self: ar_hdr) !?u32 { - const value = &self.ar_name; - if (!mem.startsWith(u8, value, "#1/")) return null; - const trimmed = mem.trimRight(u8, self.ar_name["#1/".len..], &[_]u8{0x20}); - return try std.fmt.parseInt(u32, trimmed, 10); - } -}; - pub fn isArchive(path: []const u8, fat_arch: ?fat.Arch) !bool { const file = try std.fs.cwd().openFile(path, .{}); defer file.close(); @@ -85,9 +27,9 @@ pub fn parse(self: *Archive, macho_file: *MachO, path: []const u8, handle_index: try handle.seekTo(offset); const reader = handle.reader(); - _ = try reader.readBytesNoEof(Archive.SARMAG); + _ = try reader.readBytesNoEof(SARMAG); - var pos: usize = Archive.SARMAG; + var pos: usize = SARMAG; while (true) { if (pos >= size) break; if (!mem.isAligned(pos, 2)) { @@ -123,7 +65,10 @@ pub fn parse(self: *Archive, macho_file: *MachO, path: []const u8, handle_index: pos += hdr_size; } - if (mem.eql(u8, name, "__.SYMDEF") or mem.eql(u8, name, "__.SYMDEF SORTED")) continue; + if (mem.eql(u8, name, SYMDEF) or + mem.eql(u8, name, SYMDEF64) or + mem.eql(u8, name, SYMDEF_SORTED) or + mem.eql(u8, name, SYMDEF64_SORTED)) continue; const object = Object{ .archive = .{ @@ -143,7 +88,229 @@ pub fn parse(self: *Archive, macho_file: *MachO, path: []const u8, handle_index: } } +pub fn writeHeader( + object_name: []const u8, + object_size: usize, + format: Format, + writer: anytype, +) !void { + var hdr: ar_hdr = .{ + .ar_name = undefined, + .ar_date = undefined, + .ar_uid = undefined, + .ar_gid = undefined, + .ar_mode = undefined, + .ar_size = undefined, + .ar_fmag = undefined, + }; + @memset(mem.asBytes(&hdr), 0x20); + inline for (@typeInfo(ar_hdr).Struct.fields) |field| { + var stream = std.io.fixedBufferStream(&@field(hdr, field.name)); + stream.writer().print("0", .{}) catch unreachable; + } + @memcpy(&hdr.ar_fmag, ARFMAG); + + const object_name_len = mem.alignForward(usize, object_name.len + 1, ptrWidth(format)); + const total_object_size = object_size + object_name_len; + + { + var stream = std.io.fixedBufferStream(&hdr.ar_name); + stream.writer().print("#1/{d}", .{object_name_len}) catch unreachable; + } + { + var stream = std.io.fixedBufferStream(&hdr.ar_size); + stream.writer().print("{d}", .{total_object_size}) catch unreachable; + } + + try writer.writeAll(mem.asBytes(&hdr)); + try writer.print("{s}\x00", .{object_name}); + + const padding = object_name_len - object_name.len - 1; + if (padding > 0) { + try writer.writeByteNTimes(0, padding); + } +} + +// Archive files start with the ARMAG identifying string. Then follows a +// `struct ar_hdr', and as many bytes of member file data as its `ar_size' +// member indicates, for each member file. +/// String that begins an archive file. +pub const ARMAG: *const [SARMAG:0]u8 = "!<arch>\n"; +/// Size of that string. +pub const SARMAG: u4 = 8; + +/// String in ar_fmag at the end of each header. +const ARFMAG: *const [2:0]u8 = "`\n"; + +pub const SYMDEF = "__.SYMDEF"; +pub const SYMDEF64 = "__.SYMDEF_64"; +pub const SYMDEF_SORTED = "__.SYMDEF SORTED"; +pub const SYMDEF64_SORTED = "__.SYMDEF_64 SORTED"; + +pub const ar_hdr = extern struct { + /// Member file name, sometimes / terminated. + ar_name: [16]u8, + + /// File date, decimal seconds since Epoch. + ar_date: [12]u8, + + /// User ID, in ASCII format. + ar_uid: [6]u8, + + /// Group ID, in ASCII format. + ar_gid: [6]u8, + + /// File mode, in ASCII octal. + ar_mode: [8]u8, + + /// File size, in ASCII decimal. + ar_size: [10]u8, + + /// Always contains ARFMAG. + ar_fmag: [2]u8, + + fn date(self: ar_hdr) !u64 { + const value = mem.trimRight(u8, &self.ar_date, &[_]u8{@as(u8, 0x20)}); + return std.fmt.parseInt(u64, value, 10); + } + + fn size(self: ar_hdr) !u32 { + const value = mem.trimRight(u8, &self.ar_size, &[_]u8{@as(u8, 0x20)}); + return std.fmt.parseInt(u32, value, 10); + } + + fn name(self: *const ar_hdr) ?[]const u8 { + const value = &self.ar_name; + if (mem.startsWith(u8, value, "#1/")) return null; + const sentinel = mem.indexOfScalar(u8, value, '/') orelse value.len; + return value[0..sentinel]; + } + + fn nameLength(self: ar_hdr) !?u32 { + const value = &self.ar_name; + if (!mem.startsWith(u8, value, "#1/")) return null; + const trimmed = mem.trimRight(u8, self.ar_name["#1/".len..], &[_]u8{0x20}); + return try std.fmt.parseInt(u32, trimmed, 10); + } +}; + +pub const ArSymtab = struct { + entries: std.ArrayListUnmanaged(Entry) = .{}, + strtab: StringTable = .{}, + + pub fn deinit(ar: *ArSymtab, allocator: Allocator) void { + ar.entries.deinit(allocator); + ar.strtab.deinit(allocator); + } + + pub fn sort(ar: *ArSymtab) void { + mem.sort(Entry, ar.entries.items, {}, Entry.lessThan); + } + + pub fn size(ar: ArSymtab, format: Format) usize { + const ptr_width = ptrWidth(format); + return ptr_width + ar.entries.items.len * 2 * ptr_width + ptr_width + mem.alignForward(usize, ar.strtab.buffer.items.len, ptr_width); + } + + pub fn write(ar: ArSymtab, format: Format, macho_file: *MachO, writer: anytype) !void { + const ptr_width = ptrWidth(format); + // Header + try writeHeader(SYMDEF, ar.size(format), format, writer); + // Symtab size + try writeInt(format, ar.entries.items.len * 2 * ptr_width, writer); + // Symtab entries + for (ar.entries.items) |entry| { + const file_off = switch (macho_file.getFile(entry.file).?) { + .zig_object => |x| x.output_ar_state.file_off, + .object => |x| x.output_ar_state.file_off, + else => unreachable, + }; + // Name offset + try writeInt(format, entry.off, writer); + // File offset + try writeInt(format, file_off, writer); + } + // Strtab size + const strtab_size = mem.alignForward(usize, ar.strtab.buffer.items.len, ptr_width); + const padding = strtab_size - ar.strtab.buffer.items.len; + try writeInt(format, strtab_size, writer); + // Strtab + try writer.writeAll(ar.strtab.buffer.items); + if (padding > 0) { + try writer.writeByteNTimes(0, padding); + } + } + + const FormatContext = struct { + ar: ArSymtab, + macho_file: *MachO, + }; + + pub fn fmt(ar: ArSymtab, macho_file: *MachO) std.fmt.Formatter(format2) { + return .{ .data = .{ .ar = ar, .macho_file = macho_file } }; + } + + fn format2( + ctx: FormatContext, + comptime unused_fmt_string: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = unused_fmt_string; + _ = options; + const ar = ctx.ar; + const macho_file = ctx.macho_file; + for (ar.entries.items, 0..) |entry, i| { + const name = ar.strtab.getAssumeExists(entry.off); + const file = macho_file.getFile(entry.file).?; + try writer.print(" {d}: {s} in file({d})({})\n", .{ i, name, entry.file, file.fmtPath() }); + } + } + + const Entry = struct { + /// Symbol name offset + off: u32, + /// Exporting file + file: File.Index, + + pub fn lessThan(ctx: void, lhs: Entry, rhs: Entry) bool { + _ = ctx; + if (lhs.off == rhs.off) return lhs.file < rhs.file; + return lhs.off < rhs.off; + } + }; +}; + +pub const Format = enum { + p32, + p64, +}; + +pub fn ptrWidth(format: Format) usize { + return switch (format) { + .p32 => @as(usize, 4), + .p64 => 8, + }; +} + +pub fn writeInt(format: Format, value: u64, writer: anytype) !void { + switch (format) { + .p32 => try writer.writeInt(u32, std.math.cast(u32, value) orelse return error.Overflow, .little), + .p64 => try writer.writeInt(u64, value, .little), + } +} + +pub const ArState = struct { + /// File offset of the ar_hdr describing the contributing + /// object in the archive. + file_off: u64 = 0, + + /// Total size of the contributing object (excludes ar_hdr and long name with padding). + size: u64 = 0, +}; + const fat = @import("fat.zig"); +const link = @import("../../link.zig"); const log = std.log.scoped(.link); const macho = std.macho; const mem = std.mem; @@ -154,3 +321,4 @@ const Archive = @This(); const File = @import("file.zig").File; const MachO = @import("../MachO.zig"); const Object = @import("Object.zig"); +const StringTable = @import("../StringTable.zig"); diff --git a/src/link/MachO/Object.zig b/src/link/MachO/Object.zig index 6a987cb02d..ad7ba1c240 100644 --- a/src/link/MachO/Object.zig +++ b/src/link/MachO/Object.zig @@ -1,4 +1,4 @@ -archive: ?Archive = null, +archive: ?InArchive = null, path: []const u8, file_handle: File.HandleIndex, mtime: u64, @@ -29,8 +29,9 @@ hidden: bool = false, dynamic_relocs: MachO.DynamicRelocs = .{}, output_symtab_ctx: MachO.SymtabCtx = .{}, +output_ar_state: Archive.ArState = .{}, -const Archive = struct { +const InArchive = struct { path: []const u8, offset: u64, }; @@ -1232,6 +1233,112 @@ fn addSection(self: *Object, allocator: Allocator, segname: []const u8, sectname return n_sect; } +pub fn parseAr(self: *Object, macho_file: *MachO) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = macho_file.base.comp.gpa; + const offset = if (self.archive) |ar| ar.offset else 0; + const handle = macho_file.getFileHandle(self.file_handle); + + var header_buffer: [@sizeOf(macho.mach_header_64)]u8 = undefined; + { + const amt = try handle.preadAll(&header_buffer, offset); + if (amt != @sizeOf(macho.mach_header_64)) return error.InputOutput; + } + self.header = @as(*align(1) const macho.mach_header_64, @ptrCast(&header_buffer)).*; + + const this_cpu_arch: std.Target.Cpu.Arch = switch (self.header.?.cputype) { + macho.CPU_TYPE_ARM64 => .aarch64, + macho.CPU_TYPE_X86_64 => .x86_64, + else => |x| { + try macho_file.reportParseError2(self.index, "unknown cpu architecture: {d}", .{x}); + return error.InvalidCpuArch; + }, + }; + if (macho_file.getTarget().cpu.arch != this_cpu_arch) { + try macho_file.reportParseError2(self.index, "invalid cpu architecture: {s}", .{@tagName(this_cpu_arch)}); + return error.InvalidCpuArch; + } + + const lc_buffer = try gpa.alloc(u8, self.header.?.sizeofcmds); + defer gpa.free(lc_buffer); + { + const amt = try handle.preadAll(lc_buffer, offset + @sizeOf(macho.mach_header_64)); + if (amt != self.header.?.sizeofcmds) return error.InputOutput; + } + + var it = LoadCommandIterator{ + .ncmds = self.header.?.ncmds, + .buffer = lc_buffer, + }; + while (it.next()) |lc| switch (lc.cmd()) { + .SYMTAB => { + const cmd = lc.cast(macho.symtab_command).?; + try self.strtab.resize(gpa, cmd.strsize); + { + const amt = try handle.preadAll(self.strtab.items, cmd.stroff + offset); + if (amt != self.strtab.items.len) return error.InputOutput; + } + + const symtab_buffer = try gpa.alloc(u8, cmd.nsyms * @sizeOf(macho.nlist_64)); + defer gpa.free(symtab_buffer); + { + const amt = try handle.preadAll(symtab_buffer, cmd.symoff + offset); + if (amt != symtab_buffer.len) return error.InputOutput; + } + const symtab = @as([*]align(1) const macho.nlist_64, @ptrCast(symtab_buffer.ptr))[0..cmd.nsyms]; + try self.symtab.ensureUnusedCapacity(gpa, symtab.len); + for (symtab) |nlist| { + self.symtab.appendAssumeCapacity(.{ + .nlist = nlist, + .atom = 0, + .size = 0, + }); + } + }, + .BUILD_VERSION, + .VERSION_MIN_MACOSX, + .VERSION_MIN_IPHONEOS, + .VERSION_MIN_TVOS, + .VERSION_MIN_WATCHOS, + => if (self.platform == null) { + self.platform = MachO.Platform.fromLoadCommand(lc); + }, + else => {}, + }; +} + +pub fn updateArSymtab(self: Object, ar_symtab: *Archive.ArSymtab, macho_file: *MachO) error{OutOfMemory}!void { + const gpa = macho_file.base.comp.gpa; + for (self.symtab.items(.nlist)) |nlist| { + if (!nlist.ext() or (nlist.undf() and !nlist.tentative())) continue; + const off = try ar_symtab.strtab.insert(gpa, self.getString(nlist.n_strx)); + try ar_symtab.entries.append(gpa, .{ .off = off, .file = self.index }); + } +} + +pub fn updateArSize(self: *Object, macho_file: *MachO) !void { + const file = macho_file.getFileHandle(self.file_handle); + const size = (try file.stat()).size; + self.output_ar_state.size = size; +} + +pub fn writeAr(self: Object, ar_format: Archive.Format, macho_file: *MachO, writer: anytype) !void { + // Header + const size = std.math.cast(usize, self.output_ar_state.size) orelse return error.Overflow; + try Archive.writeHeader(self.path, size, ar_format, writer); + // Data + const file = macho_file.getFileHandle(self.file_handle); + // TODO try using copyRangeAll + const gpa = macho_file.base.comp.gpa; + const data = try gpa.alloc(u8, size); + defer gpa.free(data); + const amt = try file.preadAll(data, 0); + if (amt != size) return error.InputOutput; + try writer.writeAll(data); +} + pub fn calcSymtabSize(self: *Object, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); @@ -2241,6 +2348,7 @@ const trace = @import("../../tracy.zig").trace; const std = @import("std"); const Allocator = mem.Allocator; +const Archive = @import("Archive.zig"); const Atom = @import("Atom.zig"); const Cie = eh_frame.Cie; const DwarfInfo = @import("DwarfInfo.zig"); diff --git a/src/link/MachO/ZigObject.zig b/src/link/MachO/ZigObject.zig index b39905f259..ecd2470733 100644 --- a/src/link/MachO/ZigObject.zig +++ b/src/link/MachO/ZigObject.zig @@ -1,3 +1,4 @@ +data: std.ArrayListUnmanaged(u8) = .{}, /// Externally owned memory. path: []const u8, index: File.Index, @@ -47,6 +48,7 @@ relocs: RelocationTable = .{}, dynamic_relocs: MachO.DynamicRelocs = .{}, output_symtab_ctx: MachO.SymtabCtx = .{}, +output_ar_state: Archive.ArState = .{}, pub fn init(self: *ZigObject, macho_file: *MachO) !void { const comp = macho_file.base.comp; @@ -57,6 +59,7 @@ pub fn init(self: *ZigObject, macho_file: *MachO) !void { } pub fn deinit(self: *ZigObject, allocator: Allocator) void { + self.data.deinit(allocator); self.symtab.deinit(allocator); self.strtab.deinit(allocator); self.symbols.deinit(allocator); @@ -279,6 +282,40 @@ pub fn checkDuplicates(self: *ZigObject, dupes: anytype, macho_file: *MachO) !vo } } +/// This is just a temporary helper function that allows us to re-read what we wrote to file into a buffer. +/// We need this so that we can write to an archive. +/// TODO implement writing ZigObject data directly to a buffer instead. +pub fn readFileContents(self: *ZigObject, size: usize, macho_file: *MachO) !void { + const gpa = macho_file.base.comp.gpa; + try self.data.resize(gpa, size); + const amt = try macho_file.base.file.?.preadAll(self.data.items, 0); + if (amt != size) return error.InputOutput; +} + +pub fn updateArSymtab(self: ZigObject, ar_symtab: *Archive.ArSymtab, macho_file: *MachO) error{OutOfMemory}!void { + const gpa = macho_file.base.comp.gpa; + for (self.symbols.items) |sym_index| { + const sym = macho_file.getSymbol(sym_index); + const file = sym.getFile(macho_file).?; + assert(file.getIndex() == self.index); + if (!sym.flags.@"export") continue; + const off = try ar_symtab.strtab.insert(gpa, sym.getName(macho_file)); + try ar_symtab.entries.append(gpa, .{ .off = off, .file = self.index }); + } +} + +pub fn updateArSize(self: *ZigObject) void { + self.output_ar_state.size = self.data.items.len; +} + +pub fn writeAr(self: ZigObject, ar_format: Archive.Format, writer: anytype) !void { + // Header + const size = std.math.cast(usize, self.output_ar_state.size) orelse return error.Overflow; + try Archive.writeHeader(self.path, size, ar_format, writer); + // Data + try writer.writeAll(self.data.items); +} + pub fn scanRelocs(self: *ZigObject, macho_file: *MachO) !void { for (self.atoms.items) |atom_index| { const atom = macho_file.getAtom(atom_index) orelse continue; diff --git a/src/link/MachO/file.zig b/src/link/MachO/file.zig index 20f71d80fe..60c7a70f26 100644 --- a/src/link/MachO/file.zig +++ b/src/link/MachO/file.zig @@ -44,6 +44,97 @@ pub const File = union(enum) { } } + pub fn claimUnresolved(file: File, macho_file: *MachO) error{OutOfMemory}!void { + assert(file == .object or file == .zig_object); + + for (file.getSymbols(), 0..) |sym_index, i| { + const nlist_idx = @as(Symbol.Index, @intCast(i)); + const nlist = switch (file) { + .object => |x| x.symtab.items(.nlist)[nlist_idx], + .zig_object => |x| x.symtab.items(.nlist)[nlist_idx], + else => unreachable, + }; + if (!nlist.ext()) continue; + if (!nlist.undf()) continue; + + const sym = macho_file.getSymbol(sym_index); + if (sym.getFile(macho_file) != null) continue; + + const is_import = switch (macho_file.undefined_treatment) { + .@"error" => false, + .warn, .suppress => nlist.weakRef(), + .dynamic_lookup => true, + }; + if (is_import) { + sym.value = 0; + sym.atom = 0; + sym.nlist_idx = 0; + sym.file = macho_file.internal_object.?; + sym.flags.weak = false; + sym.flags.weak_ref = nlist.weakRef(); + sym.flags.import = is_import; + sym.visibility = .global; + try macho_file.getInternalObject().?.symbols.append(macho_file.base.comp.gpa, sym_index); + } + } + } + + pub fn claimUnresolvedRelocatable(file: File, macho_file: *MachO) void { + assert(file == .object or file == .zig_object); + + for (file.getSymbols(), 0..) |sym_index, i| { + const nlist_idx = @as(Symbol.Index, @intCast(i)); + const nlist = switch (file) { + .object => |x| x.symtab.items(.nlist)[nlist_idx], + .zig_object => |x| x.symtab.items(.nlist)[nlist_idx], + else => unreachable, + }; + if (!nlist.ext()) continue; + if (!nlist.undf()) continue; + + const sym = macho_file.getSymbol(sym_index); + if (sym.getFile(macho_file) != null) continue; + + sym.value = 0; + sym.atom = 0; + sym.nlist_idx = nlist_idx; + sym.file = file.getIndex(); + sym.flags.weak_ref = nlist.weakRef(); + sym.flags.import = true; + sym.visibility = .global; + } + } + + pub fn markImportsExports(file: File, macho_file: *MachO) void { + assert(file == .object or file == .zig_object); + + for (file.getSymbols()) |sym_index| { + const sym = macho_file.getSymbol(sym_index); + const other_file = sym.getFile(macho_file) orelse continue; + if (sym.visibility != .global) continue; + if (other_file == .dylib and !sym.flags.abs) { + sym.flags.import = true; + continue; + } + if (other_file.getIndex() == file.getIndex()) { + sym.flags.@"export" = true; + } + } + } + + pub fn markExportsRelocatable(file: File, macho_file: *MachO) void { + assert(file == .object or file == .zig_object); + + for (file.getSymbols()) |sym_index| { + const sym = macho_file.getSymbol(sym_index); + const other_file = sym.getFile(macho_file) orelse continue; + if (sym.visibility != .global) continue; + if (other_file.getIndex() == file.getIndex()) { + sym.flags.@"export" = true; + } + } + } + /// Encodes symbol rank so that the following ordering applies: /// * strong in object /// * weak in object @@ -84,6 +175,29 @@ pub const File = union(enum) { }; } + pub fn updateArSymtab(file: File, ar_symtab: *Archive.ArSymtab, macho_file: *MachO) error{OutOfMemory}!void { + return switch (file) { + .dylib, .internal => unreachable, + inline else => |x| x.updateArSymtab(ar_symtab, macho_file), + }; + } + + pub fn updateArSize(file: File, macho_file: *MachO) !void { + return switch (file) { + .dylib, .internal => unreachable, + .zig_object => |x| x.updateArSize(), + .object => |x| x.updateArSize(macho_file), + }; + } + + pub fn writeAr(file: File, ar_format: Archive.Format, macho_file: *MachO, writer: anytype) !void { + return switch (file) { + .dylib, .internal => unreachable, + .zig_object => |x| x.writeAr(ar_format, writer), + .object => |x| x.writeAr(ar_format, macho_file, writer), + }; + } + pub fn calcSymtabSize(file: File, macho_file: *MachO) !void { return switch (file) { inline else => |x| x.calcSymtabSize(macho_file), @@ -110,10 +224,12 @@ pub const File = union(enum) { pub const HandleIndex = Index; }; +const assert = std.debug.assert; const macho = std.macho; const std = @import("std"); const Allocator = std.mem.Allocator; +const Archive = @import("Archive.zig"); const Atom = @import("Atom.zig"); const InternalObject = @import("InternalObject.zig"); const MachO = @import("../MachO.zig"); diff --git a/src/link/MachO/relocatable.zig b/src/link/MachO/relocatable.zig index 5e9eb9f823..8f5bf97696 100644 --- a/src/link/MachO/relocatable.zig +++ b/src/link/MachO/relocatable.zig @@ -1,4 +1,4 @@ -pub fn flush(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]const u8) link.File.FlushError!void { +pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]const u8) link.File.FlushError!void { const gpa = macho_file.base.comp.gpa; var positionals = std.ArrayList(Compilation.LinkObject).init(gpa); @@ -46,8 +46,8 @@ pub fn flush(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]const u try macho_file.addUndefinedGlobals(); try macho_file.resolveSymbols(); - try markExports(macho_file); - try claimUnresolved(macho_file); + markExports(macho_file); + claimUnresolved(macho_file); try initOutputSections(macho_file); try macho_file.sortSections(); try macho_file.addAtomsToSections(); @@ -64,7 +64,9 @@ pub fn flush(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]const u }; off = allocateSectionsRelocs(macho_file, off); - state_log.debug("{}", .{macho_file.dumpState()}); + if (build_options.enable_logging) { + state_log.debug("{}", .{macho_file.dumpState()}); + } try macho_file.calcSymtabSize(); try writeAtoms(macho_file); @@ -86,54 +88,254 @@ pub fn flush(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]const u try writeHeader(macho_file, ncmds, sizeofcmds); } -fn markExports(macho_file: *MachO) error{OutOfMemory}!void { - var objects = try std.ArrayList(File.Index).initCapacity(macho_file.base.comp.gpa, macho_file.objects.items.len + 1); - defer objects.deinit(); - if (macho_file.getZigObject()) |zo| objects.appendAssumeCapacity(zo.index); - objects.appendSliceAssumeCapacity(macho_file.objects.items); - - for (objects.items) |index| { - for (macho_file.getFile(index).?.getSymbols()) |sym_index| { - const sym = macho_file.getSymbol(sym_index); - const file = sym.getFile(macho_file) orelse continue; - if (sym.visibility != .global) continue; - if (file.getIndex() == index) { - sym.flags.@"export" = true; - } +pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]const u8) link.File.FlushError!void { + const gpa = comp.gpa; + + var positionals = std.ArrayList(Compilation.LinkObject).init(gpa); + defer positionals.deinit(); + + try positionals.ensureUnusedCapacity(comp.objects.len); + positionals.appendSliceAssumeCapacity(comp.objects); + + for (comp.c_object_table.keys()) |key| { + try positionals.append(.{ .path = key.status.success.object_path }); + } + + if (module_obj_path) |path| try positionals.append(.{ .path = path }); + + for (positionals.items) |obj| { + parsePositional(macho_file, obj.path) catch |err| switch (err) { + error.MalformedObject, + error.MalformedArchive, + error.InvalidCpuArch, + error.InvalidTarget, + => continue, // already reported + error.UnknownFileType => try macho_file.reportParseError(obj.path, "unknown file type for an object file", .{}), + else => |e| try macho_file.reportParseError( + obj.path, + "unexpected error: parsing input file failed with error {s}", + .{@errorName(e)}, + ), + }; + } + + if (comp.link_errors.items.len > 0) return error.FlushFailure; + + // First, we flush relocatable object file generated with our backends. + if (macho_file.getZigObject()) |zo| { + zo.resolveSymbols(macho_file); + zo.asFile().markExportsRelocatable(macho_file); + zo.asFile().claimUnresolvedRelocatable(macho_file); + try macho_file.sortSections(); + try macho_file.addAtomsToSections(); + try calcSectionSizes(macho_file); + try createSegment(macho_file); + try allocateSections(macho_file); + allocateSegment(macho_file); + + var off = off: { + const seg = macho_file.segments.items[0]; + const off = math.cast(u32, seg.fileoff + seg.filesize) orelse return error.Overflow; + break :off mem.alignForward(u32, off, @alignOf(macho.relocation_info)); + }; + off = allocateSectionsRelocs(macho_file, off); + + if (build_options.enable_logging) { + state_log.debug("{}", .{macho_file.dumpState()}); } + + try macho_file.calcSymtabSize(); + try writeAtoms(macho_file); + + off = mem.alignForward(u32, off, @alignOf(u64)); + off = try macho_file.writeDataInCode(0, off); + off = mem.alignForward(u32, off, @alignOf(u64)); + off = try macho_file.writeSymtab(off); + off = mem.alignForward(u32, off, @alignOf(u64)); + off = try macho_file.writeStrtab(off); + + // In order to please Apple ld (and possibly other MachO linkers in the wild), + // we will now sanitize segment names of Zig-specific segments. + sanitizeZigSections(macho_file); + + const ncmds, const sizeofcmds = try writeLoadCommands(macho_file); + try writeHeader(macho_file, ncmds, sizeofcmds); + + // TODO we can avoid reading in the file contents we just wrote if we give the linker + // ability to write directly to a buffer. + try zo.readFileContents(off, macho_file); } -} -fn claimUnresolved(macho_file: *MachO) error{OutOfMemory}!void { - var objects = try std.ArrayList(File.Index).initCapacity(macho_file.base.comp.gpa, macho_file.objects.items.len + 1); - defer objects.deinit(); - if (macho_file.getZigObject()) |zo| objects.appendAssumeCapacity(zo.index); - objects.appendSliceAssumeCapacity(macho_file.objects.items); + var files = std.ArrayList(File.Index).init(gpa); + defer files.deinit(); + try files.ensureTotalCapacityPrecise(macho_file.objects.items.len + 1); + if (macho_file.getZigObject()) |zo| files.appendAssumeCapacity(zo.index); + for (macho_file.objects.items) |index| files.appendAssumeCapacity(index); + + const format: Archive.Format = .p32; + const ptr_width = Archive.ptrWidth(format); - for (objects.items) |index| { - const file = macho_file.getFile(index).?; + // Update ar symtab from parsed objects + var ar_symtab: Archive.ArSymtab = .{}; + defer ar_symtab.deinit(gpa); + + for (files.items) |index| { + try macho_file.getFile(index).?.updateArSymtab(&ar_symtab, macho_file); + } + + ar_symtab.sort(); + + // Update sizes of contributing objects + for (files.items) |index| { + try macho_file.getFile(index).?.updateArSize(macho_file); + } - for (file.getSymbols(), 0..) |sym_index, i| { - const nlist_idx = @as(Symbol.Index, @intCast(i)); - const nlist = switch (file) { - .object => |x| x.symtab.items(.nlist)[nlist_idx], - .zig_object => |x| x.symtab.items(.nlist)[nlist_idx], + // Update file offsets of contributing objects + const total_size: usize = blk: { + var pos: usize = Archive.SARMAG; + pos += @sizeOf(Archive.ar_hdr) + Archive.SYMDEF.len + 1; + pos = mem.alignForward(usize, pos, ptr_width); + pos += ar_symtab.size(format); + + for (files.items) |index| { + const file = macho_file.getFile(index).?; + const state = switch (file) { + .zig_object => |x| &x.output_ar_state, + .object => |x| &x.output_ar_state, else => unreachable, }; - if (!nlist.ext()) continue; - if (!nlist.undf()) continue; - - const sym = macho_file.getSymbol(sym_index); - if (sym.getFile(macho_file) != null) continue; - - sym.value = 0; - sym.atom = 0; - sym.nlist_idx = nlist_idx; - sym.file = index; - sym.flags.weak_ref = nlist.weakRef(); - sym.flags.import = true; - sym.visibility = .global; + const path = switch (file) { + .zig_object => |x| x.path, + .object => |x| x.path, + else => unreachable, + }; + pos = mem.alignForward(usize, pos, ptr_width); + state.file_off = pos; + pos += @sizeOf(Archive.ar_hdr) + path.len + 1; + pos = mem.alignForward(usize, pos, ptr_width); + pos += math.cast(usize, state.size) orelse return error.Overflow; + } + + break :blk pos; + }; + + if (build_options.enable_logging) { + state_log.debug("ar_symtab\n{}\n", .{ar_symtab.fmt(macho_file)}); + } + + var buffer = std.ArrayList(u8).init(gpa); + defer buffer.deinit(); + try buffer.ensureTotalCapacityPrecise(total_size); + const writer = buffer.writer(); + + // Write magic + try writer.writeAll(Archive.ARMAG); + + // Write symtab + try ar_symtab.write(format, macho_file, writer); + + // Write object files + for (files.items) |index| { + const aligned = mem.alignForward(usize, buffer.items.len, ptr_width); + const padding = aligned - buffer.items.len; + if (padding > 0) { + try writer.writeByteNTimes(0, padding); } + try macho_file.getFile(index).?.writeAr(format, macho_file, writer); + } + + assert(buffer.items.len == total_size); + + try macho_file.base.file.?.setEndPos(total_size); + try macho_file.base.file.?.pwriteAll(buffer.items, 0); + + if (comp.link_errors.items.len > 0) return error.FlushFailure; +} + +fn parsePositional(macho_file: *MachO, path: []const u8) MachO.ParseError!void { + const tracy = trace(@src()); + defer tracy.end(); + if (try Object.isObject(path)) { + try parseObject(macho_file, path); + } else if (try fat.isFatLibrary(path)) { + const fat_arch = try macho_file.parseFatLibrary(path); + if (try Archive.isArchive(path, fat_arch)) { + try parseArchive(macho_file, path, fat_arch); + } else return error.UnknownFileType; + } else if (try Archive.isArchive(path, null)) { + try parseArchive(macho_file, path, null); + } else return error.UnknownFileType; +} + +fn parseObject(macho_file: *MachO, path: []const u8) MachO.ParseError!void { + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = macho_file.base.comp.gpa; + const file = try std.fs.cwd().openFile(path, .{}); + errdefer file.close(); + const handle = try macho_file.addFileHandle(file); + const mtime: u64 = mtime: { + const stat = file.stat() catch break :mtime 0; + break :mtime @as(u64, @intCast(@divFloor(stat.mtime, 1_000_000_000))); + }; + const index = @as(File.Index, @intCast(try macho_file.files.addOne(gpa))); + macho_file.files.set(index, .{ .object = .{ + .path = try gpa.dupe(u8, path), + .file_handle = handle, + .mtime = mtime, + .index = index, + } }); + try macho_file.objects.append(gpa, index); + + const object = macho_file.getFile(index).?.object; + try object.parseAr(macho_file); +} + +fn parseArchive(macho_file: *MachO, path: []const u8, fat_arch: ?fat.Arch) MachO.ParseError!void { + const tracy = trace(@src()); + defer tracy.end(); + + const gpa = macho_file.base.comp.gpa; + + const file = try std.fs.cwd().openFile(path, .{}); + errdefer file.close(); + const handle = try macho_file.addFileHandle(file); + + var archive = Archive{}; + defer archive.deinit(gpa); + try archive.parse(macho_file, path, handle, fat_arch); + + var has_parse_error = false; + for (archive.objects.items) |extracted| { + const index = @as(File.Index, @intCast(try macho_file.files.addOne(gpa))); + macho_file.files.set(index, .{ .object = extracted }); + const object = &macho_file.files.items(.data)[index].object; + object.index = index; + object.parseAr(macho_file) catch |err| switch (err) { + error.InvalidCpuArch => has_parse_error = true, + else => |e| return e, + }; + try macho_file.objects.append(gpa, index); + } + if (has_parse_error) return error.MalformedArchive; +} + +fn markExports(macho_file: *MachO) void { + if (macho_file.getZigObject()) |zo| { + zo.asFile().markExportsRelocatable(macho_file); + } + for (macho_file.objects.items) |index| { + macho_file.getFile(index).?.markExportsRelocatable(macho_file); + } +} + +pub fn claimUnresolved(macho_file: *MachO) void { + if (macho_file.getZigObject()) |zo| { + zo.asFile().claimUnresolvedRelocatable(macho_file); + } + for (macho_file.objects.items) |index| { + macho_file.getFile(index).?.claimUnresolvedRelocatable(macho_file); } } @@ -609,7 +811,9 @@ fn writeHeader(macho_file: *MachO, ncmds: usize, sizeofcmds: usize) !void { } const assert = std.debug.assert; +const build_options = @import("build_options"); const eh_frame = @import("eh_frame.zig"); +const fat = @import("fat.zig"); const link = @import("../../link.zig"); const load_commands = @import("load_commands.zig"); const log = std.log.scoped(.link); @@ -620,8 +824,10 @@ const state_log = std.log.scoped(.link_state); const std = @import("std"); const trace = @import("../../tracy.zig").trace; +const Archive = @import("Archive.zig"); const Atom = @import("Atom.zig"); const Compilation = @import("../../Compilation.zig"); const File = @import("file.zig").File; const MachO = @import("../MachO.zig"); +const Object = @import("Object.zig"); const Symbol = @import("Symbol.zig"); |
