authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-06-21 22:19:55+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-06-21 22:19:57+02:00
log937464f398241358c7d3e534b179dfbdfdf2ffd9
tree5e25128b1f23446c8e418ec87de6297405775f00
parent5fbdfb3f3477bc8ac70b828671a7f980e8a8ad10

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.

3 files changed, 139 insertions(+), 199 deletions(-)

lib/std/build/CheckMachOStep.zig+105-145
......@@ -1,36 +1,23 @@
11const std = @import("../std.zig");
2const assert = std.debug.assert;
23const build = std.build;
3const Step = build.Step;
4const Builder = build.Builder;
54const fs = std.fs;
65const macho = std.macho;
76const mem = std.mem;
87
98const CheckMachOStep = @This();
109
10const Allocator = mem.Allocator;
11const Builder = build.Builder;
12const Step = build.Step;
13
1114pub const base_id = .check_macho;
1215
1316step: Step,
1417builder: *Builder,
1518source: build.FileSource,
1619max_bytes: usize = 20 * 1024 * 1024,
17lc_checks: std.ArrayList(LCCheck),
18
19const LCCheck = struct {
20 // common to most LCs
21 cmd: macho.LC,
22 name: ?[]const u8 = null,
23 // LC.SEGMENT_64 specific
24 index: ?usize = null,
25 vaddr: ?u64 = null,
26 memsz: ?u64 = null,
27 offset: ?u64 = null,
28 filesz: ?u64 = null,
29 // LC.LOAD_DYLIB specific
30 timestamp: ?u64 = null,
31 current_version: ?u32 = null,
32 compat_version: ?u32 = null,
33};
20checks: std.ArrayList(Check),
3421
3522pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep {
3623 const gpa = builder.allocator;
......@@ -39,25 +26,38 @@ pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep {
3926 .builder = builder,
4027 .step = Step.init(.check_file, "CheckMachO", gpa, make),
4128 .source = source.dupe(builder),
42 .lc_checks = std.ArrayList(LCCheck).init(gpa),
29 .checks = std.ArrayList(Check).init(gpa),
4330 };
4431 self.source.addStepDependencies(&self.step);
4532 return self;
4633}
4734
48pub fn checkLoadCommand(self: *CheckMachOStep, check: LCCheck) void {
49 self.lc_checks.append(.{
50 .cmd = check.cmd,
51 .index = check.index,
52 .name = if (check.name) |name| self.builder.dupe(name) else null,
53 .vaddr = check.vaddr,
54 .memsz = check.memsz,
55 .offset = check.offset,
56 .filesz = check.filesz,
57 .timestamp = check.timestamp,
58 .current_version = check.current_version,
59 .compat_version = check.compat_version,
60 }) catch unreachable;
35const Check = struct {
36 builder: *Builder,
37 phrases: std.ArrayList([]const u8),
38
39 fn create(b: *Builder) Check {
40 return .{
41 .builder = b,
42 .phrases = std.ArrayList([]const u8).init(b.allocator),
43 };
44 }
45
46 fn addPhrase(self: *Check, phrase: []const u8) void {
47 self.phrases.append(self.builder.dupe(phrase)) catch unreachable;
48 }
49};
50
51pub fn check(self: *CheckMachOStep, phrase: []const u8) void {
52 var new_check = Check.create(self.builder);
53 new_check.addPhrase(phrase);
54 self.checks.append(new_check) catch unreachable;
55}
56
57pub fn checkNext(self: *CheckMachOStep, phrase: []const u8) void {
58 assert(self.checks.items.len > 0);
59 const last = &self.checks.items[self.checks.items.len - 1];
60 last.addPhrase(phrase);
6161}
6262
6363fn make(step: *Step) !void {
......@@ -76,135 +76,95 @@ fn make(step: *Step) !void {
7676 return error.InvalidMagicNumber;
7777 }
7878
79 var load_commands = std.ArrayList(macho.LoadCommand).init(gpa);
80 try load_commands.ensureTotalCapacity(hdr.ncmds);
79 var metadata = std.ArrayList(u8).init(gpa);
80 const writer = metadata.writer();
8181
8282 var i: u16 = 0;
8383 while (i < hdr.ncmds) : (i += 1) {
8484 var cmd = try macho.LoadCommand.read(gpa, reader);
85 load_commands.appendAssumeCapacity(cmd);
85 try dumpLoadCommand(cmd, i, writer);
86 try writer.writeByte('\n');
8687 }
8788
88 outer: for (self.lc_checks.items) |ch| {
89 if (ch.index) |index| {
90 const lc = load_commands.items[index];
91 try cmpLoadCommand(ch, lc);
92 } else {
93 for (load_commands.items) |lc| {
94 if (lc.cmd() == ch.cmd) {
95 try cmpLoadCommand(ch, lc);
96 continue :outer;
89 for (self.checks.items) |chk| {
90 const first_phrase = chk.phrases.items[0];
91
92 if (mem.indexOf(u8, metadata.items, first_phrase)) |index| {
93 // TODO backtrack to track current scope
94 var it = std.mem.tokenize(u8, metadata.items[index..], "\r\n");
95
96 outer: for (chk.phrases.items[1..]) |next_phrase| {
97 while (it.next()) |line| {
98 if (mem.eql(u8, line, next_phrase)) {
99 std.debug.print("{s} == {s}\n", .{ line, next_phrase });
100 continue :outer;
101 }
102 std.debug.print("{s} != {s}\n", .{ line, next_phrase });
103 } else {
104 return error.TestFailed;
97105 }
98 } else {
99 return err("LC not found", ch.cmd, "");
100106 }
107 } else {
108 return error.TestFailed;
101109 }
102110 }
103111}
104112
105fn cmpLoadCommand(exp: LCCheck, given: macho.LoadCommand) error{TestFailed}!void {
106 if (exp.cmd != given.cmd()) {
107 return err("LC mismatch", exp.cmd, given.cmd());
108 }
109 switch (exp.cmd) {
113fn dumpLoadCommand(lc: macho.LoadCommand, index: u16, writer: anytype) !void {
114 // print header first
115 try writer.print(
116 \\LC {d}
117 \\cmd {s}
118 \\cmdsize {d}
119 , .{ index, @tagName(lc.cmd()), lc.cmdsize() });
120
121 switch (lc.cmd()) {
110122 .SEGMENT_64 => {
111 const lc = given.segment.inner;
112 if (exp.name) |name| {
113 if (!mem.eql(u8, name, lc.segName())) {
114 return err("segment name mismatch", name, lc.segName());
115 }
116 }
117 if (exp.vaddr) |vaddr| {
118 if (vaddr != lc.vmaddr) {
119 return err("segment VM address mismatch", vaddr, lc.vmaddr);
120 }
121 }
122 if (exp.memsz) |memsz| {
123 if (memsz != lc.vmsize) {
124 return err("segment VM size mismatch", memsz, lc.vmsize);
125 }
126 }
127 if (exp.offset) |offset| {
128 if (offset != lc.fileoff) {
129 return err("segment file offset mismatch", offset, lc.fileoff);
130 }
131 }
132 if (exp.filesz) |filesz| {
133 if (filesz != lc.filesize) {
134 return err("segment file size mismatch", filesz, lc.filesize);
135 }
136 }
123 // TODO dump section headers
124 const seg = lc.segment.inner;
125 try writer.writeByte('\n');
126 try writer.print(
127 \\segname {s}
128 \\vmaddr {x}
129 \\vmsize {x}
130 \\fileoff {x}
131 \\filesz {x}
132 , .{
133 seg.segName(),
134 seg.vmaddr,
135 seg.vmsize,
136 seg.fileoff,
137 seg.filesize,
138 });
137139 },
138 .ID_DYLIB, .LOAD_DYLIB => {
139 const lc = given.dylib;
140 if (exp.name) |name| {
141 if (!mem.eql(u8, name, mem.sliceTo(lc.data, 0))) {
142 return err("dylib path mismatch", name, mem.sliceTo(lc.data, 0));
143 }
144 }
145 if (exp.timestamp) |ts| {
146 if (ts != lc.inner.dylib.timestamp) {
147 return err("timestamp mismatch", ts, lc.inner.dylib.timestamp);
148 }
149 }
150 if (exp.current_version) |cv| {
151 if (cv != lc.inner.dylib.current_version) {
152 return err("current version mismatch", cv, lc.inner.dylib.current_version);
153 }
154 }
155 if (exp.compat_version) |cv| {
156 if (cv != lc.inner.dylib.compatibility_version) {
157 return err("compatibility version mismatch", cv, lc.inner.dylib.compatibility_version);
158 }
159 }
140
141 .ID_DYLIB,
142 .LOAD_DYLIB,
143 => {
144 const dylib = lc.dylib.inner.dylib;
145 try writer.writeByte('\n');
146 try writer.print(
147 \\path {s}
148 \\timestamp {d}
149 \\current version {x}
150 \\compatibility version {x}
151 , .{
152 mem.sliceTo(lc.dylib.data, 0),
153 dylib.timestamp,
154 dylib.current_version,
155 dylib.compatibility_version,
156 });
160157 },
158
161159 .RPATH => {
162 const lc = given.rpath;
163 if (exp.name) |name| {
164 if (!mem.eql(u8, name, mem.sliceTo(lc.data, 0))) {
165 return err("rpath path mismatch", name, mem.sliceTo(lc.data, 0));
166 }
167 }
160 try writer.writeByte('\n');
161 try writer.print(
162 \\path {s}
163 , .{
164 mem.sliceTo(lc.rpath.data, 0),
165 });
168166 },
169 else => @panic("TODO compare more load commands"),
170 }
171}
172167
173fn err(msg: []const u8, exp: anytype, giv: anytype) error{TestFailed} {
174 const fmt_specifier = if (comptime isString(@TypeOf(exp))) "{s}" else switch (@typeInfo(@TypeOf(exp))) {
175 .Int => "{x}",
176 .Float => "{d}",
177 else => "{any}",
178 };
179 std.debug.print(
180 \\=====================================
181 \\{s}
182 \\
183 \\======== Expected to find: ==========
184 \\
185 ++ fmt_specifier ++
186 \\
187 \\======== But instead found: =========
188 \\
189 ++ fmt_specifier ++
190 \\
191 \\
192 , .{ msg, exp, giv });
193 return error.TestFailed;
194}
195
196fn isString(comptime T: type) bool {
197 switch (@typeInfo(T)) {
198 .Array => return std.meta.Elem(T) == u8,
199 .Pointer => |pinfo| {
200 switch (pinfo.size) {
201 .Slice, .Many => return std.meta.Elem(T) == u8,
202 else => switch (@typeInfo(pinfo.child)) {
203 .Array => return isString(pinfo.child),
204 else => return false,
205 },
206 }
207 },
208 else => return false,
168 else => {},
209169 }
210170}
test/link/dylib/build.zig+19-30
......@@ -13,17 +13,14 @@ pub fn build(b: *Builder) void {
1313 dylib.linkLibC();
1414 dylib.install();
1515
16 {
17 const check_macho = dylib.checkMachO();
18 check_macho.checkLoadCommand(.{
19 .cmd = std.macho.LC.ID_DYLIB,
20 .name = "@rpath/liba.dylib",
21 .timestamp = 2,
22 .current_version = 0x10000,
23 .compat_version = 0x10000,
24 });
25 test_step.dependOn(&check_macho.step);
26 }
16 const check_dylib = dylib.checkMachO();
17 check_dylib.check("cmd ID_DYLIB");
18 check_dylib.checkNext("path @rpath/liba.dylib");
19 check_dylib.checkNext("timestamp 2");
20 check_dylib.checkNext("current version 10000");
21 check_dylib.checkNext("compatibility version 10000");
22
23 test_step.dependOn(&check_dylib.step);
2724
2825 const exe = b.addExecutable("main", null);
2926 exe.setBuildMode(mode);
......@@ -33,25 +30,17 @@ pub fn build(b: *Builder) void {
3330 exe.addLibraryPath(b.pathFromRoot("zig-out/lib/"));
3431 exe.addRPath(b.pathFromRoot("zig-out/lib"));
3532
36 {
37 const check_macho = exe.checkMachO();
38 check_macho.checkLoadCommand(.{
39 .cmd = std.macho.LC.LOAD_DYLIB,
40 .name = "@rpath/liba.dylib",
41 .timestamp = 2,
42 .current_version = 0x10000,
43 .compat_version = 0x10000,
44 });
45 test_step.dependOn(&check_macho.step);
46 }
47 {
48 const check_macho = exe.checkMachO();
49 check_macho.checkLoadCommand(.{
50 .cmd = std.macho.LC.RPATH,
51 .name = b.pathFromRoot("zig-out/lib"),
52 });
53 test_step.dependOn(&check_macho.step);
54 }
33 const check_exe = exe.checkMachO();
34 check_exe.check("cmd LOAD_DYLIB");
35 check_exe.checkNext("path @rpath/liba.dylib");
36 check_exe.checkNext("timestamp 2");
37 check_exe.checkNext("current version 10000");
38 check_exe.checkNext("compatibility version 10000");
39
40 check_exe.check("cmd RPATH");
41 check_exe.checkNext(std.fmt.allocPrint(b.allocator, "path {s}", .{b.pathFromRoot("zig-out/lib")}) catch unreachable);
42
43 test_step.dependOn(&check_exe.step);
5544
5645 const run = exe.run();
5746 run.cwd = b.pathFromRoot(".");
test/link/pagezero/build.zig+15-24
......@@ -14,22 +14,16 @@ pub fn build(b: *Builder) void {
1414 exe.linkLibC();
1515 exe.pagezero_size = 0x4000;
1616
17 const check_macho = exe.checkMachO();
18 check_macho.checkLoadCommand(.{
19 .cmd = std.macho.LC.SEGMENT_64,
20 .index = 0,
21 .name = "__PAGEZERO",
22 .vaddr = 0,
23 .memsz = 0x4000,
24 });
25 check_macho.checkLoadCommand(.{
26 .cmd = std.macho.LC.SEGMENT_64,
27 .index = 1,
28 .name = "__TEXT",
29 .vaddr = 0x4000,
30 });
31
32 test_step.dependOn(&check_macho.step);
17 const check = exe.checkMachO();
18 check.check("LC 0");
19 check.checkNext("segname __PAGEZERO");
20 check.checkNext("vmaddr 0");
21 check.checkNext("vmsize 4000");
22
23 check.check("segname __TEXT");
24 check.checkNext("vmaddr 4000");
25
26 test_step.dependOn(&check.step);
3327 }
3428
3529 {
......@@ -39,14 +33,11 @@ pub fn build(b: *Builder) void {
3933 exe.linkLibC();
4034 exe.pagezero_size = 0;
4135
42 const check_macho = exe.checkMachO();
43 check_macho.checkLoadCommand(.{
44 .cmd = std.macho.LC.SEGMENT_64,
45 .index = 0,
46 .name = "__TEXT",
47 .vaddr = 0,
48 });
36 const check = exe.checkMachO();
37 check.check("LC 0");
38 check.checkNext("segname __TEXT");
39 check.checkNext("vmaddr 0");
4940
50 test_step.dependOn(&check_macho.step);
41 test_step.dependOn(&check.step);
5142 }
5243}