authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-06-21 15:44:22+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-06-21 15:44:24+02:00
log5fbdfb3f3477bc8ac70b828671a7f980e8a8ad10
tree5b325efa8ede38c5bf7bd45a48aed219d27ead91
parent2d09540a636ab6ef2ca5087f18d55bbc259cd652

link-tests: add CheckMachOStep

CheckMachOStep specialises CheckFileStep into directed (surgical) MachO file fuzzy searches. This will be the building block for comprehensive MachO linker tests.

4 files changed, 291 insertions(+), 33 deletions(-)

lib/std/build.zig+6
...@@ -24,6 +24,7 @@ pub const TranslateCStep = @import("build/TranslateCStep.zig");...@@ -24,6 +24,7 @@ pub const TranslateCStep = @import("build/TranslateCStep.zig");
24pub const WriteFileStep = @import("build/WriteFileStep.zig");24pub const WriteFileStep = @import("build/WriteFileStep.zig");
25pub const RunStep = @import("build/RunStep.zig");25pub const RunStep = @import("build/RunStep.zig");
26pub const CheckFileStep = @import("build/CheckFileStep.zig");26pub const CheckFileStep = @import("build/CheckFileStep.zig");
27pub const CheckMachOStep = @import("build/CheckMachOStep.zig");
27pub const InstallRawStep = @import("build/InstallRawStep.zig");28pub const InstallRawStep = @import("build/InstallRawStep.zig");
28pub const OptionsStep = @import("build/OptionsStep.zig");29pub const OptionsStep = @import("build/OptionsStep.zig");
2930
...@@ -1864,6 +1865,10 @@ pub const LibExeObjStep = struct {...@@ -1864,6 +1865,10 @@ pub const LibExeObjStep = struct {
1864 return run_step;1865 return run_step;
1865 }1866 }
18661867
1868 pub fn checkMachO(self: *LibExeObjStep) *CheckMachOStep {
1869 return CheckMachOStep.create(self.builder, self.getOutputSource());
1870 }
1871
1867 pub fn setLinkerScriptPath(self: *LibExeObjStep, source: FileSource) void {1872 pub fn setLinkerScriptPath(self: *LibExeObjStep, source: FileSource) void {
1868 self.linker_script = source.dupe(self.builder);1873 self.linker_script = source.dupe(self.builder);
1869 source.addStepDependencies(&self.step);1874 source.addStepDependencies(&self.step);
...@@ -3450,6 +3455,7 @@ pub const Step = struct {...@@ -3450,6 +3455,7 @@ pub const Step = struct {
3450 write_file,3455 write_file,
3451 run,3456 run,
3452 check_file,3457 check_file,
3458 check_macho,
3453 install_raw,3459 install_raw,
3454 options,3460 options,
3455 custom,3461 custom,
lib/std/build/CheckMachOStep.zig created+210
...@@ -0,0 +1,210 @@
1const std = @import("../std.zig");
2const build = std.build;
3const Step = build.Step;
4const Builder = build.Builder;
5const fs = std.fs;
6const macho = std.macho;
7const mem = std.mem;
8
9const CheckMachOStep = @This();
10
11pub const base_id = .check_macho;
12
13step: Step,
14builder: *Builder,
15source: build.FileSource,
16max_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};
34
35pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep {
36 const gpa = builder.allocator;
37 const self = gpa.create(CheckMachOStep) catch unreachable;
38 self.* = CheckMachOStep{
39 .builder = builder,
40 .step = Step.init(.check_file, "CheckMachO", gpa, make),
41 .source = source.dupe(builder),
42 .lc_checks = std.ArrayList(LCCheck).init(gpa),
43 };
44 self.source.addStepDependencies(&self.step);
45 return self;
46}
47
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;
61}
62
63fn make(step: *Step) !void {
64 const self = @fieldParentPtr(CheckMachOStep, "step", step);
65
66 const gpa = self.builder.allocator;
67 const src_path = self.source.getPath(self.builder);
68 const contents = try fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes);
69
70 // Parse the object file's header
71 var stream = std.io.fixedBufferStream(contents);
72 const reader = stream.reader();
73
74 const hdr = try reader.readStruct(macho.mach_header_64);
75 if (hdr.magic != macho.MH_MAGIC_64) {
76 return error.InvalidMagicNumber;
77 }
78
79 var load_commands = std.ArrayList(macho.LoadCommand).init(gpa);
80 try load_commands.ensureTotalCapacity(hdr.ncmds);
81
82 var i: u16 = 0;
83 while (i < hdr.ncmds) : (i += 1) {
84 var cmd = try macho.LoadCommand.read(gpa, reader);
85 load_commands.appendAssumeCapacity(cmd);
86 }
87
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;
97 }
98 } else {
99 return err("LC not found", ch.cmd, "");
100 }
101 }
102 }
103}
104
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) {
110 .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 }
137 },
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 }
160 },
161 .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 }
168 },
169 else => @panic("TODO compare more load commands"),
170 }
171}
172
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,
209 }
210}
test/link/dylib/build.zig+33-9
...@@ -5,6 +5,7 @@ pub fn build(b: *Builder) void {...@@ -5,6 +5,7 @@ pub fn build(b: *Builder) void {
5 const mode = b.standardReleaseOptions();5 const mode = b.standardReleaseOptions();
66
7 const test_step = b.step("test", "Test");7 const test_step = b.step("test", "Test");
8 test_step.dependOn(b.getInstallStep());
89
9 const dylib = b.addSharedLibrary("a", null, b.version(1, 0, 0));10 const dylib = b.addSharedLibrary("a", null, b.version(1, 0, 0));
10 dylib.setBuildMode(mode);11 dylib.setBuildMode(mode);
...@@ -12,6 +13,18 @@ pub fn build(b: *Builder) void {...@@ -12,6 +13,18 @@ pub fn build(b: *Builder) void {
12 dylib.linkLibC();13 dylib.linkLibC();
13 dylib.install();14 dylib.install();
1415
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 }
27
15 const exe = b.addExecutable("main", null);28 const exe = b.addExecutable("main", null);
16 exe.setBuildMode(mode);29 exe.setBuildMode(mode);
17 exe.addCSourceFile("main.c", &.{});30 exe.addCSourceFile("main.c", &.{});
...@@ -20,17 +33,28 @@ pub fn build(b: *Builder) void {...@@ -20,17 +33,28 @@ pub fn build(b: *Builder) void {
20 exe.addLibraryPath(b.pathFromRoot("zig-out/lib/"));33 exe.addLibraryPath(b.pathFromRoot("zig-out/lib/"));
21 exe.addRPath(b.pathFromRoot("zig-out/lib"));34 exe.addRPath(b.pathFromRoot("zig-out/lib"));
2235
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 }
55
23 const run = exe.run();56 const run = exe.run();
24 run.cwd = b.pathFromRoot(".");57 run.cwd = b.pathFromRoot(".");
25 run.expectStdOutEqual("Hello world");58 run.expectStdOutEqual("Hello world");
26
27 const exp_dylib = std.macho.createLoadDylibCommand(b.allocator, "@rpath/liba.dylib", 2, 0x10000, 0x10000) catch unreachable;
28 var buf = std.ArrayList(u8).init(b.allocator);
29 defer buf.deinit();
30 exp_dylib.write(buf.writer()) catch unreachable;
31 const check_file = std.build.CheckFileStep.create(b, exe.getOutputSource(), &[_][]const u8{buf.items});
32
33 test_step.dependOn(b.getInstallStep());
34 test_step.dependOn(&run.step);59 test_step.dependOn(&run.step);
35 test_step.dependOn(&check_file.step);
36}60}
test/link/pagezero/build.zig+42-24
...@@ -5,30 +5,48 @@ pub fn build(b: *Builder) void {...@@ -5,30 +5,48 @@ pub fn build(b: *Builder) void {
5 const mode = b.standardReleaseOptions();5 const mode = b.standardReleaseOptions();
66
7 const test_step = b.step("test", "Test");7 const test_step = b.step("test", "Test");
8 test_step.dependOn(b.getInstallStep());
89
9 const exe = b.addExecutable("main", null);10 {
10 exe.setBuildMode(mode);11 const exe = b.addExecutable("pagezero", null);
11 exe.addCSourceFile("main.c", &.{});12 exe.setBuildMode(mode);
12 exe.linkLibC();13 exe.addCSourceFile("main.c", &.{});
13 exe.pagezero_size = 0x4000;14 exe.linkLibC();
1415 exe.pagezero_size = 0x4000;
15 var name: [16]u8 = undefined;
16 std.mem.set(u8, &name, 0);
17 std.mem.copy(u8, &name, "__PAGEZERO");
18 const pagezero_seg = std.macho.segment_command_64{
19 .cmdsize = @sizeOf(std.macho.segment_command_64),
20 .segname = name,
21 .vmaddr = 0,
22 .vmsize = 0x4000,
23 .fileoff = 0,
24 .filesize = 0,
25 .maxprot = 0,
26 .initprot = 0,
27 .nsects = 0,
28 .flags = 0,
29 };
30 const check_file = std.build.CheckFileStep.create(b, exe.getOutputSource(), &[_][]const u8{std.mem.asBytes(&pagezero_seg)});
3116
32 test_step.dependOn(b.getInstallStep());17 const check_macho = exe.checkMachO();
33 test_step.dependOn(&check_file.step);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);
33 }
34
35 {
36 const exe = b.addExecutable("no_pagezero", null);
37 exe.setBuildMode(mode);
38 exe.addCSourceFile("main.c", &.{});
39 exe.linkLibC();
40 exe.pagezero_size = 0;
41
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 });
49
50 test_step.dependOn(&check_macho.step);
51 }
34}52}