| author | |
| committer | |
| log | 702bcfecf5732eceada9bfbca0804c706c238d49 |
| tree | 7a54653dd6e7dd77c0e5c59b98689bf0cb185853 |
| parent | 69193a4ae421a1d69481addbba03e459df8d2a14 |
6 files changed, 489 insertions(+), 554 deletions(-)
src/link/MachO.zig+376-127| ... | ... | @@ -111,6 +111,8 @@ dysymtab_cmd: macho.dysymtab_command = .{}, |
| 111 | 111 | uuid_cmd: macho.uuid_command = .{}, |
| 112 | 112 | codesig_cmd: macho.linkedit_data_command = .{ .cmd = .CODE_SIGNATURE }, |
| 113 | 113 | |
| 114 | objects: std.ArrayListUnmanaged(Object) = .{}, | |
| 115 | archives: std.ArrayListUnmanaged(Archive) = .{}, | |
| 114 | 116 | dylibs: std.ArrayListUnmanaged(Dylib) = .{}, |
| 115 | 117 | dylibs_map: std.StringHashMapUnmanaged(u16) = .{}, |
| 116 | 118 | referenced_dylibs: std.AutoArrayHashMapUnmanaged(u16, void) = .{}, |
| ... | ... | @@ -586,8 +588,30 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No |
| 586 | 588 | parent: u16, |
| 587 | 589 | }, .Dynamic).init(arena); |
| 588 | 590 | |
| 589 | try self.parseLibs(libs.keys(), libs.values(), self.base.options.sysroot, &dependent_libs); | |
| 590 | try self.parseDependentLibs(self.base.options.sysroot, &dependent_libs); | |
| 591 | for (libs.keys(), libs.values()) |path, lib| { | |
| 592 | const in_file = try std.fs.cwd().openFile(path, .{}); | |
| 593 | defer in_file.close(); | |
| 594 | ||
| 595 | parseLibrary( | |
| 596 | self, | |
| 597 | self.base.allocator, | |
| 598 | in_file, | |
| 599 | path, | |
| 600 | lib, | |
| 601 | false, | |
| 602 | &dependent_libs, | |
| 603 | &self.base.options, | |
| 604 | ) catch |err| { | |
| 605 | // TODO convert to error | |
| 606 | log.err("{s}: parsing library failed with err {s}", .{ path, @errorName(err) }); | |
| 607 | continue; | |
| 608 | }; | |
| 609 | } | |
| 610 | ||
| 611 | parseDependentLibs(self, self.base.allocator, &dependent_libs, &self.base.options) catch |err| { | |
| 612 | // TODO convert to error | |
| 613 | log.err("parsing dependent libraries failed with err {s}", .{@errorName(err)}); | |
| 614 | }; | |
| 591 | 615 | } |
| 592 | 616 | |
| 593 | 617 | if (self.dyld_stub_binder_index == null) { |
| ... | ... | @@ -880,175 +904,373 @@ fn resolveLib( |
| 880 | 904 | return full_path; |
| 881 | 905 | } |
| 882 | 906 | |
| 883 | const ParseDylibError = error{ | |
| 884 | OutOfMemory, | |
| 885 | EmptyStubFile, | |
| 886 | MismatchedCpuArchitecture, | |
| 887 | UnsupportedCpuArchitecture, | |
| 888 | EndOfStream, | |
| 889 | } || fs.File.OpenError || std.os.PReadError || Dylib.Id.ParseError; | |
| 907 | pub fn parsePositional( | |
| 908 | ctx: anytype, | |
| 909 | gpa: Allocator, | |
| 910 | file: std.fs.File, | |
| 911 | path: []const u8, | |
| 912 | must_link: bool, | |
| 913 | dependent_libs: anytype, | |
| 914 | link_options: *const link.Options, | |
| 915 | ) !void { | |
| 916 | const tracy = trace(@src()); | |
| 917 | defer tracy.end(); | |
| 890 | 918 | |
| 891 | const DylibCreateOpts = struct { | |
| 892 | syslibroot: ?[]const u8, | |
| 919 | if (Object.isObject(file)) { | |
| 920 | try parseObject(ctx, gpa, file, path, link_options); | |
| 921 | } else { | |
| 922 | try parseLibrary(ctx, gpa, file, path, .{ | |
| 923 | .path = null, | |
| 924 | .needed = false, | |
| 925 | .weak = false, | |
| 926 | }, must_link, dependent_libs, link_options); | |
| 927 | } | |
| 928 | } | |
| 929 | ||
| 930 | fn parseObject( | |
| 931 | ctx: anytype, | |
| 932 | gpa: Allocator, | |
| 933 | file: std.fs.File, | |
| 934 | path: []const u8, | |
| 935 | link_options: *const link.Options, | |
| 936 | ) !void { | |
| 937 | const tracy = trace(@src()); | |
| 938 | defer tracy.end(); | |
| 939 | ||
| 940 | const mtime: u64 = mtime: { | |
| 941 | const stat = file.stat() catch break :mtime 0; | |
| 942 | break :mtime @as(u64, @intCast(@divFloor(stat.mtime, 1_000_000_000))); | |
| 943 | }; | |
| 944 | const file_stat = try file.stat(); | |
| 945 | const file_size = math.cast(usize, file_stat.size) orelse return error.Overflow; | |
| 946 | const contents = try file.readToEndAllocOptions(gpa, file_size, file_size, @alignOf(u64), null); | |
| 947 | ||
| 948 | var object = Object{ | |
| 949 | .name = try gpa.dupe(u8, path), | |
| 950 | .mtime = mtime, | |
| 951 | .contents = contents, | |
| 952 | }; | |
| 953 | errdefer object.deinit(gpa); | |
| 954 | try object.parse(gpa); | |
| 955 | try ctx.objects.append(gpa, object); | |
| 956 | ||
| 957 | const cpu_arch: std.Target.Cpu.Arch = switch (object.header.cputype) { | |
| 958 | macho.CPU_TYPE_ARM64 => .aarch64, | |
| 959 | macho.CPU_TYPE_X86_64 => .x86_64, | |
| 960 | else => unreachable, | |
| 961 | }; | |
| 962 | const self_cpu_arch = link_options.target.cpu.arch; | |
| 963 | ||
| 964 | if (self_cpu_arch != cpu_arch) { | |
| 965 | // TODO convert into an error | |
| 966 | log.err("{s}: invalid architecture '{s}', expected '{s}'", .{ | |
| 967 | path, | |
| 968 | @tagName(cpu_arch), | |
| 969 | @tagName(self_cpu_arch), | |
| 970 | }); | |
| 971 | } | |
| 972 | } | |
| 973 | ||
| 974 | pub fn parseLibrary( | |
| 975 | ctx: anytype, | |
| 976 | gpa: Allocator, | |
| 977 | file: std.fs.File, | |
| 978 | path: []const u8, | |
| 979 | lib: link.SystemLib, | |
| 980 | must_link: bool, | |
| 981 | dependent_libs: anytype, | |
| 982 | link_options: *const link.Options, | |
| 983 | ) !void { | |
| 984 | const tracy = trace(@src()); | |
| 985 | defer tracy.end(); | |
| 986 | ||
| 987 | const cpu_arch = link_options.target.cpu.arch; | |
| 988 | ||
| 989 | if (fat.isFatLibrary(file)) { | |
| 990 | const offset = parseFatLibrary(ctx, file, path, cpu_arch) catch |err| switch (err) { | |
| 991 | error.MissingArch => return, | |
| 992 | else => |e| return e, | |
| 993 | }; | |
| 994 | try file.seekTo(offset); | |
| 995 | ||
| 996 | if (Archive.isArchive(file, offset)) { | |
| 997 | try parseArchive(ctx, gpa, path, offset, must_link, cpu_arch); | |
| 998 | } else if (Dylib.isDylib(file, offset)) { | |
| 999 | try parseDylib(ctx, gpa, file, path, offset, dependent_libs, link_options, .{ | |
| 1000 | .needed = lib.needed, | |
| 1001 | .weak = lib.weak, | |
| 1002 | }); | |
| 1003 | } else { | |
| 1004 | // TODO convert into an error | |
| 1005 | log.err("{s}: unknown file type", .{path}); | |
| 1006 | return; | |
| 1007 | } | |
| 1008 | } else if (Archive.isArchive(file, 0)) { | |
| 1009 | try parseArchive(ctx, gpa, path, 0, must_link, cpu_arch); | |
| 1010 | } else if (Dylib.isDylib(file, 0)) { | |
| 1011 | try parseDylib(ctx, gpa, file, path, 0, dependent_libs, link_options, .{ | |
| 1012 | .needed = lib.needed, | |
| 1013 | .weak = lib.weak, | |
| 1014 | }); | |
| 1015 | } else { | |
| 1016 | parseLibStub(ctx, gpa, file, path, dependent_libs, link_options, .{ | |
| 1017 | .needed = lib.needed, | |
| 1018 | .weak = lib.weak, | |
| 1019 | }) catch |err| switch (err) { | |
| 1020 | error.NotLibStub, error.UnexpectedToken => { | |
| 1021 | // TODO convert into an error | |
| 1022 | log.err("{s}: unknown file type", .{path}); | |
| 1023 | return; | |
| 1024 | }, | |
| 1025 | else => |e| return e, | |
| 1026 | }; | |
| 1027 | } | |
| 1028 | } | |
| 1029 | ||
| 1030 | pub fn parseFatLibrary( | |
| 1031 | ctx: anytype, | |
| 1032 | file: std.fs.File, | |
| 1033 | path: []const u8, | |
| 1034 | cpu_arch: std.Target.Cpu.Arch, | |
| 1035 | ) !u64 { | |
| 1036 | _ = ctx; | |
| 1037 | var buffer: [2]fat.Arch = undefined; | |
| 1038 | const fat_archs = try fat.parseArchs(file, &buffer); | |
| 1039 | const offset = for (fat_archs) |arch| { | |
| 1040 | if (arch.tag == cpu_arch) break arch.offset; | |
| 1041 | } else { | |
| 1042 | // TODO convert into an error | |
| 1043 | log.err("{s}: missing arch in universal file: expected {s}", .{ path, @tagName(cpu_arch) }); | |
| 1044 | return error.MissingArch; | |
| 1045 | }; | |
| 1046 | return offset; | |
| 1047 | } | |
| 1048 | ||
| 1049 | fn parseArchive( | |
| 1050 | ctx: anytype, | |
| 1051 | gpa: Allocator, | |
| 1052 | path: []const u8, | |
| 1053 | fat_offset: u64, | |
| 1054 | must_link: bool, | |
| 1055 | cpu_arch: std.Target.Cpu.Arch, | |
| 1056 | ) !void { | |
| 1057 | ||
| 1058 | // We take ownership of the file so that we can store it for the duration of symbol resolution. | |
| 1059 | // TODO we shouldn't need to do that and could pre-parse the archive like we do for zld/ELF? | |
| 1060 | const file = try std.fs.cwd().openFile(path, .{}); | |
| 1061 | errdefer file.close(); | |
| 1062 | try file.seekTo(fat_offset); | |
| 1063 | ||
| 1064 | var archive = Archive{ | |
| 1065 | .file = file, | |
| 1066 | .fat_offset = fat_offset, | |
| 1067 | .name = try gpa.dupe(u8, path), | |
| 1068 | }; | |
| 1069 | errdefer archive.deinit(gpa); | |
| 1070 | ||
| 1071 | try archive.parse(gpa, file.reader()); | |
| 1072 | ||
| 1073 | // Verify arch and platform | |
| 1074 | if (archive.toc.values().len > 0) { | |
| 1075 | const offsets = archive.toc.values()[0].items; | |
| 1076 | assert(offsets.len > 0); | |
| 1077 | const off = offsets[0]; | |
| 1078 | var object = try archive.parseObject(gpa, off); // TODO we are doing all this work to pull the header only! | |
| 1079 | defer object.deinit(gpa); | |
| 1080 | ||
| 1081 | const parsed_cpu_arch: std.Target.Cpu.Arch = switch (object.header.cputype) { | |
| 1082 | macho.CPU_TYPE_ARM64 => .aarch64, | |
| 1083 | macho.CPU_TYPE_X86_64 => .x86_64, | |
| 1084 | else => unreachable, | |
| 1085 | }; | |
| 1086 | if (cpu_arch != parsed_cpu_arch) { | |
| 1087 | // TODO convert into an error | |
| 1088 | log.err("{s}: invalid architecture in archive '{s}', expected '{s}'", .{ | |
| 1089 | path, | |
| 1090 | @tagName(parsed_cpu_arch), | |
| 1091 | @tagName(cpu_arch), | |
| 1092 | }); | |
| 1093 | return error.MissingArch; | |
| 1094 | } | |
| 1095 | } | |
| 1096 | ||
| 1097 | if (must_link) { | |
| 1098 | // Get all offsets from the ToC | |
| 1099 | var offsets = std.AutoArrayHashMap(u32, void).init(gpa); | |
| 1100 | defer offsets.deinit(); | |
| 1101 | for (archive.toc.values()) |offs| { | |
| 1102 | for (offs.items) |off| { | |
| 1103 | _ = try offsets.getOrPut(off); | |
| 1104 | } | |
| 1105 | } | |
| 1106 | for (offsets.keys()) |off| { | |
| 1107 | const object = try archive.parseObject(gpa, off); | |
| 1108 | try ctx.objects.append(gpa, object); | |
| 1109 | } | |
| 1110 | } else { | |
| 1111 | try ctx.archives.append(gpa, archive); | |
| 1112 | } | |
| 1113 | } | |
| 1114 | ||
| 1115 | const DylibOpts = struct { | |
| 893 | 1116 | id: ?Dylib.Id = null, |
| 894 | 1117 | dependent: bool = false, |
| 895 | 1118 | needed: bool = false, |
| 896 | 1119 | weak: bool = false, |
| 897 | 1120 | }; |
| 898 | 1121 | |
| 899 | pub fn parseDylib( | |
| 900 | self: *MachO, | |
| 1122 | fn parseDylib( | |
| 1123 | ctx: anytype, | |
| 1124 | gpa: Allocator, | |
| 1125 | file: std.fs.File, | |
| 901 | 1126 | path: []const u8, |
| 1127 | offset: u64, | |
| 902 | 1128 | dependent_libs: anytype, |
| 903 | opts: DylibCreateOpts, | |
| 904 | ) ParseDylibError!bool { | |
| 905 | const gpa = self.base.allocator; | |
| 906 | const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { | |
| 907 | error.FileNotFound => return false, | |
| 908 | else => |e| return e, | |
| 909 | }; | |
| 910 | defer file.close(); | |
| 1129 | link_options: *const link.Options, | |
| 1130 | dylib_options: DylibOpts, | |
| 1131 | ) !void { | |
| 1132 | const self_cpu_arch = link_options.target.cpu.arch; | |
| 911 | 1133 | |
| 912 | const cpu_arch = self.base.options.target.cpu.arch; | |
| 913 | 1134 | const file_stat = try file.stat(); |
| 914 | 1135 | var file_size = math.cast(usize, file_stat.size) orelse return error.Overflow; |
| 915 | 1136 | |
| 916 | const reader = file.reader(); | |
| 917 | const fat_offset = math.cast(usize, try fat.getLibraryOffset(reader, cpu_arch)) orelse | |
| 918 | return error.Overflow; | |
| 919 | try file.seekTo(fat_offset); | |
| 920 | file_size -= fat_offset; | |
| 1137 | file_size -= offset; | |
| 921 | 1138 | |
| 922 | 1139 | const contents = try file.readToEndAllocOptions(gpa, file_size, file_size, @alignOf(u64), null); |
| 923 | 1140 | defer gpa.free(contents); |
| 924 | 1141 | |
| 925 | const dylib_id = @as(u16, @intCast(self.dylibs.items.len)); | |
| 926 | var dylib = Dylib{ .weak = opts.weak }; | |
| 1142 | var dylib = Dylib{ .weak = dylib_options.weak }; | |
| 1143 | errdefer dylib.deinit(gpa); | |
| 927 | 1144 | |
| 928 | dylib.parseFromBinary( | |
| 1145 | try dylib.parseFromBinary( | |
| 929 | 1146 | gpa, |
| 930 | cpu_arch, | |
| 931 | dylib_id, | |
| 1147 | @intCast(ctx.dylibs.items.len), // TODO defer it till later | |
| 932 | 1148 | dependent_libs, |
| 933 | 1149 | path, |
| 934 | 1150 | contents, |
| 935 | ) catch |err| switch (err) { | |
| 936 | error.EndOfStream, error.NotDylib => { | |
| 937 | try file.seekTo(0); | |
| 1151 | ); | |
| 938 | 1152 | |
| 939 | var lib_stub = LibStub.loadFromFile(gpa, file) catch { | |
| 940 | dylib.deinit(gpa); | |
| 941 | return false; | |
| 942 | }; | |
| 943 | defer lib_stub.deinit(); | |
| 944 | ||
| 945 | try dylib.parseFromStub( | |
| 946 | gpa, | |
| 947 | self.base.options.target, | |
| 948 | lib_stub, | |
| 949 | dylib_id, | |
| 950 | dependent_libs, | |
| 951 | path, | |
| 952 | ); | |
| 953 | }, | |
| 1153 | const cpu_arch: std.Target.Cpu.Arch = switch (dylib.header.?.cputype) { | |
| 1154 | macho.CPU_TYPE_ARM64 => .aarch64, | |
| 1155 | macho.CPU_TYPE_X86_64 => .x86_64, | |
| 1156 | else => unreachable, | |
| 1157 | }; | |
| 1158 | if (self_cpu_arch != cpu_arch) { | |
| 1159 | // TODO convert into an error | |
| 1160 | log.err("{s}: invalid architecture '{s}', expected '{s}'", .{ | |
| 1161 | path, | |
| 1162 | @tagName(cpu_arch), | |
| 1163 | @tagName(self_cpu_arch), | |
| 1164 | }); | |
| 1165 | return error.MissingArch; | |
| 1166 | } | |
| 1167 | ||
| 1168 | // TODO verify platform | |
| 1169 | ||
| 1170 | addDylib(ctx, gpa, dylib, link_options, .{ | |
| 1171 | .needed = dylib_options.needed, | |
| 1172 | .weak = dylib_options.weak, | |
| 1173 | }) catch |err| switch (err) { | |
| 1174 | error.DylibAlreadyExists => dylib.deinit(gpa), | |
| 954 | 1175 | else => |e| return e, |
| 955 | 1176 | }; |
| 1177 | } | |
| 956 | 1178 | |
| 957 | if (opts.id) |id| { | |
| 1179 | fn parseLibStub( | |
| 1180 | ctx: anytype, | |
| 1181 | gpa: Allocator, | |
| 1182 | file: std.fs.File, | |
| 1183 | path: []const u8, | |
| 1184 | dependent_libs: anytype, | |
| 1185 | link_options: *const link.Options, | |
| 1186 | dylib_options: DylibOpts, | |
| 1187 | ) !void { | |
| 1188 | var lib_stub = try LibStub.loadFromFile(gpa, file); | |
| 1189 | defer lib_stub.deinit(); | |
| 1190 | ||
| 1191 | if (lib_stub.inner.len == 0) return error.NotLibStub; | |
| 1192 | ||
| 1193 | // TODO verify platform | |
| 1194 | ||
| 1195 | var dylib = Dylib{ .weak = dylib_options.weak }; | |
| 1196 | errdefer dylib.deinit(gpa); | |
| 1197 | ||
| 1198 | try dylib.parseFromStub( | |
| 1199 | gpa, | |
| 1200 | link_options.target, | |
| 1201 | lib_stub, | |
| 1202 | @intCast(ctx.dylibs.items.len), // TODO defer it till later | |
| 1203 | dependent_libs, | |
| 1204 | path, | |
| 1205 | ); | |
| 1206 | ||
| 1207 | addDylib(ctx, gpa, dylib, link_options, .{ | |
| 1208 | .needed = dylib_options.needed, | |
| 1209 | .weak = dylib_options.weak, | |
| 1210 | }) catch |err| switch (err) { | |
| 1211 | error.DylibAlreadyExists => dylib.deinit(gpa), | |
| 1212 | else => |e| return e, | |
| 1213 | }; | |
| 1214 | } | |
| 1215 | ||
| 1216 | fn addDylib( | |
| 1217 | ctx: anytype, | |
| 1218 | gpa: Allocator, | |
| 1219 | dylib: Dylib, | |
| 1220 | link_options: *const link.Options, | |
| 1221 | dylib_options: DylibOpts, | |
| 1222 | ) !void { | |
| 1223 | if (dylib_options.id) |id| { | |
| 958 | 1224 | if (dylib.id.?.current_version < id.compatibility_version) { |
| 1225 | // TODO convert into an error | |
| 959 | 1226 | log.warn("found dylib is incompatible with the required minimum version", .{}); |
| 960 | 1227 | log.warn(" dylib: {s}", .{id.name}); |
| 961 | 1228 | log.warn(" required minimum version: {}", .{id.compatibility_version}); |
| 962 | 1229 | log.warn(" dylib version: {}", .{dylib.id.?.current_version}); |
| 963 | ||
| 964 | // TODO maybe this should be an error and facilitate auto-cleanup? | |
| 965 | dylib.deinit(gpa); | |
| 966 | return false; | |
| 1230 | return error.IncompatibleDylibVersion; | |
| 967 | 1231 | } |
| 968 | 1232 | } |
| 969 | 1233 | |
| 970 | try self.dylibs.append(gpa, dylib); | |
| 971 | try self.dylibs_map.putNoClobber(gpa, dylib.id.?.name, dylib_id); | |
| 1234 | const gop = try ctx.dylibs_map.getOrPut(gpa, dylib.id.?.name); | |
| 1235 | if (gop.found_existing) return error.DylibAlreadyExists; | |
| 1236 | ||
| 1237 | gop.value_ptr.* = @as(u16, @intCast(ctx.dylibs.items.len)); | |
| 1238 | try ctx.dylibs.append(gpa, dylib); | |
| 972 | 1239 | |
| 973 | 1240 | const should_link_dylib_even_if_unreachable = blk: { |
| 974 | if (self.base.options.dead_strip_dylibs and !opts.needed) break :blk false; | |
| 975 | break :blk !(opts.dependent or self.referenced_dylibs.contains(dylib_id)); | |
| 1241 | if (link_options.dead_strip_dylibs and !dylib_options.needed) break :blk false; | |
| 1242 | break :blk !(dylib_options.dependent or ctx.referenced_dylibs.contains(gop.value_ptr.*)); | |
| 976 | 1243 | }; |
| 977 | 1244 | |
| 978 | 1245 | if (should_link_dylib_even_if_unreachable) { |
| 979 | try self.referenced_dylibs.putNoClobber(gpa, dylib_id, {}); | |
| 1246 | try ctx.referenced_dylibs.putNoClobber(gpa, gop.value_ptr.*, {}); | |
| 980 | 1247 | } |
| 981 | ||
| 982 | return true; | |
| 983 | 1248 | } |
| 984 | 1249 | |
| 985 | pub fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const u8, dependent_libs: anytype) !void { | |
| 986 | for (files) |file_name| { | |
| 987 | const full_path = full_path: { | |
| 988 | var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; | |
| 989 | break :full_path try fs.realpath(file_name, &buffer); | |
| 990 | }; | |
| 991 | log.debug("parsing input file path '{s}'", .{full_path}); | |
| 992 | ||
| 993 | if (try self.parseObject(full_path)) continue; | |
| 994 | if (try self.parseArchive(full_path, false)) continue; | |
| 995 | if (try self.parseDylib(full_path, dependent_libs, .{ | |
| 996 | .syslibroot = syslibroot, | |
| 997 | })) continue; | |
| 998 | ||
| 999 | log.debug("unknown filetype for positional input file: '{s}'", .{file_name}); | |
| 1000 | } | |
| 1001 | } | |
| 1002 | ||
| 1003 | pub fn parseAndForceLoadStaticArchives(self: *MachO, files: []const []const u8) !void { | |
| 1004 | for (files) |file_name| { | |
| 1005 | const full_path = full_path: { | |
| 1006 | var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; | |
| 1007 | break :full_path try fs.realpath(file_name, &buffer); | |
| 1008 | }; | |
| 1009 | log.debug("parsing and force loading static archive '{s}'", .{full_path}); | |
| 1010 | ||
| 1011 | if (try self.parseArchive(full_path, true)) continue; | |
| 1012 | log.debug("unknown filetype: expected static archive: '{s}'", .{file_name}); | |
| 1013 | } | |
| 1014 | } | |
| 1015 | ||
| 1016 | pub fn parseLibs( | |
| 1017 | self: *MachO, | |
| 1018 | lib_names: []const []const u8, | |
| 1019 | lib_infos: []const link.SystemLib, | |
| 1020 | syslibroot: ?[]const u8, | |
| 1250 | pub fn parseDependentLibs( | |
| 1251 | ctx: anytype, | |
| 1252 | gpa: Allocator, | |
| 1021 | 1253 | dependent_libs: anytype, |
| 1254 | link_options: *const link.Options, | |
| 1022 | 1255 | ) !void { |
| 1023 | for (lib_names, 0..) |lib, i| { | |
| 1024 | const lib_info = lib_infos[i]; | |
| 1025 | log.debug("parsing lib path '{s}'", .{lib}); | |
| 1026 | if (try self.parseDylib(lib, dependent_libs, .{ | |
| 1027 | .syslibroot = syslibroot, | |
| 1028 | .needed = lib_info.needed, | |
| 1029 | .weak = lib_info.weak, | |
| 1030 | })) continue; | |
| 1031 | ||
| 1032 | log.debug("unknown filetype for a library: '{s}'", .{lib}); | |
| 1033 | } | |
| 1034 | } | |
| 1256 | const tracy = trace(@src()); | |
| 1257 | defer tracy.end(); | |
| 1035 | 1258 | |
| 1036 | pub fn parseDependentLibs(self: *MachO, syslibroot: ?[]const u8, dependent_libs: anytype) !void { | |
| 1037 | 1259 | // At this point, we can now parse dependents of dylibs preserving the inclusion order of: |
| 1038 | 1260 | // 1) anything on the linker line is parsed first |
| 1039 | 1261 | // 2) afterwards, we parse dependents of the included dylibs |
| 1040 | 1262 | // TODO this should not be performed if the user specifies `-flat_namespace` flag. |
| 1041 | 1263 | // See ld64 manpages. |
| 1042 | var arena_alloc = std.heap.ArenaAllocator.init(self.base.allocator); | |
| 1264 | var arena_alloc = std.heap.ArenaAllocator.init(gpa); | |
| 1043 | 1265 | const arena = arena_alloc.allocator(); |
| 1044 | 1266 | defer arena_alloc.deinit(); |
| 1045 | 1267 | |
| 1046 | while (dependent_libs.readItem()) |*dep_id| { | |
| 1047 | defer dep_id.id.deinit(self.base.allocator); | |
| 1268 | outer: while (dependent_libs.readItem()) |dep_id| { | |
| 1269 | defer dep_id.id.deinit(gpa); | |
| 1048 | 1270 | |
| 1049 | if (self.dylibs_map.contains(dep_id.id.name)) continue; | |
| 1271 | if (ctx.dylibs_map.contains(dep_id.id.name)) continue; | |
| 1050 | 1272 | |
| 1051 | const weak = self.dylibs.items[dep_id.parent].weak; | |
| 1273 | const weak = ctx.dylibs.items[dep_id.parent].weak; | |
| 1052 | 1274 | const has_ext = blk: { |
| 1053 | 1275 | const basename = fs.path.basename(dep_id.id.name); |
| 1054 | 1276 | break :blk mem.lastIndexOfScalar(u8, basename, '.') != null; |
| ... | ... | @@ -1061,20 +1283,47 @@ pub fn parseDependentLibs(self: *MachO, syslibroot: ?[]const u8, dependent_libs: |
| 1061 | 1283 | |
| 1062 | 1284 | for (&[_][]const u8{ extension, ".tbd" }) |ext| { |
| 1063 | 1285 | const with_ext = try std.fmt.allocPrint(arena, "{s}{s}", .{ without_ext, ext }); |
| 1064 | const full_path = if (syslibroot) |root| try fs.path.join(arena, &.{ root, with_ext }) else with_ext; | |
| 1286 | const full_path = if (link_options.sysroot) |root| | |
| 1287 | try fs.path.join(arena, &.{ root, with_ext }) | |
| 1288 | else | |
| 1289 | with_ext; | |
| 1290 | ||
| 1291 | const file = std.fs.cwd().openFile(full_path, .{}) catch |err| switch (err) { | |
| 1292 | error.FileNotFound => continue, | |
| 1293 | else => |e| return e, | |
| 1294 | }; | |
| 1295 | defer file.close(); | |
| 1065 | 1296 | |
| 1066 | 1297 | log.debug("trying dependency at fully resolved path {s}", .{full_path}); |
| 1067 | 1298 | |
| 1068 | const did_parse_successfully = try self.parseDylib(full_path, dependent_libs, .{ | |
| 1069 | .id = dep_id.id, | |
| 1070 | .syslibroot = syslibroot, | |
| 1071 | .dependent = true, | |
| 1072 | .weak = weak, | |
| 1073 | }); | |
| 1074 | if (did_parse_successfully) break; | |
| 1075 | } else { | |
| 1076 | log.debug("unable to resolve dependency {s}", .{dep_id.id.name}); | |
| 1299 | const offset: u64 = if (fat.isFatLibrary(file)) blk: { | |
| 1300 | const offset = parseFatLibrary(ctx, file, full_path, link_options.target.cpu.arch) catch |err| switch (err) { | |
| 1301 | error.MissingArch => break, | |
| 1302 | else => |e| return e, | |
| 1303 | }; | |
| 1304 | try file.seekTo(offset); | |
| 1305 | break :blk offset; | |
| 1306 | } else 0; | |
| 1307 | ||
| 1308 | if (Dylib.isDylib(file, offset)) { | |
| 1309 | try parseDylib(ctx, gpa, file, full_path, offset, dependent_libs, link_options, .{ | |
| 1310 | .dependent = true, | |
| 1311 | .weak = weak, | |
| 1312 | }); | |
| 1313 | } else { | |
| 1314 | parseLibStub(ctx, gpa, file, full_path, dependent_libs, link_options, .{ | |
| 1315 | .dependent = true, | |
| 1316 | .weak = weak, | |
| 1317 | }) catch |err| switch (err) { | |
| 1318 | error.NotLibStub, error.UnexpectedToken => continue, | |
| 1319 | else => |e| return e, | |
| 1320 | }; | |
| 1321 | } | |
| 1322 | continue :outer; | |
| 1077 | 1323 | } |
| 1324 | ||
| 1325 | // TODO convert into an error | |
| 1326 | log.err("{s}: unable to resolve dependency", .{dep_id.id.name}); | |
| 1078 | 1327 | } |
| 1079 | 1328 | } |
| 1080 | 1329 | |
| ... | ... | @@ -2517,7 +2766,7 @@ fn populateMissingMetadata(self: *MachO) !void { |
| 2517 | 2766 | // The first __TEXT segment is immovable and covers MachO header and load commands. |
| 2518 | 2767 | self.header_segment_cmd_index = @as(u8, @intCast(self.segments.items.len)); |
| 2519 | 2768 | const ideal_size = @max(self.base.options.headerpad_size orelse 0, default_headerpad_size); |
| 2520 | const needed_size = mem.alignForward(u64, padToIdeal(ideal_size), getPageSize(self.base.options.target.cpu.arch)); | |
| 2769 | const needed_size = mem.alignForward(u64, padToIdeal(ideal_size), getPageSize(cpu_arch)); | |
| 2521 | 2770 | |
| 2522 | 2771 | log.debug("found __TEXT segment (header-only) free space 0x{x} to 0x{x}", .{ 0, needed_size }); |
| 2523 | 2772 |
src/link/MachO/Archive.zig+10-26| ... | ... | @@ -87,6 +87,13 @@ const ar_hdr = extern struct { |
| 87 | 87 | } |
| 88 | 88 | }; |
| 89 | 89 | |
| 90 | pub fn isArchive(file: fs.File, fat_offset: u64) bool { | |
| 91 | const reader = file.reader(); | |
| 92 | const magic = reader.readBytesNoEof(SARMAG) catch return false; | |
| 93 | defer file.seekTo(fat_offset) catch {}; | |
| 94 | return mem.eql(u8, &magic, ARMAG); | |
| 95 | } | |
| 96 | ||
| 90 | 97 | pub fn deinit(self: *Archive, allocator: Allocator) void { |
| 91 | 98 | self.file.close(); |
| 92 | 99 | for (self.toc.keys()) |*key| { |
| ... | ... | @@ -100,21 +107,8 @@ pub fn deinit(self: *Archive, allocator: Allocator) void { |
| 100 | 107 | } |
| 101 | 108 | |
| 102 | 109 | pub fn parse(self: *Archive, allocator: Allocator, reader: anytype) !void { |
| 103 | const magic = try reader.readBytesNoEof(SARMAG); | |
| 104 | if (!mem.eql(u8, &magic, ARMAG)) { | |
| 105 | log.debug("invalid magic: expected '{s}', found '{s}'", .{ ARMAG, magic }); | |
| 106 | return error.NotArchive; | |
| 107 | } | |
| 108 | ||
| 110 | _ = try reader.readBytesNoEof(SARMAG); | |
| 109 | 111 | self.header = try reader.readStruct(ar_hdr); |
| 110 | if (!mem.eql(u8, &self.header.ar_fmag, ARFMAG)) { | |
| 111 | log.debug("invalid header delimiter: expected '{s}', found '{s}'", .{ | |
| 112 | ARFMAG, | |
| 113 | self.header.ar_fmag, | |
| 114 | }); | |
| 115 | return error.NotArchive; | |
| 116 | } | |
| 117 | ||
| 118 | 112 | const name_or_length = try self.header.nameOrLength(); |
| 119 | 113 | var embedded_name = try parseName(allocator, name_or_length, reader); |
| 120 | 114 | log.debug("parsing archive '{s}' at '{s}'", .{ embedded_name, self.name }); |
| ... | ... | @@ -182,22 +176,12 @@ fn parseTableOfContents(self: *Archive, allocator: Allocator, reader: anytype) ! |
| 182 | 176 | } |
| 183 | 177 | } |
| 184 | 178 | |
| 185 | pub fn parseObject( | |
| 186 | self: Archive, | |
| 187 | gpa: Allocator, | |
| 188 | cpu_arch: std.Target.Cpu.Arch, | |
| 189 | offset: u32, | |
| 190 | ) !Object { | |
| 179 | pub fn parseObject(self: Archive, gpa: Allocator, offset: u32) !Object { | |
| 191 | 180 | const reader = self.file.reader(); |
| 192 | 181 | try reader.context.seekTo(self.fat_offset + offset); |
| 193 | 182 | |
| 194 | 183 | const object_header = try reader.readStruct(ar_hdr); |
| 195 | 184 | |
| 196 | if (!mem.eql(u8, &object_header.ar_fmag, ARFMAG)) { | |
| 197 | log.err("invalid header delimiter: expected '{s}', found '{s}'", .{ ARFMAG, object_header.ar_fmag }); | |
| 198 | return error.MalformedArchive; | |
| 199 | } | |
| 200 | ||
| 201 | 185 | const name_or_length = try object_header.nameOrLength(); |
| 202 | 186 | const object_name = try parseName(gpa, name_or_length, reader); |
| 203 | 187 | defer gpa.free(object_name); |
| ... | ... | @@ -227,7 +211,7 @@ pub fn parseObject( |
| 227 | 211 | .contents = contents, |
| 228 | 212 | }; |
| 229 | 213 | |
| 230 | try object.parse(gpa, cpu_arch); | |
| 214 | try object.parse(gpa); | |
| 231 | 215 | |
| 232 | 216 | return object; |
| 233 | 217 | } |
src/link/MachO/Dylib.zig+13-20| ... | ... | @@ -20,6 +20,8 @@ const Tbd = tapi.Tbd; |
| 20 | 20 | |
| 21 | 21 | id: ?Id = null, |
| 22 | 22 | weak: bool = false, |
| 23 | /// Header is only set if Dylib is parsed directly from a binary and not a stub file. | |
| 24 | header: ?macho.mach_header_64 = null, | |
| 23 | 25 | |
| 24 | 26 | /// Parsed symbol table represented as hash map of symbols' |
| 25 | 27 | /// names. We can and should defer creating *Symbols until |
| ... | ... | @@ -116,6 +118,13 @@ pub const Id = struct { |
| 116 | 118 | } |
| 117 | 119 | }; |
| 118 | 120 | |
| 121 | pub fn isDylib(file: std.fs.File, fat_offset: u64) bool { | |
| 122 | const reader = file.reader(); | |
| 123 | const hdr = reader.readStruct(macho.mach_header_64) catch return false; | |
| 124 | defer file.seekTo(fat_offset) catch {}; | |
| 125 | return hdr.filetype == macho.MH_DYLIB; | |
| 126 | } | |
| 127 | ||
| 119 | 128 | pub fn deinit(self: *Dylib, allocator: Allocator) void { |
| 120 | 129 | for (self.symbols.keys()) |key| { |
| 121 | 130 | allocator.free(key); |
| ... | ... | @@ -129,7 +138,6 @@ pub fn deinit(self: *Dylib, allocator: Allocator) void { |
| 129 | 138 | pub fn parseFromBinary( |
| 130 | 139 | self: *Dylib, |
| 131 | 140 | allocator: Allocator, |
| 132 | cpu_arch: std.Target.Cpu.Arch, | |
| 133 | 141 | dylib_id: u16, |
| 134 | 142 | dependent_libs: anytype, |
| 135 | 143 | name: []const u8, |
| ... | ... | @@ -140,27 +148,12 @@ pub fn parseFromBinary( |
| 140 | 148 | |
| 141 | 149 | log.debug("parsing shared library '{s}'", .{name}); |
| 142 | 150 | |
| 143 | const header = try reader.readStruct(macho.mach_header_64); | |
| 144 | ||
| 145 | if (header.filetype != macho.MH_DYLIB) { | |
| 146 | log.debug("invalid filetype: expected 0x{x}, found 0x{x}", .{ macho.MH_DYLIB, header.filetype }); | |
| 147 | return error.NotDylib; | |
| 148 | } | |
| 149 | ||
| 150 | const this_arch: std.Target.Cpu.Arch = try fat.decodeArch(header.cputype, true); | |
| 151 | ||
| 152 | if (this_arch != cpu_arch) { | |
| 153 | log.err("mismatched cpu architecture: expected {s}, found {s}", .{ | |
| 154 | @tagName(cpu_arch), | |
| 155 | @tagName(this_arch), | |
| 156 | }); | |
| 157 | return error.MismatchedCpuArchitecture; | |
| 158 | } | |
| 151 | self.header = try reader.readStruct(macho.mach_header_64); | |
| 159 | 152 | |
| 160 | const should_lookup_reexports = header.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0; | |
| 153 | const should_lookup_reexports = self.header.?.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0; | |
| 161 | 154 | var it = LoadCommandIterator{ |
| 162 | .ncmds = header.ncmds, | |
| 163 | .buffer = data[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds], | |
| 155 | .ncmds = self.header.?.ncmds, | |
| 156 | .buffer = data[@sizeOf(macho.mach_header_64)..][0..self.header.?.sizeofcmds], | |
| 164 | 157 | }; |
| 165 | 158 | while (it.next()) |cmd| { |
| 166 | 159 | switch (cmd.cmd()) { |
src/link/MachO/Object.zig+11-30| ... | ... | @@ -91,6 +91,13 @@ const Record = struct { |
| 91 | 91 | reloc: Entry, |
| 92 | 92 | }; |
| 93 | 93 | |
| 94 | pub fn isObject(file: std.fs.File) bool { | |
| 95 | const reader = file.reader(); | |
| 96 | const hdr = reader.readStruct(macho.mach_header_64) catch return false; | |
| 97 | defer file.seekTo(0) catch {}; | |
| 98 | return hdr.filetype == macho.MH_OBJECT; | |
| 99 | } | |
| 100 | ||
| 94 | 101 | pub fn deinit(self: *Object, gpa: Allocator) void { |
| 95 | 102 | self.atoms.deinit(gpa); |
| 96 | 103 | self.exec_atoms.deinit(gpa); |
| ... | ... | @@ -118,36 +125,12 @@ pub fn deinit(self: *Object, gpa: Allocator) void { |
| 118 | 125 | self.data_in_code.deinit(gpa); |
| 119 | 126 | } |
| 120 | 127 | |
| 121 | pub fn parse(self: *Object, allocator: Allocator, cpu_arch: std.Target.Cpu.Arch) !void { | |
| 128 | pub fn parse(self: *Object, allocator: Allocator) !void { | |
| 122 | 129 | var stream = std.io.fixedBufferStream(self.contents); |
| 123 | 130 | const reader = stream.reader(); |
| 124 | 131 | |
| 125 | 132 | self.header = try reader.readStruct(macho.mach_header_64); |
| 126 | 133 | |
| 127 | if (self.header.filetype != macho.MH_OBJECT) { | |
| 128 | log.debug("invalid filetype: expected 0x{x}, found 0x{x}", .{ | |
| 129 | macho.MH_OBJECT, | |
| 130 | self.header.filetype, | |
| 131 | }); | |
| 132 | return error.NotObject; | |
| 133 | } | |
| 134 | ||
| 135 | const this_arch: std.Target.Cpu.Arch = switch (self.header.cputype) { | |
| 136 | macho.CPU_TYPE_ARM64 => .aarch64, | |
| 137 | macho.CPU_TYPE_X86_64 => .x86_64, | |
| 138 | else => |value| { | |
| 139 | log.err("unsupported cpu architecture 0x{x}", .{value}); | |
| 140 | return error.UnsupportedCpuArchitecture; | |
| 141 | }, | |
| 142 | }; | |
| 143 | if (this_arch != cpu_arch) { | |
| 144 | log.err("mismatched cpu architecture: expected {s}, found {s}", .{ | |
| 145 | @tagName(cpu_arch), | |
| 146 | @tagName(this_arch), | |
| 147 | }); | |
| 148 | return error.MismatchedCpuArchitecture; | |
| 149 | } | |
| 150 | ||
| 151 | 134 | var it = LoadCommandIterator{ |
| 152 | 135 | .ncmds = self.header.ncmds, |
| 153 | 136 | .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds], |
| ... | ... | @@ -437,7 +420,7 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void { |
| 437 | 420 | // Well, shit, sometimes compilers skip the dysymtab load command altogether, meaning we |
| 438 | 421 | // have to infer the start of undef section in the symtab ourselves. |
| 439 | 422 | const iundefsym = blk: { |
| 440 | const dysymtab = self.parseDysymtab() orelse { | |
| 423 | const dysymtab = self.getDysymtab() orelse { | |
| 441 | 424 | var iundefsym: usize = self.in_symtab.?.len; |
| 442 | 425 | while (iundefsym > 0) : (iundefsym -= 1) { |
| 443 | 426 | const sym = self.symtab[iundefsym - 1]; |
| ... | ... | @@ -945,16 +928,14 @@ fn diceLessThan(ctx: void, lhs: macho.data_in_code_entry, rhs: macho.data_in_cod |
| 945 | 928 | return lhs.offset < rhs.offset; |
| 946 | 929 | } |
| 947 | 930 | |
| 948 | fn parseDysymtab(self: Object) ?macho.dysymtab_command { | |
| 931 | fn getDysymtab(self: Object) ?macho.dysymtab_command { | |
| 949 | 932 | var it = LoadCommandIterator{ |
| 950 | 933 | .ncmds = self.header.ncmds, |
| 951 | 934 | .buffer = self.contents[@sizeOf(macho.mach_header_64)..][0..self.header.sizeofcmds], |
| 952 | 935 | }; |
| 953 | 936 | while (it.next()) |cmd| { |
| 954 | 937 | switch (cmd.cmd()) { |
| 955 | .DYSYMTAB => { | |
| 956 | return cmd.cast(macho.dysymtab_command).?; | |
| 957 | }, | |
| 938 | .DYSYMTAB => return cmd.cast(macho.dysymtab_command).?, | |
| 958 | 939 | else => {}, |
| 959 | 940 | } |
| 960 | 941 | } else return null; |
src/link/MachO/fat.zig+23-25| ... | ... | @@ -1,42 +1,40 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const assert = std.debug.assert; | |
| 2 | 3 | const log = std.log.scoped(.archive); |
| 3 | 4 | const macho = std.macho; |
| 4 | 5 | const mem = std.mem; |
| 5 | 6 | |
| 6 | pub fn decodeArch(cputype: macho.cpu_type_t, comptime logError: bool) !std.Target.Cpu.Arch { | |
| 7 | const cpu_arch: std.Target.Cpu.Arch = switch (cputype) { | |
| 8 | macho.CPU_TYPE_ARM64 => .aarch64, | |
| 9 | macho.CPU_TYPE_X86_64 => .x86_64, | |
| 10 | else => { | |
| 11 | if (logError) { | |
| 12 | log.err("unsupported cpu architecture 0x{x}", .{cputype}); | |
| 13 | } | |
| 14 | return error.UnsupportedCpuArchitecture; | |
| 15 | }, | |
| 16 | }; | |
| 17 | return cpu_arch; | |
| 7 | pub fn isFatLibrary(file: std.fs.File) bool { | |
| 8 | const reader = file.reader(); | |
| 9 | const hdr = reader.readStructBig(macho.fat_header) catch return false; | |
| 10 | defer file.seekTo(0) catch {}; | |
| 11 | return hdr.magic == macho.FAT_MAGIC; | |
| 18 | 12 | } |
| 19 | 13 | |
| 20 | pub fn getLibraryOffset(reader: anytype, cpu_arch: std.Target.Cpu.Arch) !u64 { | |
| 14 | pub const Arch = struct { | |
| 15 | tag: std.Target.Cpu.Arch, | |
| 16 | offset: u64, | |
| 17 | }; | |
| 18 | ||
| 19 | pub fn parseArchs(file: std.fs.File, buffer: *[2]Arch) ![]const Arch { | |
| 20 | const reader = file.reader(); | |
| 21 | 21 | const fat_header = try reader.readStructBig(macho.fat_header); |
| 22 | if (fat_header.magic != macho.FAT_MAGIC) return 0; | |
| 22 | assert(fat_header.magic == macho.FAT_MAGIC); | |
| 23 | 23 | |
| 24 | var count: usize = 0; | |
| 24 | 25 | var fat_arch_index: u32 = 0; |
| 25 | 26 | while (fat_arch_index < fat_header.nfat_arch) : (fat_arch_index += 1) { |
| 26 | 27 | const fat_arch = try reader.readStructBig(macho.fat_arch); |
| 27 | 28 | // If we come across an architecture that we do not know how to handle, that's |
| 28 | 29 | // fine because we can keep looking for one that might match. |
| 29 | const lib_arch = decodeArch(fat_arch.cputype, false) catch |err| switch (err) { | |
| 30 | error.UnsupportedCpuArchitecture => continue, | |
| 30 | const arch: std.Target.Cpu.Arch = switch (fat_arch.cputype) { | |
| 31 | macho.CPU_TYPE_ARM64 => if (fat_arch.cpusubtype == macho.CPU_SUBTYPE_ARM_ALL) .aarch64 else continue, | |
| 32 | macho.CPU_TYPE_X86_64 => if (fat_arch.cpusubtype == macho.CPU_SUBTYPE_X86_64_ALL) .x86_64 else continue, | |
| 33 | else => continue, | |
| 31 | 34 | }; |
| 32 | if (lib_arch == cpu_arch) { | |
| 33 | // We have found a matching architecture! | |
| 34 | return fat_arch.offset; | |
| 35 | } | |
| 36 | } else { | |
| 37 | log.err("Could not find matching cpu architecture in fat library: expected {s}", .{ | |
| 38 | @tagName(cpu_arch), | |
| 39 | }); | |
| 40 | return error.MismatchedCpuArchitecture; | |
| 35 | buffer[count] = .{ .tag = arch, .offset = fat_arch.offset }; | |
| 36 | count += 1; | |
| 41 | 37 | } |
| 38 | ||
| 39 | return buffer[0..count]; | |
| 42 | 40 | } |
src/link/MachO/zld.zig+56-326| ... | ... | @@ -89,298 +89,6 @@ pub const Zld = struct { |
| 89 | 89 | |
| 90 | 90 | atoms: std.ArrayListUnmanaged(Atom) = .{}, |
| 91 | 91 | |
| 92 | fn parseObject(self: *Zld, path: []const u8) !bool { | |
| 93 | const gpa = self.gpa; | |
| 94 | const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { | |
| 95 | error.FileNotFound => return false, | |
| 96 | else => |e| return e, | |
| 97 | }; | |
| 98 | defer file.close(); | |
| 99 | ||
| 100 | const name = try gpa.dupe(u8, path); | |
| 101 | errdefer gpa.free(name); | |
| 102 | const cpu_arch = self.options.target.cpu.arch; | |
| 103 | const mtime: u64 = mtime: { | |
| 104 | const stat = file.stat() catch break :mtime 0; | |
| 105 | break :mtime @as(u64, @intCast(@divFloor(stat.mtime, 1_000_000_000))); | |
| 106 | }; | |
| 107 | const file_stat = try file.stat(); | |
| 108 | const file_size = math.cast(usize, file_stat.size) orelse return error.Overflow; | |
| 109 | const contents = try file.readToEndAllocOptions(gpa, file_size, file_size, @alignOf(u64), null); | |
| 110 | ||
| 111 | var object = Object{ | |
| 112 | .name = name, | |
| 113 | .mtime = mtime, | |
| 114 | .contents = contents, | |
| 115 | }; | |
| 116 | ||
| 117 | object.parse(gpa, cpu_arch) catch |err| switch (err) { | |
| 118 | error.EndOfStream, error.NotObject => { | |
| 119 | object.deinit(gpa); | |
| 120 | return false; | |
| 121 | }, | |
| 122 | else => |e| return e, | |
| 123 | }; | |
| 124 | ||
| 125 | try self.objects.append(gpa, object); | |
| 126 | ||
| 127 | return true; | |
| 128 | } | |
| 129 | ||
| 130 | fn parseArchive(self: *Zld, path: []const u8, force_load: bool) !bool { | |
| 131 | const gpa = self.gpa; | |
| 132 | const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { | |
| 133 | error.FileNotFound => return false, | |
| 134 | else => |e| return e, | |
| 135 | }; | |
| 136 | errdefer file.close(); | |
| 137 | ||
| 138 | const name = try gpa.dupe(u8, path); | |
| 139 | errdefer gpa.free(name); | |
| 140 | const cpu_arch = self.options.target.cpu.arch; | |
| 141 | const reader = file.reader(); | |
| 142 | const fat_offset = try fat.getLibraryOffset(reader, cpu_arch); | |
| 143 | try reader.context.seekTo(fat_offset); | |
| 144 | ||
| 145 | var archive = Archive{ | |
| 146 | .name = name, | |
| 147 | .fat_offset = fat_offset, | |
| 148 | .file = file, | |
| 149 | }; | |
| 150 | ||
| 151 | archive.parse(gpa, reader) catch |err| switch (err) { | |
| 152 | error.EndOfStream, error.NotArchive => { | |
| 153 | archive.deinit(gpa); | |
| 154 | return false; | |
| 155 | }, | |
| 156 | else => |e| return e, | |
| 157 | }; | |
| 158 | ||
| 159 | if (force_load) { | |
| 160 | defer archive.deinit(gpa); | |
| 161 | // Get all offsets from the ToC | |
| 162 | var offsets = std.AutoArrayHashMap(u32, void).init(gpa); | |
| 163 | defer offsets.deinit(); | |
| 164 | for (archive.toc.values()) |offs| { | |
| 165 | for (offs.items) |off| { | |
| 166 | _ = try offsets.getOrPut(off); | |
| 167 | } | |
| 168 | } | |
| 169 | for (offsets.keys()) |off| { | |
| 170 | const object = try archive.parseObject(gpa, cpu_arch, off); | |
| 171 | try self.objects.append(gpa, object); | |
| 172 | } | |
| 173 | } else { | |
| 174 | try self.archives.append(gpa, archive); | |
| 175 | } | |
| 176 | ||
| 177 | return true; | |
| 178 | } | |
| 179 | ||
| 180 | const ParseDylibError = error{ | |
| 181 | OutOfMemory, | |
| 182 | EmptyStubFile, | |
| 183 | MismatchedCpuArchitecture, | |
| 184 | UnsupportedCpuArchitecture, | |
| 185 | EndOfStream, | |
| 186 | } || fs.File.OpenError || std.os.PReadError || Dylib.Id.ParseError; | |
| 187 | ||
| 188 | const DylibCreateOpts = struct { | |
| 189 | syslibroot: ?[]const u8, | |
| 190 | id: ?Dylib.Id = null, | |
| 191 | dependent: bool = false, | |
| 192 | needed: bool = false, | |
| 193 | weak: bool = false, | |
| 194 | }; | |
| 195 | ||
| 196 | fn parseDylib( | |
| 197 | self: *Zld, | |
| 198 | path: []const u8, | |
| 199 | dependent_libs: anytype, | |
| 200 | opts: DylibCreateOpts, | |
| 201 | ) ParseDylibError!bool { | |
| 202 | const gpa = self.gpa; | |
| 203 | const file = fs.cwd().openFile(path, .{}) catch |err| switch (err) { | |
| 204 | error.FileNotFound => return false, | |
| 205 | else => |e| return e, | |
| 206 | }; | |
| 207 | defer file.close(); | |
| 208 | ||
| 209 | const cpu_arch = self.options.target.cpu.arch; | |
| 210 | const file_stat = try file.stat(); | |
| 211 | var file_size = math.cast(usize, file_stat.size) orelse return error.Overflow; | |
| 212 | ||
| 213 | const reader = file.reader(); | |
| 214 | const fat_offset = math.cast(usize, try fat.getLibraryOffset(reader, cpu_arch)) orelse | |
| 215 | return error.Overflow; | |
| 216 | try file.seekTo(fat_offset); | |
| 217 | file_size -= fat_offset; | |
| 218 | ||
| 219 | const contents = try file.readToEndAllocOptions(gpa, file_size, file_size, @alignOf(u64), null); | |
| 220 | defer gpa.free(contents); | |
| 221 | ||
| 222 | const dylib_id = @as(u16, @intCast(self.dylibs.items.len)); | |
| 223 | var dylib = Dylib{ .weak = opts.weak }; | |
| 224 | ||
| 225 | dylib.parseFromBinary( | |
| 226 | gpa, | |
| 227 | cpu_arch, | |
| 228 | dylib_id, | |
| 229 | dependent_libs, | |
| 230 | path, | |
| 231 | contents, | |
| 232 | ) catch |err| switch (err) { | |
| 233 | error.EndOfStream, error.NotDylib => { | |
| 234 | try file.seekTo(0); | |
| 235 | ||
| 236 | var lib_stub = LibStub.loadFromFile(gpa, file) catch { | |
| 237 | dylib.deinit(gpa); | |
| 238 | return false; | |
| 239 | }; | |
| 240 | defer lib_stub.deinit(); | |
| 241 | ||
| 242 | try dylib.parseFromStub( | |
| 243 | gpa, | |
| 244 | self.options.target, | |
| 245 | lib_stub, | |
| 246 | dylib_id, | |
| 247 | dependent_libs, | |
| 248 | path, | |
| 249 | ); | |
| 250 | }, | |
| 251 | else => |e| return e, | |
| 252 | }; | |
| 253 | ||
| 254 | if (opts.id) |id| { | |
| 255 | if (dylib.id.?.current_version < id.compatibility_version) { | |
| 256 | log.warn("found dylib is incompatible with the required minimum version", .{}); | |
| 257 | log.warn(" dylib: {s}", .{id.name}); | |
| 258 | log.warn(" required minimum version: {}", .{id.compatibility_version}); | |
| 259 | log.warn(" dylib version: {}", .{dylib.id.?.current_version}); | |
| 260 | ||
| 261 | // TODO maybe this should be an error and facilitate auto-cleanup? | |
| 262 | dylib.deinit(gpa); | |
| 263 | return false; | |
| 264 | } | |
| 265 | } | |
| 266 | ||
| 267 | try self.dylibs.append(gpa, dylib); | |
| 268 | try self.dylibs_map.putNoClobber(gpa, dylib.id.?.name, dylib_id); | |
| 269 | ||
| 270 | const should_link_dylib_even_if_unreachable = blk: { | |
| 271 | if (self.options.dead_strip_dylibs and !opts.needed) break :blk false; | |
| 272 | break :blk !(opts.dependent or self.referenced_dylibs.contains(dylib_id)); | |
| 273 | }; | |
| 274 | ||
| 275 | if (should_link_dylib_even_if_unreachable) { | |
| 276 | try self.referenced_dylibs.putNoClobber(gpa, dylib_id, {}); | |
| 277 | } | |
| 278 | ||
| 279 | return true; | |
| 280 | } | |
| 281 | ||
| 282 | fn parseInputFiles( | |
| 283 | self: *Zld, | |
| 284 | files: []const []const u8, | |
| 285 | syslibroot: ?[]const u8, | |
| 286 | dependent_libs: anytype, | |
| 287 | ) !void { | |
| 288 | for (files) |file_name| { | |
| 289 | const full_path = full_path: { | |
| 290 | var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; | |
| 291 | break :full_path try fs.realpath(file_name, &buffer); | |
| 292 | }; | |
| 293 | log.debug("parsing input file path '{s}'", .{full_path}); | |
| 294 | ||
| 295 | if (try self.parseObject(full_path)) continue; | |
| 296 | if (try self.parseArchive(full_path, false)) continue; | |
| 297 | if (try self.parseDylib(full_path, dependent_libs, .{ | |
| 298 | .syslibroot = syslibroot, | |
| 299 | })) continue; | |
| 300 | ||
| 301 | log.debug("unknown filetype for positional input file: '{s}'", .{file_name}); | |
| 302 | } | |
| 303 | } | |
| 304 | ||
| 305 | fn parseAndForceLoadStaticArchives(self: *Zld, files: []const []const u8) !void { | |
| 306 | for (files) |file_name| { | |
| 307 | const full_path = full_path: { | |
| 308 | var buffer: [fs.MAX_PATH_BYTES]u8 = undefined; | |
| 309 | break :full_path try fs.realpath(file_name, &buffer); | |
| 310 | }; | |
| 311 | log.debug("parsing and force loading static archive '{s}'", .{full_path}); | |
| 312 | ||
| 313 | if (try self.parseArchive(full_path, true)) continue; | |
| 314 | log.debug("unknown filetype: expected static archive: '{s}'", .{file_name}); | |
| 315 | } | |
| 316 | } | |
| 317 | ||
| 318 | fn parseLibs( | |
| 319 | self: *Zld, | |
| 320 | lib_names: []const []const u8, | |
| 321 | lib_infos: []const link.SystemLib, | |
| 322 | syslibroot: ?[]const u8, | |
| 323 | dependent_libs: anytype, | |
| 324 | ) !void { | |
| 325 | for (lib_names, 0..) |lib, i| { | |
| 326 | const lib_info = lib_infos[i]; | |
| 327 | log.debug("parsing lib path '{s}'", .{lib}); | |
| 328 | if (try self.parseDylib(lib, dependent_libs, .{ | |
| 329 | .syslibroot = syslibroot, | |
| 330 | .needed = lib_info.needed, | |
| 331 | .weak = lib_info.weak, | |
| 332 | })) continue; | |
| 333 | if (try self.parseArchive(lib, false)) continue; | |
| 334 | ||
| 335 | log.debug("unknown filetype for a library: '{s}'", .{lib}); | |
| 336 | } | |
| 337 | } | |
| 338 | ||
| 339 | fn parseDependentLibs(self: *Zld, syslibroot: ?[]const u8, dependent_libs: anytype) !void { | |
| 340 | // At this point, we can now parse dependents of dylibs preserving the inclusion order of: | |
| 341 | // 1) anything on the linker line is parsed first | |
| 342 | // 2) afterwards, we parse dependents of the included dylibs | |
| 343 | // TODO this should not be performed if the user specifies `-flat_namespace` flag. | |
| 344 | // See ld64 manpages. | |
| 345 | var arena_alloc = std.heap.ArenaAllocator.init(self.gpa); | |
| 346 | const arena = arena_alloc.allocator(); | |
| 347 | defer arena_alloc.deinit(); | |
| 348 | ||
| 349 | while (dependent_libs.readItem()) |*dep_id| { | |
| 350 | defer dep_id.id.deinit(self.gpa); | |
| 351 | ||
| 352 | if (self.dylibs_map.contains(dep_id.id.name)) continue; | |
| 353 | ||
| 354 | const weak = self.dylibs.items[dep_id.parent].weak; | |
| 355 | const has_ext = blk: { | |
| 356 | const basename = fs.path.basename(dep_id.id.name); | |
| 357 | break :blk mem.lastIndexOfScalar(u8, basename, '.') != null; | |
| 358 | }; | |
| 359 | const extension = if (has_ext) fs.path.extension(dep_id.id.name) else ""; | |
| 360 | const without_ext = if (has_ext) blk: { | |
| 361 | const index = mem.lastIndexOfScalar(u8, dep_id.id.name, '.') orelse unreachable; | |
| 362 | break :blk dep_id.id.name[0..index]; | |
| 363 | } else dep_id.id.name; | |
| 364 | ||
| 365 | for (&[_][]const u8{ extension, ".tbd" }) |ext| { | |
| 366 | const with_ext = try std.fmt.allocPrint(arena, "{s}{s}", .{ without_ext, ext }); | |
| 367 | const full_path = if (syslibroot) |root| try fs.path.join(arena, &.{ root, with_ext }) else with_ext; | |
| 368 | ||
| 369 | log.debug("trying dependency at fully resolved path {s}", .{full_path}); | |
| 370 | ||
| 371 | const did_parse_successfully = try self.parseDylib(full_path, dependent_libs, .{ | |
| 372 | .id = dep_id.id, | |
| 373 | .syslibroot = syslibroot, | |
| 374 | .dependent = true, | |
| 375 | .weak = weak, | |
| 376 | }); | |
| 377 | if (did_parse_successfully) break; | |
| 378 | } else { | |
| 379 | log.debug("unable to resolve dependency {s}", .{dep_id.id.name}); | |
| 380 | } | |
| 381 | } | |
| 382 | } | |
| 383 | ||
| 384 | 92 | pub fn getOutputSection(self: *Zld, sect: macho.section_64) !?u8 { |
| 385 | 93 | const segname = sect.segName(); |
| 386 | 94 | const sectname = sect.sectName(); |
| ... | ... | @@ -1009,7 +717,7 @@ pub const Zld = struct { |
| 1009 | 717 | if (self.archives.items.len == 0) return; |
| 1010 | 718 | |
| 1011 | 719 | const gpa = self.gpa; |
| 1012 | const cpu_arch = self.options.target.cpu.arch; | |
| 720 | ||
| 1013 | 721 | var next_sym: usize = 0; |
| 1014 | 722 | loop: while (next_sym < resolver.unresolved.count()) { |
| 1015 | 723 | const global = self.globals.items[resolver.unresolved.keys()[next_sym]]; |
| ... | ... | @@ -1024,13 +732,7 @@ pub const Zld = struct { |
| 1024 | 732 | assert(offsets.items.len > 0); |
| 1025 | 733 | |
| 1026 | 734 | const object_id = @as(u16, @intCast(self.objects.items.len)); |
| 1027 | const object = archive.parseObject(gpa, cpu_arch, offsets.items[0]) catch |e| switch (e) { | |
| 1028 | error.MismatchedCpuArchitecture => { | |
| 1029 | log.err("CPU architecture mismatch found in {s}", .{archive.name}); | |
| 1030 | return e; | |
| 1031 | }, | |
| 1032 | else => return e, | |
| 1033 | }; | |
| 735 | const object = try archive.parseObject(gpa, offsets.items[0]); | |
| 1034 | 736 | try self.objects.append(gpa, object); |
| 1035 | 737 | try self.resolveSymbolsInObject(object_id, resolver); |
| 1036 | 738 | |
| ... | ... | @@ -3512,37 +3214,27 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr |
| 3512 | 3214 | try zld.strtab.buffer.append(gpa, 0); |
| 3513 | 3215 | |
| 3514 | 3216 | // Positional arguments to the linker such as object files and static archives. |
| 3515 | var positionals = std.ArrayList([]const u8).init(arena); | |
| 3217 | var positionals = std.ArrayList(Compilation.LinkObject).init(arena); | |
| 3516 | 3218 | try positionals.ensureUnusedCapacity(options.objects.len); |
| 3517 | ||
| 3518 | var must_link_archives = std.StringArrayHashMap(void).init(arena); | |
| 3519 | try must_link_archives.ensureUnusedCapacity(options.objects.len); | |
| 3520 | ||
| 3521 | for (options.objects) |obj| { | |
| 3522 | if (must_link_archives.contains(obj.path)) continue; | |
| 3523 | if (obj.must_link) { | |
| 3524 | _ = must_link_archives.getOrPutAssumeCapacity(obj.path); | |
| 3525 | } else { | |
| 3526 | _ = positionals.appendAssumeCapacity(obj.path); | |
| 3527 | } | |
| 3528 | } | |
| 3219 | positionals.appendSliceAssumeCapacity(options.objects); | |
| 3529 | 3220 | |
| 3530 | 3221 | for (comp.c_object_table.keys()) |key| { |
| 3531 | try positionals.append(key.status.success.object_path); | |
| 3222 | try positionals.append(.{ .path = key.status.success.object_path }); | |
| 3532 | 3223 | } |
| 3533 | 3224 | |
| 3534 | 3225 | if (module_obj_path) |p| { |
| 3535 | try positionals.append(p); | |
| 3226 | try positionals.append(.{ .path = p }); | |
| 3536 | 3227 | } |
| 3537 | 3228 | |
| 3538 | 3229 | if (comp.compiler_rt_lib) |lib| { |
| 3539 | try positionals.append(lib.full_object_path); | |
| 3230 | try positionals.append(.{ .path = lib.full_object_path }); | |
| 3540 | 3231 | } |
| 3541 | 3232 | |
| 3542 | 3233 | // libc++ dep |
| 3543 | 3234 | if (options.link_libcpp) { |
| 3544 | try positionals.append(comp.libcxxabi_static_lib.?.full_object_path); | |
| 3545 | try positionals.append(comp.libcxx_static_lib.?.full_object_path); | |
| 3235 | try positionals.ensureUnusedCapacity(2); | |
| 3236 | positionals.appendAssumeCapacity(.{ .path = comp.libcxxabi_static_lib.?.full_object_path }); | |
| 3237 | positionals.appendAssumeCapacity(.{ .path = comp.libcxx_static_lib.?.full_object_path }); | |
| 3546 | 3238 | } |
| 3547 | 3239 | |
| 3548 | 3240 | var libs = std.StringArrayHashMap(link.SystemLib).init(arena); |
| ... | ... | @@ -3621,6 +3313,9 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr |
| 3621 | 3313 | } |
| 3622 | 3314 | |
| 3623 | 3315 | for (options.objects) |obj| { |
| 3316 | if (obj.must_link) { | |
| 3317 | try argv.append("-force_load"); | |
| 3318 | } | |
| 3624 | 3319 | try argv.append(obj.path); |
| 3625 | 3320 | } |
| 3626 | 3321 | |
| ... | ... | @@ -3682,10 +3377,6 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr |
| 3682 | 3377 | try argv.append("dynamic_lookup"); |
| 3683 | 3378 | } |
| 3684 | 3379 | |
| 3685 | for (must_link_archives.keys()) |lib| { | |
| 3686 | try argv.append(try std.fmt.allocPrint(arena, "-force_load {s}", .{lib})); | |
| 3687 | } | |
| 3688 | ||
| 3689 | 3380 | Compilation.dump_argv(argv.items); |
| 3690 | 3381 | } |
| 3691 | 3382 | |
| ... | ... | @@ -3694,10 +3385,49 @@ pub fn linkWithZld(macho_file: *MachO, comp: *Compilation, prog_node: *std.Progr |
| 3694 | 3385 | parent: u16, |
| 3695 | 3386 | }, .Dynamic).init(arena); |
| 3696 | 3387 | |
| 3697 | try zld.parseInputFiles(positionals.items, options.sysroot, &dependent_libs); | |
| 3698 | try zld.parseAndForceLoadStaticArchives(must_link_archives.keys()); | |
| 3699 | try zld.parseLibs(libs.keys(), libs.values(), options.sysroot, &dependent_libs); | |
| 3700 | try zld.parseDependentLibs(options.sysroot, &dependent_libs); | |
| 3388 | for (positionals.items) |obj| { | |
| 3389 | const in_file = try std.fs.cwd().openFile(obj.path, .{}); | |
| 3390 | defer in_file.close(); | |
| 3391 | ||
| 3392 | MachO.parsePositional( | |
| 3393 | &zld, | |
| 3394 | gpa, | |
| 3395 | in_file, | |
| 3396 | obj.path, | |
| 3397 | obj.must_link, | |
| 3398 | &dependent_libs, | |
| 3399 | options, | |
| 3400 | ) catch |err| { | |
| 3401 | // TODO convert to error | |
| 3402 | log.err("{s}: parsing positional failed with err {s}", .{ obj.path, @errorName(err) }); | |
| 3403 | continue; | |
| 3404 | }; | |
| 3405 | } | |
| 3406 | ||
| 3407 | for (libs.keys(), libs.values()) |path, lib| { | |
| 3408 | const in_file = try std.fs.cwd().openFile(path, .{}); | |
| 3409 | defer in_file.close(); | |
| 3410 | ||
| 3411 | MachO.parseLibrary( | |
| 3412 | &zld, | |
| 3413 | gpa, | |
| 3414 | in_file, | |
| 3415 | path, | |
| 3416 | lib, | |
| 3417 | false, | |
| 3418 | &dependent_libs, | |
| 3419 | options, | |
| 3420 | ) catch |err| { | |
| 3421 | // TODO convert to error | |
| 3422 | log.err("{s}: parsing library failed with err {s}", .{ path, @errorName(err) }); | |
| 3423 | continue; | |
| 3424 | }; | |
| 3425 | } | |
| 3426 | ||
| 3427 | MachO.parseDependentLibs(&zld, gpa, &dependent_libs, options) catch |err| { | |
| 3428 | // TODO convert to error | |
| 3429 | log.err("parsing dependent libraries failed with err {s}", .{@errorName(err)}); | |
| 3430 | }; | |
| 3701 | 3431 | |
| 3702 | 3432 | var resolver = SymbolResolver{ |
| 3703 | 3433 | .arena = arena, |