| ... | ... | @@ -1,36 +1,23 @@ |
| 1 | 1 | const std = @import("../std.zig"); |
| 2 | const assert = std.debug.assert; |
| 2 | 3 | const build = std.build; |
| 3 | | const Step = build.Step; |
| 4 | | const Builder = build.Builder; |
| 5 | 4 | const fs = std.fs; |
| 6 | 5 | const macho = std.macho; |
| 7 | 6 | const mem = std.mem; |
| 8 | 7 | |
| 9 | 8 | const CheckMachOStep = @This(); |
| 10 | 9 | |
| 10 | const Allocator = mem.Allocator; |
| 11 | const Builder = build.Builder; |
| 12 | const Step = build.Step; |
| 13 | |
| 11 | 14 | pub const base_id = .check_macho; |
| 12 | 15 | |
| 13 | 16 | step: Step, |
| 14 | 17 | builder: *Builder, |
| 15 | 18 | source: build.FileSource, |
| 16 | 19 | 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 | | }; |
| 20 | checks: std.ArrayList(Check), |
| 34 | 21 | |
| 35 | 22 | pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep { |
| 36 | 23 | const gpa = builder.allocator; |
| ... | ... | @@ -39,25 +26,38 @@ pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep { |
| 39 | 26 | .builder = builder, |
| 40 | 27 | .step = Step.init(.check_file, "CheckMachO", gpa, make), |
| 41 | 28 | .source = source.dupe(builder), |
| 42 | | .lc_checks = std.ArrayList(LCCheck).init(gpa), |
| 29 | .checks = std.ArrayList(Check).init(gpa), |
| 43 | 30 | }; |
| 44 | 31 | self.source.addStepDependencies(&self.step); |
| 45 | 32 | return self; |
| 46 | 33 | } |
| 47 | 34 | |
| 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; |
| 35 | const Check = struct { |
| 36 | builder: *Builder, |
| 37 | phrases: std.ArrayList([]const u8), |
| 38 | |
| 39 | fn create(b: *Builder) Check { |
| 40 | return .{ |
| 41 | .builder = b, |
| 42 | .phrases = std.ArrayList([]const u8).init(b.allocator), |
| 43 | }; |
| 44 | } |
| 45 | |
| 46 | fn addPhrase(self: *Check, phrase: []const u8) void { |
| 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 | 63 | fn make(step: *Step) !void { |
| ... | ... | @@ -76,135 +76,95 @@ fn make(step: *Step) !void { |
| 76 | 76 | return error.InvalidMagicNumber; |
| 77 | 77 | } |
| 78 | 78 | |
| 79 | | var load_commands = std.ArrayList(macho.LoadCommand).init(gpa); |
| 80 | | try load_commands.ensureTotalCapacity(hdr.ncmds); |
| 79 | var metadata = std.ArrayList(u8).init(gpa); |
| 80 | const writer = metadata.writer(); |
| 81 | 81 | |
| 82 | 82 | var i: u16 = 0; |
| 83 | 83 | while (i < hdr.ncmds) : (i += 1) { |
| 84 | 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 | | 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; |
| 89 | for (self.checks.items) |chk| { |
| 90 | const first_phrase = chk.phrases.items[0]; |
| 91 | |
| 92 | if (mem.indexOf(u8, metadata.items, first_phrase)) |index| { |
| 93 | // TODO backtrack to track current scope |
| 94 | var it = std.mem.tokenize(u8, metadata.items[index..], "\r\n"); |
| 95 | |
| 96 | outer: for (chk.phrases.items[1..]) |next_phrase| { |
| 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 { |
| 106 | | if (exp.cmd != given.cmd()) { |
| 107 | | return err("LC mismatch", exp.cmd, given.cmd()); |
| 108 | | } |
| 109 | | switch (exp.cmd) { |
| 113 | fn dumpLoadCommand(lc: macho.LoadCommand, index: u16, writer: anytype) !void { |
| 114 | // print header first |
| 115 | try writer.print( |
| 116 | \\LC {d} |
| 117 | \\cmd {s} |
| 118 | \\cmdsize {d} |
| 119 | , .{ index, @tagName(lc.cmd()), lc.cmdsize() }); |
| 120 | |
| 121 | switch (lc.cmd()) { |
| 110 | 122 | .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 | | } |
| 123 | // TODO dump section headers |
| 124 | const seg = lc.segment.inner; |
| 125 | try writer.writeByte('\n'); |
| 126 | try writer.print( |
| 127 | \\segname {s} |
| 128 | \\vmaddr {x} |
| 129 | \\vmsize {x} |
| 130 | \\fileoff {x} |
| 131 | \\filesz {x} |
| 132 | , .{ |
| 133 | seg.segName(), |
| 134 | seg.vmaddr, |
| 135 | seg.vmsize, |
| 136 | seg.fileoff, |
| 137 | seg.filesize, |
| 138 | }); |
| 137 | 139 | }, |
| 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 | | } |
| 140 | |
| 141 | .ID_DYLIB, |
| 142 | .LOAD_DYLIB, |
| 143 | => { |
| 144 | const dylib = lc.dylib.inner.dylib; |
| 145 | try writer.writeByte('\n'); |
| 146 | try writer.print( |
| 147 | \\path {s} |
| 148 | \\timestamp {d} |
| 149 | \\current version {x} |
| 150 | \\compatibility version {x} |
| 151 | , .{ |
| 152 | mem.sliceTo(lc.dylib.data, 0), |
| 153 | dylib.timestamp, |
| 154 | dylib.current_version, |
| 155 | dylib.compatibility_version, |
| 156 | }); |
| 160 | 157 | }, |
| 158 | |
| 161 | 159 | .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 | | } |
| 160 | try writer.writeByte('\n'); |
| 161 | try writer.print( |
| 162 | \\path {s} |
| 163 | , .{ |
| 164 | mem.sliceTo(lc.rpath.data, 0), |
| 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} { |
| 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, |
| 168 | else => {}, |
| 209 | 169 | } |
| 210 | 170 | } |