Commit 2cb6db2219

Jakub Konka <kubkon@jakubkonka.com>
2022-12-23 13:55:58
link-tests: add macho strict validation test
Support more operators when running simple arithmetic tests, and allow for int literals in the program spec.
1 parent 550ebcc
Changed files (4)
lib
test
link
macho
strict_validation
lib/std/build/CheckObjectStep.zig
@@ -126,22 +126,28 @@ const Action = struct {
     /// its reduced, computed value compares using `op` with the expected value, either
     /// a literal or another extracted variable.
     fn computeCmp(act: Action, gpa: Allocator, global_vars: anytype) !bool {
-        var op_stack = std.ArrayList(enum { add }).init(gpa);
+        var op_stack = std.ArrayList(enum { add, sub, mod }).init(gpa);
         var values = std.ArrayList(u64).init(gpa);
 
         var it = mem.tokenize(u8, act.phrase, " ");
         while (it.next()) |next| {
             if (mem.eql(u8, next, "+")) {
                 try op_stack.append(.add);
+            } else if (mem.eql(u8, next, "-")) {
+                try op_stack.append(.sub);
+            } else if (mem.eql(u8, next, "%")) {
+                try op_stack.append(.mod);
             } else {
-                const val = global_vars.get(next) orelse {
-                    std.debug.print(
-                        \\
-                        \\========= Variable was not extracted: ===========
-                        \\{s}
-                        \\
-                    , .{next});
-                    return error.UnknownVariable;
+                const val = std.fmt.parseInt(u64, next, 0) catch blk: {
+                    break :blk global_vars.get(next) orelse {
+                        std.debug.print(
+                            \\
+                            \\========= Variable was not extracted: ===========
+                            \\{s}
+                            \\
+                        , .{next});
+                        return error.UnknownVariable;
+                    };
                 };
                 try values.append(val);
             }
@@ -155,7 +161,14 @@ const Action = struct {
                 .add => {
                     reduced += other;
                 },
+                .sub => {
+                    reduced -= other;
+                },
+                .mod => {
+                    reduced %= other;
+                },
             }
+            op_i += 1;
         }
 
         const exp_value = switch (act.expected.?.value) {
@@ -577,6 +590,86 @@ const MachODumper = struct {
                 try writer.print("uuid {x}", .{std.fmt.fmtSliceHexLower(&uuid.uuid)});
             },
 
+            .DATA_IN_CODE,
+            .FUNCTION_STARTS,
+            .CODE_SIGNATURE,
+            => {
+                const llc = lc.cast(macho.linkedit_data_command).?;
+                try writer.writeByte('\n');
+                try writer.print(
+                    \\dataoff {x}
+                    \\datasize {x}
+                , .{ llc.dataoff, llc.datasize });
+            },
+
+            .DYLD_INFO_ONLY => {
+                const dlc = lc.cast(macho.dyld_info_command).?;
+                try writer.writeByte('\n');
+                try writer.print(
+                    \\rebaseoff {x}
+                    \\rebasesize {x}
+                    \\bindoff {x}
+                    \\bindsize {x}
+                    \\weakbindoff {x}
+                    \\weakbindsize {x}
+                    \\lazybindoff {x}
+                    \\lazybindsize {x}
+                    \\exportoff {x}
+                    \\exportsize {x}
+                , .{
+                    dlc.rebase_off,
+                    dlc.rebase_size,
+                    dlc.bind_off,
+                    dlc.bind_size,
+                    dlc.weak_bind_off,
+                    dlc.weak_bind_size,
+                    dlc.lazy_bind_off,
+                    dlc.lazy_bind_size,
+                    dlc.export_off,
+                    dlc.export_size,
+                });
+            },
+
+            .SYMTAB => {
+                const slc = lc.cast(macho.symtab_command).?;
+                try writer.writeByte('\n');
+                try writer.print(
+                    \\symoff {x}
+                    \\nsyms {x}
+                    \\stroff {x}
+                    \\strsize {x}
+                , .{
+                    slc.symoff,
+                    slc.nsyms,
+                    slc.stroff,
+                    slc.strsize,
+                });
+            },
+
+            .DYSYMTAB => {
+                const dlc = lc.cast(macho.dysymtab_command).?;
+                try writer.writeByte('\n');
+                try writer.print(
+                    \\ilocalsym {x}
+                    \\nlocalsym {x}
+                    \\iextdefsym {x}
+                    \\nextdefsym {x}
+                    \\iundefsym {x}
+                    \\nundefsym {x}
+                    \\indirectsymoff {x}
+                    \\nindirectsyms {x}
+                , .{
+                    dlc.ilocalsym,
+                    dlc.nlocalsym,
+                    dlc.iextdefsym,
+                    dlc.nextdefsym,
+                    dlc.iundefsym,
+                    dlc.nundefsym,
+                    dlc.indirectsymoff,
+                    dlc.nindirectsyms,
+                });
+            },
+
             else => {},
         }
     }
test/link/macho/strict_validation/build.zig
@@ -0,0 +1,100 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const Builder = std.build.Builder;
+const LibExeObjectStep = std.build.LibExeObjStep;
+
+pub fn build(b: *Builder) void {
+    const mode = b.standardReleaseOptions();
+    const target: std.zig.CrossTarget = .{ .os_tag = .macos };
+
+    const test_step = b.step("test", "Test");
+    test_step.dependOn(b.getInstallStep());
+
+    const exe = b.addExecutable("main", "main.zig");
+    exe.setBuildMode(mode);
+    exe.setTarget(target);
+    exe.linkLibC();
+
+    const check_exe = exe.checkObject(.macho);
+
+    check_exe.checkStart("cmd SEGMENT_64");
+    check_exe.checkNext("segname __LINKEDIT");
+    check_exe.checkNext("fileoff {fileoff}");
+    check_exe.checkNext("filesz {filesz}");
+
+    check_exe.checkStart("cmd DYLD_INFO_ONLY");
+    check_exe.checkNext("rebaseoff {rebaseoff}");
+    check_exe.checkNext("rebasesize {rebasesize}");
+    check_exe.checkNext("bindoff {bindoff}");
+    check_exe.checkNext("bindsize {bindsize}");
+    check_exe.checkNext("lazybindoff {lazybindoff}");
+    check_exe.checkNext("lazybindsize {lazybindsize}");
+    check_exe.checkNext("exportoff {exportoff}");
+    check_exe.checkNext("exportsize {exportsize}");
+
+    check_exe.checkStart("cmd SYMTAB");
+    check_exe.checkNext("symoff {symoff}");
+    check_exe.checkNext("stroff {stroff}");
+    check_exe.checkNext("strsize {strsize}");
+
+    check_exe.checkStart("cmd DYSYMTAB");
+    check_exe.checkNext("indirectsymoff {dysymoff}");
+
+    switch (builtin.cpu.arch) {
+        .aarch64 => {
+            check_exe.checkStart("cmd CODE_SIGNATURE");
+            check_exe.checkNext("dataoff {codesigoff}");
+            check_exe.checkNext("datasize {codesigsize}");
+        },
+        .x86_64 => {},
+        else => unreachable,
+    }
+
+    // Next check: DYLD_INFO_ONLY subsections are in order: rebase < bind < lazy < export
+    check_exe.checkComputeCompare("rebaseoff ", .{ .op = .lt, .value = .{ .variable = "bindoff" } });
+    check_exe.checkComputeCompare("bindoff", .{ .op = .lt, .value = .{ .variable = "lazybindoff" } });
+    check_exe.checkComputeCompare("lazybindoff", .{ .op = .lt, .value = .{ .variable = "exportoff" } });
+
+    // Next check: DYLD_INFO_ONLY subsections do not overlap
+    check_exe.checkComputeCompare("rebaseoff rebasesize +", .{ .op = .lte, .value = .{ .variable = "bindoff" } });
+    check_exe.checkComputeCompare("bindoff bindsize +", .{ .op = .lte, .value = .{ .variable = "lazybindoff" } });
+    check_exe.checkComputeCompare("lazybindoff lazybindsize +", .{ .op = .lte, .value = .{ .variable = "exportoff" } });
+
+    // Next check: we maintain order: symtab < dysymtab < strtab
+    check_exe.checkComputeCompare("symoff", .{ .op = .lt, .value = .{ .variable = "dysymoff" } });
+    check_exe.checkComputeCompare("dysymoff", .{ .op = .lt, .value = .{ .variable = "stroff" } });
+
+    // Next check: all LINKEDIT sections apart from CODE_SIGNATURE are 8-bytes aligned
+    check_exe.checkComputeCompare("rebaseoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
+    check_exe.checkComputeCompare("bindoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
+    check_exe.checkComputeCompare("lazybindoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
+    check_exe.checkComputeCompare("exportoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
+    check_exe.checkComputeCompare("symoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
+    check_exe.checkComputeCompare("stroff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
+    check_exe.checkComputeCompare("dysymoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
+
+    switch (builtin.cpu.arch) {
+        .aarch64 => {
+            // Next check: LINKEDIT segment does not extend beyond, or does not include, CODE_SIGNATURE data
+            check_exe.checkComputeCompare("fileoff filesz codesigoff codesigsize + - -", .{
+                .op = .eq,
+                .value = .{ .literal = 0 },
+            });
+
+            // Next check: CODE_SIGNATURE data offset is 16-bytes aligned
+            check_exe.checkComputeCompare("codesigoff 16 %", .{ .op = .eq, .value = .{ .literal = 0 } });
+        },
+        .x86_64 => {
+            // Next check: LINKEDIT segment does not extend beyond, or does not include, strtab data
+            check_exe.checkComputeCompare("fileoff filesz stroff strsize + - -", .{
+                .op = .eq,
+                .value = .{ .literal = 0 },
+            });
+        },
+        else => unreachable,
+    }
+
+    const run = check_exe.runAndCompare();
+    run.expectStdOutEqual("Hello!\n");
+    test_step.dependOn(&run.step);
+}
test/link/macho/strict_validation/main.zig
@@ -0,0 +1,6 @@
+const std = @import("std");
+
+pub fn main() !void {
+    const stdout = std.io.getStdOut().writer();
+    try stdout.writeAll("Hello!\n");
+}
test/link.zig
@@ -165,6 +165,11 @@ fn addMachOCases(cases: *tests.StandaloneContext) void {
         .requires_symlinks = true,
     });
 
+    cases.addBuildFile("test/link/macho/strict_validation/build.zig", .{
+        .build_modes = true,
+        .requires_symlinks = true,
+    });
+
     cases.addBuildFile("test/link/macho/tls/build.zig", .{
         .build_modes = true,
         .requires_symlinks = true,