| author | |
| committer | |
| log | 5fbdfb3f3477bc8ac70b828671a7f980e8a8ad10 |
| tree | 5b325efa8ede38c5bf7bd45a48aed219d27ead91 |
| parent | 2d09540a636ab6ef2ca5087f18d55bbc259cd652 |
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"); |
| 24 | pub const WriteFileStep = @import("build/WriteFileStep.zig"); | 24 | pub const WriteFileStep = @import("build/WriteFileStep.zig"); |
| 25 | pub const RunStep = @import("build/RunStep.zig"); | 25 | pub const RunStep = @import("build/RunStep.zig"); |
| 26 | pub const CheckFileStep = @import("build/CheckFileStep.zig"); | 26 | pub const CheckFileStep = @import("build/CheckFileStep.zig"); |
| 27 | pub const CheckMachOStep = @import("build/CheckMachOStep.zig"); | ||
| 27 | pub const InstallRawStep = @import("build/InstallRawStep.zig"); | 28 | pub const InstallRawStep = @import("build/InstallRawStep.zig"); |
| 28 | pub const OptionsStep = @import("build/OptionsStep.zig"); | 29 | pub const OptionsStep = @import("build/OptionsStep.zig"); |
| 29 | 30 | ||
| ... | @@ -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 | } |
| 1866 | 1867 | ||
| 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 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const build = std.build; | ||
| 3 | const Step = build.Step; | ||
| 4 | const Builder = build.Builder; | ||
| 5 | const fs = std.fs; | ||
| 6 | const macho = std.macho; | ||
| 7 | const mem = std.mem; | ||
| 8 | |||
| 9 | const CheckMachOStep = @This(); | ||
| 10 | |||
| 11 | pub const base_id = .check_macho; | ||
| 12 | |||
| 13 | step: Step, | ||
| 14 | builder: *Builder, | ||
| 15 | source: build.FileSource, | ||
| 16 | max_bytes: usize = 20 * 1024 * 1024, | ||
| 17 | lc_checks: std.ArrayList(LCCheck), | ||
| 18 | |||
| 19 | const 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 | |||
| 35 | pub 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 | |||
| 48 | pub 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 | |||
| 63 | fn 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 | |||
| 105 | fn 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 | |||
| 173 | fn 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 | |||
| 196 | fn 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(); |
| 6 | 6 | ||
| 7 | const test_step = b.step("test", "Test"); | 7 | const test_step = b.step("test", "Test"); |
| 8 | test_step.dependOn(b.getInstallStep()); | ||
| 8 | 9 | ||
| 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(); |
| 14 | 15 | ||
| 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")); |
| 22 | 35 | ||
| 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(); |
| 6 | 6 | ||
| 7 | const test_step = b.step("test", "Test"); | 7 | const test_step = b.step("test", "Test"); |
| 8 | test_step.dependOn(b.getInstallStep()); | ||
| 8 | 9 | ||
| 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(); |
| 14 | 15 | 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)}); | ||
| 31 | 16 | ||
| 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 | } |