| 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 | 24 | pub const WriteFileStep = @import("build/WriteFileStep.zig"); |
| 25 | 25 | pub const RunStep = @import("build/RunStep.zig"); |
| 26 | 26 | pub const CheckFileStep = @import("build/CheckFileStep.zig"); |
| 27 | pub const CheckMachOStep = @import("build/CheckMachOStep.zig"); | |
| 27 | 28 | pub const InstallRawStep = @import("build/InstallRawStep.zig"); |
| 28 | 29 | pub const OptionsStep = @import("build/OptionsStep.zig"); |
| 29 | 30 | |
| ... | ... | @@ -1864,6 +1865,10 @@ pub const LibExeObjStep = struct { |
| 1864 | 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 | 1872 | pub fn setLinkerScriptPath(self: *LibExeObjStep, source: FileSource) void { |
| 1868 | 1873 | self.linker_script = source.dupe(self.builder); |
| 1869 | 1874 | source.addStepDependencies(&self.step); |
| ... | ... | @@ -3450,6 +3455,7 @@ pub const Step = struct { |
| 3450 | 3455 | write_file, |
| 3451 | 3456 | run, |
| 3452 | 3457 | check_file, |
| 3458 | check_macho, | |
| 3453 | 3459 | install_raw, |
| 3454 | 3460 | options, |
| 3455 | 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 | 5 | const mode = b.standardReleaseOptions(); |
| 6 | 6 | |
| 7 | 7 | const test_step = b.step("test", "Test"); |
| 8 | test_step.dependOn(b.getInstallStep()); | |
| 8 | 9 | |
| 9 | 10 | const dylib = b.addSharedLibrary("a", null, b.version(1, 0, 0)); |
| 10 | 11 | dylib.setBuildMode(mode); |
| ... | ... | @@ -12,6 +13,18 @@ pub fn build(b: *Builder) void { |
| 12 | 13 | dylib.linkLibC(); |
| 13 | 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 | 28 | const exe = b.addExecutable("main", null); |
| 16 | 29 | exe.setBuildMode(mode); |
| 17 | 30 | exe.addCSourceFile("main.c", &.{}); |
| ... | ... | @@ -20,17 +33,28 @@ pub fn build(b: *Builder) void { |
| 20 | 33 | exe.addLibraryPath(b.pathFromRoot("zig-out/lib/")); |
| 21 | 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 | 56 | const run = exe.run(); |
| 24 | 57 | run.cwd = b.pathFromRoot("."); |
| 25 | 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 | 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 | 5 | const mode = b.standardReleaseOptions(); |
| 6 | 6 | |
| 7 | 7 | const test_step = b.step("test", "Test"); |
| 8 | test_step.dependOn(b.getInstallStep()); | |
| 8 | 9 | |
| 9 | const exe = b.addExecutable("main", null); | |
| 10 | exe.setBuildMode(mode); | |
| 11 | exe.addCSourceFile("main.c", &.{}); | |
| 12 | exe.linkLibC(); | |
| 13 | exe.pagezero_size = 0x4000; | |
| 14 | ||
| 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)}); | |
| 10 | { | |
| 11 | const exe = b.addExecutable("pagezero", null); | |
| 12 | exe.setBuildMode(mode); | |
| 13 | exe.addCSourceFile("main.c", &.{}); | |
| 14 | exe.linkLibC(); | |
| 15 | exe.pagezero_size = 0x4000; | |
| 31 | 16 | |
| 32 | test_step.dependOn(b.getInstallStep()); | |
| 33 | test_step.dependOn(&check_file.step); | |
| 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); | |
| 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 | } |