From 2cb6db22193acc802ff38f1cc4576ac1ee3f94fa Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Fri, 23 Dec 2022 13:55:58 +0100 Subject: [PATCH] link-tests: add macho strict validation test Support more operators when running simple arithmetic tests, and allow for int literals in the program spec. --- lib/std/build/CheckObjectStep.zig | 111 ++++++++++++++++++-- test/link.zig | 5 + test/link/macho/strict_validation/build.zig | 100 ++++++++++++++++++ test/link/macho/strict_validation/main.zig | 6 ++ 4 files changed, 213 insertions(+), 9 deletions(-) create mode 100644 test/link/macho/strict_validation/build.zig create mode 100644 test/link/macho/strict_validation/main.zig diff --git a/lib/std/build/CheckObjectStep.zig b/lib/std/build/CheckObjectStep.zig index 41a275c38f..455f53eeb7 100644 --- a/lib/std/build/CheckObjectStep.zig +++ b/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 => {}, } } diff --git a/test/link.zig b/test/link.zig index 5620ac95a0..ff18d65be5 100644 --- a/test/link.zig +++ b/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, diff --git a/test/link/macho/strict_validation/build.zig b/test/link/macho/strict_validation/build.zig new file mode 100644 index 0000000000..d5c00bffee --- /dev/null +++ b/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); +} diff --git a/test/link/macho/strict_validation/main.zig b/test/link/macho/strict_validation/main.zig new file mode 100644 index 0000000000..6510e13fd7 --- /dev/null +++ b/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"); +}