From 35c694d614c8687ffa99ac187e60d831f9d7f29d Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Mon, 17 May 2021 15:57:54 -0700 Subject: translate-c: Demote initialization of opaque types This fixes a segfault in translate-c that would previously occur when initializing structs with unnamed bitfields, due to a failed assertion in `transInitListExprRecord`. Unnamed bitfields do not have initializers, so `transInitListExprRecord` erroneously assumes that `init_count` equals the number of fields in the record. Since attempting to initialize an opaque type is a syntax error in Zig, we can just demote any attempts to initialize them. --- src/translate_c.zig | 4 ++++ test/translate_c.zig | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/translate_c.zig b/src/translate_c.zig index 19aec279ec..2af91f992f 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -2445,6 +2445,10 @@ fn transInitListExpr( var qual_type = qt.getTypePtr(); const source_loc = @ptrCast(*const clang.Expr, expr).getBeginLoc(); + if (qualTypeWasDemotedToOpaque(c, qt)) { + return fail(c, error.UnsupportedTranslation, source_loc, "Cannot initialize opaque type", .{}); + } + if (qual_type.isRecordType()) { return maybeSuppressResult(c, scope, used, try transInitListExprRecord( c, diff --git a/test/translate_c.zig b/test/translate_c.zig index 5918dbb37b..2647c712b9 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -3513,4 +3513,20 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ asm (".globl func\n\t.type func, @function\n\tfunc:\n\t.cfi_startproc\n\tmovl $42, %eax\n\tret\n\t.cfi_endproc"); \\} }); + + cases.add("Demote function that initializes opaque struct", + \\struct my_struct { + \\ unsigned a: 15; + \\ unsigned: 2; + \\ unsigned b: 15; + \\}; + \\void initialize(void) { + \\ struct my_struct S = {.a = 1, .b = 2}; + \\} + , &[_][]const u8{ + \\warning: Cannot initialize opaque type + , + \\warning: unable to translate function, demoted to extern + \\pub extern fn initialize() void; + }); } -- cgit v1.2.3 From 138cecc0283fdc3ec1b4343c15001e2802ed29de Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sun, 16 May 2021 16:32:27 +0200 Subject: zld: add prelim way of linking dylibs The support is minimalistic in the sense that we only support actual dylib files and not stubs/tbds yet, and we also don't support re-exports just yet. --- CMakeLists.txt | 1 + src/link/MachO.zig | 131 ++++++++++++++++++++++++++++++++++++----- src/link/MachO/Dylib.zig | 137 +++++++++++++++++++++++++++++++++++++++++++ src/link/MachO/Symbol.zig | 7 ++- src/link/MachO/Zld.zig | 138 ++++++++++++++++++++++++++++++++++---------- src/link/MachO/commands.zig | 26 +++++++-- 6 files changed, 386 insertions(+), 54 deletions(-) create mode 100644 src/link/MachO/Dylib.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index 27ed0ac73c..d8ee747a77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -568,6 +568,7 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/link/MachO/Archive.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/CodeSignature.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/DebugSymbols.zig" + "${CMAKE_SOURCE_DIR}/src/link/MachO/Dylib.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/Object.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/Symbol.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig" diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 2d2b4dbca1..759b193acf 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -548,7 +548,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { const is_dyn_lib = self.base.options.link_mode == .Dynamic and is_lib; const is_exe_or_dyn_lib = is_dyn_lib or self.base.options.output_mode == .Exe; const target = self.base.options.target; - const stack_size = self.base.options.stack_size_override orelse 16777216; + const stack_size = self.base.options.stack_size_override orelse 0; const allow_shlib_undefined = self.base.options.allow_shlib_undefined orelse !self.base.options.is_native_os; const id_symlink_basename = "lld.id"; @@ -675,22 +675,114 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { zld.deinit(); } zld.arch = target.cpu.arch; + zld.stack_size = stack_size; - var input_files = std.ArrayList([]const u8).init(self.base.allocator); - defer input_files.deinit(); - // Positional arguments to the linker such as object files. - try input_files.appendSlice(self.base.options.objects); + // Positional arguments to the linker such as object files and static archives. + var positionals = std.ArrayList([]const u8).init(self.base.allocator); + defer positionals.deinit(); + + try positionals.appendSlice(self.base.options.objects); for (comp.c_object_table.items()) |entry| { - try input_files.append(entry.key.status.success.object_path); + try positionals.append(entry.key.status.success.object_path); } if (module_obj_path) |p| { - try input_files.append(p); + try positionals.append(p); } - try input_files.append(comp.compiler_rt_static_lib.?.full_object_path); + try positionals.append(comp.compiler_rt_static_lib.?.full_object_path); + // libc++ dep if (self.base.options.link_libcpp) { - try input_files.append(comp.libcxxabi_static_lib.?.full_object_path); - try input_files.append(comp.libcxx_static_lib.?.full_object_path); + try positionals.append(comp.libcxxabi_static_lib.?.full_object_path); + try positionals.append(comp.libcxx_static_lib.?.full_object_path); + } + + if (self.base.options.is_native_os) {} + + // Shared libraries. + var shared_libs = std.ArrayList([]const u8).init(self.base.allocator); + defer { + for (shared_libs.items) |sh| { + self.base.allocator.free(sh); + } + shared_libs.deinit(); + } + + var search_lib_names = std.ArrayList([]const u8).init(self.base.allocator); + defer search_lib_names.deinit(); + + const system_libs = self.base.options.system_libs.items(); + for (system_libs) |entry| { + const link_lib = entry.key; + // By this time, we depend on these libs being dynamically linked libraries and not static libraries + // (the check for that needs to be earlier), but they could be full paths to .dylib files, in which + // case we want to avoid prepending "-l". + if (Compilation.classifyFileExt(link_lib) == .shared_library) { + const path = try self.base.allocator.dupe(u8, link_lib); + try shared_libs.append(path); + continue; + } + + try search_lib_names.append(link_lib); + } + + for (search_lib_names.items) |l_name| { + // TODO text-based API, or .tbd files. + const l_name_ext = try std.fmt.allocPrint(self.base.allocator, "lib{s}.dylib", .{l_name}); + defer self.base.allocator.free(l_name_ext); + + var found = false; + if (self.base.options.syslibroot) |syslibroot| { + for (self.base.options.lib_dirs) |lib_dir| { + const path = try fs.path.join(self.base.allocator, &[_][]const u8{ + syslibroot, + lib_dir, + l_name_ext, + }); + + const tmp = fs.cwd().openFile(path, .{}) catch |err| switch (err) { + error.FileNotFound => { + self.base.allocator.free(path); + continue; + }, + else => |e| return e, + }; + defer tmp.close(); + + try shared_libs.append(path); + found = true; + break; + } + } + + for (self.base.options.lib_dirs) |lib_dir| { + const path = try fs.path.join(self.base.allocator, &[_][]const u8{ lib_dir, l_name_ext }); + + const tmp = fs.cwd().openFile(path, .{}) catch |err| switch (err) { + error.FileNotFound => { + self.base.allocator.free(path); + continue; + }, + else => |e| return e, + }; + defer tmp.close(); + + try shared_libs.append(path); + found = true; + break; + } + + if (!found) { + log.warn("library '-l{s}' not found", .{l_name}); + log.warn("searched paths:", .{}); + if (self.base.options.syslibroot) |syslibroot| { + for (self.base.options.lib_dirs) |lib_dir| { + log.warn(" {s}/{s}", .{ syslibroot, lib_dir }); + } + } + for (self.base.options.lib_dirs) |lib_dir| { + log.warn(" {s}/", .{lib_dir}); + } + } } if (self.base.options.verbose_link) { @@ -700,17 +792,28 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { try argv.append("zig"); try argv.append("ld"); - try argv.appendSlice(input_files.items); + if (self.base.options.syslibroot) |syslibroot| { + try argv.append("-syslibroot"); + try argv.append(syslibroot); + } + + try argv.appendSlice(positionals.items); try argv.append("-o"); try argv.append(full_out_path); + for (search_lib_names.items) |l_name| { + try argv.append(try std.fmt.allocPrint(self.base.allocator, "-l{s}", .{l_name})); + } + + for (self.base.options.lib_dirs) |lib_dir| { + try argv.append(try std.fmt.allocPrint(self.base.allocator, "-L{s}", .{lib_dir})); + } + Compilation.dump_argv(argv.items); } - try zld.link(input_files.items, full_out_path, .{ - .stack_size = self.base.options.stack_size_override, - }); + try zld.link(positionals.items, shared_libs.items, full_out_path); break :outer; } diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig new file mode 100644 index 0000000000..c1c5b0c144 --- /dev/null +++ b/src/link/MachO/Dylib.zig @@ -0,0 +1,137 @@ +const Dylib = @This(); + +const std = @import("std"); +const fs = std.fs; +const log = std.log.scoped(.dylib); +const macho = std.macho; +const mem = std.mem; + +const Allocator = mem.Allocator; +const Symbol = @import("Symbol.zig"); + +usingnamespace @import("commands.zig"); + +allocator: *Allocator, +arch: ?std.Target.Cpu.Arch = null, +header: ?macho.mach_header_64 = null, +file: ?fs.File = null, +name: ?[]const u8 = null, + +ordinal: ?u16 = null, + +load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, + +symtab_cmd_index: ?u16 = null, +dysymtab_cmd_index: ?u16 = null, + +symbols: std.StringArrayHashMapUnmanaged(*Symbol) = .{}, + +pub fn init(allocator: *Allocator) Dylib { + return .{ .allocator = allocator }; +} + +pub fn deinit(self: *Dylib) void { + for (self.load_commands.items) |*lc| { + lc.deinit(self.allocator); + } + self.load_commands.deinit(self.allocator); + + for (self.symbols.items()) |entry| { + entry.value.deinit(self.allocator); + self.allocator.destroy(entry.value); + } + self.symbols.deinit(self.allocator); + + if (self.name) |name| { + self.allocator.free(name); + } +} + +pub fn closeFile(self: Dylib) void { + if (self.file) |file| { + file.close(); + } +} + +pub fn parse(self: *Dylib) !void { + log.warn("parsing shared library '{s}'", .{self.name.?}); + + var reader = self.file.?.reader(); + self.header = try reader.readStruct(macho.mach_header_64); + + if (self.header.?.filetype != macho.MH_DYLIB) { + log.err("invalid filetype: expected 0x{x}, found 0x{x}", .{ macho.MH_DYLIB, self.header.?.filetype }); + return error.MalformedDylib; + } + + const this_arch: std.Target.Cpu.Arch = switch (self.header.?.cputype) { + macho.CPU_TYPE_ARM64 => .aarch64, + macho.CPU_TYPE_X86_64 => .x86_64, + else => |value| { + log.err("unsupported cpu architecture 0x{x}", .{value}); + return error.UnsupportedCpuArchitecture; + }, + }; + if (this_arch != self.arch.?) { + log.err("mismatched cpu architecture: expected {s}, found {s}", .{ self.arch.?, this_arch }); + return error.MismatchedCpuArchitecture; + } + + try self.readLoadCommands(reader); + try self.parseSymbols(); +} + +pub fn readLoadCommands(self: *Dylib, reader: anytype) !void { + try self.load_commands.ensureCapacity(self.allocator, self.header.?.ncmds); + + var i: u16 = 0; + while (i < self.header.?.ncmds) : (i += 1) { + var cmd = try LoadCommand.read(self.allocator, reader); + switch (cmd.cmd()) { + macho.LC_SYMTAB => { + self.symtab_cmd_index = i; + }, + macho.LC_DYSYMTAB => { + self.dysymtab_cmd_index = i; + }, + else => { + log.debug("Unknown load command detected: 0x{x}.", .{cmd.cmd()}); + }, + } + self.load_commands.appendAssumeCapacity(cmd); + } +} + +pub fn parseSymbols(self: *Dylib) !void { + const index = self.symtab_cmd_index orelse return; + const symtab_cmd = self.load_commands.items[index].Symtab; + + var symtab = try self.allocator.alloc(u8, @sizeOf(macho.nlist_64) * symtab_cmd.nsyms); + defer self.allocator.free(symtab); + _ = try self.file.?.preadAll(symtab, symtab_cmd.symoff); + const slice = @alignCast(@alignOf(macho.nlist_64), mem.bytesAsSlice(macho.nlist_64, symtab)); + + var strtab = try self.allocator.alloc(u8, symtab_cmd.strsize); + defer self.allocator.free(strtab); + _ = try self.file.?.preadAll(strtab, symtab_cmd.stroff); + + for (slice) |sym| { + const sym_name = mem.spanZ(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx)); + + if (!(Symbol.isSect(sym) and Symbol.isExt(sym))) continue; + + const name = try self.allocator.dupe(u8, sym_name); + const proxy = try self.allocator.create(Symbol.Proxy); + errdefer self.allocator.destroy(proxy); + + proxy.* = .{ + .base = .{ + .@"type" = .proxy, + .name = name, + }, + .dylib = self, + }; + + try self.symbols.putNoClobber(self.allocator, name, &proxy.base); + } +} diff --git a/src/link/MachO/Symbol.zig b/src/link/MachO/Symbol.zig index f928c807a3..46203b5d0d 100644 --- a/src/link/MachO/Symbol.zig +++ b/src/link/MachO/Symbol.zig @@ -5,6 +5,7 @@ const macho = std.macho; const mem = std.mem; const Allocator = mem.Allocator; +const Dylib = @import("Dylib.zig"); const Object = @import("Object.zig"); pub const Type = enum { @@ -43,7 +44,7 @@ pub const Regular = struct { /// Whether the symbol is a weak ref. weak_ref: bool, - /// File where to locate this symbol. + /// Object file where to locate this symbol. file: *Object, /// Debug stab if defined. @@ -78,8 +79,8 @@ pub const Regular = struct { pub const Proxy = struct { base: Symbol, - /// Dylib ordinal. - dylib: u16, + /// Dylib where to locate this symbol. + dylib: ?*Dylib = null, pub const base_type: Symbol.Type = .proxy; }; diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index c619d0634b..0b1eed8a73 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -15,6 +15,7 @@ const reloc = @import("reloc.zig"); const Allocator = mem.Allocator; const Archive = @import("Archive.zig"); const CodeSignature = @import("CodeSignature.zig"); +const Dylib = @import("Dylib.zig"); const Object = @import("Object.zig"); const Symbol = @import("Symbol.zig"); const Trie = @import("Trie.zig"); @@ -35,6 +36,7 @@ stack_size: u64 = 0, objects: std.ArrayListUnmanaged(*Object) = .{}, archives: std.ArrayListUnmanaged(*Archive) = .{}, +dylibs: std.ArrayListUnmanaged(*Dylib) = .{}, load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, @@ -151,10 +153,18 @@ pub fn deinit(self: *Zld) void { } self.archives.deinit(self.allocator); + for (self.dylibs.items) |dylib| { + dylib.deinit(); + self.allocator.destroy(dylib); + } + self.dylibs.deinit(self.allocator); + self.mappings.deinit(self.allocator); self.unhandled_sections.deinit(self.allocator); self.globals.deinit(self.allocator); + self.imports.deinit(self.allocator); + self.unresolved.deinit(self.allocator); self.strtab.deinit(self.allocator); { @@ -176,11 +186,7 @@ pub fn closeFiles(self: Zld) void { if (self.file) |f| f.close(); } -const LinkArgs = struct { - stack_size: ?u64 = null, -}; - -pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8, args: LinkArgs) !void { +pub fn link(self: *Zld, files: []const []const u8, shared_libs: []const []const u8, out_path: []const u8) !void { if (files.len == 0) return error.NoInputFiles; if (out_path.len == 0) return error.EmptyOutputPath; @@ -214,10 +220,10 @@ pub fn link(self: *Zld, files: []const []const u8, out_path: []const u8, args: L .read = true, .mode = if (std.Target.current.os.tag == .windows) 0 else 0o777, }); - self.stack_size = args.stack_size orelse 0; try self.populateMetadata(); try self.parseInputFiles(files); + try self.parseDylibs(shared_libs); try self.resolveSymbols(); try self.resolveStubsAndGotEntries(); try self.updateMetadata(); @@ -315,6 +321,48 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void { } } +fn parseDylibs(self: *Zld, shared_libs: []const []const u8) !void { + for (shared_libs) |lib| { + const dylib = try self.allocator.create(Dylib); + errdefer self.allocator.destroy(dylib); + + dylib.* = Dylib.init(self.allocator); + dylib.arch = self.arch.?; + dylib.name = try self.allocator.dupe(u8, lib); + dylib.file = try fs.cwd().openFile(lib, .{}); + + 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 cmdsize = @intCast(u32, mem.alignForwardGeneric( + u64, + @sizeOf(macho.dylib_command) + dylib.name.?.len, + @sizeOf(u64), + )); + // TODO Read the min version from the dylib itself. + const min_version = 0x0; + var dylib_cmd = emptyGenericCommandWithData(macho.dylib_command{ + .cmd = macho.LC_LOAD_DYLIB, + .cmdsize = cmdsize, + .dylib = .{ + .name = @sizeOf(macho.dylib_command), + .timestamp = 2, // TODO parse from the dylib. + .current_version = min_version, + .compatibility_version = min_version, + }, + }); + dylib_cmd.data = try self.allocator.alloc(u8, cmdsize - dylib_cmd.inner.dylib.name); + mem.set(u8, dylib_cmd.data, 0); + mem.copy(u8, dylib_cmd.data, dylib.name.?); + try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd }); + } +} + fn mapAndUpdateSections( self: *Zld, object_id: u16, @@ -1398,35 +1446,51 @@ fn resolveSymbols(self: *Zld) !void { // Third pass, resolve symbols in dynamic libraries. // TODO Implement libSystem as a hard-coded library, or ship with // a libSystem.B.tbd definition file? - try self.imports.ensureCapacity(self.allocator, self.unresolved.count()); - for (self.unresolved.items()) |entry| { - const proxy = try self.allocator.create(Symbol.Proxy); - errdefer self.allocator.destroy(proxy); + var unresolved = std.ArrayList(*Symbol).init(self.allocator); + defer unresolved.deinit(); - proxy.* = .{ - .base = .{ - .@"type" = .proxy, - .name = try self.allocator.dupe(u8, entry.key), - }, - .dylib = 0, - }; - - self.imports.putAssumeCapacityNoClobber(proxy.base.name, &proxy.base); - entry.value.alias = &proxy.base; + try unresolved.ensureCapacity(self.unresolved.count()); + for (self.unresolved.items()) |entry| { + unresolved.appendAssumeCapacity(entry.value); } self.unresolved.clearAndFree(self.allocator); - // If there are any undefs left, flag an error. - if (self.unresolved.count() > 0) { - for (self.unresolved.items()) |entry| { - log.err("undefined reference to symbol '{s}'", .{entry.key}); - log.err(" | referenced in {s}", .{ - entry.value.cast(Symbol.Unresolved).?.file.name.?, - }); + var has_undefined = false; + while (unresolved.popOrNull()) |undef| { + var found = false; + for (self.dylibs.items) |dylib| { + const proxy = dylib.symbols.get(undef.name) orelse continue; + try self.imports.putNoClobber(self.allocator, proxy.name, proxy); + undef.alias = proxy; + found = true; + } + + if (!found) { + // TODO we currently hardcode all unresolved symbols to libSystem + const proxy = try self.allocator.create(Symbol.Proxy); + errdefer self.allocator.destroy(proxy); + + proxy.* = .{ + .base = .{ + .@"type" = .proxy, + .name = try self.allocator.dupe(u8, undef.name), + }, + .dylib = null, // TODO null means libSystem + }; + + try self.imports.putNoClobber(self.allocator, proxy.base.name, &proxy.base); + undef.alias = &proxy.base; + + // log.err("undefined reference to symbol '{s}'", .{undef.name}); + // log.err(" | referenced in {s}", .{ + // undef.cast(Symbol.Unresolved).?.file.name.?, + // }); + // has_undefined = true; } - return error.UndefinedSymbolReference; } + if (has_undefined) return error.UndefinedSymbolReference; + // Finally put dyld_stub_binder as an Import const dyld_stub_binder = try self.allocator.create(Symbol.Proxy); errdefer self.allocator.destroy(dyld_stub_binder); @@ -1436,7 +1500,7 @@ fn resolveSymbols(self: *Zld) !void { .@"type" = .proxy, .name = try self.allocator.dupe(u8, "dyld_stub_binder"), }, - .dylib = 0, + .dylib = null, // TODO null means libSystem }; try self.imports.putNoClobber( @@ -2303,7 +2367,10 @@ fn writeBindInfoTable(self: *Zld) !void { for (self.got_entries.items) |sym| { if (sym.cast(Symbol.Proxy)) |proxy| { - const dylib_ordinal = proxy.dylib + 1; + const dylib_ordinal = ordinal: { + const dylib = proxy.dylib orelse break :ordinal 1; // TODO embedded libSystem + break :ordinal dylib.ordinal.?; + }; try pointers.append(.{ .offset = base_offset + proxy.base.got_index.? * @sizeOf(u64), .segment_id = segment_id, @@ -2322,7 +2389,10 @@ fn writeBindInfoTable(self: *Zld) !void { const sym = self.imports.get("__tlv_bootstrap") orelse unreachable; const proxy = sym.cast(Symbol.Proxy) orelse unreachable; - const dylib_ordinal = proxy.dylib + 1; + const dylib_ordinal = ordinal: { + const dylib = proxy.dylib orelse break :ordinal 1; // TODO embedded libSystem + break :ordinal dylib.ordinal.?; + }; try pointers.append(.{ .offset = base_offset, @@ -2364,7 +2434,11 @@ fn writeLazyBindInfoTable(self: *Zld) !void { for (self.stubs.items) |sym| { const proxy = sym.cast(Symbol.Proxy) orelse unreachable; - const dylib_ordinal = proxy.dylib + 1; + const dylib_ordinal = ordinal: { + const dylib = proxy.dylib orelse break :ordinal 1; // TODO embedded libSystem + break :ordinal dylib.ordinal.?; + }; + pointers.appendAssumeCapacity(.{ .offset = base_offset + sym.stubs_index.? * @sizeOf(u64), .segment_id = segment_id, diff --git a/src/link/MachO/commands.zig b/src/link/MachO/commands.zig index f81e5c757d..a7bd3aa893 100644 --- a/src/link/MachO/commands.zig +++ b/src/link/MachO/commands.zig @@ -38,7 +38,9 @@ pub const LoadCommand = union(enum) { macho.LC_SEGMENT_64 => LoadCommand{ .Segment = try SegmentCommand.read(allocator, stream.reader()), }, - macho.LC_DYLD_INFO, macho.LC_DYLD_INFO_ONLY => LoadCommand{ + macho.LC_DYLD_INFO, + macho.LC_DYLD_INFO_ONLY, + => LoadCommand{ .DyldInfoOnly = try stream.reader().readStruct(macho.dyld_info_command), }, macho.LC_SYMTAB => LoadCommand{ @@ -47,16 +49,27 @@ pub const LoadCommand = union(enum) { macho.LC_DYSYMTAB => LoadCommand{ .Dysymtab = try stream.reader().readStruct(macho.dysymtab_command), }, - macho.LC_ID_DYLINKER, macho.LC_LOAD_DYLINKER, macho.LC_DYLD_ENVIRONMENT => LoadCommand{ + macho.LC_ID_DYLINKER, + macho.LC_LOAD_DYLINKER, + macho.LC_DYLD_ENVIRONMENT, + => LoadCommand{ .Dylinker = try GenericCommandWithData(macho.dylinker_command).read(allocator, stream.reader()), }, - macho.LC_ID_DYLIB, macho.LC_LOAD_WEAK_DYLIB, macho.LC_LOAD_DYLIB, macho.LC_REEXPORT_DYLIB => LoadCommand{ + macho.LC_ID_DYLIB, + macho.LC_LOAD_WEAK_DYLIB, + macho.LC_LOAD_DYLIB, + macho.LC_REEXPORT_DYLIB, + => LoadCommand{ .Dylib = try GenericCommandWithData(macho.dylib_command).read(allocator, stream.reader()), }, macho.LC_MAIN => LoadCommand{ .Main = try stream.reader().readStruct(macho.entry_point_command), }, - macho.LC_VERSION_MIN_MACOSX, macho.LC_VERSION_MIN_IPHONEOS, macho.LC_VERSION_MIN_WATCHOS, macho.LC_VERSION_MIN_TVOS => LoadCommand{ + macho.LC_VERSION_MIN_MACOSX, + macho.LC_VERSION_MIN_IPHONEOS, + macho.LC_VERSION_MIN_WATCHOS, + macho.LC_VERSION_MIN_TVOS, + => LoadCommand{ .VersionMin = try stream.reader().readStruct(macho.version_min_command), }, macho.LC_SOURCE_VERSION => LoadCommand{ @@ -65,7 +78,10 @@ pub const LoadCommand = union(enum) { macho.LC_UUID => LoadCommand{ .Uuid = try stream.reader().readStruct(macho.uuid_command), }, - macho.LC_FUNCTION_STARTS, macho.LC_DATA_IN_CODE, macho.LC_CODE_SIGNATURE => LoadCommand{ + macho.LC_FUNCTION_STARTS, + macho.LC_DATA_IN_CODE, + macho.LC_CODE_SIGNATURE, + => LoadCommand{ .LinkeditData = try stream.reader().readStruct(macho.linkedit_data_command), }, else => LoadCommand{ -- cgit v1.2.3 From 17b25885989d5f4f0e87de69f5eb3173553de4bc Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 17 May 2021 13:40:35 +0200 Subject: zld: refactor out logic for dylib LC creation --- src/link/MachO.zig | 26 +++++---------------- src/link/MachO/Zld.zig | 55 +++++++++++---------------------------------- src/link/MachO/commands.zig | 31 +++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 63 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 759b193acf..6d7f461084 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -2096,28 +2096,12 @@ pub fn populateMissingMetadata(self: *MachO) !void { } if (self.libsystem_cmd_index == null) { self.libsystem_cmd_index = @intCast(u16, self.load_commands.items.len); - const cmdsize = @intCast(u32, mem.alignForwardGeneric( - u64, - @sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH), - @sizeOf(u64), - )); - // TODO Find a way to work out runtime version from the OS version triple stored in std.Target. - // In the meantime, we're gonna hardcode to the minimum compatibility version of 0.0.0. - const min_version = 0x0; - var dylib_cmd = emptyGenericCommandWithData(macho.dylib_command{ - .cmd = macho.LC_LOAD_DYLIB, - .cmdsize = cmdsize, - .dylib = .{ - .name = @sizeOf(macho.dylib_command), - .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files - .current_version = min_version, - .compatibility_version = min_version, - }, - }); - dylib_cmd.data = try self.base.allocator.alloc(u8, cmdsize - dylib_cmd.inner.dylib.name); - mem.set(u8, dylib_cmd.data, 0); - mem.copy(u8, dylib_cmd.data, mem.spanZ(LIB_SYSTEM_PATH)); + + var dylib_cmd = try createLoadDylibCommand(self.base.allocator, mem.spanZ(LIB_SYSTEM_PATH), 2, 0, 0); + errdefer dylib_cmd.deinit(self.base.allocator); + try self.load_commands.append(self.base.allocator, .{ .Dylib = dylib_cmd }); + self.header_dirty = true; self.load_commands_dirty = true; } diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index 0b1eed8a73..12f3968d53 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -339,26 +339,10 @@ fn parseDylibs(self: *Zld, shared_libs: []const []const u8) !void { try self.dylibs.append(self.allocator, dylib); // Add LC_LOAD_DYLIB command - const cmdsize = @intCast(u32, mem.alignForwardGeneric( - u64, - @sizeOf(macho.dylib_command) + dylib.name.?.len, - @sizeOf(u64), - )); - // TODO Read the min version from the dylib itself. - const min_version = 0x0; - var dylib_cmd = emptyGenericCommandWithData(macho.dylib_command{ - .cmd = macho.LC_LOAD_DYLIB, - .cmdsize = cmdsize, - .dylib = .{ - .name = @sizeOf(macho.dylib_command), - .timestamp = 2, // TODO parse from the dylib. - .current_version = min_version, - .compatibility_version = min_version, - }, - }); - dylib_cmd.data = try self.allocator.alloc(u8, cmdsize - dylib_cmd.inner.dylib.name); - mem.set(u8, dylib_cmd.data, 0); - mem.copy(u8, dylib_cmd.data, dylib.name.?); + // TODO Read the timestamp and versions from the dylib itself. + var dylib_cmd = try createLoadDylibCommand(self.allocator, dylib.name.?, 2, 0, 0); + errdefer dylib_cmd.deinit(self.allocator); + try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd }); } } @@ -2061,27 +2045,10 @@ fn populateMetadata(self: *Zld) !void { if (self.libsystem_cmd_index == null) { self.libsystem_cmd_index = @intCast(u16, self.load_commands.items.len); - const cmdsize = @intCast(u32, mem.alignForwardGeneric( - u64, - @sizeOf(macho.dylib_command) + mem.lenZ(LIB_SYSTEM_PATH), - @sizeOf(u64), - )); - // TODO Find a way to work out runtime version from the OS version triple stored in std.Target. - // In the meantime, we're gonna hardcode to the minimum compatibility version of 0.0.0. - const min_version = 0x0; - var dylib_cmd = emptyGenericCommandWithData(macho.dylib_command{ - .cmd = macho.LC_LOAD_DYLIB, - .cmdsize = cmdsize, - .dylib = .{ - .name = @sizeOf(macho.dylib_command), - .timestamp = 2, // not sure why not simply 0; this is reverse engineered from Mach-O files - .current_version = min_version, - .compatibility_version = min_version, - }, - }); - dylib_cmd.data = try self.allocator.alloc(u8, cmdsize - dylib_cmd.inner.dylib.name); - mem.set(u8, dylib_cmd.data, 0); - mem.copy(u8, dylib_cmd.data, mem.spanZ(LIB_SYSTEM_PATH)); + + var dylib_cmd = try createLoadDylibCommand(self.allocator, mem.spanZ(LIB_SYSTEM_PATH), 2, 0, 0); + errdefer dylib_cmd.deinit(self.allocator); + try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd }); } @@ -2738,11 +2705,15 @@ fn writeSymbolTable(self: *Zld) !void { for (self.imports.items()) |entry| { const sym = entry.value; + const ordinal = ordinal: { + const dylib = sym.cast(Symbol.Proxy).?.dylib orelse break :ordinal 1; // TODO handle libSystem + break :ordinal dylib.ordinal.?; + }; try undefs.append(.{ .n_strx = try self.makeString(sym.name), .n_type = macho.N_UNDF | macho.N_EXT, .n_sect = 0, - .n_desc = macho.N_SYMBOL_RESOLVER | macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY, + .n_desc = (ordinal * macho.N_SYMBOL_RESOLVER) | macho.REFERENCE_FLAG_UNDEFINED_NON_LAZY, .n_value = 0, }); } diff --git a/src/link/MachO/commands.zig b/src/link/MachO/commands.zig index a7bd3aa893..6370d89770 100644 --- a/src/link/MachO/commands.zig +++ b/src/link/MachO/commands.zig @@ -298,6 +298,37 @@ pub fn GenericCommandWithData(comptime Cmd: type) type { }; } +pub fn createLoadDylibCommand( + allocator: *Allocator, + name: []const u8, + timestamp: u32, + current_version: u32, + compatibility_version: u32, +) !GenericCommandWithData(macho.dylib_command) { + const cmdsize = @intCast(u32, mem.alignForwardGeneric( + u64, + @sizeOf(macho.dylib_command) + name.len, + @sizeOf(u64), + )); + + var dylib_cmd = emptyGenericCommandWithData(macho.dylib_command{ + .cmd = macho.LC_LOAD_DYLIB, + .cmdsize = cmdsize, + .dylib = .{ + .name = @sizeOf(macho.dylib_command), + .timestamp = timestamp, + .current_version = current_version, + .compatibility_version = compatibility_version, + }, + }); + dylib_cmd.data = try allocator.alloc(u8, cmdsize - dylib_cmd.inner.dylib.name); + + mem.set(u8, dylib_cmd.data, 0); + mem.copy(u8, dylib_cmd.data, name); + + return dylib_cmd; +} + fn testRead(allocator: *Allocator, buffer: []const u8, expected: anytype) !void { var stream = io.fixedBufferStream(buffer); var given = try LoadCommand.read(allocator, stream.reader()); -- cgit v1.2.3 From 73c015b956909d9e28392c858e7c9ecf6827e919 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 17 May 2021 14:28:49 +0200 Subject: zld: parse dylib id --- lib/std/macho.zig | 32 ++++++++++++++++++++++++++++++++ src/link/MachO/Dylib.zig | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ src/link/MachO/Zld.zig | 10 ++++++++-- 3 files changed, 88 insertions(+), 2 deletions(-) diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 8f60d8fe94..cbbbdc1d06 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -71,6 +71,38 @@ pub const source_version_command = extern struct { version: u64, }; +/// The build_version_command contains the min OS version on which this +/// binary was built to run for its platform. The list of known platforms and +/// tool values following it. +pub const build_version_command = extern struct { + /// LC_BUILD_VERSION + cmd: u32, + + /// sizeof(struct build_version_command) plus + /// ntools * sizeof(struct build_version_command) + cmdsize: u32, + + /// platform + platform: u32, + + /// X.Y.Z is encoded in nibbles xxxx.yy.zz + minos: u32, + + /// X.Y.Z is encoded in nibbles xxxx.yy.zz + sdk: u32, + + /// number of tool entries following this + ntools: u32, +}; + +pub const build_tool_version = extern struct { + /// enum for the tool + tool: u32, + + /// version number of the tool + version: u32, +}; + /// The entry_point_command is a replacement for thread_command. /// It is used for main executables to specify the location (file offset) /// of main(). If -stack_size was used at link time, the stacksize diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig index c1c5b0c144..4d7f57a955 100644 --- a/src/link/MachO/Dylib.zig +++ b/src/link/MachO/Dylib.zig @@ -23,9 +23,23 @@ load_commands: std.ArrayListUnmanaged(LoadCommand) = .{}, symtab_cmd_index: ?u16 = null, dysymtab_cmd_index: ?u16 = null, +id_cmd_index: ?u16 = null, + +id: ?Id = null, symbols: std.StringArrayHashMapUnmanaged(*Symbol) = .{}, +pub const Id = struct { + name: []const u8, + timestamp: u32, + current_version: u32, + compatibility_version: u32, + + pub fn deinit(id: *Id, allocator: *Allocator) void { + allocator.free(id.name); + } +}; + pub fn init(allocator: *Allocator) Dylib { return .{ .allocator = allocator }; } @@ -45,6 +59,10 @@ pub fn deinit(self: *Dylib) void { if (self.name) |name| { self.allocator.free(name); } + + if (self.id) |*id| { + id.deinit(self.allocator); + } } pub fn closeFile(self: Dylib) void { @@ -78,6 +96,7 @@ pub fn parse(self: *Dylib) !void { } try self.readLoadCommands(reader); + try self.parseId(); try self.parseSymbols(); } @@ -94,6 +113,9 @@ pub fn readLoadCommands(self: *Dylib, reader: anytype) !void { macho.LC_DYSYMTAB => { self.dysymtab_cmd_index = i; }, + macho.LC_ID_DYLIB => { + self.id_cmd_index = i; + }, else => { log.debug("Unknown load command detected: 0x{x}.", .{cmd.cmd()}); }, @@ -102,6 +124,32 @@ pub fn readLoadCommands(self: *Dylib, reader: anytype) !void { } } +pub fn parseId(self: *Dylib) !void { + const index = self.id_cmd_index orelse { + log.debug("no LC_ID_DYLIB load command found; using hard-coded defaults...", .{}); + self.id = .{ + .name = try self.allocator.dupe(u8, self.name.?), + .timestamp = 2, + .current_version = 0, + .compatibility_version = 0, + }; + return; + }; + const id_cmd = self.load_commands.items[index].Dylib; + const dylib = id_cmd.inner.dylib; + + // TODO should we compare the name from the dylib's id with the user-specified one? + const dylib_name = @ptrCast([*:0]const u8, id_cmd.data[dylib.name - @sizeOf(macho.dylib_command) ..]); + const name = try self.allocator.dupe(u8, mem.spanZ(dylib_name)); + + self.id = .{ + .name = name, + .timestamp = dylib.timestamp, + .current_version = dylib.current_version, + .compatibility_version = dylib.compatibility_version, + }; +} + pub fn parseSymbols(self: *Dylib) !void { const index = self.symtab_cmd_index orelse return; const symtab_cmd = self.load_commands.items[index].Symtab; diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index 12f3968d53..4c6ac8600c 100644 --- a/src/link/MachO/Zld.zig +++ b/src/link/MachO/Zld.zig @@ -339,8 +339,14 @@ fn parseDylibs(self: *Zld, shared_libs: []const []const u8) !void { try self.dylibs.append(self.allocator, dylib); // Add LC_LOAD_DYLIB command - // TODO Read the timestamp and versions from the dylib itself. - var dylib_cmd = try createLoadDylibCommand(self.allocator, dylib.name.?, 2, 0, 0); + 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 }); -- cgit v1.2.3 From ca772735c3d01c58551639d8bcfe355bc7ac9785 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 17 May 2021 14:47:49 +0200 Subject: zld: refactor order of searching for the dylibs Current assumed order is: * cwd * `self.base.options.lib_dirs` * finally, if `self.base.options.syslibroot` is defined, `self.base.options.lib_dirs` prefixed with syslibroot --- src/link/MachO.zig | 92 ++++++++++++++++-------------------------------- src/link/MachO/Dylib.zig | 2 +- 2 files changed, 32 insertions(+), 62 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 6d7f461084..c9b6197ddf 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -678,8 +678,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { zld.stack_size = stack_size; // Positional arguments to the linker such as object files and static archives. - var positionals = std.ArrayList([]const u8).init(self.base.allocator); - defer positionals.deinit(); + var positionals = std.ArrayList([]const u8).init(arena); try positionals.appendSlice(self.base.options.objects); for (comp.c_object_table.items()) |entry| { @@ -696,19 +695,9 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { try positionals.append(comp.libcxx_static_lib.?.full_object_path); } - if (self.base.options.is_native_os) {} - // Shared libraries. - var shared_libs = std.ArrayList([]const u8).init(self.base.allocator); - defer { - for (shared_libs.items) |sh| { - self.base.allocator.free(sh); - } - shared_libs.deinit(); - } - - var search_lib_names = std.ArrayList([]const u8).init(self.base.allocator); - defer search_lib_names.deinit(); + var shared_libs = std.ArrayList([]const u8).init(arena); + var search_lib_names = std.ArrayList([]const u8).init(arena); const system_libs = self.base.options.system_libs.items(); for (system_libs) |entry| { @@ -717,56 +706,43 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { // (the check for that needs to be earlier), but they could be full paths to .dylib files, in which // case we want to avoid prepending "-l". if (Compilation.classifyFileExt(link_lib) == .shared_library) { - const path = try self.base.allocator.dupe(u8, link_lib); - try shared_libs.append(path); + try shared_libs.append(link_lib); continue; } try search_lib_names.append(link_lib); } - for (search_lib_names.items) |l_name| { - // TODO text-based API, or .tbd files. - const l_name_ext = try std.fmt.allocPrint(self.base.allocator, "lib{s}.dylib", .{l_name}); - defer self.base.allocator.free(l_name_ext); + var search_lib_dirs = std.ArrayList([]const u8).init(arena); + try search_lib_dirs.ensureCapacity(self.base.options.lib_dirs.len * 2 + 1); - var found = false; - if (self.base.options.syslibroot) |syslibroot| { - for (self.base.options.lib_dirs) |lib_dir| { - const path = try fs.path.join(self.base.allocator, &[_][]const u8{ - syslibroot, - lib_dir, - l_name_ext, - }); - - const tmp = fs.cwd().openFile(path, .{}) catch |err| switch (err) { - error.FileNotFound => { - self.base.allocator.free(path); - continue; - }, - else => |e| return e, - }; - defer tmp.close(); - - try shared_libs.append(path); - found = true; - break; - } - } + search_lib_dirs.appendAssumeCapacity("."); // We will always start the search in cwd + for (self.base.options.lib_dirs) |lib_dir| { + search_lib_dirs.appendAssumeCapacity(lib_dir); + } + + if (self.base.options.syslibroot) |syslibroot| { for (self.base.options.lib_dirs) |lib_dir| { - const path = try fs.path.join(self.base.allocator, &[_][]const u8{ lib_dir, l_name_ext }); + const path = try fs.path.join(arena, &[_][]const u8{ syslibroot, lib_dir }); + search_lib_dirs.appendAssumeCapacity(path); + } + } + + for (search_lib_names.items) |l_name| { + // TODO text-based API, or .tbd files. + const l_name_ext = try std.fmt.allocPrint(arena, "lib{s}.dylib", .{l_name}); - const tmp = fs.cwd().openFile(path, .{}) catch |err| switch (err) { - error.FileNotFound => { - self.base.allocator.free(path); - continue; - }, + var found = false; + for (search_lib_dirs.items) |lib_dir| { + const full_path = try fs.path.join(arena, &[_][]const u8{ lib_dir, l_name_ext }); + const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) { + error.FileNotFound => continue, else => |e| return e, }; defer tmp.close(); - try shared_libs.append(path); + try shared_libs.append(full_path); found = true; break; } @@ -774,20 +750,14 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { if (!found) { log.warn("library '-l{s}' not found", .{l_name}); log.warn("searched paths:", .{}); - if (self.base.options.syslibroot) |syslibroot| { - for (self.base.options.lib_dirs) |lib_dir| { - log.warn(" {s}/{s}", .{ syslibroot, lib_dir }); - } - } - for (self.base.options.lib_dirs) |lib_dir| { - log.warn(" {s}/", .{lib_dir}); + for (search_lib_dirs.items) |lib_dir| { + log.warn(" {s}", .{lib_dir}); } } } if (self.base.options.verbose_link) { - var argv = std.ArrayList([]const u8).init(self.base.allocator); - defer argv.deinit(); + var argv = std.ArrayList([]const u8).init(arena); try argv.append("zig"); try argv.append("ld"); @@ -803,11 +773,11 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { try argv.append(full_out_path); for (search_lib_names.items) |l_name| { - try argv.append(try std.fmt.allocPrint(self.base.allocator, "-l{s}", .{l_name})); + try argv.append(try std.fmt.allocPrint(arena, "-l{s}", .{l_name})); } for (self.base.options.lib_dirs) |lib_dir| { - try argv.append(try std.fmt.allocPrint(self.base.allocator, "-L{s}", .{lib_dir})); + try argv.append(try std.fmt.allocPrint(arena, "-L{s}", .{lib_dir})); } Compilation.dump_argv(argv.items); diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig index 4d7f57a955..8d864454f8 100644 --- a/src/link/MachO/Dylib.zig +++ b/src/link/MachO/Dylib.zig @@ -72,7 +72,7 @@ pub fn closeFile(self: Dylib) void { } pub fn parse(self: *Dylib) !void { - log.warn("parsing shared library '{s}'", .{self.name.?}); + log.debug("parsing shared library '{s}'", .{self.name.?}); var reader = self.file.?.reader(); self.header = try reader.readStruct(macho.mach_header_64); -- cgit v1.2.3 From 1dac5f5214aa9e35ad0fbc2ba561a894bd193ae3 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 17 May 2021 17:59:38 +0200 Subject: zld: parse dylibs as positionals * add preliminary rpath support * enable shared_library test on x86_64 macOS --- lib/std/macho.zig | 13 ++++++++ src/link/MachO.zig | 23 +++++++++++++- src/link/MachO/Zld.zig | 77 +++++++++++++++++++++++++++++++++++++++++++-- src/link/MachO/commands.zig | 9 ++++++ test/standalone.zig | 5 +-- 5 files changed, 119 insertions(+), 8 deletions(-) diff --git a/lib/std/macho.zig b/lib/std/macho.zig index cbbbdc1d06..0cbb0f0f11 100644 --- a/lib/std/macho.zig +++ b/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, diff --git a/src/link/MachO.zig b/src/link/MachO.zig index c9b6197ddf..598c41330d 100644 --- a/src/link/MachO.zig +++ b/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; } diff --git a/src/link/MachO/Zld.zig b/src/link/MachO/Zld.zig index 4c6ac8600c..cc39ce1047 100644 --- a/src/link/MachO/Zld.zig +++ b/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(); diff --git a/src/link/MachO/commands.zig b/src/link/MachO/commands.zig index 6370d89770..93e6890a31 100644 --- a/src/link/MachO/commands.zig +++ b/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), }; } diff --git a/test/standalone.zig b/test/standalone.zig index 04b8af869c..e1236d448b 100644 --- a/test/standalone.zig +++ b/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"); -- cgit v1.2.3 From daaec68aec6e78999a0066621dfd382025505918 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 17 May 2021 19:38:45 +0200 Subject: zld: apply @mikdusan's suggestions Library search paths now should be closer to what ld64 does. Also, cwd now has to be specified explicitly to be considered. --- src/link/MachO.zig | 50 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 598c41330d..c42263c898 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -705,6 +705,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { // By this time, we depend on these libs being dynamically linked libraries and not static libraries // (the check for that needs to be earlier), but they could be full paths to .dylib files, in which // case we want to avoid prepending "-l". + // TODO I think then they should go as an input file instead of via shared_libs. if (Compilation.classifyFileExt(link_lib) == .shared_library) { try shared_libs.append(link_lib); continue; @@ -714,18 +715,45 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { } var search_lib_dirs = std.ArrayList([]const u8).init(arena); - try search_lib_dirs.ensureCapacity(self.base.options.lib_dirs.len * 2 + 1); - search_lib_dirs.appendAssumeCapacity("."); // We will always start the search in cwd + for (self.base.options.lib_dirs) |path| { + if (fs.path.isAbsolute(path)) { + var candidates = std.ArrayList([]const u8).init(arena); + if (self.base.options.syslibroot) |syslibroot| { + const full_path = try fs.path.join(arena, &[_][]const u8{ syslibroot, path }); + try candidates.append(full_path); + } + try candidates.append(path); + + var found = false; + for (candidates.items) |candidate| { + // Verify that search path actually exists + var tmp = fs.cwd().openDir(candidate, .{}) catch |err| switch (err) { + error.FileNotFound => continue, + else => |e| return e, + }; + defer tmp.close(); + + try search_lib_dirs.append(candidate); + found = true; + break; + } - for (self.base.options.lib_dirs) |lib_dir| { - search_lib_dirs.appendAssumeCapacity(lib_dir); - } + if (!found) { + log.warn("directory not found for '-L{s}'", .{path}); + } + } else { + // Verify that search path actually exists + var tmp = fs.cwd().openDir(path, .{}) catch |err| switch (err) { + error.FileNotFound => { + log.warn("directory not found for '-L{s}'", .{path}); + continue; + }, + else => |e| return e, + }; + defer tmp.close(); - if (self.base.options.syslibroot) |syslibroot| { - for (self.base.options.lib_dirs) |lib_dir| { - const path = try fs.path.join(arena, &[_][]const u8{ syslibroot, lib_dir }); - search_lib_dirs.appendAssumeCapacity(path); + try search_lib_dirs.append(path); } } @@ -749,7 +777,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { if (!found) { log.warn("library '-l{s}' not found", .{l_name}); - log.warn("searched paths:", .{}); + log.warn("Library search paths:", .{}); for (search_lib_dirs.items) |lib_dir| { log.warn(" {s}", .{lib_dir}); } @@ -763,7 +791,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { try rpath_table.putNoClobber(rpath, {}); } - var rpaths = std.ArrayList([]const u8) .init(arena); + var rpaths = std.ArrayList([]const u8).init(arena); try rpaths.ensureCapacity(rpath_table.count()); for (rpath_table.items()) |entry| { rpaths.appendAssumeCapacity(entry.key); -- cgit v1.2.3 From cb45c5521ab5360e2b8c5d1ce16d351d526a3fe5 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 18 May 2021 09:27:41 +0200 Subject: zld: refactor warnings --- src/link/MachO.zig | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index c42263c898..4b8f8ed30e 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -681,12 +681,15 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { var positionals = std.ArrayList([]const u8).init(arena); try positionals.appendSlice(self.base.options.objects); + for (comp.c_object_table.items()) |entry| { try positionals.append(entry.key.status.success.object_path); } + if (module_obj_path) |p| { try positionals.append(p); } + try positionals.append(comp.compiler_rt_static_lib.?.full_object_path); // libc++ dep @@ -705,7 +708,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { // By this time, we depend on these libs being dynamically linked libraries and not static libraries // (the check for that needs to be earlier), but they could be full paths to .dylib files, in which // case we want to avoid prepending "-l". - // TODO I think then they should go as an input file instead of via shared_libs. + // TODO I think they should go as an input file instead of via shared_libs. if (Compilation.classifyFileExt(link_lib) == .shared_library) { try shared_libs.append(link_lib); continue; @@ -764,6 +767,8 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { var found = false; for (search_lib_dirs.items) |lib_dir| { const full_path = try fs.path.join(arena, &[_][]const u8{ lib_dir, l_name_ext }); + + // Check if the dylib file exists. const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) { error.FileNotFound => continue, else => |e| return e, @@ -776,7 +781,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { } if (!found) { - log.warn("library '-l{s}' not found", .{l_name}); + log.warn("library not found for '-l{s}'", .{l_name}); log.warn("Library search paths:", .{}); for (search_lib_dirs.items) |lib_dir| { log.warn(" {s}", .{lib_dir}); -- cgit v1.2.3 From d228d86059cf16f4b37b2853cc1323bf98d242cf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 18 May 2021 11:29:00 -0700 Subject: ci: drone: refresh docker image with latest alpine hopefully resolves the failures we've been seeing recently --- ci/drone/drone.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ci/drone/drone.yml b/ci/drone/drone.yml index f0c4ab66d4..3bd5a5cb8e 100644 --- a/ci/drone/drone.yml +++ b/ci/drone/drone.yml @@ -7,28 +7,28 @@ platform: steps: - name: build - image: ziglang/static-base:llvm12-aarch64-1 + image: ziglang/static-base:llvm12-aarch64-2 commands: - ./ci/drone/linux_script_build - name: test-1 depends_on: - build - image: ziglang/static-base:llvm12-aarch64-1 + image: ziglang/static-base:llvm12-aarch64-2 commands: - ./ci/drone/linux_script_test 1 - name: test-2 depends_on: - build - image: ziglang/static-base:llvm12-aarch64-1 + image: ziglang/static-base:llvm12-aarch64-2 commands: - ./ci/drone/linux_script_test 2 - name: test-3 depends_on: - build - image: ziglang/static-base:llvm12-aarch64-1 + image: ziglang/static-base:llvm12-aarch64-2 commands: - ./ci/drone/linux_script_test 3 @@ -38,7 +38,7 @@ steps: - test-1 - test-2 - test-3 - image: ziglang/static-base:llvm12-aarch64-1 + image: ziglang/static-base:llvm12-aarch64-2 environment: SRHT_OAUTH_TOKEN: from_secret: SRHT_OAUTH_TOKEN -- cgit v1.2.3