authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-08-11 10:52:21+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-08-11 19:38:00+02:00
log16bb5c05f15e1ec4cc1616c5c33e56f67ea0763e
tree6592e0ea2bcc004ccd8667be271c4cdfa6f30910
parentd95e8bc5f8c48752aed73d077e2f9c87293a6617

macho: refactor stub parsing in Dylib


2 files changed, 137 insertions(+), 165 deletions(-)

src/link/MachO/Dylib.zig+135-162
...@@ -180,9 +180,9 @@ pub fn createAndParseFromPath(...@@ -180,9 +180,9 @@ pub fn createAndParseFromPath(
180 if (opts.id) |id| {180 if (opts.id) |id| {
181 if (dylib.id.?.current_version < id.compatibility_version) {181 if (dylib.id.?.current_version < id.compatibility_version) {
182 log.warn("found dylib is incompatible with the required minimum version", .{});182 log.warn("found dylib is incompatible with the required minimum version", .{});
183 log.warn(" | dylib: {s}", .{id.name});183 log.warn(" dylib: {s}", .{id.name});
184 log.warn(" | required minimum version: {}", .{id.compatibility_version});184 log.warn(" required minimum version: {}", .{id.compatibility_version});
185 log.warn(" | dylib version: {}", .{dylib.id.?.current_version});185 log.warn(" dylib version: {}", .{dylib.id.?.current_version});
186186
187 // TODO maybe this should be an error and facilitate auto-cleanup?187 // TODO maybe this should be an error and facilitate auto-cleanup?
188 dylib.deinit(allocator);188 dylib.deinit(allocator);
...@@ -316,14 +316,7 @@ fn parseSymbols(self: *Dylib, allocator: *Allocator) !void {...@@ -316,14 +316,7 @@ fn parseSymbols(self: *Dylib, allocator: *Allocator) !void {
316 }316 }
317}317}
318318
319fn hasValue(stack: []const []const u8, needle: []const u8) bool {319fn addObjCClassSymbol(self: *Dylib, allocator: *Allocator, sym_name: []const u8) !void {
320 for (stack) |v| {
321 if (mem.eql(u8, v, needle)) return true;
322 }
323 return false;
324}
325
326fn addObjCClassSymbols(self: *Dylib, allocator: *Allocator, sym_name: []const u8) !void {
327 const expanded = &[_][]const u8{320 const expanded = &[_][]const u8{
328 try std.fmt.allocPrint(allocator, "_OBJC_CLASS_$_{s}", .{sym_name}),321 try std.fmt.allocPrint(allocator, "_OBJC_CLASS_$_{s}", .{sym_name}),
329 try std.fmt.allocPrint(allocator, "_OBJC_METACLASS_$_{s}", .{sym_name}),322 try std.fmt.allocPrint(allocator, "_OBJC_METACLASS_$_{s}", .{sym_name}),
...@@ -335,91 +328,21 @@ fn addObjCClassSymbols(self: *Dylib, allocator: *Allocator, sym_name: []const u8...@@ -335,91 +328,21 @@ fn addObjCClassSymbols(self: *Dylib, allocator: *Allocator, sym_name: []const u8
335 }328 }
336}329}
337330
338fn hasArch(archs: []const []const u8, arch: []const u8) bool {331fn addSymbol(self: *Dylib, allocator: *Allocator, sym_name: []const u8) !void {
339 for (archs) |x| {332 if (self.symbols.contains(sym_name)) return;
340 if (mem.eql(u8, x, arch)) return true;333 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {});
341 }
342 return false;
343}
344
345fn parseFromStubV3(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void {
346 var umbrella_libs = std.StringHashMap(void).init(allocator);
347 defer umbrella_libs.deinit();
348
349 const arch_string = @tagName(target.cpu.arch);
350
351 log.debug("{s}", .{lib_stub.inner[0].installName()});
352
353 for (lib_stub.inner) |elem, stub_index| {
354 const stub = elem.v3;
355 if (!hasArch(stub.archs, arch_string)) continue;
356
357 if (stub_index > 0) {
358 // TODO I thought that we could switch on presence of `parent-umbrella` map;
359 // however, turns out `libsystem_notify.dylib` is fully reexported by `libSystem.dylib`
360 // BUT does not feature a `parent-umbrella` map as the only sublib. Apple's bug perhaps?
361 try umbrella_libs.put(stub.install_name, .{});
362 }
363
364 if (stub.exports) |exports| {
365 for (exports) |exp| {
366 if (!hasArch(exp.archs, arch_string)) continue;
367
368 if (exp.symbols) |symbols| {
369 for (symbols) |sym_name| {
370 if (self.symbols.contains(sym_name)) continue;
371 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {});
372 }
373 }
374
375 if (exp.objc_classes) |objc_classes| {
376 for (objc_classes) |class_name| {
377 try self.addObjCClassSymbols(allocator, class_name);
378 }
379 }
380
381 if (exp.re_exports) |re_exports| {
382 for (re_exports) |lib| {
383 if (umbrella_libs.contains(lib)) {
384 log.debug(" | {s} <= {s}", .{ lib, lib_stub.inner[0].installName() });
385 continue;
386 }
387
388 log.debug(" | {s}", .{lib});
389
390 const dep_id = try Id.default(allocator, lib);
391 try self.dependent_libs.append(allocator, dep_id);
392 }
393 }
394 }
395 }
396 }
397}
398
399fn targetToAppleString(allocator: *Allocator, target: std.Target) ![]const u8 {
400 const arch = switch (target.cpu.arch) {
401 .aarch64 => "arm64",
402 .x86_64 => "x86_64",
403 else => unreachable,
404 };
405 const os = @tagName(target.os.tag);
406 const abi: ?[]const u8 = switch (target.abi) {
407 .gnu => null,
408 .simulator => "simulator",
409 else => unreachable,
410 };
411 if (abi) |x| {
412 return std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ arch, os, x });
413 }
414 return std.fmt.allocPrint(allocator, "{s}-{s}", .{ arch, os });
415}334}
416335
417const TargetMatcher = struct {336const TargetMatcher = struct {
418 allocator: *Allocator,337 allocator: *Allocator,
338 target: std.Target,
419 target_strings: std.ArrayListUnmanaged([]const u8) = .{},339 target_strings: std.ArrayListUnmanaged([]const u8) = .{},
420340
421 fn init(allocator: *Allocator, target: std.Target) !TargetMatcher {341 fn init(allocator: *Allocator, target: std.Target) !TargetMatcher {
422 var self = TargetMatcher{ .allocator = allocator };342 var self = TargetMatcher{
343 .allocator = allocator,
344 .target = target,
345 };
423 try self.target_strings.append(allocator, try targetToAppleString(allocator, target));346 try self.target_strings.append(allocator, try targetToAppleString(allocator, target));
424347
425 if (target.abi == .simulator) {348 if (target.abi == .simulator) {
...@@ -442,102 +365,174 @@ const TargetMatcher = struct {...@@ -442,102 +365,174 @@ const TargetMatcher = struct {
442 self.target_strings.deinit(self.allocator);365 self.target_strings.deinit(self.allocator);
443 }366 }
444367
445 fn hasTarget(targets: []const []const u8, target: []const u8) bool {368 fn targetToAppleString(allocator: *Allocator, target: std.Target) ![]const u8 {
446 for (targets) |x| {369 const arch = switch (target.cpu.arch) {
447 if (mem.eql(u8, x, target)) return true;370 .aarch64 => "arm64",
371 .x86_64 => "x86_64",
372 else => unreachable,
373 };
374 const os = @tagName(target.os.tag);
375 const abi: ?[]const u8 = switch (target.abi) {
376 .gnu => null,
377 .simulator => "simulator",
378 else => unreachable,
379 };
380 if (abi) |x| {
381 return std.fmt.allocPrint(allocator, "{s}-{s}-{s}", .{ arch, os, x });
382 }
383 return std.fmt.allocPrint(allocator, "{s}-{s}", .{ arch, os });
384 }
385
386 fn hasValue(stack: []const []const u8, needle: []const u8) bool {
387 for (stack) |v| {
388 if (mem.eql(u8, v, needle)) return true;
448 }389 }
449 return false;390 return false;
450 }391 }
451392
452 fn matches(self: TargetMatcher, targets: []const []const u8) bool {393 fn matchesTarget(self: TargetMatcher, targets: []const []const u8) bool {
453 for (self.target_strings.items) |t| {394 for (self.target_strings.items) |t| {
454 if (hasTarget(targets, t)) return true;395 if (hasValue(targets, t)) return true;
455 }396 }
456 return false;397 return false;
457 }398 }
399
400 fn matchesArch(self: TargetMatcher, archs: []const []const u8) bool {
401 return hasValue(archs, @tagName(self.target.cpu.arch));
402 }
458};403};
459404
460fn parseFromStubV4(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void {405pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void {
461 var matcher = try TargetMatcher.init(allocator, target);406 if (lib_stub.inner.len == 0) return error.EmptyStubFile;
462 defer matcher.deinit();407
408 log.debug("parsing shared library from stub '{s}'", .{self.name});
409
410 const umbrella_lib = lib_stub.inner[0];
411
412 var id = try Id.default(allocator, umbrella_lib.installName());
413 if (umbrella_lib.currentVersion()) |version| {
414 try id.parseCurrentVersion(version);
415 }
416 if (umbrella_lib.compatibilityVersion()) |version| {
417 try id.parseCompatibilityVersion(version);
418 }
419 self.id = id;
463420
464 var umbrella_libs = std.StringHashMap(void).init(allocator);421 var umbrella_libs = std.StringHashMap(void).init(allocator);
465 defer umbrella_libs.deinit();422 defer umbrella_libs.deinit();
466423
424 log.debug("found umbrella lib '{s}'", .{umbrella_lib.installName()});
425
426 var matcher = try TargetMatcher.init(allocator, target);
427 defer matcher.deinit();
428
467 for (lib_stub.inner) |elem, stub_index| {429 for (lib_stub.inner) |elem, stub_index| {
468 const stub = elem.v4;430 const is_match = switch (elem) {
469 if (!matcher.matches(stub.targets)) continue;431 .v3 => |stub| matcher.matchesArch(stub.archs),
432 .v4 => |stub| matcher.matchesTarget(stub.targets),
433 };
434 if (!is_match) continue;
470435
471 if (stub_index > 0) {436 if (stub_index > 0) {
472 // TODO I thought that we could switch on presence of `parent-umbrella` map;437 // TODO I thought that we could switch on presence of `parent-umbrella` map;
473 // however, turns out `libsystem_notify.dylib` is fully reexported by `libSystem.dylib`438 // however, turns out `libsystem_notify.dylib` is fully reexported by `libSystem.dylib`
474 // BUT does not feature a `parent-umbrella` map as the only sublib. Apple's bug perhaps?439 // BUT does not feature a `parent-umbrella` map as the only sublib. Apple's bug perhaps?
475 try umbrella_libs.put(stub.install_name, .{});440 try umbrella_libs.put(elem.installName(), .{});
476 }441 }
477442
478 if (stub.exports) |exports| {443 switch (elem) {
479 for (exports) |exp| {444 .v3 => |stub| {
480 if (!matcher.matches(exp.targets)) continue;445 if (stub.exports) |exports| {
446 for (exports) |exp| {
447 if (!matcher.matchesArch(exp.archs)) continue;
448
449 if (exp.symbols) |symbols| {
450 for (symbols) |sym_name| {
451 try self.addSymbol(allocator, sym_name);
452 }
453 }
481454
482 if (exp.symbols) |symbols| {455 if (exp.objc_classes) |objc_classes| {
483 for (symbols) |sym_name| {456 for (objc_classes) |class_name| {
484 if (self.symbols.contains(sym_name)) continue;457 try self.addObjCClassSymbol(allocator, class_name);
485 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {});458 }
459 }
460
461 // TODO track which libs were already parsed in different steps
462 if (exp.re_exports) |re_exports| {
463 for (re_exports) |lib| {
464 if (umbrella_libs.contains(lib)) continue;
465
466 log.debug(" (found re-export '{s}')", .{lib});
467
468 const dep_id = try Id.default(allocator, lib);
469 try self.dependent_libs.append(allocator, dep_id);
470 }
471 }
486 }472 }
487 }473 }
474 },
475 .v4 => |stub| {
476 if (stub.exports) |exports| {
477 for (exports) |exp| {
478 if (!matcher.matchesTarget(exp.targets)) continue;
479
480 if (exp.symbols) |symbols| {
481 for (symbols) |sym_name| {
482 try self.addSymbol(allocator, sym_name);
483 }
484 }
488485
489 if (exp.objc_classes) |classes| {486 if (exp.objc_classes) |classes| {
490 for (classes) |sym_name| {487 for (classes) |sym_name| {
491 try self.addObjCClassSymbols(allocator, sym_name);488 try self.addObjCClassSymbol(allocator, sym_name);
489 }
490 }
492 }491 }
493 }492 }
494 }
495 }
496493
497 if (stub.reexports) |reexports| {494 if (stub.reexports) |reexports| {
498 for (reexports) |reexp| {495 for (reexports) |reexp| {
499 if (!matcher.matches(reexp.targets)) continue;496 if (!matcher.matchesTarget(reexp.targets)) continue;
497
498 if (reexp.symbols) |symbols| {
499 for (symbols) |sym_name| {
500 try self.addSymbol(allocator, sym_name);
501 }
502 }
500503
501 if (reexp.symbols) |symbols| {504 if (reexp.objc_classes) |classes| {
502 for (symbols) |sym_name| {505 for (classes) |sym_name| {
503 if (self.symbols.contains(sym_name)) continue;506 try self.addObjCClassSymbol(allocator, sym_name);
504 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {});507 }
508 }
505 }509 }
506 }510 }
507511
508 if (reexp.objc_classes) |classes| {512 if (stub.objc_classes) |classes| {
509 for (classes) |sym_name| {513 for (classes) |sym_name| {
510 try self.addObjCClassSymbols(allocator, sym_name);514 try self.addObjCClassSymbol(allocator, sym_name);
511 }515 }
512 }516 }
513 }517 },
514 }
515
516 if (stub.objc_classes) |classes| {
517 for (classes) |sym_name| {
518 try self.addObjCClassSymbols(allocator, sym_name);
519 }
520 }518 }
521 }519 }
522520
523 log.debug("{s}", .{lib_stub.inner[0].installName()});521 // For V4, we add dependent libs in a separate pass since some stubs such as libSystem include
524522 // re-exports directly in the stub file.
525 // TODO track which libs were already parsed in different steps523 for (lib_stub.inner) |elem, stub_index| {
526 for (lib_stub.inner) |elem| {524 if (elem == .v3) break;
527 const stub = elem.v4;525 const stub = elem.v4;
528 if (!matcher.matches(stub.targets)) continue;
529526
527 // TODO track which libs were already parsed in different steps
530 if (stub.reexported_libraries) |reexports| {528 if (stub.reexported_libraries) |reexports| {
531 for (reexports) |reexp| {529 for (reexports) |reexp| {
532 if (!matcher.matches(reexp.targets)) continue;530 if (!matcher.matchesTarget(reexp.targets)) continue;
533531
534 for (reexp.libraries) |lib| {532 for (reexp.libraries) |lib| {
535 if (umbrella_libs.contains(lib)) {533 if (umbrella_libs.contains(lib)) continue;
536 log.debug(" | {s} <= {s}", .{ lib, lib_stub.inner[0].installName() });
537 continue;
538 }
539534
540 log.debug(" | {s}", .{lib});535 log.debug(" (found re-export '{s}')", .{lib});
541536
542 const dep_id = try Id.default(allocator, lib);537 const dep_id = try Id.default(allocator, lib);
543 try self.dependent_libs.append(allocator, dep_id);538 try self.dependent_libs.append(allocator, dep_id);
...@@ -547,28 +542,6 @@ fn parseFromStubV4(self: *Dylib, allocator: *Allocator, target: std.Target, lib_...@@ -547,28 +542,6 @@ fn parseFromStubV4(self: *Dylib, allocator: *Allocator, target: std.Target, lib_
547 }542 }
548}543}
549544
550pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void {
551 if (lib_stub.inner.len == 0) return error.EmptyStubFile;
552
553 log.debug("parsing shared library from stub '{s}'", .{self.name});
554
555 const umbrella_lib = lib_stub.inner[0];
556
557 var id = try Id.default(allocator, umbrella_lib.installName());
558 if (umbrella_lib.currentVersion()) |version| {
559 try id.parseCurrentVersion(version);
560 }
561 if (umbrella_lib.compatibilityVersion()) |version| {
562 try id.parseCompatibilityVersion(version);
563 }
564 self.id = id;
565
566 switch (umbrella_lib) {
567 .v3 => try self.parseFromStubV3(allocator, target, lib_stub),
568 .v4 => try self.parseFromStubV4(allocator, target, lib_stub),
569 }
570}
571
572pub fn parseDependentLibs(545pub fn parseDependentLibs(
573 self: *Dylib,546 self: *Dylib,
574 allocator: *Allocator,547 allocator: *Allocator,
...@@ -619,7 +592,7 @@ pub fn parseDependentLibs(...@@ -619,7 +592,7 @@ pub fn parseDependentLibs(
619592
620 continue :outer;593 continue :outer;
621 } else {594 } else {
622 log.warn("unable to resolve dependency {s}", .{id.name});595 log.debug("unable to resolve dependency {s}", .{id.name});
623 }596 }
624 }597 }
625}598}
src/link/tapi.zig+2-3
...@@ -105,7 +105,7 @@ pub const LibStub = struct {...@@ -105,7 +105,7 @@ pub const LibStub = struct {
105 .inner = undefined,105 .inner = undefined,
106 };106 };
107107
108 // TODO clean this up.108 // TODO revisit this logic in the hope of simplifying it.
109 lib_stub.inner = blk: {109 lib_stub.inner = blk: {
110 err: {110 err: {
111 log.debug("trying to parse as []TbdV4", .{});111 log.debug("trying to parse as []TbdV4", .{});
...@@ -133,8 +133,7 @@ pub const LibStub = struct {...@@ -133,8 +133,7 @@ pub const LibStub = struct {
133 break :blk out;133 break :blk out;
134 }134 }
135135
136 // TODO this is clunky. Perhaps an optional would be better here?136 return error.NotLibStub;
137 return error.TypeMismatch;
138 };137 };
139138
140 return lib_stub;139 return lib_stub;