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/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/ diff --git a/lib/std/build.zig b/lib/std/build.zig index ce527ff021..57304242cd 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 CheckObjectStep = @import("build/CheckObjectStep.zig"); pub const InstallRawStep = @import("build/InstallRawStep.zig"); pub const OptionsStep = @import("build/OptionsStep.zig"); @@ -1582,6 +1583,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, @@ -1861,6 +1865,10 @@ pub const LibExeObjStep = struct { return run_step; } + 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 { self.linker_script = source.dupe(self.builder); source.addStepDependencies(&self.step); @@ -2638,6 +2646,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) { @@ -3443,6 +3455,7 @@ pub const Step = struct { write_file, run, check_file, + check_object, install_raw, options, custom, diff --git a/lib/std/build/CheckObjectStep.zig b/lib/std/build/CheckObjectStep.zig new file mode 100644 index 0000000000..65a57f8832 --- /dev/null +++ b/lib/std/build/CheckObjectStep.zig @@ -0,0 +1,392 @@ +const std = @import("../std.zig"); +const assert = std.debug.assert; +const build = std.build; +const fs = std.fs; +const macho = std.macho; +const mem = std.mem; +const testing = std.testing; + +const CheckObjectStep = @This(); + +const Allocator = mem.Allocator; +const Builder = build.Builder; +const Step = build.Step; + +pub const base_id = .check_obj; + +step: Step, +builder: *Builder, +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, obj_format: std.Target.ObjectFormat) *CheckObjectStep { + const gpa = builder.allocator; + const self = gpa.create(CheckObjectStep) catch unreachable; + self.* = .{ + .builder = builder, + .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; +} + +const Action = union(enum) { + match: MatchAction, + 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, " "), " "); + + 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]; + if (name.len == 0) return error.MissingBraceValue; + 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; + } +}; + +/// 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), + op_stack: std.ArrayList(Op), + + const Op = enum { + add, + }; +}; + +const Check = struct { + builder: *Builder, + actions: std.ArrayList(Action), + + fn create(b: *Builder) Check { + return .{ + .builder = b, + .actions = std.ArrayList(Action).init(b.allocator), + }; + } + + fn match(self: *Check, needle: []const u8) void { + self.actions.append(.{ + .match = .{ .needle = self.builder.dupe(needle) }, + }) catch unreachable; + } + + fn computeEq(self: *Check, act: ComputeEqAction) void { + self.actions.append(.{ + .compute_eq = act, + }) catch unreachable; + } +}; + +/// Creates a new sequence of actions with `phrase` as the first anchor searched phrase. +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.checkStart(...)`. +/// 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; + 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 +/// 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{ + .expected = expected, + .var_stack = std.ArrayList([]const u8).init(gpa), + .op_stack = std.ArrayList(ComputeEqAction.Op).init(gpa), + }; + + var it = mem.tokenize(u8, program, " "); + 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.computeEq(ca); + self.checks.append(new_check) catch unreachable; +} + +fn make(step: *Step) !void { + 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); + + 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); + + for (self.checks.items) |chk| { + 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)) break; + } else { + std.debug.print( + \\ + \\========= Expected to find: ========================== + \\{s} + \\========= But parsed file does not contain it: ======= + \\{s} + \\ + , .{ match_act.needle, output }); + 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 { + std.debug.print( + \\ + \\========= Variable was not extracted: =========== + \\{s} + \\========= From parsed file: ===================== + \\{s} + \\ + , .{ vv, output }); + return error.TestFailed; + }; + values.appendAssumeCapacity(val); + } + + 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; + }, + } + } + + 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); + }, + } + } + } +} + +const Opts = struct { + gpa: ?Allocator = null, + dump_symtab: bool = false, +}; + +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); + 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'); + } + + if (symtab_cmd) |cmd| { + 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); + + 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 }); + } + } + + 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( + \\name {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/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/src/link/MachO.zig b/src/link/MachO.zig index aea44d9357..a7681e976d 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; @@ -5711,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); @@ -5740,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); @@ -5758,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; } @@ -5928,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; } @@ -6001,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, @@ -6022,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(); @@ -6122,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; @@ -6142,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| { @@ -6191,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; } @@ -6201,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; } @@ -6236,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; } diff --git a/test/link.zig b/test/link.zig new file mode 100644 index 0000000000..42ce12f73c --- /dev/null +++ b/test/link.zig @@ -0,0 +1,64 @@ +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/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/macho/entry/build.zig", .{ + .build_modes = true, + }); + + 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/macho/objc/build.zig", .{ + .build_modes = true, + .requires_macos_sdk = true, + }); + + // Try to build and run an Objective-C++ executable. + 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/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/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/link/macho/dylib/a.c b/test/link/macho/dylib/a.c new file mode 100644 index 0000000000..199b31e1a0 --- /dev/null +++ b/test/link/macho/dylib/a.c @@ -0,0 +1,7 @@ +#include + +char world[] = "world"; + +char* hello() { + return "Hello"; +} diff --git a/test/link/macho/dylib/build.zig b/test/link/macho/dylib/build.zig new file mode 100644 index 0000000000..1587def9b8 --- /dev/null +++ b/test/link/macho/dylib/build.zig @@ -0,0 +1,49 @@ +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 dylib = b.addSharedLibrary("a", null, b.version(1, 0, 0)); + dylib.setBuildMode(mode); + dylib.addCSourceFile("a.c", &.{}); + dylib.linkLibC(); + dylib.install(); + + const check_dylib = dylib.checkObject(.macho); + check_dylib.checkStart("cmd ID_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"); + + test_step.dependOn(&check_dylib.step); + + 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 check_exe = exe.checkObject(.macho); + 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.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); + + const run = exe.run(); + run.cwd = b.pathFromRoot("."); + run.expectStdOutEqual("Hello world"); + test_step.dependOn(&run.step); +} diff --git a/test/link/macho/dylib/main.c b/test/link/macho/dylib/main.c new file mode 100644 index 0000000000..be1647ddad --- /dev/null +++ b/test/link/macho/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/link/macho/entry/build.zig b/test/link/macho/entry/build.zig new file mode 100644 index 0000000000..f0ac084e6a --- /dev/null +++ b/test/link/macho/entry/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"); + 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.checkObject(.macho); + + check_exe.checkStart("segname __TEXT"); + check_exe.checkNext("vmaddr {vmaddr}"); + + check_exe.checkStart("cmd MAIN"); + check_exe.checkNext("entryoff {entryoff}"); + + check_exe.checkInSymtab(); + check_exe.checkNext("_non_main {n_value}"); + + check_exe.checkComputeEq("vmaddr entryoff +", "n_value"); + + 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/macho/frameworks/build.zig b/test/link/macho/frameworks/build.zig new file mode 100644 index 0000000000..7086606f30 --- /dev/null +++ b/test/link/macho/frameworks/build.zig @@ -0,0 +1,32 @@ +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 the program"); + + const exe = b.addExecutable("test", null); + b.default_step.dependOn(&exe.step); + exe.addCSourceFile("main.c", &[0][]const u8{}); + exe.setBuildMode(mode); + exe.linkLibC(); + exe.linkFramework("Cocoa"); + + const check = exe.checkObject(.macho); + check.checkStart("cmd LOAD_DYLIB"); + check.checkNext("name {*}Cocoa"); + + switch (mode) { + .Debug, .ReleaseSafe => { + check.checkStart("cmd LOAD_DYLIB"); + check.checkNext("name {*}libobjc{*}.dylib"); + }, + else => {}, + } + + test_step.dependOn(&check.step); + + const run_cmd = exe.run(); + test_step.dependOn(&run_cmd.step); +} diff --git a/test/standalone/link_frameworks/main.c b/test/link/macho/frameworks/main.c similarity index 100% rename from test/standalone/link_frameworks/main.c rename to test/link/macho/frameworks/main.c diff --git a/test/standalone/objc/Foo.h b/test/link/macho/objc/Foo.h similarity index 100% rename from test/standalone/objc/Foo.h rename to test/link/macho/objc/Foo.h diff --git a/test/standalone/objc/Foo.m b/test/link/macho/objc/Foo.m similarity index 100% rename from test/standalone/objc/Foo.m rename to test/link/macho/objc/Foo.m diff --git a/test/standalone/objc/build.zig b/test/link/macho/objc/build.zig similarity index 54% rename from test/standalone/objc/build.zig rename to test/link/macho/objc/build.zig index 1b3a90f90f..e41fd48e71 100644 --- a/test/standalone/objc/build.zig +++ b/test/link/macho/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/macho/objc/test.m similarity index 100% rename from test/standalone/objc/test.m rename to test/link/macho/objc/test.m diff --git a/test/standalone/objcpp/Foo.h b/test/link/macho/objcpp/Foo.h similarity index 100% rename from test/standalone/objcpp/Foo.h rename to test/link/macho/objcpp/Foo.h diff --git a/test/standalone/objcpp/Foo.mm b/test/link/macho/objcpp/Foo.mm similarity index 100% rename from test/standalone/objcpp/Foo.mm rename to test/link/macho/objcpp/Foo.mm diff --git a/test/standalone/objcpp/build.zig b/test/link/macho/objcpp/build.zig similarity index 54% rename from test/standalone/objcpp/build.zig rename to test/link/macho/objcpp/build.zig index 688592793a..767578e225 100644 --- a/test/standalone/objcpp/build.zig +++ b/test/link/macho/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/macho/objcpp/test.mm similarity index 100% rename from test/standalone/objcpp/test.mm rename to test/link/macho/objcpp/test.mm diff --git a/test/link/macho/pagezero/build.zig b/test/link/macho/pagezero/build.zig new file mode 100644 index 0000000000..e858d1f4d8 --- /dev/null +++ b/test/link/macho/pagezero/build.zig @@ -0,0 +1,43 @@ +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("pagezero", null); + exe.setBuildMode(mode); + exe.addCSourceFile("main.c", &.{}); + exe.linkLibC(); + exe.pagezero_size = 0x4000; + + const check = exe.checkObject(.macho); + check.checkStart("LC 0"); + check.checkNext("segname __PAGEZERO"); + check.checkNext("vmaddr 0"); + check.checkNext("vmsize 4000"); + + check.checkStart("segname __TEXT"); + check.checkNext("vmaddr 4000"); + + test_step.dependOn(&check.step); + } + + { + const exe = b.addExecutable("no_pagezero", null); + exe.setBuildMode(mode); + exe.addCSourceFile("main.c", &.{}); + exe.linkLibC(); + exe.pagezero_size = 0; + + const check = exe.checkObject(.macho); + check.checkStart("LC 0"); + check.checkNext("segname __TEXT"); + check.checkNext("vmaddr 0"); + + test_step.dependOn(&check.step); + } +} diff --git a/test/link/macho/pagezero/main.c b/test/link/macho/pagezero/main.c new file mode 100644 index 0000000000..ca68d24cc7 --- /dev/null +++ b/test/link/macho/pagezero/main.c @@ -0,0 +1,3 @@ +int main(int argc, char* argv[]) { + return 0; +} diff --git a/test/link/macho/stack_size/build.zig b/test/link/macho/stack_size/build.zig new file mode 100644 index 0000000000..8da59dcb53 --- /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.checkObject(.macho); + check_exe.checkStart("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; +} 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_frameworks/build.zig b/test/standalone/link_frameworks/build.zig deleted file mode 100644 index 460e1675e0..0000000000 --- a/test/standalone/link_frameworks/build.zig +++ /dev/null @@ -1,34 +0,0 @@ -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"); - - const exe = b.addExecutable("test", null); - 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); - } -} 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..d1f319673c 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; @@ -973,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; }