From 38edef35bfcba5789ea50adc7c76dec504079812 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 28 May 2022 11:44:53 +0200 Subject: [PATCH 01/17] test: introduce link(er) tests - builds on standalone tests --- build.zig | 1 + lib/std/build/RunStep.zig | 1 + test/link.zig | 52 +++++++++++++++++++ test/link/bss/build.zig | 14 +++++ test/link/bss/main.zig | 13 +++++ .../common_symbols}/a.c | 0 .../common_symbols}/b.c | 0 .../common_symbols}/build.zig | 0 .../common_symbols}/c.c | 0 .../common_symbols}/main.zig | 0 .../common_symbols_alignment}/a.c | 0 .../common_symbols_alignment}/build.zig | 0 .../common_symbols_alignment}/main.zig | 0 test/link/dylib/a.c | 7 +++ test/link/dylib/build.zig | 29 +++++++++++ test/link/dylib/main.c | 9 ++++ .../frameworks}/build.zig | 18 +------ .../frameworks}/main.c | 0 .../interdependent_static_c_libs}/a.c | 0 .../interdependent_static_c_libs}/a.h | 0 .../interdependent_static_c_libs}/b.c | 0 .../interdependent_static_c_libs}/b.h | 0 .../interdependent_static_c_libs}/build.zig | 0 .../interdependent_static_c_libs}/main.zig | 0 test/{standalone => link}/objc/Foo.h | 0 test/{standalone => link}/objc/Foo.m | 0 test/{standalone => link}/objc/build.zig | 18 +------ test/{standalone => link}/objc/test.m | 0 test/{standalone => link}/objcpp/Foo.h | 0 test/{standalone => link}/objcpp/Foo.mm | 0 test/{standalone => link}/objcpp/build.zig | 20 ++----- test/{standalone => link}/objcpp/test.mm | 0 .../static_lib_as_system_lib}/a.c | 0 .../static_lib_as_system_lib}/a.h | 0 .../static_lib_as_system_lib}/build.zig | 0 .../static_lib_as_system_lib}/main.zig | 0 test/link/tls/a.c | 5 ++ .../tls}/build.zig | 2 + test/link/tls/main.zig | 15 ++++++ test/standalone.zig | 37 ++----------- test/standalone/link_import_tls_dylib/a.c | 1 - .../standalone/link_import_tls_dylib/main.zig | 7 --- test/tests.zig | 22 ++++++++ 43 files changed, 182 insertions(+), 89 deletions(-) create mode 100644 test/link.zig create mode 100644 test/link/bss/build.zig create mode 100644 test/link/bss/main.zig rename test/{standalone/link_common_symbols => link/common_symbols}/a.c (100%) rename test/{standalone/link_common_symbols => link/common_symbols}/b.c (100%) rename test/{standalone/link_common_symbols => link/common_symbols}/build.zig (100%) rename test/{standalone/link_common_symbols => link/common_symbols}/c.c (100%) rename test/{standalone/link_common_symbols => link/common_symbols}/main.zig (100%) rename test/{standalone/link_common_symbols_alignment => link/common_symbols_alignment}/a.c (100%) rename test/{standalone/link_common_symbols_alignment => link/common_symbols_alignment}/build.zig (100%) rename test/{standalone/link_common_symbols_alignment => link/common_symbols_alignment}/main.zig (100%) create mode 100644 test/link/dylib/a.c create mode 100644 test/link/dylib/build.zig create mode 100644 test/link/dylib/main.c rename test/{standalone/link_frameworks => link/frameworks}/build.zig (50%) rename test/{standalone/link_frameworks => link/frameworks}/main.c (100%) rename test/{standalone/link_interdependent_static_c_libs => link/interdependent_static_c_libs}/a.c (100%) rename test/{standalone/link_interdependent_static_c_libs => link/interdependent_static_c_libs}/a.h (100%) rename test/{standalone/link_interdependent_static_c_libs => link/interdependent_static_c_libs}/b.c (100%) rename test/{standalone/link_interdependent_static_c_libs => link/interdependent_static_c_libs}/b.h (100%) rename test/{standalone/link_interdependent_static_c_libs => link/interdependent_static_c_libs}/build.zig (100%) rename test/{standalone/link_interdependent_static_c_libs => link/interdependent_static_c_libs}/main.zig (100%) rename test/{standalone => link}/objc/Foo.h (100%) rename test/{standalone => link}/objc/Foo.m (100%) rename test/{standalone => link}/objc/build.zig (54%) rename test/{standalone => link}/objc/test.m (100%) rename test/{standalone => link}/objcpp/Foo.h (100%) rename test/{standalone => link}/objcpp/Foo.mm (100%) rename test/{standalone => link}/objcpp/build.zig (54%) rename test/{standalone => link}/objcpp/test.mm (100%) rename test/{standalone/link_static_lib_as_system_lib => link/static_lib_as_system_lib}/a.c (100%) rename test/{standalone/link_static_lib_as_system_lib => link/static_lib_as_system_lib}/a.h (100%) rename test/{standalone/link_static_lib_as_system_lib => link/static_lib_as_system_lib}/build.zig (100%) rename test/{standalone/link_static_lib_as_system_lib => link/static_lib_as_system_lib}/main.zig (100%) create mode 100644 test/link/tls/a.c rename test/{standalone/link_import_tls_dylib => link/tls}/build.zig (91%) create mode 100644 test/link/tls/main.zig delete mode 100644 test/standalone/link_import_tls_dylib/a.c delete mode 100644 test/standalone/link_import_tls_dylib/main.zig diff --git a/build.zig b/build.zig index fc97fc91bd..0ca2e0d7d7 100644 --- a/build.zig +++ b/build.zig @@ -489,6 +489,7 @@ pub fn build(b: *Builder) !void { toolchain_step.dependOn(tests.addCompareOutputTests(b, test_filter, modes)); toolchain_step.dependOn(tests.addStandaloneTests(b, test_filter, modes, skip_non_native, enable_macos_sdk, target)); + toolchain_step.dependOn(tests.addLinkTests(b, test_filter, modes, enable_macos_sdk)); toolchain_step.dependOn(tests.addStackTraceTests(b, test_filter, modes)); toolchain_step.dependOn(tests.addCliTests(b, test_filter, modes)); toolchain_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, modes)); diff --git a/lib/std/build/RunStep.zig b/lib/std/build/RunStep.zig index e8cf87a441..0b8c233bfa 100644 --- a/lib/std/build/RunStep.zig +++ b/lib/std/build/RunStep.zig @@ -149,6 +149,7 @@ fn make(step: *Step) !void { const cwd = if (self.cwd) |cwd| self.builder.pathFromRoot(cwd) else self.builder.build_root; var argv_list = ArrayList([]const u8).init(self.builder.allocator); + for (self.argv.items) |arg| { switch (arg) { .bytes => |bytes| try argv_list.append(bytes), diff --git a/test/link.zig b/test/link.zig new file mode 100644 index 0000000000..3c1b268d86 --- /dev/null +++ b/test/link.zig @@ -0,0 +1,52 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const tests = @import("tests.zig"); + +pub fn addCases(cases: *tests.StandaloneContext) void { + cases.addBuildFile("test/link/bss/build.zig", .{ + .build_modes = false, // we only guarantee zerofill for undefined in Debug + }); + + cases.addBuildFile("test/link/dylib/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/common_symbols/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/common_symbols_alignment/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/interdependent_static_c_libs/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/static_lib_as_system_lib/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/tls/build.zig", .{ + .build_modes = true, + }); + + if (builtin.os.tag == .macos) { + cases.addBuildFile("test/link/frameworks/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); + + // Try to build and run an Objective-C executable. + cases.addBuildFile("test/link/objc/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); + + // Try to build and run an Objective-C++ executable. + cases.addBuildFile("test/link/objcpp/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); + } +} diff --git a/test/link/bss/build.zig b/test/link/bss/build.zig new file mode 100644 index 0000000000..76e9bdb305 --- /dev/null +++ b/test/link/bss/build.zig @@ -0,0 +1,14 @@ +const Builder = @import("std").build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + const test_step = b.step("test", "Test"); + + const exe = b.addExecutable("bss", "main.zig"); + b.default_step.dependOn(&exe.step); + exe.setBuildMode(mode); + + const run = exe.run(); + run.expectStdOutEqual("0, 1, 0\n"); + test_step.dependOn(&run.step); +} diff --git a/test/link/bss/main.zig b/test/link/bss/main.zig new file mode 100644 index 0000000000..c901f0bb27 --- /dev/null +++ b/test/link/bss/main.zig @@ -0,0 +1,13 @@ +const std = @import("std"); + +// Stress test zerofill layout +var buffer: [0x1000000]u64 = undefined; + +pub fn main() anyerror!void { + buffer[0x10] = 1; + try std.io.getStdOut().writer().print("{d}, {d}, {d}\n", .{ + buffer[0], + buffer[0x10], + buffer[0x1000000 - 1], + }); +} diff --git a/test/standalone/link_common_symbols/a.c b/test/link/common_symbols/a.c similarity index 100% rename from test/standalone/link_common_symbols/a.c rename to test/link/common_symbols/a.c diff --git a/test/standalone/link_common_symbols/b.c b/test/link/common_symbols/b.c similarity index 100% rename from test/standalone/link_common_symbols/b.c rename to test/link/common_symbols/b.c diff --git a/test/standalone/link_common_symbols/build.zig b/test/link/common_symbols/build.zig similarity index 100% rename from test/standalone/link_common_symbols/build.zig rename to test/link/common_symbols/build.zig diff --git a/test/standalone/link_common_symbols/c.c b/test/link/common_symbols/c.c similarity index 100% rename from test/standalone/link_common_symbols/c.c rename to test/link/common_symbols/c.c diff --git a/test/standalone/link_common_symbols/main.zig b/test/link/common_symbols/main.zig similarity index 100% rename from test/standalone/link_common_symbols/main.zig rename to test/link/common_symbols/main.zig diff --git a/test/standalone/link_common_symbols_alignment/a.c b/test/link/common_symbols_alignment/a.c similarity index 100% rename from test/standalone/link_common_symbols_alignment/a.c rename to test/link/common_symbols_alignment/a.c diff --git a/test/standalone/link_common_symbols_alignment/build.zig b/test/link/common_symbols_alignment/build.zig similarity index 100% rename from test/standalone/link_common_symbols_alignment/build.zig rename to test/link/common_symbols_alignment/build.zig diff --git a/test/standalone/link_common_symbols_alignment/main.zig b/test/link/common_symbols_alignment/main.zig similarity index 100% rename from test/standalone/link_common_symbols_alignment/main.zig rename to test/link/common_symbols_alignment/main.zig diff --git a/test/link/dylib/a.c b/test/link/dylib/a.c new file mode 100644 index 0000000000..199b31e1a0 --- /dev/null +++ b/test/link/dylib/a.c @@ -0,0 +1,7 @@ +#include + +char world[] = "world"; + +char* hello() { + return "Hello"; +} diff --git a/test/link/dylib/build.zig b/test/link/dylib/build.zig new file mode 100644 index 0000000000..a9dee4aafb --- /dev/null +++ b/test/link/dylib/build.zig @@ -0,0 +1,29 @@ +const std = @import("std"); +const Builder = std.build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const test_step = b.step("test", "Test"); + + const dylib = b.addSharedLibrary("a", null, b.version(1, 0, 0)); + dylib.setBuildMode(mode); + dylib.addCSourceFile("a.c", &.{}); + dylib.linkLibC(); + dylib.install(); + + const exe = b.addExecutable("main", null); + exe.setBuildMode(mode); + exe.addCSourceFile("main.c", &.{}); + exe.linkSystemLibrary("a"); + exe.linkLibC(); + exe.addLibraryPath(b.pathFromRoot("zig-out/lib/")); + exe.addRPath(b.pathFromRoot("zig-out/lib")); + + const run = exe.run(); + run.cwd = b.pathFromRoot("."); + run.expectStdOutEqual("Hello world"); + + test_step.dependOn(b.getInstallStep()); + test_step.dependOn(&run.step); +} diff --git a/test/link/dylib/main.c b/test/link/dylib/main.c new file mode 100644 index 0000000000..be1647ddad --- /dev/null +++ b/test/link/dylib/main.c @@ -0,0 +1,9 @@ +#include + +char* hello(); +extern char world[]; + +int main() { + printf("%s %s", hello(), world); + return 0; +} diff --git a/test/standalone/link_frameworks/build.zig b/test/link/frameworks/build.zig similarity index 50% rename from test/standalone/link_frameworks/build.zig rename to test/link/frameworks/build.zig index 460e1675e0..5700422a41 100644 --- a/test/standalone/link_frameworks/build.zig +++ b/test/link/frameworks/build.zig @@ -1,19 +1,8 @@ const std = @import("std"); const Builder = std.build.Builder; -const CrossTarget = std.zig.CrossTarget; - -fn isRunnableTarget(t: CrossTarget) bool { - // TODO I think we might be able to run this on Linux via Darling. - // Add a check for that here, and return true if Darling is available. - if (t.isNative() and t.getOsTag() == .macos) - return true - else - return false; -} pub fn build(b: *Builder) void { const mode = b.standardReleaseOptions(); - const target = b.standardTargetOptions(.{}); const test_step = b.step("test", "Test the program"); @@ -21,14 +10,11 @@ pub fn build(b: *Builder) void { b.default_step.dependOn(&exe.step); exe.addCSourceFile("main.c", &[0][]const u8{}); exe.setBuildMode(mode); - exe.setTarget(target); exe.linkLibC(); // TODO when we figure out how to ship framework stubs for cross-compilation, // populate paths to the sysroot here. exe.linkFramework("Cocoa"); - if (isRunnableTarget(target)) { - const run_cmd = exe.run(); - test_step.dependOn(&run_cmd.step); - } + const run_cmd = exe.run(); + test_step.dependOn(&run_cmd.step); } diff --git a/test/standalone/link_frameworks/main.c b/test/link/frameworks/main.c similarity index 100% rename from test/standalone/link_frameworks/main.c rename to test/link/frameworks/main.c diff --git a/test/standalone/link_interdependent_static_c_libs/a.c b/test/link/interdependent_static_c_libs/a.c similarity index 100% rename from test/standalone/link_interdependent_static_c_libs/a.c rename to test/link/interdependent_static_c_libs/a.c diff --git a/test/standalone/link_interdependent_static_c_libs/a.h b/test/link/interdependent_static_c_libs/a.h similarity index 100% rename from test/standalone/link_interdependent_static_c_libs/a.h rename to test/link/interdependent_static_c_libs/a.h diff --git a/test/standalone/link_interdependent_static_c_libs/b.c b/test/link/interdependent_static_c_libs/b.c similarity index 100% rename from test/standalone/link_interdependent_static_c_libs/b.c rename to test/link/interdependent_static_c_libs/b.c diff --git a/test/standalone/link_interdependent_static_c_libs/b.h b/test/link/interdependent_static_c_libs/b.h similarity index 100% rename from test/standalone/link_interdependent_static_c_libs/b.h rename to test/link/interdependent_static_c_libs/b.h diff --git a/test/standalone/link_interdependent_static_c_libs/build.zig b/test/link/interdependent_static_c_libs/build.zig similarity index 100% rename from test/standalone/link_interdependent_static_c_libs/build.zig rename to test/link/interdependent_static_c_libs/build.zig diff --git a/test/standalone/link_interdependent_static_c_libs/main.zig b/test/link/interdependent_static_c_libs/main.zig similarity index 100% rename from test/standalone/link_interdependent_static_c_libs/main.zig rename to test/link/interdependent_static_c_libs/main.zig diff --git a/test/standalone/objc/Foo.h b/test/link/objc/Foo.h similarity index 100% rename from test/standalone/objc/Foo.h rename to test/link/objc/Foo.h diff --git a/test/standalone/objc/Foo.m b/test/link/objc/Foo.m similarity index 100% rename from test/standalone/objc/Foo.m rename to test/link/objc/Foo.m diff --git a/test/standalone/objc/build.zig b/test/link/objc/build.zig similarity index 54% rename from test/standalone/objc/build.zig rename to test/link/objc/build.zig index 1b3a90f90f..e41fd48e71 100644 --- a/test/standalone/objc/build.zig +++ b/test/link/objc/build.zig @@ -1,19 +1,8 @@ const std = @import("std"); const Builder = std.build.Builder; -const CrossTarget = std.zig.CrossTarget; - -fn isRunnableTarget(t: CrossTarget) bool { - // TODO I think we might be able to run this on Linux via Darling. - // Add a check for that here, and return true if Darling is available. - if (t.isNative() and t.getOsTag() == .macos) - return true - else - return false; -} pub fn build(b: *Builder) void { const mode = b.standardReleaseOptions(); - const target = b.standardTargetOptions(.{}); const test_step = b.step("test", "Test the program"); @@ -23,14 +12,11 @@ pub fn build(b: *Builder) void { exe.addCSourceFile("Foo.m", &[0][]const u8{}); exe.addCSourceFile("test.m", &[0][]const u8{}); exe.setBuildMode(mode); - exe.setTarget(target); exe.linkLibC(); // TODO when we figure out how to ship framework stubs for cross-compilation, // populate paths to the sysroot here. exe.linkFramework("Foundation"); - if (isRunnableTarget(target)) { - const run_cmd = exe.run(); - test_step.dependOn(&run_cmd.step); - } + const run_cmd = exe.run(); + test_step.dependOn(&run_cmd.step); } diff --git a/test/standalone/objc/test.m b/test/link/objc/test.m similarity index 100% rename from test/standalone/objc/test.m rename to test/link/objc/test.m diff --git a/test/standalone/objcpp/Foo.h b/test/link/objcpp/Foo.h similarity index 100% rename from test/standalone/objcpp/Foo.h rename to test/link/objcpp/Foo.h diff --git a/test/standalone/objcpp/Foo.mm b/test/link/objcpp/Foo.mm similarity index 100% rename from test/standalone/objcpp/Foo.mm rename to test/link/objcpp/Foo.mm diff --git a/test/standalone/objcpp/build.zig b/test/link/objcpp/build.zig similarity index 54% rename from test/standalone/objcpp/build.zig rename to test/link/objcpp/build.zig index 688592793a..767578e225 100644 --- a/test/standalone/objcpp/build.zig +++ b/test/link/objcpp/build.zig @@ -1,19 +1,8 @@ const std = @import("std"); const Builder = std.build.Builder; -const CrossTarget = std.zig.CrossTarget; - -fn isRunnableTarget(t: CrossTarget) bool { - // TODO I think we might be able to run this on Linux via Darling. - // Add a check for that here, and return true if Darling is available. - if (t.isNative() and t.getOsTag() == .macos) - return true - else - return false; -} pub fn build(b: *Builder) void { const mode = b.standardReleaseOptions(); - const target = b.standardTargetOptions(.{}); const test_step = b.step("test", "Test the program"); @@ -23,14 +12,13 @@ pub fn build(b: *Builder) void { exe.addCSourceFile("Foo.mm", &[0][]const u8{}); exe.addCSourceFile("test.mm", &[0][]const u8{}); exe.setBuildMode(mode); - exe.setTarget(target); exe.linkLibCpp(); // TODO when we figure out how to ship framework stubs for cross-compilation, // populate paths to the sysroot here. exe.linkFramework("Foundation"); - if (isRunnableTarget(target)) { - const run_cmd = exe.run(); - test_step.dependOn(&run_cmd.step); - } + const run_cmd = exe.run(); + run_cmd.expectStdOutEqual("Hello from C++ and Zig"); + + test_step.dependOn(&run_cmd.step); } diff --git a/test/standalone/objcpp/test.mm b/test/link/objcpp/test.mm similarity index 100% rename from test/standalone/objcpp/test.mm rename to test/link/objcpp/test.mm diff --git a/test/standalone/link_static_lib_as_system_lib/a.c b/test/link/static_lib_as_system_lib/a.c similarity index 100% rename from test/standalone/link_static_lib_as_system_lib/a.c rename to test/link/static_lib_as_system_lib/a.c diff --git a/test/standalone/link_static_lib_as_system_lib/a.h b/test/link/static_lib_as_system_lib/a.h similarity index 100% rename from test/standalone/link_static_lib_as_system_lib/a.h rename to test/link/static_lib_as_system_lib/a.h diff --git a/test/standalone/link_static_lib_as_system_lib/build.zig b/test/link/static_lib_as_system_lib/build.zig similarity index 100% rename from test/standalone/link_static_lib_as_system_lib/build.zig rename to test/link/static_lib_as_system_lib/build.zig diff --git a/test/standalone/link_static_lib_as_system_lib/main.zig b/test/link/static_lib_as_system_lib/main.zig similarity index 100% rename from test/standalone/link_static_lib_as_system_lib/main.zig rename to test/link/static_lib_as_system_lib/main.zig diff --git a/test/link/tls/a.c b/test/link/tls/a.c new file mode 100644 index 0000000000..8602d02419 --- /dev/null +++ b/test/link/tls/a.c @@ -0,0 +1,5 @@ +_Thread_local int a; + +int getA() { + return a; +} diff --git a/test/standalone/link_import_tls_dylib/build.zig b/test/link/tls/build.zig similarity index 91% rename from test/standalone/link_import_tls_dylib/build.zig rename to test/link/tls/build.zig index 332173fbb6..ebf15ca439 100644 --- a/test/standalone/link_import_tls_dylib/build.zig +++ b/test/link/tls/build.zig @@ -6,10 +6,12 @@ pub fn build(b: *Builder) void { const lib = b.addSharedLibrary("a", null, b.version(1, 0, 0)); lib.setBuildMode(mode); lib.addCSourceFile("a.c", &.{}); + lib.linkLibC(); const test_exe = b.addTest("main.zig"); test_exe.setBuildMode(mode); test_exe.linkLibrary(lib); + test_exe.linkLibC(); const test_step = b.step("test", "Test it"); test_step.dependOn(&test_exe.step); diff --git a/test/link/tls/main.zig b/test/link/tls/main.zig new file mode 100644 index 0000000000..ab01616e31 --- /dev/null +++ b/test/link/tls/main.zig @@ -0,0 +1,15 @@ +const std = @import("std"); + +extern threadlocal var a: i32; +extern fn getA() i32; + +fn getA2() i32 { + return a; +} + +test { + a = 2; + try std.testing.expect(getA() == 2); + try std.testing.expect(2 == getA2()); + try std.testing.expect(getA() == getA2()); +} diff --git a/test/standalone.zig b/test/standalone.zig index c34f9467d6..92d0ea4aa3 100644 --- a/test/standalone.zig +++ b/test/standalone.zig @@ -13,31 +13,12 @@ pub fn addCases(cases: *tests.StandaloneContext) void { cases.addBuildFile("test/standalone/main_pkg_path/build.zig", .{}); cases.addBuildFile("test/standalone/shared_library/build.zig", .{}); cases.addBuildFile("test/standalone/mix_o_files/build.zig", .{}); - if (builtin.os.tag == .macos) { - // Zig's macOS linker does not yet support LTO for LLVM IR files: - // https://github.com/ziglang/zig/issues/8680 - cases.addBuildFile("test/standalone/mix_c_files/build.zig", .{ - .build_modes = false, - .cross_targets = true, - }); - } else { - cases.addBuildFile("test/standalone/mix_c_files/build.zig", .{ - .build_modes = true, - .cross_targets = true, - }); - } + cases.addBuildFile("test/standalone/mix_c_files/build.zig", .{ + .build_modes = true, + .cross_targets = true, + }); cases.addBuildFile("test/standalone/global_linkage/build.zig", .{}); cases.addBuildFile("test/standalone/static_c_lib/build.zig", .{}); - cases.addBuildFile("test/standalone/link_interdependent_static_c_libs/build.zig", .{}); - cases.addBuildFile("test/standalone/link_static_lib_as_system_lib/build.zig", .{}); - cases.addBuildFile("test/standalone/link_common_symbols/build.zig", .{}); - cases.addBuildFile("test/standalone/link_frameworks/build.zig", .{ - .requires_macos_sdk = true, - }); - cases.addBuildFile("test/standalone/link_common_symbols_alignment/build.zig", .{}); - if (builtin.os.tag == .macos) { - cases.addBuildFile("test/standalone/link_import_tls_dylib/build.zig", .{}); - } cases.addBuildFile("test/standalone/issue_339/build.zig", .{}); cases.addBuildFile("test/standalone/issue_8550/build.zig", .{}); cases.addBuildFile("test/standalone/issue_794/build.zig", .{}); @@ -69,16 +50,6 @@ pub fn addCases(cases: *tests.StandaloneContext) void { if (builtin.os.tag == .linux) { cases.addBuildFile("test/standalone/pie/build.zig", .{}); } - // Try to build and run an Objective-C executable. - cases.addBuildFile("test/standalone/objc/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - }); - // Try to build and run an Objective-C++ executable. - cases.addBuildFile("test/standalone/objcpp/build.zig", .{ - .build_modes = true, - .requires_macos_sdk = true, - }); // Ensure the development tools are buildable. cases.add("tools/gen_spirv_spec.zig"); diff --git a/test/standalone/link_import_tls_dylib/a.c b/test/standalone/link_import_tls_dylib/a.c deleted file mode 100644 index 5c5aa5bae4..0000000000 --- a/test/standalone/link_import_tls_dylib/a.c +++ /dev/null @@ -1 +0,0 @@ -_Thread_local int a; diff --git a/test/standalone/link_import_tls_dylib/main.zig b/test/standalone/link_import_tls_dylib/main.zig deleted file mode 100644 index 354c6f545e..0000000000 --- a/test/standalone/link_import_tls_dylib/main.zig +++ /dev/null @@ -1,7 +0,0 @@ -const std = @import("std"); - -extern threadlocal var a: i32; - -test { - try std.testing.expect(a == 0); -} diff --git a/test/tests.zig b/test/tests.zig index 3666ef1028..8bc415d28b 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -21,6 +21,7 @@ const assemble_and_link = @import("assemble_and_link.zig"); const translate_c = @import("translate_c.zig"); const run_translated_c = @import("run_translated_c.zig"); const gen_h = @import("gen_h.zig"); +const link = @import("link.zig"); // Implementations pub const TranslateCContext = @import("src/translate_c.zig").TranslateCContext; @@ -479,6 +480,27 @@ pub fn addStandaloneTests( return cases.step; } +pub fn addLinkTests( + b: *build.Builder, + test_filter: ?[]const u8, + modes: []const Mode, + enable_macos_sdk: bool, +) *build.Step { + const cases = b.allocator.create(StandaloneContext) catch unreachable; + cases.* = StandaloneContext{ + .b = b, + .step = b.step("test-link", "Run the linker tests"), + .test_index = 0, + .test_filter = test_filter, + .modes = modes, + .skip_non_native = true, + .enable_macos_sdk = enable_macos_sdk, + .target = .{}, + }; + link.addCases(cases); + return cases.step; +} + pub fn addCliTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step { _ = test_filter; _ = modes; From 6e56a8df20395749b21b3857952dae1c227aacf1 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 20 Jun 2022 17:29:00 +0200 Subject: [PATCH 02/17] link-tests: CheckFileStep to do grep test on the binary --- test/link.zig | 8 ++++---- test/link/dylib/build.zig | 7 +++++++ test/tests.zig | 3 ++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/test/link.zig b/test/link.zig index 3c1b268d86..9026da65ea 100644 --- a/test/link.zig +++ b/test/link.zig @@ -7,10 +7,6 @@ pub fn addCases(cases: *tests.StandaloneContext) void { .build_modes = false, // we only guarantee zerofill for undefined in Debug }); - cases.addBuildFile("test/link/dylib/build.zig", .{ - .build_modes = true, - }); - cases.addBuildFile("test/link/common_symbols/build.zig", .{ .build_modes = true, }); @@ -32,6 +28,10 @@ pub fn addCases(cases: *tests.StandaloneContext) void { }); if (builtin.os.tag == .macos) { + cases.addBuildFile("test/link/dylib/build.zig", .{ + .build_modes = true, + }); + cases.addBuildFile("test/link/frameworks/build.zig", .{ .build_modes = true, .requires_macos_sdk = true, diff --git a/test/link/dylib/build.zig b/test/link/dylib/build.zig index a9dee4aafb..c14bf0b7d5 100644 --- a/test/link/dylib/build.zig +++ b/test/link/dylib/build.zig @@ -24,6 +24,13 @@ pub fn build(b: *Builder) void { run.cwd = b.pathFromRoot("."); run.expectStdOutEqual("Hello world"); + const exp_dylib = std.macho.createLoadDylibCommand(b.allocator, "@rpath/liba.dylib", 2, 0x10000, 0x10000) catch unreachable; + var buf = std.ArrayList(u8).init(b.allocator); + defer buf.deinit(); + exp_dylib.write(buf.writer()) catch unreachable; + const check_file = std.build.CheckFileStep.create(b, exe.getOutputSource(), &[_][]const u8{buf.items}); + test_step.dependOn(b.getInstallStep()); test_step.dependOn(&run.step); + test_step.dependOn(&check_file.step); } diff --git a/test/tests.zig b/test/tests.zig index 8bc415d28b..d1f319673c 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -995,7 +995,8 @@ pub const StandaloneContext = struct { } if (features.cross_targets and !self.target.isNative()) { - const target_arg = fmt.allocPrint(b.allocator, "-Dtarget={s}", .{self.target.zigTriple(b.allocator) catch unreachable}) catch unreachable; + const target_triple = self.target.zigTriple(b.allocator) catch unreachable; + const target_arg = fmt.allocPrint(b.allocator, "-Dtarget={s}", .{target_triple}) catch unreachable; zig_args.append(target_arg) catch unreachable; } From 2d09540a636ab6ef2ca5087f18d55bbc259cd652 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 20 Jun 2022 18:25:20 +0200 Subject: [PATCH 03/17] link-tests: test pagezero_size option for macho --- lib/std/build.zig | 7 +++++++ test/link.zig | 4 ++++ test/link/pagezero/build.zig | 34 ++++++++++++++++++++++++++++++++++ test/link/pagezero/main.c | 3 +++ 4 files changed, 48 insertions(+) create mode 100644 test/link/pagezero/build.zig create mode 100644 test/link/pagezero/main.c diff --git a/lib/std/build.zig b/lib/std/build.zig index ce527ff021..af135ea02a 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -1582,6 +1582,9 @@ pub const LibExeObjStep = struct { /// (Darwin) Path to entitlements file entitlements: ?[]const u8 = null, + /// (Darwin) Size of the pagezero segment. + pagezero_size: ?u64 = null, + /// Position Independent Code force_pic: ?bool = null, @@ -2638,6 +2641,10 @@ pub const LibExeObjStep = struct { if (self.entitlements) |entitlements| { try zig_args.appendSlice(&[_][]const u8{ "--entitlements", entitlements }); } + if (self.pagezero_size) |pagezero_size| { + const size = try std.fmt.allocPrint(builder.allocator, "{x}", .{pagezero_size}); + try zig_args.appendSlice(&[_][]const u8{ "-pagezero_size", size }); + } if (self.bundle_compiler_rt) |x| { if (x) { diff --git a/test/link.zig b/test/link.zig index 9026da65ea..bc69003e68 100644 --- a/test/link.zig +++ b/test/link.zig @@ -28,6 +28,10 @@ pub fn addCases(cases: *tests.StandaloneContext) void { }); if (builtin.os.tag == .macos) { + cases.addBuildFile("test/link/pagezero/build.zig", .{ + .build_modes = false, + }); + cases.addBuildFile("test/link/dylib/build.zig", .{ .build_modes = true, }); diff --git a/test/link/pagezero/build.zig b/test/link/pagezero/build.zig new file mode 100644 index 0000000000..65129008a4 --- /dev/null +++ b/test/link/pagezero/build.zig @@ -0,0 +1,34 @@ +const std = @import("std"); +const Builder = std.build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const test_step = b.step("test", "Test"); + + const exe = b.addExecutable("main", null); + exe.setBuildMode(mode); + exe.addCSourceFile("main.c", &.{}); + exe.linkLibC(); + exe.pagezero_size = 0x4000; + + var name: [16]u8 = undefined; + std.mem.set(u8, &name, 0); + std.mem.copy(u8, &name, "__PAGEZERO"); + const pagezero_seg = std.macho.segment_command_64{ + .cmdsize = @sizeOf(std.macho.segment_command_64), + .segname = name, + .vmaddr = 0, + .vmsize = 0x4000, + .fileoff = 0, + .filesize = 0, + .maxprot = 0, + .initprot = 0, + .nsects = 0, + .flags = 0, + }; + const check_file = std.build.CheckFileStep.create(b, exe.getOutputSource(), &[_][]const u8{std.mem.asBytes(&pagezero_seg)}); + + test_step.dependOn(b.getInstallStep()); + test_step.dependOn(&check_file.step); +} diff --git a/test/link/pagezero/main.c b/test/link/pagezero/main.c new file mode 100644 index 0000000000..ca68d24cc7 --- /dev/null +++ b/test/link/pagezero/main.c @@ -0,0 +1,3 @@ +int main(int argc, char* argv[]) { + return 0; +} From 5fbdfb3f3477bc8ac70b828671a7f980e8a8ad10 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 21 Jun 2022 15:44:22 +0200 Subject: [PATCH 04/17] link-tests: add CheckMachOStep CheckMachOStep specialises CheckFileStep into directed (surgical) MachO file fuzzy searches. This will be the building block for comprehensive MachO linker tests. --- lib/std/build.zig | 6 + lib/std/build/CheckMachOStep.zig | 210 +++++++++++++++++++++++++++++++ test/link/dylib/build.zig | 42 +++++-- test/link/pagezero/build.zig | 68 ++++++---- 4 files changed, 292 insertions(+), 34 deletions(-) create mode 100644 lib/std/build/CheckMachOStep.zig diff --git a/lib/std/build.zig b/lib/std/build.zig index af135ea02a..4582a0658d 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -24,6 +24,7 @@ pub const TranslateCStep = @import("build/TranslateCStep.zig"); pub const WriteFileStep = @import("build/WriteFileStep.zig"); pub const RunStep = @import("build/RunStep.zig"); pub const CheckFileStep = @import("build/CheckFileStep.zig"); +pub const CheckMachOStep = @import("build/CheckMachOStep.zig"); pub const InstallRawStep = @import("build/InstallRawStep.zig"); pub const OptionsStep = @import("build/OptionsStep.zig"); @@ -1864,6 +1865,10 @@ pub const LibExeObjStep = struct { return run_step; } + pub fn checkMachO(self: *LibExeObjStep) *CheckMachOStep { + return CheckMachOStep.create(self.builder, self.getOutputSource()); + } + pub fn setLinkerScriptPath(self: *LibExeObjStep, source: FileSource) void { self.linker_script = source.dupe(self.builder); source.addStepDependencies(&self.step); @@ -3450,6 +3455,7 @@ pub const Step = struct { write_file, run, check_file, + check_macho, install_raw, options, custom, diff --git a/lib/std/build/CheckMachOStep.zig b/lib/std/build/CheckMachOStep.zig new file mode 100644 index 0000000000..1ac12a9749 --- /dev/null +++ b/lib/std/build/CheckMachOStep.zig @@ -0,0 +1,210 @@ +const std = @import("../std.zig"); +const build = std.build; +const Step = build.Step; +const Builder = build.Builder; +const fs = std.fs; +const macho = std.macho; +const mem = std.mem; + +const CheckMachOStep = @This(); + +pub const base_id = .check_macho; + +step: Step, +builder: *Builder, +source: build.FileSource, +max_bytes: usize = 20 * 1024 * 1024, +lc_checks: std.ArrayList(LCCheck), + +const LCCheck = struct { + // common to most LCs + cmd: macho.LC, + name: ?[]const u8 = null, + // LC.SEGMENT_64 specific + index: ?usize = null, + vaddr: ?u64 = null, + memsz: ?u64 = null, + offset: ?u64 = null, + filesz: ?u64 = null, + // LC.LOAD_DYLIB specific + timestamp: ?u64 = null, + current_version: ?u32 = null, + compat_version: ?u32 = null, +}; + +pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep { + const gpa = builder.allocator; + const self = gpa.create(CheckMachOStep) catch unreachable; + self.* = CheckMachOStep{ + .builder = builder, + .step = Step.init(.check_file, "CheckMachO", gpa, make), + .source = source.dupe(builder), + .lc_checks = std.ArrayList(LCCheck).init(gpa), + }; + self.source.addStepDependencies(&self.step); + return self; +} + +pub fn checkLoadCommand(self: *CheckMachOStep, check: LCCheck) void { + self.lc_checks.append(.{ + .cmd = check.cmd, + .index = check.index, + .name = if (check.name) |name| self.builder.dupe(name) else null, + .vaddr = check.vaddr, + .memsz = check.memsz, + .offset = check.offset, + .filesz = check.filesz, + .timestamp = check.timestamp, + .current_version = check.current_version, + .compat_version = check.compat_version, + }) catch unreachable; +} + +fn make(step: *Step) !void { + const self = @fieldParentPtr(CheckMachOStep, "step", step); + + const gpa = self.builder.allocator; + const src_path = self.source.getPath(self.builder); + const contents = try fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes); + + // Parse the object file's header + var stream = std.io.fixedBufferStream(contents); + const reader = stream.reader(); + + const hdr = try reader.readStruct(macho.mach_header_64); + if (hdr.magic != macho.MH_MAGIC_64) { + return error.InvalidMagicNumber; + } + + var load_commands = std.ArrayList(macho.LoadCommand).init(gpa); + try load_commands.ensureTotalCapacity(hdr.ncmds); + + var i: u16 = 0; + while (i < hdr.ncmds) : (i += 1) { + var cmd = try macho.LoadCommand.read(gpa, reader); + load_commands.appendAssumeCapacity(cmd); + } + + outer: for (self.lc_checks.items) |ch| { + if (ch.index) |index| { + const lc = load_commands.items[index]; + try cmpLoadCommand(ch, lc); + } else { + for (load_commands.items) |lc| { + if (lc.cmd() == ch.cmd) { + try cmpLoadCommand(ch, lc); + continue :outer; + } + } else { + return err("LC not found", ch.cmd, ""); + } + } + } +} + +fn cmpLoadCommand(exp: LCCheck, given: macho.LoadCommand) error{TestFailed}!void { + if (exp.cmd != given.cmd()) { + return err("LC mismatch", exp.cmd, given.cmd()); + } + switch (exp.cmd) { + .SEGMENT_64 => { + const lc = given.segment.inner; + if (exp.name) |name| { + if (!mem.eql(u8, name, lc.segName())) { + return err("segment name mismatch", name, lc.segName()); + } + } + if (exp.vaddr) |vaddr| { + if (vaddr != lc.vmaddr) { + return err("segment VM address mismatch", vaddr, lc.vmaddr); + } + } + if (exp.memsz) |memsz| { + if (memsz != lc.vmsize) { + return err("segment VM size mismatch", memsz, lc.vmsize); + } + } + if (exp.offset) |offset| { + if (offset != lc.fileoff) { + return err("segment file offset mismatch", offset, lc.fileoff); + } + } + if (exp.filesz) |filesz| { + if (filesz != lc.filesize) { + return err("segment file size mismatch", filesz, lc.filesize); + } + } + }, + .ID_DYLIB, .LOAD_DYLIB => { + const lc = given.dylib; + if (exp.name) |name| { + if (!mem.eql(u8, name, mem.sliceTo(lc.data, 0))) { + return err("dylib path mismatch", name, mem.sliceTo(lc.data, 0)); + } + } + if (exp.timestamp) |ts| { + if (ts != lc.inner.dylib.timestamp) { + return err("timestamp mismatch", ts, lc.inner.dylib.timestamp); + } + } + if (exp.current_version) |cv| { + if (cv != lc.inner.dylib.current_version) { + return err("current version mismatch", cv, lc.inner.dylib.current_version); + } + } + if (exp.compat_version) |cv| { + if (cv != lc.inner.dylib.compatibility_version) { + return err("compatibility version mismatch", cv, lc.inner.dylib.compatibility_version); + } + } + }, + .RPATH => { + const lc = given.rpath; + if (exp.name) |name| { + if (!mem.eql(u8, name, mem.sliceTo(lc.data, 0))) { + return err("rpath path mismatch", name, mem.sliceTo(lc.data, 0)); + } + } + }, + else => @panic("TODO compare more load commands"), + } +} + +fn err(msg: []const u8, exp: anytype, giv: anytype) error{TestFailed} { + const fmt_specifier = if (comptime isString(@TypeOf(exp))) "{s}" else switch (@typeInfo(@TypeOf(exp))) { + .Int => "{x}", + .Float => "{d}", + else => "{any}", + }; + std.debug.print( + \\===================================== + \\{s} + \\ + \\======== Expected to find: ========== + \\ + ++ fmt_specifier ++ + \\ + \\======== But instead found: ========= + \\ + ++ fmt_specifier ++ + \\ + \\ + , .{ msg, exp, giv }); + return error.TestFailed; +} + +fn isString(comptime T: type) bool { + switch (@typeInfo(T)) { + .Array => return std.meta.Elem(T) == u8, + .Pointer => |pinfo| { + switch (pinfo.size) { + .Slice, .Many => return std.meta.Elem(T) == u8, + else => switch (@typeInfo(pinfo.child)) { + .Array => return isString(pinfo.child), + else => return false, + }, + } + }, + else => return false, + } +} diff --git a/test/link/dylib/build.zig b/test/link/dylib/build.zig index c14bf0b7d5..9878f9a66c 100644 --- a/test/link/dylib/build.zig +++ b/test/link/dylib/build.zig @@ -5,6 +5,7 @@ pub fn build(b: *Builder) void { const mode = b.standardReleaseOptions(); const test_step = b.step("test", "Test"); + test_step.dependOn(b.getInstallStep()); const dylib = b.addSharedLibrary("a", null, b.version(1, 0, 0)); dylib.setBuildMode(mode); @@ -12,6 +13,18 @@ pub fn build(b: *Builder) void { dylib.linkLibC(); dylib.install(); + { + const check_macho = dylib.checkMachO(); + check_macho.checkLoadCommand(.{ + .cmd = std.macho.LC.ID_DYLIB, + .name = "@rpath/liba.dylib", + .timestamp = 2, + .current_version = 0x10000, + .compat_version = 0x10000, + }); + test_step.dependOn(&check_macho.step); + } + const exe = b.addExecutable("main", null); exe.setBuildMode(mode); exe.addCSourceFile("main.c", &.{}); @@ -20,17 +33,28 @@ pub fn build(b: *Builder) void { exe.addLibraryPath(b.pathFromRoot("zig-out/lib/")); exe.addRPath(b.pathFromRoot("zig-out/lib")); + { + const check_macho = exe.checkMachO(); + check_macho.checkLoadCommand(.{ + .cmd = std.macho.LC.LOAD_DYLIB, + .name = "@rpath/liba.dylib", + .timestamp = 2, + .current_version = 0x10000, + .compat_version = 0x10000, + }); + test_step.dependOn(&check_macho.step); + } + { + const check_macho = exe.checkMachO(); + check_macho.checkLoadCommand(.{ + .cmd = std.macho.LC.RPATH, + .name = b.pathFromRoot("zig-out/lib"), + }); + test_step.dependOn(&check_macho.step); + } + const run = exe.run(); run.cwd = b.pathFromRoot("."); run.expectStdOutEqual("Hello world"); - - const exp_dylib = std.macho.createLoadDylibCommand(b.allocator, "@rpath/liba.dylib", 2, 0x10000, 0x10000) catch unreachable; - var buf = std.ArrayList(u8).init(b.allocator); - defer buf.deinit(); - exp_dylib.write(buf.writer()) catch unreachable; - const check_file = std.build.CheckFileStep.create(b, exe.getOutputSource(), &[_][]const u8{buf.items}); - - test_step.dependOn(b.getInstallStep()); test_step.dependOn(&run.step); - test_step.dependOn(&check_file.step); } diff --git a/test/link/pagezero/build.zig b/test/link/pagezero/build.zig index 65129008a4..71068b4480 100644 --- a/test/link/pagezero/build.zig +++ b/test/link/pagezero/build.zig @@ -5,30 +5,48 @@ pub fn build(b: *Builder) void { const mode = b.standardReleaseOptions(); const test_step = b.step("test", "Test"); - - const exe = b.addExecutable("main", null); - exe.setBuildMode(mode); - exe.addCSourceFile("main.c", &.{}); - exe.linkLibC(); - exe.pagezero_size = 0x4000; - - var name: [16]u8 = undefined; - std.mem.set(u8, &name, 0); - std.mem.copy(u8, &name, "__PAGEZERO"); - const pagezero_seg = std.macho.segment_command_64{ - .cmdsize = @sizeOf(std.macho.segment_command_64), - .segname = name, - .vmaddr = 0, - .vmsize = 0x4000, - .fileoff = 0, - .filesize = 0, - .maxprot = 0, - .initprot = 0, - .nsects = 0, - .flags = 0, - }; - const check_file = std.build.CheckFileStep.create(b, exe.getOutputSource(), &[_][]const u8{std.mem.asBytes(&pagezero_seg)}); - test_step.dependOn(b.getInstallStep()); - test_step.dependOn(&check_file.step); + + { + const exe = b.addExecutable("pagezero", null); + exe.setBuildMode(mode); + exe.addCSourceFile("main.c", &.{}); + exe.linkLibC(); + exe.pagezero_size = 0x4000; + + const check_macho = exe.checkMachO(); + check_macho.checkLoadCommand(.{ + .cmd = std.macho.LC.SEGMENT_64, + .index = 0, + .name = "__PAGEZERO", + .vaddr = 0, + .memsz = 0x4000, + }); + check_macho.checkLoadCommand(.{ + .cmd = std.macho.LC.SEGMENT_64, + .index = 1, + .name = "__TEXT", + .vaddr = 0x4000, + }); + + test_step.dependOn(&check_macho.step); + } + + { + const exe = b.addExecutable("no_pagezero", null); + exe.setBuildMode(mode); + exe.addCSourceFile("main.c", &.{}); + exe.linkLibC(); + exe.pagezero_size = 0; + + const check_macho = exe.checkMachO(); + check_macho.checkLoadCommand(.{ + .cmd = std.macho.LC.SEGMENT_64, + .index = 0, + .name = "__TEXT", + .vaddr = 0, + }); + + test_step.dependOn(&check_macho.step); + } } From 937464f398241358c7d3e534b179dfbdfdf2ffd9 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 21 Jun 2022 22:19:55 +0200 Subject: [PATCH 05/17] link-tests: dump metadata to string and grep results This approach is more inline with what LLVM/LLD does for testing of their output, and seems to be more generic and easier to extend than implementing a lot of repetitive and nontrivial comparison logic when working directly on structures. --- lib/std/build/CheckMachOStep.zig | 252 +++++++++++++------------------ test/link/dylib/build.zig | 49 +++--- test/link/pagezero/build.zig | 37 ++--- 3 files changed, 139 insertions(+), 199 deletions(-) diff --git a/lib/std/build/CheckMachOStep.zig b/lib/std/build/CheckMachOStep.zig index 1ac12a9749..b58cfa6e46 100644 --- a/lib/std/build/CheckMachOStep.zig +++ b/lib/std/build/CheckMachOStep.zig @@ -1,36 +1,23 @@ const std = @import("../std.zig"); +const assert = std.debug.assert; const build = std.build; -const Step = build.Step; -const Builder = build.Builder; const fs = std.fs; const macho = std.macho; const mem = std.mem; const CheckMachOStep = @This(); +const Allocator = mem.Allocator; +const Builder = build.Builder; +const Step = build.Step; + pub const base_id = .check_macho; step: Step, builder: *Builder, source: build.FileSource, max_bytes: usize = 20 * 1024 * 1024, -lc_checks: std.ArrayList(LCCheck), - -const LCCheck = struct { - // common to most LCs - cmd: macho.LC, - name: ?[]const u8 = null, - // LC.SEGMENT_64 specific - index: ?usize = null, - vaddr: ?u64 = null, - memsz: ?u64 = null, - offset: ?u64 = null, - filesz: ?u64 = null, - // LC.LOAD_DYLIB specific - timestamp: ?u64 = null, - current_version: ?u32 = null, - compat_version: ?u32 = null, -}; +checks: std.ArrayList(Check), pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep { const gpa = builder.allocator; @@ -39,25 +26,38 @@ pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep { .builder = builder, .step = Step.init(.check_file, "CheckMachO", gpa, make), .source = source.dupe(builder), - .lc_checks = std.ArrayList(LCCheck).init(gpa), + .checks = std.ArrayList(Check).init(gpa), }; self.source.addStepDependencies(&self.step); return self; } -pub fn checkLoadCommand(self: *CheckMachOStep, check: LCCheck) void { - self.lc_checks.append(.{ - .cmd = check.cmd, - .index = check.index, - .name = if (check.name) |name| self.builder.dupe(name) else null, - .vaddr = check.vaddr, - .memsz = check.memsz, - .offset = check.offset, - .filesz = check.filesz, - .timestamp = check.timestamp, - .current_version = check.current_version, - .compat_version = check.compat_version, - }) catch unreachable; +const Check = struct { + builder: *Builder, + phrases: std.ArrayList([]const u8), + + fn create(b: *Builder) Check { + return .{ + .builder = b, + .phrases = std.ArrayList([]const u8).init(b.allocator), + }; + } + + fn addPhrase(self: *Check, phrase: []const u8) void { + self.phrases.append(self.builder.dupe(phrase)) catch unreachable; + } +}; + +pub fn check(self: *CheckMachOStep, phrase: []const u8) void { + var new_check = Check.create(self.builder); + new_check.addPhrase(phrase); + self.checks.append(new_check) catch unreachable; +} + +pub fn checkNext(self: *CheckMachOStep, phrase: []const u8) void { + assert(self.checks.items.len > 0); + const last = &self.checks.items[self.checks.items.len - 1]; + last.addPhrase(phrase); } fn make(step: *Step) !void { @@ -76,135 +76,95 @@ fn make(step: *Step) !void { return error.InvalidMagicNumber; } - var load_commands = std.ArrayList(macho.LoadCommand).init(gpa); - try load_commands.ensureTotalCapacity(hdr.ncmds); + var metadata = std.ArrayList(u8).init(gpa); + const writer = metadata.writer(); var i: u16 = 0; while (i < hdr.ncmds) : (i += 1) { var cmd = try macho.LoadCommand.read(gpa, reader); - load_commands.appendAssumeCapacity(cmd); + try dumpLoadCommand(cmd, i, writer); + try writer.writeByte('\n'); } - outer: for (self.lc_checks.items) |ch| { - if (ch.index) |index| { - const lc = load_commands.items[index]; - try cmpLoadCommand(ch, lc); - } else { - for (load_commands.items) |lc| { - if (lc.cmd() == ch.cmd) { - try cmpLoadCommand(ch, lc); - continue :outer; + for (self.checks.items) |chk| { + const first_phrase = chk.phrases.items[0]; + + if (mem.indexOf(u8, metadata.items, first_phrase)) |index| { + // TODO backtrack to track current scope + var it = std.mem.tokenize(u8, metadata.items[index..], "\r\n"); + + outer: for (chk.phrases.items[1..]) |next_phrase| { + while (it.next()) |line| { + if (mem.eql(u8, line, next_phrase)) { + std.debug.print("{s} == {s}\n", .{ line, next_phrase }); + continue :outer; + } + std.debug.print("{s} != {s}\n", .{ line, next_phrase }); + } else { + return error.TestFailed; } - } else { - return err("LC not found", ch.cmd, ""); } + } else { + return error.TestFailed; } } } -fn cmpLoadCommand(exp: LCCheck, given: macho.LoadCommand) error{TestFailed}!void { - if (exp.cmd != given.cmd()) { - return err("LC mismatch", exp.cmd, given.cmd()); - } - switch (exp.cmd) { +fn dumpLoadCommand(lc: macho.LoadCommand, index: u16, writer: anytype) !void { + // print header first + try writer.print( + \\LC {d} + \\cmd {s} + \\cmdsize {d} + , .{ index, @tagName(lc.cmd()), lc.cmdsize() }); + + switch (lc.cmd()) { .SEGMENT_64 => { - const lc = given.segment.inner; - if (exp.name) |name| { - if (!mem.eql(u8, name, lc.segName())) { - return err("segment name mismatch", name, lc.segName()); - } - } - if (exp.vaddr) |vaddr| { - if (vaddr != lc.vmaddr) { - return err("segment VM address mismatch", vaddr, lc.vmaddr); - } - } - if (exp.memsz) |memsz| { - if (memsz != lc.vmsize) { - return err("segment VM size mismatch", memsz, lc.vmsize); - } - } - if (exp.offset) |offset| { - if (offset != lc.fileoff) { - return err("segment file offset mismatch", offset, lc.fileoff); - } - } - if (exp.filesz) |filesz| { - if (filesz != lc.filesize) { - return err("segment file size mismatch", filesz, lc.filesize); - } - } + // TODO dump section headers + const seg = lc.segment.inner; + try writer.writeByte('\n'); + try writer.print( + \\segname {s} + \\vmaddr {x} + \\vmsize {x} + \\fileoff {x} + \\filesz {x} + , .{ + seg.segName(), + seg.vmaddr, + seg.vmsize, + seg.fileoff, + seg.filesize, + }); }, - .ID_DYLIB, .LOAD_DYLIB => { - const lc = given.dylib; - if (exp.name) |name| { - if (!mem.eql(u8, name, mem.sliceTo(lc.data, 0))) { - return err("dylib path mismatch", name, mem.sliceTo(lc.data, 0)); - } - } - if (exp.timestamp) |ts| { - if (ts != lc.inner.dylib.timestamp) { - return err("timestamp mismatch", ts, lc.inner.dylib.timestamp); - } - } - if (exp.current_version) |cv| { - if (cv != lc.inner.dylib.current_version) { - return err("current version mismatch", cv, lc.inner.dylib.current_version); - } - } - if (exp.compat_version) |cv| { - if (cv != lc.inner.dylib.compatibility_version) { - return err("compatibility version mismatch", cv, lc.inner.dylib.compatibility_version); - } - } + + .ID_DYLIB, + .LOAD_DYLIB, + => { + const dylib = lc.dylib.inner.dylib; + try writer.writeByte('\n'); + try writer.print( + \\path {s} + \\timestamp {d} + \\current version {x} + \\compatibility version {x} + , .{ + mem.sliceTo(lc.dylib.data, 0), + dylib.timestamp, + dylib.current_version, + dylib.compatibility_version, + }); }, + .RPATH => { - const lc = given.rpath; - if (exp.name) |name| { - if (!mem.eql(u8, name, mem.sliceTo(lc.data, 0))) { - return err("rpath path mismatch", name, mem.sliceTo(lc.data, 0)); - } - } + try writer.writeByte('\n'); + try writer.print( + \\path {s} + , .{ + mem.sliceTo(lc.rpath.data, 0), + }); }, - else => @panic("TODO compare more load commands"), - } -} - -fn err(msg: []const u8, exp: anytype, giv: anytype) error{TestFailed} { - const fmt_specifier = if (comptime isString(@TypeOf(exp))) "{s}" else switch (@typeInfo(@TypeOf(exp))) { - .Int => "{x}", - .Float => "{d}", - else => "{any}", - }; - std.debug.print( - \\===================================== - \\{s} - \\ - \\======== Expected to find: ========== - \\ - ++ fmt_specifier ++ - \\ - \\======== But instead found: ========= - \\ - ++ fmt_specifier ++ - \\ - \\ - , .{ msg, exp, giv }); - return error.TestFailed; -} - -fn isString(comptime T: type) bool { - switch (@typeInfo(T)) { - .Array => return std.meta.Elem(T) == u8, - .Pointer => |pinfo| { - switch (pinfo.size) { - .Slice, .Many => return std.meta.Elem(T) == u8, - else => switch (@typeInfo(pinfo.child)) { - .Array => return isString(pinfo.child), - else => return false, - }, - } - }, - else => return false, + + else => {}, } } diff --git a/test/link/dylib/build.zig b/test/link/dylib/build.zig index 9878f9a66c..4f41e204ec 100644 --- a/test/link/dylib/build.zig +++ b/test/link/dylib/build.zig @@ -13,17 +13,14 @@ pub fn build(b: *Builder) void { dylib.linkLibC(); dylib.install(); - { - const check_macho = dylib.checkMachO(); - check_macho.checkLoadCommand(.{ - .cmd = std.macho.LC.ID_DYLIB, - .name = "@rpath/liba.dylib", - .timestamp = 2, - .current_version = 0x10000, - .compat_version = 0x10000, - }); - test_step.dependOn(&check_macho.step); - } + const check_dylib = dylib.checkMachO(); + check_dylib.check("cmd ID_DYLIB"); + check_dylib.checkNext("path @rpath/liba.dylib"); + check_dylib.checkNext("timestamp 2"); + check_dylib.checkNext("current version 10000"); + check_dylib.checkNext("compatibility version 10000"); + + test_step.dependOn(&check_dylib.step); const exe = b.addExecutable("main", null); exe.setBuildMode(mode); @@ -33,25 +30,17 @@ pub fn build(b: *Builder) void { exe.addLibraryPath(b.pathFromRoot("zig-out/lib/")); exe.addRPath(b.pathFromRoot("zig-out/lib")); - { - const check_macho = exe.checkMachO(); - check_macho.checkLoadCommand(.{ - .cmd = std.macho.LC.LOAD_DYLIB, - .name = "@rpath/liba.dylib", - .timestamp = 2, - .current_version = 0x10000, - .compat_version = 0x10000, - }); - test_step.dependOn(&check_macho.step); - } - { - const check_macho = exe.checkMachO(); - check_macho.checkLoadCommand(.{ - .cmd = std.macho.LC.RPATH, - .name = b.pathFromRoot("zig-out/lib"), - }); - test_step.dependOn(&check_macho.step); - } + const check_exe = exe.checkMachO(); + check_exe.check("cmd LOAD_DYLIB"); + check_exe.checkNext("path @rpath/liba.dylib"); + check_exe.checkNext("timestamp 2"); + check_exe.checkNext("current version 10000"); + check_exe.checkNext("compatibility version 10000"); + + check_exe.check("cmd RPATH"); + check_exe.checkNext(std.fmt.allocPrint(b.allocator, "path {s}", .{b.pathFromRoot("zig-out/lib")}) catch unreachable); + + test_step.dependOn(&check_exe.step); const run = exe.run(); run.cwd = b.pathFromRoot("."); diff --git a/test/link/pagezero/build.zig b/test/link/pagezero/build.zig index 71068b4480..05bc4052fe 100644 --- a/test/link/pagezero/build.zig +++ b/test/link/pagezero/build.zig @@ -14,22 +14,16 @@ pub fn build(b: *Builder) void { exe.linkLibC(); exe.pagezero_size = 0x4000; - const check_macho = exe.checkMachO(); - check_macho.checkLoadCommand(.{ - .cmd = std.macho.LC.SEGMENT_64, - .index = 0, - .name = "__PAGEZERO", - .vaddr = 0, - .memsz = 0x4000, - }); - check_macho.checkLoadCommand(.{ - .cmd = std.macho.LC.SEGMENT_64, - .index = 1, - .name = "__TEXT", - .vaddr = 0x4000, - }); + const check = exe.checkMachO(); + check.check("LC 0"); + check.checkNext("segname __PAGEZERO"); + check.checkNext("vmaddr 0"); + check.checkNext("vmsize 4000"); - test_step.dependOn(&check_macho.step); + check.check("segname __TEXT"); + check.checkNext("vmaddr 4000"); + + test_step.dependOn(&check.step); } { @@ -39,14 +33,11 @@ pub fn build(b: *Builder) void { exe.linkLibC(); exe.pagezero_size = 0; - const check_macho = exe.checkMachO(); - check_macho.checkLoadCommand(.{ - .cmd = std.macho.LC.SEGMENT_64, - .index = 0, - .name = "__TEXT", - .vaddr = 0, - }); + const check = exe.checkMachO(); + check.check("LC 0"); + check.checkNext("segname __TEXT"); + check.checkNext("vmaddr 0"); - test_step.dependOn(&check_macho.step); + test_step.dependOn(&check.step); } } From 3bb4d65b2f8d7cbaf1586b85909c7e45f6b5eec2 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 21 Jun 2022 23:01:06 +0200 Subject: [PATCH 06/17] link-tests: move macho tests to subfolder Handle `-e` option in MachO linker allowing the user to set a custom entrypoint address. --- lib/std/build/CheckMachOStep.zig | 8 +++++++ src/link/MachO.zig | 12 +++++++---- test/link.zig | 24 ++++++++++++++------- test/link/{ => macho}/dylib/a.c | 0 test/link/{ => macho}/dylib/build.zig | 0 test/link/{ => macho}/dylib/main.c | 0 test/link/macho/entry/build.zig | 25 ++++++++++++++++++++++ test/link/macho/entry/main.c | 6 ++++++ test/link/{ => macho}/frameworks/build.zig | 0 test/link/{ => macho}/frameworks/main.c | 0 test/link/{ => macho}/objc/Foo.h | 0 test/link/{ => macho}/objc/Foo.m | 0 test/link/{ => macho}/objc/build.zig | 0 test/link/{ => macho}/objc/test.m | 0 test/link/{ => macho}/objcpp/Foo.h | 0 test/link/{ => macho}/objcpp/Foo.mm | 0 test/link/{ => macho}/objcpp/build.zig | 0 test/link/{ => macho}/objcpp/test.mm | 0 test/link/{ => macho}/pagezero/build.zig | 0 test/link/{ => macho}/pagezero/main.c | 0 test/link/macho/stack_size/build.zig | 24 +++++++++++++++++++++ test/link/macho/stack_size/main.c | 3 +++ 22 files changed, 90 insertions(+), 12 deletions(-) rename test/link/{ => macho}/dylib/a.c (100%) rename test/link/{ => macho}/dylib/build.zig (100%) rename test/link/{ => macho}/dylib/main.c (100%) create mode 100644 test/link/macho/entry/build.zig create mode 100644 test/link/macho/entry/main.c rename test/link/{ => macho}/frameworks/build.zig (100%) rename test/link/{ => macho}/frameworks/main.c (100%) rename test/link/{ => macho}/objc/Foo.h (100%) rename test/link/{ => macho}/objc/Foo.m (100%) rename test/link/{ => macho}/objc/build.zig (100%) rename test/link/{ => macho}/objc/test.m (100%) rename test/link/{ => macho}/objcpp/Foo.h (100%) rename test/link/{ => macho}/objcpp/Foo.mm (100%) rename test/link/{ => macho}/objcpp/build.zig (100%) rename test/link/{ => macho}/objcpp/test.mm (100%) rename test/link/{ => macho}/pagezero/build.zig (100%) rename test/link/{ => macho}/pagezero/main.c (100%) create mode 100644 test/link/macho/stack_size/build.zig create mode 100644 test/link/macho/stack_size/main.c diff --git a/lib/std/build/CheckMachOStep.zig b/lib/std/build/CheckMachOStep.zig index b58cfa6e46..06e79081f5 100644 --- a/lib/std/build/CheckMachOStep.zig +++ b/lib/std/build/CheckMachOStep.zig @@ -156,6 +156,14 @@ fn dumpLoadCommand(lc: macho.LoadCommand, index: u16, writer: anytype) !void { }); }, + .MAIN => { + try writer.writeByte('\n'); + try writer.print( + \\entryoff {x} + \\stacksize {x} + , .{ lc.main.entryoff, lc.main.stacksize }); + }, + .RPATH => { try writer.writeByte('\n'); try writer.print( diff --git a/src/link/MachO.zig b/src/link/MachO.zig index aea44d9357..af84ce8846 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -934,6 +934,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{pagezero_size})); } + if (self.base.options.entry) |entry| { + try argv.append("-e"); + try argv.append(entry); + } + try argv.appendSlice(positionals.items); try argv.append("-o"); @@ -3371,13 +3376,12 @@ fn addCodeSignatureLC(self: *MachO) !void { fn setEntryPoint(self: *MachO) !void { if (self.base.options.output_mode != .Exe) return; - // TODO we should respect the -entry flag passed in by the user to set a custom - // entrypoint. For now, assume default of `_main`. const seg = self.load_commands.items[self.text_segment_cmd_index.?].segment; - const n_strx = self.strtab_dir.getKeyAdapted(@as([]const u8, "_main"), StringIndexAdapter{ + const entry_name = self.base.options.entry orelse "_main"; + const n_strx = self.strtab_dir.getKeyAdapted(entry_name, StringIndexAdapter{ .bytes = &self.strtab, }) orelse { - log.err("'_main' export not found", .{}); + log.err("entrypoint '{s}' not found", .{entry_name}); return error.MissingMainEntrypoint; }; const resolv = self.symbol_resolver.get(n_strx) orelse unreachable; diff --git a/test/link.zig b/test/link.zig index bc69003e68..e3dcfa1ec6 100644 --- a/test/link.zig +++ b/test/link.zig @@ -28,29 +28,37 @@ pub fn addCases(cases: *tests.StandaloneContext) void { }); if (builtin.os.tag == .macos) { - cases.addBuildFile("test/link/pagezero/build.zig", .{ - .build_modes = false, - }); - - cases.addBuildFile("test/link/dylib/build.zig", .{ + cases.addBuildFile("test/link/entry/build.zig", .{ .build_modes = true, }); - cases.addBuildFile("test/link/frameworks/build.zig", .{ + cases.addBuildFile("test/link/macho/pagezero/build.zig", .{ + .build_modes = false, + }); + + cases.addBuildFile("test/link/macho/dylib/build.zig", .{ + .build_modes = true, + }); + + cases.addBuildFile("test/link/macho/frameworks/build.zig", .{ .build_modes = true, .requires_macos_sdk = true, }); // Try to build and run an Objective-C executable. - cases.addBuildFile("test/link/objc/build.zig", .{ + cases.addBuildFile("test/link/macho/objc/build.zig", .{ .build_modes = true, .requires_macos_sdk = true, }); // Try to build and run an Objective-C++ executable. - cases.addBuildFile("test/link/objcpp/build.zig", .{ + cases.addBuildFile("test/link/macho/objcpp/build.zig", .{ .build_modes = true, .requires_macos_sdk = true, }); + + cases.addBuildFile("test/link/macho/stack_size/build.zig", .{ + .build_modes = true, + }); } } diff --git a/test/link/dylib/a.c b/test/link/macho/dylib/a.c similarity index 100% rename from test/link/dylib/a.c rename to test/link/macho/dylib/a.c diff --git a/test/link/dylib/build.zig b/test/link/macho/dylib/build.zig similarity index 100% rename from test/link/dylib/build.zig rename to test/link/macho/dylib/build.zig diff --git a/test/link/dylib/main.c b/test/link/macho/dylib/main.c similarity index 100% rename from test/link/dylib/main.c rename to test/link/macho/dylib/main.c diff --git a/test/link/macho/entry/build.zig b/test/link/macho/entry/build.zig new file mode 100644 index 0000000000..24037ba8eb --- /dev/null +++ b/test/link/macho/entry/build.zig @@ -0,0 +1,25 @@ +const std = @import("std"); +const Builder = std.build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const test_step = b.step("test", "Test"); + test_step.dependOn(b.getInstallStep()); + + const exe = b.addExecutable("main", null); + exe.setBuildMode(mode); + exe.addCSourceFile("main.c", &.{}); + exe.linkLibC(); + exe.entry_symbol_name = "_non_main"; + + const check_exe = exe.checkMachO(); + check_exe.check("cmd MAIN"); + check_exe.checkNext("entryoff {x}"); + + test_step.dependOn(&check_exe.step); + + const run = exe.run(); + run.expectStdOutEqual("42"); + test_step.dependOn(&run.step); +} diff --git a/test/link/macho/entry/main.c b/test/link/macho/entry/main.c new file mode 100644 index 0000000000..5fc58fc465 --- /dev/null +++ b/test/link/macho/entry/main.c @@ -0,0 +1,6 @@ +#include + +int non_main() { + printf("%d", 42); + return 0; +} diff --git a/test/link/frameworks/build.zig b/test/link/macho/frameworks/build.zig similarity index 100% rename from test/link/frameworks/build.zig rename to test/link/macho/frameworks/build.zig diff --git a/test/link/frameworks/main.c b/test/link/macho/frameworks/main.c similarity index 100% rename from test/link/frameworks/main.c rename to test/link/macho/frameworks/main.c diff --git a/test/link/objc/Foo.h b/test/link/macho/objc/Foo.h similarity index 100% rename from test/link/objc/Foo.h rename to test/link/macho/objc/Foo.h diff --git a/test/link/objc/Foo.m b/test/link/macho/objc/Foo.m similarity index 100% rename from test/link/objc/Foo.m rename to test/link/macho/objc/Foo.m diff --git a/test/link/objc/build.zig b/test/link/macho/objc/build.zig similarity index 100% rename from test/link/objc/build.zig rename to test/link/macho/objc/build.zig diff --git a/test/link/objc/test.m b/test/link/macho/objc/test.m similarity index 100% rename from test/link/objc/test.m rename to test/link/macho/objc/test.m diff --git a/test/link/objcpp/Foo.h b/test/link/macho/objcpp/Foo.h similarity index 100% rename from test/link/objcpp/Foo.h rename to test/link/macho/objcpp/Foo.h diff --git a/test/link/objcpp/Foo.mm b/test/link/macho/objcpp/Foo.mm similarity index 100% rename from test/link/objcpp/Foo.mm rename to test/link/macho/objcpp/Foo.mm diff --git a/test/link/objcpp/build.zig b/test/link/macho/objcpp/build.zig similarity index 100% rename from test/link/objcpp/build.zig rename to test/link/macho/objcpp/build.zig diff --git a/test/link/objcpp/test.mm b/test/link/macho/objcpp/test.mm similarity index 100% rename from test/link/objcpp/test.mm rename to test/link/macho/objcpp/test.mm diff --git a/test/link/pagezero/build.zig b/test/link/macho/pagezero/build.zig similarity index 100% rename from test/link/pagezero/build.zig rename to test/link/macho/pagezero/build.zig diff --git a/test/link/pagezero/main.c b/test/link/macho/pagezero/main.c similarity index 100% rename from test/link/pagezero/main.c rename to test/link/macho/pagezero/main.c diff --git a/test/link/macho/stack_size/build.zig b/test/link/macho/stack_size/build.zig new file mode 100644 index 0000000000..10b5a3f83d --- /dev/null +++ b/test/link/macho/stack_size/build.zig @@ -0,0 +1,24 @@ +const std = @import("std"); +const Builder = std.build.Builder; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + + const test_step = b.step("test", "Test"); + test_step.dependOn(b.getInstallStep()); + + const exe = b.addExecutable("main", null); + exe.setBuildMode(mode); + exe.addCSourceFile("main.c", &.{}); + exe.linkLibC(); + exe.stack_size = 0x100000000; + + const check_exe = exe.checkMachO(); + check_exe.check("cmd MAIN"); + check_exe.checkNext("stacksize 100000000"); + + test_step.dependOn(&check_exe.step); + + const run = exe.run(); + test_step.dependOn(&run.step); +} diff --git a/test/link/macho/stack_size/main.c b/test/link/macho/stack_size/main.c new file mode 100644 index 0000000000..ca68d24cc7 --- /dev/null +++ b/test/link/macho/stack_size/main.c @@ -0,0 +1,3 @@ +int main(int argc, char* argv[]) { + return 0; +} From b5601a2da60df2f8f2bc6ac1ef287d4733a47df2 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 22 Jun 2022 00:49:20 +0200 Subject: [PATCH 07/17] link-tests: extract values into variables We can then collect multiple variables (currently assumed always in global scope) and run a comparison with some very basic arithmetic on the values. --- lib/std/build/CheckMachOStep.zig | 191 ++++++++++++++++++++++++++++--- test/link/macho/entry/build.zig | 11 +- 2 files changed, 182 insertions(+), 20 deletions(-) diff --git a/lib/std/build/CheckMachOStep.zig b/lib/std/build/CheckMachOStep.zig index 06e79081f5..a4cd35e95a 100644 --- a/lib/std/build/CheckMachOStep.zig +++ b/lib/std/build/CheckMachOStep.zig @@ -18,6 +18,7 @@ builder: *Builder, source: build.FileSource, max_bytes: usize = 20 * 1024 * 1024, checks: std.ArrayList(Check), +dump_symtab: bool = false, pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep { const gpa = builder.allocator; @@ -32,32 +33,107 @@ pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep { return self; } +const Action = union(enum) { + exact_match: []const u8, + extract_var: struct { + fuzzy_match: []const u8, + var_name: []const u8, + var_value: u64, + }, + compare: CompareAction, +}; + +const CompareAction = struct { + expected: union(enum) { + literal: u64, + varr: []const u8, + }, + var_stack: std.ArrayList([]const u8), + op_stack: std.ArrayList(Op), + + const Op = enum { + add, + }; +}; + const Check = struct { builder: *Builder, - phrases: std.ArrayList([]const u8), + actions: std.ArrayList(Action), fn create(b: *Builder) Check { return .{ .builder = b, - .phrases = std.ArrayList([]const u8).init(b.allocator), + .actions = std.ArrayList(Action).init(b.allocator), }; } - fn addPhrase(self: *Check, phrase: []const u8) void { - self.phrases.append(self.builder.dupe(phrase)) catch unreachable; + fn exactMatch(self: *Check, phrase: []const u8) void { + self.actions.append(.{ + .exact_match = self.builder.dupe(phrase), + }) catch unreachable; + } + + fn extractVar(self: *Check, phrase: []const u8, var_name: []const u8) void { + self.actions.append(.{ + .extract_var = .{ + .fuzzy_match = self.builder.dupe(phrase), + .var_name = self.builder.dupe(var_name), + .var_value = undefined, + }, + }) catch unreachable; } }; pub fn check(self: *CheckMachOStep, phrase: []const u8) void { var new_check = Check.create(self.builder); - new_check.addPhrase(phrase); + new_check.exactMatch(phrase); self.checks.append(new_check) catch unreachable; } pub fn checkNext(self: *CheckMachOStep, phrase: []const u8) void { assert(self.checks.items.len > 0); const last = &self.checks.items[self.checks.items.len - 1]; - last.addPhrase(phrase); + last.exactMatch(phrase); +} + +pub fn checkNextExtract(self: *CheckMachOStep, comptime phrase: []const u8) void { + assert(self.checks.items.len > 0); + const matcher_start = comptime mem.indexOf(u8, phrase, "{") orelse + @compileError("missing { } matcher"); + const matcher_end = comptime mem.indexOf(u8, phrase, "}") orelse + @compileError("missing { } matcher"); + const last = &self.checks.items[self.checks.items.len - 1]; + last.extractVar(phrase[0..matcher_start], phrase[matcher_start + 1 .. matcher_end]); +} + +pub fn checkInSymtab(self: *CheckMachOStep) void { + self.dump_symtab = true; + self.check("symtab"); +} + +pub fn checkCompare(self: *CheckMachOStep, comptime phrase: []const u8, expected: anytype) void { + comptime assert(phrase[0] == '{'); + comptime assert(phrase[phrase.len - 1] == '}'); + + const gpa = self.builder.allocator; + var ca = CompareAction{ + .expected = expected, + .var_stack = std.ArrayList([]const u8).init(gpa), + .op_stack = std.ArrayList(CompareAction.Op).init(gpa), + }; + + var it = mem.tokenize(u8, phrase[1 .. phrase.len - 1], " "); + while (it.next()) |next| { + if (mem.eql(u8, next, "+")) { + ca.op_stack.append(.add) catch unreachable; + } else { + ca.var_stack.append(self.builder.dupe(next)) catch unreachable; + } + } + + var new_check = Check.create(self.builder); + new_check.actions.append(.{ .compare = ca }) catch unreachable; + self.checks.append(new_check) catch unreachable; } fn make(step: *Step) !void { @@ -79,35 +155,112 @@ fn make(step: *Step) !void { var metadata = std.ArrayList(u8).init(gpa); const writer = metadata.writer(); + var symtab_cmd: ?macho.symtab_command = null; var i: u16 = 0; while (i < hdr.ncmds) : (i += 1) { var cmd = try macho.LoadCommand.read(gpa, reader); + + if (self.dump_symtab and cmd.cmd() == .SYMTAB) { + symtab_cmd = cmd.symtab; + } + try dumpLoadCommand(cmd, i, writer); try writer.writeByte('\n'); } + if (symtab_cmd) |cmd| { + try writer.writeAll("symtab\n"); + const strtab = contents[cmd.stroff..][0..cmd.strsize]; + const symtab = @ptrCast( + [*]const macho.nlist_64, + @alignCast(@alignOf(macho.nlist_64), contents.ptr + cmd.symoff), + )[0..cmd.nsyms]; + + for (symtab) |sym| { + if (sym.stab()) continue; + const sym_name = mem.sliceTo(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx), 0); + try writer.print("{s} {x}\n", .{ sym_name, sym.n_value }); + } + } + + var vars = std.StringHashMap(u64).init(gpa); + for (self.checks.items) |chk| { - const first_phrase = chk.phrases.items[0]; + const first_action = chk.actions.items[0]; - if (mem.indexOf(u8, metadata.items, first_phrase)) |index| { - // TODO backtrack to track current scope - var it = std.mem.tokenize(u8, metadata.items[index..], "\r\n"); + switch (first_action) { + .exact_match => |first| { + if (mem.indexOf(u8, metadata.items, first)) |index| { + // TODO backtrack to track current scope + var it = std.mem.tokenize(u8, metadata.items[index..], "\r\n"); - outer: for (chk.phrases.items[1..]) |next_phrase| { - while (it.next()) |line| { - if (mem.eql(u8, line, next_phrase)) { - std.debug.print("{s} == {s}\n", .{ line, next_phrase }); - continue :outer; + outer: for (chk.actions.items[1..]) |next_action| { + switch (next_action) { + .exact_match => |exact| { + while (it.next()) |line| { + if (mem.eql(u8, line, exact)) { + std.debug.print("{s} == {s}\n", .{ line, exact }); + continue :outer; + } + std.debug.print("{s} != {s}\n", .{ line, exact }); + } else { + return error.TestFailed; + } + }, + .extract_var => |extract| { + const phrase = extract.fuzzy_match; + while (it.next()) |line| { + if (mem.indexOf(u8, line, phrase)) |found| { + std.debug.print("{s} in {s}\n", .{ phrase, line }); + // Extract variable and save back in the action. + const trimmed = mem.trim(u8, line[found + phrase.len ..], " "); + const parsed = try std.fmt.parseInt(u64, trimmed, 16); + try vars.putNoClobber(extract.var_name, parsed); + continue :outer; + } + std.debug.print("{s} not in {s}\n", .{ extract.fuzzy_match, line }); + } + }, + .compare => unreachable, + } } - std.debug.print("{s} != {s}\n", .{ line, next_phrase }); } else { return error.TestFailed; } - } - } else { - return error.TestFailed; + }, + .compare => |act| { + var values = std.ArrayList(u64).init(gpa); + try values.ensureTotalCapacity(act.var_stack.items.len); + for (act.var_stack.items) |vv| { + const val = vars.get(vv) orelse return error.TestFailed; + values.appendAssumeCapacity(val); + } + + var op_i: usize = 1; + var reduced: u64 = values.items[0]; + for (act.op_stack.items) |op| { + const other = values.items[op_i]; + switch (op) { + .add => { + reduced += other; + }, + } + } + + const expected = switch (act.expected) { + .literal => |exp| exp, + .varr => |vv| vars.get(vv) orelse return error.TestFailed, + }; + if (reduced != expected) return error.TestFailed; + }, + .extract_var => unreachable, } } + + var it = vars.iterator(); + while (it.next()) |entry| { + std.debug.print(" {s} => {x}", .{ entry.key_ptr.*, entry.value_ptr.* }); + } } fn dumpLoadCommand(lc: macho.LoadCommand, index: u16, writer: anytype) !void { diff --git a/test/link/macho/entry/build.zig b/test/link/macho/entry/build.zig index 24037ba8eb..e8c0901b20 100644 --- a/test/link/macho/entry/build.zig +++ b/test/link/macho/entry/build.zig @@ -14,8 +14,17 @@ pub fn build(b: *Builder) void { exe.entry_symbol_name = "_non_main"; const check_exe = exe.checkMachO(); + + check_exe.check("segname __TEXT"); + check_exe.checkNextExtract("vmaddr {vmaddr}"); + check_exe.check("cmd MAIN"); - check_exe.checkNext("entryoff {x}"); + check_exe.checkNextExtract("entryoff {entryoff}"); + + check_exe.checkInSymtab(); + check_exe.checkNextExtract("_non_main {n_value}"); + + check_exe.checkCompare("{vmaddr entryoff +}", .{ .varr = "n_value" }); test_step.dependOn(&check_exe.step); From 23a63f4ce445d26d7fc577eecc6c3f5ca129a007 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 22 Jun 2022 10:27:51 +0200 Subject: [PATCH 08/17] link-tests: rename CheckMachOStep to CheckObjectStep and accept obj format --- lib/std/build.zig | 8 +- ...CheckMachOStep.zig => CheckObjectStep.zig} | 250 ++++++++++-------- test/link.zig | 2 +- test/link/macho/dylib/build.zig | 4 +- test/link/macho/entry/build.zig | 2 +- test/link/macho/pagezero/build.zig | 4 +- test/link/macho/stack_size/build.zig | 2 +- 7 files changed, 148 insertions(+), 124 deletions(-) rename lib/std/build/{CheckMachOStep.zig => CheckObjectStep.zig} (59%) diff --git a/lib/std/build.zig b/lib/std/build.zig index 4582a0658d..57304242cd 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -24,7 +24,7 @@ pub const TranslateCStep = @import("build/TranslateCStep.zig"); pub const WriteFileStep = @import("build/WriteFileStep.zig"); pub const RunStep = @import("build/RunStep.zig"); pub const CheckFileStep = @import("build/CheckFileStep.zig"); -pub const CheckMachOStep = @import("build/CheckMachOStep.zig"); +pub const CheckObjectStep = @import("build/CheckObjectStep.zig"); pub const InstallRawStep = @import("build/InstallRawStep.zig"); pub const OptionsStep = @import("build/OptionsStep.zig"); @@ -1865,8 +1865,8 @@ pub const LibExeObjStep = struct { return run_step; } - pub fn checkMachO(self: *LibExeObjStep) *CheckMachOStep { - return CheckMachOStep.create(self.builder, self.getOutputSource()); + pub fn checkObject(self: *LibExeObjStep, obj_format: std.Target.ObjectFormat) *CheckObjectStep { + return CheckObjectStep.create(self.builder, self.getOutputSource(), obj_format); } pub fn setLinkerScriptPath(self: *LibExeObjStep, source: FileSource) void { @@ -3455,7 +3455,7 @@ pub const Step = struct { write_file, run, check_file, - check_macho, + check_object, install_raw, options, custom, diff --git a/lib/std/build/CheckMachOStep.zig b/lib/std/build/CheckObjectStep.zig similarity index 59% rename from lib/std/build/CheckMachOStep.zig rename to lib/std/build/CheckObjectStep.zig index a4cd35e95a..dae17b93db 100644 --- a/lib/std/build/CheckMachOStep.zig +++ b/lib/std/build/CheckObjectStep.zig @@ -5,13 +5,13 @@ const fs = std.fs; const macho = std.macho; const mem = std.mem; -const CheckMachOStep = @This(); +const CheckObjectStep = @This(); const Allocator = mem.Allocator; const Builder = build.Builder; const Step = build.Step; -pub const base_id = .check_macho; +pub const base_id = .check_obj; step: Step, builder: *Builder, @@ -19,15 +19,17 @@ source: build.FileSource, max_bytes: usize = 20 * 1024 * 1024, checks: std.ArrayList(Check), dump_symtab: bool = false, +obj_format: std.Target.ObjectFormat, -pub fn create(builder: *Builder, source: build.FileSource) *CheckMachOStep { +pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Target.ObjectFormat) *CheckObjectStep { const gpa = builder.allocator; - const self = gpa.create(CheckMachOStep) catch unreachable; - self.* = CheckMachOStep{ + const self = gpa.create(CheckObjectStep) catch unreachable; + self.* = .{ .builder = builder, - .step = Step.init(.check_file, "CheckMachO", gpa, make), + .step = Step.init(.check_file, "CheckObject", gpa, make), .source = source.dupe(builder), .checks = std.ArrayList(Check).init(gpa), + .obj_format = obj_format, }; self.source.addStepDependencies(&self.step); return self; @@ -84,19 +86,19 @@ const Check = struct { } }; -pub fn check(self: *CheckMachOStep, phrase: []const u8) void { +pub fn check(self: *CheckObjectStep, phrase: []const u8) void { var new_check = Check.create(self.builder); new_check.exactMatch(phrase); self.checks.append(new_check) catch unreachable; } -pub fn checkNext(self: *CheckMachOStep, phrase: []const u8) void { +pub fn checkNext(self: *CheckObjectStep, phrase: []const u8) void { assert(self.checks.items.len > 0); const last = &self.checks.items[self.checks.items.len - 1]; last.exactMatch(phrase); } -pub fn checkNextExtract(self: *CheckMachOStep, comptime phrase: []const u8) void { +pub fn checkNextExtract(self: *CheckObjectStep, comptime phrase: []const u8) void { assert(self.checks.items.len > 0); const matcher_start = comptime mem.indexOf(u8, phrase, "{") orelse @compileError("missing { } matcher"); @@ -106,12 +108,12 @@ pub fn checkNextExtract(self: *CheckMachOStep, comptime phrase: []const u8) void last.extractVar(phrase[0..matcher_start], phrase[matcher_start + 1 .. matcher_end]); } -pub fn checkInSymtab(self: *CheckMachOStep) void { +pub fn checkInSymtab(self: *CheckObjectStep) void { self.dump_symtab = true; self.check("symtab"); } -pub fn checkCompare(self: *CheckMachOStep, comptime phrase: []const u8, expected: anytype) void { +pub fn checkCompare(self: *CheckObjectStep, comptime phrase: []const u8, expected: anytype) void { comptime assert(phrase[0] == '{'); comptime assert(phrase[phrase.len - 1] == '}'); @@ -137,51 +139,22 @@ pub fn checkCompare(self: *CheckMachOStep, comptime phrase: []const u8, expected } fn make(step: *Step) !void { - const self = @fieldParentPtr(CheckMachOStep, "step", step); + const self = @fieldParentPtr(CheckObjectStep, "step", step); const gpa = self.builder.allocator; const src_path = self.source.getPath(self.builder); const contents = try fs.cwd().readFileAlloc(gpa, src_path, self.max_bytes); - // Parse the object file's header - var stream = std.io.fixedBufferStream(contents); - const reader = stream.reader(); - - const hdr = try reader.readStruct(macho.mach_header_64); - if (hdr.magic != macho.MH_MAGIC_64) { - return error.InvalidMagicNumber; - } - - var metadata = std.ArrayList(u8).init(gpa); - const writer = metadata.writer(); - - var symtab_cmd: ?macho.symtab_command = null; - var i: u16 = 0; - while (i < hdr.ncmds) : (i += 1) { - var cmd = try macho.LoadCommand.read(gpa, reader); - - if (self.dump_symtab and cmd.cmd() == .SYMTAB) { - symtab_cmd = cmd.symtab; - } - - try dumpLoadCommand(cmd, i, writer); - try writer.writeByte('\n'); - } - - if (symtab_cmd) |cmd| { - try writer.writeAll("symtab\n"); - const strtab = contents[cmd.stroff..][0..cmd.strsize]; - const symtab = @ptrCast( - [*]const macho.nlist_64, - @alignCast(@alignOf(macho.nlist_64), contents.ptr + cmd.symoff), - )[0..cmd.nsyms]; - - for (symtab) |sym| { - if (sym.stab()) continue; - const sym_name = mem.sliceTo(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx), 0); - try writer.print("{s} {x}\n", .{ sym_name, sym.n_value }); - } - } + const output = switch (self.obj_format) { + .macho => try MachODumper.parseAndDump(contents, .{ + .gpa = gpa, + .dump_symtab = self.dump_symtab, + }), + .elf => @panic("TODO elf parser"), + .coff => @panic("TODO coff parser"), + .wasm => @panic("TODO wasm parser"), + else => unreachable, + }; var vars = std.StringHashMap(u64).init(gpa); @@ -190,9 +163,9 @@ fn make(step: *Step) !void { switch (first_action) { .exact_match => |first| { - if (mem.indexOf(u8, metadata.items, first)) |index| { + if (mem.indexOf(u8, output, first)) |index| { // TODO backtrack to track current scope - var it = std.mem.tokenize(u8, metadata.items[index..], "\r\n"); + var it = std.mem.tokenize(u8, output[index..], "\r\n"); outer: for (chk.actions.items[1..]) |next_action| { switch (next_action) { @@ -263,69 +236,120 @@ fn make(step: *Step) !void { } } -fn dumpLoadCommand(lc: macho.LoadCommand, index: u16, writer: anytype) !void { - // print header first - try writer.print( - \\LC {d} - \\cmd {s} - \\cmdsize {d} - , .{ index, @tagName(lc.cmd()), lc.cmdsize() }); +const Opts = struct { + gpa: ?Allocator = null, + dump_symtab: bool = false, +}; - switch (lc.cmd()) { - .SEGMENT_64 => { - // TODO dump section headers - const seg = lc.segment.inner; +const MachODumper = struct { + fn parseAndDump(bytes: []const u8, opts: Opts) ![]const u8 { + const gpa = opts.gpa orelse unreachable; // MachO dumper requires an allocator + var stream = std.io.fixedBufferStream(bytes); + const reader = stream.reader(); + + const hdr = try reader.readStruct(macho.mach_header_64); + if (hdr.magic != macho.MH_MAGIC_64) { + return error.InvalidMagicNumber; + } + + var output = std.ArrayList(u8).init(gpa); + const writer = output.writer(); + + var symtab_cmd: ?macho.symtab_command = null; + var i: u16 = 0; + while (i < hdr.ncmds) : (i += 1) { + var cmd = try macho.LoadCommand.read(gpa, reader); + + if (opts.dump_symtab and cmd.cmd() == .SYMTAB) { + symtab_cmd = cmd.symtab; + } + + try dumpLoadCommand(cmd, i, writer); try writer.writeByte('\n'); - try writer.print( - \\segname {s} - \\vmaddr {x} - \\vmsize {x} - \\fileoff {x} - \\filesz {x} - , .{ - seg.segName(), - seg.vmaddr, - seg.vmsize, - seg.fileoff, - seg.filesize, - }); - }, + } - .ID_DYLIB, - .LOAD_DYLIB, - => { - const dylib = lc.dylib.inner.dylib; - try writer.writeByte('\n'); - try writer.print( - \\path {s} - \\timestamp {d} - \\current version {x} - \\compatibility version {x} - , .{ - mem.sliceTo(lc.dylib.data, 0), - dylib.timestamp, - dylib.current_version, - dylib.compatibility_version, - }); - }, + if (symtab_cmd) |cmd| { + try writer.writeAll("symtab\n"); + const strtab = bytes[cmd.stroff..][0..cmd.strsize]; + const symtab = @ptrCast( + [*]const macho.nlist_64, + @alignCast(@alignOf(macho.nlist_64), bytes.ptr + cmd.symoff), + )[0..cmd.nsyms]; - .MAIN => { - try writer.writeByte('\n'); - try writer.print( - \\entryoff {x} - \\stacksize {x} - , .{ lc.main.entryoff, lc.main.stacksize }); - }, + for (symtab) |sym| { + if (sym.stab()) continue; + const sym_name = mem.sliceTo(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx), 0); + try writer.print("{s} {x}\n", .{ sym_name, sym.n_value }); + } + } - .RPATH => { - try writer.writeByte('\n'); - try writer.print( - \\path {s} - , .{ - mem.sliceTo(lc.rpath.data, 0), - }); - }, - - else => {}, + return output.toOwnedSlice(); } -} + + fn dumpLoadCommand(lc: macho.LoadCommand, index: u16, writer: anytype) !void { + // print header first + try writer.print( + \\LC {d} + \\cmd {s} + \\cmdsize {d} + , .{ index, @tagName(lc.cmd()), lc.cmdsize() }); + + switch (lc.cmd()) { + .SEGMENT_64 => { + // TODO dump section headers + const seg = lc.segment.inner; + try writer.writeByte('\n'); + try writer.print( + \\segname {s} + \\vmaddr {x} + \\vmsize {x} + \\fileoff {x} + \\filesz {x} + , .{ + seg.segName(), + seg.vmaddr, + seg.vmsize, + seg.fileoff, + seg.filesize, + }); + }, + + .ID_DYLIB, + .LOAD_DYLIB, + => { + const dylib = lc.dylib.inner.dylib; + try writer.writeByte('\n'); + try writer.print( + \\path {s} + \\timestamp {d} + \\current version {x} + \\compatibility version {x} + , .{ + mem.sliceTo(lc.dylib.data, 0), + dylib.timestamp, + dylib.current_version, + dylib.compatibility_version, + }); + }, + + .MAIN => { + try writer.writeByte('\n'); + try writer.print( + \\entryoff {x} + \\stacksize {x} + , .{ lc.main.entryoff, lc.main.stacksize }); + }, + + .RPATH => { + try writer.writeByte('\n'); + try writer.print( + \\path {s} + , .{ + mem.sliceTo(lc.rpath.data, 0), + }); + }, + + else => {}, + } + } +}; diff --git a/test/link.zig b/test/link.zig index e3dcfa1ec6..42ce12f73c 100644 --- a/test/link.zig +++ b/test/link.zig @@ -28,7 +28,7 @@ pub fn addCases(cases: *tests.StandaloneContext) void { }); if (builtin.os.tag == .macos) { - cases.addBuildFile("test/link/entry/build.zig", .{ + cases.addBuildFile("test/link/macho/entry/build.zig", .{ .build_modes = true, }); diff --git a/test/link/macho/dylib/build.zig b/test/link/macho/dylib/build.zig index 4f41e204ec..a613f02b4a 100644 --- a/test/link/macho/dylib/build.zig +++ b/test/link/macho/dylib/build.zig @@ -13,7 +13,7 @@ pub fn build(b: *Builder) void { dylib.linkLibC(); dylib.install(); - const check_dylib = dylib.checkMachO(); + const check_dylib = dylib.checkObject(.macho); check_dylib.check("cmd ID_DYLIB"); check_dylib.checkNext("path @rpath/liba.dylib"); check_dylib.checkNext("timestamp 2"); @@ -30,7 +30,7 @@ pub fn build(b: *Builder) void { exe.addLibraryPath(b.pathFromRoot("zig-out/lib/")); exe.addRPath(b.pathFromRoot("zig-out/lib")); - const check_exe = exe.checkMachO(); + const check_exe = exe.checkObject(.macho); check_exe.check("cmd LOAD_DYLIB"); check_exe.checkNext("path @rpath/liba.dylib"); check_exe.checkNext("timestamp 2"); diff --git a/test/link/macho/entry/build.zig b/test/link/macho/entry/build.zig index e8c0901b20..78225b0d73 100644 --- a/test/link/macho/entry/build.zig +++ b/test/link/macho/entry/build.zig @@ -13,7 +13,7 @@ pub fn build(b: *Builder) void { exe.linkLibC(); exe.entry_symbol_name = "_non_main"; - const check_exe = exe.checkMachO(); + const check_exe = exe.checkObject(.macho); check_exe.check("segname __TEXT"); check_exe.checkNextExtract("vmaddr {vmaddr}"); diff --git a/test/link/macho/pagezero/build.zig b/test/link/macho/pagezero/build.zig index 05bc4052fe..a35be919c5 100644 --- a/test/link/macho/pagezero/build.zig +++ b/test/link/macho/pagezero/build.zig @@ -14,7 +14,7 @@ pub fn build(b: *Builder) void { exe.linkLibC(); exe.pagezero_size = 0x4000; - const check = exe.checkMachO(); + const check = exe.checkObject(.macho); check.check("LC 0"); check.checkNext("segname __PAGEZERO"); check.checkNext("vmaddr 0"); @@ -33,7 +33,7 @@ pub fn build(b: *Builder) void { exe.linkLibC(); exe.pagezero_size = 0; - const check = exe.checkMachO(); + const check = exe.checkObject(.macho); check.check("LC 0"); check.checkNext("segname __TEXT"); check.checkNext("vmaddr 0"); diff --git a/test/link/macho/stack_size/build.zig b/test/link/macho/stack_size/build.zig index 10b5a3f83d..b840f8928c 100644 --- a/test/link/macho/stack_size/build.zig +++ b/test/link/macho/stack_size/build.zig @@ -13,7 +13,7 @@ pub fn build(b: *Builder) void { exe.linkLibC(); exe.stack_size = 0x100000000; - const check_exe = exe.checkMachO(); + const check_exe = exe.checkObject(.macho); check_exe.check("cmd MAIN"); check_exe.checkNext("stacksize 100000000"); From 211de9b63b53b7252a6321074ace9018cf392db2 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 22 Jun 2022 10:40:10 +0200 Subject: [PATCH 09/17] link-tests: fix dumping of LOAD_DYLIB: name instead of path field --- lib/std/build/CheckObjectStep.zig | 2 +- test/link/macho/dylib/build.zig | 4 ++-- test/link/macho/frameworks/build.zig | 8 ++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/std/build/CheckObjectStep.zig b/lib/std/build/CheckObjectStep.zig index dae17b93db..4f9a7f0997 100644 --- a/lib/std/build/CheckObjectStep.zig +++ b/lib/std/build/CheckObjectStep.zig @@ -320,7 +320,7 @@ const MachODumper = struct { const dylib = lc.dylib.inner.dylib; try writer.writeByte('\n'); try writer.print( - \\path {s} + \\name {s} \\timestamp {d} \\current version {x} \\compatibility version {x} diff --git a/test/link/macho/dylib/build.zig b/test/link/macho/dylib/build.zig index a613f02b4a..ad116e23e9 100644 --- a/test/link/macho/dylib/build.zig +++ b/test/link/macho/dylib/build.zig @@ -15,7 +15,7 @@ pub fn build(b: *Builder) void { const check_dylib = dylib.checkObject(.macho); check_dylib.check("cmd ID_DYLIB"); - check_dylib.checkNext("path @rpath/liba.dylib"); + check_dylib.checkNext("name @rpath/liba.dylib"); check_dylib.checkNext("timestamp 2"); check_dylib.checkNext("current version 10000"); check_dylib.checkNext("compatibility version 10000"); @@ -32,7 +32,7 @@ pub fn build(b: *Builder) void { const check_exe = exe.checkObject(.macho); check_exe.check("cmd LOAD_DYLIB"); - check_exe.checkNext("path @rpath/liba.dylib"); + check_exe.checkNext("name @rpath/liba.dylib"); check_exe.checkNext("timestamp 2"); check_exe.checkNext("current version 10000"); check_exe.checkNext("compatibility version 10000"); diff --git a/test/link/macho/frameworks/build.zig b/test/link/macho/frameworks/build.zig index 5700422a41..f196f47b1b 100644 --- a/test/link/macho/frameworks/build.zig +++ b/test/link/macho/frameworks/build.zig @@ -11,10 +11,14 @@ pub fn build(b: *Builder) void { exe.addCSourceFile("main.c", &[0][]const u8{}); exe.setBuildMode(mode); exe.linkLibC(); - // TODO when we figure out how to ship framework stubs for cross-compilation, - // populate paths to the sysroot here. exe.linkFramework("Cocoa"); + const check = exe.checkObject(.macho); + check.check("cmd LOAD_DYLIB"); + check.checkNext("name /System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa"); + + test_step.dependOn(&check.step); + const run_cmd = exe.run(); test_step.dependOn(&run_cmd.step); } From b35e434caeb7448a93c14119e73d7e54d3864337 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 22 Jun 2022 18:34:39 +0200 Subject: [PATCH 10/17] link-tests: clean up linker testing harness --- lib/std/build/CheckObjectStep.zig | 193 ++++++++++++--------------- test/link/macho/entry/build.zig | 8 +- test/link/macho/frameworks/build.zig | 5 +- 3 files changed, 97 insertions(+), 109 deletions(-) diff --git a/lib/std/build/CheckObjectStep.zig b/lib/std/build/CheckObjectStep.zig index 4f9a7f0997..40266d0c85 100644 --- a/lib/std/build/CheckObjectStep.zig +++ b/lib/std/build/CheckObjectStep.zig @@ -36,20 +36,52 @@ pub fn create(builder: *Builder, source: build.FileSource, obj_format: std.Targe } const Action = union(enum) { - exact_match: []const u8, - extract_var: struct { - fuzzy_match: []const u8, - var_name: []const u8, - var_value: u64, - }, - compare: CompareAction, + match: MatchAction, + compute_eq: ComputeEqAction, }; -const CompareAction = struct { - expected: union(enum) { - literal: u64, - varr: []const u8, - }, +const MatchAction = struct { + needle: []const u8, + + fn match(act: MatchAction, haystack: []const u8, global_vars: anytype) !bool { + var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " "); + var needle_it = mem.tokenize(u8, mem.trim(u8, act.needle, " "), " "); + + while (needle_it.next()) |needle_tok| { + const hay_tok = hay_it.next() orelse return false; + + if (mem.indexOf(u8, needle_tok, "{*}")) |index| { + // We have fuzzy matchers within the search pattern, so we match substrings. + var start = index; + var n_tok = needle_tok; + var h_tok = hay_tok; + while (true) { + n_tok = n_tok[start + 3 ..]; + const inner = if (mem.indexOf(u8, n_tok, "{*}")) |sub_end| + n_tok[0..sub_end] + else + n_tok; + if (mem.indexOf(u8, h_tok, inner) == null) return false; + start = mem.indexOf(u8, n_tok, "{*}") orelse break; + } + } else if (mem.startsWith(u8, needle_tok, "{")) { + const closing_brace = mem.indexOf(u8, needle_tok, "}") orelse return error.MissingClosingBrace; + if (closing_brace != needle_tok.len - 1) return error.ClosingBraceNotLast; + + const name = needle_tok[1..closing_brace]; + const value = try std.fmt.parseInt(u64, hay_tok, 16); + try global_vars.putNoClobber(name, value); + } else { + if (!mem.eql(u8, hay_tok, needle_tok)) return false; + } + } + + return true; + } +}; + +const ComputeEqAction = struct { + expected: []const u8, var_stack: std.ArrayList([]const u8), op_stack: std.ArrayList(Op), @@ -69,43 +101,29 @@ const Check = struct { }; } - fn exactMatch(self: *Check, phrase: []const u8) void { + fn match(self: *Check, needle: []const u8) void { self.actions.append(.{ - .exact_match = self.builder.dupe(phrase), + .match = .{ .needle = self.builder.dupe(needle) }, }) catch unreachable; } - fn extractVar(self: *Check, phrase: []const u8, var_name: []const u8) void { + fn computeEq(self: *Check, act: ComputeEqAction) void { self.actions.append(.{ - .extract_var = .{ - .fuzzy_match = self.builder.dupe(phrase), - .var_name = self.builder.dupe(var_name), - .var_value = undefined, - }, + .compute_eq = act, }) catch unreachable; } }; pub fn check(self: *CheckObjectStep, phrase: []const u8) void { var new_check = Check.create(self.builder); - new_check.exactMatch(phrase); + new_check.match(phrase); self.checks.append(new_check) catch unreachable; } pub fn checkNext(self: *CheckObjectStep, phrase: []const u8) void { assert(self.checks.items.len > 0); const last = &self.checks.items[self.checks.items.len - 1]; - last.exactMatch(phrase); -} - -pub fn checkNextExtract(self: *CheckObjectStep, comptime phrase: []const u8) void { - assert(self.checks.items.len > 0); - const matcher_start = comptime mem.indexOf(u8, phrase, "{") orelse - @compileError("missing { } matcher"); - const matcher_end = comptime mem.indexOf(u8, phrase, "}") orelse - @compileError("missing { } matcher"); - const last = &self.checks.items[self.checks.items.len - 1]; - last.extractVar(phrase[0..matcher_start], phrase[matcher_start + 1 .. matcher_end]); + last.match(phrase); } pub fn checkInSymtab(self: *CheckObjectStep) void { @@ -113,18 +131,15 @@ pub fn checkInSymtab(self: *CheckObjectStep) void { self.check("symtab"); } -pub fn checkCompare(self: *CheckObjectStep, comptime phrase: []const u8, expected: anytype) void { - comptime assert(phrase[0] == '{'); - comptime assert(phrase[phrase.len - 1] == '}'); - +pub fn checkComputeEq(self: *CheckObjectStep, program: []const u8, expected: []const u8) void { const gpa = self.builder.allocator; - var ca = CompareAction{ + var ca = ComputeEqAction{ .expected = expected, .var_stack = std.ArrayList([]const u8).init(gpa), - .op_stack = std.ArrayList(CompareAction.Op).init(gpa), + .op_stack = std.ArrayList(ComputeEqAction.Op).init(gpa), }; - var it = mem.tokenize(u8, phrase[1 .. phrase.len - 1], " "); + var it = mem.tokenize(u8, program, " "); while (it.next()) |next| { if (mem.eql(u8, next, "+")) { ca.op_stack.append(.add) catch unreachable; @@ -134,7 +149,7 @@ pub fn checkCompare(self: *CheckObjectStep, comptime phrase: []const u8, expecte } var new_check = Check.create(self.builder); - new_check.actions.append(.{ .compare = ca }) catch unreachable; + new_check.computeEq(ca); self.checks.append(new_check) catch unreachable; } @@ -159,80 +174,50 @@ fn make(step: *Step) !void { var vars = std.StringHashMap(u64).init(gpa); for (self.checks.items) |chk| { - const first_action = chk.actions.items[0]; + var it = mem.tokenize(u8, output, "\r\n"); + for (chk.actions.items) |act| { + switch (act) { + .match => |match_act| { + while (it.next()) |line| { + if (try match_act.match(line, &vars)) { + std.debug.print("{s} == {s}\n", .{ line, match_act.needle }); + break; + } else { + std.debug.print("{s} != {s}\n", .{ line, match_act.needle }); + } + } else { + return error.TestFailed; + } + }, + .compute_eq => |c_eq| { + var values = std.ArrayList(u64).init(gpa); + try values.ensureTotalCapacity(c_eq.var_stack.items.len); + for (c_eq.var_stack.items) |vv| { + const val = vars.get(vv) orelse return error.TestFailed; + values.appendAssumeCapacity(val); + } - switch (first_action) { - .exact_match => |first| { - if (mem.indexOf(u8, output, first)) |index| { - // TODO backtrack to track current scope - var it = std.mem.tokenize(u8, output[index..], "\r\n"); - - outer: for (chk.actions.items[1..]) |next_action| { - switch (next_action) { - .exact_match => |exact| { - while (it.next()) |line| { - if (mem.eql(u8, line, exact)) { - std.debug.print("{s} == {s}\n", .{ line, exact }); - continue :outer; - } - std.debug.print("{s} != {s}\n", .{ line, exact }); - } else { - return error.TestFailed; - } + var op_i: usize = 1; + var reduced: u64 = values.items[0]; + for (c_eq.op_stack.items) |op| { + const other = values.items[op_i]; + switch (op) { + .add => { + reduced += other; }, - .extract_var => |extract| { - const phrase = extract.fuzzy_match; - while (it.next()) |line| { - if (mem.indexOf(u8, line, phrase)) |found| { - std.debug.print("{s} in {s}\n", .{ phrase, line }); - // Extract variable and save back in the action. - const trimmed = mem.trim(u8, line[found + phrase.len ..], " "); - const parsed = try std.fmt.parseInt(u64, trimmed, 16); - try vars.putNoClobber(extract.var_name, parsed); - continue :outer; - } - std.debug.print("{s} not in {s}\n", .{ extract.fuzzy_match, line }); - } - }, - .compare => unreachable, } } - } else { - return error.TestFailed; - } - }, - .compare => |act| { - var values = std.ArrayList(u64).init(gpa); - try values.ensureTotalCapacity(act.var_stack.items.len); - for (act.var_stack.items) |vv| { - const val = vars.get(vv) orelse return error.TestFailed; - values.appendAssumeCapacity(val); - } - var op_i: usize = 1; - var reduced: u64 = values.items[0]; - for (act.op_stack.items) |op| { - const other = values.items[op_i]; - switch (op) { - .add => { - reduced += other; - }, - } - } - - const expected = switch (act.expected) { - .literal => |exp| exp, - .varr => |vv| vars.get(vv) orelse return error.TestFailed, - }; - if (reduced != expected) return error.TestFailed; - }, - .extract_var => unreachable, + const expected = vars.get(c_eq.expected) orelse return error.TestFailed; + if (reduced != expected) return error.TestFailed; + }, + } } } var it = vars.iterator(); while (it.next()) |entry| { - std.debug.print(" {s} => {x}", .{ entry.key_ptr.*, entry.value_ptr.* }); + std.debug.print(" {s} => {x}\n", .{ entry.key_ptr.*, entry.value_ptr.* }); } } diff --git a/test/link/macho/entry/build.zig b/test/link/macho/entry/build.zig index 78225b0d73..82d3917521 100644 --- a/test/link/macho/entry/build.zig +++ b/test/link/macho/entry/build.zig @@ -16,15 +16,15 @@ pub fn build(b: *Builder) void { const check_exe = exe.checkObject(.macho); check_exe.check("segname __TEXT"); - check_exe.checkNextExtract("vmaddr {vmaddr}"); + check_exe.checkNext("vmaddr {vmaddr}"); check_exe.check("cmd MAIN"); - check_exe.checkNextExtract("entryoff {entryoff}"); + check_exe.checkNext("entryoff {entryoff}"); check_exe.checkInSymtab(); - check_exe.checkNextExtract("_non_main {n_value}"); + check_exe.checkNext("_non_main {n_value}"); - check_exe.checkCompare("{vmaddr entryoff +}", .{ .varr = "n_value" }); + check_exe.checkComputeEq("vmaddr entryoff +", "n_value"); test_step.dependOn(&check_exe.step); diff --git a/test/link/macho/frameworks/build.zig b/test/link/macho/frameworks/build.zig index f196f47b1b..a85f6a7350 100644 --- a/test/link/macho/frameworks/build.zig +++ b/test/link/macho/frameworks/build.zig @@ -15,7 +15,10 @@ pub fn build(b: *Builder) void { const check = exe.checkObject(.macho); check.check("cmd LOAD_DYLIB"); - check.checkNext("name /System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa"); + check.checkNext("name {*}Cocoa"); + + check.check("cmd LOAD_DYLIB"); + check.checkNext("name {*}libobjc{*}.dylib"); test_step.dependOn(&check.step); From ba768614aceb486bc387e60a6026f4f48188402e Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 22 Jun 2022 18:51:35 +0200 Subject: [PATCH 11/17] link-tests: frameworks example can test for libobjc autolink in safety modes --- test/link/macho/frameworks/build.zig | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/link/macho/frameworks/build.zig b/test/link/macho/frameworks/build.zig index a85f6a7350..2e1c8f3211 100644 --- a/test/link/macho/frameworks/build.zig +++ b/test/link/macho/frameworks/build.zig @@ -17,8 +17,13 @@ pub fn build(b: *Builder) void { check.check("cmd LOAD_DYLIB"); check.checkNext("name {*}Cocoa"); - check.check("cmd LOAD_DYLIB"); - check.checkNext("name {*}libobjc{*}.dylib"); + switch (mode) { + .Debug, .ReleaseSafe => { + check.check("cmd LOAD_DYLIB"); + check.checkNext("name {*}libobjc{*}.dylib"); + }, + else => {}, + } test_step.dependOn(&check.step); From 51f2442fc4160415796cf6271d1951969ab71c83 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 22 Jun 2022 22:24:52 +0200 Subject: [PATCH 12/17] link-tests: clean up error messages in case of failure --- lib/std/build/CheckObjectStep.zig | 47 ++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/lib/std/build/CheckObjectStep.zig b/lib/std/build/CheckObjectStep.zig index 40266d0c85..66edc084cd 100644 --- a/lib/std/build/CheckObjectStep.zig +++ b/lib/std/build/CheckObjectStep.zig @@ -4,6 +4,7 @@ const build = std.build; const fs = std.fs; const macho = std.macho; const mem = std.mem; +const testing = std.testing; const CheckObjectStep = @This(); @@ -179,13 +180,16 @@ fn make(step: *Step) !void { switch (act) { .match => |match_act| { while (it.next()) |line| { - if (try match_act.match(line, &vars)) { - std.debug.print("{s} == {s}\n", .{ line, match_act.needle }); - break; - } else { - std.debug.print("{s} != {s}\n", .{ line, match_act.needle }); - } + if (try match_act.match(line, &vars)) break; } else { + std.debug.print( + \\ + \\========= Expected to find: ========================== + \\{s} + \\========= But parsed file does not contain it: ======= + \\{s} + \\ + , .{ match_act.needle, output }); return error.TestFailed; } }, @@ -193,7 +197,17 @@ fn make(step: *Step) !void { var values = std.ArrayList(u64).init(gpa); try values.ensureTotalCapacity(c_eq.var_stack.items.len); for (c_eq.var_stack.items) |vv| { - const val = vars.get(vv) orelse return error.TestFailed; + const val = vars.get(vv) orelse { + std.debug.print( + \\ + \\========= Variable was not extracted: =========== + \\{s} + \\========= From parsed file: ===================== + \\{s} + \\ + , .{ vv, output }); + return error.TestFailed; + }; values.appendAssumeCapacity(val); } @@ -208,17 +222,22 @@ fn make(step: *Step) !void { } } - const expected = vars.get(c_eq.expected) orelse return error.TestFailed; - if (reduced != expected) return error.TestFailed; + const expected = vars.get(c_eq.expected) orelse { + std.debug.print( + \\ + \\========= Variable was not extracted: =========== + \\{s} + \\========= From parsed file: ===================== + \\{s} + \\ + , .{ c_eq.expected, output }); + return error.TestFailed; + }; + try testing.expectEqual(reduced, expected); }, } } } - - var it = vars.iterator(); - while (it.next()) |entry| { - std.debug.print(" {s} => {x}\n", .{ entry.key_ptr.*, entry.value_ptr.* }); - } } const Opts = struct { From e6c012c7432a6de409bf08eb7964b3d99fe4362e Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 22 Jun 2022 22:40:05 +0200 Subject: [PATCH 13/17] link-tests: add better docs --- lib/std/build/CheckObjectStep.zig | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/lib/std/build/CheckObjectStep.zig b/lib/std/build/CheckObjectStep.zig index 66edc084cd..9af66d88fa 100644 --- a/lib/std/build/CheckObjectStep.zig +++ b/lib/std/build/CheckObjectStep.zig @@ -41,9 +41,22 @@ const Action = union(enum) { compute_eq: ComputeEqAction, }; +/// MatchAction is the main building block of standard matchers with optional eat-all token `{*}` +/// and extractors by name such as `{n_value}`. Please note this action is very simplistic in nature +/// i.e., it won't really handle edge cases/nontrivial examples. But given that we do want to use +/// it mainly to test the output of our object format parser-dumpers when testing the linkers, etc. +/// it should be plenty useful in its current form. const MatchAction = struct { needle: []const u8, + /// Will return true if the `needle` was found in the `haystack`. + /// Some examples include: + /// + /// LC 0 => will match in its entirety + /// vmaddr {vmaddr} => will match `vmaddr` and then extract the following value as u64 + /// and save under `vmaddr` global name (see `global_vars` param) + /// name {*}libobjc{*}.dylib => will match `name` followed by a token which contains `libobjc` and `.dylib` + /// in that order with other letters in between fn match(act: MatchAction, haystack: []const u8, global_vars: anytype) !bool { var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " "); var needle_it = mem.tokenize(u8, mem.trim(u8, act.needle, " "), " "); @@ -81,6 +94,12 @@ const MatchAction = struct { } }; +/// ComputeEqAction can be used to perform an operation on the extracted global variables +/// using the MatchAction. It currently only supports an addition. The operation is required +/// to be specified in Reverse Polish Notation to ease in operator-precedence parsing (well, +/// to avoid any parsing really). +/// For example, if the two extracted values were saved as `vmaddr` and `entryoff` respectively +/// they could then be added with this simple program `vmaddr entryoff +`. const ComputeEqAction = struct { expected: []const u8, var_stack: std.ArrayList([]const u8), @@ -115,23 +134,32 @@ const Check = struct { } }; +/// Creates a new sequence of actions with `phrase` as the first anchor searched phrase. pub fn check(self: *CheckObjectStep, phrase: []const u8) void { var new_check = Check.create(self.builder); new_check.match(phrase); self.checks.append(new_check) catch unreachable; } +/// Adds another searched phrase to the latest created Check with `CheckObjectStep.check(...)`. +/// Asserts at least one check already exists. pub fn checkNext(self: *CheckObjectStep, phrase: []const u8) void { assert(self.checks.items.len > 0); const last = &self.checks.items[self.checks.items.len - 1]; last.match(phrase); } +/// Creates a new check checking specifically symbol table parsed and dumped from the object +/// file. +/// Issuing this check will force parsing and dumping of the symbol table. pub fn checkInSymtab(self: *CheckObjectStep) void { self.dump_symtab = true; self.check("symtab"); } +/// Creates a new standalone, singular check which allows running simple binary operations +/// on the extracted variables. It will then compare the reduced program with the value of +/// the expected variable. pub fn checkComputeEq(self: *CheckObjectStep, program: []const u8, expected: []const u8) void { const gpa = self.builder.allocator; var ca = ComputeEqAction{ From ab8a670a571f6ae5edb77dba13f0e250c53d3742 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 22 Jun 2022 23:04:47 +0200 Subject: [PATCH 14/17] link-tests: enable on macos CI host only for now --- ci/azure/macos_script | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/azure/macos_script b/ci/azure/macos_script index e958dc28de..149ae9245e 100755 --- a/ci/azure/macos_script +++ b/ci/azure/macos_script @@ -76,6 +76,7 @@ release/bin/zig build test-run-translated-c -Denable-macos-sdk release/bin/zig build docs -Denable-macos-sdk release/bin/zig build test-fmt -Denable-macos-sdk release/bin/zig build test-cases -Denable-macos-sdk -Dsingle-threaded +release/bin/zig build test-link -Denable-macos-sdk if [ "${BUILD_REASON}" != "PullRequest" ]; then mv ../LICENSE release/ From 4497e422f0629ccbde699d7ecc900ab7e358caa2 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 23 Jun 2022 11:31:02 +0200 Subject: [PATCH 15/17] macho: fix aligning linkedit sections Align by file offsets and not file size. --- src/link/MachO.zig | 152 +++++++++++++++++++++++++-------------------- 1 file changed, 84 insertions(+), 68 deletions(-) diff --git a/src/link/MachO.zig b/src/link/MachO.zig index af84ce8846..a7681e976d 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -5715,28 +5715,46 @@ fn writeDyldInfoData(self: *MachO) !void { const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment; const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].dyld_info_only; + + const rebase_off = mem.alignForwardGeneric(u64, seg.inner.fileoff, @alignOf(u64)); const rebase_size = try bind.rebaseInfoSize(rebase_pointers.items); + dyld_info.rebase_off = @intCast(u32, rebase_off); + dyld_info.rebase_size = @intCast(u32, rebase_size); + log.debug("writing rebase info from 0x{x} to 0x{x}", .{ + dyld_info.rebase_off, + dyld_info.rebase_off + dyld_info.rebase_size, + }); + + const bind_off = mem.alignForwardGeneric(u64, dyld_info.rebase_off + dyld_info.rebase_size, @alignOf(u64)); const bind_size = try bind.bindInfoSize(bind_pointers.items); + dyld_info.bind_off = @intCast(u32, bind_off); + dyld_info.bind_size = @intCast(u32, bind_size); + log.debug("writing bind info from 0x{x} to 0x{x}", .{ + dyld_info.bind_off, + dyld_info.bind_off + dyld_info.bind_size, + }); + + const lazy_bind_off = mem.alignForwardGeneric(u64, dyld_info.bind_off + dyld_info.bind_size, @alignOf(u64)); const lazy_bind_size = try bind.lazyBindInfoSize(lazy_bind_pointers.items); + dyld_info.lazy_bind_off = @intCast(u32, lazy_bind_off); + dyld_info.lazy_bind_size = @intCast(u32, lazy_bind_size); + log.debug("writing lazy bind info from 0x{x} to 0x{x}", .{ + dyld_info.lazy_bind_off, + dyld_info.lazy_bind_off + dyld_info.lazy_bind_size, + }); + + const export_off = mem.alignForwardGeneric(u64, dyld_info.lazy_bind_off + dyld_info.lazy_bind_size, @alignOf(u64)); const export_size = trie.size; + dyld_info.export_off = @intCast(u32, export_off); + dyld_info.export_size = @intCast(u32, export_size); + log.debug("writing export trie from 0x{x} to 0x{x}", .{ + dyld_info.export_off, + dyld_info.export_off + dyld_info.export_size, + }); - dyld_info.rebase_off = @intCast(u32, seg.inner.fileoff); - dyld_info.rebase_size = @intCast(u32, mem.alignForwardGeneric(u64, rebase_size, @alignOf(u64))); - seg.inner.filesize += dyld_info.rebase_size; + seg.inner.filesize = dyld_info.export_off + dyld_info.export_size - seg.inner.fileoff; - dyld_info.bind_off = dyld_info.rebase_off + dyld_info.rebase_size; - dyld_info.bind_size = @intCast(u32, mem.alignForwardGeneric(u64, bind_size, @alignOf(u64))); - seg.inner.filesize += dyld_info.bind_size; - - dyld_info.lazy_bind_off = dyld_info.bind_off + dyld_info.bind_size; - dyld_info.lazy_bind_size = @intCast(u32, mem.alignForwardGeneric(u64, lazy_bind_size, @alignOf(u64))); - seg.inner.filesize += dyld_info.lazy_bind_size; - - dyld_info.export_off = dyld_info.lazy_bind_off + dyld_info.lazy_bind_size; - dyld_info.export_size = @intCast(u32, mem.alignForwardGeneric(u64, export_size, @alignOf(u64))); - seg.inner.filesize += dyld_info.export_size; - - const needed_size = dyld_info.rebase_size + dyld_info.bind_size + dyld_info.lazy_bind_size + dyld_info.export_size; + const needed_size = dyld_info.export_off + dyld_info.export_size - dyld_info.rebase_off; var buffer = try self.base.allocator.alloc(u8, needed_size); defer self.base.allocator.free(buffer); mem.set(u8, buffer, 0); @@ -5744,14 +5762,15 @@ fn writeDyldInfoData(self: *MachO) !void { var stream = std.io.fixedBufferStream(buffer); const writer = stream.writer(); + const base_off = dyld_info.rebase_off; try bind.writeRebaseInfo(rebase_pointers.items, writer); - try stream.seekBy(@intCast(i64, dyld_info.rebase_size) - @intCast(i64, rebase_size)); + try stream.seekTo(dyld_info.bind_off - base_off); try bind.writeBindInfo(bind_pointers.items, writer); - try stream.seekBy(@intCast(i64, dyld_info.bind_size) - @intCast(i64, bind_size)); + try stream.seekTo(dyld_info.lazy_bind_off - base_off); try bind.writeLazyBindInfo(lazy_bind_pointers.items, writer); - try stream.seekBy(@intCast(i64, dyld_info.lazy_bind_size) - @intCast(i64, lazy_bind_size)); + try stream.seekTo(dyld_info.export_off - base_off); _ = try trie.write(writer); @@ -5762,7 +5781,7 @@ fn writeDyldInfoData(self: *MachO) !void { try self.base.file.?.pwriteAll(buffer, dyld_info.rebase_off); try self.populateLazyBindOffsetsInStubHelper( - buffer[dyld_info.rebase_size + dyld_info.bind_size ..][0..dyld_info.lazy_bind_size], + buffer[dyld_info.lazy_bind_off - base_off ..][0..dyld_info.lazy_bind_size], ); self.load_commands_dirty = true; } @@ -5932,32 +5951,31 @@ fn writeFunctionStarts(self: *MachO) !void { } else break; } - const max_size = @intCast(usize, offsets.items.len * @sizeOf(u64)); - var buffer = try self.base.allocator.alloc(u8, max_size); - defer self.base.allocator.free(buffer); - mem.set(u8, buffer, 0); + var buffer = std.ArrayList(u8).init(self.base.allocator); + defer buffer.deinit(); - var stream = std.io.fixedBufferStream(buffer); - const writer = stream.writer(); + const max_size = @intCast(usize, offsets.items.len * @sizeOf(u64)); + try buffer.ensureTotalCapacity(max_size); for (offsets.items) |offset| { - try std.leb.writeULEB128(writer, offset); + try std.leb.writeULEB128(buffer.writer(), offset); } - const needed_size = @intCast(u32, mem.alignForwardGeneric(u64, stream.pos, @sizeOf(u64))); const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment; const fn_cmd = &self.load_commands.items[self.function_starts_cmd_index.?].linkedit_data; - fn_cmd.dataoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); - fn_cmd.datasize = needed_size; - seg.inner.filesize += needed_size; + const dataoff = mem.alignForwardGeneric(u64, seg.inner.fileoff + seg.inner.filesize, @alignOf(u64)); + const datasize = buffer.items.len; + fn_cmd.dataoff = @intCast(u32, dataoff); + fn_cmd.datasize = @intCast(u32, datasize); + seg.inner.filesize = fn_cmd.dataoff + fn_cmd.datasize - seg.inner.fileoff; log.debug("writing function starts info from 0x{x} to 0x{x}", .{ fn_cmd.dataoff, fn_cmd.dataoff + fn_cmd.datasize, }); - try self.base.file.?.pwriteAll(buffer[0..needed_size], fn_cmd.dataoff); + try self.base.file.?.pwriteAll(buffer.items, fn_cmd.dataoff); self.load_commands_dirty = true; } @@ -6005,11 +6023,12 @@ fn writeDices(self: *MachO) !void { const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment; const dice_cmd = &self.load_commands.items[self.data_in_code_cmd_index.?].linkedit_data; - const needed_size = @intCast(u32, buf.items.len); - dice_cmd.dataoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); - dice_cmd.datasize = needed_size; - seg.inner.filesize += needed_size; + const dataoff = mem.alignForwardGeneric(u64, seg.inner.fileoff + seg.inner.filesize, @alignOf(u64)); + const datasize = buf.items.len; + dice_cmd.dataoff = @intCast(u32, dataoff); + dice_cmd.datasize = @intCast(u32, datasize); + seg.inner.filesize = dice_cmd.dataoff + dice_cmd.datasize - seg.inner.fileoff; log.debug("writing data-in-code from 0x{x} to 0x{x}", .{ dice_cmd.dataoff, @@ -6026,7 +6045,8 @@ fn writeSymbolTable(self: *MachO) !void { const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment; const symtab = &self.load_commands.items[self.symtab_cmd_index.?].symtab; - symtab.symoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); + const symoff = mem.alignForwardGeneric(u64, seg.inner.fileoff + seg.inner.filesize, @alignOf(macho.nlist_64)); + symtab.symoff = @intCast(u32, symoff); var locals = std.ArrayList(macho.nlist_64).init(self.base.allocator); defer locals.deinit(); @@ -6126,7 +6146,7 @@ fn writeSymbolTable(self: *MachO) !void { try self.base.file.?.pwriteAll(mem.sliceAsBytes(undefs.items), undefs_off); symtab.nsyms = @intCast(u32, nlocals + nexports + nundefs); - seg.inner.filesize += locals_size + exports_size + undefs_size; + seg.inner.filesize = symtab.symoff + symtab.nsyms * @sizeOf(macho.nlist_64) - seg.inner.fileoff; // Update dynamic symbol table. const dysymtab = &self.load_commands.items[self.dysymtab_cmd_index.?].dysymtab; @@ -6146,22 +6166,21 @@ fn writeSymbolTable(self: *MachO) !void { const nstubs = @intCast(u32, self.stubs_table.keys().len); const ngot_entries = @intCast(u32, self.got_entries_table.keys().len); - dysymtab.indirectsymoff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); + const indirectsymoff = mem.alignForwardGeneric(u64, seg.inner.fileoff + seg.inner.filesize, @alignOf(u64)); + dysymtab.indirectsymoff = @intCast(u32, indirectsymoff); dysymtab.nindirectsyms = nstubs * 2 + ngot_entries; - const needed_size = dysymtab.nindirectsyms * @sizeOf(u32); - seg.inner.filesize += needed_size; + seg.inner.filesize = dysymtab.indirectsymoff + dysymtab.nindirectsyms * @sizeOf(u32) - seg.inner.fileoff; log.debug("writing indirect symbol table from 0x{x} to 0x{x}", .{ dysymtab.indirectsymoff, - dysymtab.indirectsymoff + needed_size, + dysymtab.indirectsymoff + dysymtab.nindirectsyms * @sizeOf(u32), }); - var buf = try self.base.allocator.alloc(u8, needed_size); - defer self.base.allocator.free(buf); - - var stream = std.io.fixedBufferStream(buf); - var writer = stream.writer(); + var buf = std.ArrayList(u8).init(self.base.allocator); + defer buf.deinit(); + try buf.ensureTotalCapacity(dysymtab.nindirectsyms * @sizeOf(u32)); + const writer = buf.writer(); stubs.reserved1 = 0; for (self.stubs_table.keys()) |key| { @@ -6195,7 +6214,9 @@ fn writeSymbolTable(self: *MachO) !void { } } - try self.base.file.?.pwriteAll(buf, dysymtab.indirectsymoff); + assert(buf.items.len == dysymtab.nindirectsyms * @sizeOf(u32)); + + try self.base.file.?.pwriteAll(buf.items, dysymtab.indirectsymoff); self.load_commands_dirty = true; } @@ -6205,18 +6226,16 @@ fn writeStringTable(self: *MachO) !void { const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment; const symtab = &self.load_commands.items[self.symtab_cmd_index.?].symtab; - symtab.stroff = @intCast(u32, seg.inner.fileoff + seg.inner.filesize); - symtab.strsize = @intCast(u32, mem.alignForwardGeneric(u64, self.strtab.items.len, @alignOf(u64))); - seg.inner.filesize += symtab.strsize; + const stroff = mem.alignForwardGeneric(u64, seg.inner.fileoff + seg.inner.filesize, @alignOf(u64)); + const strsize = self.strtab.items.len; + symtab.stroff = @intCast(u32, stroff); + symtab.strsize = @intCast(u32, strsize); + seg.inner.filesize = symtab.stroff + symtab.strsize - seg.inner.fileoff; log.debug("writing string table from 0x{x} to 0x{x}", .{ symtab.stroff, symtab.stroff + symtab.strsize }); try self.base.file.?.pwriteAll(self.strtab.items, symtab.stroff); - if (symtab.strsize > self.strtab.items.len) { - // This is potentially the last section, so we need to pad it out. - try self.base.file.?.pwriteAll(&[_]u8{0}, seg.inner.fileoff + seg.inner.filesize - 1); - } self.load_commands_dirty = true; } @@ -6240,25 +6259,22 @@ fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void { const tracy = trace(@src()); defer tracy.end(); - const linkedit_segment = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment; - const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].linkedit_data; + const seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].segment; + const cs_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].linkedit_data; // Code signature data has to be 16-bytes aligned for Apple tools to recognize the file // https://github.com/opensource-apple/cctools/blob/fdb4825f303fd5c0751be524babd32958181b3ed/libstuff/checkout.c#L271 - const fileoff = mem.alignForwardGeneric(u64, linkedit_segment.inner.fileoff + linkedit_segment.inner.filesize, 16); - const padding = fileoff - (linkedit_segment.inner.fileoff + linkedit_segment.inner.filesize); - const needed_size = code_sig.estimateSize(fileoff); - code_sig_cmd.dataoff = @intCast(u32, fileoff); - code_sig_cmd.datasize = needed_size; + const dataoff = mem.alignForwardGeneric(u64, seg.inner.fileoff + seg.inner.filesize, 16); + const datasize = code_sig.estimateSize(dataoff); + cs_cmd.dataoff = @intCast(u32, dataoff); + cs_cmd.datasize = @intCast(u32, code_sig.estimateSize(dataoff)); // Advance size of __LINKEDIT segment - linkedit_segment.inner.filesize += needed_size + padding; - if (linkedit_segment.inner.vmsize < linkedit_segment.inner.filesize) { - linkedit_segment.inner.vmsize = mem.alignForwardGeneric(u64, linkedit_segment.inner.filesize, self.page_size); - } - log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ fileoff, fileoff + needed_size }); + seg.inner.filesize = cs_cmd.dataoff + cs_cmd.datasize - seg.inner.fileoff; + seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size); + log.debug("writing code signature padding from 0x{x} to 0x{x}", .{ dataoff, dataoff + datasize }); // Pad out the space. We need to do this to calculate valid hashes for everything in the file // except for code signature data. - try self.base.file.?.pwriteAll(&[_]u8{0}, fileoff + needed_size - 1); + try self.base.file.?.pwriteAll(&[_]u8{0}, dataoff + datasize - 1); self.load_commands_dirty = true; } From 6e04c2faabf4d632f80fa97ccbb0a20ad42a5e9f Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 23 Jun 2022 12:14:10 +0200 Subject: [PATCH 16/17] link-tests: fix parsing symtab for macho --- lib/std/build/CheckObjectStep.zig | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/std/build/CheckObjectStep.zig b/lib/std/build/CheckObjectStep.zig index 9af66d88fa..2aaec8c4a7 100644 --- a/lib/std/build/CheckObjectStep.zig +++ b/lib/std/build/CheckObjectStep.zig @@ -303,10 +303,8 @@ const MachODumper = struct { if (symtab_cmd) |cmd| { try writer.writeAll("symtab\n"); const strtab = bytes[cmd.stroff..][0..cmd.strsize]; - const symtab = @ptrCast( - [*]const macho.nlist_64, - @alignCast(@alignOf(macho.nlist_64), bytes.ptr + cmd.symoff), - )[0..cmd.nsyms]; + const raw_symtab = bytes[cmd.symoff..][0 .. cmd.nsyms * @sizeOf(macho.nlist_64)]; + const symtab = mem.bytesAsSlice(macho.nlist_64, raw_symtab); for (symtab) |sym| { if (sym.stab()) continue; From 03ddb42b8bb96815c1bb4b857ffdfb94191ab861 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 23 Jun 2022 12:56:28 +0200 Subject: [PATCH 17/17] link-tests: rename check() to checkStart() Do not hardcode the symtab label; instead allow each parser to define its own. Check for missing extractor value in the matcher when matching `{}`. --- lib/std/build/CheckObjectStep.zig | 15 +++++++++++---- test/link/macho/dylib/build.zig | 6 +++--- test/link/macho/entry/build.zig | 4 ++-- test/link/macho/frameworks/build.zig | 4 ++-- test/link/macho/pagezero/build.zig | 6 +++--- test/link/macho/stack_size/build.zig | 2 +- 6 files changed, 22 insertions(+), 15 deletions(-) diff --git a/lib/std/build/CheckObjectStep.zig b/lib/std/build/CheckObjectStep.zig index 2aaec8c4a7..65a57f8832 100644 --- a/lib/std/build/CheckObjectStep.zig +++ b/lib/std/build/CheckObjectStep.zig @@ -83,6 +83,7 @@ const MatchAction = struct { if (closing_brace != needle_tok.len - 1) return error.ClosingBraceNotLast; const name = needle_tok[1..closing_brace]; + if (name.len == 0) return error.MissingBraceValue; const value = try std.fmt.parseInt(u64, hay_tok, 16); try global_vars.putNoClobber(name, value); } else { @@ -135,13 +136,13 @@ const Check = struct { }; /// Creates a new sequence of actions with `phrase` as the first anchor searched phrase. -pub fn check(self: *CheckObjectStep, phrase: []const u8) void { +pub fn checkStart(self: *CheckObjectStep, phrase: []const u8) void { var new_check = Check.create(self.builder); new_check.match(phrase); self.checks.append(new_check) catch unreachable; } -/// Adds another searched phrase to the latest created Check with `CheckObjectStep.check(...)`. +/// Adds another searched phrase to the latest created Check with `CheckObjectStep.checkStart(...)`. /// Asserts at least one check already exists. pub fn checkNext(self: *CheckObjectStep, phrase: []const u8) void { assert(self.checks.items.len > 0); @@ -154,7 +155,11 @@ pub fn checkNext(self: *CheckObjectStep, phrase: []const u8) void { /// Issuing this check will force parsing and dumping of the symbol table. pub fn checkInSymtab(self: *CheckObjectStep) void { self.dump_symtab = true; - self.check("symtab"); + const symtab_label = switch (self.obj_format) { + .macho => MachODumper.symtab_label, + else => @panic("TODO other parsers"), + }; + self.checkStart(symtab_label); } /// Creates a new standalone, singular check which allows running simple binary operations @@ -274,6 +279,8 @@ const Opts = struct { }; const MachODumper = struct { + const symtab_label = "symtab"; + fn parseAndDump(bytes: []const u8, opts: Opts) ![]const u8 { const gpa = opts.gpa orelse unreachable; // MachO dumper requires an allocator var stream = std.io.fixedBufferStream(bytes); @@ -301,7 +308,7 @@ const MachODumper = struct { } if (symtab_cmd) |cmd| { - try writer.writeAll("symtab\n"); + try writer.writeAll(symtab_label ++ "\n"); const strtab = bytes[cmd.stroff..][0..cmd.strsize]; const raw_symtab = bytes[cmd.symoff..][0 .. cmd.nsyms * @sizeOf(macho.nlist_64)]; const symtab = mem.bytesAsSlice(macho.nlist_64, raw_symtab); diff --git a/test/link/macho/dylib/build.zig b/test/link/macho/dylib/build.zig index ad116e23e9..1587def9b8 100644 --- a/test/link/macho/dylib/build.zig +++ b/test/link/macho/dylib/build.zig @@ -14,7 +14,7 @@ pub fn build(b: *Builder) void { dylib.install(); const check_dylib = dylib.checkObject(.macho); - check_dylib.check("cmd ID_DYLIB"); + check_dylib.checkStart("cmd ID_DYLIB"); check_dylib.checkNext("name @rpath/liba.dylib"); check_dylib.checkNext("timestamp 2"); check_dylib.checkNext("current version 10000"); @@ -31,13 +31,13 @@ pub fn build(b: *Builder) void { exe.addRPath(b.pathFromRoot("zig-out/lib")); const check_exe = exe.checkObject(.macho); - check_exe.check("cmd LOAD_DYLIB"); + check_exe.checkStart("cmd LOAD_DYLIB"); check_exe.checkNext("name @rpath/liba.dylib"); check_exe.checkNext("timestamp 2"); check_exe.checkNext("current version 10000"); check_exe.checkNext("compatibility version 10000"); - check_exe.check("cmd RPATH"); + check_exe.checkStart("cmd RPATH"); check_exe.checkNext(std.fmt.allocPrint(b.allocator, "path {s}", .{b.pathFromRoot("zig-out/lib")}) catch unreachable); test_step.dependOn(&check_exe.step); diff --git a/test/link/macho/entry/build.zig b/test/link/macho/entry/build.zig index 82d3917521..f0ac084e6a 100644 --- a/test/link/macho/entry/build.zig +++ b/test/link/macho/entry/build.zig @@ -15,10 +15,10 @@ pub fn build(b: *Builder) void { const check_exe = exe.checkObject(.macho); - check_exe.check("segname __TEXT"); + check_exe.checkStart("segname __TEXT"); check_exe.checkNext("vmaddr {vmaddr}"); - check_exe.check("cmd MAIN"); + check_exe.checkStart("cmd MAIN"); check_exe.checkNext("entryoff {entryoff}"); check_exe.checkInSymtab(); diff --git a/test/link/macho/frameworks/build.zig b/test/link/macho/frameworks/build.zig index 2e1c8f3211..7086606f30 100644 --- a/test/link/macho/frameworks/build.zig +++ b/test/link/macho/frameworks/build.zig @@ -14,12 +14,12 @@ pub fn build(b: *Builder) void { exe.linkFramework("Cocoa"); const check = exe.checkObject(.macho); - check.check("cmd LOAD_DYLIB"); + check.checkStart("cmd LOAD_DYLIB"); check.checkNext("name {*}Cocoa"); switch (mode) { .Debug, .ReleaseSafe => { - check.check("cmd LOAD_DYLIB"); + check.checkStart("cmd LOAD_DYLIB"); check.checkNext("name {*}libobjc{*}.dylib"); }, else => {}, diff --git a/test/link/macho/pagezero/build.zig b/test/link/macho/pagezero/build.zig index a35be919c5..e858d1f4d8 100644 --- a/test/link/macho/pagezero/build.zig +++ b/test/link/macho/pagezero/build.zig @@ -15,12 +15,12 @@ pub fn build(b: *Builder) void { exe.pagezero_size = 0x4000; const check = exe.checkObject(.macho); - check.check("LC 0"); + check.checkStart("LC 0"); check.checkNext("segname __PAGEZERO"); check.checkNext("vmaddr 0"); check.checkNext("vmsize 4000"); - check.check("segname __TEXT"); + check.checkStart("segname __TEXT"); check.checkNext("vmaddr 4000"); test_step.dependOn(&check.step); @@ -34,7 +34,7 @@ pub fn build(b: *Builder) void { exe.pagezero_size = 0; const check = exe.checkObject(.macho); - check.check("LC 0"); + check.checkStart("LC 0"); check.checkNext("segname __TEXT"); check.checkNext("vmaddr 0"); diff --git a/test/link/macho/stack_size/build.zig b/test/link/macho/stack_size/build.zig index b840f8928c..8da59dcb53 100644 --- a/test/link/macho/stack_size/build.zig +++ b/test/link/macho/stack_size/build.zig @@ -14,7 +14,7 @@ pub fn build(b: *Builder) void { exe.stack_size = 0x100000000; const check_exe = exe.checkObject(.macho); - check_exe.check("cmd MAIN"); + check_exe.checkStart("cmd MAIN"); check_exe.checkNext("stacksize 100000000"); test_step.dependOn(&check_exe.step);