Commit 1dac5f5214

Jakub Konka <kubkon@jakubkonka.com>
2021-05-17 17:59:38
zld: parse dylibs as positionals
* add preliminary rpath support * enable shared_library test on x86_64 macOS
1 parent ca77273
Changed files (5)
lib/std/macho.zig
@@ -516,6 +516,19 @@ pub const dylib = extern struct {
     compatibility_version: u32,
 };
 
+/// The rpath_command contains a path which at runtime should be added to the current
+/// run path used to find @rpath prefixed dylibs.
+pub const rpath_command = extern struct {
+    /// LC_RPATH
+    cmd: u32,
+
+    /// includes string
+    cmdsize: u32,
+
+    /// path to add to run path
+    path: u32,
+};
+
 /// The segment load command indicates that a part of this file is to be
 /// mapped into the task's address space.  The size of this segment in memory,
 /// vmsize, maybe equal to or larger than the amount to map from this file,
src/link/MachO/commands.zig
@@ -24,6 +24,7 @@ pub const LoadCommand = union(enum) {
     SourceVersion: macho.source_version_command,
     Uuid: macho.uuid_command,
     LinkeditData: macho.linkedit_data_command,
+    Rpath: GenericCommandWithData(macho.rpath_command),
     Unknown: GenericCommandWithData(macho.load_command),
 
     pub fn read(allocator: *Allocator, reader: anytype) !LoadCommand {
@@ -84,6 +85,9 @@ pub const LoadCommand = union(enum) {
             => LoadCommand{
                 .LinkeditData = try stream.reader().readStruct(macho.linkedit_data_command),
             },
+            macho.LC_RPATH => LoadCommand{
+                .Rpath = try GenericCommandWithData(macho.rpath_command).read(allocator, stream.reader()),
+            },
             else => LoadCommand{
                 .Unknown = try GenericCommandWithData(macho.load_command).read(allocator, stream.reader()),
             },
@@ -103,6 +107,7 @@ pub const LoadCommand = union(enum) {
             .Segment => |x| x.write(writer),
             .Dylinker => |x| x.write(writer),
             .Dylib => |x| x.write(writer),
+            .Rpath => |x| x.write(writer),
             .Unknown => |x| x.write(writer),
         };
     }
@@ -120,6 +125,7 @@ pub const LoadCommand = union(enum) {
             .Segment => |x| x.inner.cmd,
             .Dylinker => |x| x.inner.cmd,
             .Dylib => |x| x.inner.cmd,
+            .Rpath => |x| x.inner.cmd,
             .Unknown => |x| x.inner.cmd,
         };
     }
@@ -137,6 +143,7 @@ pub const LoadCommand = union(enum) {
             .Segment => |x| x.inner.cmdsize,
             .Dylinker => |x| x.inner.cmdsize,
             .Dylib => |x| x.inner.cmdsize,
+            .Rpath => |x| x.inner.cmdsize,
             .Unknown => |x| x.inner.cmdsize,
         };
     }
@@ -146,6 +153,7 @@ pub const LoadCommand = union(enum) {
             .Segment => |*x| x.deinit(allocator),
             .Dylinker => |*x| x.deinit(allocator),
             .Dylib => |*x| x.deinit(allocator),
+            .Rpath => |*x| x.deinit(allocator),
             .Unknown => |*x| x.deinit(allocator),
             else => {},
         };
@@ -169,6 +177,7 @@ pub const LoadCommand = union(enum) {
             .Segment => |x| x.eql(other.Segment),
             .Dylinker => |x| x.eql(other.Dylinker),
             .Dylib => |x| x.eql(other.Dylib),
+            .Rpath => |x| x.eql(other.Rpath),
             .Unknown => |x| x.eql(other.Unknown),
         };
     }
src/link/MachO/Zld.zig
@@ -186,7 +186,12 @@ pub fn closeFiles(self: Zld) void {
     if (self.file) |f| f.close();
 }
 
-pub fn link(self: *Zld, files: []const []const u8, shared_libs: []const []const u8, out_path: []const u8) !void {
+const LinkArgs = struct {
+    shared_libs: []const []const u8,
+    rpaths: []const []const u8,
+};
+
+pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8, args: LinkArgs) !void {
     if (files.len == 0) return error.NoInputFiles;
     if (out_path.len == 0) return error.EmptyOutputPath;
 
@@ -222,8 +227,9 @@ pub fn link(self: *Zld, files: []const []const u8, shared_libs: []const []const
     });
 
     try self.populateMetadata();
+    try self.addRpaths(args.rpaths);
     try self.parseInputFiles(files);
-    try self.parseDylibs(shared_libs);
+    try self.parseDylibs(args.shared_libs);
     try self.resolveSymbols();
     try self.resolveStubsAndGotEntries();
     try self.updateMetadata();
@@ -241,6 +247,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
         kind: enum {
             object,
             archive,
+            dylib,
         },
         file: fs.File,
         name: []const u8,
@@ -248,7 +255,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
     var classified = std.ArrayList(Input).init(self.allocator);
     defer classified.deinit();
 
