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(
180180 if (opts.id) |id| {
181181 if (dylib.id.?.current_version < id.compatibility_version) {
182182 log.warn("found dylib is incompatible with the required minimum version", .{});
183 log.warn(" | dylib: {s}", .{id.name});
184 log.warn(" | required minimum version: {}", .{id.compatibility_version});
185 log.warn(" | dylib version: {}", .{dylib.id.?.current_version});
183 log.warn(" dylib: {s}", .{id.name});
184 log.warn(" required minimum version: {}", .{id.compatibility_version});
185 log.warn(" dylib version: {}", .{dylib.id.?.current_version});
186186
187187 // TODO maybe this should be an error and facilitate auto-cleanup?
188188 dylib.deinit(allocator);
......@@ -316,14 +316,7 @@ fn parseSymbols(self: *Dylib, allocator: *Allocator) !void {
316316 }
317317}
318318
319fn hasValue(stack: []const []const u8, needle: []const u8) bool {
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 {
319fn addObjCClassSymbol(self: *Dylib, allocator: *Allocator, sym_name: []const u8) !void {
327320 const expanded = &[_][]const u8{
328321 try std.fmt.allocPrint(allocator, "_OBJC_CLASS_$_{s}", .{sym_name}),
329322 try std.fmt.allocPrint(allocator, "_OBJC_METACLASS_$_{s}", .{sym_name}),
......@@ -335,91 +328,21 @@ fn addObjCClassSymbols(self: *Dylib, allocator: *Allocator, sym_name: []const u8
335328 }
336329}
337330
338fn hasArch(archs: []const []const u8, arch: []const u8) bool {
339 for (archs) |x| {
340 if (mem.eql(u8, x, arch)) return true;
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 });
331fn addSymbol(self: *Dylib, allocator: *Allocator, sym_name: []const u8) !void {
332 if (self.symbols.contains(sym_name)) return;
333 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {});
415334}
416335
417336const TargetMatcher = struct {
418337 allocator: *Allocator,
338 target: std.Target,
419339 target_strings: std.ArrayListUnmanaged([]const u8) = .{},
420340
421341 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 };
423346 try self.target_strings.append(allocator, try targetToAppleString(allocator, target));
424347
425348 if (target.abi == .simulator) {
......@@ -442,102 +365,174 @@ const TargetMatcher = struct {
442365 self.target_strings.deinit(self.allocator);
443366 }
444367
445 fn hasTarget(targets: []const []const u8, target: []const u8) bool {
446 for (targets) |x| {
447 if (mem.eql(u8, x, target)) return true;
368 fn targetToAppleString(allocator: *Allocator, target: std.Target) ![]const u8 {
369 const arch = switch (target.cpu.arch) {
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;
448389 }
449390 return false;
450391 }
451392
452 fn matches(self: TargetMatcher, targets: []const []const u8) bool {
393 fn matchesTarget(self: TargetMatcher, targets: []const []const u8) bool {
453394 for (self.target_strings.items) |t| {
454 if (hasTarget(targets, t)) return true;
395 if (hasValue(targets, t)) return true;
455396 }
456397 return false;
457398 }
399
400 fn matchesArch(self: TargetMatcher, archs: []const []const u8) bool {
401 return hasValue(archs, @tagName(self.target.cpu.arch));
402 }
458403};
459404
460fn parseFromStubV4(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void {
461 var matcher = try TargetMatcher.init(allocator, target);
462 defer matcher.deinit();
405pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void {
406 if (lib_stub.inner.len == 0) return error.EmptyStubFile;
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
464421 var umbrella_libs = std.StringHashMap(void).init(allocator);
465422 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
467429 for (lib_stub.inner) |elem, stub_index| {
468 const stub = elem.v4;
469 if (!matcher.matches(stub.targets)) continue;
430 const is_match = switch (elem) {
431 .v3 => |stub| matcher.matchesArch(stub.archs),
432 .v4 => |stub| matcher.matchesTarget(stub.targets),
433 };
434 if (!is_match) continue;
470435
471436 if (stub_index > 0) {
472437 // TODO I thought that we could switch on presence of `parent-umbrella` map;
473438 // however, turns out `libsystem_notify.dylib` is fully reexported by `libSystem.dylib`
474439 // 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(), .{});
476441 }
477442
478 if (stub.exports) |exports| {
479 for (exports) |exp| {
480 if (!matcher.matches(exp.targets)) continue;
443 switch (elem) {
444 .v3 => |stub| {
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| {
483 for (symbols) |sym_name| {
484 if (self.symbols.contains(sym_name)) continue;
485 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {});
455 if (exp.objc_classes) |objc_classes| {
456 for (objc_classes) |class_name| {
457 try self.addObjCClassSymbol(allocator, class_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 }
486472 }
487473 }
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| {
490 for (classes) |sym_name| {
491 try self.addObjCClassSymbols(allocator, sym_name);
486 if (exp.objc_classes) |classes| {
487 for (classes) |sym_name| {
488 try self.addObjCClassSymbol(allocator, sym_name);
489 }
490 }
492491 }
493492 }
494 }
495 }
496493
497 if (stub.reexports) |reexports| {
498 for (reexports) |reexp| {
499 if (!matcher.matches(reexp.targets)) continue;
494 if (stub.reexports) |reexports| {
495 for (reexports) |reexp| {
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| {
502 for (symbols) |sym_name| {
503 if (self.symbols.contains(sym_name)) continue;
504 try self.symbols.putNoClobber(allocator, try allocator.dupe(u8, sym_name), {});
504 if (reexp.objc_classes) |classes| {
505 for (classes) |sym_name| {
506 try self.addObjCClassSymbol(allocator, sym_name);
507 }
508 }
505509 }
506510 }
507511
508 if (reexp.objc_classes) |classes| {
512 if (stub.objc_classes) |classes| {
509513 for (classes) |sym_name| {
510 try self.addObjCClassSymbols(allocator, sym_name);
514 try self.addObjCClassSymbol(allocator, sym_name);
511515 }
512516 }
513 }
514 }
515
516 if (stub.objc_classes) |classes| {
517 for (classes) |sym_name| {
518 try self.addObjCClassSymbols(allocator, sym_name);
519 }
517 },
520518 }
521519 }
522520
523 log.debug("{s}", .{lib_stub.inner[0].installName()});
524
525 // TODO track which libs were already parsed in different steps
526 for (lib_stub.inner) |elem| {
521 // For V4, we add dependent libs in a separate pass since some stubs such as libSystem include
522 // re-exports directly in the stub file.
523 for (lib_stub.inner) |elem, stub_index| {
524 if (elem == .v3) break;
527525 const stub = elem.v4;
528 if (!matcher.matches(stub.targets)) continue;
529526
527 // TODO track which libs were already parsed in different steps
530528 if (stub.reexported_libraries) |reexports| {
531529 for (reexports) |reexp| {
532 if (!matcher.matches(reexp.targets)) continue;
530 if (!matcher.matchesTarget(reexp.targets)) continue;
533531
534532 for (reexp.libraries) |lib| {
535 if (umbrella_libs.contains(lib)) {
536 log.debug(" | {s} <= {s}", .{ lib, lib_stub.inner[0].installName() });
537 continue;
538 }
533 if (umbrella_libs.contains(lib)) continue;
539534
540 log.debug(" | {s}", .{lib});
535 log.debug(" (found re-export '{s}')", .{lib});
541536
542537 const dep_id = try Id.default(allocator, lib);
543538 try self.dependent_libs.append(allocator, dep_id);
......@@ -547,28 +542,6 @@ fn parseFromStubV4(self: *Dylib, allocator: *Allocator, target: std.Target, lib_
547542 }
548543}
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
572545pub fn parseDependentLibs(
573546 self: *Dylib,
574547 allocator: *Allocator,
......@@ -619,7 +592,7 @@ pub fn parseDependentLibs(
619592
620593 continue :outer;
621594 } else {
622 log.warn("unable to resolve dependency {s}", .{id.name});
595 log.debug("unable to resolve dependency {s}", .{id.name});
623596 }
624597 }
625598}
src/link/tapi.zig+2-3
......@@ -105,7 +105,7 @@ pub const LibStub = struct {
105105 .inner = undefined,
106106 };
107107
108 // TODO clean this up.
108 // TODO revisit this logic in the hope of simplifying it.
109109 lib_stub.inner = blk: {
110110 err: {
111111 log.debug("trying to parse as []TbdV4", .{});
......@@ -133,8 +133,7 @@ pub const LibStub = struct {
133133 break :blk out;
134134 }
135135
136 // TODO this is clunky. Perhaps an optional would be better here?
137 return error.TypeMismatch;
136 return error.NotLibStub;
138137 };
139138
140139 return lib_stub;