diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 6b5ed4c0fb..7789c563d1 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -606,7 +606,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(); diff --git a/src/link/MachO/Archive.zig b/src/link/MachO/Archive.zig index 0dbc145b87..2777eb8587 100644 --- a/src/link/MachO/Archive.zig +++ b/src/link/MachO/Archive.zig @@ -1,5 +1,136 @@ objects: std.ArrayListUnmanaged(Object) = .{}, +pub fn isArchive(path: []const u8, fat_arch: ?fat.Arch) !bool { + const file = try std.fs.cwd().openFile(path, .{}); + defer file.close(); + if (fat_arch) |arch| { + try file.seekTo(arch.offset); + } + const magic = file.reader().readBytesNoEof(SARMAG) catch return false; + if (!mem.eql(u8, &magic, ARMAG)) return false; + return true; +} + +pub fn deinit(self: *Archive, allocator: Allocator) void { + self.objects.deinit(allocator); +} + +pub fn parse(self: *Archive, macho_file: *MachO, path: []const u8, handle_index: File.HandleIndex, fat_arch: ?fat.Arch) !void { + const gpa = macho_file.base.comp.gpa; + + var arena = std.heap.ArenaAllocator.init(gpa); + defer arena.deinit(); + + const handle = macho_file.getFileHandle(handle_index); + const offset = if (fat_arch) |ar| ar.offset else 0; + const size = if (fat_arch) |ar| ar.size else (try handle.stat()).size; + try handle.seekTo(offset); + + const reader = handle.reader(); + _ = try reader.readBytesNoEof(SARMAG); + + var pos: usize = SARMAG; + while (true) { + if (pos >= size) break; + if (!mem.isAligned(pos, 2)) { + try handle.seekBy(1); + pos += 1; + } + + const hdr = try reader.readStruct(ar_hdr); + pos += @sizeOf(ar_hdr); + + if (!mem.eql(u8, &hdr.ar_fmag, ARFMAG)) { + try macho_file.reportParseError(path, "invalid header delimiter: expected '{s}', found '{s}'", .{ + std.fmt.fmtSliceEscapeLower(ARFMAG), std.fmt.fmtSliceEscapeLower(&hdr.ar_fmag), + }); + return error.MalformedArchive; + } + + var hdr_size = try hdr.size(); + const name = name: { + if (hdr.name()) |n| break :name n; + if (try hdr.nameLength()) |len| { + hdr_size -= len; + const buf = try arena.allocator().alloc(u8, len); + try reader.readNoEof(buf); + pos += len; + const actual_len = mem.indexOfScalar(u8, buf, @as(u8, 0)) orelse len; + break :name buf[0..actual_len]; + } + unreachable; + }; + defer { + _ = handle.seekBy(hdr_size) catch {}; + pos += hdr_size; + } + + 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 = .{ + .path = try gpa.dupe(u8, path), + .offset = offset + pos, + }, + .path = try gpa.dupe(u8, name), + .file_handle = handle_index, + .index = undefined, + .alive = false, + .mtime = hdr.date() catch 0, + }; + + log.debug("extracting object '{s}' from archive '{s}'", .{ object.path, path }); + + try self.objects.append(gpa, object); + } +} + +pub fn writeHeader( + object_name: []const u8, + object_size: u32, + 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(u32, object_name.len + 1, format.ptrWidth()); + 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. @@ -11,6 +142,11 @@ pub const SARMAG: u4 = 8; /// String in ar_fmag at the end of each header. const ARFMAG: *const [2:0]u8 = "`\n"; +const SYMDEF = "__.SYMDEF"; +const SYMDEF64 = "__.SYMDEF_64"; +const SYMDEF_SORTED = "__.SYMDEF SORTED"; +const SYMDEF64_SORTED = "__.SYMDEF_64 SORTED"; + const ar_hdr = extern struct { /// Member file name, sometimes / terminated. ar_name: [16]u8, @@ -58,90 +194,120 @@ const ar_hdr = extern struct { } }; -pub fn isArchive(path: []const u8, fat_arch: ?fat.Arch) !bool { - const file = try std.fs.cwd().openFile(path, .{}); - defer file.close(); - if (fat_arch) |arch| { - try file.seekTo(arch.offset); +pub const ArSymtab = struct { + entries: std.ArrayListUnmanaged(Entry) = .{}, + strtab: StringTable = .{}, + format: Format = .p32, + + pub fn deinit(ar: *ArSymtab, allocator: Allocator) void { + ar.entries.deinit(allocator); + ar.strtab.deinit(allocator); } - const magic = file.reader().readBytesNoEof(SARMAG) catch return false; - if (!mem.eql(u8, &magic, ARMAG)) return false; - return true; -} -pub fn deinit(self: *Archive, allocator: Allocator) void { - self.objects.deinit(allocator); -} - -pub fn parse(self: *Archive, macho_file: *MachO, path: []const u8, handle_index: File.HandleIndex, fat_arch: ?fat.Arch) !void { - const gpa = macho_file.base.comp.gpa; - - var arena = std.heap.ArenaAllocator.init(gpa); - defer arena.deinit(); - - const handle = macho_file.getFileHandle(handle_index); - const offset = if (fat_arch) |ar| ar.offset else 0; - const size = if (fat_arch) |ar| ar.size else (try handle.stat()).size; - try handle.seekTo(offset); - - const reader = handle.reader(); - _ = try reader.readBytesNoEof(Archive.SARMAG); - - var pos: usize = Archive.SARMAG; - while (true) { - if (pos >= size) break; - if (!mem.isAligned(pos, 2)) { - try handle.seekBy(1); - pos += 1; - } - - const hdr = try reader.readStruct(ar_hdr); - pos += @sizeOf(ar_hdr); - - if (!mem.eql(u8, &hdr.ar_fmag, ARFMAG)) { - try macho_file.reportParseError(path, "invalid header delimiter: expected '{s}', found '{s}'", .{ - std.fmt.fmtSliceEscapeLower(ARFMAG), std.fmt.fmtSliceEscapeLower(&hdr.ar_fmag), - }); - return error.MalformedArchive; - } - - var hdr_size = try hdr.size(); - const name = name: { - if (hdr.name()) |n| break :name n; - if (try hdr.nameLength()) |len| { - hdr_size -= len; - const buf = try arena.allocator().alloc(u8, len); - try reader.readNoEof(buf); - pos += len; - const actual_len = mem.indexOfScalar(u8, buf, @as(u8, 0)) orelse len; - break :name buf[0..actual_len]; - } - unreachable; - }; - defer { - _ = handle.seekBy(hdr_size) catch {}; - pos += hdr_size; - } - - if (mem.eql(u8, name, "__.SYMDEF") or mem.eql(u8, name, "__.SYMDEF SORTED")) continue; - - const object = Object{ - .archive = .{ - .path = try gpa.dupe(u8, path), - .offset = offset + pos, - }, - .path = try gpa.dupe(u8, name), - .file_handle = handle_index, - .index = undefined, - .alive = false, - .mtime = hdr.date() catch 0, - }; - - log.debug("extracting object '{s}' from archive '{s}'", .{ object.path, path }); - - try self.objects.append(gpa, object); + pub fn sort(ar: *ArSymtab) void { + mem.sort(Entry, ar.entries.items, {}, Entry.lessThan); } -} + + pub fn size(ar: ArSymtab) usize { + const ptr_width = ar.format.ptrWidth(); + 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, macho_file: *MachO, writer: anytype) !void { + // Header + try writeHeader(SYMDEF, ar.size()); + // Symtab size + try ar.writeInt(ar.entries.items.len * 2); + // 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 ar.writeInt(entry.off); + // File offset + try ar.writeInt(file_off); + } + // Strtab size + const strtab_size = mem.alignForward(u64, ar.strtab.buffer.items.len, ar.format.ptrWidth()); + const padding = strtab_size - ar.strtab.buffer.items.len; + try ar.writeInt(strtab_size); + // Strtab + try writer.writeAll(ar.strtab.buffer.items); + if (padding > 0) { + try writer.writeByteNTimes(0, padding); + } + } + + fn writeInt(ar: ArSymtab, value: u64, writer: anytype) !void { + switch (ar.format) { + .p32 => try writer.writeInt(u32, std.math.cast(u32, value) orelse return error.Overflow, .little), + .p64 => try writer.writeInt(u64, value, .little), + } + } + + 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; + } + }; +}; + +const Format = enum { + p32, + p64, + + fn ptrWidth(self: Format) usize { + return switch (self) { + .p32 => @as(usize, 4), + .p64 => 8, + }; + } +}; + +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"); @@ -155,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..7e91e04dd2 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,33 @@ fn addSection(self: *Object, allocator: Allocator, segname: []const u8, sectname return n_sect; } +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, macho_file: *MachO, writer: anytype) !void { + // Header + try Archive.writeHeader(self.path, self.output_ar_state.size, writer); + // Data + const file = macho_file.getFileHandle(self.file_handle); + // TODO try using copyRangeAll + const gpa = macho_file.base.comp.gpa; + const data = try file.readToEndAlloc(gpa, self.output_ar_state.size); + defer gpa.free(data); + try writer.writeAll(data); +} + pub fn calcSymtabSize(self: *Object, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); @@ -2241,6 +2269,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 bdcc658e75..65ae3788ad 100644 --- a/src/link/MachO/ZigObject.zig +++ b/src/link/MachO/ZigObject.zig @@ -48,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; @@ -297,6 +298,29 @@ pub fn readFileContents(self: *ZigObject, macho_file: *MachO) !void { 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, writer: anytype) !void { + // Header + try Archive.writeHeader(self.path, self.output_ar_state.size, 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 bfaa90fba5..d6e21c2f38 100644 --- a/src/link/MachO/file.zig +++ b/src/link/MachO/file.zig @@ -175,6 +175,13 @@ 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 calcSymtabSize(file: File, macho_file: *MachO) !void { return switch (file) { inline else => |x| x.calcSymtabSize(macho_file), @@ -206,6 +213,7 @@ 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 37ea17d751..f90988c32e 100644 --- a/src/link/MachO/relocatable.zig +++ b/src/link/MachO/relocatable.zig @@ -64,7 +64,9 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]c }; 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); @@ -111,6 +113,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ? // 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(); @@ -126,7 +129,9 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ? }; 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); @@ -150,6 +155,26 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ? try zo.readFileContents(macho_file); } + 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); + + // 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(); + + if (build_options.enable_logging) { + state_log.debug("ar_symtab\n{}\n", .{ar_symtab.fmt(macho_file)}); + } + var err = try macho_file.addErrorWithNotes(0); try err.addMsg(macho_file, "TODO implement flushStaticLib", .{}); @@ -646,6 +671,7 @@ 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 link = @import("../../link.zig"); const load_commands = @import("load_commands.zig"); @@ -657,6 +683,7 @@ 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;