diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2021-05-16 16:32:27 +0200 |
|---|---|---|
| committer | Jakub Konka <kubkon@jakubkonka.com> | 2021-05-18 09:28:00 +0200 |
| commit | 138cecc0283fdc3ec1b4343c15001e2802ed29de (patch) | |
| tree | 135a487f95e754102c35d5a9f470d776870acc7e /src | |
| parent | 35c694d614c8687ffa99ac187e60d831f9d7f29d (diff) | |
| download | zig-138cecc0283fdc3ec1b4343c15001e2802ed29de.tar.gz zig-138cecc0283fdc3ec1b4343c15001e2802ed29de.zip | |
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.
Diffstat (limited to 'src')
| -rw-r--r-- | src/link/MachO.zig | 131 | ||||
| -rw-r--r-- | src/link/MachO/Dylib.zig | 137 | ||||
| -rw-r--r-- | src/link/MachO/Symbol.zig | 7 | ||||
| -rw-r--r-- | src/link/MachO/Zld.zig | 138 | ||||
| -rw-r--r-- | src/link/MachO/commands.zig | 26 |
5 files changed, 385 insertions, 54 deletions
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{ |
