From f353b59efac62dd8bc2ec966e6ea46131a64b19a Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 27 Jun 2022 23:56:45 +0200 Subject: [PATCH 1/5] macho: discriminate between normal and weak dylibs Parse `-weak-lx` and `-weak_framework x` in the CLI. --- lib/std/macho.zig | 4 +- src/link.zig | 1 + src/link/MachO.zig | 113 ++++++++++++++++++++++++++------------- src/link/MachO/Dylib.zig | 26 ++++++--- src/main.zig | 41 +++++++++++--- 5 files changed, 134 insertions(+), 51 deletions(-) diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 1f8b7e8f33..2ca6fe9d0a 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -2085,11 +2085,13 @@ pub fn GenericCommandWithData(comptime Cmd: type) type { pub fn createLoadDylibCommand( allocator: Allocator, + cmd_id: LC, name: []const u8, timestamp: u32, current_version: u32, compatibility_version: u32, ) !GenericCommandWithData(dylib_command) { + assert(cmd_id == .LOAD_DYLIB or cmd_id == .LOAD_WEAK_DYLIB or cmd_id == .REEXPORT_DYLIB or cmd_id == .ID_DYLIB); const cmdsize = @intCast(u32, mem.alignForwardGeneric( u64, @sizeOf(dylib_command) + name.len + 1, // +1 for nul @@ -2097,7 +2099,7 @@ pub fn createLoadDylibCommand( )); var dylib_cmd = emptyGenericCommandWithData(dylib_command{ - .cmd = .LOAD_DYLIB, + .cmd = cmd_id, .cmdsize = cmdsize, .dylib = .{ .name = @sizeOf(dylib_command), diff --git a/src/link.zig b/src/link.zig index c3d1d216c0..da6e8c53ed 100644 --- a/src/link.zig +++ b/src/link.zig @@ -21,6 +21,7 @@ const TypedValue = @import("TypedValue.zig"); pub const SystemLib = struct { needed: bool = false, + weak: bool = false, }; pub const CacheMode = enum { incremental, whole }; diff --git a/src/link/MachO.zig b/src/link/MachO.zig index d660d2ce7e..a406923329 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -52,6 +52,11 @@ pub const SearchStrategy = enum { dylibs_first, }; +const SystemLib = struct { + needed: bool = false, + weak: bool = false, +}; + base: File, /// If this is not null, an object file is created by LLVM and linked with LLD afterwards. @@ -768,7 +773,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No } // Shared and static libraries passed via `-l` flag. - var candidate_libs = std.StringArrayHashMap(Compilation.SystemLib).init(arena); + var candidate_libs = std.StringArrayHashMap(SystemLib).init(arena); const system_lib_names = self.base.options.system_libs.keys(); for (system_lib_names) |system_lib_name| { @@ -781,7 +786,10 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No } const system_lib_info = self.base.options.system_libs.get(system_lib_name).?; - try candidate_libs.put(system_lib_name, system_lib_info); + try candidate_libs.put(system_lib_name, .{ + .needed = system_lib_info.needed, + .weak = system_lib_info.weak, + }); } var lib_dirs = std.ArrayList([]const u8).init(arena); @@ -793,7 +801,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No } } - var libs = std.StringArrayHashMap(Compilation.SystemLib).init(arena); + var libs = std.StringArrayHashMap(SystemLib).init(arena); // Assume ld64 default -search_paths_first if no strategy specified. const search_strategy = self.base.options.search_strategy orelse .paths_first; @@ -890,7 +898,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No for (framework_dirs.items) |dir| { for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| { if (try resolveFramework(arena, dir, f_name, ext)) |full_path| { - try libs.put(full_path, self.base.options.frameworks.get(f_name).?); + const info = self.base.options.frameworks.get(f_name).?; + try libs.put(full_path, .{ + .needed = info.needed, + .weak = info.weak, + }); continue :outer; } } @@ -1026,9 +1038,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No try argv.append("-lc"); for (self.base.options.system_libs.keys()) |l_name| { - const needed = self.base.options.system_libs.get(l_name).?.needed; - const arg = if (needed) + const info = self.base.options.system_libs.get(l_name).?; + const arg = if (info.needed) try std.fmt.allocPrint(arena, "-needed-l{s}", .{l_name}) + else if (info.weak) + try std.fmt.allocPrint(arena, "-weak-l{s}", .{l_name}) else try std.fmt.allocPrint(arena, "-l{s}", .{l_name}); try argv.append(arg); @@ -1039,9 +1053,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No } for (self.base.options.frameworks.keys()) |framework| { - const needed = self.base.options.frameworks.get(framework).?.needed; - const arg = if (needed) + const info = self.base.options.frameworks.get(framework).?; + const arg = if (info.needed) try std.fmt.allocPrint(arena, "-needed_framework {s}", .{framework}) + else if (info.weak) + try std.fmt.allocPrint(arena, "-weak_framework {s}", .{framework}) else try std.fmt.allocPrint(arena, "-framework {s}", .{framework}); try argv.append(arg); @@ -1063,7 +1079,10 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No Compilation.dump_argv(argv.items); } - var dependent_libs = std.fifo.LinearFifo(Dylib.Id, .Dynamic).init(self.base.allocator); + var dependent_libs = std.fifo.LinearFifo(struct { + id: Dylib.Id, + parent: u16, + }, .Dynamic).init(self.base.allocator); defer dependent_libs.deinit(); try self.parseInputFiles(positionals.items, self.base.options.sysroot, &dependent_libs); try self.parseAndForceLoadStaticArchives(must_link_archives.keys()); @@ -1389,13 +1408,18 @@ const ParseDylibError = error{ const DylibCreateOpts = struct { syslibroot: ?[]const u8, - dependent_libs: *std.fifo.LinearFifo(Dylib.Id, .Dynamic), id: ?Dylib.Id = null, - is_dependent: bool = false, - is_needed: bool = false, + dependent: bool = false, + needed: bool = false, + weak: bool = false, }; -pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDylibError!bool { +pub fn parseDylib( + self: *MachO, + path: []const u8, + dependent_libs: anytype, + opts: DylibCreateOpts, +) ParseDylibError!bool { const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { error.FileNotFound => return false, else => |e| return e, @@ -1405,12 +1429,19 @@ pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDy const name = try self.base.allocator.dupe(u8, path); errdefer self.base.allocator.free(name); + const dylib_id = @intCast(u16, self.dylibs.items.len); var dylib = Dylib{ .name = name, .file = file, + .weak = opts.weak, }; - dylib.parse(self.base.allocator, self.base.options.target, opts.dependent_libs) catch |err| switch (err) { + dylib.parse( + self.base.allocator, + self.base.options.target, + dylib_id, + dependent_libs, + ) catch |err| switch (err) { error.EndOfStream, error.NotDylib => { try file.seekTo(0); @@ -1420,7 +1451,13 @@ pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDy }; defer lib_stub.deinit(); - try dylib.parseFromStub(self.base.allocator, self.base.options.target, lib_stub, opts.dependent_libs); + try dylib.parseFromStub( + self.base.allocator, + self.base.options.target, + lib_stub, + dylib_id, + dependent_libs, + ); }, else => |e| return e, }; @@ -1438,13 +1475,12 @@ pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDy } } - const dylib_id = @intCast(u16, self.dylibs.items.len); try self.dylibs.append(self.base.allocator, dylib); try self.dylibs_map.putNoClobber(self.base.allocator, dylib.id.?.name, dylib_id); const should_link_dylib_even_if_unreachable = blk: { - if (self.base.options.dead_strip_dylibs and !opts.is_needed) break :blk false; - break :blk !(opts.is_dependent or self.referenced_dylibs.contains(dylib_id)); + if (self.base.options.dead_strip_dylibs and !opts.needed) break :blk false; + break :blk !(opts.dependent or self.referenced_dylibs.contains(dylib_id)); }; if (should_link_dylib_even_if_unreachable) { @@ -1467,9 +1503,8 @@ fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const if (try self.parseObject(full_path)) continue; if (try self.parseArchive(full_path, false)) continue; - if (try self.parseDylib(full_path, .{ + if (try self.parseDylib(full_path, dependent_libs, .{ .syslibroot = syslibroot, - .dependent_libs = dependent_libs, })) continue; log.warn("unknown filetype for positional input file: '{s}'", .{file_name}); @@ -1494,17 +1529,17 @@ fn parseAndForceLoadStaticArchives(self: *MachO, files: []const []const u8) !voi fn parseLibs( self: *MachO, lib_names: []const []const u8, - lib_infos: []const Compilation.SystemLib, + lib_infos: []const SystemLib, syslibroot: ?[]const u8, dependent_libs: anytype, ) !void { for (lib_names) |lib, i| { const lib_info = lib_infos[i]; log.debug("parsing lib path '{s}'", .{lib}); - if (try self.parseDylib(lib, .{ + if (try self.parseDylib(lib, dependent_libs, .{ .syslibroot = syslibroot, - .dependent_libs = dependent_libs, - .is_needed = lib_info.needed, + .needed = lib_info.needed, + .weak = lib_info.weak, })) continue; if (try self.parseArchive(lib, false)) continue; @@ -1522,20 +1557,21 @@ fn parseDependentLibs(self: *MachO, syslibroot: ?[]const u8, dependent_libs: any const arena = arena_alloc.allocator(); defer arena_alloc.deinit(); - while (dependent_libs.readItem()) |*id| { - defer id.deinit(self.base.allocator); + while (dependent_libs.readItem()) |*dep_id| { + defer dep_id.id.deinit(self.base.allocator); - if (self.dylibs_map.contains(id.name)) continue; + if (self.dylibs_map.contains(dep_id.id.name)) continue; + const weak = self.dylibs.items[dep_id.parent].weak; const has_ext = blk: { - const basename = fs.path.basename(id.name); + const basename = fs.path.basename(dep_id.id.name); break :blk mem.lastIndexOfScalar(u8, basename, '.') != null; }; - const extension = if (has_ext) fs.path.extension(id.name) else ""; + const extension = if (has_ext) fs.path.extension(dep_id.id.name) else ""; const without_ext = if (has_ext) blk: { - const index = mem.lastIndexOfScalar(u8, id.name, '.') orelse unreachable; - break :blk id.name[0..index]; - } else id.name; + const index = mem.lastIndexOfScalar(u8, dep_id.id.name, '.') orelse unreachable; + break :blk dep_id.id.name[0..index]; + } else dep_id.id.name; for (&[_][]const u8{ extension, ".tbd" }) |ext| { const with_ext = try std.fmt.allocPrint(arena, "{s}{s}", .{ without_ext, ext }); @@ -1543,15 +1579,15 @@ fn parseDependentLibs(self: *MachO, syslibroot: ?[]const u8, dependent_libs: any log.debug("trying dependency at fully resolved path {s}", .{full_path}); - const did_parse_successfully = try self.parseDylib(full_path, .{ - .id = id.*, + const did_parse_successfully = try self.parseDylib(full_path, dependent_libs, .{ + .id = dep_id.id, .syslibroot = syslibroot, - .is_dependent = true, - .dependent_libs = dependent_libs, + .dependent = true, + .weak = weak, }); if (did_parse_successfully) break; } else { - log.warn("unable to resolve dependency {s}", .{id.name}); + log.warn("unable to resolve dependency {s}", .{dep_id.id.name}); } } } @@ -3441,6 +3477,7 @@ fn addLoadDylibLC(self: *MachO, id: u16) !void { const dylib_id = dylib.id orelse unreachable; var dylib_cmd = try macho.createLoadDylibCommand( self.base.allocator, + if (dylib.weak) .LOAD_WEAK_DYLIB else .LOAD_DYLIB, dylib_id.name, dylib_id.timestamp, dylib_id.current_version, @@ -4885,13 +4922,13 @@ fn populateMissingMetadata(self: *MachO) !void { std.builtin.Version{ .major = 1, .minor = 0, .patch = 0 }; var dylib_cmd = try macho.createLoadDylibCommand( self.base.allocator, + .ID_DYLIB, install_name, 2, current_version.major << 16 | current_version.minor << 8 | current_version.patch, compat_version.major << 16 | compat_version.minor << 8 | compat_version.patch, ); errdefer dylib_cmd.deinit(self.base.allocator); - dylib_cmd.inner.cmd = .ID_DYLIB; try self.load_commands.append(self.base.allocator, .{ .dylib = dylib_cmd }); self.load_commands_dirty = true; } diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig index 801f810f80..ba519aac0a 100644 --- a/src/link/MachO/Dylib.zig +++ b/src/link/MachO/Dylib.zig @@ -30,6 +30,7 @@ dysymtab_cmd_index: ?u16 = null, id_cmd_index: ?u16 = null, id: ?Id = null, +weak: bool = false, /// Parsed symbol table represented as hash map of symbols' /// names. We can and should defer creating *Symbols until @@ -141,7 +142,13 @@ pub fn deinit(self: *Dylib, allocator: Allocator) void { } } -pub fn parse(self: *Dylib, allocator: Allocator, target: std.Target, dependent_libs: anytype) !void { +pub fn parse( + self: *Dylib, + allocator: Allocator, + target: std.Target, + dylib_id: u16, + dependent_libs: anytype, +) !void { log.debug("parsing shared library '{s}'", .{self.name}); self.library_offset = try fat.getLibraryOffset(self.file.reader(), target); @@ -163,12 +170,18 @@ pub fn parse(self: *Dylib, allocator: Allocator, target: std.Target, dependent_l return error.MismatchedCpuArchitecture; } - try self.readLoadCommands(allocator, reader, dependent_libs); + try self.readLoadCommands(allocator, reader, dylib_id, dependent_libs); try self.parseId(allocator); try self.parseSymbols(allocator); } -fn readLoadCommands(self: *Dylib, allocator: Allocator, reader: anytype, dependent_libs: anytype) !void { +fn readLoadCommands( + self: *Dylib, + allocator: Allocator, + reader: anytype, + dylib_id: u16, + dependent_libs: anytype, +) !void { const should_lookup_reexports = self.header.?.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0; try self.load_commands.ensureUnusedCapacity(allocator, self.header.?.ncmds); @@ -190,7 +203,7 @@ fn readLoadCommands(self: *Dylib, allocator: Allocator, reader: anytype, depende if (should_lookup_reexports) { // Parse install_name to dependent dylib. var id = try Id.fromLoadCommand(allocator, cmd.dylib); - try dependent_libs.writeItem(id); + try dependent_libs.writeItem(.{ .id = id, .parent = dylib_id }); } }, else => { @@ -338,6 +351,7 @@ pub fn parseFromStub( allocator: Allocator, target: std.Target, lib_stub: LibStub, + dylib_id: u16, dependent_libs: anytype, ) !void { if (lib_stub.inner.len == 0) return error.EmptyStubFile; @@ -417,7 +431,7 @@ pub fn parseFromStub( log.debug(" (found re-export '{s}')", .{lib}); var dep_id = try Id.default(allocator, lib); - try dependent_libs.writeItem(dep_id); + try dependent_libs.writeItem(.{ .id = dep_id, .parent = dylib_id }); } } } @@ -522,7 +536,7 @@ pub fn parseFromStub( log.debug(" (found re-export '{s}')", .{lib}); var dep_id = try Id.default(allocator, lib); - try dependent_libs.writeItem(dep_id); + try dependent_libs.writeItem(.{ .id = dep_id, .parent = dylib_id }); } } } diff --git a/src/main.zig b/src/main.zig index 749534416f..ae1b95a4aa 100644 --- a/src/main.zig +++ b/src/main.zig @@ -443,9 +443,12 @@ const usage_build_generic = \\ --subsystem [subsystem] (Windows) /SUBSYSTEM: to the linker \\ --stack [size] Override default stack size \\ --image-base [addr] Set base address for executable image + \\ -weak-l[lib] (Darwin) link against system library and mark it and all referenced symbols as weak + \\ -weak_library [lib] \\ -framework [name] (Darwin) link against framework \\ -needed_framework [name] (Darwin) link against framework (even if unused) \\ -needed_library [lib] (Darwin) link against system library (even if unused) + \\ -weak_framework [name] (Darwin) link against framework and mark it and all referenced symbols as weak \\ -F[dir] (Darwin) add search path for frameworks \\ -install_name=[value] (Darwin) add dylib's install name \\ --entitlements [path] (Darwin) add path to entitlements file for embedding in code signature @@ -916,7 +919,12 @@ fn buildOutputType( const path = args_iter.next() orelse { fatal("expected parameter after {s}", .{arg}); }; - try frameworks.put(gpa, path, .{ .needed = false }); + try frameworks.put(gpa, path, .{}); + } else if (mem.eql(u8, arg, "-weak_framework")) { + const path = args_iter.next() orelse { + fatal("expected parameter after {s}", .{arg}); + }; + try frameworks.put(gpa, path, .{ .weak = true }); } else if (mem.eql(u8, arg, "-needed_framework")) { const path = args_iter.next() orelse { fatal("expected parameter after {s}", .{arg}); @@ -962,7 +970,7 @@ fn buildOutputType( }; // We don't know whether this library is part of libc or libc++ until // we resolve the target, so we simply append to the list for now. - try system_libs.put(next_arg, .{ .needed = false }); + try system_libs.put(next_arg, .{}); } else if (mem.eql(u8, arg, "--needed-library") or mem.eql(u8, arg, "-needed-l") or mem.eql(u8, arg, "-needed_library")) @@ -971,6 +979,11 @@ fn buildOutputType( fatal("expected parameter after {s}", .{arg}); }; try system_libs.put(next_arg, .{ .needed = true }); + } else if (mem.eql(u8, arg, "-weak_library") or mem.eql(u8, arg, "-weak-l")) { + const next_arg = args_iter.next() orelse { + fatal("expected parameter after {s}", .{arg}); + }; + try system_libs.put(next_arg, .{ .weak = true }); } else if (mem.eql(u8, arg, "-D") or mem.eql(u8, arg, "-isystem") or mem.eql(u8, arg, "-I") or @@ -1300,9 +1313,11 @@ fn buildOutputType( } else if (mem.startsWith(u8, arg, "-l")) { // We don't know whether this library is part of libc or libc++ until // we resolve the target, so we simply append to the list for now. - try system_libs.put(arg["-l".len..], .{ .needed = false }); + try system_libs.put(arg["-l".len..], .{}); } else if (mem.startsWith(u8, arg, "-needed-l")) { try system_libs.put(arg["-needed-l".len..], .{ .needed = true }); + } else if (mem.startsWith(u8, arg, "-weak-l")) { + try system_libs.put(arg["-weak-l".len..], .{ .weak = true }); } else if (mem.startsWith(u8, arg, "-D") or mem.startsWith(u8, arg, "-I")) { @@ -1596,7 +1611,7 @@ fn buildOutputType( try clang_argv.appendSlice(it.other_args); }, .framework_dir => try framework_dirs.append(it.only_arg), - .framework => try frameworks.put(gpa, it.only_arg, .{ .needed = false }), + .framework => try frameworks.put(gpa, it.only_arg, .{}), .nostdlibinc => want_native_include_dirs = false, .strip => strip = true, .exec_model => { @@ -1879,12 +1894,18 @@ fn buildOutputType( ) catch |err| { fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) }); }; - } else if (mem.eql(u8, arg, "-framework") or mem.eql(u8, arg, "-weak_framework")) { + } else if (mem.eql(u8, arg, "-framework")) { i += 1; if (i >= linker_args.items.len) { fatal("expected linker arg after '{s}'", .{arg}); } - try frameworks.put(gpa, linker_args.items[i], .{ .needed = false }); + try frameworks.put(gpa, linker_args.items[i], .{}); + } else if (mem.eql(u8, arg, "-weak_framework")) { + i += 1; + if (i >= linker_args.items.len) { + fatal("expected linker arg after '{s}'", .{arg}); + } + try frameworks.put(gpa, linker_args.items[i], .{ .weak = true }); } else if (mem.eql(u8, arg, "-needed_framework")) { i += 1; if (i >= linker_args.items.len) { @@ -1897,6 +1918,14 @@ fn buildOutputType( fatal("expected linker arg after '{s}'", .{arg}); } try system_libs.put(linker_args.items[i], .{ .needed = true }); + } else if (mem.startsWith(u8, arg, "-weak-l")) { + try system_libs.put(arg["-weak-l".len..], .{ .weak = true }); + } else if (mem.eql(u8, arg, "-weak_library")) { + i += 1; + if (i >= linker_args.items.len) { + fatal("expected linker arg after '{s}'", .{arg}); + } + try system_libs.put(linker_args.items[i], .{ .weak = true }); } else if (mem.eql(u8, arg, "-compatibility_version")) { i += 1; if (i >= linker_args.items.len) { From cd5dcfbf41dc6f40a853b2a207bd3ab0ea0dc0ee Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 28 Jun 2022 09:12:43 +0200 Subject: [PATCH 2/5] macho: annotate weak imports when linking with weak lib/framework --- src/link/MachO.zig | 18 ++++++++++++++++-- src/link/MachO/bind.zig | 5 +++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index a406923329..f5e1bf4b4d 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -3117,6 +3117,10 @@ fn resolveSymbolsInDylibs(self: *MachO) !void { undef.n_type |= macho.N_EXT; undef.n_desc = @intCast(u16, ordinal + 1) * macho.N_SYMBOL_RESOLVER; + if (dylib.weak) { + undef.n_desc |= macho.N_WEAK_REF; + } + if (self.unresolved.fetchSwapRemove(resolv.where_index)) |entry| outer_blk: { switch (entry.value) { .none => {}, @@ -5806,11 +5810,16 @@ fn writeDyldInfoData(self: *MachO) !void { }, .undef => { const bind_sym = self.undefs.items[resolv.where_index]; + var flags: u4 = 0; + if (bind_sym.weakRef()) { + flags |= @truncate(u4, macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT); + } try bind_pointers.append(.{ .offset = binding.offset + base_offset, .segment_id = match.seg, - .dylib_ordinal = @divExact(@bitCast(i16, bind_sym.n_desc), macho.N_SYMBOL_RESOLVER), + .dylib_ordinal = @divTrunc(@bitCast(i16, bind_sym.n_desc), macho.N_SYMBOL_RESOLVER), .name = self.getString(bind_sym.n_strx), + .bind_flags = flags, }); }, } @@ -5828,11 +5837,16 @@ fn writeDyldInfoData(self: *MachO) !void { }, .undef => { const bind_sym = self.undefs.items[resolv.where_index]; + var flags: u4 = 0; + if (bind_sym.weakRef()) { + flags |= @truncate(u4, macho.BIND_SYMBOL_FLAGS_WEAK_IMPORT); + } try lazy_bind_pointers.append(.{ .offset = binding.offset + base_offset, .segment_id = match.seg, - .dylib_ordinal = @divExact(@bitCast(i16, bind_sym.n_desc), macho.N_SYMBOL_RESOLVER), + .dylib_ordinal = @divTrunc(@bitCast(i16, bind_sym.n_desc), macho.N_SYMBOL_RESOLVER), .name = self.getString(bind_sym.n_strx), + .bind_flags = flags, }); }, } diff --git a/src/link/MachO/bind.zig b/src/link/MachO/bind.zig index 14a5ba3e30..9e34581a23 100644 --- a/src/link/MachO/bind.zig +++ b/src/link/MachO/bind.zig @@ -7,6 +7,7 @@ pub const Pointer = struct { segment_id: u16, dylib_ordinal: ?i64 = null, name: ?[]const u8 = null, + bind_flags: u4 = 0, }; pub fn rebaseInfoSize(pointers: []const Pointer) !u64 { @@ -73,7 +74,7 @@ pub fn writeBindInfo(pointers: []const Pointer, writer: anytype) !void { } try writer.writeByte(macho.BIND_OPCODE_SET_TYPE_IMM | @truncate(u4, macho.BIND_TYPE_POINTER)); - try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags. + try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | pointer.bind_flags); try writer.writeAll(pointer.name.?); try writer.writeByte(0); @@ -127,7 +128,7 @@ pub fn writeLazyBindInfo(pointers: []const Pointer, writer: anytype) !void { try writer.writeByte(macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | @truncate(u4, @bitCast(u64, pointer.dylib_ordinal.?))); } - try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM); // TODO Sometimes we might want to add flags. + try writer.writeByte(macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | pointer.bind_flags); try writer.writeAll(pointer.name.?); try writer.writeByte(0); From 20bd722464699bd11a05fe8e250f9f8086853cf2 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 28 Jun 2022 09:13:45 +0200 Subject: [PATCH 3/5] build: handle weakly imported libs and frameworks --- lib/std/build.zig | 72 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 11 deletions(-) diff --git a/lib/std/build.zig b/lib/std/build.zig index ab4472d01e..96abc51fac 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -1483,7 +1483,7 @@ pub const LibExeObjStep = struct { lib_paths: ArrayList([]const u8), rpaths: ArrayList([]const u8), framework_dirs: ArrayList([]const u8), - frameworks: StringHashMap(bool), + frameworks: StringHashMap(FrameworkLinkInfo), verbose_link: bool, verbose_cc: bool, emit_analysis: EmitOption = .default, @@ -1643,6 +1643,7 @@ pub const LibExeObjStep = struct { pub const SystemLib = struct { name: []const u8, needed: bool, + weak: bool, use_pkg_config: enum { /// Don't use pkg-config, just pass -lfoo where foo is name. no, @@ -1655,6 +1656,11 @@ pub const LibExeObjStep = struct { }, }; + const FrameworkLinkInfo = struct { + needed: bool = false, + weak: bool = false, + }; + pub const IncludeDir = union(enum) { raw_path: []const u8, raw_path_system: []const u8, @@ -1744,7 +1750,7 @@ pub const LibExeObjStep = struct { .kind = kind, .root_src = root_src, .name = name, - .frameworks = StringHashMap(bool).init(builder.allocator), + .frameworks = StringHashMap(FrameworkLinkInfo).init(builder.allocator), .step = Step.init(base_id, name, builder.allocator, make), .version = ver, .out_filename = undefined, @@ -1893,11 +1899,19 @@ pub const LibExeObjStep = struct { } pub fn linkFramework(self: *LibExeObjStep, framework_name: []const u8) void { - self.frameworks.put(self.builder.dupe(framework_name), false) catch unreachable; + self.frameworks.put(self.builder.dupe(framework_name), .{}) catch unreachable; } pub fn linkFrameworkNeeded(self: *LibExeObjStep, framework_name: []const u8) void { - self.frameworks.put(self.builder.dupe(framework_name), true) catch unreachable; + self.frameworks.put(self.builder.dupe(framework_name), .{ + .needed = true, + }) catch unreachable; + } + + pub fn linkFrameworkWeak(self: *LibExeObjStep, framework_name: []const u8) void { + self.frameworks.put(self.builder.dupe(framework_name), .{ + .weak = true, + }) catch unreachable; } /// Returns whether the library, executable, or object depends on a particular system library. @@ -1939,6 +1953,7 @@ pub const LibExeObjStep = struct { .system_lib = .{ .name = "c", .needed = false, + .weak = false, .use_pkg_config = .no, }, }) catch unreachable; @@ -1952,6 +1967,7 @@ pub const LibExeObjStep = struct { .system_lib = .{ .name = "c++", .needed = false, + .weak = false, .use_pkg_config = .no, }, }) catch unreachable; @@ -1977,6 +1993,7 @@ pub const LibExeObjStep = struct { .system_lib = .{ .name = self.builder.dupe(name), .needed = false, + .weak = false, .use_pkg_config = .no, }, }) catch unreachable; @@ -1989,6 +2006,20 @@ pub const LibExeObjStep = struct { .system_lib = .{ .name = self.builder.dupe(name), .needed = true, + .weak = false, + .use_pkg_config = .no, + }, + }) catch unreachable; + } + + /// Darwin-only. This one has no integration with anything, it just puts -weak-lname on the + /// command line. Prefer to use `linkSystemLibraryWeak` instead. + pub fn linkSystemLibraryWeakName(self: *LibExeObjStep, name: []const u8) void { + self.link_objects.append(.{ + .system_lib = .{ + .name = self.builder.dupe(name), + .needed = false, + .weak = true, .use_pkg_config = .no, }, }) catch unreachable; @@ -2001,6 +2032,7 @@ pub const LibExeObjStep = struct { .system_lib = .{ .name = self.builder.dupe(lib_name), .needed = false, + .weak = false, .use_pkg_config = .force, }, }) catch unreachable; @@ -2013,6 +2045,7 @@ pub const LibExeObjStep = struct { .system_lib = .{ .name = self.builder.dupe(lib_name), .needed = true, + .weak = false, .use_pkg_config = .force, }, }) catch unreachable; @@ -2115,14 +2148,21 @@ pub const LibExeObjStep = struct { } pub fn linkSystemLibrary(self: *LibExeObjStep, name: []const u8) void { - self.linkSystemLibraryInner(name, false); + self.linkSystemLibraryInner(name, .{}); } pub fn linkSystemLibraryNeeded(self: *LibExeObjStep, name: []const u8) void { - self.linkSystemLibraryInner(name, true); + self.linkSystemLibraryInner(name, .{ .needed = true }); } - fn linkSystemLibraryInner(self: *LibExeObjStep, name: []const u8, needed: bool) void { + pub fn linkSystemLibraryWeak(self: *LibExeObjStep, name: []const u8) void { + self.linkSystemLibraryInner(name, .{ .weak = true }); + } + + fn linkSystemLibraryInner(self: *LibExeObjStep, name: []const u8, opts: struct { + needed: bool = false, + weak: bool = false, + }) void { if (isLibCLibrary(name)) { self.linkLibC(); return; @@ -2135,7 +2175,8 @@ pub const LibExeObjStep = struct { self.link_objects.append(.{ .system_lib = .{ .name = self.builder.dupe(name), - .needed = needed, + .needed = opts.needed, + .weak = opts.weak, .use_pkg_config = .yes, }, }) catch unreachable; @@ -2513,7 +2554,14 @@ pub const LibExeObjStep = struct { }, .system_lib => |system_lib| { - const prefix: []const u8 = if (system_lib.needed) "-needed-l" else "-l"; + const prefix: []const u8 = prefix: { + if (system_lib.needed) break :prefix "-needed-l"; + if (system_lib.weak) { + if (self.target.isDarwin()) break :prefix "-weak-l"; + warn("Weak library import used for a non-darwin target, this will be converted to normally library import `-lname`\n", .{}); + } + break :prefix "-l"; + }; switch (system_lib.use_pkg_config) { .no => try zig_args.append(builder.fmt("{s}{s}", .{ prefix, system_lib.name })), .yes, .force => { @@ -3018,9 +3066,11 @@ pub const LibExeObjStep = struct { var it = self.frameworks.iterator(); while (it.next()) |entry| { const name = entry.key_ptr.*; - const needed = entry.value_ptr.*; - if (needed) { + const info = entry.value_ptr.*; + if (info.needed) { zig_args.append("-needed_framework") catch unreachable; + } else if (info.weak) { + zig_args.append("-weak_framework") catch unreachable; } else { zig_args.append("-framework") catch unreachable; } From 075f5bc5ffdbe0a458a733d596a1fc4a74b8e1ab Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 28 Jun 2022 09:14:35 +0200 Subject: [PATCH 4/5] link-tests: test -weak-lx and -weak_framework x --- lib/std/build/CheckObjectStep.zig | 2 ++ test/link.zig | 11 ++++++- .../macho/{needed_l => needed_library}/a.c | 0 .../{needed_l => needed_library}/build.zig | 0 .../macho/{needed_l => needed_library}/main.c | 0 test/link/macho/weak_framework/build.zig | 24 ++++++++++++++ test/link/macho/weak_framework/main.c | 3 ++ test/link/macho/weak_library/a.c | 9 +++++ test/link/macho/weak_library/build.zig | 33 +++++++++++++++++++ test/link/macho/weak_library/main.c | 9 +++++ 10 files changed, 90 insertions(+), 1 deletion(-) rename test/link/macho/{needed_l => needed_library}/a.c (100%) rename test/link/macho/{needed_l => needed_library}/build.zig (100%) rename test/link/macho/{needed_l => needed_library}/main.c (100%) create mode 100644 test/link/macho/weak_framework/build.zig create mode 100644 test/link/macho/weak_framework/main.c create mode 100644 test/link/macho/weak_library/a.c create mode 100644 test/link/macho/weak_library/build.zig create mode 100644 test/link/macho/weak_library/main.c diff --git a/lib/std/build/CheckObjectStep.zig b/lib/std/build/CheckObjectStep.zig index 375e183231..c7e91bb7cb 100644 --- a/lib/std/build/CheckObjectStep.zig +++ b/lib/std/build/CheckObjectStep.zig @@ -408,6 +408,8 @@ const MachODumper = struct { .ID_DYLIB, .LOAD_DYLIB, + .LOAD_WEAK_DYLIB, + .REEXPORT_DYLIB, => { const dylib = lc.dylib.inner.dylib; try writer.writeByte('\n'); diff --git a/test/link.zig b/test/link.zig index 6878881a66..c578638ec3 100644 --- a/test/link.zig +++ b/test/link.zig @@ -45,7 +45,11 @@ pub fn addCases(cases: *tests.StandaloneContext) void { .requires_macos_sdk = true, }); - cases.addBuildFile("test/link/macho/needed_l/build.zig", .{ + cases.addBuildFile("test/link/macho/needed_library/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/macho/weak_library/build.zig", .{ .build_modes = true, }); @@ -54,6 +58,11 @@ pub fn addCases(cases: *tests.StandaloneContext) void { .requires_macos_sdk = true, }); + cases.addBuildFile("test/link/macho/weak_framework/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); + // Try to build and run an Objective-C executable. cases.addBuildFile("test/link/macho/objc/build.zig", .{ .build_modes = true, diff --git a/test/link/macho/needed_l/a.c b/test/link/macho/needed_library/a.c similarity index 100% rename from test/link/macho/needed_l/a.c rename to test/link/macho/needed_library/a.c diff --git a/test/link/macho/needed_l/build.zig b/test/link/macho/needed_library/build.zig similarity index 100% rename from test/link/macho/needed_l/build.zig rename to test/link/macho/needed_library/build.zig diff --git a/test/link/macho/needed_l/main.c b/test/link/macho/needed_library/main.c similarity index 100% rename from test/link/macho/needed_l/main.c rename to test/link/macho/needed_library/main.c diff --git a/test/link/macho/weak_framework/build.zig b/test/link/macho/weak_framework/build.zig new file mode 100644 index 0000000000..44675a15f8 --- /dev/null +++ b/test/link/macho/weak_framework/build.zig @@ -0,0 +1,24 @@ +const std = @import("std"); +const Builder = std.build.Builder; +const LibExeObjectStep = std.build.LibExeObjStep; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const test_step = b.step("test", "Test the program"); + test_step.dependOn(b.getInstallStep()); + + const exe = b.addExecutable("test", null); + exe.addCSourceFile("main.c", &[0][]const u8{}); + exe.setBuildMode(mode); + exe.linkLibC(); + exe.linkFrameworkWeak("Cocoa"); + + const check = exe.checkObject(.macho); + check.checkStart("cmd LOAD_WEAK_DYLIB"); + check.checkNext("name {*}Cocoa"); + test_step.dependOn(&check.step); + + const run_cmd = exe.run(); + test_step.dependOn(&run_cmd.step); +} diff --git a/test/link/macho/weak_framework/main.c b/test/link/macho/weak_framework/main.c new file mode 100644 index 0000000000..ca68d24cc7 --- /dev/null +++ b/test/link/macho/weak_framework/main.c @@ -0,0 +1,3 @@ +int main(int argc, char* argv[]) { + return 0; +} diff --git a/test/link/macho/weak_library/a.c b/test/link/macho/weak_library/a.c new file mode 100644 index 0000000000..9f49802ce6 --- /dev/null +++ b/test/link/macho/weak_library/a.c @@ -0,0 +1,9 @@ +#include + +int a = 42; + +const char* asStr() { + static char str[3]; + sprintf(str, "%d", 42); + return str; +} diff --git a/test/link/macho/weak_library/build.zig b/test/link/macho/weak_library/build.zig new file mode 100644 index 0000000000..5a1f7b4ce5 --- /dev/null +++ b/test/link/macho/weak_library/build.zig @@ -0,0 +1,33 @@ +const std = @import("std"); +const Builder = std.build.Builder; +const LibExeObjectStep = std.build.LibExeObjStep; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const test_step = b.step("test", "Test the program"); + test_step.dependOn(b.getInstallStep()); + + const dylib = b.addSharedLibrary("a", null, b.version(1, 0, 0)); + dylib.setBuildMode(mode); + dylib.addCSourceFile("a.c", &.{}); + dylib.linkLibC(); + dylib.install(); + + const exe = b.addExecutable("test", null); + exe.addCSourceFile("main.c", &[0][]const u8{}); + exe.setBuildMode(mode); + exe.linkLibC(); + exe.linkSystemLibraryWeak("a"); + exe.addLibraryPath(b.pathFromRoot("zig-out/lib")); + exe.addRPath(b.pathFromRoot("zig-out/lib")); + + const check = exe.checkObject(.macho); + check.checkStart("cmd LOAD_WEAK_DYLIB"); + check.checkNext("name @rpath/liba.dylib"); + test_step.dependOn(&check.step); + + const run_cmd = exe.run(); + run_cmd.expectStdOutEqual("42 42"); + test_step.dependOn(&run_cmd.step); +} diff --git a/test/link/macho/weak_library/main.c b/test/link/macho/weak_library/main.c new file mode 100644 index 0000000000..ee5367fef7 --- /dev/null +++ b/test/link/macho/weak_library/main.c @@ -0,0 +1,9 @@ +#include + +extern int a; +extern const char* asStr(); + +int main(int argc, char* argv[]) { + printf("%d %s", a, asStr()); + return 0; +} From 5834a608fc629319772b1623a31b62dd49ac6d63 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 28 Jun 2022 10:23:25 +0200 Subject: [PATCH 5/5] link-tests: do not save global extracted var unless a match Improve testing MachO binaries by verbose printing of the symtab which includes segment,section names for defined symbols, and import (dylib) name for imports. --- lib/std/build/CheckObjectStep.zig | 87 ++++++++++++++++++++++++-- test/link/macho/entry/build.zig | 2 +- test/link/macho/weak_library/build.zig | 5 ++ 3 files changed, 87 insertions(+), 7 deletions(-) diff --git a/lib/std/build/CheckObjectStep.zig b/lib/std/build/CheckObjectStep.zig index c7e91bb7cb..e4ad7423c4 100644 --- a/lib/std/build/CheckObjectStep.zig +++ b/lib/std/build/CheckObjectStep.zig @@ -65,6 +65,7 @@ const Action = struct { fn match(act: Action, haystack: []const u8, global_vars: anytype) !bool { assert(act.tag == .match); + var candidate_var: ?struct { name: []const u8, value: u64 } = null; var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " "); var needle_it = mem.tokenize(u8, mem.trim(u8, act.phrase, " "), " "); @@ -92,12 +93,19 @@ const Action = struct { const name = needle_tok[1..closing_brace]; if (name.len == 0) return error.MissingBraceValue; const value = try std.fmt.parseInt(u64, hay_tok, 16); - try global_vars.putNoClobber(name, value); + candidate_var = .{ + .name = name, + .value = value, + }; } else { if (!mem.eql(u8, hay_tok, needle_tok)) return false; } } + if (candidate_var) |v| { + try global_vars.putNoClobber(v.name, v.value); + } + return true; } @@ -332,20 +340,43 @@ const MachODumper = struct { var output = std.ArrayList(u8).init(gpa); const writer = output.writer(); - var symtab_cmd: ?macho.symtab_command = null; + var load_commands = std.ArrayList(macho.LoadCommand).init(gpa); + try load_commands.ensureTotalCapacity(hdr.ncmds); + + var sections = std.ArrayList(struct { seg: u16, sect: u16 }).init(gpa); + var imports = std.ArrayList(u16).init(gpa); + + var symtab_cmd: ?u16 = null; var i: u16 = 0; while (i < hdr.ncmds) : (i += 1) { var cmd = try macho.LoadCommand.read(gpa, reader); + load_commands.appendAssumeCapacity(cmd); - if (opts.dump_symtab and cmd.cmd() == .SYMTAB) { - symtab_cmd = cmd.symtab; + switch (cmd.cmd()) { + .SEGMENT_64 => { + const seg = cmd.segment; + for (seg.sections.items) |_, j| { + try sections.append(.{ .seg = i, .sect = @intCast(u16, j) }); + } + }, + .SYMTAB => { + symtab_cmd = i; + }, + .LOAD_DYLIB, + .LOAD_WEAK_DYLIB, + .REEXPORT_DYLIB, + => { + try imports.append(i); + }, + else => {}, } try dumpLoadCommand(cmd, i, writer); try writer.writeByte('\n'); } - if (symtab_cmd) |cmd| { + if (opts.dump_symtab) { + const cmd = load_commands.items[symtab_cmd.?].symtab; try writer.writeAll(symtab_label ++ "\n"); const strtab = bytes[cmd.stroff..][0..cmd.strsize]; const raw_symtab = bytes[cmd.symoff..][0 .. cmd.nsyms * @sizeOf(macho.nlist_64)]; @@ -354,7 +385,51 @@ const MachODumper = struct { for (symtab) |sym| { if (sym.stab()) continue; const sym_name = mem.sliceTo(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx), 0); - try writer.print("{s} {x}\n", .{ sym_name, sym.n_value }); + if (sym.sect()) { + const map = sections.items[sym.n_sect - 1]; + const seg = load_commands.items[map.seg].segment; + const sect = seg.sections.items[map.sect]; + try writer.print("{x} ({s},{s})", .{ + sym.n_value, + sect.segName(), + sect.sectName(), + }); + if (sym.ext()) { + try writer.writeAll(" external"); + } + try writer.print(" {s}\n", .{sym_name}); + } else if (sym.undf()) { + const ordinal = @divTrunc(@bitCast(i16, sym.n_desc), macho.N_SYMBOL_RESOLVER); + const import_name = blk: { + if (ordinal <= 0) { + if (ordinal == macho.BIND_SPECIAL_DYLIB_SELF) + break :blk "self import"; + if (ordinal == macho.BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE) + break :blk "main executable"; + if (ordinal == macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP) + break :blk "flat lookup"; + unreachable; + } + const import_id = imports.items[@bitCast(u16, ordinal) - 1]; + const import = load_commands.items[import_id].dylib; + const full_path = mem.sliceTo(import.data, 0); + const basename = fs.path.basename(full_path); + assert(basename.len > 0); + const ext = mem.lastIndexOfScalar(u8, basename, '.') orelse basename.len; + break :blk basename[0..ext]; + }; + try writer.writeAll("(undefined)"); + if (sym.weakRef()) { + try writer.writeAll(" weak"); + } + if (sym.ext()) { + try writer.writeAll(" external"); + } + try writer.print(" {s} (from {s})\n", .{ + sym_name, + import_name, + }); + } else unreachable; } } diff --git a/test/link/macho/entry/build.zig b/test/link/macho/entry/build.zig index 1cd9099985..1f40b3d8e0 100644 --- a/test/link/macho/entry/build.zig +++ b/test/link/macho/entry/build.zig @@ -22,7 +22,7 @@ pub fn build(b: *Builder) void { check_exe.checkNext("entryoff {entryoff}"); check_exe.checkInSymtab(); - check_exe.checkNext("_non_main {n_value}"); + check_exe.checkNext("{n_value} (__TEXT,__text) external _non_main"); check_exe.checkComputeCompare("vmaddr entryoff +", .{ .op = .eq, .value = .{ .variable = "n_value" } }); diff --git a/test/link/macho/weak_library/build.zig b/test/link/macho/weak_library/build.zig index 5a1f7b4ce5..f1070f3b2b 100644 --- a/test/link/macho/weak_library/build.zig +++ b/test/link/macho/weak_library/build.zig @@ -25,6 +25,11 @@ pub fn build(b: *Builder) void { const check = exe.checkObject(.macho); check.checkStart("cmd LOAD_WEAK_DYLIB"); check.checkNext("name @rpath/liba.dylib"); + + check.checkInSymtab(); + check.checkNext("(undefined) weak external _a (from liba)"); + check.checkNext("(undefined) weak external _asStr (from liba)"); + test_step.dependOn(&check.step); const run_cmd = exe.run();