authorgravatar for readcuttingt@gmail.comTom Read Cutting <readcuttingt@gmail.com> 2021-06-27 15:06:08+01:00
committergravatar for readcuttingt@gmail.comTom Read Cutting <readcuttingt@gmail.com> 2021-06-29 18:42:57+01:00
log186577225f2a026fea01b48c7bcca47f36efc95c
treeadad450658e16f6f7252764aa99c85f626cadb2e
parent6d47b4f39e8cdd95ead71b1efdbf67a737174e97

Add fat/universal archive support to zig ld

This is an extension of adding fat dylib support to zig ld, pulling out the functionality needed to support fat headers & offsets and applying it to zig archives. Co-authored-by: Jakub Konka <kubkon@jakubkonka.com>

3 files changed, 68 insertions(+), 53 deletions(-)

src/link/MachO/Archive.zig+10-1
...@@ -6,6 +6,7 @@ const fs = std.fs;...@@ -6,6 +6,7 @@ const fs = std.fs;
6const log = std.log.scoped(.archive);6const log = std.log.scoped(.archive);
7const macho = std.macho;7const macho = std.macho;
8const mem = std.mem;8const mem = std.mem;
9const fat = @import("fat.zig");
910
10const Allocator = mem.Allocator;11const Allocator = mem.Allocator;
11const Arch = std.Target.Cpu.Arch;12const Arch = std.Target.Cpu.Arch;
...@@ -19,6 +20,10 @@ file: ?fs.File = null,...@@ -19,6 +20,10 @@ file: ?fs.File = null,
19header: ?ar_hdr = null,20header: ?ar_hdr = null,
20name: ?[]const u8 = null,21name: ?[]const u8 = null,
2122
23// The actual contents we care about linking with will be embedded at
24// an offset within a file if we are linking against a fat lib
25library_offset: u64 = 0,
26
22/// Parsed table of contents.27/// Parsed table of contents.
23/// Each symbol name points to a list of all definition28/// Each symbol name points to a list of all definition
24/// sites within the current static archive.29/// sites within the current static archive.
...@@ -139,6 +144,10 @@ pub fn closeFile(self: Archive) void {...@@ -139,6 +144,10 @@ pub fn closeFile(self: Archive) void {
139}144}
140145
141pub fn parse(self: *Archive) !void {146pub fn parse(self: *Archive) !void {
147 self.library_offset = try fat.getLibraryOffset(self.file.?.reader(), self.arch.?);
148
149 try self.file.?.seekTo(self.library_offset);
150
142 var reader = self.file.?.reader();151 var reader = self.file.?.reader();
143 const magic = try reader.readBytesNoEof(SARMAG);152 const magic = try reader.readBytesNoEof(SARMAG);
144153
...@@ -226,7 +235,7 @@ fn parseTableOfContents(self: *Archive, reader: anytype) !void {...@@ -226,7 +235,7 @@ fn parseTableOfContents(self: *Archive, reader: anytype) !void {
226/// Caller owns the Object instance.235/// Caller owns the Object instance.
227pub fn parseObject(self: Archive, offset: u32) !*Object {236pub fn parseObject(self: Archive, offset: u32) !*Object {
228 var reader = self.file.?.reader();237 var reader = self.file.?.reader();
229 try reader.context.seekTo(offset);238 try reader.context.seekTo(offset + self.library_offset);
230239
231 const object_header = try reader.readStruct(ar_hdr);240 const object_header = try reader.readStruct(ar_hdr);
232241
src/link/MachO/Dylib.zig+3-52
...@@ -1,7 +1,6 @@...@@ -1,7 +1,6 @@
1const Dylib = @This();1const Dylib = @This();
22
3const std = @import("std");3const std = @import("std");
4const builtin = std.builtin;
5const assert = std.debug.assert;4const assert = std.debug.assert;
6const fs = std.fs;5const fs = std.fs;
7const fmt = std.fmt;6const fmt = std.fmt;
...@@ -9,7 +8,7 @@ const log = std.log.scoped(.dylib);...@@ -9,7 +8,7 @@ const log = std.log.scoped(.dylib);
9const macho = std.macho;8const macho = std.macho;
10const math = std.math;9const math = std.math;
11const mem = std.mem;10const mem = std.mem;
12const native_endian = builtin.target.cpu.arch.endian();11const fat = @import("fat.zig");
1312
14const Allocator = mem.Allocator;13const Allocator = mem.Allocator;
15const Arch = std.Target.Cpu.Arch;14const Arch = std.Target.Cpu.Arch;
...@@ -211,42 +210,10 @@ pub fn closeFile(self: Dylib) void {...@@ -211,42 +210,10 @@ pub fn closeFile(self: Dylib) void {
211 }210 }
212}211}
213212
214fn decodeArch(cputype: macho.cpu_type_t) !std.Target.Cpu.Arch {
215 const arch: Arch = switch (cputype) {
216 macho.CPU_TYPE_ARM64 => .aarch64,
217 macho.CPU_TYPE_X86_64 => .x86_64,
218 else => {
219 return error.UnsupportedCpuArchitecture;
220 },
221 };
222 return arch;
223}
224
225pub fn parse(self: *Dylib) !void {213pub fn parse(self: *Dylib) !void {
226 log.debug("parsing shared library '{s}'", .{self.name.?});214 log.debug("parsing shared library '{s}'", .{self.name.?});
227215
228 self.library_offset = offset: {216 self.library_offset = try fat.getLibraryOffset(self.file.?.reader(), self.arch.?);
229 const fat_header = try readFatStruct(self.file.?.reader(), macho.fat_header);
230 if (fat_header.magic != macho.FAT_MAGIC) break :offset 0;
231
232 var fat_arch_index: u32 = 0;
233 while (fat_arch_index < fat_header.nfat_arch) : (fat_arch_index += 1) {
234 const fat_arch = try readFatStruct(self.file.?.reader(), macho.fat_arch);
235 // If we come across an architecture that we do not know how to handle, that's
236 // fine because we can keep looking for one that might match.
237 const lib_arch = decodeArch(fat_arch.cputype) catch |err| switch (err) {
238 error.UnsupportedCpuArchitecture => continue,
239 else => |e| return e,
240 };
241 if (lib_arch == self.arch.?) {
242 // We have found a matching architecture!
243 break :offset fat_arch.offset;
244 }
245 } else {
246 log.err("Could not find matching cpu architecture in fat library: expected {s}", .{self.arch.?});
247 return error.MismatchedCpuArchitecture;
248 }
249 };
250217
251 try self.file.?.seekTo(self.library_offset);218 try self.file.?.seekTo(self.library_offset);
252219
...@@ -258,13 +225,7 @@ pub fn parse(self: *Dylib) !void {...@@ -258,13 +225,7 @@ pub fn parse(self: *Dylib) !void {
258 return error.NotDylib;225 return error.NotDylib;
259 }226 }
260227
261 const this_arch: Arch = decodeArch(self.header.?.cputype) catch |err| switch (err) {228 const this_arch: Arch = try fat.decodeArch(self.header.?.cputype, true);
262 error.UnsupportedCpuArchitecture => |e| {
263 log.err("unsupported cpu architecture 0x{x}", .{self.header.?.cputype});
264 return e;
265 },
266 else => |e| return e,
267 };
268229
269 if (this_arch != self.arch.?) {230 if (this_arch != self.arch.?) {
270 log.err("mismatched cpu architecture: expected {s}, found {s}", .{ self.arch.?, this_arch });231 log.err("mismatched cpu architecture: expected {s}, found {s}", .{ self.arch.?, this_arch });
...@@ -276,16 +237,6 @@ pub fn parse(self: *Dylib) !void {...@@ -276,16 +237,6 @@ pub fn parse(self: *Dylib) !void {
276 try self.parseSymbols();237 try self.parseSymbols();
277}238}
278239
279fn readFatStruct(reader: anytype, comptime T: type) !T {
280 // Fat structures (fat_header & fat_arch) are always written and read to/from
281 // disk in big endian order.
282 var res: T = try reader.readStruct(T);
283 if (native_endian != builtin.Endian.Big) {
284 mem.bswapAllFields(T, &res);
285 }
286 return res;
287}
288
289fn readLoadCommands(self: *Dylib, reader: anytype) !void {240fn readLoadCommands(self: *Dylib, reader: anytype) !void {
290 try self.load_commands.ensureCapacity(self.allocator, self.header.?.ncmds);241 try self.load_commands.ensureCapacity(self.allocator, self.header.?.ncmds);
291242
src/link/MachO/fat.zig created+55
...@@ -0,0 +1,55 @@
1const std = @import("std");
2const builtin = std.builtin;
3const log = std.log.scoped(.archive);
4const macho = std.macho;
5const mem = std.mem;
6const native_endian = builtin.target.cpu.arch.endian();
7
8const Arch = std.Target.Cpu.Arch;
9
10pub fn decodeArch(cputype: macho.cpu_type_t, comptime logError: bool) !std.Target.Cpu.Arch {
11 const arch: Arch = switch (cputype) {
12 macho.CPU_TYPE_ARM64 => .aarch64,
13 macho.CPU_TYPE_X86_64 => .x86_64,
14 else => {
15 if (logError) {
16 log.err("unsupported cpu architecture 0x{x}", .{cputype});
17 }
18 return error.UnsupportedCpuArchitecture;
19 },
20 };
21 return arch;
22}
23
24fn readFatStruct(reader: anytype, comptime T: type) !T {
25 // Fat structures (fat_header & fat_arch) are always written and read to/from
26 // disk in big endian order.
27 var res = try reader.readStruct(T);
28 if (native_endian != builtin.Endian.Big) {
29 mem.bswapAllFields(T, &res);
30 }
31 return res;
32}
33
34pub fn getLibraryOffset(reader: anytype, arch: Arch) !u64 {
35 const fat_header = try readFatStruct(reader, macho.fat_header);
36 if (fat_header.magic != macho.FAT_MAGIC) return 0;
37
38 var fat_arch_index: u32 = 0;
39 while (fat_arch_index < fat_header.nfat_arch) : (fat_arch_index += 1) {
40 const fat_arch = try readFatStruct(reader, macho.fat_arch);
41 // If we come across an architecture that we do not know how to handle, that's
42 // fine because we can keep looking for one that might match.
43 const lib_arch = decodeArch(fat_arch.cputype, false) catch |err| switch (err) {
44 error.UnsupportedCpuArchitecture => continue,
45 else => |e| return e,
46 };
47 if (lib_arch == arch) {
48 // We have found a matching architecture!
49 return fat_arch.offset;
50 }
51 } else {
52 log.err("Could not find matching cpu architecture in fat library: expected {s}", .{arch});
53 return error.MismatchedCpuArchitecture;
54 }
55}