authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2024-01-12 20:54:13+01:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2024-01-24 12:34:39+01:00
logee7a027059d87c10533744920793bca0bdec9687
tree85433a8be101f593f9104871541f656896a86559
parentef9aea75d0b1a0727cbf52be9344cd4c04954f9a

macho: parse dependent dylibs


3 files changed, 219 insertions(+), 159 deletions(-)

src/Compilation.zig+1
...@@ -1542,6 +1542,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil...@@ -1542,6 +1542,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
1542 .darwin_sdk_layout = libc_dirs.darwin_sdk_layout,1542 .darwin_sdk_layout = libc_dirs.darwin_sdk_layout,
1543 .frameworks = options.frameworks,1543 .frameworks = options.frameworks,
1544 .lib_dirs = options.lib_dirs,1544 .lib_dirs = options.lib_dirs,
1545 .framework_dirs = options.framework_dirs,
1545 .rpath_list = options.rpath_list,1546 .rpath_list = options.rpath_list,
1546 .symbol_wrap_set = options.symbol_wrap_set,1547 .symbol_wrap_set = options.symbol_wrap_set,
1547 .allow_shlib_undefined = options.linker_allow_shlib_undefined,1548 .allow_shlib_undefined = options.linker_allow_shlib_undefined,
src/link.zig+1
...@@ -133,6 +133,7 @@ pub const File = struct {...@@ -133,6 +133,7 @@ pub const File = struct {
133133
134 // TODO: remove this. libraries are resolved by the frontend.134 // TODO: remove this. libraries are resolved by the frontend.
135 lib_dirs: []const []const u8,135 lib_dirs: []const []const u8,
136 framework_dirs: []const []const u8,
136 rpath_list: []const []const u8,137 rpath_list: []const []const u8,
137138
138 /// (Zig compiler development) Enable dumping of linker's state as JSON.139 /// (Zig compiler development) Enable dumping of linker's state as JSON.
src/link/MachO.zig+217-159
...@@ -98,6 +98,10 @@ headerpad_max_install_names: bool,...@@ -98,6 +98,10 @@ headerpad_max_install_names: bool,
98dead_strip_dylibs: bool,98dead_strip_dylibs: bool,
99/// Treatment of undefined symbols99/// Treatment of undefined symbols
100undefined_treatment: UndefinedTreatment,100undefined_treatment: UndefinedTreatment,
101/// Resolved list of library search directories
102lib_dirs: []const []const u8,
103/// Resolved list of framework search directories
104framework_dirs: []const []const u8,
101/// List of input frameworks105/// List of input frameworks
102frameworks: []const Framework,106frameworks: []const Framework,
103/// Install name for the dylib.107/// Install name for the dylib.
...@@ -112,6 +116,8 @@ platform: Platform,...@@ -112,6 +116,8 @@ platform: Platform,
112sdk_version: ?std.SemanticVersion,116sdk_version: ?std.SemanticVersion,
113/// Rpath table117/// Rpath table
114rpath_table: std.StringArrayHashMapUnmanaged(void) = .{},118rpath_table: std.StringArrayHashMapUnmanaged(void) = .{},
119/// When set to true, the linker will hoist all dylibs including system dependent dylibs.
120no_implicit_dylibs: bool = false,
115121
116/// Hot-code swapping state.122/// Hot-code swapping state.
117hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{},123hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{},
...@@ -190,6 +196,8 @@ pub fn createEmpty(...@@ -190,6 +196,8 @@ pub fn createEmpty(
190 .platform = Platform.fromTarget(target),196 .platform = Platform.fromTarget(target),
191 .sdk_version = if (options.darwin_sdk_layout) |layout| inferSdkVersion(comp, layout) else null,197 .sdk_version = if (options.darwin_sdk_layout) |layout| inferSdkVersion(comp, layout) else null,
192 .undefined_treatment = if (allow_shlib_undefined) .dynamic_lookup else .@"error",198 .undefined_treatment = if (allow_shlib_undefined) .dynamic_lookup else .@"error",
199 .lib_dirs = options.lib_dirs,
200 .framework_dirs = options.framework_dirs,
193 };201 };
194 if (use_llvm and comp.config.have_zcu) {202 if (use_llvm and comp.config.have_zcu) {
195 self.llvm_object = try LlvmObject.create(arena, comp);203 self.llvm_object = try LlvmObject.create(arena, comp);
...@@ -483,7 +491,18 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node...@@ -483,7 +491,18 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node
483 self.getFile(index).?.dylib.umbrella = index;491 self.getFile(index).?.dylib.umbrella = index;
484 }492 }
485493
486 // TODO: try self.parseDependentDylibs();494 if (self.dylibs.items.len > 0) {
495 self.parseDependentDylibs() catch |err| {
496 switch (err) {
497 error.MissingLibraryDependencies => {},
498 else => |e| try self.reportUnexpectedError(
499 "unexpected error while parsing dependent libraries: {s}",
500 .{@errorName(e)},
501 ),
502 }
503 return error.FlushFailure;
504 };
505 }
487506
488 for (self.dylibs.items) |index| {507 for (self.dylibs.items) |index| {
489 const dylib = self.getFile(index).?.dylib;508 const dylib = self.getFile(index).?.dylib;
...@@ -1056,152 +1075,198 @@ fn parseTbd(self: *MachO, lib: SystemLib, explicit: bool) ParseError!File.Index...@@ -1056,152 +1075,198 @@ fn parseTbd(self: *MachO, lib: SystemLib, explicit: bool) ParseError!File.Index
1056 return index;1075 return index;
1057}1076}
10581077
1059// /// According to ld64's manual, public (i.e., system) dylibs/frameworks are hoisted into the final1078/// According to ld64's manual, public (i.e., system) dylibs/frameworks are hoisted into the final
1060// /// image unless overriden by -no_implicit_dylibs.1079/// image unless overriden by -no_implicit_dylibs.
1061// fn isHoisted(self: *MachO, install_name: []const u8) bool {1080fn isHoisted(self: *MachO, install_name: []const u8) bool {
1062// _ = self;1081 if (self.no_implicit_dylibs) return true;
1063// // TODO: if (self.options.no_implicit_dylibs) return true;1082 if (std.fs.path.dirname(install_name)) |dirname| {
1064// if (std.fs.path.dirname(install_name)) |dirname| {1083 if (mem.startsWith(u8, dirname, "/usr/lib")) return true;
1065// if (mem.startsWith(u8, dirname, "/usr/lib")) return true;1084 if (eatPrefix(dirname, "/System/Library/Frameworks/")) |path| {
1066// if (eatPrefix(dirname, "/System/Library/Frameworks/")) |path| {1085 const basename = std.fs.path.basename(install_name);
1067// const basename = std.fs.path.basename(install_name);1086 if (mem.indexOfScalar(u8, path, '.')) |index| {
1068// if (mem.indexOfScalar(u8, path, '.')) |index| {1087 if (mem.eql(u8, basename, path[0..index])) return true;
1069// if (mem.eql(u8, basename, path[0..index])) return true;1088 }
1070// }1089 }
1071// }1090 }
1072// }1091 return false;
1073// return false;1092}
1074// }1093
10751094fn accessPath(path: []const u8) !bool {
1076// TODO:1095 std.fs.cwd().access(path, .{}) catch |err| switch (err) {
1077// fn parseDependentDylibs(1096 error.FileNotFound => return false,
1078// self: *MachO1097 else => |e| return e,
1079// ) !void {1098 };
1080// const tracy = trace(@src());1099 return true;
1081// defer tracy.end();1100}
10821101
1083// const gpa = self.base.comp.gpa;1102fn resolveLib(arena: Allocator, search_dirs: []const []const u8, name: []const u8) !?[]const u8 {
1084// const lib_dirs = self.base.comp.lib_dirs;1103 const path = try std.fmt.allocPrint(arena, "lib{s}", .{name});
1085// const framework_dirs = self.base.comp.framework_dirs;1104 for (search_dirs) |dir| {
10861105 for (&[_][]const u8{ ".tbd", ".dylib" }) |ext| {
1087// if (self.dylibs.items.len == 0) return;1106 const with_ext = try std.fmt.allocPrint(arena, "{s}{s}", .{ path, ext });
10881107 const full_path = try std.fs.path.join(arena, &[_][]const u8{ dir, with_ext });
1089// var arena = std.heap.ArenaAllocator.init(gpa);1108 if (try accessPath(full_path)) return full_path;
1090// defer arena.deinit();1109 }
10911110 }
1092// // TODO handle duplicate dylibs - it is not uncommon to have the same dylib loaded multiple times1111 return null;
1093// // in which case we should track that and return File.Index immediately instead re-parsing paths.1112}
10941113
1095// var index: usize = 0;1114fn resolveFramework(arena: Allocator, search_dirs: []const []const u8, name: []const u8) !?[]const u8 {
1096// while (index < self.dylibs.items.len) : (index += 1) {1115 const prefix = try std.fmt.allocPrint(arena, "{s}.framework", .{name});
1097// const dylib_index = self.dylibs.items[index];1116 const path = try std.fs.path.join(arena, &[_][]const u8{ prefix, name });
10981117 for (search_dirs) |dir| {
1099// var dependents = std.ArrayList(File.Index).init(gpa);1118 for (&[_][]const u8{ ".tbd", ".dylib" }) |ext| {
1100// defer dependents.deinit();1119 const with_ext = try std.fmt.allocPrint(arena, "{s}{s}", .{ path, ext });
1101// try dependents.ensureTotalCapacityPrecise(self.getFile(dylib_index).?.dylib.dependents.items.len);1120 const full_path = try std.fs.path.join(arena, &[_][]const u8{ dir, with_ext });
11021121 if (try accessPath(full_path)) return full_path;
1103// const is_weak = self.getFile(dylib_index).?.dylib.weak;1122 }
1104// for (self.getFile(dylib_index).?.dylib.dependents.items) |id| {1123 }
1105// // We will search for the dependent dylibs in the following order:1124 return null;
1106// // 1. Basename is in search lib directories or framework directories1125}
1107// // 2. If name is an absolute path, search as-is optionally prepending a syslibroot1126
1108// // if specified.1127fn parseDependentDylibs(self: *MachO) !void {
1109// // 3. If name is a relative path, substitute @rpath, @loader_path, @executable_path with1128 const tracy = trace(@src());
1110// // dependees list of rpaths, and search there.1129 defer tracy.end();
1111// // 4. Finally, just search the provided relative path directly in CWD.1130
1112// const full_path = full_path: {1131 const gpa = self.base.comp.gpa;
1113// fail: {1132 const lib_dirs = self.lib_dirs;
1114// const stem = std.fs.path.stem(id.name);1133 const framework_dirs = self.framework_dirs;
1115// const framework_name = try std.fmt.allocPrint(gpa, "{s}.framework" ++ std.fs.path.sep_str ++ "{s}", .{1134
1116// stem,1135 var arena = std.heap.ArenaAllocator.init(gpa);
1117// stem,1136 defer arena.deinit();
1118// });1137
1119// defer gpa.free(framework_name);1138 // TODO handle duplicate dylibs - it is not uncommon to have the same dylib loaded multiple times
11201139 // in which case we should track that and return File.Index immediately instead re-parsing paths.
1121// if (mem.endsWith(u8, id.name, framework_name)) {1140
1122// // Framework1141 var has_errors = false;
1123// const full_path = (try self.resolveFramework(arena, framework_dirs, stem)) orelse break :fail;1142 var index: usize = 0;
1124// break :full_path full_path;1143 while (index < self.dylibs.items.len) : (index += 1) {
1125// }1144 const dylib_index = self.dylibs.items[index];
11261145
1127// // Library1146 var dependents = std.ArrayList(File.Index).init(gpa);
1128// const lib_name = eatPrefix(stem, "lib") orelse stem;1147 defer dependents.deinit();
1129// const full_path = (try self.resolveLib(arena, lib_dirs, lib_name)) orelse break :fail;1148 try dependents.ensureTotalCapacityPrecise(self.getFile(dylib_index).?.dylib.dependents.items.len);
1130// break :full_path full_path;1149
1131// }1150 const is_weak = self.getFile(dylib_index).?.dylib.weak;
11321151 for (self.getFile(dylib_index).?.dylib.dependents.items) |id| {
1133// if (std.fs.path.isAbsolute(id.name)) {1152 // We will search for the dependent dylibs in the following order:
1134// const path = if (self.options.syslibroot) |root|1153 // 1. Basename is in search lib directories or framework directories
1135// try std.fs.path.join(arena, &.{ root, id.name })1154 // 2. If name is an absolute path, search as-is optionally prepending a syslibroot
1136// else1155 // if specified.
1137// id.name;1156 // 3. If name is a relative path, substitute @rpath, @loader_path, @executable_path with
1138// for (&[_][]const u8{ "", ".tbd", ".dylib" }) |ext| {1157 // dependees list of rpaths, and search there.
1139// const full_path = try std.fmt.allocPrint(arena, "{s}{s}", .{ path, ext });1158 // 4. Finally, just search the provided relative path directly in CWD.
1140// if (try accessLibPath(full_path)) break :full_path full_path;1159 const full_path = full_path: {
1141// }1160 fail: {
1142// }1161 const stem = std.fs.path.stem(id.name);
11431162 const framework_name = try std.fmt.allocPrint(gpa, "{s}.framework" ++ std.fs.path.sep_str ++ "{s}", .{
1144// if (eatPrefix(id.name, "@rpath/")) |path| {1163 stem,
1145// const dylib = self.getFile(dylib_index).?.dylib;1164 stem,
1146// for (self.getFile(dylib.umbrella).?.dylib.rpaths.keys()) |rpath| {1165 });
1147// const prefix = eatPrefix(rpath, "@loader_path/") orelse rpath;1166 defer gpa.free(framework_name);
1148// const rel_path = try std.fs.path.join(arena, &.{ prefix, path });1167
1149// var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;1168 if (mem.endsWith(u8, id.name, framework_name)) {
1150// const full_path = std.fs.realpath(rel_path, &buffer) catch continue;1169 // Framework
1151// break :full_path full_path;1170 const full_path = (try resolveFramework(arena.allocator(), framework_dirs, stem)) orelse break :fail;
1152// }1171 break :full_path full_path;
1153// } else if (eatPrefix(id.name, "@loader_path/")) |_| {1172 }
1154// return self.base.fatal("{s}: TODO handle install_name '{s}'", .{1173
1155// self.getFile(dylib_index).?.dylib.path, id.name,1174 // Library
1156// });1175 const lib_name = eatPrefix(stem, "lib") orelse stem;
1157// } else if (eatPrefix(id.name, "@executable_path/")) |_| {1176 const full_path = (try resolveLib(arena.allocator(), lib_dirs, lib_name)) orelse break :fail;
1158// return self.base.fatal("{s}: TODO handle install_name '{s}'", .{1177 break :full_path full_path;
1159// self.getFile(dylib_index).?.dylib.path, id.name,1178 }
1160// });1179
1161// }1180 if (std.fs.path.isAbsolute(id.name)) {
11621181 const path = if (self.base.comp.sysroot) |root|
1163// var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;1182 try std.fs.path.join(arena.allocator(), &.{ root, id.name })
1164// const full_path = std.fs.realpath(id.name, &buffer) catch {1183 else
1165// dependents.appendAssumeCapacity(0);1184 id.name;
1166// continue;1185 for (&[_][]const u8{ "", ".tbd", ".dylib" }) |ext| {
1167// };1186 const full_path = try std.fmt.allocPrint(arena.allocator(), "{s}{s}", .{ path, ext });
1168// break :full_path full_path;1187 if (try accessPath(full_path)) break :full_path full_path;
1169// };1188 }
1170// const link_obj = LinkObject{1189 }
1171// .path = full_path,1190
1172// .tag = .obj,1191 if (eatPrefix(id.name, "@rpath/")) |path| {
1173// .weak = is_weak,1192 const dylib = self.getFile(dylib_index).?.dylib;
1174// };1193 for (self.getFile(dylib.umbrella).?.dylib.rpaths.keys()) |rpath| {
1175// const file_index = file_index: {1194 const prefix = eatPrefix(rpath, "@loader_path/") orelse rpath;
1176// if (try self.parseDylib(arena, link_obj, false)) |file| break :file_index file;1195 const rel_path = try std.fs.path.join(arena.allocator(), &.{ prefix, path });
1177// if (try self.parseTbd(link_obj, false)) |file| break :file_index file;1196 var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
1178// break :file_index @as(File.Index, 0);1197 const full_path = std.fs.realpath(rel_path, &buffer) catch continue;
1179// };1198 break :full_path full_path;
1180// dependents.appendAssumeCapacity(file_index);1199 }
1181// }1200 } else if (eatPrefix(id.name, "@loader_path/")) |_| {
11821201 try self.reportParseError2(dylib_index, "TODO handle install_name '{s}'", .{id.name});
1183// const dylib = self.getFile(dylib_index).?.dylib;1202 return error.Unhandled;
1184// for (dylib.dependents.items, dependents.items) |id, file_index| {1203 } else if (eatPrefix(id.name, "@executable_path/")) |_| {
1185// if (self.getFile(file_index)) |file| {1204 try self.reportParseError2(dylib_index, "TODO handle install_name '{s}'", .{id.name});
1186// const dep_dylib = file.dylib;1205 return error.Unhandled;
1187// dep_dylib.hoisted = self.isHoisted(id.name);1206 }
1188// if (self.getFile(dep_dylib.umbrella) == null) {1207
1189// dep_dylib.umbrella = dylib.umbrella;1208 var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
1190// }1209 const full_path = std.fs.realpath(id.name, &buffer) catch {
1191// if (!dep_dylib.hoisted) {1210 dependents.appendAssumeCapacity(0);
1192// const umbrella = dep_dylib.getUmbrella(self);1211 continue;
1193// for (dep_dylib.exports.items(.name), dep_dylib.exports.items(.flags)) |off, flags| {1212 };
1194// try umbrella.addExport(gpa, dep_dylib.getString(off), flags);1213 break :full_path full_path;
1195// }1214 };
1196// try umbrella.rpaths.ensureUnusedCapacity(gpa, dep_dylib.rpaths.keys().len);1215 const lib = SystemLib{
1197// for (dep_dylib.rpaths.keys()) |rpath| {1216 .path = full_path,
1198// umbrella.rpaths.putAssumeCapacity(rpath, {});1217 .weak = is_weak,
1199// }1218 };
1200// }1219 const file_index = file_index: {
1201// } else self.base.fatal("{s}: unable to resolve dependency {s}", .{ dylib.getUmbrella(self).path, id.name });1220 if (try fat.isFatLibrary(lib.path)) {
1202// }1221 const fat_arch = try self.parseFatLibrary(lib.path);
1203// }1222 if (try Dylib.isDylib(lib.path, fat_arch)) {
1204// }1223 break :file_index try self.parseDylib(lib, false, fat_arch);
1224 } else break :file_index @as(File.Index, 0);
1225 } else if (try Dylib.isDylib(lib.path, null)) {
1226 break :file_index try self.parseDylib(lib, false, null);
1227 } else {
1228 const file_index = self.parseTbd(lib, false) catch |err| switch (err) {
1229 error.MalformedTbd => @as(File.Index, 0),
1230 else => |e| return e,
1231 };
1232 break :file_index file_index;
1233 }
1234 };
1235 dependents.appendAssumeCapacity(file_index);
1236 }
1237
1238 const dylib = self.getFile(dylib_index).?.dylib;
1239 for (dylib.dependents.items, dependents.items) |id, file_index| {
1240 if (self.getFile(file_index)) |file| {
1241 const dep_dylib = file.dylib;
1242 dep_dylib.hoisted = self.isHoisted(id.name);
1243 if (self.getFile(dep_dylib.umbrella) == null) {
1244 dep_dylib.umbrella = dylib.umbrella;
1245 }
1246 if (!dep_dylib.hoisted) {
1247 const umbrella = dep_dylib.getUmbrella(self);
1248 for (dep_dylib.exports.items(.name), dep_dylib.exports.items(.flags)) |off, flags| {
1249 try umbrella.addExport(gpa, dep_dylib.getString(off), flags);
1250 }
1251 try umbrella.rpaths.ensureUnusedCapacity(gpa, dep_dylib.rpaths.keys().len);
1252 for (dep_dylib.rpaths.keys()) |rpath| {
1253 umbrella.rpaths.putAssumeCapacity(rpath, {});
1254 }
1255 }
1256 } else {
1257 try self.reportDependencyError(
1258 dylib.getUmbrella(self).index,
1259 id.name,
1260 "unable to resolve dependency",
1261 .{},
1262 );
1263 has_errors = true;
1264 }
1265 }
1266 }
1267
1268 if (has_errors) return error.MissingLibraryDependencies;
1269}
12051270
1206pub fn addUndefinedGlobals(self: *MachO) !void {1271pub fn addUndefinedGlobals(self: *MachO) !void {
1207 const gpa = self.base.comp.gpa;1272 const gpa = self.base.comp.gpa;
...@@ -3459,24 +3524,17 @@ fn reportMissingLibraryError(...@@ -3459,24 +3524,17 @@ fn reportMissingLibraryError(
34593524
3460fn reportDependencyError(3525fn reportDependencyError(
3461 self: *MachO,3526 self: *MachO,
3462 parent: []const u8,3527 parent: File.Index,
3463 path: ?[]const u8,3528 path: ?[]const u8,
3464 comptime format: []const u8,3529 comptime format: []const u8,
3465 args: anytype,3530 args: anytype,
3466) error{OutOfMemory}!void {3531) error{OutOfMemory}!void {
3467 const comp = self.base.comp;3532 var err = try self.addErrorWithNotes(2);
3468 const gpa = comp.gpa;3533 try err.addMsg(self, format, args);
3469 try comp.link_errors.ensureUnusedCapacity(gpa, 1);
3470 var notes = try std.ArrayList(link.File.ErrorMsg).initCapacity(gpa, 2);
3471 defer notes.deinit();
3472 if (path) |p| {3534 if (path) |p| {
3473 notes.appendAssumeCapacity(.{ .msg = try std.fmt.allocPrint(gpa, "while parsing {s}", .{p}) });3535 try err.addNote(self, "while parsing {s}", .{p});
3474 }3536 }
3475 notes.appendAssumeCapacity(.{ .msg = try std.fmt.allocPrint(gpa, "a dependency of {s}", .{parent}) });3537 try err.addNote(self, "a dependency of {}", .{self.getFile(parent).?.fmtPath()});
3476 comp.link_errors.appendAssumeCapacity(.{
3477 .msg = try std.fmt.allocPrint(gpa, format, args),
3478 .notes = try notes.toOwnedSlice(),
3479 });
3480}3538}
34813539
3482fn reportUnexpectedError(self: *MachO, comptime format: []const u8, args: anytype) error{OutOfMemory}!void {3540fn reportUnexpectedError(self: *MachO, comptime format: []const u8, args: anytype) error{OutOfMemory}!void {