From 937464f398241358c7d3e534b179dfbdfdf2ffd9 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 21 Jun 2022 22:19:55 +0200 Subject: [PATCH] link-tests: dump metadata to string and grep results This approach is more inline with what LLVM/LLD does for testing of their output, and seems to be more generic and easier to extend than implementing a lot of repetitive and nontrivial comparison logic when working directly on structures. --- lib/std/build/CheckMachOStep.zig | 252 +++++++++++++------------------ test/link/dylib/build.zig | 49 +++--- test/link/pagezero/build.zig | 37 ++--- 3 files changed, 139 insertions(+), 199 deletions(-) diff --git a/lib/std/build/CheckMachOStep.zig b/lib/std/build/CheckMachOStep.zig index 1ac12a9749..b58cfa6e46 100644 --- a/lib/std/build/CheckMachOStep.zig +++ b/lib/std/build/CheckMachOStep.zig @@ -1,36 +1,23 @@ const std = @import("../std.zig"); +const assert = std.debug.assert; const build = std.build; -const Step = build.Step; -const Builder = build.Builder; const fs = std.fs; const macho = std.macho; const mem = std.mem; const CheckMachOStep = @This(); +const Allocator = mem.Allocator; +const Builder = build.Builder; +const Step = build.Step; + pub const base_id = .check_macho; step: Step, builder: *Builder, source: build.FileSource, max_bytes: usize = 20 * 1024 * 1024, -lc_checks: std.ArrayList(LCCheck), - -const LCCheck = struct { - // common to most LCs - cmd: macho.LC, - name: ?[]const u8 = null, - // LC.SEGMENT_64 specific - index: ?usize = null, - vaddr: ?u64 = null, - memsz: ?u64 = null, - offset: ?u64 = null, - filesz: ?u64 = null, - // LC.LOAD_DYLIB specific - timestamp: ?u64 = null, - current_version: ?u32 = null, - compat_version: ?u32 = null, -}; +checks: std.ArrayList(Check), pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep { const gpa = builder.allocator; @@ -39,25 +26,38 @@ pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep { .builder = builder, .step = Step.init(.check_file, "CheckMachO", gpa, make), .source = source.dupe(builder), - .lc_checks = std.ArrayList(LCCheck).init(gpa), + .checks = std.ArrayList(Check).init(gpa), }; self.source.addStepDependencies(&self.step); return self; } -pub fn checkLoadCommand(self: *CheckMachOStep, check: LCCheck) void { - self.lc_checks.append(.{ - .cmd = check.cmd, - .index = check.index, - .name = if (check.name) |name| self.builder.dupe(name) else null, - .vaddr = check.vaddr, - .memsz = check.memsz, - .offset = check.offset, - .filesz = check.filesz, - .timestamp = check.timestamp, - .current_version = check.current_version, - .compat_version = check.compat_version, - }) catch unreachable; +const Check = struct { + builder: *Builder, + phrases: std.ArrayList([]const u8), + + fn create(b: *Builder) Check { + return .{ + .builder = b, + .phrases = std.ArrayList([]const u8).init(b.allocator), + }; + } + + fn addPhrase(self: *Check, phrase: []const u8) void { + self.phrases.append(self.builder.dupe(phrase)) catch unreachable; + } +}; + +pub fn check(self: *CheckMachOStep, phrase: []const u8) void { + var new_check = Check.create(self.builder); + new_check.addPhrase(phrase); + self.checks.append(new_check) catch unreachable; +} + +pub fn checkNext(self: *CheckMachOStep, phrase: []const u8) void { + assert(self.checks.items.len > 0); + const last = &self.checks.items[self.checks.items.len - 1]; + last.addPhrase(phrase); } fn make(step: *Step) !void { @@ -76,135 +76,95 @@ fn make(step: *Step) !void { return error.InvalidMagicNumber; } - var load_commands = std.ArrayList(macho.LoadCommand).init(gpa); - try load_commands.ensureTotalCapacity(hdr.ncmds); + var metadata = std.ArrayList(u8).init(gpa); + const writer = metadata.writer(); var i: u16 = 0; while (i < hdr.ncmds) : (i += 1) { var cmd = try macho.LoadCommand.read(gpa, reader); - load_commands.appendAssumeCapacity(cmd); + try dumpLoadCommand(cmd, i, writer); + try writer.writeByte('\n'); } - outer: for (self.lc_checks.items) |ch| { - if (ch.index) |index| { - const lc = load_commands.items[index]; - try cmpLoadCommand(ch, lc); - } else { - for (load_commands.items) |lc| { - if (lc.cmd() == ch.cmd) { - try cmpLoadCommand(ch, lc); - continue :outer; + for (self.checks.items) |chk| { + const first_phrase = chk.phrases.items[0]; + + if (mem.indexOf(u8, metadata.items, first_phrase)) |index| { + // TODO backtrack to track current scope + var it = std.mem.tokenize(u8, metadata.items[index..], "\r\n"); + + outer: for (chk.phrases.items[1..]) |next_phrase| { + while (it.next()) |line| { + if (mem.eql(u8, line, next_phrase)) { + std.debug.print("{s} == {s}\n", .{ line, next_phrase }); + continue :outer; + } + std.debug.print("{s} != {s}\n", .{ line, next_phrase }); + } else { + return error.TestFailed; } - } else { - return err("LC not found", ch.cmd, ""); } + } else { + return error.TestFailed; } } } -fn cmpLoadCommand(exp: LCCheck, given: macho.LoadCommand) error{TestFailed}!void { - if (exp.cmd != given.cmd()) { - return err("LC mismatch", exp.cmd, given.cmd()); - } - switch (exp.cmd) { +fn dumpLoadCommand(lc: macho.LoadCommand, index: u16, writer: anytype) !void { + // print header first + try writer.print( + \\LC {d} + \\cmd {s} + \\cmdsize {d} + , .{ index, @tagName(lc.cmd()), lc.cmdsize() }); + + switch (lc.cmd()) { .SEGMENT_64 => { - const lc = given.segment.inner; - if (exp.name) |name| { - if (!mem.eql(u8, name, lc.segName())) { - return err("segment name mismatch", name, lc.segName()); - } - } - if (exp.vaddr) |vaddr| { - if (vaddr != lc.vmaddr) { - return err("segment VM address mismatch", vaddr, lc.vmaddr); - } - } - if (exp.memsz) |memsz| { - if (memsz != lc.vmsize) { - return err("segment VM size mismatch", memsz, lc.vmsize); - } - } - if (exp.offset) |offset| { - if (offset != lc.fileoff) { - return err("segment file offset mismatch", offset, lc.fileoff); - } - } - if (exp.filesz) |filesz| { - if (filesz != lc.filesize) { - return err("segment file size mismatch", filesz, lc.filesize); - } - } + // TODO dump section headers + const seg = lc.segment.inner; + try writer.writeByte('\n'); + try writer.print( + \\segname {s} + \\vmaddr {x} + \\vmsize {x} + \\fileoff {x} + \\filesz {x} + , .{ + seg.segName(), + seg.vmaddr, + seg.vmsize, + seg.fileoff, + seg.filesize, + }); }, - .ID_DYLIB, .LOAD_DYLIB => { - const lc = given.dylib; - if (exp.name) |name| { - if (!mem.eql(u8, name, mem.sliceTo(lc.data, 0))) { - return err("dylib path mismatch", name, mem.sliceTo(lc.data, 0)); - } - } - if (exp.timestamp) |ts| { - if (ts != lc.inner.dylib.timestamp) { - return err("timestamp mismatch", ts, lc.inner.dylib.timestamp); - } - } - if (exp.current_version) |cv| { - if (cv != lc.inner.dylib.current_version) { - return err("current version mismatch", cv, lc.inner.dylib.current_version); - } - } - if (exp.compat_version) |cv| { - if (cv != lc.inner.dylib.compatibility_version) { - return err("compatibility version mismatch", cv, lc.inner.dylib.compatibility_version); - } - } + + .ID_DYLIB, + .LOAD_DYLIB, + => { + const dylib = lc.dylib.inner.dylib; + try writer.writeByte('\n'); + try writer.print( + \\path {s} + \\timestamp {d} + \\current version {x} + \\compatibility version {x} + , .{ + mem.sliceTo(lc.dylib.data, 0), + dylib.timestamp, + dylib.current_version, + dylib.compatibility_version, + }); }, + .RPATH => { - const lc = given.rpath; - if (exp.name) |name| { - if (!mem.eql(u8, name, mem.sliceTo(lc.data, 0))) { - return err("rpath path mismatch", name, mem.sliceTo(lc.data, 0)); - } - } + try writer.writeByte('\n'); + try writer.print( + \\path {s} + , .{ + mem.sliceTo(lc.rpath.data, 0), + }); }, - else => @panic("TODO compare more load commands"), - } -} - -fn err(msg: []const u8, exp: anytype, giv: anytype) error{TestFailed} { - const fmt_specifier = if (comptime isString(@TypeOf(exp))) "{s}" else switch (@typeInfo(@TypeOf(exp))) { - .Int => "{x}", - .Float => "{d}", - else => "{any}", - }; - std.debug.print( - \\===================================== - \\{s} - \\ - \\======== Expected to find: ========== - \\ - ++ fmt_specifier ++ - \\ - \\======== But instead found: ========= - \\ - ++ fmt_specifier ++ - \\ - \\ - , .{ msg, exp, giv }); - return error.TestFailed; -} - -fn isString(comptime T: type) bool { - switch (@typeInfo(T)) { - .Array => return std.meta.Elem(T) == u8, - .Pointer => |pinfo| { - switch (pinfo.size) { - .Slice, .Many => return std.meta.Elem(T) == u8, - else => switch (@typeInfo(pinfo.child)) { - .Array => return isString(pinfo.child), - else => return false, - }, - } - }, - else => return false, + + else => {}, } } diff --git a/test/link/dylib/build.zig b/test/link/dylib/build.zig index 9878f9a66c..4f41e204ec 100644 --- a/test/link/dylib/build.zig +++ b/test/link/dylib/build.zig @@ -13,17 +13,14 @@ pub fn build(b: *Builder) void { dylib.linkLibC(); dylib.install(); - { - const check_macho = dylib.checkMachO(); - check_macho.checkLoadCommand(.{ - .cmd = std.macho.LC.ID_DYLIB, - .name = "@rpath/liba.dylib", - .timestamp = 2, - .current_version = 0x10000, - .compat_version = 0x10000, - }); - test_step.dependOn(&check_macho.step); - } + const check_dylib = dylib.checkMachO(); + check_dylib.check("cmd ID_DYLIB"); + check_dylib.checkNext("path @rpath/liba.dylib"); + check_dylib.checkNext("timestamp 2"); + check_dylib.checkNext("current version 10000"); + check_dylib.checkNext("compatibility version 10000"); + + test_step.dependOn(&check_dylib.step); const exe = b.addExecutable("main", null); exe.setBuildMode(mode); @@ -33,25 +30,17 @@ pub fn build(b: *Builder) void { exe.addLibraryPath(b.pathFromRoot("zig-out/lib/")); exe.addRPath(b.pathFromRoot("zig-out/lib")); - { - const check_macho = exe.checkMachO(); - check_macho.checkLoadCommand(.{ - .cmd = std.macho.LC.LOAD_DYLIB, - .name = "@rpath/liba.dylib", - .timestamp = 2, - .current_version = 0x10000, - .compat_version = 0x10000, - }); - test_step.dependOn(&check_macho.step); - } - { - const check_macho = exe.checkMachO(); - check_macho.checkLoadCommand(.{ - .cmd = std.macho.LC.RPATH, - .name = b.pathFromRoot("zig-out/lib"), - }); - test_step.dependOn(&check_macho.step); - } + const check_exe = exe.checkMachO(); + check_exe.check("cmd LOAD_DYLIB"); + check_exe.checkNext("path @rpath/liba.dylib"); + check_exe.checkNext("timestamp 2"); + check_exe.checkNext("current version 10000"); + check_exe.checkNext("compatibility version 10000"); + + check_exe.check("cmd RPATH"); + check_exe.checkNext(std.fmt.allocPrint(b.allocator, "path {s}", .{b.pathFromRoot("zig-out/lib")}) catch unreachable); + + test_step.dependOn(&check_exe.step); const run = exe.run(); run.cwd = b.pathFromRoot("."); diff --git a/test/link/pagezero/build.zig b/test/link/pagezero/build.zig index 71068b4480..05bc4052fe 100644 --- a/test/link/pagezero/build.zig +++ b/test/link/pagezero/build.zig @@ -14,22 +14,16 @@ pub fn build(b: *Builder) void { exe.linkLibC(); exe.pagezero_size = 0x4000; - const check_macho = exe.checkMachO(); - check_macho.checkLoadCommand(.{ - .cmd = std.macho.LC.SEGMENT_64, - .index = 0, - .name = "__PAGEZERO", - .vaddr = 0, - .memsz = 0x4000, - }); - check_macho.checkLoadCommand(.{ - .cmd = std.macho.LC.SEGMENT_64, - .index = 1, - .name = "__TEXT", - .vaddr = 0x4000, - }); + const check = exe.checkMachO(); + check.check("LC 0"); + check.checkNext("segname __PAGEZERO"); + check.checkNext("vmaddr 0"); + check.checkNext("vmsize 4000"); - test_step.dependOn(&check_macho.step); + check.check("segname __TEXT"); + check.checkNext("vmaddr 4000"); + + test_step.dependOn(&check.step); } { @@ -39,14 +33,11 @@ pub fn build(b: *Builder) void { exe.linkLibC(); exe.pagezero_size = 0; - const check_macho = exe.checkMachO(); - check_macho.checkLoadCommand(.{ - .cmd = std.macho.LC.SEGMENT_64, - .index = 0, - .name = "__TEXT", - .vaddr = 0, - }); + const check = exe.checkMachO(); + check.check("LC 0"); + check.checkNext("segname __TEXT"); + check.checkNext("vmaddr 0"); - test_step.dependOn(&check_macho.step); + test_step.dependOn(&check.step); } }