-    // First, classify input files as either object or archive.
+    // First, classify input files: object, archive or dylib.
     for (files) |file_name| {
         const file = try fs.cwd().openFile(file_name, .{});
         const full_path = full_path: {
@@ -289,6 +296,22 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
             continue;
         }
 
+        try_dylib: {
+            const header = try file.reader().readStruct(macho.mach_header_64);
+            if (header.filetype != macho.MH_DYLIB) {
+                try file.seekTo(0);
+                break :try_dylib;
+            }
+
+            try file.seekTo(0);
+            try classified.append(.{
+                .kind = .dylib,
+                .file = file,
+                .name = full_path,
+            });
+            continue;
+        }
+
         log.debug("unexpected input file of unknown type '{s}'", .{file_name});
     }
 
@@ -317,6 +340,35 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
                 try archive.parse();
                 try self.archives.append(self.allocator, archive);
             },
+            .dylib => {
+                const dylib = try self.allocator.create(Dylib);
+                errdefer self.allocator.destroy(dylib);
+
+                dylib.* = Dylib.init(self.allocator);
+                dylib.arch = self.arch.?;
+                dylib.name = input.name;
+                dylib.file = input.file;
+
+                const ordinal = @intCast(u16, self.dylibs.items.len);
+                dylib.ordinal = ordinal + 2; // TODO +2 since 1 is reserved for libSystem
+
+                // TODO Defer parsing of the dylibs until they are actually needed
+                try dylib.parse();
+                try self.dylibs.append(self.allocator, dylib);
+
+                // Add LC_LOAD_DYLIB command
+                const dylib_id = dylib.id orelse unreachable;
+                var dylib_cmd = try createLoadDylibCommand(
+                    self.allocator,
+                    dylib_id.name,
+                    dylib_id.timestamp,
+                    dylib_id.current_version,
+                    dylib_id.compatibility_version,
+                );
+                errdefer dylib_cmd.deinit(self.allocator);
+
+                try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd });
+            },
         }
     }
 }
@@ -2117,6 +2169,25 @@ fn populateMetadata(self: *Zld) !void {
     }
 }
 
+fn addRpaths(self: *Zld, rpaths: []const []const u8) !void {
+    for (rpaths) |rpath| {
+        const cmdsize = @intCast(u32, mem.alignForwardGeneric(
+            u64,
+            @sizeOf(macho.rpath_command) + rpath.len,
+            @sizeOf(u64),
+        ));
+        var rpath_cmd = emptyGenericCommandWithData(macho.rpath_command{
+            .cmd = macho.LC_RPATH,
+            .cmdsize = cmdsize,
+            .path = @sizeOf(macho.rpath_command),
+        });
+        rpath_cmd.data = try self.allocator.alloc(u8, cmdsize - rpath_cmd.inner.path);
+        mem.set(u8, rpath_cmd.data, 0);
+        mem.copy(u8, rpath_cmd.data, rpath);
+        try self.load_commands.append(self.allocator, .{ .Rpath = rpath_cmd });
+    }
+}
+
 fn flush(self: *Zld) !void {
     try self.writeStubHelperCommon();
     try self.resolveRelocsAndWriteSections();
src/link/MachO.zig
@@ -756,6 +756,19 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
                 }
             }
 
+            // rpaths
+            var rpath_table = std.StringArrayHashMap(void).init(arena);
+            for (self.base.options.rpath_list) |rpath| {
+                if (rpath_table.contains(rpath)) continue;
+                try rpath_table.putNoClobber(rpath, {});
+            }
+
+            var rpaths = std.ArrayList([]const u8) .init(arena);
+            try rpaths.ensureCapacity(rpath_table.count());
+            for (rpath_table.items()) |entry| {
+                rpaths.appendAssumeCapacity(entry.key);
+            }
+
             if (self.base.options.verbose_link) {
                 var argv = std.ArrayList([]const u8).init(arena);
 
@@ -767,6 +780,11 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
                     try argv.append(syslibroot);
                 }
 
+                for (rpaths.items) |rpath| {
+                    try argv.append("-rpath");
+                    try argv.append(rpath);
+                }
+
                 try argv.appendSlice(positionals.items);
 
                 try argv.append("-o");
@@ -783,7 +801,10 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
                 Compilation.dump_argv(argv.items);
             }
 
-            try zld.link(positionals.items, shared_libs.items, full_out_path);
+            try zld.link(positionals.items, full_out_path, .{
+                .shared_libs = shared_libs.items,
+                .rpaths = rpaths.items,
+            });
 
             break :outer;
         }
test/standalone.zig
@@ -9,10 +9,7 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
     cases.add("test/standalone/main_return_error/error_u8.zig");
     cases.add("test/standalone/main_return_error/error_u8_non_zero.zig");
     cases.addBuildFile("test/standalone/main_pkg_path/build.zig");
-    if (std.Target.current.os.tag != .macos) {
-        // TODO zld cannot link shared libraries yet.
-        cases.addBuildFile("test/standalone/shared_library/build.zig");
-    }
+    cases.addBuildFile("test/standalone/shared_library/build.zig");
     cases.addBuildFile("test/standalone/mix_o_files/build.zig");
     cases.addBuildFile("test/standalone/global_linkage/build.zig");
     cases.addBuildFile("test/standalone/static_c_lib/build.zig");