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 @@...@@ -1,36 +1,23 @@
1const std = @import("../std.zig");1const std = @import("../std.zig");
2const assert = std.debug.assert;
2const build = std.build;3const build = std.build;
3const Step = build.Step;
4const Builder = build.Builder;
5const fs = std.fs;4const fs = std.fs;
6const macho = std.macho;5const macho = std.macho;
7const mem = std.mem;6const mem = std.mem;
87
9const CheckMachOStep = @This();8const CheckMachOStep = @This();
109
10const Allocator = mem.Allocator;
11const Builder = build.Builder;
12const Step = build.Step;
13
11pub const base_id = .check_macho;14pub const base_id = .check_macho;
1215
13step: Step,16step: Step,
14builder: *Builder,17builder: *Builder,
15source: build.FileSource,18source: build.FileSource,
16max_bytes: usize = 20 * 1024 * 1024,19max_bytes: usize = 20 * 1024 * 1024,
17lc_checks: std.ArrayList(LCCheck),20checks: std.ArrayList(Check),
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};
3421
35pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep {22pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep {
36 const gpa = builder.allocator;23 const gpa = builder.allocator;
...@@ -39,25 +26,38 @@ pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep {...@@ -39,25 +26,38 @@ pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep {
39 .builder = builder,26 .builder = builder,
40 .step = Step.init(.check_file, "CheckMachO", gpa, make),27 .step = Step.init(.check_file, "CheckMachO", gpa, make),
41 .source = source.dupe(builder),28 .source = source.dupe(builder),
42 .lc_checks = std.ArrayList(LCCheck).init(gpa),29 .checks = std.ArrayList(Check).init(gpa),
43 };30 };
44 self.source.addStepDependencies(&self.step);31 self.source.addStepDependencies(&self.step);
45 return self;32 return self;
46}33}
4734
48pub fn checkLoadCommand(self: *CheckMachOStep, check: LCCheck) void {35const Check = struct {
49 self.lc_checks.append(.{36 builder: *Builder,
50 .cmd = check.cmd,37 phrases: std.ArrayList([]const u8),
51 .index = check.index,38
52 .name = if (check.name) |name| self.builder.dupe(name) else null,39 fn create(b: *Builder) Check {
53 .vaddr = check.vaddr,40 return .{
54 .memsz = check.memsz,41 .builder = b,
55 .offset = check.offset,42 .phrases = std.ArrayList([]const u8).init(b.allocator),
56 .filesz = check.filesz,43 };
57 .timestamp = check.timestamp,44 }
58 .current_version = check.current_version,45
59 .compat_version = check.compat_version,46 fn addPhrase(self: *Check, phrase: []const u8) void {
60 }) catch unreachable;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);
61}61}
6262
63fn make(step: *Step) !void {63fn make(step: *Step) !void {
...@@ -76,135 +76,95 @@ fn make(step: *Step) !void {...@@ -76,135 +76,95 @@ fn make(step: *Step) !void {
76 return error.InvalidMagicNumber;76 return error.InvalidMagicNumber;
77 }77 }
7878
79 var load_commands = std.ArrayList(macho.LoadCommand).init(gpa);79 var metadata = std.ArrayList(u8).init(gpa);
80 try load_commands.ensureTotalCapacity(hdr.ncmds);80 const writer = metadata.writer();
8181
82 var i: u16 = 0;82 var i: u16 = 0;
83 while (i < hdr.ncmds) : (i += 1) {83 while (i < hdr.ncmds) : (i += 1) {
84 var cmd = try macho.LoadCommand.read(gpa, reader);84 var cmd = try macho.LoadCommand.read(gpa, reader);
85 load_commands.appendAssumeCapacity(cmd);85 try dumpLoadCommand(cmd, i, writer);
86 try writer.writeByte('\n');
86 }87 }
8788
88 outer: for (self.lc_checks.items) |ch| {89 for (self.checks.items) |chk| {
89 if (ch.index) |index| {90 const first_phrase = chk.phrases.items[0];
90 const lc = load_commands.items[index];91
91 try cmpLoadCommand(ch, lc);92 if (mem.indexOf(u8, metadata.items, first_phrase)) |index| {
92 } else {93 // TODO backtrack to track current scope
93 for (load_commands.items) |lc| {94 var it = std.mem.tokenize(u8, metadata.items[index..], "\r\n");
94 if (lc.cmd() == ch.cmd) {95
95 try cmpLoadCommand(ch, lc);96 outer: for (chk.phrases.items[1..]) |next_phrase| {
96 continue :outer;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;
97 }105 }
98 } else {
99 return err("LC not found", ch.cmd, "");
100 }106 }
107 } else {
108 return error.TestFailed;
101 }109 }
102 }110 }
103}111}
104112
105fn cmpLoadCommand(exp: LCCheck, given: macho.LoadCommand) error{TestFailed}!void {113fn dumpLoadCommand(lc: macho.LoadCommand, index: u16, writer: anytype) !void {
106 if (exp.cmd != given.cmd()) {114 // print header first
107 return err("LC mismatch", exp.cmd, given.cmd());115 try writer.print(
108 }116 \\LC {d}
109 switch (exp.cmd) {117 \\cmd {s}
118 \\cmdsize {d}
119 , .{ index, @tagName(lc.cmd()), lc.cmdsize() });
120
121 switch (lc.cmd()) {
110 .SEGMENT_64 => {122 .SEGMENT_64 => {
111 const lc = given.segment.inner;123 // TODO dump section headers
112 if (exp.name) |name| {124 const seg = lc.segment.inner;
113 if (!mem.eql(u8, name, lc.segName())) {125 try writer.writeByte('\n');
114 return err("segment name mismatch", name, lc.segName());126 try writer.print(
115 }127 \\segname {s}
116 }128 \\vmaddr {x}
117 if (exp.vaddr) |vaddr| {129 \\vmsize {x}
118 if (vaddr != lc.vmaddr) {130 \\fileoff {x}
119 return err("segment VM address mismatch", vaddr, lc.vmaddr);131 \\filesz {x}
120 }132 , .{
121 }133 seg.segName(),
122 if (exp.memsz) |memsz| {134 seg.vmaddr,
123 if (memsz != lc.vmsize) {135 seg.vmsize,
124 return err("segment VM size mismatch", memsz, lc.vmsize);136 seg.fileoff,
125 }137 seg.filesize,
126 }138 });
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 }
137 },139 },
138 .ID_DYLIB, .LOAD_DYLIB => {140
139 const lc = given.dylib;141 .ID_DYLIB,
140 if (exp.name) |name| {142 .LOAD_DYLIB,
141 if (!mem.eql(u8, name, mem.sliceTo(lc.data, 0))) {143 => {
142 return err("dylib path mismatch", name, mem.sliceTo(lc.data, 0));144 const dylib = lc.dylib.inner.dylib;
143 }145 try writer.writeByte('\n');
144 }146 try writer.print(
145 if (exp.timestamp) |ts| {147 \\path {s}
146 if (ts != lc.inner.dylib.timestamp) {148 \\timestamp {d}
147 return err("timestamp mismatch", ts, lc.inner.dylib.timestamp);149 \\current version {x}
148 }150 \\compatibility version {x}
149 }151 , .{
150 if (exp.current_version) |cv| {152 mem.sliceTo(lc.dylib.data, 0),
151 if (cv != lc.inner.dylib.current_version) {153 dylib.timestamp,
152 return err("current version mismatch", cv, lc.inner.dylib.current_version);154 dylib.current_version,
153 }155 dylib.compatibility_version,
154 }156 });
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 }
160 },157 },
158
161 .RPATH => {159 .RPATH => {
162 const lc = given.rpath;160 try writer.writeByte('\n');
163 if (exp.name) |name| {161 try writer.print(
164 if (!mem.eql(u8, name, mem.sliceTo(lc.data, 0))) {162 \\path {s}
165 return err("rpath path mismatch", name, mem.sliceTo(lc.data, 0));163 , .{
166 }164 mem.sliceTo(lc.rpath.data, 0),
167 }165 });
168 },166 },
169 else => @panic("TODO compare more load commands"),
170 }
171}
172167
173fn err(msg: []const u8, exp: anytype, giv: anytype) error{TestFailed} {168 else => {},
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,
209 }169 }
210}170}
test/link/dylib/build.zig+19-30
...@@ -13,17 +13,14 @@ pub fn build(b: *Builder) void {...@@ -13,17 +13,14 @@ pub fn build(b: *Builder) void {
13 dylib.linkLibC();13 dylib.linkLibC();
14 dylib.install();14 dylib.install();
1515
16 {16 const check_dylib = dylib.checkMachO();
17 const check_macho = dylib.checkMachO();17 check_dylib.check("cmd ID_DYLIB");
18 check_macho.checkLoadCommand(.{18 check_dylib.checkNext("path @rpath/liba.dylib");
19 .cmd = std.macho.LC.ID_DYLIB,19 check_dylib.checkNext("timestamp 2");
20 .name = "@rpath/liba.dylib",20 check_dylib.checkNext("current version 10000");
21 .timestamp = 2,21 check_dylib.checkNext("compatibility version 10000");
22 .current_version = 0x10000,22
23 .compat_version = 0x10000,23 test_step.dependOn(&check_dylib.step);
24 });
25 test_step.dependOn(&check_macho.step);
26 }
2724
28 const exe = b.addExecutable("main", null);25 const exe = b.addExecutable("main", null);
29 exe.setBuildMode(mode);26 exe.setBuildMode(mode);
...@@ -33,25 +30,17 @@ pub fn build(b: *Builder) void {...@@ -33,25 +30,17 @@ pub fn build(b: *Builder) void {
33 exe.addLibraryPath(b.pathFromRoot("zig-out/lib/"));30 exe.addLibraryPath(b.pathFromRoot("zig-out/lib/"));
34 exe.addRPath(b.pathFromRoot("zig-out/lib"));31 exe.addRPath(b.pathFromRoot("zig-out/lib"));
3532
36 {33 const check_exe = exe.checkMachO();
37 const check_macho = exe.checkMachO();34 check_exe.check("cmd LOAD_DYLIB");
38 check_macho.checkLoadCommand(.{35 check_exe.checkNext("path @rpath/liba.dylib");
39 .cmd = std.macho.LC.LOAD_DYLIB,36 check_exe.checkNext("timestamp 2");
40 .name = "@rpath/liba.dylib",37 check_exe.checkNext("current version 10000");
41 .timestamp = 2,38 check_exe.checkNext("compatibility version 10000");
42 .current_version = 0x10000,39
43 .compat_version = 0x10000,40 check_exe.check("cmd RPATH");
44 });41 check_exe.checkNext(std.fmt.allocPrint(b.allocator, "path {s}", .{b.pathFromRoot("zig-out/lib")}) catch unreachable);
45 test_step.dependOn(&check_macho.step);42
46 }43 test_step.dependOn(&check_exe.step);
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 }
5544
56 const run = exe.run();45 const run = exe.run();
57 run.cwd = b.pathFromRoot(".");46 run.cwd = b.pathFromRoot(".");
test/link/pagezero/build.zig+15-24
...@@ -14,22 +14,16 @@ pub fn build(b: *Builder) void {...@@ -14,22 +14,16 @@ pub fn build(b: *Builder) void {
14 exe.linkLibC();14 exe.linkLibC();
15 exe.pagezero_size = 0x4000;15 exe.pagezero_size = 0x4000;
1616
17 const check_macho = exe.checkMachO();17 const check = exe.checkMachO();
18 check_macho.checkLoadCommand(.{18 check.check("LC 0");
19 .cmd = std.macho.LC.SEGMENT_64,19 check.checkNext("segname __PAGEZERO");
20 .index = 0,20 check.checkNext("vmaddr 0");
21 .name = "__PAGEZERO",21 check.checkNext("vmsize 4000");
22 .vaddr = 0,22
23 .memsz = 0x4000,23 check.check("segname __TEXT");
24 });24 check.checkNext("vmaddr 4000");
25 check_macho.checkLoadCommand(.{25
26 .cmd = std.macho.LC.SEGMENT_64,26 test_step.dependOn(&check.step);
27 .index = 1,
28 .name = "__TEXT",
29 .vaddr = 0x4000,
30 });
31
32 test_step.dependOn(&check_macho.step);
33 }27 }
3428
35 {29 {
...@@ -39,14 +33,11 @@ pub fn build(b: *Builder) void {...@@ -39,14 +33,11 @@ pub fn build(b: *Builder) void {
39 exe.linkLibC();33 exe.linkLibC();
40 exe.pagezero_size = 0;34 exe.pagezero_size = 0;
4135
42 const check_macho = exe.checkMachO();36 const check = exe.checkMachO();
43 check_macho.checkLoadCommand(.{37 check.check("LC 0");
44 .cmd = std.macho.LC.SEGMENT_64,38 check.checkNext("segname __TEXT");
45 .index = 0,39 check.checkNext("vmaddr 0");
46 .name = "__TEXT",
47 .vaddr = 0,
48 });
4940
50 test_step.dependOn(&check_macho.step);41 test_step.dependOn(&check.step);
51 }42 }
52}43}