| ... | @@ -1,36 +1,23 @@ | ... | @@ -1,36 +1,23 @@ |
| 1 | const std = @import("../std.zig"); | 1 | const std = @import("../std.zig"); |
| | 2 | const assert = std.debug.assert; |
| 2 | const build = std.build; | 3 | const build = std.build; |
| 3 | const Step = build.Step; | | |
| 4 | const Builder = build.Builder; | | |
| 5 | const fs = std.fs; | 4 | const fs = std.fs; |
| 6 | const macho = std.macho; | 5 | const macho = std.macho; |
| 7 | const mem = std.mem; | 6 | const mem = std.mem; |
| 8 | | 7 | |
| 9 | const CheckMachOStep = @This(); | 8 | const CheckMachOStep = @This(); |
| 10 | | 9 | |
| | 10 | const Allocator = mem.Allocator; |
| | 11 | const Builder = build.Builder; |
| | 12 | const Step = build.Step; |
| | 13 | |
| 11 | pub const base_id = .check_macho; | 14 | pub const base_id = .check_macho; |
| 12 | | 15 | |
| 13 | step: Step, | 16 | step: Step, |
| 14 | builder: *Builder, | 17 | builder: *Builder, |
| 15 | source: build.FileSource, | 18 | source: build.FileSource, |
| 16 | max_bytes: usize = 20 * 1024 * 1024, | 19 | max_bytes: usize = 20 * 1024 * 1024, |
| 17 | lc_checks: std.ArrayList(LCCheck), | 20 | checks: std.ArrayList(Check), |
| 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 | | 21 | |
| 35 | pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep { | 22 | pub 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 | } |
| 47 | | 34 | |
| 48 | pub fn checkLoadCommand(self: *CheckMachOStep, check: LCCheck) void { | 35 | const 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 | |
| | 51 | pub 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 | |
| | 57 | pub 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 | } |
| 62 | | 62 | |
| 63 | fn make(step: *Step) !void { | 63 | fn 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 | } |
| 78 | | 78 | |
| 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(); |
| 81 | | 81 | |
| 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 | } |
| 87 | | 88 | |
| 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 | } |
| 104 | | 112 | |
| 105 | fn cmpLoadCommand(exp: LCCheck, given: macho.LoadCommand) error{TestFailed}!void { | 113 | fn 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 | } | | |
| 172 | | 167 | |
| 173 | fn 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 | | | |
| 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 | } | 169 | } |
| 210 | } | 170 | } |