| ... | @@ -18,6 +18,7 @@ builder: *Builder, | ... | @@ -18,6 +18,7 @@ builder: *Builder, |
| 18 | source: build.FileSource, | 18 | source: build.FileSource, |
| 19 | max_bytes: usize = 20 * 1024 * 1024, | 19 | max_bytes: usize = 20 * 1024 * 1024, |
| 20 | checks: std.ArrayList(Check), | 20 | checks: std.ArrayList(Check), |
| | 21 | dump_symtab: bool = false, |
| 21 | | 22 | |
| 22 | pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep { | 23 | pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep { |
| 23 | const gpa = builder.allocator; | 24 | const gpa = builder.allocator; |
| ... | @@ -32,32 +33,107 @@ pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep { | ... | @@ -32,32 +33,107 @@ pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep { |
| 32 | return self; | 33 | return self; |
| 33 | } | 34 | } |
| 34 | | 35 | |
| | 36 | const Action = union(enum) { |
| | 37 | exact_match: []const u8, |
| | 38 | extract_var: struct { |
| | 39 | fuzzy_match: []const u8, |
| | 40 | var_name: []const u8, |
| | 41 | var_value: u64, |
| | 42 | }, |
| | 43 | compare: CompareAction, |
| | 44 | }; |
| | 45 | |
| | 46 | const CompareAction = struct { |
| | 47 | expected: union(enum) { |
| | 48 | literal: u64, |
| | 49 | varr: []const u8, |
| | 50 | }, |
| | 51 | var_stack: std.ArrayList([]const u8), |
| | 52 | op_stack: std.ArrayList(Op), |
| | 53 | |
| | 54 | const Op = enum { |
| | 55 | add, |
| | 56 | }; |
| | 57 | }; |
| | 58 | |
| 35 | const Check = struct { | 59 | const Check = struct { |
| 36 | builder: *Builder, | 60 | builder: *Builder, |
| 37 | phrases: std.ArrayList([]const u8), | 61 | actions: std.ArrayList(Action), |
| 38 | | 62 | |
| 39 | fn create(b: *Builder) Check { | 63 | fn create(b: *Builder) Check { |
| 40 | return .{ | 64 | return .{ |
| 41 | .builder = b, | 65 | .builder = b, |
| 42 | .phrases = std.ArrayList([]const u8).init(b.allocator), | 66 | .actions = std.ArrayList(Action).init(b.allocator), |
| 43 | }; | 67 | }; |
| 44 | } | 68 | } |
| 45 | | 69 | |
| 46 | fn addPhrase(self: *Check, phrase: []const u8) void { | 70 | fn exactMatch(self: *Check, phrase: []const u8) void { |
| 47 | self.phrases.append(self.builder.dupe(phrase)) catch unreachable; | 71 | self.actions.append(.{ |
| | 72 | .exact_match = self.builder.dupe(phrase), |
| | 73 | }) catch unreachable; |
| | 74 | } |
| | 75 | |
| | 76 | fn extractVar(self: *Check, phrase: []const u8, var_name: []const u8) void { |
| | 77 | self.actions.append(.{ |
| | 78 | .extract_var = .{ |
| | 79 | .fuzzy_match = self.builder.dupe(phrase), |
| | 80 | .var_name = self.builder.dupe(var_name), |
| | 81 | .var_value = undefined, |
| | 82 | }, |
| | 83 | }) catch unreachable; |
| 48 | } | 84 | } |
| 49 | }; | 85 | }; |
| 50 | | 86 | |
| 51 | pub fn check(self: *CheckMachOStep, phrase: []const u8) void { | 87 | pub fn check(self: *CheckMachOStep, phrase: []const u8) void { |
| 52 | var new_check = Check.create(self.builder); | 88 | var new_check = Check.create(self.builder); |
| 53 | new_check.addPhrase(phrase); | 89 | new_check.exactMatch(phrase); |
| 54 | self.checks.append(new_check) catch unreachable; | 90 | self.checks.append(new_check) catch unreachable; |
| 55 | } | 91 | } |
| 56 | | 92 | |
| 57 | pub fn checkNext(self: *CheckMachOStep, phrase: []const u8) void { | 93 | pub fn checkNext(self: *CheckMachOStep, phrase: []const u8) void { |
| 58 | assert(self.checks.items.len > 0); | 94 | assert(self.checks.items.len > 0); |
| 59 | const last = &self.checks.items[self.checks.items.len - 1]; | 95 | const last = &self.checks.items[self.checks.items.len - 1]; |
| 60 | last.addPhrase(phrase); | 96 | last.exactMatch(phrase); |
| | 97 | } |
| | 98 | |
| | 99 | pub fn checkNextExtract(self: *CheckMachOStep, comptime phrase: []const u8) void { |
| | 100 | assert(self.checks.items.len > 0); |
| | 101 | const matcher_start = comptime mem.indexOf(u8, phrase, "{") orelse |
| | 102 | @compileError("missing { } matcher"); |
| | 103 | const matcher_end = comptime mem.indexOf(u8, phrase, "}") orelse |
| | 104 | @compileError("missing { } matcher"); |
| | 105 | const last = &self.checks.items[self.checks.items.len - 1]; |
| | 106 | last.extractVar(phrase[0..matcher_start], phrase[matcher_start + 1 .. matcher_end]); |
| | 107 | } |
| | 108 | |
| | 109 | pub fn checkInSymtab(self: *CheckMachOStep) void { |
| | 110 | self.dump_symtab = true; |
| | 111 | self.check("symtab"); |
| | 112 | } |
| | 113 | |
| | 114 | pub fn checkCompare(self: *CheckMachOStep, comptime phrase: []const u8, expected: anytype) void { |
| | 115 | comptime assert(phrase[0] == '{'); |
| | 116 | comptime assert(phrase[phrase.len - 1] == '}'); |
| | 117 | |
| | 118 | const gpa = self.builder.allocator; |
| | 119 | var ca = CompareAction{ |
| | 120 | .expected = expected, |
| | 121 | .var_stack = std.ArrayList([]const u8).init(gpa), |
| | 122 | .op_stack = std.ArrayList(CompareAction.Op).init(gpa), |
| | 123 | }; |
| | 124 | |
| | 125 | var it = mem.tokenize(u8, phrase[1 .. phrase.len - 1], " "); |
| | 126 | while (it.next()) |next| { |
| | 127 | if (mem.eql(u8, next, "+")) { |
| | 128 | ca.op_stack.append(.add) catch unreachable; |
| | 129 | } else { |
| | 130 | ca.var_stack.append(self.builder.dupe(next)) catch unreachable; |
| | 131 | } |
| | 132 | } |
| | 133 | |
| | 134 | var new_check = Check.create(self.builder); |
| | 135 | new_check.actions.append(.{ .compare = ca }) catch unreachable; |
| | 136 | self.checks.append(new_check) catch unreachable; |
| 61 | } | 137 | } |
| 62 | | 138 | |
| 63 | fn make(step: *Step) !void { | 139 | fn make(step: *Step) !void { |
| ... | @@ -79,35 +155,112 @@ fn make(step: *Step) !void { | ... | @@ -79,35 +155,112 @@ fn make(step: *Step) !void { |
| 79 | var metadata = std.ArrayList(u8).init(gpa); | 155 | var metadata = std.ArrayList(u8).init(gpa); |
| 80 | const writer = metadata.writer(); | 156 | const writer = metadata.writer(); |
| 81 | | 157 | |
| | 158 | var symtab_cmd: ?macho.symtab_command = null; |
| 82 | var i: u16 = 0; | 159 | var i: u16 = 0; |
| 83 | while (i < hdr.ncmds) : (i += 1) { | 160 | while (i < hdr.ncmds) : (i += 1) { |
| 84 | var cmd = try macho.LoadCommand.read(gpa, reader); | 161 | var cmd = try macho.LoadCommand.read(gpa, reader); |
| | 162 | |
| | 163 | if (self.dump_symtab and cmd.cmd() == .SYMTAB) { |
| | 164 | symtab_cmd = cmd.symtab; |
| | 165 | } |
| | 166 | |
| 85 | try dumpLoadCommand(cmd, i, writer); | 167 | try dumpLoadCommand(cmd, i, writer); |
| 86 | try writer.writeByte('\n'); | 168 | try writer.writeByte('\n'); |
| 87 | } | 169 | } |
| 88 | | 170 | |
| | 171 | if (symtab_cmd) |cmd| { |
| | 172 | try writer.writeAll("symtab\n"); |
| | 173 | const strtab = contents[cmd.stroff..][0..cmd.strsize]; |
| | 174 | const symtab = @ptrCast( |
| | 175 | [*]const macho.nlist_64, |
| | 176 | @alignCast(@alignOf(macho.nlist_64), contents.ptr + cmd.symoff), |
| | 177 | )[0..cmd.nsyms]; |
| | 178 | |
| | 179 | for (symtab) |sym| { |
| | 180 | if (sym.stab()) continue; |
| | 181 | const sym_name = mem.sliceTo(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx), 0); |
| | 182 | try writer.print("{s} {x}\n", .{ sym_name, sym.n_value }); |
| | 183 | } |
| | 184 | } |
| | 185 | |
| | 186 | var vars = std.StringHashMap(u64).init(gpa); |
| | 187 | |
| 89 | for (self.checks.items) |chk| { | 188 | for (self.checks.items) |chk| { |
| 90 | const first_phrase = chk.phrases.items[0]; | 189 | const first_action = chk.actions.items[0]; |
| 91 | | 190 | |
| 92 | if (mem.indexOf(u8, metadata.items, first_phrase)) |index| { | 191 | switch (first_action) { |
| 93 | // TODO backtrack to track current scope | 192 | .exact_match => |first| { |
| 94 | var it = std.mem.tokenize(u8, metadata.items[index..], "\r\n"); | 193 | if (mem.indexOf(u8, metadata.items, first)) |index| { |
| | 194 | // TODO backtrack to track current scope |
| | 195 | var it = std.mem.tokenize(u8, metadata.items[index..], "\r\n"); |
| 95 | | 196 | |
| 96 | outer: for (chk.phrases.items[1..]) |next_phrase| { | 197 | outer: for (chk.actions.items[1..]) |next_action| { |
| 97 | while (it.next()) |line| { | 198 | switch (next_action) { |
| 98 | if (mem.eql(u8, line, next_phrase)) { | 199 | .exact_match => |exact| { |
| 99 | std.debug.print("{s} == {s}\n", .{ line, next_phrase }); | 200 | while (it.next()) |line| { |
| 100 | continue :outer; | 201 | if (mem.eql(u8, line, exact)) { |
| | 202 | std.debug.print("{s} == {s}\n", .{ line, exact }); |
| | 203 | continue :outer; |
| | 204 | } |
| | 205 | std.debug.print("{s} != {s}\n", .{ line, exact }); |
| | 206 | } else { |
| | 207 | return error.TestFailed; |
| | 208 | } |
| | 209 | }, |
| | 210 | .extract_var => |extract| { |
| | 211 | const phrase = extract.fuzzy_match; |
| | 212 | while (it.next()) |line| { |
| | 213 | if (mem.indexOf(u8, line, phrase)) |found| { |
| | 214 | std.debug.print("{s} in {s}\n", .{ phrase, line }); |
| | 215 | // Extract variable and save back in the action. |
| | 216 | const trimmed = mem.trim(u8, line[found + phrase.len ..], " "); |
| | 217 | const parsed = try std.fmt.parseInt(u64, trimmed, 16); |
| | 218 | try vars.putNoClobber(extract.var_name, parsed); |
| | 219 | continue :outer; |
| | 220 | } |
| | 221 | std.debug.print("{s} not in {s}\n", .{ extract.fuzzy_match, line }); |
| | 222 | } |
| | 223 | }, |
| | 224 | .compare => unreachable, |
| | 225 | } |
| 101 | } | 226 | } |
| 102 | std.debug.print("{s} != {s}\n", .{ line, next_phrase }); | | |
| 103 | } else { | 227 | } else { |
| 104 | return error.TestFailed; | 228 | return error.TestFailed; |
| 105 | } | 229 | } |
| 106 | } | 230 | }, |
| 107 | } else { | 231 | .compare => |act| { |
| 108 | return error.TestFailed; | 232 | var values = std.ArrayList(u64).init(gpa); |
| | 233 | try values.ensureTotalCapacity(act.var_stack.items.len); |
| | 234 | for (act.var_stack.items) |vv| { |
| | 235 | const val = vars.get(vv) orelse return error.TestFailed; |
| | 236 | values.appendAssumeCapacity(val); |
| | 237 | } |
| | 238 | |
| | 239 | var op_i: usize = 1; |
| | 240 | var reduced: u64 = values.items[0]; |
| | 241 | for (act.op_stack.items) |op| { |
| | 242 | const other = values.items[op_i]; |
| | 243 | switch (op) { |
| | 244 | .add => { |
| | 245 | reduced += other; |
| | 246 | }, |
| | 247 | } |
| | 248 | } |
| | 249 | |
| | 250 | const expected = switch (act.expected) { |
| | 251 | .literal => |exp| exp, |
| | 252 | .varr => |vv| vars.get(vv) orelse return error.TestFailed, |
| | 253 | }; |
| | 254 | if (reduced != expected) return error.TestFailed; |
| | 255 | }, |
| | 256 | .extract_var => unreachable, |
| 109 | } | 257 | } |
| 110 | } | 258 | } |
| | 259 | |
| | 260 | var it = vars.iterator(); |
| | 261 | while (it.next()) |entry| { |
| | 262 | std.debug.print(" {s} => {x}", .{ entry.key_ptr.*, entry.value_ptr.* }); |
| | 263 | } |
| 111 | } | 264 | } |
| 112 | | 265 | |
| 113 | fn dumpLoadCommand(lc: macho.LoadCommand, index: u16, writer: anytype) !void { | 266 | fn dumpLoadCommand(lc: macho.LoadCommand, index: u16, writer: anytype) !void { |