| ... | @@ -3,6 +3,7 @@ const assert = std.debug.assert; | ... | @@ -3,6 +3,7 @@ const assert = std.debug.assert; |
| 3 | const build = std.build; | 3 | const build = std.build; |
| 4 | const fs = std.fs; | 4 | const fs = std.fs; |
| 5 | const macho = std.macho; | 5 | const macho = std.macho; |
| | 6 | const math = std.math; |
| 6 | const mem = std.mem; | 7 | const mem = std.mem; |
| 7 | const testing = std.testing; | 8 | const testing = std.testing; |
| 8 | | 9 | |
| ... | @@ -36,20 +37,24 @@ pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Targe | ... | @@ -36,20 +37,24 @@ pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Targe |
| 36 | return self; | 37 | return self; |
| 37 | } | 38 | } |
| 38 | | 39 | |
| 39 | const Action = union(enum) { | 40 | /// There two types of actions currently suported: |
| 40 | match: MatchAction, | 41 | /// * `.match` - is the main building block of standard matchers with optional eat-all token `{*}` |
| 41 | compute_eq: ComputeEqAction, | | |
| 42 | }; | | |
| 43 | | | |
| 44 | /// MatchAction is the main building block of standard matchers with optional eat-all token `{*}` | | |
| 45 | /// and extractors by name such as `{n_value}`. Please note this action is very simplistic in nature | 42 | /// and extractors by name such as `{n_value}`. Please note this action is very simplistic in nature |
| 46 | /// i.e., it won't really handle edge cases/nontrivial examples. But given that we do want to use | 43 | /// i.e., it won't really handle edge cases/nontrivial examples. But given that we do want to use |
| 47 | /// it mainly to test the output of our object format parser-dumpers when testing the linkers, etc. | 44 | /// it mainly to test the output of our object format parser-dumpers when testing the linkers, etc. |
| 48 | /// it should be plenty useful in its current form. | 45 | /// it should be plenty useful in its current form. |
| 49 | const MatchAction = struct { | 46 | /// * `.compute_cmp` - can be used to perform an operation on the extracted global variables |
| 50 | needle: []const u8, | 47 | /// using the MatchAction. It currently only supports an addition. The operation is required |
| | 48 | /// to be specified in Reverse Polish Notation to ease in operator-precedence parsing (well, |
| | 49 | /// to avoid any parsing really). |
| | 50 | /// For example, if the two extracted values were saved as `vmaddr` and `entryoff` respectively |
| | 51 | /// they could then be added with this simple program `vmaddr entryoff +`. |
| | 52 | const Action = struct { |
| | 53 | tag: enum { match, compute_cmp }, |
| | 54 | phrase: []const u8, |
| | 55 | expected: ?ComputeCompareExpected = null, |
| 51 | | 56 | |
| 52 | /// Will return true if the `needle` was found in the `haystack`. | 57 | /// Will return true if the `phrase` was found in the `haystack`. |
| 53 | /// Some examples include: | 58 | /// Some examples include: |
| 54 | /// | 59 | /// |
| 55 | /// LC 0 => will match in its entirety | 60 | /// LC 0 => will match in its entirety |
| ... | @@ -57,9 +62,11 @@ const MatchAction = struct { | ... | @@ -57,9 +62,11 @@ const MatchAction = struct { |
| 57 | /// and save under `vmaddr` global name (see `global_vars` param) | 62 | /// and save under `vmaddr` global name (see `global_vars` param) |
| 58 | /// name {*}libobjc{*}.dylib => will match `name` followed by a token which contains `libobjc` and `.dylib` | 63 | /// name {*}libobjc{*}.dylib => will match `name` followed by a token which contains `libobjc` and `.dylib` |
| 59 | /// in that order with other letters in between | 64 | /// in that order with other letters in between |
| 60 | fn match(act: MatchAction, haystack: []const u8, global_vars: anytype) !bool { | 65 | fn match(act: Action, haystack: []const u8, global_vars: anytype) !bool { |
| | 66 | assert(act.tag == .match); |
| | 67 | |
| 61 | var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " "); | 68 | var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " "); |
| 62 | var needle_it = mem.tokenize(u8, mem.trim(u8, act.needle, " "), " "); | 69 | var needle_it = mem.tokenize(u8, mem.trim(u8, act.phrase, " "), " "); |
| 63 | | 70 | |
| 64 | while (needle_it.next()) |needle_tok| { | 71 | while (needle_it.next()) |needle_tok| { |
| 65 | const hay_tok = hay_it.next() orelse return false; | 72 | const hay_tok = hay_it.next() orelse return false; |
| ... | @@ -93,22 +100,80 @@ const MatchAction = struct { | ... | @@ -93,22 +100,80 @@ const MatchAction = struct { |
| 93 | | 100 | |
| 94 | return true; | 101 | return true; |
| 95 | } | 102 | } |
| 96 | }; | | |
| 97 | | 103 | |
| 98 | /// ComputeEqAction can be used to perform an operation on the extracted global variables | 104 | /// Will return true if the `phrase` is correctly parsed into an RPN program and |
| 99 | /// using the MatchAction. It currently only supports an addition. The operation is required | 105 | /// its reduced, computed value compares using `op` with the expected value, either |
| 100 | /// to be specified in Reverse Polish Notation to ease in operator-precedence parsing (well, | 106 | /// a literal or another extracted variable. |
| 101 | /// to avoid any parsing really). | 107 | fn computeCmp(act: Action, gpa: Allocator, global_vars: anytype) !bool { |
| 102 | /// For example, if the two extracted values were saved as `vmaddr` and `entryoff` respectively | 108 | var op_stack = std.ArrayList(enum { add }).init(gpa); |
| 103 | /// they could then be added with this simple program `vmaddr entryoff +`. | 109 | var values = std.ArrayList(u64).init(gpa); |
| 104 | const ComputeEqAction = struct { | 110 | |
| 105 | expected: []const u8, | 111 | var it = mem.tokenize(u8, act.phrase, " "); |
| 106 | var_stack: std.ArrayList([]const u8), | 112 | while (it.next()) |next| { |
| 107 | op_stack: std.ArrayList(Op), | 113 | if (mem.eql(u8, next, "+")) { |
| | 114 | try op_stack.append(.add); |
| | 115 | } else { |
| | 116 | const val = global_vars.get(next) orelse { |
| | 117 | std.debug.print( |
| | 118 | \\ |
| | 119 | \\========= Variable was not extracted: =========== |
| | 120 | \\{s} |
| | 121 | \\ |
| | 122 | , .{next}); |
| | 123 | return error.UnknownVariable; |
| | 124 | }; |
| | 125 | try values.append(val); |
| | 126 | } |
| | 127 | } |
| 108 | | 128 | |
| 109 | const Op = enum { | 129 | var op_i: usize = 1; |
| 110 | add, | 130 | var reduced: u64 = values.items[0]; |
| 111 | }; | 131 | for (op_stack.items) |op| { |
| | 132 | const other = values.items[op_i]; |
| | 133 | switch (op) { |
| | 134 | .add => { |
| | 135 | reduced += other; |
| | 136 | }, |
| | 137 | } |
| | 138 | } |
| | 139 | |
| | 140 | const exp_value = switch (act.expected.?.value) { |
| | 141 | .variable => |name| global_vars.get(name) orelse { |
| | 142 | std.debug.print( |
| | 143 | \\ |
| | 144 | \\========= Variable was not extracted: =========== |
| | 145 | \\{s} |
| | 146 | \\ |
| | 147 | , .{name}); |
| | 148 | return error.UnknownVariable; |
| | 149 | }, |
| | 150 | .literal => |x| x, |
| | 151 | }; |
| | 152 | return math.compare(reduced, act.expected.?.op, exp_value); |
| | 153 | } |
| | 154 | }; |
| | 155 | |
| | 156 | const ComputeCompareExpected = struct { |
| | 157 | op: math.CompareOperator, |
| | 158 | value: union(enum) { |
| | 159 | variable: []const u8, |
| | 160 | literal: u64, |
| | 161 | }, |
| | 162 | |
| | 163 | pub fn format( |
| | 164 | value: @This(), |
| | 165 | comptime fmt: []const u8, |
| | 166 | options: std.fmt.FormatOptions, |
| | 167 | writer: anytype, |
| | 168 | ) !void { |
| | 169 | _ = fmt; |
| | 170 | _ = options; |
| | 171 | try writer.print("{s} ", .{@tagName(value.op)}); |
| | 172 | switch (value.value) { |
| | 173 | .variable => |name| try writer.writeAll(name), |
| | 174 | .literal => |x| try writer.print("{x}", .{x}), |
| | 175 | } |
| | 176 | } |
| 112 | }; | 177 | }; |
| 113 | | 178 | |
| 114 | const Check = struct { | 179 | const Check = struct { |
| ... | @@ -122,15 +187,18 @@ const Check = struct { | ... | @@ -122,15 +187,18 @@ const Check = struct { |
| 122 | }; | 187 | }; |
| 123 | } | 188 | } |
| 124 | | 189 | |
| 125 | fn match(self: *Check, needle: []const u8) void { | 190 | fn match(self: *Check, phrase: []const u8) void { |
| 126 | self.actions.append(.{ | 191 | self.actions.append(.{ |
| 127 | .match = .{ .needle = self.builder.dupe(needle) }, | 192 | .tag = .match, |
| | 193 | .phrase = self.builder.dupe(phrase), |
| 128 | }) catch unreachable; | 194 | }) catch unreachable; |
| 129 | } | 195 | } |
| 130 | | 196 | |
| 131 | fn computeEq(self: *Check, act: ComputeEqAction) void { | 197 | fn computeCmp(self: *Check, phrase: []const u8, expected: ComputeCompareExpected) void { |
| 132 | self.actions.append(.{ | 198 | self.actions.append(.{ |
| 133 | .compute_eq = act, | 199 | .tag = .compute_cmp, |
| | 200 | .phrase = self.builder.dupe(phrase), |
| | 201 | .expected = expected, |
| 134 | }) catch unreachable; | 202 | }) catch unreachable; |
| 135 | } | 203 | } |
| 136 | }; | 204 | }; |
| ... | @@ -165,25 +233,13 @@ pub fn checkInSymtab(self: *CheckObjectStep) void { | ... | @@ -165,25 +233,13 @@ pub fn checkInSymtab(self: *CheckObjectStep) void { |
| 165 | /// Creates a new standalone, singular check which allows running simple binary operations | 233 | /// Creates a new standalone, singular check which allows running simple binary operations |
| 166 | /// on the extracted variables. It will then compare the reduced program with the value of | 234 | /// on the extracted variables. It will then compare the reduced program with the value of |
| 167 | /// the expected variable. | 235 | /// the expected variable. |
| 168 | pub fn checkComputeEq(self: *CheckObjectStep, program: []const u8, expected: []const u8) void { | 236 | pub fn checkComputeCompare( |
| 169 | const gpa = self.builder.allocator; | 237 | self: *CheckObjectStep, |
| 170 | var ca = ComputeEqAction{ | 238 | program: []const u8, |
| 171 | .expected = expected, | 239 | expected: ComputeCompareExpected, |
| 172 | .var_stack = std.ArrayList([]const u8).init(gpa), | 240 | ) void { |
| 173 | .op_stack = std.ArrayList(ComputeEqAction.Op).init(gpa), | | |
| 174 | }; | | |
| 175 | | | |
| 176 | var it = mem.tokenize(u8, program, " "); | | |
| 177 | while (it.next()) |next| { | | |
| 178 | if (mem.eql(u8, next, "+")) { | | |
| 179 | ca.op_stack.append(.add) catch unreachable; | | |
| 180 | } else { | | |
| 181 | ca.var_stack.append(self.builder.dupe(next)) catch unreachable; | | |
| 182 | } | | |
| 183 | } | | |
| 184 | | | |
| 185 | var new_check = Check.create(self.builder); | 241 | var new_check = Check.create(self.builder); |
| 186 | new_check.computeEq(ca); | 242 | new_check.computeCmp(program, expected); |
| 187 | self.checks.append(new_check) catch unreachable; | 243 | self.checks.append(new_check) catch unreachable; |
| 188 | } | 244 | } |
| 189 | | 245 | |
| ... | @@ -210,10 +266,10 @@ fn make(step: *Step) !void { | ... | @@ -210,10 +266,10 @@ fn make(step: *Step) !void { |
| 210 | for (self.checks.items) |chk| { | 266 | for (self.checks.items) |chk| { |
| 211 | var it = mem.tokenize(u8, output, "\r\n"); | 267 | var it = mem.tokenize(u8, output, "\r\n"); |
| 212 | for (chk.actions.items) |act| { | 268 | for (chk.actions.items) |act| { |
| 213 | switch (act) { | 269 | switch (act.tag) { |
| 214 | .match => |match_act| { | 270 | .match => { |
| 215 | while (it.next()) |line| { | 271 | while (it.next()) |line| { |
| 216 | if (try match_act.match(line, &vars)) break; | 272 | if (try act.match(line, &vars)) break; |
| 217 | } else { | 273 | } else { |
| 218 | std.debug.print( | 274 | std.debug.print( |
| 219 | \\ | 275 | \\ |
| ... | @@ -222,51 +278,33 @@ fn make(step: *Step) !void { | ... | @@ -222,51 +278,33 @@ fn make(step: *Step) !void { |
| 222 | \\========= But parsed file does not contain it: ======= | 278 | \\========= But parsed file does not contain it: ======= |
| 223 | \\{s} | 279 | \\{s} |
| 224 | \\ | 280 | \\ |
| 225 | , .{ match_act.needle, output }); | 281 | , .{ act.phrase, output }); |
| 226 | return error.TestFailed; | 282 | return error.TestFailed; |
| 227 | } | 283 | } |
| 228 | }, | 284 | }, |
| 229 | .compute_eq => |c_eq| { | 285 | .compute_cmp => { |
| 230 | var values = std.ArrayList(u64).init(gpa); | 286 | const res = act.computeCmp(gpa, vars) catch |err| switch (err) { |
| 231 | try values.ensureTotalCapacity(c_eq.var_stack.items.len); | 287 | error.UnknownVariable => { |
| 232 | for (c_eq.var_stack.items) |vv| { | | |
| 233 | const val = vars.get(vv) orelse { | | |
| 234 | std.debug.print( | 288 | std.debug.print( |
| 235 | \\ | | |
| 236 | \\========= Variable was not extracted: =========== | | |
| 237 | \\{s} | | |
| 238 | \\========= From parsed file: ===================== | 289 | \\========= From parsed file: ===================== |
| 239 | \\{s} | 290 | \\{s} |
| 240 | \\ | 291 | \\ |
| 241 | , .{ vv, output }); | 292 | , .{output}); |
| 242 | return error.TestFailed; | 293 | return error.TestFailed; |
| 243 | }; | 294 | }, |
| 244 | values.appendAssumeCapacity(val); | 295 | else => |e| return e, |
| 245 | } | 296 | }; |
| 246 | | 297 | if (!res) { |
| 247 | var op_i: usize = 1; | | |
| 248 | var reduced: u64 = values.items[0]; | | |
| 249 | for (c_eq.op_stack.items) |op| { | | |
| 250 | const other = values.items[op_i]; | | |
| 251 | switch (op) { | | |
| 252 | .add => { | | |
| 253 | reduced += other; | | |
| 254 | }, | | |
| 255 | } | | |
| 256 | } | | |
| 257 | | | |
| 258 | const expected = vars.get(c_eq.expected) orelse { | | |
| 259 | std.debug.print( | 298 | std.debug.print( |
| 260 | \\ | 299 | \\ |
| 261 | \\========= Variable was not extracted: =========== | 300 | \\========= Comparison failed for action: =========== |
| 262 | \\{s} | 301 | \\{s} {s} |
| 263 | \\========= From parsed file: ===================== | 302 | \\========= From parsed file: ======================= |
| 264 | \\{s} | 303 | \\{s} |
| 265 | \\ | 304 | \\ |
| 266 | , .{ c_eq.expected, output }); | 305 | , .{ act.phrase, act.expected.?, output }); |
| 267 | return error.TestFailed; | 306 | return error.TestFailed; |
| 268 | }; | 307 | } |
| 269 | try testing.expectEqual(reduced, expected); | | |
| 270 | }, | 308 | }, |
| 271 | } | 309 | } |
| 272 | } | 310 | } |
| ... | @@ -349,6 +387,23 @@ const MachODumper = struct { | ... | @@ -349,6 +387,23 @@ const MachODumper = struct { |
| 349 | seg.fileoff, | 387 | seg.fileoff, |
| 350 | seg.filesize, | 388 | seg.filesize, |
| 351 | }); | 389 | }); |
| | 390 | |
| | 391 | for (lc.segment.sections.items) |sect| { |
| | 392 | try writer.writeByte('\n'); |
| | 393 | try writer.print( |
| | 394 | \\sectname {s} |
| | 395 | \\addr {x} |
| | 396 | \\size {x} |
| | 397 | \\offset {x} |
| | 398 | \\align {x} |
| | 399 | , .{ |
| | 400 | sect.sectName(), |
| | 401 | sect.addr, |
| | 402 | sect.size, |
| | 403 | sect.offset, |
| | 404 | sect.@"align", |
| | 405 | }); |
| | 406 | } |
| 352 | }, | 407 | }, |
| 353 | | 408 | |
| 354 | .ID_DYLIB, | 409 | .ID_DYLIB, |