authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-05-18 11:04:06+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-05-18 11:04:06+02:00
log7588fcdd2a7c232286f2c298bd14cbfb65e54604
tree5f2a7bf072f90ee1e95e508307642a97a9e172c0
parent35c694d614c8687ffa99ac187e60d831f9d7f29d
parentcb45c5521ab5360e2b8c5d1ce16d351d526a3fe5
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #8806 from ziglang/zld-link-shared

zig ld: add preliminary mechanism for linking dylibs

8 files changed, 617 insertions(+), 99 deletions(-)

CMakeLists.txt+1
......@@ -568,6 +568,7 @@ set(ZIG_STAGE2_SOURCES
568568 "${CMAKE_SOURCE_DIR}/src/link/MachO/Archive.zig"
569569 "${CMAKE_SOURCE_DIR}/src/link/MachO/CodeSignature.zig"
570570 "${CMAKE_SOURCE_DIR}/src/link/MachO/DebugSymbols.zig"
571 "${CMAKE_SOURCE_DIR}/src/link/MachO/Dylib.zig"
571572 "${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig"
572573 "${CMAKE_SOURCE_DIR}/src/link/MachO/Symbol.zig"
573574 "${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig"
lib/std/macho.zig+45
......@@ -71,6 +71,38 @@ pub const source_version_command = extern struct {
7171 version: u64,
7272};
7373
74/// The build_version_command contains the min OS version on which this
75/// binary was built to run for its platform. The list of known platforms and
76/// tool values following it.
77pub const build_version_command = extern struct {
78 /// LC_BUILD_VERSION
79 cmd: u32,
80
81 /// sizeof(struct build_version_command) plus
82 /// ntools * sizeof(struct build_version_command)
83 cmdsize: u32,
84
85 /// platform
86 platform: u32,
87
88 /// X.Y.Z is encoded in nibbles xxxx.yy.zz
89 minos: u32,
90
91 /// X.Y.Z is encoded in nibbles xxxx.yy.zz
92 sdk: u32,
93
94 /// number of tool entries following this
95 ntools: u32,
96};
97
98pub const build_tool_version = extern struct {
99 /// enum for the tool
100 tool: u32,
101
102 /// version number of the tool
103 version: u32,
104};
105
74106/// The entry_point_command is a replacement for thread_command.
75107/// It is used for main executables to specify the location (file offset)
76108/// of main(). If -stack_size was used at link time, the stacksize
......@@ -484,6 +516,19 @@ pub const dylib = extern struct {
484516 compatibility_version: u32,
485517};
486518
519/// The rpath_command contains a path which at runtime should be added to the current
520/// run path used to find @rpath prefixed dylibs.
521pub const rpath_command = extern struct {
522 /// LC_RPATH
523 cmd: u32,
524
525 /// includes string
526 cmdsize: u32,
527
528 /// path to add to run path
529 path: u32,
530};
531
487532/// The segment load command indicates that a part of this file is to be
488533/// mapped into the task's address space. The size of this segment in memory,
489534/// vmsize, maybe equal to or larger than the amount to map from this file,
src/link/MachO.zig+147-36
......@@ -548,7 +548,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
548548 const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib;
549549 const is_exe_or_dyn_lib = is_dyn_lib or self.base.options.output_mode == .Exe;
550550 const target = self.base.options.target;
551 const stack_size = self.base.options.stack_size_override orelse 16777216;
551 const stack_size = self.base.options.stack_size_override orelse 0;
552552 const allow_shlib_undefined = self.base.options.allow_shlib_undefined orelse !self.base.options.is_native_os;
553553
554554 const id_symlink_basename = "lld.id";
......@@ -675,41 +675,168 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
675675 zld.deinit();
676676 }
677677 zld.arch = target.cpu.arch;
678 zld.stack_size = stack_size;
679
680 // Positional arguments to the linker such as object files and static archives.
681 var positionals = std.ArrayList([]const u8).init(arena);
682
683 try positionals.appendSlice(self.base.options.objects);
678684
679 var input_files = std.ArrayList([]const u8).init(self.base.allocator);
680 defer input_files.deinit();
681 // Positional arguments to the linker such as object files.
682 try input_files.appendSlice(self.base.options.objects);
683685 for (comp.c_object_table.items()) |entry| {
684 try input_files.append(entry.key.status.success.object_path);
686 try positionals.append(entry.key.status.success.object_path);
685687 }
688
686689 if (module_obj_path) |p| {
687 try input_files.append(p);
690 try positionals.append(p);
688691 }
689 try input_files.append(comp.compiler_rt_static_lib.?.full_object_path);
692
693 try positionals.append(comp.compiler_rt_static_lib.?.full_object_path);
694
690695 // libc++ dep
691696 if (self.base.options.link_libcpp) {
692 try input_files.append(comp.libcxxabi_static_lib.?.full_object_path);
693 try input_files.append(comp.libcxx_static_lib.?.full_object_path);
697 try positionals.append(comp.libcxxabi_static_lib.?.full_object_path);
698 try positionals.append(comp.libcxx_static_lib.?.full_object_path);
699 }
700
701 // Shared libraries.
702 var shared_libs = std.ArrayList([]const u8).init(arena);
703 var search_lib_names = std.ArrayList([]const u8).init(arena);
704
705 const system_libs = self.base.options.system_libs.items();
706 for (system_libs) |entry| {
707 const link_lib = entry.key;
708 // By this time, we depend on these libs being dynamically linked libraries and not static libraries
709 // (the check for that needs to be earlier), but they could be full paths to .dylib files, in which
710 // case we want to avoid prepending "-l".
711 // TODO I think they should go as an input file instead of via shared_libs.
712 if (Compilation.classifyFileExt(link_lib) == .shared_library) {
713 try shared_libs.append(link_lib);
714 continue;
715 }
716
717 try search_lib_names.append(link_lib);
718 }
719
720 var search_lib_dirs = std.ArrayList([]const u8).init(arena);
721
722 for (self.base.options.lib_dirs) |path| {
723 if (fs.path.isAbsolute(path)) {
724 var candidates = std.ArrayList([]const u8).init(arena);
725 if (self.base.options.syslibroot) |syslibroot| {
726 const full_path = try fs.path.join(arena, &[_][]const u8{ syslibroot, path });
727 try candidates.append(full_path);
728 }
729 try candidates.append(path);
730
731 var found = false;
732 for (candidates.items) |candidate| {
733 // Verify that search path actually exists
734 var tmp = fs.cwd().openDir(candidate, .{}) catch |err| switch (err) {
735 error.FileNotFound => continue,
736 else => |e| return e,
737 };
738 defer tmp.close();
739
740 try search_lib_dirs.append(candidate);
741 found = true;
742 break;
743 }
744
745 if (!found) {
746 log.warn("directory not found for '-L{s}'", .{path});
747 }
748 } else {
749 // Verify that search path actually exists
750 var tmp = fs.cwd().openDir(path, .{}) catch |err| switch (err) {
751 error.FileNotFound => {
752 log.warn("directory not found for '-L{s}'", .{path});
753 continue;
754 },
755 else => |e| return e,
756 };
757 defer tmp.close();
758
759 try search_lib_dirs.append(path);
760 }
761 }
762
763 for (search_lib_names.items) |l_name| {
764 // TODO text-based API, or .tbd files.
765 const l_name_ext = try std.fmt.allocPrint(arena, "lib{s}.dylib", .{l_name});
766
767 var found = false;
768 for (search_lib_dirs.items) |lib_dir| {
769 const full_path = try fs.path.join(arena, &[_][]const u8{ lib_dir, l_name_ext });
770
771 // Check if the dylib file exists.
772 const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) {
773 error.FileNotFound => continue,
774 else => |e| return e,
775 };
776 defer tmp.close();
777
778 try shared_libs.append(full_path);
779 found = true;
780 break;
781 }
782
783 if (!found) {
784 log.warn("library not found for '-l{s}'", .{l_name});
785 log.warn("Library search paths:", .{});
786 for (search_lib_dirs.items) |lib_dir| {
787 log.warn(" {s}", .{lib_dir});
788 }
789 }
790 }
791
792 // rpaths
793 var rpath_table = std.StringArrayHashMap(void).init(arena);
794 for (self.base.options.rpath_list) |rpath| {
795 if (rpath_table.contains(rpath)) continue;
796 try rpath_table.putNoClobber(rpath, {});
797 }
798
799 var rpaths = std.ArrayList([]const u8).init(arena);
800 try rpaths.ensureCapacity(rpath_table.count());
801 for (rpath_table.items()) |entry| {
802 rpaths.appendAssumeCapacity(entry.key);
694803 }
695804
696805 if (self.base.options.verbose_link) {
697 var argv = std.ArrayList([]const u8).init(self.base.allocator);
698 defer argv.deinit();
806 var argv = std.ArrayList([]const u8).init(arena);
699807
700808 try argv.append("zig");
701809 try argv.append("ld");
702810
703 try argv.appendSlice(input_files.items);
811 if (self.base.options.syslibroot) |syslibroot| {
812 try argv.append("-syslibroot");
813 try argv.append(syslibroot);
814 }
815
816 for (rpaths.items) |rpath| {
817 try argv.append("-rpath");
818 try argv.append(rpath);
819 }
820
821 try argv.appendSlice(positionals.items);
704822
705823 try argv.append("-o");
706824 try argv.append(full_out_path);
707825
826 for (search_lib_names.items) |l_name| {
827 try argv.append(try std.fmt.allocPrint(arena, "-l{s}", .{l_name}));
828 }
829
830 for (self.base.options.lib_dirs) |lib_dir| {
831 try argv.append(try std.fmt.allocPrint(arena, "-L{s}", .{lib_dir}));
832 }
833
708834 Compilation.dump_argv(argv.items);
709835 }
710836
711 try zld.link(input_files.items, full_out_path, .{
712 .stack_size = self.base.options.stack_size_override,
837 try zld.link(positionals.items, full_out_path, .{
838 .shared_libs = shared_libs.items,
839 .rpaths = rpaths.items,
713840 });
714841
715842 break :outer;
......@@ -1993,28 +2120,12 @@ pub fn populateMissingMetadata(self: *MachO) !void {
19932120 }
19942121 if (self.libsystem_cmd_index == null) {
19952122 self.libsystem_cmd_index = @intCast(u16, self.load_commands.items.len);
1996 const cmdsize = @intCast(u32, mem.alignForwardGeneric(
1997 u64,
1998 @sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH),
1999 @sizeOf(u64),
2000 ));
2001 // TODO Find a way to work out runtime version from the OS version triple stored in std.Target.
2002 // In the meantime, we're gonna hardcode to the minimum compatibility version of 0.0.0.
2003 const min_version = 0x0;
2004 var dylib_cmd = emptyGenericCommandWithData(macho.dylib_command{
2005 .cmd = macho.LC_LOAD_DYLIB,
2006 .cmdsize = cmdsize,
2007 .dylib = .{
2008 .name = @sizeOf(macho.dylib_command),
2009 .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files
2010 .current_version = min_version,
2011 .compatibility_version = min_version,
2012 },
2013 });
2014 dylib_cmd.data = try self.base.allocator.alloc(u8, cmdsize - dylib_cmd.inner.dylib.name);
2015 mem.set(u8, dylib_cmd.data, 0);
2016 mem.copy(u8, dylib_cmd.data, mem.spanZ(LIB_SYSTEM_PATH));
2123
2124 var dylib_cmd = try createLoadDylibCommand(self.base.allocator, mem.spanZ(LIB_SYSTEM_PATH), 2, 0, 0);
2125 errdefer dylib_cmd.deinit(self.base.allocator);
2126
20172127 try self.load_commands.append(self.base.allocator, .{ .Dylib = dylib_cmd });
2128
20182129 self.header_dirty = true;
20192130 self.load_commands_dirty = true;
20202131 }
src/link/MachO/Dylib.zig created+185
......@@ -0,0 +1,185 @@
1const Dylib = @This();
2
3const std = @import("std");
4const fs = std.fs;
5const log = std.log.scoped(.dylib);
6const macho = std.macho;
7const mem = std.mem;
8
9const Allocator = mem.Allocator;
10const Symbol = @import("Symbol.zig");
11
12usingnamespace @import("commands.zig");
13
14allocator: *Allocator,
15arch: ?std.Target.Cpu.Arch = null,
16header: ?macho.mach_header_64 = null,
17file: ?fs.File = null,
18name: ?[]const u8 = null,
19
20ordinal: ?u16 = null,
21
22load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
23
24symtab_cmd_index: ?u16 = null,
25dysymtab_cmd_index: ?u16 = null,
26id_cmd_index: ?u16 = null,
27
28id: ?Id = null,
29
30symbols: std.StringArrayHashMapUnmanaged(*Symbol) = .{},
31
32pub const Id = struct {
33 name: []const u8,
34 timestamp: u32,
35 current_version: u32,
36 compatibility_version: u32,
37
38 pub fn deinit(id: *Id, allocator: *Allocator) void {
39 allocator.free(id.name);
40 }
41};
42
43pub fn init(allocator: *Allocator) Dylib {
44 return .{ .allocator = allocator };
45}
46
47pub fn deinit(self: *Dylib) void {
48 for (self.load_commands.items) |*lc| {
49 lc.deinit(self.allocator);
50 }
51 self.load_commands.deinit(self.allocator);
52
53 for (self.symbols.items()) |entry| {
54 entry.value.deinit(self.allocator);
55 self.allocator.destroy(entry.value);
56 }
57 self.symbols.deinit(self.allocator);
58
59 if (self.name) |name| {
60 self.allocator.free(name);
61 }
62
63 if (self.id) |*id| {
64 id.deinit(self.allocator);
65 }
66}
67
68pub fn closeFile(self: Dylib) void {
69 if (self.file) |file| {
70 file.close();
71 }
72}
73
74pub fn parse(self: *Dylib) !void {
75 log.debug("parsing shared library '{s}'", .{self.name.?});
76
77 var reader = self.file.?.reader();
78 self.header = try reader.readStruct(macho.mach_header_64);
79
80 if (self.header.?.filetype != macho.MH_DYLIB) {
81 log.err("invalid filetype: expected 0x{x}, found 0x{x}", .{ macho.MH_DYLIB, self.header.?.filetype });
82 return error.MalformedDylib;
83 }
84
85 const this_arch: std.Target.Cpu.Arch = switch (self.header.?.cputype) {
86 macho.CPU_TYPE_ARM64 => .aarch64,
87 macho.CPU_TYPE_X86_64 => .x86_64,
88 else => |value| {
89 log.err("unsupported cpu architecture 0x{x}", .{value});
90 return error.UnsupportedCpuArchitecture;
91 },
92 };
93 if (this_arch != self.arch.?) {
94 log.err("mismatched cpu architecture: expected {s}, found {s}", .{ self.arch.?, this_arch });
95 return error.MismatchedCpuArchitecture;
96 }
97
98 try self.readLoadCommands(reader);
99 try self.parseId();
100 try self.parseSymbols();
101}
102
103pub fn readLoadCommands(self: *Dylib, reader: anytype) !void {
104 try self.load_commands.ensureCapacity(self.allocator, self.header.?.ncmds);
105
106 var i: u16 = 0;
107 while (i < self.header.?.ncmds) : (i += 1) {
108 var cmd = try LoadCommand.read(self.allocator, reader);
109 switch (cmd.cmd()) {
110 macho.LC_SYMTAB => {
111 self.symtab_cmd_index = i;
112 },
113 macho.LC_DYSYMTAB => {
114 self.dysymtab_cmd_index = i;
115 },
116 macho.LC_ID_DYLIB => {
117 self.id_cmd_index = i;
118 },
119 else => {
120 log.debug("Unknown load command detected: 0x{x}.", .{cmd.cmd()});
121 },
122 }
123 self.load_commands.appendAssumeCapacity(cmd);
124 }
125}
126
127pub fn parseId(self: *Dylib) !void {
128 const index = self.id_cmd_index orelse {
129 log.debug("no LC_ID_DYLIB load command found; using hard-coded defaults...", .{});
130 self.id = .{
131 .name = try self.allocator.dupe(u8, self.name.?),
132 .timestamp = 2,
133 .current_version = 0,
134 .compatibility_version = 0,
135 };
136 return;
137 };
138 const id_cmd = self.load_commands.items[index].Dylib;
139 const dylib = id_cmd.inner.dylib;
140
141 // TODO should we compare the name from the dylib's id with the user-specified one?
142 const dylib_name = @ptrCast([*:0]const u8, id_cmd.data[dylib.name - @sizeOf(macho.dylib_command) ..]);
143 const name = try self.allocator.dupe(u8, mem.spanZ(dylib_name));
144
145 self.id = .{
146 .name = name,
147 .timestamp = dylib.timestamp,
148 .current_version = dylib.current_version,
149 .compatibility_version = dylib.compatibility_version,
150 };
151}
152
153pub fn parseSymbols(self: *Dylib) !void {
154 const index = self.symtab_cmd_index orelse return;
155 const symtab_cmd = self.load_commands.items[index].Symtab;
156
157 var symtab = try self.allocator.alloc(u8, @sizeOf(macho.nlist_64) * symtab_cmd.nsyms);
158 defer self.allocator.free(symtab);
159 _ = try self.file.?.preadAll(symtab, symtab_cmd.symoff);
160 const slice = @alignCast(@alignOf(macho.nlist_64), mem.bytesAsSlice(macho.nlist_64, symtab));
161
162 var strtab = try self.allocator.alloc(u8, symtab_cmd.strsize);
163 defer self.allocator.free(strtab);
164 _ = try self.file.?.preadAll(strtab, symtab_cmd.stroff);
165
166 for (slice) |sym| {
167 const sym_name = mem.spanZ(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx));
168
169 if (!(Symbol.isSect(sym) and Symbol.isExt(sym))) continue;
170
171 const name = try self.allocator.dupe(u8, sym_name);
172 const proxy = try self.allocator.create(Symbol.Proxy);
173 errdefer self.allocator.destroy(proxy);
174
175 proxy.* = .{
176 .base = .{
177 .@"type" = .proxy,
178 .name = name,
179 },
180 .dylib = self,
181 };
182
183 try self.symbols.putNoClobber(self.allocator, name, &proxy.base);
184 }
185}
src/link/MachO/Symbol.zig+4-3
......@@ -5,6 +5,7 @@ const macho = std.macho;
55const mem = std.mem;
66
77const Allocator = mem.Allocator;
8const Dylib = @import("Dylib.zig");
89const Object = @import("Object.zig");
910
1011pub const Type = enum {
......@@ -43,7 +44,7 @@ pub const Regular = struct {
4344 /// Whether the symbol is a weak ref.
4445 weak_ref: bool,
4546
46 /// File where to locate this symbol.
47 /// Object file where to locate this symbol.
4748 file: *Object,
4849
4950 /// Debug stab if defined.
......@@ -78,8 +79,8 @@ pub const Regular = struct {
7879pub const Proxy = struct {
7980 base: Symbol,
8081
81 /// Dylib ordinal.
82 dylib: u16,
82 /// Dylib where to locate this symbol.
83 dylib: ?*Dylib = null,
8384
8485 pub const base_type: Symbol.Type = .proxy;
8586};
src/link/MachO/Zld.zig+173-51
......@@ -15,6 +15,7 @@ const reloc = @import("reloc.zig");
1515const Allocator = mem.Allocator;
1616const Archive = @import("Archive.zig");
1717const CodeSignature = @import("CodeSignature.zig");
18const Dylib = @import("Dylib.zig");
1819const Object = @import("Object.zig");
1920const Symbol = @import("Symbol.zig");
2021const Trie = @import("Trie.zig");
......@@ -35,6 +36,7 @@ stack_size: u64 = 0,
3536
3637objects: std.ArrayListUnmanaged(*Object) = .{},
3738archives: std.ArrayListUnmanaged(*Archive) = .{},
39dylibs: std.ArrayListUnmanaged(*Dylib) = .{},
3840
3941load_commands: std.ArrayListUnmanaged(LoadCommand) = .{},
4042
......@@ -151,10 +153,18 @@ pub fn deinit(self: *Zld) void {
151153 }
152154 self.archives.deinit(self.allocator);
153155
156 for (self.dylibs.items) |dylib| {
157 dylib.deinit();
158 self.allocator.destroy(dylib);
159 }
160 self.dylibs.deinit(self.allocator);
161
154162 self.mappings.deinit(self.allocator);
155163 self.unhandled_sections.deinit(self.allocator);
156164
157165 self.globals.deinit(self.allocator);
166 self.imports.deinit(self.allocator);
167 self.unresolved.deinit(self.allocator);
158168 self.strtab.deinit(self.allocator);
159169
160170 {
......@@ -177,7 +187,8 @@ pub fn closeFiles(self: Zld) void {
177187}
178188
179189const LinkArgs = struct {
180 stack_size: ?u64 = null,
190 shared_libs: []const []const u8,
191 rpaths: []const []const u8,
181192};
182193
183194pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8, args: LinkArgs) !void {
......@@ -214,10 +225,11 @@ pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8, args: L
214225 .read = true,
215226 .mode = if (std.Target.current.os.tag == .windows) 0 else 0o777,
216227 });
217 self.stack_size = args.stack_size orelse 0;
218228
219229 try self.populateMetadata();
230 try self.addRpaths(args.rpaths);
220231 try self.parseInputFiles(files);
232 try self.parseDylibs(args.shared_libs);
221233 try self.resolveSymbols();
222234 try self.resolveStubsAndGotEntries();
223235 try self.updateMetadata();
......@@ -235,6 +247,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
235247 kind: enum {
236248 object,
237249 archive,
250 dylib,
238251 },
239252 file: fs.File,
240253 name: []const u8,
......@@ -242,7 +255,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
242255 var classified = std.ArrayList(Input).init(self.allocator);
243256 defer classified.deinit();
244257
245 // First, classify input files as either object or archive.
258 // First, classify input files: object, archive or dylib.
246259 for (files) |file_name| {
247260 const file = try fs.cwd().openFile(file_name, .{});
248261 const full_path = full_path: {
......@@ -283,6 +296,22 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
283296 continue;
284297 }
285298
299 try_dylib: {
300 const header = try file.reader().readStruct(macho.mach_header_64);
301 if (header.filetype != macho.MH_DYLIB) {
302 try file.seekTo(0);
303 break :try_dylib;
304 }
305
306 try file.seekTo(0);
307 try classified.append(.{
308 .kind = .dylib,
309 .file = file,
310 .name = full_path,
311 });
312 continue;
313 }
314
286315 log.debug("unexpected input file of unknown type '{s}'", .{file_name});
287316 }
288317
......@@ -311,10 +340,71 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
311340 try archive.parse();
312341 try self.archives.append(self.allocator, archive);
313342 },
343 .dylib => {
344 const dylib = try self.allocator.create(Dylib);
345 errdefer self.allocator.destroy(dylib);
346
347 dylib.* = Dylib.init(self.allocator);
348 dylib.arch = self.arch.?;
349 dylib.name = input.name;
350 dylib.file = input.file;
351
352 const ordinal = @intCast(u16, self.dylibs.items.len);
353 dylib.ordinal = ordinal + 2; // TODO +2 since 1 is reserved for libSystem
354
355 // TODO Defer parsing of the dylibs until they are actually needed
356 try dylib.parse();
357 try self.dylibs.append(self.allocator, dylib);
358
359 // Add LC_LOAD_DYLIB command
360 const dylib_id = dylib.id orelse unreachable;
361 var dylib_cmd = try createLoadDylibCommand(
362 self.allocator,
363 dylib_id.name,
364 dylib_id.timestamp,
365 dylib_id.current_version,
366 dylib_id.compatibility_version,
367 );
368 errdefer dylib_cmd.deinit(self.allocator);
369
370 try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd });
371 },
314372 }
315373 }
316374}
317375
376fn parseDylibs(self: *Zld, shared_libs: []const []const u8) !void {
377 for (shared_libs) |lib| {
378 const dylib = try self.allocator.create(Dylib);
379 errdefer self.allocator.destroy(dylib);
380
381 dylib.* = Dylib.init(self.allocator);
382 dylib.arch = self.arch.?;
383 dylib.name = try self.allocator.dupe(u8, lib);
384 dylib.file = try fs.cwd().openFile(lib, .{});
385
386 const ordinal = @intCast(u16, self.dylibs.items.len);
387 dylib.ordinal = ordinal + 2; // TODO +2 since 1 is reserved for libSystem
388
389 // TODO Defer parsing of the dylibs until they are actually needed
390 try dylib.parse();
391 try self.dylibs.append(self.allocator, dylib);
392
393 // Add LC_LOAD_DYLIB command
394 const dylib_id = dylib.id orelse unreachable;
395 var dylib_cmd = try createLoadDylibCommand(
396 self.allocator,
397 dylib_id.name,
398 dylib_id.timestamp,
399 dylib_id.current_version,
400 dylib_id.compatibility_version,
401 );
402 errdefer dylib_cmd.deinit(self.allocator);
403
404 try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd });
405 }
406}
407
318408fn mapAndUpdateSections(
319409 self: *Zld,
320410 object_id: u16,
......@@ -1398,35 +1488,51 @@ fn resolveSymbols(self: *Zld) !void {
13981488 // Third pass, resolve symbols in dynamic libraries.
13991489 // TODO Implement libSystem as a hard-coded library, or ship with
14001490 // a libSystem.B.tbd definition file?
1401 try self.imports.ensureCapacity(self.allocator, self.unresolved.count());
1402 for (self.unresolved.items()) |entry| {
1403 const proxy = try self.allocator.create(Symbol.Proxy);
1404 errdefer self.allocator.destroy(proxy);
1491 var unresolved = std.ArrayList(*Symbol).init(self.allocator);
1492 defer unresolved.deinit();
14051493
1406 proxy.* = .{
1407 .base = .{
1408 .@"type" = .proxy,
1409 .name = try self.allocator.dupe(u8, entry.key),
1410 },
1411 .dylib = 0,
1412 };
1413
1414 self.imports.putAssumeCapacityNoClobber(proxy.base.name, &proxy.base);
1415 entry.value.alias = &proxy.base;
1494 try unresolved.ensureCapacity(self.unresolved.count());
1495 for (self.unresolved.items()) |entry| {
1496 unresolved.appendAssumeCapacity(entry.value);
14161497 }
14171498 self.unresolved.clearAndFree(self.allocator);
14181499
1419 // If there are any undefs left, flag an error.
1420 if (self.unresolved.count() > 0) {
1421 for (self.unresolved.items()) |entry| {
1422 log.err("undefined reference to symbol '{s}'", .{entry.key});
1423 log.err(" | referenced in {s}", .{
1424 entry.value.cast(Symbol.Unresolved).?.file.name.?,
1425 });
1500 var has_undefined = false;
1501 while (unresolved.popOrNull()) |undef| {
1502 var found = false;
1503 for (self.dylibs.items) |dylib| {
1504 const proxy = dylib.symbols.get(undef.name) orelse continue;
1505 try self.imports.putNoClobber(self.allocator, proxy.name, proxy);
1506 undef.alias = proxy;
1507 found = true;
1508 }
1509
1510 if (!found) {
1511 // TODO we currently hardcode all unresolved symbols to libSystem
1512 const proxy = try self.allocator.create(Symbol.Proxy);
1513 errdefer self.allocator.destroy(proxy);
1514
1515 proxy.* = .{
1516 .base = .{
1517 .@"type" = .proxy,
1518 .name = try self.allocator.dupe(u8, undef.name),
1519 },
1520 .dylib = null, // TODO null means libSystem
1521 };
1522
1523 try self.imports.putNoClobber(self.allocator, proxy.base.name, &proxy.base);
1524 undef.alias = &proxy.base;
1525
1526 // log.err("undefined reference to symbol '{s}'", .{undef.name});
1527 // log.err(" | referenced in {s}", .{
1528 // undef.cast(Symbol.Unresolved).?.file.name.?,
1529 // });
1530 // has_undefined = true;
14261531 }
1427 return error.UndefinedSymbolReference;
14281532 }
14291533
1534 if (has_undefined) return error.UndefinedSymbolReference;
1535
14301536 // Finally put dyld_stub_binder as an Import
14311537 const dyld_stub_binder = try self.allocator.create(Symbol.Proxy);
14321538 errdefer self.allocator.destroy(dyld_stub_binder);
......@@ -1436,7 +1542,7 @@ fn resolveSymbols(self: *Zld) !void {
14361542 .@"type" = .proxy,
14371543 .name = try self.allocator.dupe(u8, "dyld_stub_binder"),
14381544 },
1439 .dylib = 0,
1545 .dylib = null, // TODO null means libSystem
14401546 };
14411547
14421548 try self.imports.putNoClobber(
......@@ -1997,27 +2103,10 @@ fn populateMetadata(self: *Zld) !void {
19972103
19982104 if (self.libsystem_cmd_index == null) {
19992105 self.libsystem_cmd_index = @intCast(u16, self.load_commands.items.len);
2000 const cmdsize = @intCast(u32, mem.alignForwardGeneric(
2001 u64,
2002 @sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH),
2003 @sizeOf(u64),
2004 ));
2005 // TODO Find a way to work out runtime version from the OS version triple stored in std.Target.
2006 // In the meantime, we're gonna hardcode to the minimum compatibility version of 0.0.0.
2007 const min_version = 0x0;
2008 var dylib_cmd = emptyGenericCommandWithData(macho.dylib_command{
2009 .cmd = macho.LC_LOAD_DYLIB,
2010 .cmdsize = cmdsize,
2011 .dylib = .{
2012 .name = @sizeOf(macho.dylib_command),
2013 .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files
2014 .current_version = min_version,
2015 .compatibility_version = min_version,
2016 },
2017 });
2018 dylib_cmd.data = try self.allocator.alloc(u8, cmdsize - dylib_cmd.inner.dylib.name);
2019 mem.set(u8, dylib_cmd.data, 0);
2020 mem.copy(u8, dylib_cmd.data, mem.spanZ(LIB_SYSTEM_PATH));
2106
2107 var dylib_cmd = try createLoadDylibCommand(self.allocator, mem.spanZ(LIB_SYSTEM_PATH), 2, 0, 0);
2108 errdefer dylib_cmd.deinit(self.allocator);
2109
20212110 try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd });
20222111 }
20232112
......@@ -2080,6 +2169,25 @@ fn populateMetadata(self: *Zld) !void {
20802169 }
20812170}
20822171
2172fn addRpaths(self: *Zld, rpaths: []const []const u8) !void {
2173 for (rpaths) |rpath| {
2174 const cmdsize = @intCast(u32, mem.alignForwardGeneric(
2175 u64,
2176 @sizeOf(macho.rpath_command) + rpath.len,
2177 @sizeOf(u64),
2178 ));
2179 var rpath_cmd = emptyGenericCommandWithData(macho.rpath_command{
2180 .cmd = macho.LC_RPATH,
2181 .cmdsize = cmdsize,
2182 .path = @sizeOf(macho.rpath_command),
2183 });
2184 rpath_cmd.data = try self.allocator.alloc(u8, cmdsize - rpath_cmd.inner.path);
2185 mem.set(u8, rpath_cmd.data, 0);
2186 mem.copy(u8, rpath_cmd.data, rpath);
2187 try self.load_commands.append(self.allocator, .{ .Rpath = rpath_cmd });
2188 }
2189}
2190
20832191fn flush(self: *Zld) !void {
20842192 try self.writeStubHelperCommon();
20852193 try self.resolveRelocsAndWriteSections();
......@@ -2303,7 +2411,10 @@ fn writeBindInfoTable(self: *Zld) !void {
23032411
23042412 for (self.got_entries.items) |sym| {
23052413 if (sym.cast(Symbol.Proxy)) |proxy| {
2306 const dylib_ordinal = proxy.dylib + 1;
2414 const dylib_ordinal = ordinal: {
2415 const dylib = proxy.dylib orelse break :ordinal 1; // TODO embedded libSystem
2416 break :ordinal dylib.ordinal.?;
2417 };
23072418 try pointers.append(.{
23082419 .offset = base_offset + proxy.base.got_index.? * @sizeOf(u64),
23092420 .segment_id = segment_id,
......@@ -2322,7 +2433,10 @@ fn writeBindInfoTable(self: *Zld) !void {
23222433
23232434 const sym = self.imports.get("__tlv_bootstrap") orelse unreachable;
23242435 const proxy = sym.cast(Symbol.Proxy) orelse unreachable;
2325 const dylib_ordinal = proxy.dylib + 1;
2436 const dylib_ordinal = ordinal: {
2437 const dylib = proxy.dylib orelse break :ordinal 1; // TODO embedded libSystem
2438 break :ordinal dylib.ordinal.?;
2439 };
23262440
23272441 try pointers.append(.{
23282442 .offset = base_offset,
......@@ -2364,7 +2478,11 @@ fn writeLazyBindInfoTable(self: *Zld) !void {
23642478
23652479 for (self.stubs.items) |sym| {
23662480 const proxy = sym.cast(Symbol.Proxy) orelse unreachable;
2367 const dylib_ordinal = proxy.dylib + 1;
2481 const dylib_ordinal = ordinal: {
2482 const dylib = proxy.dylib orelse break :ordinal 1; // TODO embedded libSystem
2483 break :ordinal dylib.ordinal.?;
2484 };
2485
23682486 pointers.appendAssumeCapacity(.{
23692487 .offset = base_offset + sym.stubs_index.? * @sizeOf(u64),
23702488 .segment_id = segment_id,
......@@ -2664,11 +2782,15 @@ fn writeSymbolTable(self: *Zld) !void {
26642782
26652783 for (self.imports.items()) |entry| {
26662784 const sym = entry.value;
2785 const ordinal = ordinal: {
2786 const dylib = sym.cast(Symbol.Proxy).?.dylib orelse break :ordinal 1; // TODO handle libSystem
2787 break :ordinal dylib.ordinal.?;
2788 };
26672789 try undefs.append(.{
26682790 .n_strx = try self.makeString(sym.name),
26692791 .n_type = macho.N_UNDF | macho.N_EXT,
26702792 .n_sect = 0,
2671 .n_desc = macho.N_SYMBOL_RESOLVER | macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY,
2793 .n_desc = (ordinal * macho.N_SYMBOL_RESOLVER) | macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY,
26722794 .n_value = 0,
26732795 });
26742796 }
src/link/MachO/commands.zig+61-5
......@@ -24,6 +24,7 @@ pub const LoadCommand = union(enum) {
2424 SourceVersion: macho.source_version_command,
2525 Uuid: macho.uuid_command,
2626 LinkeditData: macho.linkedit_data_command,
27 Rpath: GenericCommandWithData(macho.rpath_command),
2728 Unknown: GenericCommandWithData(macho.load_command),
2829
2930 pub fn read(allocator: *Allocator, reader: anytype) !LoadCommand {
......@@ -38,7 +39,9 @@ pub const LoadCommand = union(enum) {
3839 macho.LC_SEGMENT_64 => LoadCommand{
3940 .Segment = try SegmentCommand.read(allocator, stream.reader()),
4041 },
41 macho.LC_DYLD_INFO, macho.LC_DYLD_INFO_ONLY => LoadCommand{
42 macho.LC_DYLD_INFO,
43 macho.LC_DYLD_INFO_ONLY,
44 => LoadCommand{
4245 .DyldInfoOnly = try stream.reader().readStruct(macho.dyld_info_command),
4346 },
4447 macho.LC_SYMTAB => LoadCommand{
......@@ -47,16 +50,27 @@ pub const LoadCommand = union(enum) {
4750 macho.LC_DYSYMTAB => LoadCommand{
4851 .Dysymtab = try stream.reader().readStruct(macho.dysymtab_command),
4952 },
50 macho.LC_ID_DYLINKER, macho.LC_LOAD_DYLINKER, macho.LC_DYLD_ENVIRONMENT => LoadCommand{
53 macho.LC_ID_DYLINKER,
54 macho.LC_LOAD_DYLINKER,
55 macho.LC_DYLD_ENVIRONMENT,
56 => LoadCommand{
5157 .Dylinker = try GenericCommandWithData(macho.dylinker_command).read(allocator, stream.reader()),
5258 },
53 macho.LC_ID_DYLIB, macho.LC_LOAD_WEAK_DYLIB, macho.LC_LOAD_DYLIB, macho.LC_REEXPORT_DYLIB => LoadCommand{
59 macho.LC_ID_DYLIB,
60 macho.LC_LOAD_WEAK_DYLIB,
61 macho.LC_LOAD_DYLIB,
62 macho.LC_REEXPORT_DYLIB,
63 => LoadCommand{
5464 .Dylib = try GenericCommandWithData(macho.dylib_command).read(allocator, stream.reader()),
5565 },
5666 macho.LC_MAIN => LoadCommand{
5767 .Main = try stream.reader().readStruct(macho.entry_point_command),
5868 },
59 macho.LC_VERSION_MIN_MACOSX, macho.LC_VERSION_MIN_IPHONEOS, macho.LC_VERSION_MIN_WATCHOS, macho.LC_VERSION_MIN_TVOS => LoadCommand{
69 macho.LC_VERSION_MIN_MACOSX,
70 macho.LC_VERSION_MIN_IPHONEOS,
71 macho.LC_VERSION_MIN_WATCHOS,
72 macho.LC_VERSION_MIN_TVOS,
73 => LoadCommand{
6074 .VersionMin = try stream.reader().readStruct(macho.version_min_command),
6175 },
6276 macho.LC_SOURCE_VERSION => LoadCommand{
......@@ -65,9 +79,15 @@ pub const LoadCommand = union(enum) {
6579 macho.LC_UUID => LoadCommand{
6680 .Uuid = try stream.reader().readStruct(macho.uuid_command),
6781 },
68 macho.LC_FUNCTION_STARTS, macho.LC_DATA_IN_CODE, macho.LC_CODE_SIGNATURE => LoadCommand{
82 macho.LC_FUNCTION_STARTS,
83 macho.LC_DATA_IN_CODE,
84 macho.LC_CODE_SIGNATURE,
85 => LoadCommand{
6986 .LinkeditData = try stream.reader().readStruct(macho.linkedit_data_command),
7087 },
88 macho.LC_RPATH => LoadCommand{
89 .Rpath = try GenericCommandWithData(macho.rpath_command).read(allocator, stream.reader()),
90 },
7191 else => LoadCommand{
7292 .Unknown = try GenericCommandWithData(macho.load_command).read(allocator, stream.reader()),
7393 },
......@@ -87,6 +107,7 @@ pub const LoadCommand = union(enum) {
87107 .Segment => |x| x.write(writer),
88108 .Dylinker => |x| x.write(writer),
89109 .Dylib => |x| x.write(writer),
110 .Rpath => |x| x.write(writer),
90111 .Unknown => |x| x.write(writer),
91112 };
92113 }
......@@ -104,6 +125,7 @@ pub const LoadCommand = union(enum) {
104125 .Segment => |x| x.inner.cmd,
105126 .Dylinker => |x| x.inner.cmd,
106127 .Dylib => |x| x.inner.cmd,
128 .Rpath => |x| x.inner.cmd,
107129 .Unknown => |x| x.inner.cmd,
108130 };
109131 }
......@@ -121,6 +143,7 @@ pub const LoadCommand = union(enum) {
121143 .Segment => |x| x.inner.cmdsize,
122144 .Dylinker => |x| x.inner.cmdsize,
123145 .Dylib => |x| x.inner.cmdsize,
146 .Rpath => |x| x.inner.cmdsize,
124147 .Unknown => |x| x.inner.cmdsize,
125148 };
126149 }
......@@ -130,6 +153,7 @@ pub const LoadCommand = union(enum) {
130153 .Segment => |*x| x.deinit(allocator),
131154 .Dylinker => |*x| x.deinit(allocator),
132155 .Dylib => |*x| x.deinit(allocator),
156 .Rpath => |*x| x.deinit(allocator),
133157 .Unknown => |*x| x.deinit(allocator),
134158 else => {},
135159 };
......@@ -153,6 +177,7 @@ pub const LoadCommand = union(enum) {
153177 .Segment => |x| x.eql(other.Segment),
154178 .Dylinker => |x| x.eql(other.Dylinker),
155179 .Dylib => |x| x.eql(other.Dylib),
180 .Rpath => |x| x.eql(other.Rpath),
156181 .Unknown => |x| x.eql(other.Unknown),
157182 };
158183 }
......@@ -282,6 +307,37 @@ pub fn GenericCommandWithData(comptime Cmd: type) type {
282307 };
283308}
284309
310pub fn createLoadDylibCommand(
311 allocator: *Allocator,
312 name: []const u8,
313 timestamp: u32,
314 current_version: u32,
315 compatibility_version: u32,
316) !GenericCommandWithData(macho.dylib_command) {
317 const cmdsize = @intCast(u32, mem.alignForwardGeneric(
318 u64,
319 @sizeOf(macho.dylib_command) + name.len,
320 @sizeOf(u64),
321 ));
322
323 var dylib_cmd = emptyGenericCommandWithData(macho.dylib_command{
324 .cmd = macho.LC_LOAD_DYLIB,
325 .cmdsize = cmdsize,
326 .dylib = .{
327 .name = @sizeOf(macho.dylib_command),
328 .timestamp = timestamp,
329 .current_version = current_version,
330 .compatibility_version = compatibility_version,
331 },
332 });
333 dylib_cmd.data = try allocator.alloc(u8, cmdsize - dylib_cmd.inner.dylib.name);
334
335 mem.set(u8, dylib_cmd.data, 0);
336 mem.copy(u8, dylib_cmd.data, name);
337
338 return dylib_cmd;
339}
340
285341fn testRead(allocator: *Allocator, buffer: []const u8, expected: anytype) !void {
286342 var stream = io.fixedBufferStream(buffer);
287343 var given = try LoadCommand.read(allocator, stream.reader());
test/standalone.zig+1-4
......@@ -9,10 +9,7 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
99 cases.add("test/standalone/main_return_error/error_u8.zig");
1010 cases.add("test/standalone/main_return_error/error_u8_non_zero.zig");
1111 cases.addBuildFile("test/standalone/main_pkg_path/build.zig");
12 if (std.Target.current.os.tag != .macos) {
13 // TODO zld cannot link shared libraries yet.
14 cases.addBuildFile("test/standalone/shared_library/build.zig");
15 }
12 cases.addBuildFile("test/standalone/shared_library/build.zig");
1613 cases.addBuildFile("test/standalone/mix_o_files/build.zig");
1714 cases.addBuildFile("test/standalone/global_linkage/build.zig");
1815 cases.addBuildFile("test/standalone/static_c_lib/build.zig");