mirror of
https://github.com/ziglang/zig.git
synced 2026-02-12 20:37:54 +00:00
Merge pull request #11910 from ziglang/linker-tests
This commit is contained in:
commit
291c08f7b0
@ -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));
|
||||
|
||||
@ -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/
|
||||
|
||||
@ -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,
|
||||
|
||||
392
lib/std/build/CheckObjectStep.zig
Normal file
392
lib/std/build/CheckObjectStep.zig
Normal file
@ -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 => {},
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -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),
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
64
test/link.zig
Normal file
64
test/link.zig
Normal file
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
14
test/link/bss/build.zig
Normal file
14
test/link/bss/build.zig
Normal file
@ -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);
|
||||
}
|
||||
13
test/link/bss/main.zig
Normal file
13
test/link/bss/main.zig
Normal file
@ -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],
|
||||
});
|
||||
}
|
||||
7
test/link/macho/dylib/a.c
Normal file
7
test/link/macho/dylib/a.c
Normal file
@ -0,0 +1,7 @@
|
||||
#include <stdio.h>
|
||||
|
||||
char world[] = "world";
|
||||
|
||||
char* hello() {
|
||||
return "Hello";
|
||||
}
|
||||
49
test/link/macho/dylib/build.zig
Normal file
49
test/link/macho/dylib/build.zig
Normal file
@ -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);
|
||||
}
|
||||
9
test/link/macho/dylib/main.c
Normal file
9
test/link/macho/dylib/main.c
Normal file
@ -0,0 +1,9 @@
|
||||
#include <stdio.h>
|
||||
|
||||
char* hello();
|
||||
extern char world[];
|
||||
|
||||
int main() {
|
||||
printf("%s %s", hello(), world);
|
||||
return 0;
|
||||
}
|
||||
34
test/link/macho/entry/build.zig
Normal file
34
test/link/macho/entry/build.zig
Normal file
@ -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);
|
||||
}
|
||||
6
test/link/macho/entry/main.c
Normal file
6
test/link/macho/entry/main.c
Normal file
@ -0,0 +1,6 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int non_main() {
|
||||
printf("%d", 42);
|
||||
return 0;
|
||||
}
|
||||
32
test/link/macho/frameworks/build.zig
Normal file
32
test/link/macho/frameworks/build.zig
Normal file
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
43
test/link/macho/pagezero/build.zig
Normal file
43
test/link/macho/pagezero/build.zig
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
3
test/link/macho/pagezero/main.c
Normal file
3
test/link/macho/pagezero/main.c
Normal file
@ -0,0 +1,3 @@
|
||||
int main(int argc, char* argv[]) {
|
||||
return 0;
|
||||
}
|
||||
24
test/link/macho/stack_size/build.zig
Normal file
24
test/link/macho/stack_size/build.zig
Normal file
@ -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);
|
||||
}
|
||||
3
test/link/macho/stack_size/main.c
Normal file
3
test/link/macho/stack_size/main.c
Normal file
@ -0,0 +1,3 @@
|
||||
int main(int argc, char* argv[]) {
|
||||
return 0;
|
||||
}
|
||||
5
test/link/tls/a.c
Normal file
5
test/link/tls/a.c
Normal file
@ -0,0 +1,5 @@
|
||||
_Thread_local int a;
|
||||
|
||||
int getA() {
|
||||
return a;
|
||||
}
|
||||
@ -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);
|
||||
15
test/link/tls/main.zig
Normal file
15
test/link/tls/main.zig
Normal file
@ -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());
|
||||
}
|
||||
@ -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");
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
_Thread_local int a;
|
||||
@ -1,7 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
extern threadlocal var a: i32;
|
||||
|
||||
test {
|
||||
try std.testing.expect(a == 0);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user