authorgravatar for readcuttingt@gmail.comTom Read Cutting <readcuttingt@gmail.com> 2021-06-24 23:29:39+01:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-06-25 08:38:47+02:00
log177b1b6bf9a7402eb688159dfa94ea5a5ea6f550
tree2b14f44eb4fd81a0f3e0067190f71e09f8a2cffb
parent2d2a6ed1a46349355650bfdd68688738c67bbf9c

Add fat/universal dylib support to zig ld

With this change zig ld can link with dynamic libraries contained within a fat/universal file that had multiple seperate binaries embedded within it for multi-arch support (in macOS). Whilst zig can still only create single-architecture executables - the ability to link with fat libraries is useful for cases where they are the easiest (or only) option to link against.

6 files changed, 137 insertions(+), 52 deletions(-)

lib/std/elf.zig+4-24
......@@ -429,7 +429,7 @@ pub fn ProgramHeaderIterator(ParseSource: anytype) type {
429429 if (self.elf_header.endian == native_endian) return phdr;
430430
431431 // Convert fields to native endianness.
432 bswapAllFields(Elf64_Phdr, &phdr);
432 mem.bswapAllFields(Elf64_Phdr, &phdr);
433433 return phdr;
434434 }
435435
......@@ -441,7 +441,7 @@ pub fn ProgramHeaderIterator(ParseSource: anytype) type {
441441 // ELF endianness does NOT match native endianness.
442442 if (self.elf_header.endian != native_endian) {
443443 // Convert fields to native endianness.
444 bswapAllFields(Elf32_Phdr, &phdr);
444 mem.bswapAllFields(Elf32_Phdr, &phdr);
445445 }
446446
447447 // Convert 32-bit header to 64-bit.
......@@ -479,7 +479,7 @@ pub fn SectionHeaderIterator(ParseSource: anytype) type {
479479 if (self.elf_header.endian == native_endian) return shdr;
480480
481481 // Convert fields to native endianness.
482 bswapAllFields(Elf64_Shdr, &shdr);
482 mem.bswapAllFields(Elf64_Shdr, &shdr);
483483 return shdr;
484484 }
485485
......@@ -491,7 +491,7 @@ pub fn SectionHeaderIterator(ParseSource: anytype) type {
491491 // ELF endianness does NOT match native endianness.
492492 if (self.elf_header.endian != native_endian) {
493493 // Convert fields to native endianness.
494 bswapAllFields(Elf32_Shdr, &shdr);
494 mem.bswapAllFields(Elf32_Shdr, &shdr);
495495 }
496496
497497 // Convert 32-bit header to 64-bit.
......@@ -531,26 +531,6 @@ pub fn int32(need_bswap: bool, int_32: anytype, comptime Int64: anytype) Int64 {
531531 }
532532}
533533
534pub fn bswapAllFields(comptime S: type, ptr: *S) void {
535 if (@typeInfo(S) != .Struct) @compileError("bswapAllFields expects a struct as the first argument");
536 inline for (std.meta.fields(S)) |f| {
537 @field(ptr, f.name) = @byteSwap(f.field_type, @field(ptr, f.name));
538 }
539}
540test "bswapAllFields" {
541 var s: Elf32_Chdr = .{
542 .ch_type = 0x12341234,
543 .ch_size = 0x56785678,
544 .ch_addralign = 0x12124242,
545 };
546 bswapAllFields(Elf32_Chdr, &s);
547 try std.testing.expectEqual(Elf32_Chdr{
548 .ch_type = 0x34123412,
549 .ch_size = 0x78567856,
550 .ch_addralign = 0x42421212,
551 }, s);
552}
553
554534pub const EI_NIDENT = 16;
555535
556536pub const EI_CLASS = 4;
lib/std/macho.zig+27
......@@ -24,6 +24,19 @@ pub const mach_header_64 = extern struct {
2424 reserved: u32,
2525};
2626
27pub const fat_header = extern struct {
28 magic: u32,
29 nfat_arch: u32,
30};
31
32pub const fat_arch = extern struct {
33 cputype: cpu_type_t,
34 cpusubtype: cpu_subtype_t,
35 offset: u32,
36 size: u32,
37 @"align": u32,
38};
39
2740pub const load_command = extern struct {
2841 cmd: u32,
2942 cmdsize: u32,
......@@ -1040,6 +1053,20 @@ pub const MH_APP_EXTENSION_SAFE = 0x02000000;
10401053/// The external symbols listed in the nlist symbol table do not include all the symbols listed in the dyld info.
10411054pub const MH_NLIST_OUTOFSYNC_WITH_DYLDINFO = 0x04000000;
10421055
1056// Constants for the flags field of the fat_header
1057
1058/// the fat magic number
1059pub const FAT_MAGIC = 0xcafebabe;
1060
1061/// NXSwapLong(FAT_MAGIC)
1062pub const FAT_CIGAM = 0xbebafeca;
1063
1064/// the 64-bit fat magic number
1065pub const FAT_MAGIC_64 = 0xcafebabf;
1066
1067/// NXSwapLong(FAT_MAGIC_64)
1068pub const FAT_CIGAM_64 = 0xbfbafeca;
1069
10431070/// The flags field of a section structure is separated into two parts a section
10441071/// type and section attributes. The section types are mutually exclusive (it
10451072/// can only have one type) but the section attributes are not (it may have more
lib/std/mem.zig+28
......@@ -1539,6 +1539,34 @@ test "writeIntBig and writeIntLittle" {
15391539 try testing.expect(eql(u8, buf2[0..], &[_]u8{ 0xfc, 0xff }));
15401540}
15411541
1542/// Swap the byte order of all the members of the fields of a struct
1543/// (Changing their endianess)
1544pub fn bswapAllFields(comptime S: type, ptr: *S) void {
1545 if (@typeInfo(S) != .Struct) @compileError("bswapAllFields expects a struct as the first argument");
1546 inline for (std.meta.fields(S)) |f| {
1547 @field(ptr, f.name) = @byteSwap(f.field_type, @field(ptr, f.name));
1548 }
1549}
1550
1551test "bswapAllFields" {
1552 const T = extern struct {
1553 f0: u8,
1554 f1: u16,
1555 f2: u32,
1556 };
1557 var s = T{
1558 .f0 = 0x12,
1559 .f1 = 0x1234,
1560 .f2 = 0x12345678,
1561 };
1562 bswapAllFields(T, &s);
1563 try std.testing.expectEqual(T{
1564 .f0 = 0x12,
1565 .f1 = 0x3412,
1566 .f2 = 0x78563412,
1567 }, s);
1568}
1569
15421570/// Returns an iterator that iterates over the slices of `buffer` that are not
15431571/// any of the bytes in `delimiter_bytes`.
15441572/// tokenize(" abc def ghi ", " ")
src/link/Elf.zig+12-12
......@@ -1108,7 +1108,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void {
11081108 for (buf) |*phdr, i| {
11091109 phdr.* = progHeaderTo32(self.program_headers.items[i]);
11101110 if (foreign_endian) {
1111 std.elf.bswapAllFields(elf.Elf32_Phdr, phdr);
1111 mem.bswapAllFields(elf.Elf32_Phdr, phdr);
11121112 }
11131113 }
11141114 try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?);
......@@ -1120,7 +1120,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void {
11201120 for (buf) |*phdr, i| {
11211121 phdr.* = self.program_headers.items[i];
11221122 if (foreign_endian) {
1123 std.elf.bswapAllFields(elf.Elf64_Phdr, phdr);
1123 mem.bswapAllFields(elf.Elf64_Phdr, phdr);
11241124 }
11251125 }
11261126 try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?);
......@@ -1197,7 +1197,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void {
11971197 shdr.* = sectHeaderTo32(self.sections.items[i]);
11981198 log.debug("writing section {}", .{shdr.*});
11991199 if (foreign_endian) {
1200 std.elf.bswapAllFields(elf.Elf32_Shdr, shdr);
1200 mem.bswapAllFields(elf.Elf32_Shdr, shdr);
12011201 }
12021202 }
12031203 try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), self.shdr_table_offset.?);
......@@ -1210,7 +1210,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation) !void {
12101210 shdr.* = self.sections.items[i];
12111211 log.debug("writing section {}", .{shdr.*});
12121212 if (foreign_endian) {
1213 std.elf.bswapAllFields(elf.Elf64_Shdr, shdr);
1213 mem.bswapAllFields(elf.Elf64_Shdr, shdr);
12141214 }
12151215 }
12161216 try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), self.shdr_table_offset.?);
......@@ -2740,14 +2740,14 @@ fn writeProgHeader(self: *Elf, index: usize) !void {
27402740 .p32 => {
27412741 var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])};
27422742 if (foreign_endian) {
2743 std.elf.bswapAllFields(elf.Elf32_Phdr, &phdr[0]);
2743 mem.bswapAllFields(elf.Elf32_Phdr, &phdr[0]);
27442744 }
27452745 return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset);
27462746 },
27472747 .p64 => {
27482748 var phdr = [1]elf.Elf64_Phdr{self.program_headers.items[index]};
27492749 if (foreign_endian) {
2750 std.elf.bswapAllFields(elf.Elf64_Phdr, &phdr[0]);
2750 mem.bswapAllFields(elf.Elf64_Phdr, &phdr[0]);
27512751 }
27522752 return self.base.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset);
27532753 },
......@@ -2761,7 +2761,7 @@ fn writeSectHeader(self: *Elf, index: usize) !void {
27612761 var shdr: [1]elf.Elf32_Shdr = undefined;
27622762 shdr[0] = sectHeaderTo32(self.sections.items[index]);
27632763 if (foreign_endian) {
2764 std.elf.bswapAllFields(elf.Elf32_Shdr, &shdr[0]);
2764 mem.bswapAllFields(elf.Elf32_Shdr, &shdr[0]);
27652765 }
27662766 const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf32_Shdr);
27672767 return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);
......@@ -2769,7 +2769,7 @@ fn writeSectHeader(self: *Elf, index: usize) !void {
27692769 .p64 => {
27702770 var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]};
27712771 if (foreign_endian) {
2772 std.elf.bswapAllFields(elf.Elf64_Shdr, &shdr[0]);
2772 mem.bswapAllFields(elf.Elf64_Shdr, &shdr[0]);
27732773 }
27742774 const offset = self.shdr_table_offset.? + index * @sizeOf(elf.Elf64_Shdr);
27752775 return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);
......@@ -2867,7 +2867,7 @@ fn writeSymbol(self: *Elf, index: usize) !void {
28672867 },
28682868 };
28692869 if (foreign_endian) {
2870 std.elf.bswapAllFields(elf.Elf32_Sym, &sym[0]);
2870 mem.bswapAllFields(elf.Elf32_Sym, &sym[0]);
28712871 }
28722872 const off = syms_sect.sh_offset + @sizeOf(elf.Elf32_Sym) * index;
28732873 try self.base.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off);
......@@ -2875,7 +2875,7 @@ fn writeSymbol(self: *Elf, index: usize) !void {
28752875 .p64 => {
28762876 var sym = [1]elf.Elf64_Sym{self.local_symbols.items[index]};
28772877 if (foreign_endian) {
2878 std.elf.bswapAllFields(elf.Elf64_Sym, &sym[0]);
2878 mem.bswapAllFields(elf.Elf64_Sym, &sym[0]);
28792879 }
28802880 const off = syms_sect.sh_offset + @sizeOf(elf.Elf64_Sym) * index;
28812881 try self.base.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off);
......@@ -2906,7 +2906,7 @@ fn writeAllGlobalSymbols(self: *Elf) !void {
29062906 .st_shndx = self.global_symbols.items[i].st_shndx,
29072907 };
29082908 if (foreign_endian) {
2909 std.elf.bswapAllFields(elf.Elf32_Sym, sym);
2909 mem.bswapAllFields(elf.Elf32_Sym, sym);
29102910 }
29112911 }
29122912 try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), global_syms_off);
......@@ -2925,7 +2925,7 @@ fn writeAllGlobalSymbols(self: *Elf) !void {
29252925 .st_shndx = self.global_symbols.items[i].st_shndx,
29262926 };
29272927 if (foreign_endian) {
2928 std.elf.bswapAllFields(elf.Elf64_Sym, sym);
2928 mem.bswapAllFields(elf.Elf64_Sym, sym);
29292929 }
29302930 }
29312931 try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), global_syms_off);
src/link/MachO/Dylib.zig+60-8
......@@ -1,6 +1,7 @@
11const Dylib = @This();
22
33const std = @import("std");
4const builtin = std.builtin;
45const assert = std.debug.assert;
56const fs = std.fs;
67const fmt = std.fmt;
......@@ -8,6 +9,7 @@ const log = std.log.scoped(.dylib);
89const macho = std.macho;
910const math = std.math;
1011const mem = std.mem;
12const native_endian = builtin.target.cpu.arch.endian();
1113
1214const Allocator = mem.Allocator;
1315const Arch = std.Target.Cpu.Arch;
......@@ -26,6 +28,10 @@ syslibroot: ?[]const u8 = null,
2628
2729ordinal: ?u16 = null,
2830
31// The actual dylib contents we care about linking with will be embedded at
32// an offset within a file if we are linking against a fat lib
33library_offset: u64 = 0,
34
2935load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
3036
3137symtab_cmd_index: ?u16 = null,
......@@ -205,9 +211,45 @@ pub fn closeFile(self: Dylib) void {
205211 }
206212}
207213
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
208225pub fn parse(self: *Dylib) !void {
209226 log.debug("parsing shared library '{s}'", .{self.name.?});
210227
228 self.library_offset = offset: {
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 };
250
251 try self.file.?.seekTo(self.library_offset);
252
211253 var reader = self.file.?.reader();
212254 self.header = try reader.readStruct(macho.mach_header_64);
213255
......@@ -216,14 +258,14 @@ pub fn parse(self: *Dylib) !void {
216258 return error.NotDylib;
217259 }
218260
219 const this_arch: std.Target.Cpu.Arch = switch (self.header.?.cputype) {
220 macho.CPU_TYPE_ARM64 => .aarch64,
221 macho.CPU_TYPE_X86_64 => .x86_64,
222 else => |value| {
223 log.err("unsupported cpu architecture 0x{x}", .{value});
224 return error.UnsupportedCpuArchitecture;
261 const this_arch: Arch = decodeArch(self.header.?.cputype) catch |err| switch (err) {
262 error.UnsupportedCpuArchitecture => |e| {
263 log.err("unsupported cpu architecture 0x{x}", .{self.header.?.cputype});
264 return e;
225265 },
266 else => |e| return e,
226267 };
268
227269 if (this_arch != self.arch.?) {
228270 log.err("mismatched cpu architecture: expected {s}, found {s}", .{ self.arch.?, this_arch });
229271 return error.MismatchedCpuArchitecture;
......@@ -234,6 +276,16 @@ pub fn parse(self: *Dylib) !void {
234276 try self.parseSymbols();
235277}
236278
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
237289fn readLoadCommands(self: *Dylib, reader: anytype) !void {
238290 try self.load_commands.ensureCapacity(self.allocator, self.header.?.ncmds);
239291
......@@ -285,12 +337,12 @@ fn parseSymbols(self: *Dylib) !void {
285337
286338 var symtab = try self.allocator.alloc(u8, @sizeOf(macho.nlist_64) * symtab_cmd.nsyms);
287339 defer self.allocator.free(symtab);
288 _ = try self.file.?.preadAll(symtab, symtab_cmd.symoff);
340 _ = try self.file.?.preadAll(symtab, symtab_cmd.symoff + self.library_offset);
289341 const slice = @alignCast(@alignOf(macho.nlist_64), mem.bytesAsSlice(macho.nlist_64, symtab));
290342
291343 var strtab = try self.allocator.alloc(u8, symtab_cmd.strsize);
292344 defer self.allocator.free(strtab);
293 _ = try self.file.?.preadAll(strtab, symtab_cmd.stroff);
345 _ = try self.file.?.preadAll(strtab, symtab_cmd.stroff + self.library_offset);
294346
295347 for (slice) |sym| {
296348 const sym_name = mem.spanZ(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx));
src/link/MachO/Object.zig+6-8
......@@ -247,18 +247,14 @@ pub fn parse(self: *Object) !void {
247247 try reader.context.seekTo(offset);
248248 }
249249
250 self.header = try reader.readStruct(macho.mach_header_64);
251
252 if (self.header.?.filetype != macho.MH_OBJECT) {
253 log.debug("invalid filetype: expected 0x{x}, found 0x{x}", .{
254 macho.MH_OBJECT,
255 self.header.?.filetype,
256 });
250 const header = try reader.readStruct(macho.mach_header_64);
257251
252 if (header.filetype != macho.MH_OBJECT) {
253 log.debug("invalid filetype: expected 0x{x}, found 0x{x}", .{ macho.MH_OBJECT, header.filetype });
258254 return error.NotObject;
259255 }
260256
261 const this_arch: Arch = switch (self.header.?.cputype) {
257 const this_arch: Arch = switch (header.cputype) {
262258 macho.CPU_TYPE_ARM64 => .aarch64,
263259 macho.CPU_TYPE_X86_64 => .x86_64,
264260 else => |value| {
......@@ -271,6 +267,8 @@ pub fn parse(self: *Object) !void {
271267 return error.MismatchedCpuArchitecture;
272268 }
273269
270 self.header = header;
271
274272 try self.readLoadCommands(reader);
275273 try self.parseSymbols();
276274 try self.parseSections();