declare linker test bankruptcy

The active contributors and maintainers of Zig's linker code have
generally found the current linker test harness to be cumbersome. The
tests require a lot of maintenance, but do not provide a lot of
coverage, and when they fail it is painful to troubleshoot.

Furthermore, as part of working on #31691, I don't want to port over the
CheckObject step, because I don't like the code anyway.

The plan forward is to start enhancing `zig objdump` to assist in
linker development, as well as using it as the basis for snapshot
testing.

We absolutely need linker test coverage, but we need to try to improve
these things about the next attempt:
* less effort to create and maintain tests
* less CPU overhead - we should be able to add a lot of tests without
  adding a lot of CI time.
* more helpful failures. A failed linker test should provide the next
  steps a developer can take to understand why the test failed.
* a goal of porting over all of LLD's test suite, or at least the good
  ones.

I'm not going to open an issue to track the lost linker test coverage,
because there was already so much lack of coverage for linker stuff.

However I will open issues to track this lost coverage:
* the deleted checks from test/standalone/glibc_compat/build.zig
* the deleted checks from test/standalone/compiler_rt_panic/build.zig
* the deleted checks from test/standalone/ios/build.zig
This commit is contained in:
Andrew Kelley
2026-04-24 19:43:45 -07:00
parent 23bcb8148f
commit e7d74e49b0
60 changed files with 0 additions and 11870 deletions
-1
View File
@@ -618,7 +618,6 @@ pub fn build(b: *std.Build) !void {
.skip_llvm = skip_llvm,
.max_rss = 3_300_000_000,
}));
test_step.dependOn(tests.addLinkTests(b, enable_macos_sdk, enable_ios_sdk, enable_symlinks_windows));
test_step.dependOn(tests.addStackTraceTests(b, test_filters, skip_non_native));
test_step.dependOn(tests.addErrorTraceTests(b, test_filters, optimize_modes, skip_non_native));
test_step.dependOn(tests.addCliTests(b));
-3
View File
@@ -176,7 +176,6 @@ pub const Id = enum {
.update_source_files => UpdateSourceFiles,
.run => Run,
.check_file => CheckFile,
.check_object => CheckObject,
.config_header => ConfigHeader,
.objcopy => ObjCopy,
.options => Options,
@@ -186,7 +185,6 @@ pub const Id = enum {
};
pub const CheckFile = @import("Step/CheckFile.zig");
pub const CheckObject = @import("Step/CheckObject.zig");
pub const ConfigHeader = @import("Step/ConfigHeader.zig");
pub const Fail = @import("Step/Fail.zig");
pub const Fmt = @import("Step/Fmt.zig");
@@ -1004,7 +1002,6 @@ pub fn invalidateResult(step: *Step, gpa: Allocator) bool {
test {
_ = CheckFile;
_ = CheckObject;
_ = Fail;
_ = Fmt;
_ = InstallArtifact;
-2764
View File
@@ -1,2764 +0,0 @@
const std = @import("std");
const assert = std.debug.assert;
const elf = std.elf;
const fs = std.fs;
const macho = std.macho;
const math = std.math;
const mem = std.mem;
const testing = std.testing;
const Writer = std.Io.Writer;
const CheckObject = @This();
const Allocator = mem.Allocator;
const Step = std.Build.Step;
pub const base_id: Step.Id = .check_object;
step: Step,
source: std.Build.LazyPath,
max_bytes: usize = 20 * 1024 * 1024,
checks: std.array_list.Managed(Check),
obj_format: std.Target.ObjectFormat,
pub fn create(
owner: *std.Build,
source: std.Build.LazyPath,
obj_format: std.Target.ObjectFormat,
) *CheckObject {
const gpa = owner.allocator;
const check_object = gpa.create(CheckObject) catch @panic("OOM");
check_object.* = .{
.step = .init(.{
.id = base_id,
.name = "CheckObject",
.owner = owner,
.makeFn = make,
}),
.source = source.dupe(owner),
.checks = std.array_list.Managed(Check).init(gpa),
.obj_format = obj_format,
};
check_object.source.addStepDependencies(&check_object.step);
return check_object;
}
const SearchPhrase = struct {
string: []const u8,
lazy_path: ?std.Build.LazyPath = null,
fn resolve(phrase: SearchPhrase, b: *std.Build, step: *Step) []const u8 {
const lazy_path = phrase.lazy_path orelse return phrase.string;
return b.fmt("{s} {s}", .{ phrase.string, lazy_path.getPath2(b, step) });
}
};
/// There five types of actions currently supported:
/// .exact - will do an exact match against the haystack
/// .contains - will check for existence within the haystack
/// .not_present - will check for non-existence within the haystack
/// .extract - will do an exact match and extract into a variable enclosed within `{name}` braces
/// .compute_cmp - will 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 Action = struct {
tag: enum { exact, contains, not_present, extract, compute_cmp },
phrase: SearchPhrase,
expected: ?ComputeCompareExpected = null,
/// Returns true if the `phrase` is an exact match with the haystack and variable was successfully extracted.
fn extract(
act: Action,
b: *std.Build,
step: *Step,
haystack: []const u8,
global_vars: anytype,
) !bool {
assert(act.tag == .extract);
const hay = mem.trim(u8, haystack, " ");
const phrase = mem.trim(u8, act.phrase.resolve(b, step), " ");
var candidate_vars: std.array_list.Managed(struct { name: []const u8, value: u64 }) = .init(b.allocator);
var hay_it = mem.tokenizeScalar(u8, hay, ' ');
var needle_it = mem.tokenizeScalar(u8, phrase, ' ');
while (needle_it.next()) |needle_tok| {
const hay_tok = hay_it.next() orelse break;
if (mem.startsWith(u8, needle_tok, "{")) {
const closing_brace = mem.find(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 = std.fmt.parseInt(u64, hay_tok, 16) catch return false;
try candidate_vars.append(.{
.name = name,
.value = value,
});
} else {
if (!mem.eql(u8, hay_tok, needle_tok)) return false;
}
}
if (candidate_vars.items.len == 0) return false;
for (candidate_vars.items) |cv| try global_vars.putNoClobber(cv.name, cv.value);
return true;
}
/// Returns true if the `phrase` is an exact match with the haystack.
fn exact(
act: Action,
b: *std.Build,
step: *Step,
haystack: []const u8,
) bool {
assert(act.tag == .exact);
const hay = mem.trim(u8, haystack, " ");
const phrase = mem.trim(u8, act.phrase.resolve(b, step), " ");
return mem.eql(u8, hay, phrase);
}
/// Returns true if the `phrase` exists within the haystack.
fn contains(
act: Action,
b: *std.Build,
step: *Step,
haystack: []const u8,
) bool {
assert(act.tag == .contains);
const hay = mem.trim(u8, haystack, " ");
const phrase = mem.trim(u8, act.phrase.resolve(b, step), " ");
return mem.find(u8, hay, phrase) != null;
}
/// Returns true if the `phrase` does not exist within the haystack.
fn notPresent(
act: Action,
b: *std.Build,
step: *Step,
haystack: []const u8,
) bool {
assert(act.tag == .not_present);
return !contains(.{
.tag = .contains,
.phrase = act.phrase,
.expected = act.expected,
}, b, step, haystack);
}
/// Will return true if the `phrase` is correctly parsed into an RPN program and
/// its reduced, computed value compares using `op` with the expected value, either
/// a literal or another extracted variable.
fn computeCmp(act: Action, b: *std.Build, step: *Step, global_vars: anytype) !bool {
const gpa = step.owner.allocator;
const phrase = act.phrase.resolve(b, step);
var op_stack = std.array_list.Managed(enum { add, sub, mod, mul }).init(gpa);
var values = std.array_list.Managed(u64).init(gpa);
var it = mem.tokenizeScalar(u8, phrase, ' ');
while (it.next()) |next| {
if (mem.eql(u8, next, "+")) {
try op_stack.append(.add);
} else if (mem.eql(u8, next, "-")) {
try op_stack.append(.sub);
} else if (mem.eql(u8, next, "%")) {
try op_stack.append(.mod);
} else if (mem.eql(u8, next, "*")) {
try op_stack.append(.mul);
} else {
const val = std.fmt.parseInt(u64, next, 0) catch blk: {
break :blk global_vars.get(next) orelse {
try step.addError(
\\
\\========= variable was not extracted: ===========
\\{s}
\\=================================================
, .{next});
return error.UnknownVariable;
};
};
try values.append(val);
}
}
var op_i: usize = 1;
var reduced: u64 = values.items[0];
for (op_stack.items) |op| {
const other = values.items[op_i];
switch (op) {
.add => {
reduced += other;
},
.sub => {
reduced -= other;
},
.mod => {
reduced %= other;
},
.mul => {
reduced *= other;
},
}
op_i += 1;
}
const exp_value = switch (act.expected.?.value) {
.variable => |name| global_vars.get(name) orelse {
try step.addError(
\\
\\========= variable was not extracted: ===========
\\{s}
\\=================================================
, .{name});
return error.UnknownVariable;
},
.literal => |x| x,
};
return math.compare(reduced, act.expected.?.op, exp_value);
}
};
const ComputeCompareExpected = struct {
op: math.CompareOperator,
value: union(enum) {
variable: []const u8,
literal: u64,
},
pub fn format(value: ComputeCompareExpected, w: *Writer) Writer.Error!void {
try w.print("{t} ", .{value.op});
switch (value.value) {
.variable => |name| try w.writeAll(name),
.literal => |x| try w.print("{x}", .{x}),
}
}
};
const Check = struct {
kind: Kind,
payload: Payload,
data: std.array_list.Managed(u8),
actions: std.array_list.Managed(Action),
fn create(allocator: Allocator, kind: Kind) Check {
return .{
.kind = kind,
.payload = .{ .none = {} },
.data = std.array_list.Managed(u8).init(allocator),
.actions = std.array_list.Managed(Action).init(allocator),
};
}
fn dumpSection(allocator: Allocator, name: [:0]const u8) Check {
var check = Check.create(allocator, .dump_section);
const off: u32 = @intCast(check.data.items.len);
check.data.print("{s}\x00", .{name}) catch @panic("OOM");
check.payload = .{ .dump_section = off };
return check;
}
fn extract(check: *Check, phrase: SearchPhrase) void {
check.actions.append(.{
.tag = .extract,
.phrase = phrase,
}) catch @panic("OOM");
}
fn exact(check: *Check, phrase: SearchPhrase) void {
check.actions.append(.{
.tag = .exact,
.phrase = phrase,
}) catch @panic("OOM");
}
fn contains(check: *Check, phrase: SearchPhrase) void {
check.actions.append(.{
.tag = .contains,
.phrase = phrase,
}) catch @panic("OOM");
}
fn notPresent(check: *Check, phrase: SearchPhrase) void {
check.actions.append(.{
.tag = .not_present,
.phrase = phrase,
}) catch @panic("OOM");
}
fn computeCmp(check: *Check, phrase: SearchPhrase, expected: ComputeCompareExpected) void {
check.actions.append(.{
.tag = .compute_cmp,
.phrase = phrase,
.expected = expected,
}) catch @panic("OOM");
}
const Kind = enum {
headers,
symtab,
indirect_symtab,
dynamic_symtab,
archive_symtab,
dynamic_section,
dyld_rebase,
dyld_bind,
dyld_weak_bind,
dyld_lazy_bind,
exports,
compute_compare,
dump_section,
};
const Payload = union {
none: void,
/// Null-delimited string in the 'data' buffer.
dump_section: u32,
};
};
/// Creates a new empty sequence of actions.
fn checkStart(check_object: *CheckObject, kind: Check.Kind) void {
const check = Check.create(check_object.step.owner.allocator, kind);
check_object.checks.append(check) catch @panic("OOM");
}
/// Adds an exact match phrase to the latest created Check.
pub fn checkExact(check_object: *CheckObject, phrase: []const u8) void {
check_object.checkExactInner(phrase, null);
}
/// Like `checkExact()` but takes an additional argument `LazyPath` which will be
/// resolved to a full search query in `make()`.
pub fn checkExactPath(check_object: *CheckObject, phrase: []const u8, lazy_path: std.Build.LazyPath) void {
check_object.checkExactInner(phrase, lazy_path);
}
fn checkExactInner(check_object: *CheckObject, phrase: []const u8, lazy_path: ?std.Build.LazyPath) void {
assert(check_object.checks.items.len > 0);
const last = &check_object.checks.items[check_object.checks.items.len - 1];
last.exact(.{ .string = check_object.step.owner.dupe(phrase), .lazy_path = lazy_path });
}
/// Adds a fuzzy match phrase to the latest created Check.
pub fn checkContains(check_object: *CheckObject, phrase: []const u8) void {
check_object.checkContainsInner(phrase, null);
}
/// Like `checkContains()` but takes an additional argument `lazy_path` which will be
/// resolved to a full search query in `make()`.
pub fn checkContainsPath(
check_object: *CheckObject,
phrase: []const u8,
lazy_path: std.Build.LazyPath,
) void {
check_object.checkContainsInner(phrase, lazy_path);
}
fn checkContainsInner(check_object: *CheckObject, phrase: []const u8, lazy_path: ?std.Build.LazyPath) void {
assert(check_object.checks.items.len > 0);
const last = &check_object.checks.items[check_object.checks.items.len - 1];
last.contains(.{ .string = check_object.step.owner.dupe(phrase), .lazy_path = lazy_path });
}
/// Adds an exact match phrase with variable extractor to the latest created Check.
pub fn checkExtract(check_object: *CheckObject, phrase: []const u8) void {
check_object.checkExtractInner(phrase, null);
}
/// Like `checkExtract()` but takes an additional argument `LazyPath` which will be
/// resolved to a full search query in `make()`.
pub fn checkExtractLazyPath(check_object: *CheckObject, phrase: []const u8, lazy_path: std.Build.LazyPath) void {
check_object.checkExtractInner(phrase, lazy_path);
}
fn checkExtractInner(check_object: *CheckObject, phrase: []const u8, lazy_path: ?std.Build.LazyPath) void {
assert(check_object.checks.items.len > 0);
const last = &check_object.checks.items[check_object.checks.items.len - 1];
last.extract(.{ .string = check_object.step.owner.dupe(phrase), .lazy_path = lazy_path });
}
/// Adds another searched phrase to the latest created Check
/// however ensures there is no matching phrase in the output.
pub fn checkNotPresent(check_object: *CheckObject, phrase: []const u8) void {
check_object.checkNotPresentInner(phrase, null);
}
/// Like `checkExtract()` but takes an additional argument `LazyPath` which will be
/// resolved to a full search query in `make()`.
pub fn checkNotPresentLazyPath(check_object: *CheckObject, phrase: []const u8, lazy_path: std.Build.LazyPath) void {
check_object.checkNotPresentInner(phrase, lazy_path);
}
fn checkNotPresentInner(check_object: *CheckObject, phrase: []const u8, lazy_path: ?std.Build.LazyPath) void {
assert(check_object.checks.items.len > 0);
const last = &check_object.checks.items[check_object.checks.items.len - 1];
last.notPresent(.{ .string = check_object.step.owner.dupe(phrase), .lazy_path = lazy_path });
}
/// Creates a new check checking in the file headers (section, program headers, etc.).
pub fn checkInHeaders(check_object: *CheckObject) void {
check_object.checkStart(.headers);
}
/// Creates a new check checking specifically symbol table parsed and dumped from the object
/// file.
pub fn checkInSymtab(check_object: *CheckObject) void {
const label = switch (check_object.obj_format) {
.macho => MachODumper.symtab_label,
.elf => ElfDumper.symtab_label,
.wasm => WasmDumper.symtab_label,
.coff => @panic("TODO symtab for coff"),
else => @panic("TODO other file formats"),
};
check_object.checkStart(.symtab);
check_object.checkExact(label);
}
/// Creates a new check checking specifically dyld rebase opcodes contents parsed and dumped
/// from the object file.
/// This check is target-dependent and applicable to MachO only.
pub fn checkInDyldRebase(check_object: *CheckObject) void {
const label = switch (check_object.obj_format) {
.macho => MachODumper.dyld_rebase_label,
else => @panic("Unsupported target platform"),
};
check_object.checkStart(.dyld_rebase);
check_object.checkExact(label);
}
/// Creates a new check checking specifically dyld bind opcodes contents parsed and dumped
/// from the object file.
/// This check is target-dependent and applicable to MachO only.
pub fn checkInDyldBind(check_object: *CheckObject) void {
const label = switch (check_object.obj_format) {
.macho => MachODumper.dyld_bind_label,
else => @panic("Unsupported target platform"),
};
check_object.checkStart(.dyld_bind);
check_object.checkExact(label);
}
/// Creates a new check checking specifically dyld weak bind opcodes contents parsed and dumped
/// from the object file.
/// This check is target-dependent and applicable to MachO only.
pub fn checkInDyldWeakBind(check_object: *CheckObject) void {
const label = switch (check_object.obj_format) {
.macho => MachODumper.dyld_weak_bind_label,
else => @panic("Unsupported target platform"),
};
check_object.checkStart(.dyld_weak_bind);
check_object.checkExact(label);
}
/// Creates a new check checking specifically dyld lazy bind opcodes contents parsed and dumped
/// from the object file.
/// This check is target-dependent and applicable to MachO only.
pub fn checkInDyldLazyBind(check_object: *CheckObject) void {
const label = switch (check_object.obj_format) {
.macho => MachODumper.dyld_lazy_bind_label,
else => @panic("Unsupported target platform"),
};
check_object.checkStart(.dyld_lazy_bind);
check_object.checkExact(label);
}
/// Creates a new check checking specifically exports info contents parsed and dumped
/// from the object file.
/// This check is target-dependent and applicable to MachO only.
pub fn checkInExports(check_object: *CheckObject) void {
const label = switch (check_object.obj_format) {
.macho => MachODumper.exports_label,
else => @panic("Unsupported target platform"),
};
check_object.checkStart(.exports);
check_object.checkExact(label);
}
/// Creates a new check checking specifically indirect symbol table parsed and dumped
/// from the object file.
/// This check is target-dependent and applicable to MachO only.
pub fn checkInIndirectSymtab(check_object: *CheckObject) void {
const label = switch (check_object.obj_format) {
.macho => MachODumper.indirect_symtab_label,
else => @panic("Unsupported target platform"),
};
check_object.checkStart(.indirect_symtab);
check_object.checkExact(label);
}
/// Creates a new check checking specifically dynamic symbol table parsed and dumped from the object
/// file.
/// This check is target-dependent and applicable to ELF only.
pub fn checkInDynamicSymtab(check_object: *CheckObject) void {
const label = switch (check_object.obj_format) {
.elf => ElfDumper.dynamic_symtab_label,
else => @panic("Unsupported target platform"),
};
check_object.checkStart(.dynamic_symtab);
check_object.checkExact(label);
}
/// Creates a new check checking specifically dynamic section parsed and dumped from the object
/// file.
/// This check is target-dependent and applicable to ELF only.
pub fn checkInDynamicSection(check_object: *CheckObject) void {
const label = switch (check_object.obj_format) {
.elf => ElfDumper.dynamic_section_label,
else => @panic("Unsupported target platform"),
};
check_object.checkStart(.dynamic_section);
check_object.checkExact(label);
}
/// Creates a new check checking specifically symbol table parsed and dumped from the archive
/// file.
pub fn checkInArchiveSymtab(check_object: *CheckObject) void {
const label = switch (check_object.obj_format) {
.elf => ElfDumper.archive_symtab_label,
else => @panic("TODO other file formats"),
};
check_object.checkStart(.archive_symtab);
check_object.checkExact(label);
}
pub fn dumpSection(check_object: *CheckObject, name: [:0]const u8) void {
const check = Check.dumpSection(check_object.step.owner.allocator, name);
check_object.checks.append(check) catch @panic("OOM");
}
/// 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 checkComputeCompare(
check_object: *CheckObject,
program: []const u8,
expected: ComputeCompareExpected,
) void {
var check = Check.create(check_object.step.owner.allocator, .compute_compare);
check.computeCmp(.{ .string = check_object.step.owner.dupe(program) }, expected);
check_object.checks.append(check) catch @panic("OOM");
}
fn make(step: *Step, make_options: Step.MakeOptions) !void {
_ = make_options;
const b = step.owner;
const io = b.graph.io;
const gpa = b.allocator;
const check_object: *CheckObject = @fieldParentPtr("step", step);
try step.singleUnchangingWatchInput(check_object.source);
const src_path = check_object.source.getPath3(b, step);
const contents = src_path.root_dir.handle.readFileAllocOptions(
io,
src_path.sub_path,
gpa,
.limited(check_object.max_bytes),
.of(u64),
null,
) catch |err| return step.fail("unable to read '{f}': {t}", .{
std.fmt.alt(src_path, .formatEscapeChar), err,
});
var vars: std.StringHashMap(u64) = .init(gpa);
for (check_object.checks.items) |chk| {
if (chk.kind == .compute_compare) {
assert(chk.actions.items.len == 1);
const act = chk.actions.items[0];
assert(act.tag == .compute_cmp);
const res = act.computeCmp(b, step, vars) catch |err| switch (err) {
error.UnknownVariable => return step.fail("Unknown variable", .{}),
else => |e| return e,
};
if (!res) {
return step.fail(
\\
\\========= comparison failed for action: ===========
\\{s} {f}
\\===================================================
, .{ act.phrase.resolve(b, step), act.expected.? });
}
continue;
}
const output = switch (check_object.obj_format) {
.macho => try MachODumper.parseAndDump(step, chk, contents),
.elf => try ElfDumper.parseAndDump(step, chk, contents),
.coff => return step.fail("TODO coff parser", .{}),
.wasm => try WasmDumper.parseAndDump(step, chk, contents),
else => unreachable,
};
// Depending on whether we requested dumping section verbatim or not,
// we either format message string with escaped codes, or not to aid debugging
// the failed test.
const fmtMessageString = struct {
fn fmtMessageString(kind: Check.Kind, msg: []const u8) std.fmt.Alt(Ctx, formatMessageString) {
return .{ .data = .{
.kind = kind,
.msg = msg,
} };
}
const Ctx = struct {
kind: Check.Kind,
msg: []const u8,
};
fn formatMessageString(ctx: Ctx, w: *Writer) !void {
switch (ctx.kind) {
.dump_section => try w.print("{f}", .{std.ascii.hexEscape(ctx.msg, .lower)}),
else => try w.writeAll(ctx.msg),
}
}
}.fmtMessageString;
var it = mem.tokenizeAny(u8, output, "\r\n");
for (chk.actions.items) |act| {
switch (act.tag) {
.exact => {
while (it.next()) |line| {
if (act.exact(b, step, line)) break;
} else {
return step.fail(
\\
\\========= expected to find: ==========================
\\{f}
\\========= but parsed file does not contain it: =======
\\{f}
\\========= file path: =================================
\\{f}
, .{
fmtMessageString(chk.kind, act.phrase.resolve(b, step)),
fmtMessageString(chk.kind, output),
src_path,
});
}
},
.contains => {
while (it.next()) |line| {
if (act.contains(b, step, line)) break;
} else {
return step.fail(
\\
\\========= expected to find: ==========================
\\*{f}*
\\========= but parsed file does not contain it: =======
\\{f}
\\========= file path: =================================
\\{f}
, .{
fmtMessageString(chk.kind, act.phrase.resolve(b, step)),
fmtMessageString(chk.kind, output),
src_path,
});
}
},
.not_present => {
while (it.next()) |line| {
if (act.notPresent(b, step, line)) continue;
return step.fail(
\\
\\========= expected not to find: ===================
\\{f}
\\========= but parsed file does contain it: ========
\\{f}
\\========= file path: ==============================
\\{f}
, .{
fmtMessageString(chk.kind, act.phrase.resolve(b, step)),
fmtMessageString(chk.kind, output),
src_path,
});
}
},
.extract => {
while (it.next()) |line| {
if (try act.extract(b, step, line, &vars)) break;
} else {
return step.fail(
\\
\\========= expected to find and extract: ==============
\\{f}
\\========= but parsed file does not contain it: =======
\\{f}
\\========= file path: ==============================
\\{f}
, .{
fmtMessageString(chk.kind, act.phrase.resolve(b, step)),
fmtMessageString(chk.kind, output),
src_path,
});
}
},
.compute_cmp => unreachable,
}
}
}
}
const MachODumper = struct {
const dyld_rebase_label = "dyld rebase data";
const dyld_bind_label = "dyld bind data";
const dyld_weak_bind_label = "dyld weak bind data";
const dyld_lazy_bind_label = "dyld lazy bind data";
const exports_label = "exports data";
const symtab_label = "symbol table";
const indirect_symtab_label = "indirect symbol table";
fn parseAndDump(step: *Step, check: Check, bytes: []const u8) ![]const u8 {
// TODO: handle archives and fat files
return parseAndDumpObject(step, check, bytes);
}
const ObjectContext = struct {
gpa: Allocator,
data: []const u8,
header: macho.mach_header_64,
segments: std.ArrayList(macho.segment_command_64) = .empty,
sections: std.ArrayList(macho.section_64) = .empty,
symtab: std.ArrayList(macho.nlist_64) = .empty,
strtab: std.ArrayList(u8) = .empty,
indsymtab: std.ArrayList(u32) = .empty,
imports: std.ArrayList([]const u8) = .empty,
fn parse(ctx: *ObjectContext) !void {
var it = try ctx.getLoadCommandIterator();
var i: usize = 0;
while (try it.next()) |cmd| {
switch (cmd.hdr.cmd) {
.SEGMENT_64 => {
const seg = cmd.cast(macho.segment_command_64).?;
try ctx.segments.append(ctx.gpa, seg);
try ctx.sections.ensureUnusedCapacity(ctx.gpa, seg.nsects);
for (cmd.getSections()) |sect| {
ctx.sections.appendAssumeCapacity(sect);
}
},
.SYMTAB => {
const lc = cmd.cast(macho.symtab_command).?;
const symtab = @as([*]align(1) const macho.nlist_64, @ptrCast(ctx.data.ptr + lc.symoff))[0..lc.nsyms];
const strtab = ctx.data[lc.stroff..][0..lc.strsize];
try ctx.symtab.appendUnalignedSlice(ctx.gpa, symtab);
try ctx.strtab.appendSlice(ctx.gpa, strtab);
},
.DYSYMTAB => {
const lc = cmd.cast(macho.dysymtab_command).?;
const indexes = @as([*]align(1) const u32, @ptrCast(ctx.data.ptr + lc.indirectsymoff))[0..lc.nindirectsyms];
try ctx.indsymtab.appendUnalignedSlice(ctx.gpa, indexes);
},
.LOAD_DYLIB,
.LOAD_WEAK_DYLIB,
.REEXPORT_DYLIB,
=> {
try ctx.imports.append(ctx.gpa, cmd.getDylibPathName());
},
else => {},
}
i += 1;
}
}
fn getString(ctx: ObjectContext, off: u32) [:0]const u8 {
assert(off < ctx.strtab.items.len);
return mem.sliceTo(@as([*:0]const u8, @ptrCast(ctx.strtab.items.ptr + off)), 0);
}
fn getLoadCommandIterator(ctx: ObjectContext) !macho.LoadCommandIterator {
return .init(&ctx.header, ctx.data[@sizeOf(macho.mach_header_64)..]);
}
fn getLoadCommand(ctx: ObjectContext, cmd: macho.LC) !?macho.LoadCommandIterator.LoadCommand {
var it = try ctx.getLoadCommandIterator();
while (try it.next()) |lc| if (lc.hdr.cmd == cmd) {
return lc;
};
return null;
}
fn getSegmentByName(ctx: ObjectContext, name: []const u8) ?macho.segment_command_64 {
for (ctx.segments.items) |seg| {
if (mem.eql(u8, seg.segName(), name)) return seg;
}
return null;
}
fn getSectionByName(ctx: ObjectContext, segname: []const u8, sectname: []const u8) ?macho.section_64 {
for (ctx.sections.items) |sect| {
if (mem.eql(u8, sect.segName(), segname) and mem.eql(u8, sect.sectName(), sectname)) return sect;
}
return null;
}
fn dumpHeader(hdr: macho.mach_header_64, writer: anytype) !void {
const cputype = switch (hdr.cputype) {
macho.CPU_TYPE_ARM64 => "ARM64",
macho.CPU_TYPE_X86_64 => "X86_64",
else => "Unknown",
};
const filetype = switch (hdr.filetype) {
macho.MH_OBJECT => "MH_OBJECT",
macho.MH_EXECUTE => "MH_EXECUTE",
macho.MH_FVMLIB => "MH_FVMLIB",
macho.MH_CORE => "MH_CORE",
macho.MH_PRELOAD => "MH_PRELOAD",
macho.MH_DYLIB => "MH_DYLIB",
macho.MH_DYLINKER => "MH_DYLINKER",
macho.MH_BUNDLE => "MH_BUNDLE",
macho.MH_DYLIB_STUB => "MH_DYLIB_STUB",
macho.MH_DSYM => "MH_DSYM",
macho.MH_KEXT_BUNDLE => "MH_KEXT_BUNDLE",
else => "Unknown",
};
try writer.print(
\\header
\\cputype {s}
\\filetype {s}
\\ncmds {d}
\\sizeofcmds {x}
\\flags
, .{
cputype,
filetype,
hdr.ncmds,
hdr.sizeofcmds,
});
if (hdr.flags > 0) {
if (hdr.flags & macho.MH_NOUNDEFS != 0) try writer.writeAll(" NOUNDEFS");
if (hdr.flags & macho.MH_INCRLINK != 0) try writer.writeAll(" INCRLINK");
if (hdr.flags & macho.MH_DYLDLINK != 0) try writer.writeAll(" DYLDLINK");
if (hdr.flags & macho.MH_BINDATLOAD != 0) try writer.writeAll(" BINDATLOAD");
if (hdr.flags & macho.MH_PREBOUND != 0) try writer.writeAll(" PREBOUND");
if (hdr.flags & macho.MH_SPLIT_SEGS != 0) try writer.writeAll(" SPLIT_SEGS");
if (hdr.flags & macho.MH_LAZY_INIT != 0) try writer.writeAll(" LAZY_INIT");
if (hdr.flags & macho.MH_TWOLEVEL != 0) try writer.writeAll(" TWOLEVEL");
if (hdr.flags & macho.MH_FORCE_FLAT != 0) try writer.writeAll(" FORCE_FLAT");
if (hdr.flags & macho.MH_NOMULTIDEFS != 0) try writer.writeAll(" NOMULTIDEFS");
if (hdr.flags & macho.MH_NOFIXPREBINDING != 0) try writer.writeAll(" NOFIXPREBINDING");
if (hdr.flags & macho.MH_PREBINDABLE != 0) try writer.writeAll(" PREBINDABLE");
if (hdr.flags & macho.MH_ALLMODSBOUND != 0) try writer.writeAll(" ALLMODSBOUND");
if (hdr.flags & macho.MH_SUBSECTIONS_VIA_SYMBOLS != 0) try writer.writeAll(" SUBSECTIONS_VIA_SYMBOLS");
if (hdr.flags & macho.MH_CANONICAL != 0) try writer.writeAll(" CANONICAL");
if (hdr.flags & macho.MH_WEAK_DEFINES != 0) try writer.writeAll(" WEAK_DEFINES");
if (hdr.flags & macho.MH_BINDS_TO_WEAK != 0) try writer.writeAll(" BINDS_TO_WEAK");
if (hdr.flags & macho.MH_ALLOW_STACK_EXECUTION != 0) try writer.writeAll(" ALLOW_STACK_EXECUTION");
if (hdr.flags & macho.MH_ROOT_SAFE != 0) try writer.writeAll(" ROOT_SAFE");
if (hdr.flags & macho.MH_SETUID_SAFE != 0) try writer.writeAll(" SETUID_SAFE");
if (hdr.flags & macho.MH_NO_REEXPORTED_DYLIBS != 0) try writer.writeAll(" NO_REEXPORTED_DYLIBS");
if (hdr.flags & macho.MH_PIE != 0) try writer.writeAll(" PIE");
if (hdr.flags & macho.MH_DEAD_STRIPPABLE_DYLIB != 0) try writer.writeAll(" DEAD_STRIPPABLE_DYLIB");
if (hdr.flags & macho.MH_HAS_TLV_DESCRIPTORS != 0) try writer.writeAll(" HAS_TLV_DESCRIPTORS");
if (hdr.flags & macho.MH_NO_HEAP_EXECUTION != 0) try writer.writeAll(" NO_HEAP_EXECUTION");
if (hdr.flags & macho.MH_APP_EXTENSION_SAFE != 0) try writer.writeAll(" APP_EXTENSION_SAFE");
if (hdr.flags & macho.MH_NLIST_OUTOFSYNC_WITH_DYLDINFO != 0) try writer.writeAll(" NLIST_OUTOFSYNC_WITH_DYLDINFO");
}
try writer.writeByte('\n');
}
fn dumpLoadCommand(lc: macho.LoadCommandIterator.LoadCommand, index: usize, writer: anytype) !void {
// print header first
try writer.print(
\\LC {d}
\\cmd {s}
\\cmdsize {d}
, .{ index, @tagName(lc.hdr.cmd), lc.hdr.cmdsize });
switch (lc.hdr.cmd) {
.SEGMENT_64 => {
const seg = lc.cast(macho.segment_command_64).?;
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,
});
for (lc.getSections()) |sect| {
try writer.writeByte('\n');
try writer.print(
\\sectname {s}
\\addr {x}
\\size {x}
\\offset {x}
\\align {x}
, .{
sect.sectName(),
sect.addr,
sect.size,
sect.offset,
sect.@"align",
});
}
},
.ID_DYLIB,
.LOAD_DYLIB,
.LOAD_WEAK_DYLIB,
.REEXPORT_DYLIB,
=> {
const dylib = lc.cast(macho.dylib_command).?;
try writer.writeByte('\n');
try writer.print(
\\name {s}
\\timestamp {d}
\\current version {x}
\\compatibility version {x}
, .{
lc.getDylibPathName(),
dylib.dylib.timestamp,
dylib.dylib.current_version,
dylib.dylib.compatibility_version,
});
},
.MAIN => {
const main = lc.cast(macho.entry_point_command).?;
try writer.writeByte('\n');
try writer.print(
\\entryoff {x}
\\stacksize {x}
, .{ main.entryoff, main.stacksize });
},
.RPATH => {
try writer.writeByte('\n');
try writer.print(
\\path {s}
, .{
lc.getRpathPathName(),
});
},
.UUID => {
const uuid = lc.cast(macho.uuid_command).?;
try writer.writeByte('\n');
try writer.print("uuid {x}", .{&uuid.uuid});
},
.DATA_IN_CODE,
.FUNCTION_STARTS,
.CODE_SIGNATURE,
=> {
const llc = lc.cast(macho.linkedit_data_command).?;
try writer.writeByte('\n');
try writer.print(
\\dataoff {x}
\\datasize {x}
, .{ llc.dataoff, llc.datasize });
},
.DYLD_INFO_ONLY => {
const dlc = lc.cast(macho.dyld_info_command).?;
try writer.writeByte('\n');
try writer.print(
\\rebaseoff {x}
\\rebasesize {x}
\\bindoff {x}
\\bindsize {x}
\\weakbindoff {x}
\\weakbindsize {x}
\\lazybindoff {x}
\\lazybindsize {x}
\\exportoff {x}
\\exportsize {x}
, .{
dlc.rebase_off,
dlc.rebase_size,
dlc.bind_off,
dlc.bind_size,
dlc.weak_bind_off,
dlc.weak_bind_size,
dlc.lazy_bind_off,
dlc.lazy_bind_size,
dlc.export_off,
dlc.export_size,
});
},
.SYMTAB => {
const slc = lc.cast(macho.symtab_command).?;
try writer.writeByte('\n');
try writer.print(
\\symoff {x}
\\nsyms {x}
\\stroff {x}
\\strsize {x}
, .{
slc.symoff,
slc.nsyms,
slc.stroff,
slc.strsize,
});
},
.DYSYMTAB => {
const dlc = lc.cast(macho.dysymtab_command).?;
try writer.writeByte('\n');
try writer.print(
\\ilocalsym {x}
\\nlocalsym {x}
\\iextdefsym {x}
\\nextdefsym {x}
\\iundefsym {x}
\\nundefsym {x}
\\indirectsymoff {x}
\\nindirectsyms {x}
, .{
dlc.ilocalsym,
dlc.nlocalsym,
dlc.iextdefsym,
dlc.nextdefsym,
dlc.iundefsym,
dlc.nundefsym,
dlc.indirectsymoff,
dlc.nindirectsyms,
});
},
.BUILD_VERSION => {
const blc = lc.cast(macho.build_version_command).?;
try writer.writeByte('\n');
try writer.print(
\\platform {s}
\\minos {d}.{d}.{d}
\\sdk {d}.{d}.{d}
\\ntools {d}
, .{
@tagName(blc.platform),
blc.minos >> 16,
@as(u8, @truncate(blc.minos >> 8)),
@as(u8, @truncate(blc.minos)),
blc.sdk >> 16,
@as(u8, @truncate(blc.sdk >> 8)),
@as(u8, @truncate(blc.sdk)),
blc.ntools,
});
for (lc.getBuildVersionTools()) |tool| {
try writer.writeByte('\n');
switch (tool.tool) {
.CLANG, .SWIFT, .LD, .LLD, .ZIG => try writer.print("tool {s}\n", .{@tagName(tool.tool)}),
else => |x| try writer.print("tool {d}\n", .{@intFromEnum(x)}),
}
try writer.print(
\\version {d}.{d}.{d}
, .{
tool.version >> 16,
@as(u8, @truncate(tool.version >> 8)),
@as(u8, @truncate(tool.version)),
});
}
},
.VERSION_MIN_MACOSX,
.VERSION_MIN_IPHONEOS,
.VERSION_MIN_WATCHOS,
.VERSION_MIN_TVOS,
=> {
const vlc = lc.cast(macho.version_min_command).?;
try writer.writeByte('\n');
try writer.print(
\\version {d}.{d}.{d}
\\sdk {d}.{d}.{d}
, .{
vlc.version >> 16,
@as(u8, @truncate(vlc.version >> 8)),
@as(u8, @truncate(vlc.version)),
vlc.sdk >> 16,
@as(u8, @truncate(vlc.sdk >> 8)),
@as(u8, @truncate(vlc.sdk)),
});
},
else => {},
}
}
fn dumpSymtab(ctx: ObjectContext, writer: anytype) !void {
try writer.writeAll(symtab_label ++ "\n");
for (ctx.symtab.items) |sym| {
const sym_name = ctx.getString(sym.n_strx);
if (sym.n_type.bits.is_stab != 0) {
const tt = switch (sym.n_type.stab) {
_ => "UNKNOWN STAB",
else => @tagName(sym.n_type.stab),
};
try writer.print("{x}", .{sym.n_value});
if (sym.n_sect > 0) {
const sect = ctx.sections.items[sym.n_sect - 1];
try writer.print(" ({s},{s})", .{ sect.segName(), sect.sectName() });
}
try writer.print(" {s} (stab) {s}\n", .{ tt, sym_name });
} else if (sym.n_type.bits.type == .sect) {
const sect = ctx.sections.items[sym.n_sect - 1];
try writer.print("{x} ({s},{s})", .{
sym.n_value,
sect.segName(),
sect.sectName(),
});
if (sym.n_desc.referenced_dynamically) try writer.writeAll(" [referenced dynamically]");
if (sym.n_desc.weak_def_or_ref_to_weak) try writer.writeAll(" weak");
if (sym.n_desc.weak_ref) try writer.writeAll(" weakref");
if (sym.n_type.bits.ext) {
if (sym.n_type.bits.pext) try writer.writeAll(" private");
try writer.writeAll(" external");
} else if (sym.n_type.bits.pext) try writer.writeAll(" (was private external)");
try writer.print(" {s}\n", .{sym_name});
} else if (sym.tentative()) {
const alignment = (@as(u16, @bitCast(sym.n_desc)) >> 8) & 0x0F;
try writer.print(" 0x{x:0>16} (common) (alignment 2^{d})", .{ sym.n_value, alignment });
if (sym.n_type.bits.ext) try writer.writeAll(" external");
try writer.print(" {s}\n", .{sym_name});
} else if (sym.n_type.bits.type == .undf) {
const ordinal = @divFloor(@as(i16, @bitCast(sym.n_desc)), macho.N_SYMBOL_RESOLVER);
const import_name = blk: {
if (ordinal <= 0) {
if (ordinal == macho.BIND_SPECIAL_DYLIB_SELF)
break :blk "self import";
if (ordinal == macho.BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE)
break :blk "main executable";
if (ordinal == macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP)
break :blk "flat lookup";
unreachable;
}
const full_path = ctx.imports.items[@as(u16, @bitCast(ordinal)) - 1];
const basename = fs.path.basename(full_path);
assert(basename.len > 0);
const ext = mem.lastIndexOfScalar(u8, basename, '.') orelse basename.len;
break :blk basename[0..ext];
};
try writer.writeAll("(undefined)");
if (sym.n_desc.weak_ref) try writer.writeAll(" weakref");
if (sym.n_type.bits.ext) try writer.writeAll(" external");
try writer.print(" {s} (from {s})\n", .{
sym_name,
import_name,
});
}
}
}
fn dumpIndirectSymtab(ctx: ObjectContext, writer: anytype) !void {
try writer.writeAll(indirect_symtab_label ++ "\n");
var sects_buffer: [3]macho.section_64 = undefined;
const sects = blk: {
var count: usize = 0;
if (ctx.getSectionByName("__TEXT", "__stubs")) |sect| {
sects_buffer[count] = sect;
count += 1;
}
if (ctx.getSectionByName("__DATA_CONST", "__got")) |sect| {
sects_buffer[count] = sect;
count += 1;
}
if (ctx.getSectionByName("__DATA", "__la_symbol_ptr")) |sect| {
sects_buffer[count] = sect;
count += 1;
}
break :blk sects_buffer[0..count];
};
const sortFn = struct {
fn sortFn(c: void, lhs: macho.section_64, rhs: macho.section_64) bool {
_ = c;
return lhs.reserved1 < rhs.reserved1;
}
}.sortFn;
mem.sort(macho.section_64, sects, {}, sortFn);
var i: usize = 0;
while (i < sects.len) : (i += 1) {
const sect = sects[i];
const start = sect.reserved1;
const end = if (i + 1 >= sects.len) ctx.indsymtab.items.len else sects[i + 1].reserved1;
const entry_size = blk: {
if (mem.eql(u8, sect.sectName(), "__stubs")) break :blk sect.reserved2;
break :blk @sizeOf(u64);
};
try writer.print("{s},{s}\n", .{ sect.segName(), sect.sectName() });
try writer.print("nentries {d}\n", .{end - start});
for (ctx.indsymtab.items[start..end], 0..) |index, j| {
const sym = ctx.symtab.items[index];
const addr = sect.addr + entry_size * j;
try writer.print("0x{x} {d} {s}\n", .{ addr, index, ctx.getString(sym.n_strx) });
}
}
}
fn dumpRebaseInfo(ctx: ObjectContext, data: []const u8, writer: anytype) !void {
var rebases = std.array_list.Managed(u64).init(ctx.gpa);
defer rebases.deinit();
try ctx.parseRebaseInfo(data, &rebases);
mem.sort(u64, rebases.items, {}, std.sort.asc(u64));
for (rebases.items) |addr| {
try writer.print("0x{x}\n", .{addr});
}
}
fn parseRebaseInfo(ctx: ObjectContext, data: []const u8, rebases: *std.array_list.Managed(u64)) !void {
var reader: std.Io.Reader = .fixed(data);
var seg_id: ?u8 = null;
var offset: u64 = 0;
while (true) {
const byte = reader.takeByte() catch break;
const opc = byte & macho.REBASE_OPCODE_MASK;
const imm = byte & macho.REBASE_IMMEDIATE_MASK;
switch (opc) {
macho.REBASE_OPCODE_DONE => break,
macho.REBASE_OPCODE_SET_TYPE_IMM => {},
macho.REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => {
seg_id = imm;
offset = try reader.takeLeb128(u64);
},
macho.REBASE_OPCODE_ADD_ADDR_IMM_SCALED => {
offset += imm * @sizeOf(u64);
},
macho.REBASE_OPCODE_ADD_ADDR_ULEB => {
const addend = try reader.takeLeb128(u64);
offset += addend;
},
macho.REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB => {
const addend = try reader.takeLeb128(u64);
const seg = ctx.segments.items[seg_id.?];
const addr = seg.vmaddr + offset;
try rebases.append(addr);
offset += addend + @sizeOf(u64);
},
macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES,
macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES,
macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB,
=> {
var ntimes: u64 = 1;
var skip: u64 = 0;
switch (opc) {
macho.REBASE_OPCODE_DO_REBASE_IMM_TIMES => {
ntimes = imm;
},
macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES => {
ntimes = try reader.takeLeb128(u64);
},
macho.REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB => {
ntimes = try reader.takeLeb128(u64);
skip = try reader.takeLeb128(u64);
},
else => unreachable,
}
const seg = ctx.segments.items[seg_id.?];
const base_addr = seg.vmaddr;
var count: usize = 0;
while (count < ntimes) : (count += 1) {
const addr = base_addr + offset;
try rebases.append(addr);
offset += skip + @sizeOf(u64);
}
},
else => break,
}
}
}
const Binding = struct {
address: u64,
addend: i64,
ordinal: u16,
tag: Tag,
name: []const u8,
fn deinit(binding: *Binding, gpa: Allocator) void {
gpa.free(binding.name);
}
fn lessThan(ctx: void, lhs: Binding, rhs: Binding) bool {
_ = ctx;
return lhs.address < rhs.address;
}
const Tag = enum {
ord,
self,
exe,
flat,
};
};
fn dumpBindInfo(ctx: ObjectContext, data: []const u8, writer: anytype) !void {
var bindings = std.array_list.Managed(Binding).init(ctx.gpa);
defer {
for (bindings.items) |*b| {
b.deinit(ctx.gpa);
}
bindings.deinit();
}
var data_reader: std.Io.Reader = .fixed(data);
try ctx.parseBindInfo(&data_reader, &bindings);
mem.sort(Binding, bindings.items, {}, Binding.lessThan);
for (bindings.items) |binding| {
try writer.print("0x{x} [addend: {d}]", .{ binding.address, binding.addend });
try writer.writeAll(" (");
switch (binding.tag) {
.self => try writer.writeAll("self"),
.exe => try writer.writeAll("main executable"),
.flat => try writer.writeAll("flat lookup"),
.ord => try writer.writeAll(std.fs.path.basename(ctx.imports.items[binding.ordinal - 1])),
}
try writer.print(") {s}\n", .{binding.name});
}
}
fn parseBindInfo(ctx: ObjectContext, reader: *std.Io.Reader, bindings: *std.array_list.Managed(Binding)) !void {
var seg_id: ?u8 = null;
var tag: Binding.Tag = .self;
var ordinal: u16 = 0;
var offset: u64 = 0;
var addend: i64 = 0;
var name_buf = std.array_list.Managed(u8).init(ctx.gpa);
defer name_buf.deinit();
while (true) {
const byte = reader.takeByte() catch break;
const opc = byte & macho.BIND_OPCODE_MASK;
const imm = byte & macho.BIND_IMMEDIATE_MASK;
switch (opc) {
macho.BIND_OPCODE_DONE,
macho.BIND_OPCODE_SET_TYPE_IMM,
=> {},
macho.BIND_OPCODE_SET_DYLIB_ORDINAL_IMM => {
tag = .ord;
ordinal = imm;
},
macho.BIND_OPCODE_SET_DYLIB_SPECIAL_IMM => {
switch (imm) {
0 => tag = .self,
0xf => tag = .exe,
0xe => tag = .flat,
else => unreachable,
}
},
macho.BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB => {
seg_id = imm;
offset = try reader.takeLeb128(u64);
},
macho.BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM => {
name_buf.clearRetainingCapacity();
try name_buf.appendSlice(try reader.takeDelimiterInclusive(0));
},
macho.BIND_OPCODE_SET_ADDEND_SLEB => {
addend = try reader.takeLeb128(i64);
},
macho.BIND_OPCODE_ADD_ADDR_ULEB => {
const x = try reader.takeLeb128(u64);
offset = @intCast(@as(i64, @intCast(offset)) + @as(i64, @bitCast(x)));
},
macho.BIND_OPCODE_DO_BIND,
macho.BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB,
macho.BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED,
macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB,
=> {
var add_addr: u64 = 0;
var count: u64 = 1;
var skip: u64 = 0;
switch (opc) {
macho.BIND_OPCODE_DO_BIND => {},
macho.BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB => {
add_addr = try reader.takeLeb128(u64);
},
macho.BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED => {
add_addr = imm * @sizeOf(u64);
},
macho.BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB => {
count = try reader.takeLeb128(u64);
skip = try reader.takeLeb128(u64);
},
else => unreachable,
}
const seg = ctx.segments.items[seg_id.?];
var i: u64 = 0;
while (i < count) : (i += 1) {
const addr: u64 = @intCast(@as(i64, @intCast(seg.vmaddr + offset)));
try bindings.append(.{
.address = addr,
.addend = addend,
.tag = tag,
.ordinal = ordinal,
.name = try ctx.gpa.dupe(u8, name_buf.items),
});
offset += skip + @sizeOf(u64) + add_addr;
}
},
else => break,
}
}
}
fn dumpExportsTrie(ctx: ObjectContext, data: []const u8, writer: anytype) !void {
const seg = ctx.getSegmentByName("__TEXT") orelse return;
var arena = std.heap.ArenaAllocator.init(ctx.gpa);
defer arena.deinit();
var exports = std.array_list.Managed(Export).init(arena.allocator());
var it: TrieIterator = .{ .stream = .fixed(data) };
try parseTrieNode(arena.allocator(), &it, "", &exports);
mem.sort(Export, exports.items, {}, Export.lessThan);
for (exports.items) |exp| {
switch (exp.tag) {
.@"export" => {
const info = exp.data.@"export";
if (info.kind != .regular or info.weak) {
try writer.writeByte('[');
}
switch (info.kind) {
.regular => {},
.absolute => try writer.writeAll("ABS, "),
.tlv => try writer.writeAll("THREAD_LOCAL, "),
}
if (info.weak) try writer.writeAll("WEAK");
if (info.kind != .regular or info.weak) {
try writer.writeAll("] ");
}
try writer.print("{x} ", .{seg.vmaddr + info.vmoffset});
},
else => {},
}
try writer.print("{s}\n", .{exp.name});
}
}
const TrieIterator = struct {
stream: std.Io.Reader,
fn takeLeb128(it: *TrieIterator) !u64 {
return it.stream.takeLeb128(u64);
}
fn readString(it: *TrieIterator) ![:0]const u8 {
return it.stream.takeSentinel(0);
}
fn takeByte(it: *TrieIterator) !u8 {
return it.stream.takeByte();
}
};
const Export = struct {
name: []const u8,
tag: enum { @"export", reexport, stub_resolver },
data: union {
@"export": struct {
kind: enum { regular, absolute, tlv },
weak: bool = false,
vmoffset: u64,
},
reexport: u64,
stub_resolver: struct {
stub_offset: u64,
resolver_offset: u64,
},
},
inline fn rankByTag(@"export": Export) u3 {
return switch (@"export".tag) {
.@"export" => 1,
.reexport => 2,
.stub_resolver => 3,
};
}
fn lessThan(ctx: void, lhs: Export, rhs: Export) bool {
_ = ctx;
if (lhs.rankByTag() == rhs.rankByTag()) {
return switch (lhs.tag) {
.@"export" => lhs.data.@"export".vmoffset < rhs.data.@"export".vmoffset,
.reexport => lhs.data.reexport < rhs.data.reexport,
.stub_resolver => lhs.data.stub_resolver.stub_offset < rhs.data.stub_resolver.stub_offset,
};
}
return lhs.rankByTag() < rhs.rankByTag();
}
};
fn parseTrieNode(
arena: Allocator,
it: *TrieIterator,
prefix: []const u8,
exports: *std.array_list.Managed(Export),
) !void {
const size = try it.takeLeb128();
if (size > 0) {
const flags = try it.takeLeb128();
switch (flags) {
macho.EXPORT_SYMBOL_FLAGS_REEXPORT => {
const ord = try it.takeLeb128();
const name = try arena.dupe(u8, try it.readString());
try exports.append(.{
.name = if (name.len > 0) name else prefix,
.tag = .reexport,
.data = .{ .reexport = ord },
});
},
macho.EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER => {
const stub_offset = try it.takeLeb128();
const resolver_offset = try it.takeLeb128();
try exports.append(.{
.name = prefix,
.tag = .stub_resolver,
.data = .{ .stub_resolver = .{
.stub_offset = stub_offset,
.resolver_offset = resolver_offset,
} },
});
},
else => {
const vmoff = try it.takeLeb128();
try exports.append(.{
.name = prefix,
.tag = .@"export",
.data = .{ .@"export" = .{
.kind = switch (flags & macho.EXPORT_SYMBOL_FLAGS_KIND_MASK) {
macho.EXPORT_SYMBOL_FLAGS_KIND_REGULAR => .regular,
macho.EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE => .absolute,
macho.EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL => .tlv,
else => unreachable,
},
.weak = flags & macho.EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION != 0,
.vmoffset = vmoff,
} },
});
},
}
}
const nedges = try it.takeByte();
for (0..nedges) |_| {
const label = try it.readString();
const off = try it.takeLeb128();
const prefix_label = try std.fmt.allocPrint(arena, "{s}{s}", .{ prefix, label });
const curr = it.stream.seek;
it.stream.seek = off;
try parseTrieNode(arena, it, prefix_label, exports);
it.stream.seek = curr;
}
}
fn dumpSection(ctx: ObjectContext, sect: macho.section_64, writer: anytype) !void {
const data = ctx.data[sect.offset..][0..sect.size];
try writer.print("{s}", .{data});
}
};
fn parseAndDumpObject(step: *Step, check: Check, bytes: []const u8) ![]const u8 {
const gpa = step.owner.allocator;
const hdr = @as(*align(1) const macho.mach_header_64, @ptrCast(bytes.ptr)).*;
if (hdr.magic != macho.MH_MAGIC_64) {
return error.InvalidMagicNumber;
}
var ctx = ObjectContext{ .gpa = gpa, .data = bytes, .header = hdr };
try ctx.parse();
var output: std.Io.Writer.Allocating = .init(gpa);
defer output.deinit();
const writer = &output.writer;
switch (check.kind) {
.headers => {
try ObjectContext.dumpHeader(ctx.header, writer);
var it = try ctx.getLoadCommandIterator();
var i: usize = 0;
while (try it.next()) |cmd| {
try ObjectContext.dumpLoadCommand(cmd, i, writer);
try writer.writeByte('\n');
i += 1;
}
},
.symtab => if (ctx.symtab.items.len > 0) {
try ctx.dumpSymtab(writer);
} else return step.fail("no symbol table found", .{}),
.indirect_symtab => if (ctx.symtab.items.len > 0 and ctx.indsymtab.items.len > 0) {
try ctx.dumpIndirectSymtab(writer);
} else return step.fail("no indirect symbol table found", .{}),
.dyld_rebase,
.dyld_bind,
.dyld_weak_bind,
.dyld_lazy_bind,
=> {
const cmd = try ctx.getLoadCommand(.DYLD_INFO_ONLY) orelse
return step.fail("no dyld info found", .{});
const lc = cmd.cast(macho.dyld_info_command).?;
switch (check.kind) {
.dyld_rebase => if (lc.rebase_size > 0) {
const data = ctx.data[lc.rebase_off..][0..lc.rebase_size];
try writer.writeAll(dyld_rebase_label ++ "\n");
try ctx.dumpRebaseInfo(data, writer);
} else return step.fail("no rebase data found", .{}),
.dyld_bind => if (lc.bind_size > 0) {
const data = ctx.data[lc.bind_off..][0..lc.bind_size];
try writer.writeAll(dyld_bind_label ++ "\n");
try ctx.dumpBindInfo(data, writer);
} else return step.fail("no bind data found", .{}),
.dyld_weak_bind => if (lc.weak_bind_size > 0) {
const data = ctx.data[lc.weak_bind_off..][0..lc.weak_bind_size];
try writer.writeAll(dyld_weak_bind_label ++ "\n");
try ctx.dumpBindInfo(data, writer);
} else return step.fail("no weak bind data found", .{}),
.dyld_lazy_bind => if (lc.lazy_bind_size > 0) {
const data = ctx.data[lc.lazy_bind_off..][0..lc.lazy_bind_size];
try writer.writeAll(dyld_lazy_bind_label ++ "\n");
try ctx.dumpBindInfo(data, writer);
} else return step.fail("no lazy bind data found", .{}),
else => unreachable,
}
},
.exports => blk: {
if (try ctx.getLoadCommand(.DYLD_INFO_ONLY)) |cmd| {
const lc = cmd.cast(macho.dyld_info_command).?;
if (lc.export_size > 0) {
const data = ctx.data[lc.export_off..][0..lc.export_size];
try writer.writeAll(exports_label ++ "\n");
try ctx.dumpExportsTrie(data, writer);
break :blk;
}
}
return step.fail("no exports data found", .{});
},
.dump_section => {
const name = mem.sliceTo(@as([*:0]const u8, @ptrCast(check.data.items.ptr + check.payload.dump_section)), 0);
const sep_index = mem.findScalar(u8, name, ',') orelse
return step.fail("invalid section name: {s}", .{name});
const segname = name[0..sep_index];
const sectname = name[sep_index + 1 ..];
const sect = ctx.getSectionByName(segname, sectname) orelse
return step.fail("section '{s}' not found", .{name});
try ctx.dumpSection(sect, writer);
},
else => return step.fail("invalid check kind for MachO file format: {s}", .{@tagName(check.kind)}),
}
return output.toOwnedSlice();
}
};
const ElfDumper = struct {
const symtab_label = "symbol table";
const dynamic_symtab_label = "dynamic symbol table";
const dynamic_section_label = "dynamic section";
const archive_symtab_label = "archive symbol table";
fn parseAndDump(step: *Step, check: Check, bytes: []const u8) ![]const u8 {
return parseAndDumpArchive(step, check, bytes) catch |err| switch (err) {
error.InvalidArchiveMagicNumber => try parseAndDumpObject(step, check, bytes),
else => |e| return e,
};
}
fn parseAndDumpArchive(step: *Step, check: Check, bytes: []const u8) ![]const u8 {
const gpa = step.owner.allocator;
var reader: std.Io.Reader = .fixed(bytes);
const magic = try reader.takeArray(elf.ARMAG.len);
if (!mem.eql(u8, magic, elf.ARMAG)) {
return error.InvalidArchiveMagicNumber;
}
if (!mem.isAligned(bytes.len, 2)) {
return error.InvalidArchivePadding;
}
var ctx = ArchiveContext{
.gpa = gpa,
.data = bytes,
.strtab = &[0]u8{},
};
defer {
for (ctx.objects.items) |*object| {
gpa.free(object.name);
}
ctx.objects.deinit(gpa);
}
while (true) {
if (!mem.isAligned(reader.seek, 2)) reader.seek += 1;
if (reader.seek >= ctx.data.len) break;
const hdr = try reader.takeStruct(elf.ar_hdr, .little);
if (!mem.eql(u8, &hdr.ar_fmag, elf.ARFMAG)) return error.InvalidArchiveHeaderMagicNumber;
const size = try hdr.size();
defer reader.seek += size;
if (hdr.isSymtab()) {
try ctx.parseSymtab(ctx.data[reader.seek..][0..size], .p32);
continue;
}
if (hdr.isSymtab64()) {
try ctx.parseSymtab(ctx.data[reader.seek..][0..size], .p64);
continue;
}
if (hdr.isStrtab()) {
ctx.strtab = ctx.data[reader.seek..][0..size];
continue;
}
if (hdr.isSymdef() or hdr.isSymdefSorted()) continue;
const name = if (hdr.name()) |name|
try gpa.dupe(u8, name)
else if (try hdr.nameOffset()) |off|
try gpa.dupe(u8, ctx.getString(off))
else
unreachable;
try ctx.objects.append(gpa, .{ .name = name, .off = reader.seek, .len = size });
}
var output: std.Io.Writer.Allocating = .init(gpa);
defer output.deinit();
const writer = &output.writer;
switch (check.kind) {
.archive_symtab => if (ctx.symtab.items.len > 0) {
try ctx.dumpSymtab(writer);
} else return step.fail("no archive symbol table found", .{}),
else => if (ctx.objects.items.len > 0) {
try ctx.dumpObjects(step, check, writer);
} else return step.fail("empty archive", .{}),
}
return output.toOwnedSlice();
}
const ArchiveContext = struct {
gpa: Allocator,
data: []const u8,
symtab: std.ArrayList(ArSymtabEntry) = .empty,
strtab: []const u8,
objects: std.ArrayList(struct { name: []const u8, off: usize, len: usize }) = .empty,
fn parseSymtab(ctx: *ArchiveContext, raw: []const u8, ptr_width: enum { p32, p64 }) !void {
var reader: std.Io.Reader = .fixed(raw);
const num = switch (ptr_width) {
.p32 => try reader.takeInt(u32, .big),
.p64 => try reader.takeInt(u64, .big),
};
const ptr_size: usize = switch (ptr_width) {
.p32 => @sizeOf(u32),
.p64 => @sizeOf(u64),
};
const strtab_off = (num + 1) * ptr_size;
const strtab_len = raw.len - strtab_off;
const strtab = raw[strtab_off..][0..strtab_len];
try ctx.symtab.ensureTotalCapacityPrecise(ctx.gpa, num);
var stroff: usize = 0;
for (0..num) |_| {
const off = switch (ptr_width) {
.p32 => try reader.takeInt(u32, .big),
.p64 => try reader.takeInt(u64, .big),
};
const name = mem.sliceTo(@as([*:0]const u8, @ptrCast(strtab.ptr + stroff)), 0);
stroff += name.len + 1;
ctx.symtab.appendAssumeCapacity(.{ .off = off, .name = name });
}
}
fn dumpSymtab(ctx: ArchiveContext, writer: anytype) !void {
var files = std.AutoHashMap(usize, []const u8).init(ctx.gpa);
defer files.deinit();
try files.ensureUnusedCapacity(@intCast(ctx.objects.items.len));
for (ctx.objects.items) |object| {
files.putAssumeCapacityNoClobber(object.off - @sizeOf(elf.ar_hdr), object.name);
}
var symbols: std.array_hash_map.Auto(usize, std.array_list.Managed([]const u8)) = .empty;
defer {
for (symbols.values()) |*value| {
value.deinit();
}
symbols.deinit(ctx.gpa);
}
for (ctx.symtab.items) |entry| {
const gop = try symbols.getOrPut(ctx.gpa, @intCast(entry.off));
if (!gop.found_existing) {
gop.value_ptr.* = std.array_list.Managed([]const u8).init(ctx.gpa);
}
try gop.value_ptr.append(entry.name);
}
try writer.print("{s}\n", .{archive_symtab_label});
for (symbols.keys(), symbols.values()) |off, values| {
try writer.print("in object {s}\n", .{files.get(off).?});
for (values.items) |value| {
try writer.print("{s}\n", .{value});
}
}
}
fn dumpObjects(ctx: ArchiveContext, step: *Step, check: Check, writer: anytype) !void {
for (ctx.objects.items) |object| {
try writer.print("object {s}\n", .{object.name});
const output = try parseAndDumpObject(step, check, ctx.data[object.off..][0..object.len]);
defer ctx.gpa.free(output);
try writer.print("{s}\n", .{output});
}
}
fn getString(ctx: ArchiveContext, off: u32) []const u8 {
assert(off < ctx.strtab.len);
const name = mem.sliceTo(@as([*:'\n']const u8, @ptrCast(ctx.strtab.ptr + off)), 0);
return name[0 .. name.len - 1];
}
const ArSymtabEntry = struct {
name: [:0]const u8,
off: u64,
};
};
fn parseAndDumpObject(step: *Step, check: Check, bytes: []const u8) ![]const u8 {
const gpa = step.owner.allocator;
// `std.elf.Header` takes care of endianness issues for us.
var reader: std.Io.Reader = .fixed(bytes);
const hdr = try elf.Header.read(&reader);
var shdrs = try gpa.alloc(elf.Elf64_Shdr, hdr.shnum);
defer gpa.free(shdrs);
{
var shdr_it = hdr.iterateSectionHeadersBuffer(bytes);
var shdr_i: usize = 0;
while (try shdr_it.next()) |shdr| : (shdr_i += 1) shdrs[shdr_i] = shdr;
}
var phdrs = try gpa.alloc(elf.Elf64_Phdr, hdr.shnum);
defer gpa.free(phdrs);
{
var phdr_it = hdr.iterateProgramHeadersBuffer(bytes);
var phdr_i: usize = 0;
while (try phdr_it.next()) |phdr| : (phdr_i += 1) phdrs[phdr_i] = phdr;
}
var ctx = ObjectContext{
.gpa = gpa,
.data = bytes,
.hdr = hdr,
.shdrs = shdrs,
.phdrs = phdrs,
.shstrtab = undefined,
};
ctx.shstrtab = ctx.getSectionContents(ctx.hdr.shstrndx);
defer gpa.free(ctx.symtab.symbols);
defer gpa.free(ctx.dysymtab.symbols);
defer gpa.free(ctx.dyns);
for (ctx.shdrs, 0..) |shdr, i| switch (shdr.sh_type) {
elf.SHT_SYMTAB, elf.SHT_DYNSYM => {
const raw = ctx.getSectionContents(i);
const nsyms = @divExact(raw.len, @sizeOf(elf.Elf64_Sym));
const symbols = try gpa.alloc(elf.Elf64_Sym, nsyms);
var r: std.Io.Reader = .fixed(raw);
for (0..nsyms) |si| symbols[si] = r.takeStruct(elf.Elf64_Sym, ctx.hdr.endian) catch unreachable;
const strings = ctx.getSectionContents(shdr.sh_link);
switch (shdr.sh_type) {
elf.SHT_SYMTAB => {
ctx.symtab = .{
.symbols = symbols,
.strings = strings,
};
},
elf.SHT_DYNSYM => {
ctx.dysymtab = .{
.symbols = symbols,
.strings = strings,
};
},
else => unreachable,
}
},
elf.SHT_DYNAMIC => {
const raw = ctx.getSectionContents(i);
const ndyns = @divExact(raw.len, @sizeOf(elf.Elf64_Dyn));
const dyns = try gpa.alloc(elf.Elf64_Dyn, ndyns);
var r: std.Io.Reader = .fixed(raw);
for (0..ndyns) |si| dyns[si] = r.takeStruct(elf.Elf64_Dyn, ctx.hdr.endian) catch unreachable;
ctx.dyns = dyns;
ctx.dyns_strings = ctx.getSectionContents(shdr.sh_link);
},
else => {},
};
var output: std.Io.Writer.Allocating = .init(gpa);
defer output.deinit();
const writer = &output.writer;
switch (check.kind) {
.headers => {
try ctx.dumpHeader(writer);
try ctx.dumpShdrs(writer);
try ctx.dumpPhdrs(writer);
},
.symtab => if (ctx.symtab.symbols.len > 0) {
try ctx.dumpSymtab(.symtab, writer);
} else return step.fail("no symbol table found", .{}),
.dynamic_symtab => if (ctx.dysymtab.symbols.len > 0) {
try ctx.dumpSymtab(.dysymtab, writer);
} else return step.fail("no dynamic symbol table found", .{}),
.dynamic_section => if (ctx.dyns.len > 0) {
try ctx.dumpDynamicSection(writer);
} else return step.fail("no dynamic section found", .{}),
.dump_section => {
const name = mem.sliceTo(@as([*:0]const u8, @ptrCast(check.data.items.ptr + check.payload.dump_section)), 0);
const shndx = ctx.getSectionByName(name) orelse return step.fail("no '{s}' section found", .{name});
try ctx.dumpSection(shndx, writer);
},
else => return step.fail("invalid check kind for ELF file format: {s}", .{@tagName(check.kind)}),
}
return output.toOwnedSlice();
}
const ObjectContext = struct {
gpa: Allocator,
data: []const u8,
hdr: elf.Header,
shdrs: []const elf.Elf64_Shdr,
phdrs: []const elf.Elf64_Phdr,
shstrtab: []const u8,
symtab: Symtab = .{},
dysymtab: Symtab = .{},
dyns: []const elf.Elf64_Dyn = &.{},
dyns_strings: []const u8 = &.{},
fn dumpHeader(ctx: ObjectContext, writer: anytype) !void {
try writer.writeAll("header\n");
try writer.print("type {s}\n", .{@tagName(ctx.hdr.type)});
try writer.print("entry {x}\n", .{ctx.hdr.entry});
}
fn dumpPhdrs(ctx: ObjectContext, writer: anytype) !void {
if (ctx.phdrs.len == 0) return;
try writer.writeAll("program headers\n");
for (ctx.phdrs, 0..) |phdr, phndx| {
try writer.print("phdr {d}\n", .{phndx});
try writer.print("type {f}\n", .{fmtPhType(phdr.p_type)});
try writer.print("vaddr {x}\n", .{phdr.p_vaddr});
try writer.print("paddr {x}\n", .{phdr.p_paddr});
try writer.print("offset {x}\n", .{phdr.p_offset});
try writer.print("memsz {x}\n", .{phdr.p_memsz});
try writer.print("filesz {x}\n", .{phdr.p_filesz});
try writer.print("align {x}\n", .{phdr.p_align});
{
const flags = phdr.p_flags;
try writer.writeAll("flags");
if (flags > 0) try writer.writeByte(' ');
if (flags & elf.PF_R != 0) {
try writer.writeByte('R');
}
if (flags & elf.PF_W != 0) {
try writer.writeByte('W');
}
if (flags & elf.PF_X != 0) {
try writer.writeByte('E');
}
if (flags & elf.PF_MASKOS != 0) {
try writer.writeAll("OS");
}
if (flags & elf.PF_MASKPROC != 0) {
try writer.writeAll("PROC");
}
try writer.writeByte('\n');
}
}
}
fn dumpShdrs(ctx: ObjectContext, writer: anytype) !void {
if (ctx.shdrs.len == 0) return;
try writer.writeAll("section headers\n");
for (ctx.shdrs, 0..) |shdr, shndx| {
try writer.print("shdr {d}\n", .{shndx});
try writer.print("name {s}\n", .{ctx.getSectionName(shndx)});
try writer.print("type {f}\n", .{fmtShType(shdr.sh_type)});
try writer.print("addr {x}\n", .{shdr.sh_addr});
try writer.print("offset {x}\n", .{shdr.sh_offset});
try writer.print("size {x}\n", .{shdr.sh_size});
try writer.print("addralign {x}\n", .{shdr.sh_addralign});
// TODO dump formatted sh_flags
}
}
fn dumpDynamicSection(ctx: ObjectContext, writer: anytype) !void {
try writer.writeAll(ElfDumper.dynamic_section_label ++ "\n");
for (ctx.dyns) |entry| {
const key = @as(u64, @bitCast(entry.d_tag));
const value = entry.d_val;
const key_str = switch (key) {
elf.DT_NEEDED => "NEEDED",
elf.DT_SONAME => "SONAME",
elf.DT_INIT_ARRAY => "INIT_ARRAY",
elf.DT_INIT_ARRAYSZ => "INIT_ARRAYSZ",
elf.DT_FINI_ARRAY => "FINI_ARRAY",
elf.DT_FINI_ARRAYSZ => "FINI_ARRAYSZ",
elf.DT_HASH => "HASH",
elf.DT_GNU_HASH => "GNU_HASH",
elf.DT_STRTAB => "STRTAB",
elf.DT_SYMTAB => "SYMTAB",
elf.DT_STRSZ => "STRSZ",
elf.DT_SYMENT => "SYMENT",
elf.DT_PLTGOT => "PLTGOT",
elf.DT_PLTRELSZ => "PLTRELSZ",
elf.DT_PLTREL => "PLTREL",
elf.DT_JMPREL => "JMPREL",
elf.DT_RELA => "RELA",
elf.DT_RELASZ => "RELASZ",
elf.DT_RELAENT => "RELAENT",
elf.DT_VERDEF => "VERDEF",
elf.DT_VERDEFNUM => "VERDEFNUM",
elf.DT_FLAGS => "FLAGS",
elf.DT_FLAGS_1 => "FLAGS_1",
elf.DT_VERNEED => "VERNEED",
elf.DT_VERNEEDNUM => "VERNEEDNUM",
elf.DT_VERSYM => "VERSYM",
elf.DT_RELACOUNT => "RELACOUNT",
elf.DT_RPATH => "RPATH",
elf.DT_RUNPATH => "RUNPATH",
elf.DT_INIT => "INIT",
elf.DT_FINI => "FINI",
elf.DT_NULL => "NULL",
else => "UNKNOWN",
};
try writer.print("{s}", .{key_str});
switch (key) {
elf.DT_NEEDED,
elf.DT_SONAME,
elf.DT_RPATH,
elf.DT_RUNPATH,
=> {
const name = getString(ctx.dyns_strings, @intCast(value));
try writer.print(" {s}", .{name});
},
elf.DT_INIT_ARRAY,
elf.DT_FINI_ARRAY,
elf.DT_HASH,
elf.DT_GNU_HASH,
elf.DT_STRTAB,
elf.DT_SYMTAB,
elf.DT_PLTGOT,
elf.DT_JMPREL,
elf.DT_RELA,
elf.DT_VERDEF,
elf.DT_VERNEED,
elf.DT_VERSYM,
elf.DT_INIT,
elf.DT_FINI,
elf.DT_NULL,
=> try writer.print(" {x}", .{value}),
elf.DT_INIT_ARRAYSZ,
elf.DT_FINI_ARRAYSZ,
elf.DT_STRSZ,
elf.DT_SYMENT,
elf.DT_PLTRELSZ,
elf.DT_RELASZ,
elf.DT_RELAENT,
elf.DT_RELACOUNT,
=> try writer.print(" {d}", .{value}),
elf.DT_PLTREL => try writer.writeAll(switch (value) {
elf.DT_REL => " REL",
elf.DT_RELA => " RELA",
else => " UNKNOWN",
}),
elf.DT_FLAGS => if (value > 0) {
if (value & elf.DF_ORIGIN != 0) try writer.writeAll(" ORIGIN");
if (value & elf.DF_SYMBOLIC != 0) try writer.writeAll(" SYMBOLIC");
if (value & elf.DF_TEXTREL != 0) try writer.writeAll(" TEXTREL");
if (value & elf.DF_BIND_NOW != 0) try writer.writeAll(" BIND_NOW");
if (value & elf.DF_STATIC_TLS != 0) try writer.writeAll(" STATIC_TLS");
},
elf.DT_FLAGS_1 => if (value > 0) {
if (value & elf.DF_1_NOW != 0) try writer.writeAll(" NOW");
if (value & elf.DF_1_GLOBAL != 0) try writer.writeAll(" GLOBAL");
if (value & elf.DF_1_GROUP != 0) try writer.writeAll(" GROUP");
if (value & elf.DF_1_NODELETE != 0) try writer.writeAll(" NODELETE");
if (value & elf.DF_1_LOADFLTR != 0) try writer.writeAll(" LOADFLTR");
if (value & elf.DF_1_INITFIRST != 0) try writer.writeAll(" INITFIRST");
if (value & elf.DF_1_NOOPEN != 0) try writer.writeAll(" NOOPEN");
if (value & elf.DF_1_ORIGIN != 0) try writer.writeAll(" ORIGIN");
if (value & elf.DF_1_DIRECT != 0) try writer.writeAll(" DIRECT");
if (value & elf.DF_1_TRANS != 0) try writer.writeAll(" TRANS");
if (value & elf.DF_1_INTERPOSE != 0) try writer.writeAll(" INTERPOSE");
if (value & elf.DF_1_NODEFLIB != 0) try writer.writeAll(" NODEFLIB");
if (value & elf.DF_1_NODUMP != 0) try writer.writeAll(" NODUMP");
if (value & elf.DF_1_CONFALT != 0) try writer.writeAll(" CONFALT");
if (value & elf.DF_1_ENDFILTEE != 0) try writer.writeAll(" ENDFILTEE");
if (value & elf.DF_1_DISPRELDNE != 0) try writer.writeAll(" DISPRELDNE");
if (value & elf.DF_1_DISPRELPND != 0) try writer.writeAll(" DISPRELPND");
if (value & elf.DF_1_NODIRECT != 0) try writer.writeAll(" NODIRECT");
if (value & elf.DF_1_IGNMULDEF != 0) try writer.writeAll(" IGNMULDEF");
if (value & elf.DF_1_NOKSYMS != 0) try writer.writeAll(" NOKSYMS");
if (value & elf.DF_1_NOHDR != 0) try writer.writeAll(" NOHDR");
if (value & elf.DF_1_EDITED != 0) try writer.writeAll(" EDITED");
if (value & elf.DF_1_NORELOC != 0) try writer.writeAll(" NORELOC");
if (value & elf.DF_1_SYMINTPOSE != 0) try writer.writeAll(" SYMINTPOSE");
if (value & elf.DF_1_GLOBAUDIT != 0) try writer.writeAll(" GLOBAUDIT");
if (value & elf.DF_1_SINGLETON != 0) try writer.writeAll(" SINGLETON");
if (value & elf.DF_1_STUB != 0) try writer.writeAll(" STUB");
if (value & elf.DF_1_PIE != 0) try writer.writeAll(" PIE");
},
else => try writer.print(" {x}", .{value}),
}
try writer.writeByte('\n');
}
}
fn dumpSymtab(ctx: ObjectContext, comptime @"type": enum { symtab, dysymtab }, writer: anytype) !void {
const symtab = switch (@"type") {
.symtab => ctx.symtab,
.dysymtab => ctx.dysymtab,
};
try writer.writeAll(switch (@"type") {
.symtab => symtab_label,
.dysymtab => dynamic_symtab_label,
} ++ "\n");
for (symtab.symbols, 0..) |sym, index| {
try writer.print("{x} {x}", .{ sym.st_value, sym.st_size });
{
if (elf.SHN_LORESERVE <= sym.st_shndx and sym.st_shndx < elf.SHN_HIRESERVE) {
if (elf.SHN_LOPROC <= sym.st_shndx and sym.st_shndx < elf.SHN_HIPROC) {
try writer.print(" LO+{d}", .{sym.st_shndx - elf.SHN_LOPROC});
} else {
const sym_ndx = switch (sym.st_shndx) {
elf.SHN_ABS => "ABS",
elf.SHN_COMMON => "COM",
elf.SHN_LIVEPATCH => "LIV",
else => "UNK",
};
try writer.print(" {s}", .{sym_ndx});
}
} else if (sym.st_shndx == elf.SHN_UNDEF) {
try writer.writeAll(" UND");
} else {
try writer.print(" {x}", .{sym.st_shndx});
}
}
blk: {
const tt = sym.st_type();
const sym_type = switch (tt) {
elf.STT_NOTYPE => "NOTYPE",
elf.STT_OBJECT => "OBJECT",
elf.STT_FUNC => "FUNC",
elf.STT_SECTION => "SECTION",
elf.STT_FILE => "FILE",
elf.STT_COMMON => "COMMON",
elf.STT_TLS => "TLS",
elf.STT_NUM => "NUM",
elf.STT_GNU_IFUNC => "IFUNC",
else => if (elf.STT_LOPROC <= tt and tt < elf.STT_HIPROC) {
break :blk try writer.print(" LOPROC+{d}", .{tt - elf.STT_LOPROC});
} else if (elf.STT_LOOS <= tt and tt < elf.STT_HIOS) {
break :blk try writer.print(" LOOS+{d}", .{tt - elf.STT_LOOS});
} else "UNK",
};
try writer.print(" {s}", .{sym_type});
}
blk: {
const bind = sym.st_bind();
const sym_bind = switch (bind) {
elf.STB_LOCAL => "LOCAL",
elf.STB_GLOBAL => "GLOBAL",
elf.STB_WEAK => "WEAK",
elf.STB_NUM => "NUM",
else => if (elf.STB_LOPROC <= bind and bind < elf.STB_HIPROC) {
break :blk try writer.print(" LOPROC+{d}", .{bind - elf.STB_LOPROC});
} else if (elf.STB_LOOS <= bind and bind < elf.STB_HIOS) {
break :blk try writer.print(" LOOS+{d}", .{bind - elf.STB_LOOS});
} else "UNKNOWN",
};
try writer.print(" {s}", .{sym_bind});
}
const sym_vis = @as(elf.STV, @enumFromInt(@as(u3, @truncate(sym.st_other))));
try writer.print(" {s}", .{@tagName(sym_vis)});
const sym_name = switch (sym.st_type()) {
elf.STT_SECTION => ctx.getSectionName(sym.st_shndx),
else => symtab.getName(index).?,
};
try writer.print(" {s}\n", .{sym_name});
}
}
fn dumpSection(ctx: ObjectContext, shndx: usize, writer: anytype) !void {
const data = ctx.getSectionContents(shndx);
try writer.print("{s}", .{data});
}
inline fn getSectionName(ctx: ObjectContext, shndx: usize) []const u8 {
const shdr = ctx.shdrs[shndx];
return getString(ctx.shstrtab, shdr.sh_name);
}
fn getSectionContents(ctx: ObjectContext, shndx: usize) []const u8 {
const shdr = ctx.shdrs[shndx];
assert(shdr.sh_offset < ctx.data.len);
assert(shdr.sh_offset + shdr.sh_size <= ctx.data.len);
return ctx.data[shdr.sh_offset..][0..shdr.sh_size];
}
fn getSectionByName(ctx: ObjectContext, name: []const u8) ?usize {
for (0..ctx.shdrs.len) |shndx| {
if (mem.eql(u8, ctx.getSectionName(shndx), name)) return shndx;
} else return null;
}
};
const Symtab = struct {
symbols: []const elf.Elf64_Sym = &.{},
strings: []const u8 = &.{},
fn get(st: Symtab, index: usize) ?elf.Elf64_Sym {
if (index >= st.symbols.len) return null;
return st.symbols[index];
}
fn getName(st: Symtab, index: usize) ?[]const u8 {
const sym = st.get(index) orelse return null;
return getString(st.strings, sym.st_name);
}
};
fn getString(strtab: []const u8, off: u32) []const u8 {
assert(off < strtab.len);
return mem.sliceTo(@as([*:0]const u8, @ptrCast(strtab.ptr + off)), 0);
}
fn fmtShType(sh_type: u32) std.fmt.Alt(u32, formatShType) {
return .{ .data = sh_type };
}
fn formatShType(sh_type: u32, writer: *Writer) Writer.Error!void {
const name = switch (sh_type) {
elf.SHT_NULL => "NULL",
elf.SHT_PROGBITS => "PROGBITS",
elf.SHT_SYMTAB => "SYMTAB",
elf.SHT_STRTAB => "STRTAB",
elf.SHT_RELA => "RELA",
elf.SHT_HASH => "HASH",
elf.SHT_DYNAMIC => "DYNAMIC",
elf.SHT_NOTE => "NOTE",
elf.SHT_NOBITS => "NOBITS",
elf.SHT_REL => "REL",
elf.SHT_SHLIB => "SHLIB",
elf.SHT_DYNSYM => "DYNSYM",
elf.SHT_INIT_ARRAY => "INIT_ARRAY",
elf.SHT_FINI_ARRAY => "FINI_ARRAY",
elf.SHT_PREINIT_ARRAY => "PREINIT_ARRAY",
elf.SHT_GROUP => "GROUP",
elf.SHT_SYMTAB_SHNDX => "SYMTAB_SHNDX",
elf.SHT_X86_64_UNWIND => "X86_64_UNWIND",
elf.SHT_LLVM_ADDRSIG => "LLVM_ADDRSIG",
elf.SHT_GNU_HASH => "GNU_HASH",
elf.SHT_GNU_VERDEF => "VERDEF",
elf.SHT_GNU_VERNEED => "VERNEED",
elf.SHT_GNU_VERSYM => "VERSYM",
else => if (elf.SHT_LOOS <= sh_type and sh_type < elf.SHT_HIOS) {
return try writer.print("LOOS+0x{x}", .{sh_type - elf.SHT_LOOS});
} else if (elf.SHT_LOPROC <= sh_type and sh_type < elf.SHT_HIPROC) {
return try writer.print("LOPROC+0x{x}", .{sh_type - elf.SHT_LOPROC});
} else if (elf.SHT_LOUSER <= sh_type and sh_type < elf.SHT_HIUSER) {
return try writer.print("LOUSER+0x{x}", .{sh_type - elf.SHT_LOUSER});
} else "UNKNOWN",
};
try writer.writeAll(name);
}
fn fmtPhType(ph_type: u32) std.fmt.Alt(u32, formatPhType) {
return .{ .data = ph_type };
}
fn formatPhType(ph_type: u32, writer: *Writer) Writer.Error!void {
const p_type = switch (ph_type) {
elf.PT_NULL => "NULL",
elf.PT_LOAD => "LOAD",
elf.PT_DYNAMIC => "DYNAMIC",
elf.PT_INTERP => "INTERP",
elf.PT_NOTE => "NOTE",
elf.PT_SHLIB => "SHLIB",
elf.PT_PHDR => "PHDR",
elf.PT_TLS => "TLS",
elf.PT_NUM => "NUM",
elf.PT_GNU_EH_FRAME => "GNU_EH_FRAME",
elf.PT_GNU_STACK => "GNU_STACK",
elf.PT_GNU_RELRO => "GNU_RELRO",
else => if (elf.PT_LOOS <= ph_type and ph_type < elf.PT_HIOS) {
return try writer.print("LOOS+0x{x}", .{ph_type - elf.PT_LOOS});
} else if (elf.PT_LOPROC <= ph_type and ph_type < elf.PT_HIPROC) {
return try writer.print("LOPROC+0x{x}", .{ph_type - elf.PT_LOPROC});
} else "UNKNOWN",
};
try writer.writeAll(p_type);
}
};
const WasmDumper = struct {
const symtab_label = "symbols";
fn parseAndDump(step: *Step, check: Check, bytes: []const u8) ![]const u8 {
const gpa = step.owner.allocator;
var reader: std.Io.Reader = .fixed(bytes);
const buf = try reader.takeArray(8);
if (!mem.eql(u8, buf[0..4], &std.wasm.magic)) {
return error.InvalidMagicByte;
}
if (!mem.eql(u8, buf[4..], &std.wasm.version)) {
return error.UnsupportedWasmVersion;
}
var output: std.Io.Writer.Allocating = .init(gpa);
defer output.deinit();
parseAndDumpInner(step, check, bytes, &reader, &output.writer) catch |err| switch (err) {
error.EndOfStream => try output.writer.writeAll("\n<UnexpectedEndOfStream>"),
else => |e| return e,
};
return output.toOwnedSlice();
}
fn parseAndDumpInner(
step: *Step,
check: Check,
bytes: []const u8,
reader: *std.Io.Reader,
writer: *std.Io.Writer,
) !void {
switch (check.kind) {
.headers => {
while (reader.takeByte()) |current_byte| {
const section = std.enums.fromInt(std.wasm.Section, current_byte) orelse {
return step.fail("Found invalid section id '{d}'", .{current_byte});
};
const section_length = try reader.takeLeb128(u32);
try parseAndDumpSection(step, section, bytes[reader.seek..][0..section_length], writer);
reader.seek += section_length;
} else |_| {} // reached end of stream
},
else => return step.fail("invalid check kind for Wasm file format: {s}", .{@tagName(check.kind)}),
}
}
fn parseAndDumpSection(
step: *Step,
section: std.wasm.Section,
data: []const u8,
writer: *std.Io.Writer,
) !void {
var reader: std.Io.Reader = .fixed(data);
try writer.print(
\\Section {s}
\\size {d}
, .{ @tagName(section), data.len });
switch (section) {
.type,
.import,
.function,
.table,
.memory,
.global,
.@"export",
.element,
.code,
.data,
=> {
const entries = try reader.takeLeb128(u32);
try writer.print("\nentries {d}\n", .{entries});
try parseSection(step, section, data[reader.seek..], entries, writer);
},
.custom => {
const name_length = try reader.takeLeb128(u32);
const name = data[reader.seek..][0..name_length];
reader.seek += name_length;
try writer.print("\nname {s}\n", .{name});
if (mem.eql(u8, name, "name")) {
try parseDumpNames(step, &reader, writer, data);
} else if (mem.eql(u8, name, "producers")) {
try parseDumpProducers(&reader, writer, data);
} else if (mem.eql(u8, name, "target_features")) {
try parseDumpFeatures(&reader, writer, data);
}
// TODO: Implement parsing and dumping other custom sections (such as relocations)
},
.start => {
const start = try reader.takeLeb128(u32);
try writer.print("\nstart {d}\n", .{start});
},
.data_count => {
const count = try reader.takeLeb128(u32);
try writer.print("\ncount {d}\n", .{count});
},
else => {}, // skip unknown sections
}
}
fn parseSection(step: *Step, section: std.wasm.Section, data: []const u8, entries: u32, writer: anytype) !void {
var reader: std.Io.Reader = .fixed(data);
switch (section) {
.type => {
var i: u32 = 0;
while (i < entries) : (i += 1) {
const func_type = try reader.takeByte();
if (func_type != std.wasm.function_type) {
return step.fail("expected function type, found byte '{d}'", .{func_type});
}
const params = try reader.takeLeb128(u32);
try writer.print("params {d}\n", .{params});
var index: u32 = 0;
while (index < params) : (index += 1) {
_ = try parseDumpType(step, std.wasm.Valtype, &reader, writer);
} else index = 0;
const returns = try reader.takeLeb128(u32);
try writer.print("returns {d}\n", .{returns});
while (index < returns) : (index += 1) {
_ = try parseDumpType(step, std.wasm.Valtype, &reader, writer);
}
}
},
.import => {
var i: u32 = 0;
while (i < entries) : (i += 1) {
const module_name_len = try reader.takeLeb128(u32);
const module_name = data[reader.seek..][0..module_name_len];
reader.seek += module_name_len;
const name_len = try reader.takeLeb128(u32);
const name = data[reader.seek..][0..name_len];
reader.seek += name_len;
const kind = std.enums.fromInt(std.wasm.ExternalKind, try reader.takeByte()) orelse {
return step.fail("invalid import kind", .{});
};
try writer.print(
\\module {s}
\\name {s}
\\kind {s}
, .{ module_name, name, @tagName(kind) });
try writer.writeByte('\n');
switch (kind) {
.function => {
try writer.print("index {d}\n", .{try reader.takeLeb128(u32)});
},
.memory => {
try parseDumpLimits(&reader, writer);
},
.global => {
_ = try parseDumpType(step, std.wasm.Valtype, &reader, writer);
try writer.print("mutable {}\n", .{0x01 == try reader.takeLeb128(u32)});
},
.table => {
_ = try parseDumpType(step, std.wasm.RefType, &reader, writer);
try parseDumpLimits(&reader, writer);
},
}
}
},
.function => {
var i: u32 = 0;
while (i < entries) : (i += 1) {
try writer.print("index {d}\n", .{try reader.takeLeb128(u32)});
}
},
.table => {
var i: u32 = 0;
while (i < entries) : (i += 1) {
_ = try parseDumpType(step, std.wasm.RefType, &reader, writer);
try parseDumpLimits(&reader, writer);
}
},
.memory => {
var i: u32 = 0;
while (i < entries) : (i += 1) {
try parseDumpLimits(&reader, writer);
}
},
.global => {
var i: u32 = 0;
while (i < entries) : (i += 1) {
_ = try parseDumpType(step, std.wasm.Valtype, &reader, writer);
try writer.print("mutable {}\n", .{0x01 == try reader.takeLeb128(u1)});
try parseDumpInit(step, &reader, writer);
}
},
.@"export" => {
var i: u32 = 0;
while (i < entries) : (i += 1) {
const name_len = try reader.takeLeb128(u32);
const name = data[reader.seek..][0..name_len];
reader.seek += name_len;
const kind_byte = try reader.takeLeb128(u8);
const kind = std.enums.fromInt(std.wasm.ExternalKind, kind_byte) orelse {
return step.fail("invalid export kind value '{d}'", .{kind_byte});
};
const index = try reader.takeLeb128(u32);
try writer.print(
\\name {s}
\\kind {s}
\\index {d}
, .{ name, @tagName(kind), index });
try writer.writeByte('\n');
}
},
.element => {
var i: u32 = 0;
while (i < entries) : (i += 1) {
try writer.print("table index {d}\n", .{try reader.takeLeb128(u32)});
try parseDumpInit(step, &reader, writer);
const function_indexes = try reader.takeLeb128(u32);
var function_index: u32 = 0;
try writer.print("indexes {d}\n", .{function_indexes});
while (function_index < function_indexes) : (function_index += 1) {
try writer.print("index {d}\n", .{try reader.takeLeb128(u32)});
}
}
},
.code => {}, // code section is considered opaque to linker
.data => {
var i: u32 = 0;
while (i < entries) : (i += 1) {
const flags = try reader.takeLeb128(u32);
const index = if (flags & 0x02 != 0)
try reader.takeLeb128(u32)
else
0;
try writer.print("memory index 0x{x}\n", .{index});
if (flags == 0) {
try parseDumpInit(step, &reader, writer);
}
const size = try reader.takeLeb128(u32);
try writer.print("size {d}\n", .{size});
try reader.discardAll(size); // we do not care about the content of the segments
}
},
else => unreachable,
}
}
fn parseDumpType(step: *Step, comptime E: type, reader: *std.Io.Reader, writer: *std.Io.Writer) !E {
const byte = try reader.takeByte();
const tag = std.enums.fromInt(E, byte) orelse {
return step.fail("invalid wasm type value '{d}'", .{byte});
};
try writer.print("type {s}\n", .{@tagName(tag)});
return tag;
}
fn parseDumpLimits(reader: anytype, writer: anytype) !void {
const flags = try reader.takeLeb128(u8);
const min = try reader.takeLeb128(u32);
try writer.print("min {x}\n", .{min});
if (flags != 0) {
try writer.print("max {x}\n", .{try reader.takeLeb128(u32)});
}
}
fn parseDumpInit(step: *Step, reader: *std.Io.Reader, writer: *std.Io.Writer) !void {
const byte = try reader.takeByte();
const opcode = std.enums.fromInt(std.wasm.Opcode, byte) orelse {
return step.fail("invalid wasm opcode '{d}'", .{byte});
};
switch (opcode) {
.i32_const => try writer.print("i32.const {x}\n", .{try reader.takeLeb128(i32)}),
.i64_const => try writer.print("i64.const {x}\n", .{try reader.takeLeb128(i64)}),
.f32_const => try writer.print("f32.const {x}\n", .{@as(f32, @bitCast(try reader.takeInt(u32, .little)))}),
.f64_const => try writer.print("f64.const {x}\n", .{@as(f64, @bitCast(try reader.takeInt(u64, .little)))}),
.global_get => try writer.print("global.get {x}\n", .{try reader.takeLeb128(u32)}),
else => unreachable,
}
const end_opcode = try reader.takeLeb128(u8);
if (end_opcode != @intFromEnum(std.wasm.Opcode.end)) {
return step.fail("expected 'end' opcode in init expression", .{});
}
}
/// https://webassembly.github.io/spec/core/appendix/custom.html
fn parseDumpNames(step: *Step, reader: *std.Io.Reader, writer: *std.Io.Writer, data: []const u8) !void {
while (reader.seek < data.len) {
switch (try parseDumpType(step, std.wasm.NameSubsection, reader, writer)) {
// The module name subsection ... consists of a single name
// that is assigned to the module itself.
.module => {
const size = try reader.takeLeb128(u32);
const name_len = try reader.takeLeb128(u32);
if (size != name_len + 1) return error.BadSubsectionSize;
if (reader.seek + name_len > data.len) return error.UnexpectedEndOfStream;
try writer.print("name {s}\n", .{data[reader.seek..][0..name_len]});
reader.seek += name_len;
},
// The function name subsection ... consists of a name map
// assigning function names to function indices.
.function, .global, .data_segment => {
const size = try reader.takeLeb128(u32);
const entries = try reader.takeLeb128(u32);
try writer.print(
\\size {d}
\\names {d}
\\
, .{ size, entries });
for (0..entries) |_| {
const index = try reader.takeLeb128(u32);
const name_len = try reader.takeLeb128(u32);
if (reader.seek + name_len > data.len) return error.UnexpectedEndOfStream;
const name = data[reader.seek..][0..name_len];
reader.seek += name.len;
try writer.print(
\\index {d}
\\name {s}
\\
, .{ index, name });
}
},
// The local name subsection ... consists of an indirect name
// map assigning local names to local indices grouped by
// function indices.
.local => {
return step.fail("TODO implement parseDumpNames for local subsections", .{});
},
else => |t| return step.fail("invalid subsection type: {s}", .{@tagName(t)}),
}
}
}
fn parseDumpProducers(reader: *std.Io.Reader, writer: *std.Io.Writer, data: []const u8) !void {
const field_count = try reader.takeLeb128(u32);
try writer.print("fields {d}\n", .{field_count});
var current_field: u32 = 0;
while (current_field < field_count) : (current_field += 1) {
const field_name_length = try reader.takeLeb128(u32);
const field_name = data[reader.seek..][0..field_name_length];
reader.seek += field_name_length;
const value_count = try reader.takeLeb128(u32);
try writer.print(
\\field_name {s}
\\values {d}
, .{ field_name, value_count });
try writer.writeByte('\n');
var current_value: u32 = 0;
while (current_value < value_count) : (current_value += 1) {
const value_length = try reader.takeLeb128(u32);
const value = data[reader.seek..][0..value_length];
reader.seek += value_length;
const version_length = try reader.takeLeb128(u32);
const version = data[reader.seek..][0..version_length];
reader.seek += version_length;
try writer.print(
\\value_name {s}
\\version {s}
, .{ value, version });
try writer.writeByte('\n');
}
}
}
fn parseDumpFeatures(reader: *std.Io.Reader, writer: *std.Io.Writer, data: []const u8) !void {
const feature_count = try reader.takeLeb128(u32);
try writer.print("features {d}\n", .{feature_count});
var index: u32 = 0;
while (index < feature_count) : (index += 1) {
const prefix_byte = try reader.takeLeb128(u8);
const name_length = try reader.takeLeb128(u32);
const feature_name = data[reader.seek..][0..name_length];
reader.seek += name_length;
try writer.print("{c} {s}\n", .{ prefix_byte, feature_name });
}
}
};
-4
View File
@@ -611,10 +611,6 @@ pub fn addObjCopy(cs: *Compile, options: Step.ObjCopy.Options) *Step.ObjCopy {
return b.addObjCopy(cs.getEmittedBin(), copy);
}
pub fn checkObject(compile: *Compile) *Step.CheckObject {
return Step.CheckObject.create(compile.step.owner, compile.getEmittedBin(), compile.rootModuleTarget().ofmt);
}
pub fn setLinkerScript(compile: *Compile, source: LazyPath) void {
const b = compile.step.owner;
compile.linker_script = source.dupe(b);
-20
View File
@@ -1,20 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test");
b.default_step = test_step;
const exe = b.addExecutable(.{
.name = "bss",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.target = b.graph.host,
.optimize = .Debug,
}),
});
const run = b.addRunArtifact(exe);
run.expectStdOutEqual("0, 1, 0\n");
test_step.dependOn(&run.step);
}
-17
View File
@@ -1,17 +0,0 @@
const std = @import("std");
// Stress test zerofill layout
var buffer: [0x1000000]u64 = [1]u64{0} ** 0x1000000;
pub fn main() anyerror!void {
var stdout_writer = std.Io.File.stdout().writerStreaming(std.Options.debug_io, &.{});
buffer[0x10] = 1;
try stdout_writer.interface.print("{d}, {d}, {d}\n", .{
// workaround the dreaded decl_val
(&buffer)[0],
(&buffer)[0x10],
(&buffer)[0x1000000 - 1],
});
}
-54
View File
@@ -1,54 +0,0 @@
const std = @import("std");
const builtin = @import("builtin");
const link = @import("link.zig");
pub fn build(b: *std.Build) void {
const step = b.step("test", "Run link test cases");
b.default_step = step;
const enable_ios_sdk = b.option(bool, "enable_ios_sdk", "Run tests requiring presence of iOS SDK and frameworks") orelse false;
const enable_macos_sdk = b.option(bool, "enable_macos_sdk", "Run tests requiring presence of macOS SDK and frameworks") orelse enable_ios_sdk;
const enable_symlinks_windows = b.option(bool, "enable_symlinks_windows", "Run tests requiring presence of symlinks on Windows") orelse false;
const has_symlinks = builtin.os.tag != .windows or enable_symlinks_windows;
const build_opts: link.BuildOptions = .{
.has_ios_sdk = enable_ios_sdk,
.has_macos_sdk = enable_macos_sdk,
.has_symlinks = has_symlinks,
};
step.dependOn(@import("elf.zig").testAll(b, build_opts));
step.dependOn(@import("macho.zig").testAll(b, build_opts));
add_dep_steps: for (b.available_deps) |available_dep| {
const dep_name, const dep_hash = available_dep;
const all_pkgs = @import("root").dependencies.packages;
inline for (@typeInfo(all_pkgs).@"struct".decls) |decl| {
const pkg_hash = decl.name;
if (std.mem.eql(u8, dep_hash, pkg_hash)) {
const pkg = @field(all_pkgs, pkg_hash);
if (!@hasDecl(pkg, "build_zig")) {
std.debug.panic("link test case '{s}' is missing a 'build.zig' file", .{dep_name});
}
const requires_ios_sdk = @hasDecl(pkg.build_zig, "requires_ios_sdk") and
pkg.build_zig.requires_ios_sdk;
const requires_macos_sdk = @hasDecl(pkg.build_zig, "requires_macos_sdk") and
pkg.build_zig.requires_macos_sdk;
const requires_symlinks = @hasDecl(pkg.build_zig, "requires_symlinks") and
pkg.build_zig.requires_symlinks;
if ((requires_symlinks and !has_symlinks) or
(requires_macos_sdk and !enable_macos_sdk) or
(requires_ios_sdk and !enable_ios_sdk))
{
continue :add_dep_steps;
}
break;
}
} else unreachable;
const dep = b.dependency(dep_name, .{});
const dep_step = dep.builder.default_step;
dep_step.name = b.fmt("link_test_cases.{s}", .{dep_name});
step.dependOn(dep_step);
}
}
-61
View File
@@ -1,61 +0,0 @@
.{
.name = .link_test_cases,
.fingerprint = 0x404f657576fec9f2,
.version = "0.0.0",
.dependencies = .{
.bss = .{
.path = "bss",
},
.common_symbols_alignment = .{
.path = "common_symbols_alignment",
},
.interdependent_static_c_libs = .{
.path = "interdependent_static_c_libs",
},
.static_libs_from_object_files = .{
.path = "static_libs_from_object_files",
},
// WASM Cases
.wasm_archive = .{
.path = "wasm/archive",
},
.wasm_basic_features = .{
.path = "wasm/basic-features",
},
.wasm_export = .{
.path = "wasm/export",
},
.wasm_export_data = .{
.path = "wasm/export-data",
},
.wasm_extern = .{
.path = "wasm/extern",
},
.wasm_extern_mangle = .{
.path = "wasm/extern-mangle",
},
.wasm_function_table = .{
.path = "wasm/function-table",
},
.wasm_infer_features = .{
.path = "wasm/infer-features",
},
.wasm_producers = .{
.path = "wasm/producers",
},
.wasm_shared_memory = .{
.path = "wasm/shared-memory",
},
.wasm_stack_pointer = .{
.path = "wasm/stack_pointer",
},
.wasm_type = .{
.path = "wasm/type",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"link.zig",
},
}
-6
View File
@@ -1,6 +0,0 @@
int i;
int j;
int add_to_i_and_j(int x) {
return x + i + j;
}
-7
View File
@@ -1,7 +0,0 @@
long i;
int j = 2;
int k;
void incr_i() {
i++;
}
-37
View File
@@ -1,37 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const lib_a = b.addLibrary(.{
.linkage = .static,
.name = "a",
.root_module = b.createModule(.{
.root_source_file = null,
.optimize = optimize,
.target = b.graph.host,
}),
});
lib_a.root_module.addCSourceFiles(.{
.files = &.{ "c.c", "a.c", "b.c" },
.flags = &.{"-fcommon"},
});
const test_exe = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.optimize = optimize,
}),
});
test_exe.root_module.linkLibrary(lib_a);
test_step.dependOn(&b.addRunArtifact(test_exe).step);
}
-5
View File
@@ -1,5 +0,0 @@
extern int k;
int common_defined_externally() {
return k;
}
-16
View File
@@ -1,16 +0,0 @@
const std = @import("std");
const expect = std.testing.expect;
extern fn common_defined_externally() c_int;
extern fn incr_i() void;
extern fn add_to_i_and_j(x: c_int) c_int;
test "undef shadows common symbol: issue #9937" {
try expect(common_defined_externally() == 0);
}
test "import C common symbols" {
incr_i();
const res = add_to_i_and_j(2);
try expect(res == 5);
}
-2
View File
@@ -1,2 +0,0 @@
int foo;
__attribute__((aligned(4096))) int bar;
@@ -1,38 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const lib_a = b.addLibrary(.{
.linkage = .static,
.name = "a",
.root_module = b.createModule(.{
.root_source_file = null,
.optimize = optimize,
.target = b.graph.host,
}),
});
lib_a.root_module.addCSourceFiles(.{
.files = &.{"a.c"},
.flags = &.{"-fcommon"},
});
const test_exe = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.target = b.graph.host,
.optimize = optimize,
}),
});
test_exe.root_module.linkLibrary(lib_a);
test_step.dependOn(&b.addRunArtifact(test_exe).step);
}
@@ -1,9 +0,0 @@
const std = @import("std");
extern var foo: i32;
extern var bar: i32;
test {
try std.testing.expect(@intFromPtr(&foo) % 4 == 0);
try std.testing.expect(@intFromPtr(&bar) % 4096 == 0);
}
-4300
View File
@@ -1,4300 +0,0 @@
pub fn testAll(b: *Build, build_opts: BuildOptions) *Step {
_ = build_opts;
const elf_step = b.step("test-elf", "Run ELF tests");
// https://github.com/ziglang/zig/issues/25323
if (builtin.os.tag == .freebsd) return elf_step;
// https://github.com/ziglang/zig/issues/25961
if (comptime builtin.cpu.arch.endian() == .big) return elf_step;
const default_target = b.resolveTargetQuery(.{
.cpu_arch = .x86_64, // TODO relax this once ELF linker is able to handle other archs
.os_tag = .linux,
});
const x86_64_musl = b.resolveTargetQuery(.{
.cpu_arch = .x86_64,
.os_tag = .linux,
.abi = .musl,
});
const x86_64_gnu = b.resolveTargetQuery(.{
.cpu_arch = .x86_64,
.os_tag = .linux,
.abi = .gnu,
});
const aarch64_musl = b.resolveTargetQuery(.{
.cpu_arch = .aarch64,
.os_tag = .linux,
.abi = .musl,
});
const riscv64_musl = b.resolveTargetQuery(.{
.cpu_arch = .riscv64,
.os_tag = .linux,
.abi = .musl,
});
// Common tests
for (&[_]std.Target.Cpu.Arch{
.x86_64,
.aarch64,
}) |cpu_arch| {
const musl_target = b.resolveTargetQuery(.{
.cpu_arch = cpu_arch,
.os_tag = .linux,
.abi = .musl,
});
const gnu_target = b.resolveTargetQuery(.{
.cpu_arch = cpu_arch,
.os_tag = .linux,
.abi = .gnu,
});
// Exercise linker in -r mode
elf_step.dependOn(testEmitRelocatable(b, .{ .target = musl_target }));
elf_step.dependOn(testRelocatableArchive(b, .{ .target = musl_target }));
elf_step.dependOn(testRelocatableEhFrame(b, .{ .target = musl_target }));
elf_step.dependOn(testRelocatableEhFrameComdatHeavy(b, .{ .target = musl_target }));
elf_step.dependOn(testRelocatableNoEhFrame(b, .{ .target = musl_target }));
// Exercise linker in ar mode
elf_step.dependOn(testEmitStaticLib(b, .{ .target = musl_target }));
elf_step.dependOn(testEmitStaticLibZig(b, .{ .target = musl_target }));
// Exercise linker with LLVM backend
// musl tests
elf_step.dependOn(testAbsSymbols(b, .{ .target = musl_target }));
elf_step.dependOn(testComdatElimination(b, .{ .target = musl_target }));
elf_step.dependOn(testCommonSymbols(b, .{ .target = musl_target }));
elf_step.dependOn(testCommonSymbolsInArchive(b, .{ .target = musl_target }));
elf_step.dependOn(testCommentString(b, .{ .target = musl_target }));
elf_step.dependOn(testEmptyObject(b, .{ .target = musl_target }));
elf_step.dependOn(testEntryPoint(b, .{ .target = musl_target }));
elf_step.dependOn(testGcSections(b, .{ .target = musl_target }));
elf_step.dependOn(testGcSectionsZig(b, .{ .target = musl_target }));
elf_step.dependOn(testImageBase(b, .{ .target = musl_target }));
elf_step.dependOn(testInitArrayOrder(b, .{ .target = musl_target }));
elf_step.dependOn(testLargeAlignmentExe(b, .{ .target = musl_target }));
// https://codeberg.org/ziglang/zig/issues/31580
// elf_step.dependOn(testLargeBss(b, .{ .target = musl_target }));
elf_step.dependOn(testLinkingC(b, .{ .target = musl_target }));
elf_step.dependOn(testLinkingCpp(b, .{ .target = musl_target }));
elf_step.dependOn(testLinkingZig(b, .{ .target = musl_target }));
elf_step.dependOn(testLinksection(b, .{ .target = musl_target }));
elf_step.dependOn(testMergeStrings(b, .{ .target = musl_target }));
elf_step.dependOn(testMergeStrings2(b, .{ .target = musl_target }));
// https://github.com/ziglang/zig/issues/17451
// elf_step.dependOn(testNoEhFrameHdr(b, .{ .target = musl_target }));
elf_step.dependOn(testTlsStatic(b, .{ .target = musl_target }));
elf_step.dependOn(testStrip(b, .{ .target = musl_target }));
// glibc tests
elf_step.dependOn(testAsNeeded(b, .{ .target = gnu_target }));
// https://github.com/ziglang/zig/issues/17430
// elf_step.dependOn(testCanonicalPlt(b, .{ .target = gnu_target }));
elf_step.dependOn(testCommentString(b, .{ .target = gnu_target }));
elf_step.dependOn(testCopyrel(b, .{ .target = gnu_target }));
// https://github.com/ziglang/zig/issues/17430
// elf_step.dependOn(testCopyrelAlias(b, .{ .target = gnu_target }));
// https://github.com/ziglang/zig/issues/17430
// elf_step.dependOn(testCopyrelAlignment(b, .{ .target = gnu_target }));
elf_step.dependOn(testDsoPlt(b, .{ .target = gnu_target }));
elf_step.dependOn(testDsoUndef(b, .{ .target = gnu_target }));
elf_step.dependOn(testExportDynamic(b, .{ .target = gnu_target }));
elf_step.dependOn(testExportSymbolsFromExe(b, .{ .target = gnu_target }));
// https://github.com/ziglang/zig/issues/17430
// elf_step.dependOn(testFuncAddress(b, .{ .target = gnu_target }));
elf_step.dependOn(testHiddenWeakUndef(b, .{ .target = gnu_target }));
elf_step.dependOn(testIFuncAlias(b, .{ .target = gnu_target }));
// https://github.com/ziglang/zig/issues/17430
// elf_step.dependOn(testIFuncDlopen(b, .{ .target = gnu_target }));
elf_step.dependOn(testIFuncDso(b, .{ .target = gnu_target }));
elf_step.dependOn(testIFuncDynamic(b, .{ .target = gnu_target }));
elf_step.dependOn(testIFuncExport(b, .{ .target = gnu_target }));
elf_step.dependOn(testIFuncFuncPtr(b, .{ .target = gnu_target }));
elf_step.dependOn(testIFuncNoPlt(b, .{ .target = gnu_target }));
// https://github.com/ziglang/zig/issues/17430 ??
// elf_step.dependOn(testIFuncStatic(b, .{ .target = gnu_target }));
// elf_step.dependOn(testIFuncStaticPie(b, .{ .target = gnu_target }));
elf_step.dependOn(testInitArrayOrder(b, .{ .target = gnu_target }));
elf_step.dependOn(testLargeAlignmentDso(b, .{ .target = gnu_target }));
elf_step.dependOn(testLargeAlignmentExe(b, .{ .target = gnu_target }));
elf_step.dependOn(testLargeBss(b, .{ .target = gnu_target }));
elf_step.dependOn(testLinkOrder(b, .{ .target = gnu_target }));
elf_step.dependOn(testLdScript(b, .{ .target = gnu_target }));
// https://github.com/ziglang/zig/issues/23125
// elf_step.dependOn(testLdScriptPathError(b, .{ .target = gnu_target }));
elf_step.dependOn(testLdScriptAllowUndefinedVersion(b, .{ .target = gnu_target, .use_lld = true }));
elf_step.dependOn(testLdScriptDisallowUndefinedVersion(b, .{ .target = gnu_target, .use_lld = true }));
// https://github.com/ziglang/zig/issues/17451
// elf_step.dependOn(testNoEhFrameHdr(b, .{ .target = gnu_target }));
elf_step.dependOn(testPie(b, .{ .target = gnu_target }));
elf_step.dependOn(testPltGot(b, .{ .target = gnu_target }));
elf_step.dependOn(testPreinitArray(b, .{ .target = gnu_target }));
elf_step.dependOn(testSharedAbsSymbol(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsDfStaticTls(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsDso(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsGd(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsGdNoPlt(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsGdToIe(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsIe(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsLargeAlignment(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsLargeTbss(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsLargeStaticImage(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsLd(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsLdDso(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsLdNoPlt(b, .{ .target = gnu_target }));
// https://github.com/ziglang/zig/issues/17430
// elf_step.dependOn(testTlsNoPic(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsOffsetAlignment(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsPic(b, .{ .target = gnu_target }));
elf_step.dependOn(testTlsSmallAlignment(b, .{ .target = gnu_target }));
elf_step.dependOn(testUnknownFileTypeError(b, .{ .target = gnu_target }));
elf_step.dependOn(testUnresolvedError(b, .{ .target = gnu_target }));
elf_step.dependOn(testWeakExports(b, .{ .target = gnu_target }));
elf_step.dependOn(testWeakUndefsDso(b, .{ .target = gnu_target }));
elf_step.dependOn(testZNow(b, .{ .target = gnu_target }));
elf_step.dependOn(testZStackSize(b, .{ .target = gnu_target }));
}
// x86_64 specific tests
elf_step.dependOn(testMismatchedCpuArchitectureError(b, .{ .target = x86_64_musl }));
elf_step.dependOn(testZText(b, .{ .target = x86_64_gnu }));
// aarch64 specific tests
elf_step.dependOn(testThunks(b, .{ .target = aarch64_musl }));
// x86_64 self-hosted backend
elf_step.dependOn(testCommentString(b, .{ .use_llvm = false, .target = default_target }));
elf_step.dependOn(testCommentStringStaticLib(b, .{ .use_llvm = false, .target = default_target }));
elf_step.dependOn(testEmitRelocatable(b, .{ .use_llvm = false, .target = x86_64_musl }));
elf_step.dependOn(testEmitStaticLibZig(b, .{ .use_llvm = false, .target = x86_64_musl }));
elf_step.dependOn(testGcSectionsZig(b, .{ .use_llvm = false, .target = default_target }));
elf_step.dependOn(testLinkingObj(b, .{ .use_llvm = false, .target = default_target }));
elf_step.dependOn(testLinkingStaticLib(b, .{ .use_llvm = false, .target = default_target }));
elf_step.dependOn(testLinkingZig(b, .{ .use_llvm = false, .target = default_target }));
elf_step.dependOn(testLinksection(b, .{ .use_llvm = false, .target = default_target }));
elf_step.dependOn(testImportingDataDynamic(b, .{ .use_llvm = false, .target = x86_64_gnu }));
elf_step.dependOn(testImportingDataStatic(b, .{ .use_llvm = false, .target = x86_64_musl }));
// riscv64 linker backend is currently not complete enough to support more
elf_step.dependOn(testLinkingC(b, .{ .target = riscv64_musl }));
return elf_step;
}
fn testAbsSymbols(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "abs-symbols", opts);
const obj = addObject(b, opts, .{
.name = "obj",
.asm_source_bytes =
\\.globl foo
\\foo = 0x800008
\\
,
});
const exe = addExecutable(b, opts, .{
.name = "test",
.c_source_bytes =
\\#include <signal.h>
\\#include <stdio.h>
\\#include <stdlib.h>
\\#include <ucontext.h>
\\#include <assert.h>
\\void handler(int signum, siginfo_t *info, void *ptr) {
\\ assert((size_t)info->si_addr == 0x800008);
\\ exit(0);
\\}
\\extern int foo;
\\int main() {
\\ struct sigaction act;
\\ act.sa_flags = SA_SIGINFO | SA_RESETHAND;
\\ act.sa_sigaction = handler;
\\ sigemptyset(&act.sa_mask);
\\ sigaction(SIGSEGV, &act, 0);
\\ foo = 5;
\\ return 0;
\\}
,
});
exe.root_module.addObject(obj);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testAsNeeded(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "as-needed", opts);
const main_o = addObject(b, opts, .{
.name = "main",
.c_source_bytes =
\\#include <stdio.h>
\\int baz();
\\int main() {
\\ printf("%d\n", baz());
\\ return 0;
\\}
\\
,
});
main_o.root_module.link_libc = true;
const libfoo = addSharedLibrary(b, opts, .{ .name = "foo" });
addCSourceBytes(libfoo, "int foo() { return 42; }", &.{});
const libbar = addSharedLibrary(b, opts, .{ .name = "bar" });
addCSourceBytes(libbar, "int bar() { return 42; }", &.{});
const libbaz = addSharedLibrary(b, opts, .{ .name = "baz" });
addCSourceBytes(libbaz,
\\int foo();
\\int baz() { return foo(); }
, &.{});
{
const exe = addExecutable(b, opts, .{
.name = "test",
});
exe.root_module.addObject(main_o);
exe.root_module.linkSystemLibrary("foo", .{ .needed = true });
exe.root_module.addLibraryPath(libfoo.getEmittedBinDirectory());
exe.root_module.addRPath(libfoo.getEmittedBinDirectory());
exe.root_module.linkSystemLibrary("bar", .{ .needed = true });
exe.root_module.addLibraryPath(libbar.getEmittedBinDirectory());
exe.root_module.addRPath(libbar.getEmittedBinDirectory());
exe.root_module.linkSystemLibrary("baz", .{ .needed = true });
exe.root_module.addLibraryPath(libbaz.getEmittedBinDirectory());
exe.root_module.addRPath(libbaz.getEmittedBinDirectory());
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("42\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInDynamicSection();
check.checkExact("NEEDED libfoo.so");
check.checkExact("NEEDED libbar.so");
check.checkExact("NEEDED libbaz.so");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{
.name = "test",
});
exe.root_module.addObject(main_o);
exe.root_module.linkSystemLibrary("foo", .{ .needed = false });
exe.root_module.addLibraryPath(libfoo.getEmittedBinDirectory());
exe.root_module.addRPath(libfoo.getEmittedBinDirectory());
exe.root_module.linkSystemLibrary("bar", .{ .needed = false });
exe.root_module.addLibraryPath(libbar.getEmittedBinDirectory());
exe.root_module.addRPath(libbar.getEmittedBinDirectory());
exe.root_module.linkSystemLibrary("baz", .{ .needed = false });
exe.root_module.addLibraryPath(libbaz.getEmittedBinDirectory());
exe.root_module.addRPath(libbaz.getEmittedBinDirectory());
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("42\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInDynamicSection();
check.checkNotPresent("NEEDED libbar.so");
check.checkInDynamicSection();
check.checkExact("NEEDED libfoo.so");
check.checkExact("NEEDED libbaz.so");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testCanonicalPlt(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "canonical-plt", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\void *foo() {
\\ return foo;
\\}
\\void *bar() {
\\ return bar;
\\}
, &.{});
const b_o = addObject(b, opts, .{
.name = "obj",
.c_source_bytes =
\\void *bar();
\\void *baz() {
\\ return bar;
\\}
\\
,
.pic = true,
});
const main_o = addObject(b, opts, .{
.name = "main",
.c_source_bytes =
\\#include <assert.h>
\\void *foo();
\\void *bar();
\\void *baz();
\\int main() {
\\ assert(foo == foo());
\\ assert(bar == bar());
\\ assert(bar == baz());
\\ return 0;
\\}
\\
,
.pic = false,
});
main_o.root_module.link_libc = true;
const exe = addExecutable(b, opts, .{
.name = "main",
});
exe.root_module.addObject(main_o);
exe.root_module.addObject(b_o);
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
exe.pie = false;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testComdatElimination(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "comdat-elimination", opts);
const a_o = addObject(b, opts, .{
.name = "a",
.cpp_source_bytes =
\\#include <stdio.h>
\\inline void foo() {
\\ printf("calling foo in a\n");
\\}
\\void hello() {
\\ foo();
\\}
,
});
a_o.root_module.link_libcpp = true;
const main_o = addObject(b, opts, .{
.name = "main",
.cpp_source_bytes =
\\#include <stdio.h>
\\inline void foo() {
\\ printf("calling foo in main\n");
\\}
\\void hello();
\\int main() {
\\ foo();
\\ hello();
\\ return 0;
\\}
,
});
main_o.root_module.link_libcpp = true;
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(main_o);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual(
\\calling foo in a
\\calling foo in a
\\
);
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(main_o);
exe.root_module.addObject(a_o);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual(
\\calling foo in main
\\calling foo in main
\\
);
test_step.dependOn(&run.step);
}
{
const c_o = addObject(b, opts, .{ .name = "c" });
c_o.root_module.addObject(main_o);
c_o.root_module.addObject(a_o);
const exe = addExecutable(b, opts, .{ .name = "main3" });
exe.root_module.addObject(c_o);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual(
\\calling foo in main
\\calling foo in main
\\
);
test_step.dependOn(&run.step);
}
{
const d_o = addObject(b, opts, .{ .name = "d" });
d_o.root_module.addObject(a_o);
d_o.root_module.addObject(main_o);
const exe = addExecutable(b, opts, .{ .name = "main4" });
exe.root_module.addObject(d_o);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual(
\\calling foo in a
\\calling foo in a
\\
);
test_step.dependOn(&run.step);
}
return test_step;
}
fn testCommentString(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "comment-string", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes =
\\pub fn main() void {}
});
const check = exe.checkObject();
check.dumpSection(".comment");
check.checkContains("zig");
test_step.dependOn(&check.step);
return test_step;
}
fn testCommentStringStaticLib(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "comment-string-static-lib", opts);
const lib = addStaticLibrary(b, opts, .{ .name = "lib", .zig_source_bytes =
\\export fn foo() void {}
});
const check = lib.checkObject();
check.dumpSection(".comment");
check.checkContains("zig");
test_step.dependOn(&check.step);
return test_step;
}
fn testCommonSymbols(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "common-symbols", opts);
const exe = addExecutable(b, opts, .{
.name = "test",
});
addCSourceBytes(exe,
\\int foo;
\\int bar;
\\int baz = 42;
, &.{"-fcommon"});
addCSourceBytes(exe,
\\#include<stdio.h>
\\int foo;
\\int bar = 5;
\\int baz;
\\int main() {
\\ printf("%d %d %d\n", foo, bar, baz);
\\}
, &.{"-fcommon"});
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("0 5 42\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testCommonSymbolsInArchive(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "common-symbols-in-archive", opts);
const a_o = addObject(b, opts, .{
.name = "a",
.c_source_bytes =
\\#include <stdio.h>
\\int foo;
\\int bar;
\\extern int baz;
\\__attribute__((weak)) int two();
\\int main() {
\\ printf("%d %d %d %d\n", foo, bar, baz, two ? two() : -1);
\\}
\\
,
.c_source_flags = &.{"-fcommon"},
});
a_o.root_module.link_libc = true;
const b_o = addObject(b, opts, .{
.name = "b",
.c_source_bytes = "int foo = 5;",
.c_source_flags = &.{"-fcommon"},
});
{
const c_o = addObject(b, opts, .{
.name = "c",
.c_source_bytes =
\\int bar;
\\int two() { return 2; }
\\
,
.c_source_flags = &.{"-fcommon"},
});
const d_o = addObject(b, opts, .{
.name = "d",
.c_source_bytes = "int baz;",
.c_source_flags = &.{"-fcommon"},
});
const lib = addStaticLibrary(b, opts, .{ .name = "lib" });
lib.root_module.addObject(b_o);
lib.root_module.addObject(c_o);
lib.root_module.addObject(d_o);
const exe = addExecutable(b, opts, .{
.name = "test",
});
exe.root_module.addObject(a_o);
exe.root_module.linkLibrary(lib);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("5 0 0 -1\n");
test_step.dependOn(&run.step);
}
{
const e_o = addObject(b, opts, .{
.name = "e",
.c_source_bytes =
\\int bar = 0;
\\int baz = 7;
\\int two() { return 2; }
,
.c_source_flags = &.{"-fcommon"},
});
const lib = addStaticLibrary(b, opts, .{ .name = "lib" });
lib.root_module.addObject(b_o);
lib.root_module.addObject(e_o);
const exe = addExecutable(b, opts, .{
.name = "test",
});
exe.root_module.addObject(a_o);
exe.root_module.linkLibrary(lib);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("5 0 7 2\n");
test_step.dependOn(&run.step);
}
return test_step;
}
fn testCopyrel(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "copyrel", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\int foo = 3;
\\int bar = 5;
, &.{});
const exe = addExecutable(b, opts, .{
.name = "main",
.c_source_bytes =
\\#include<stdio.h>
\\extern int foo, bar;
\\int main() {
\\ printf("%d %d\n", foo, bar);
\\ return 0;
\\}
,
});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("3 5\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testCopyrelAlias(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "copyrel-alias", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\int bruh = 31;
\\int foo = 42;
\\extern int bar __attribute__((alias("foo")));
\\extern int baz __attribute__((alias("foo")));
, &.{});
const exe = addExecutable(b, opts, .{
.name = "main",
.pic = false,
});
addCSourceBytes(exe,
\\#include<stdio.h>
\\extern int foo;
\\extern int *get_bar();
\\int main() {
\\ printf("%d %d %d\n", foo, *get_bar(), &foo == get_bar());
\\ return 0;
\\}
, &.{});
addCSourceBytes(exe,
\\extern int bar;
\\int *get_bar() { return &bar; }
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
exe.pie = false;
const run = addRunArtifact(exe);
run.expectStdOutEqual("42 42 1\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testCopyrelAlignment(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "copyrel-alignment", opts);
const a_so = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(a_so, "__attribute__((aligned(32))) int foo = 5;", &.{});
const b_so = addSharedLibrary(b, opts, .{ .name = "b" });
addCSourceBytes(b_so, "__attribute__((aligned(8))) int foo = 5;", &.{});
const c_so = addSharedLibrary(b, opts, .{ .name = "c" });
addCSourceBytes(c_so, "__attribute__((aligned(256))) int foo = 5;", &.{});
const obj = addObject(b, opts, .{
.name = "main",
.c_source_bytes =
\\#include <stdio.h>
\\extern int foo;
\\int main() { printf("%d\n", foo); }
\\
,
.pic = false,
});
obj.root_module.link_libc = true;
const exp_stdout = "5\n";
{
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(obj);
exe.root_module.linkLibrary(a_so);
exe.root_module.link_libc = true;
exe.pie = false;
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("section headers");
check.checkExact("name .copyrel");
check.checkExact("addralign 20");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(obj);
exe.root_module.linkLibrary(b_so);
exe.root_module.link_libc = true;
exe.pie = false;
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("section headers");
check.checkExact("name .copyrel");
check.checkExact("addralign 8");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(obj);
exe.root_module.linkLibrary(c_so);
exe.root_module.link_libc = true;
exe.pie = false;
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("section headers");
check.checkExact("name .copyrel");
check.checkExact("addralign 100");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testDsoPlt(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "dso-plt", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "dso" });
addCSourceBytes(dso,
\\#include<stdio.h>
\\void world() {
\\ printf("world\n");
\\}
\\void real_hello() {
\\ printf("Hello ");
\\ world();
\\}
\\void hello() {
\\ real_hello();
\\}
, &.{});
dso.root_module.link_libc = true;
const exe = addExecutable(b, opts, .{ .name = "test" });
addCSourceBytes(exe,
\\#include<stdio.h>
\\void world() {
\\ printf("WORLD\n");
\\}
\\void hello();
\\int main() {
\\ hello();
\\}
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello WORLD\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testDsoUndef(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "dso-undef", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "dso" });
addCSourceBytes(dso,
\\extern int foo;
\\int bar = 5;
\\int baz() { return foo; }
, &.{});
dso.root_module.link_libc = true;
const obj = addObject(b, opts, .{
.name = "obj",
.c_source_bytes = "int foo = 3;",
});
const lib = addStaticLibrary(b, opts, .{ .name = "lib" });
lib.root_module.addObject(obj);
const exe = addExecutable(b, opts, .{ .name = "test" });
exe.root_module.linkLibrary(dso);
exe.root_module.linkLibrary(lib);
addCSourceBytes(exe,
\\extern int bar;
\\int main() {
\\ return bar - 5;
\\}
, &.{});
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInDynamicSymtab();
check.checkContains("foo");
test_step.dependOn(&check.step);
return test_step;
}
fn testEmitRelocatable(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "emit-relocatable", opts);
const a_o = addObject(b, opts, .{ .name = "a", .zig_source_bytes =
\\const std = @import("std");
\\extern var bar: i32;
\\export fn foo() i32 {
\\ return bar;
\\}
\\export fn printFoo() void {
\\ std.debug.print("foo={d}\n", .{foo()});
\\}
});
a_o.root_module.link_libc = true;
const b_o = addObject(b, opts, .{ .name = "b", .c_source_bytes =
\\#include <stdio.h>
\\int bar = 42;
\\void printBar() {
\\ fprintf(stderr, "bar=%d\n", bar);
\\}
});
b_o.root_module.link_libc = true;
const c_o = addObject(b, opts, .{ .name = "c" });
c_o.root_module.addObject(a_o);
c_o.root_module.addObject(b_o);
const exe = addExecutable(b, opts, .{ .name = "test", .zig_source_bytes =
\\const std = @import("std");
\\extern fn printFoo() void;
\\extern fn printBar() void;
\\pub fn main() void {
\\ printFoo();
\\ printBar();
\\}
});
exe.root_module.addObject(c_o);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdErrEqual(
\\foo=42
\\bar=42
\\
);
test_step.dependOn(&run.step);
return test_step;
}
fn testEmitStaticLib(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "emit-static-lib", opts);
const obj1 = addObject(b, opts, .{
.name = "obj1",
.c_source_bytes =
\\int foo = 0;
\\int bar = 2;
\\int fooBar() {
\\ return foo + bar;
\\}
,
});
const obj2 = addObject(b, opts, .{
.name = "obj2",
.c_source_bytes = "int tentative;",
.c_source_flags = &.{"-fcommon"},
});
const obj3 = addObject(b, opts, .{
.name = "a_very_long_file_name_so_that_it_ends_up_in_strtab",
.zig_source_bytes =
\\fn weakFoo() callconv(.c) usize {
\\ return 42;
\\}
\\export var strongBar: usize = 100;
\\comptime {
\\ @export(&weakFoo, .{ .name = "weakFoo", .linkage = .weak });
\\ @export(&strongBar, .{ .name = "strongBarAlias", .linkage = .strong });
\\}
,
});
const lib = addStaticLibrary(b, opts, .{ .name = "lib" });
lib.root_module.addObject(obj1);
lib.root_module.addObject(obj2);
lib.root_module.addObject(obj3);
const check = lib.checkObject();
check.checkInArchiveSymtab();
check.checkExact("in object obj1.o");
check.checkExact("foo");
check.checkInArchiveSymtab();
check.checkExact("in object obj1.o");
check.checkExact("bar");
check.checkInArchiveSymtab();
check.checkExact("in object obj1.o");
check.checkExact("fooBar");
check.checkInArchiveSymtab();
check.checkExact("in object obj2.o");
check.checkExact("tentative");
check.checkInArchiveSymtab();
check.checkExact("in object a_very_long_file_name_so_that_it_ends_up_in_strtab.o");
check.checkExact("weakFoo");
check.checkInArchiveSymtab();
check.checkExact("in object a_very_long_file_name_so_that_it_ends_up_in_strtab.o");
check.checkExact("strongBar");
check.checkInArchiveSymtab();
check.checkExact("in object a_very_long_file_name_so_that_it_ends_up_in_strtab.o");
check.checkExact("strongBarAlias");
test_step.dependOn(&check.step);
return test_step;
}
fn testEmitStaticLibZig(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "emit-static-lib-zig", opts);
const obj1 = addObject(b, opts, .{
.name = "obj1",
.zig_source_bytes =
\\export var foo: i32 = 42;
\\export var bar: i32 = 2;
,
});
const lib = addStaticLibrary(b, opts, .{
.name = "lib",
.zig_source_bytes =
\\extern var foo: i32;
\\extern var bar: i32;
\\export fn fooBar() i32 {
\\ return foo + bar;
\\}
,
});
lib.root_module.addObject(obj1);
const exe = addExecutable(b, opts, .{
.name = "test",
.zig_source_bytes =
\\const std = @import("std");
\\extern fn fooBar() i32;
\\pub fn main() void {
\\ std.debug.print("{d}", .{fooBar()});
\\}
,
});
exe.root_module.linkLibrary(lib);
const run = addRunArtifact(exe);
run.expectStdErrEqual("44");
test_step.dependOn(&run.step);
return test_step;
}
fn testEmptyObject(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "empty-object", opts);
const exe = addExecutable(b, opts, .{ .name = "test" });
addCSourceBytes(exe, "int main() { return 0; }", &.{});
addCSourceBytes(exe, "", &.{});
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testEntryPoint(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "entry-point", opts);
const a_o = addObject(b, opts, .{
.name = "a",
.asm_source_bytes =
\\.globl foo, bar
\\foo = 0x1000
\\bar = 0x2000
\\
,
});
const b_o = addObject(b, opts, .{
.name = "b",
.c_source_bytes = "int main() { return 0; }",
});
{
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.entry = .{ .symbol_name = "foo" };
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkExact("entry 1000");
test_step.dependOn(&check.step);
}
{
// TODO looks like not assigning a unique name to this executable will
// cause an artifact collision taking the cached executable from the above
// step instead of generating a new one.
const exe = addExecutable(b, opts, .{ .name = "other" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.entry = .{ .symbol_name = "bar" };
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkExact("entry 2000");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testExportDynamic(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "export-dynamic", opts);
const obj = addObject(b, opts, .{
.name = "obj",
.asm_source_bytes =
\\.text
\\ .globl foo
\\ .hidden foo
\\foo:
\\ nop
\\ .globl bar
\\bar:
\\ nop
\\ .globl _start
\\_start:
\\ nop
\\
,
});
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso, "int baz = 10;", &.{});
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\extern int baz;
\\int callBaz() {
\\ return baz;
\\}
, &.{});
exe.root_module.addObject(obj);
exe.root_module.linkLibrary(dso);
exe.rdynamic = true;
const check = exe.checkObject();
check.checkInDynamicSymtab();
check.checkContains("bar");
check.checkInDynamicSymtab();
check.checkContains("_start");
test_step.dependOn(&check.step);
return test_step;
}
fn testExportSymbolsFromExe(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "export-symbols-from-exe", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\void expfn1();
\\void expfn2() {}
\\
\\void foo() {
\\ expfn1();
\\}
, &.{});
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\void expfn1() {}
\\void expfn2() {}
\\void foo();
\\
\\int main() {
\\ expfn1();
\\ expfn2();
\\ foo();
\\}
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const check = exe.checkObject();
check.checkInDynamicSymtab();
check.checkContains("expfn2");
check.checkInDynamicSymtab();
check.checkContains("expfn1");
test_step.dependOn(&check.step);
return test_step;
}
fn testFuncAddress(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "func-address", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso, "void fn() {}", &.{});
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <assert.h>
\\typedef void Func();
\\void fn();
\\Func *const ptr = fn;
\\int main() {
\\ assert(fn == ptr);
\\}
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.pic = false;
exe.pie = false;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testGcSections(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "gc-sections", opts);
const obj = addObject(b, opts, .{
.name = "obj",
.cpp_source_bytes =
\\#include <stdio.h>
\\int two() { return 2; }
\\int live_var1 = 1;
\\int live_var2 = two();
\\int dead_var1 = 3;
\\int dead_var2 = 4;
\\void live_fn1() {}
\\void live_fn2() { live_fn1(); }
\\void dead_fn1() {}
\\void dead_fn2() { dead_fn1(); }
\\int main() {
\\ printf("%d %d\n", live_var1, live_var2);
\\ live_fn2();
\\}
,
});
obj.link_function_sections = true;
obj.link_data_sections = true;
obj.root_module.link_libc = true;
obj.root_module.link_libcpp = true;
{
const exe = addExecutable(b, opts, .{ .name = "test" });
exe.root_module.addObject(obj);
exe.link_gc_sections = false;
exe.root_module.link_libc = true;
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("live_var1");
check.checkInSymtab();
check.checkContains("live_var2");
check.checkInSymtab();
check.checkContains("dead_var1");
check.checkInSymtab();
check.checkContains("dead_var2");
check.checkInSymtab();
check.checkContains("live_fn1");
check.checkInSymtab();
check.checkContains("live_fn2");
check.checkInSymtab();
check.checkContains("dead_fn1");
check.checkInSymtab();
check.checkContains("dead_fn2");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "test" });
exe.root_module.addObject(obj);
exe.link_gc_sections = true;
exe.root_module.link_libc = true;
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("live_var1");
check.checkInSymtab();
check.checkContains("live_var2");
check.checkInSymtab();
check.checkNotPresent("dead_var1");
check.checkInSymtab();
check.checkNotPresent("dead_var2");
check.checkInSymtab();
check.checkContains("live_fn1");
check.checkInSymtab();
check.checkContains("live_fn2");
check.checkInSymtab();
check.checkNotPresent("dead_fn1");
check.checkInSymtab();
check.checkNotPresent("dead_fn2");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testGcSectionsZig(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "gc-sections-zig", opts);
const obj = addObject(b, .{
.target = opts.target,
.use_llvm = true,
}, .{
.name = "obj",
.c_source_bytes =
\\int live_var1 = 1;
\\int live_var2 = 2;
\\int dead_var1 = 3;
\\int dead_var2 = 4;
\\void live_fn1() {}
\\void live_fn2() { live_fn1(); }
\\void dead_fn1() {}
\\void dead_fn2() { dead_fn1(); }
,
});
obj.link_function_sections = true;
obj.link_data_sections = true;
{
const exe = addExecutable(b, opts, .{
.name = "test1",
.zig_source_bytes =
\\const std = @import("std");
\\extern var live_var1: i32;
\\extern var live_var2: i32;
\\extern fn live_fn2() void;
\\pub fn main() void {
\\ var stdout_writer = std.Io.File.stdout().writerStreaming(std.Options.debug_io, &.{});
\\ stdout_writer.interface.print("{d} {d}\n", .{ live_var1, live_var2 }) catch @panic("fail");
\\ live_fn2();
\\}
,
});
exe.root_module.addObject(obj);
exe.link_gc_sections = false;
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("live_var1");
check.checkInSymtab();
check.checkContains("live_var2");
check.checkInSymtab();
check.checkContains("dead_var1");
check.checkInSymtab();
check.checkContains("dead_var2");
check.checkInSymtab();
check.checkContains("live_fn1");
check.checkInSymtab();
check.checkContains("live_fn2");
check.checkInSymtab();
check.checkContains("dead_fn1");
check.checkInSymtab();
check.checkContains("dead_fn2");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{
.name = "test2",
.zig_source_bytes =
\\const std = @import("std");
\\extern var live_var1: i32;
\\extern var live_var2: i32;
\\extern fn live_fn2() void;
\\pub fn main() void {
\\ var stdout_writer = std.Io.File.stdout().writerStreaming(std.Options.debug_io, &.{});
\\ stdout_writer.interface.print("{d} {d}\n", .{ live_var1, live_var2 }) catch @panic("fail");
\\ live_fn2();
\\}
,
});
exe.root_module.addObject(obj);
exe.link_gc_sections = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("live_var1");
check.checkInSymtab();
check.checkContains("live_var2");
check.checkInSymtab();
check.checkNotPresent("dead_var1");
check.checkInSymtab();
check.checkNotPresent("dead_var2");
check.checkInSymtab();
check.checkContains("live_fn1");
check.checkInSymtab();
check.checkContains("live_fn2");
check.checkInSymtab();
check.checkNotPresent("dead_fn1");
check.checkInSymtab();
check.checkNotPresent("dead_fn2");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testHiddenWeakUndef(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "hidden-weak-undef", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\__attribute__((weak, visibility("hidden"))) void foo();
\\void bar() { foo(); }
, &.{});
const check = dso.checkObject();
check.checkInDynamicSymtab();
check.checkNotPresent("foo");
check.checkInDynamicSymtab();
check.checkContains("bar");
test_step.dependOn(&check.step);
return test_step;
}
fn testIFuncAlias(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ifunc-alias", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <assert.h>
\\void foo() {}
\\int bar() __attribute__((ifunc("resolve_bar")));
\\void *resolve_bar() { return foo; }
\\void *bar2 = bar;
\\int main() {
\\ assert(bar == bar2);
\\}
, &.{});
exe.root_module.pic = true;
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testIFuncDlopen(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ifunc-dlopen", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\__attribute__((ifunc("resolve_foo")))
\\void foo(void);
\\static void real_foo(void) {
\\}
\\typedef void Func();
\\static Func *resolve_foo(void) {
\\ return real_foo;
\\}
, &.{});
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <dlfcn.h>
\\#include <assert.h>
\\#include <stdlib.h>
\\typedef void Func();
\\void foo(void);
\\int main() {
\\ void *handle = dlopen(NULL, RTLD_NOW);
\\ Func *p = dlsym(handle, "foo");
\\
\\ foo();
\\ p();
\\ assert(foo == p);
\\}
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
exe.root_module.linkSystemLibrary("dl", .{});
exe.root_module.pic = false;
exe.pie = false;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testIFuncDso(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ifunc-dso", opts);
const dso = addSharedLibrary(b, opts, .{
.name = "a",
.c_source_bytes =
\\#include<stdio.h>
\\__attribute__((ifunc("resolve_foobar")))
\\void foobar(void);
\\static void real_foobar(void) {
\\ printf("Hello world\n");
\\}
\\typedef void Func();
\\static Func *resolve_foobar(void) {
\\ return real_foobar;
\\}
,
});
dso.root_module.link_libc = true;
const exe = addExecutable(b, opts, .{
.name = "main",
.c_source_bytes =
\\void foobar(void);
\\int main() {
\\ foobar();
\\}
,
});
exe.root_module.linkLibrary(dso);
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testIFuncDynamic(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ifunc-dynamic", opts);
const main_c =
\\#include <stdio.h>
\\__attribute__((ifunc("resolve_foobar")))
\\static void foobar(void);
\\static void real_foobar(void) {
\\ printf("Hello world\n");
\\}
\\typedef void Func();
\\static Func *resolve_foobar(void) {
\\ return real_foobar;
\\}
\\int main() {
\\ foobar();
\\}
;
{
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe, main_c, &.{});
exe.root_module.link_libc = true;
exe.link_z_lazy = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world\n");
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "other" });
addCSourceBytes(exe, main_c, &.{});
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world\n");
test_step.dependOn(&run.step);
}
return test_step;
}
fn testIFuncExport(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ifunc-export", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\#include <stdio.h>
\\__attribute__((ifunc("resolve_foobar")))
\\void foobar(void);
\\void real_foobar(void) {
\\ printf("Hello world\n");
\\}
\\typedef void Func();
\\Func *resolve_foobar(void) {
\\ return real_foobar;
\\}
, &.{});
dso.root_module.link_libc = true;
const check = dso.checkObject();
check.checkInDynamicSymtab();
check.checkContains("IFUNC GLOBAL DEFAULT foobar");
test_step.dependOn(&check.step);
return test_step;
}
fn testIFuncFuncPtr(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ifunc-func-ptr", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\typedef int Fn();
\\int foo() __attribute__((ifunc("resolve_foo")));
\\int real_foo() { return 3; }
\\Fn *resolve_foo(void) {
\\ return real_foo;
\\}
, &.{});
addCSourceBytes(exe,
\\typedef int Fn();
\\int foo();
\\Fn *get_foo() { return foo; }
, &.{});
addCSourceBytes(exe,
\\#include <stdio.h>
\\typedef int Fn();
\\Fn *get_foo();
\\int main() {
\\ Fn *f = get_foo();
\\ printf("%d\n", f());
\\}
, &.{});
exe.root_module.pic = true;
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("3\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testIFuncNoPlt(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ifunc-noplt", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\__attribute__((ifunc("resolve_foo")))
\\void foo(void);
\\void hello(void) {
\\ printf("Hello world\n");
\\}
\\typedef void Fn();
\\Fn *resolve_foo(void) {
\\ return hello;
\\}
\\int main() {
\\ foo();
\\}
, &.{"-fno-plt"});
exe.root_module.pic = true;
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testIFuncStatic(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ifunc-static", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\void foo() __attribute__((ifunc("resolve_foo")));
\\void hello() {
\\ printf("Hello world\n");
\\}
\\void *resolve_foo() {
\\ return hello;
\\}
\\int main() {
\\ foo();
\\ return 0;
\\}
, &.{});
exe.root_module.link_libc = true;
exe.linkage = .static;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testIFuncStaticPie(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ifunc-static-pie", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\void foo() __attribute__((ifunc("resolve_foo")));
\\void hello() {
\\ printf("Hello world\n");
\\}
\\void *resolve_foo() {
\\ return hello;
\\}
\\int main() {
\\ foo();
\\ return 0;
\\}
, &.{});
exe.linkage = .static;
exe.root_module.pic = true;
exe.pie = true;
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkExact("type DYN");
check.checkInHeaders();
check.checkExact("section headers");
check.checkExact("name .dynamic");
check.checkInHeaders();
check.checkExact("section headers");
check.checkNotPresent("name .interp");
test_step.dependOn(&check.step);
return test_step;
}
fn testImageBase(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "image-base", opts);
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\int main() {
\\ printf("Hello World!\n");
\\ return 0;
\\}
, &.{});
exe.root_module.link_libc = true;
exe.image_base = 0x8000000;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello World!\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkExtract("entry {addr}");
check.checkComputeCompare("addr", .{ .op = .gte, .value = .{ .literal = 0x8000000 } });
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
addCSourceBytes(exe, "void _start() {}", &.{});
exe.image_base = 0xffffffff8000000;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkExtract("entry {addr}");
check.checkComputeCompare("addr", .{ .op = .gte, .value = .{ .literal = 0xffffffff8000000 } });
test_step.dependOn(&check.step);
}
return test_step;
}
fn testImportingDataDynamic(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "importing-data-dynamic", opts);
const dso = addSharedLibrary(b, .{
.target = opts.target,
.optimize = opts.optimize,
.use_llvm = true,
}, .{
.name = "a",
.c_source_bytes =
\\#include <stdio.h>
\\int foo = 42;
\\void printFoo() { fprintf(stderr, "lib foo=%d\n", foo); }
,
});
dso.root_module.link_libc = true;
const main = addExecutable(b, opts, .{
.name = "main",
.zig_source_bytes =
\\const std = @import("std");
\\extern var foo: i32;
\\extern fn printFoo() void;
\\pub fn main() void {
\\ std.debug.print("exe foo={d}\n", .{foo});
\\ printFoo();
\\ foo += 1;
\\ std.debug.print("exe foo={d}\n", .{foo});
\\ printFoo();
\\}
,
.strip = true, // TODO temp hack
});
main.pie = true;
main.root_module.linkLibrary(dso);
const run = addRunArtifact(main);
run.expectStdErrEqual(
\\exe foo=42
\\lib foo=42
\\exe foo=43
\\lib foo=43
\\
);
test_step.dependOn(&run.step);
return test_step;
}
fn testImportingDataStatic(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "importing-data-static", opts);
const obj = addObject(b, .{
.target = opts.target,
.optimize = opts.optimize,
.use_llvm = true,
}, .{
.name = "a",
.c_source_bytes = "int foo = 42;",
});
const lib = addStaticLibrary(b, .{
.target = opts.target,
.optimize = opts.optimize,
.use_llvm = true,
}, .{
.name = "a",
});
lib.root_module.addObject(obj);
const main = addExecutable(b, opts, .{
.name = "main",
.zig_source_bytes =
\\extern var foo: i32;
\\pub fn main() void {
\\ @import("std").debug.print("{d}\n", .{foo});
\\}
,
.strip = true, // TODO temp hack
});
main.root_module.linkLibrary(lib);
main.root_module.link_libc = true;
const run = addRunArtifact(main);
run.expectStdErrEqual("42\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testInitArrayOrder(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "init-array-order", opts);
const a_o = addObject(b, opts, .{
.name = "a",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((constructor(10000))) void init4() { printf("1"); }
,
});
a_o.root_module.link_libc = true;
const b_o = addObject(b, opts, .{
.name = "b",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((constructor(1000))) void init3() { printf("2"); }
,
});
b_o.root_module.link_libc = true;
const c_o = addObject(b, opts, .{
.name = "c",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((constructor)) void init1() { printf("3"); }
,
});
c_o.root_module.link_libc = true;
const d_o = addObject(b, opts, .{
.name = "d",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((constructor)) void init2() { printf("4"); }
,
});
d_o.root_module.link_libc = true;
const e_o = addObject(b, opts, .{
.name = "e",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((destructor(10000))) void fini4() { printf("5"); }
,
});
e_o.root_module.link_libc = true;
const f_o = addObject(b, opts, .{
.name = "f",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((destructor(1000))) void fini3() { printf("6"); }
,
});
f_o.root_module.link_libc = true;
const g_o = addObject(b, opts, .{
.name = "g",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((destructor)) void fini1() { printf("7"); }
,
});
g_o.root_module.link_libc = true;
const h_o = addObject(b, opts, .{ .name = "h", .c_source_bytes =
\\#include <stdio.h>
\\__attribute__((destructor)) void fini2() { printf("8"); }
});
h_o.root_module.link_libc = true;
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe, "int main() { return 0; }", &.{});
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.root_module.addObject(c_o);
exe.root_module.addObject(d_o);
exe.root_module.addObject(e_o);
exe.root_module.addObject(f_o);
exe.root_module.addObject(g_o);
exe.root_module.addObject(h_o);
if (opts.target.result.isGnuLibC()) {
// TODO I think we need to clarify our use of `-fPIC -fPIE` flags for different targets
exe.pie = true;
}
const run = addRunArtifact(exe);
run.expectStdOutEqual("21348756");
test_step.dependOn(&run.step);
return test_step;
}
fn testLargeAlignmentDso(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "large-alignment-dso", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "dso" });
addCSourceBytes(dso,
\\#include <stdio.h>
\\#include <stdint.h>
\\void hello() __attribute__((aligned(32768), section(".hello")));
\\void world() __attribute__((aligned(32768), section(".world")));
\\void hello() {
\\ printf("Hello");
\\}
\\void world() {
\\ printf(" world");
\\}
\\void greet() {
\\ hello();
\\ world();
\\}
, &.{});
dso.link_function_sections = true;
dso.root_module.link_libc = true;
const check = dso.checkObject();
check.checkInSymtab();
check.checkExtract("{addr1} {size1} {shndx1} FUNC GLOBAL DEFAULT hello");
check.checkInSymtab();
check.checkExtract("{addr2} {size2} {shndx2} FUNC GLOBAL DEFAULT world");
check.checkComputeCompare("addr1 16 %", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("addr2 16 %", .{ .op = .eq, .value = .{ .literal = 0 } });
test_step.dependOn(&check.step);
const exe = addExecutable(b, opts, .{ .name = "test" });
addCSourceBytes(exe,
\\void greet();
\\int main() { greet(); }
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world");
test_step.dependOn(&run.step);
return test_step;
}
fn testLargeAlignmentExe(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "large-alignment-exe", opts);
const exe = addExecutable(b, opts, .{ .name = "test" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\#include <stdint.h>
\\
\\void hello() __attribute__((aligned(32768), section(".hello")));
\\void world() __attribute__((aligned(32768), section(".world")));
\\
\\void hello() {
\\ printf("Hello");
\\}
\\
\\void world() {
\\ printf(" world");
\\}
\\
\\int main() {
\\ hello();
\\ world();
\\}
, &.{});
exe.link_function_sections = true;
exe.root_module.link_libc = true;
const check = exe.checkObject();
check.checkInSymtab();
check.checkExtract("{addr1} {size1} {shndx1} FUNC LOCAL DEFAULT hello");
check.checkInSymtab();
check.checkExtract("{addr2} {size2} {shndx2} FUNC LOCAL DEFAULT world");
check.checkComputeCompare("addr1 16 %", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("addr2 16 %", .{ .op = .eq, .value = .{ .literal = 0 } });
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world");
test_step.dependOn(&run.step);
return test_step;
}
fn testLargeBss(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "large-bss", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\char arr[0x100000000];
\\int main() {
\\ return arr[2000];
\\}
, &.{});
exe.root_module.link_libc = true;
// Disabled to work around the ELF linker crashing.
// Can be reproduced on a x86_64-linux host by commenting out the line below.
exe.root_module.sanitize_c = .off;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testLinkOrder(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "link-order", opts);
const obj = addObject(b, opts, .{
.name = "obj",
.c_source_bytes = "void foo() {}",
.pic = true,
});
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
dso.root_module.addObject(obj);
const lib = addStaticLibrary(b, opts, .{ .name = "b" });
lib.root_module.addObject(obj);
const main_o = addObject(b, opts, .{
.name = "main",
.c_source_bytes =
\\void foo();
\\int main() {
\\ foo();
\\}
,
});
// https://github.com/ziglang/zig/issues/17450
// {
// const exe = addExecutable(b, opts, .{ .name = "main1"});
// exe.root_module.addObject(main_o);
// exe.root_module.linkSystemLibrary("a", .{});
// exe.root_module.addLibraryPath(dso.getEmittedBinDirectory());
// exe.root_module.addRPath(dso.getEmittedBinDirectory());
// exe.root_module.linkSystemLibrary("b", .{});
// exe.root_module.addLibraryPath(lib.getEmittedBinDirectory());
// exe.root_module.addRPath(lib.getEmittedBinDirectory());
// exe.root_module.link_libc = true;
// const check = exe.checkObject();
// check.checkInDynamicSection();
// check.checkContains("libb.so");
// test_step.dependOn(&check.step);
// }
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(main_o);
exe.root_module.linkSystemLibrary("b", .{});
exe.root_module.addLibraryPath(lib.getEmittedBinDirectory());
exe.root_module.addRPath(lib.getEmittedBinDirectory());
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.addLibraryPath(dso.getEmittedBinDirectory());
exe.root_module.addRPath(dso.getEmittedBinDirectory());
exe.root_module.link_libc = true;
const check = exe.checkObject();
check.checkInDynamicSection();
check.checkNotPresent("libb.so");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testLdScript(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ld-script", opts);
const bar = addSharedLibrary(b, opts, .{ .name = "bar" });
addCSourceBytes(bar, "int bar() { return 42; }", &.{});
const baz = addSharedLibrary(b, opts, .{ .name = "baz" });
addCSourceBytes(baz, "int baz() { return 42; }", &.{});
const scripts = WriteFile.create(b);
_ = scripts.add("liba.so", "INPUT(libfoo.so libfoo2.so.1)");
_ = scripts.add("libfoo.so", "GROUP(AS_NEEDED(-lbar))");
// Check finding a versioned .so file that is elsewhere in the library search paths.
const scripts2 = WriteFile.create(b);
_ = scripts2.add("libfoo2.so.1", "GROUP(AS_NEEDED(-lbaz))");
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\int bar();
\\int baz();
\\int main() {
\\ return bar() - baz();
\\}
, &.{});
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.addLibraryPath(scripts.getDirectory());
exe.root_module.addLibraryPath(scripts2.getDirectory());
exe.root_module.addLibraryPath(bar.getEmittedBinDirectory());
exe.root_module.addLibraryPath(baz.getEmittedBinDirectory());
exe.root_module.addRPath(bar.getEmittedBinDirectory());
exe.root_module.addRPath(baz.getEmittedBinDirectory());
exe.root_module.link_libc = true;
exe.allow_so_scripts = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testLdScriptPathError(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ld-script-path-error", opts);
const scripts = WriteFile.create(b);
_ = scripts.add("liba.so", "INPUT(libfoo.so)");
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe, "int main() { return 0; }", &.{});
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.addLibraryPath(scripts.getDirectory());
exe.root_module.link_libc = true;
exe.allow_so_scripts = true;
// TODO: A future enhancement could make this error message also mention
// the file that references the missing library.
expectLinkErrors(exe, test_step, .{
.stderr_contains = "error: libfoo.so: file listed in linker script not found",
});
return test_step;
}
fn testLdScriptAllowUndefinedVersion(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ld-script-allow-undefined-version", opts);
const so = addSharedLibrary(b, opts, .{
.name = "add",
.zig_source_bytes =
\\export fn add(a: i32, b: i32) i32 {
\\ return a + b;
\\}
,
});
const ld = b.addWriteFiles().add("add.ld", "VERSION { ADD_1.0 { global: add; sub; local: *; }; }");
so.setLinkerScript(ld);
so.linker_allow_undefined_version = true;
const exe = addExecutable(b, opts, .{
.name = "main",
.zig_source_bytes =
\\const std = @import("std");
\\extern fn add(a: i32, b: i32) i32;
\\pub fn main() void {
\\ std.debug.print("{d}\n", .{add(1, 2)});
\\}
,
});
exe.root_module.linkLibrary(so);
exe.root_module.link_libc = true;
exe.allow_so_scripts = true;
const run = addRunArtifact(exe);
run.expectStdErrEqual("3\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testLdScriptDisallowUndefinedVersion(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "ld-script-disallow-undefined-version", opts);
const so = addSharedLibrary(b, opts, .{
.name = "add",
.zig_source_bytes =
\\export fn add(a: i32, b: i32) i32 {
\\ return a + b;
\\}
,
});
const ld = b.addWriteFiles().add("add.ld", "VERSION { ADD_1.0 { global: add; sub; local: *; }; }");
so.setLinkerScript(ld);
so.linker_allow_undefined_version = false;
so.allow_so_scripts = true;
expectLinkErrors(
so,
test_step,
.{
.contains = "error: ld.lld: version script assignment of 'ADD_1.0' to symbol 'sub' failed: symbol not defined",
},
);
return test_step;
}
fn testMismatchedCpuArchitectureError(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "mismatched-cpu-architecture-error", opts);
const obj = addObject(b, .{
.target = b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .gnu }),
}, .{
.name = "a",
.c_source_bytes = "int foo;",
.strip = true,
});
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\extern int foo;
\\int main() {
\\ return foo;
\\}
, &.{});
exe.root_module.addObject(obj);
exe.root_module.link_libc = true;
expectLinkErrors(exe, test_step, .{ .exact = &.{
"invalid ELF machine type: AARCH64",
"note: while parsing /?/a.o",
} });
return test_step;
}
fn testLinkingC(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "linking-c", opts);
const exe = addExecutable(b, opts, .{ .name = "test" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\int main() {
\\ printf("Hello World!\n");
\\ return 0;
\\}
, &.{});
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello World!\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkExact("type EXEC");
check.checkInHeaders();
check.checkExact("section headers");
check.checkNotPresent("name .dynamic");
test_step.dependOn(&check.step);
return test_step;
}
fn testLinkingCpp(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "linking-cpp", opts);
const exe = addExecutable(b, opts, .{ .name = "test" });
addCppSourceBytes(exe,
\\#include <iostream>
\\int main() {
\\ std::cout << "Hello World!" << std::endl;
\\ return 0;
\\}
, &.{});
exe.root_module.link_libc = true;
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello World!\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkExact("type EXEC");
check.checkInHeaders();
check.checkExact("section headers");
check.checkNotPresent("name .dynamic");
test_step.dependOn(&check.step);
return test_step;
}
fn testLinkingObj(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "linking-obj", opts);
const obj = addObject(b, opts, .{
.name = "aobj",
.zig_source_bytes =
\\extern var mod: usize;
\\export fn callMe() usize {
\\ return me * mod;
\\}
\\var me: usize = 42;
,
});
const exe = addExecutable(b, opts, .{
.name = "testobj",
.zig_source_bytes =
\\const std = @import("std");
\\extern fn callMe() usize;
\\export var mod: usize = 2;
\\pub fn main() void {
\\ std.debug.print("{d}\n", .{callMe()});
\\}
,
});
exe.root_module.addObject(obj);
const run = addRunArtifact(exe);
run.expectStdErrEqual("84\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testLinkingStaticLib(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "linking-static-lib", opts);
const obj = addObject(b, opts, .{
.name = "bobj",
.zig_source_bytes = "export var bar: i32 = -42;",
});
const lib = addStaticLibrary(b, opts, .{
.name = "alib",
.zig_source_bytes =
\\export fn foo() i32 {
\\ return 42;
\\}
,
});
lib.root_module.addObject(obj);
const exe = addExecutable(b, opts, .{
.name = "testlib",
.zig_source_bytes =
\\const std = @import("std");
\\extern fn foo() i32;
\\extern var bar: i32;
\\pub fn main() void {
\\ std.debug.print("{d}\n", .{foo() + bar});
\\}
,
});
exe.root_module.linkLibrary(lib);
const run = addRunArtifact(exe);
run.expectStdErrEqual("0\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testLinkingZig(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "linking-zig-static", opts);
const exe = addExecutable(b, opts, .{
.name = "test",
.zig_source_bytes =
\\pub fn main() void {
\\ @import("std").debug.print("Hello World!\n", .{});
\\}
,
});
const run = addRunArtifact(exe);
run.expectStdErrEqual("Hello World!\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkExact("type EXEC");
check.checkInHeaders();
check.checkExact("section headers");
check.checkNotPresent("name .dynamic");
test_step.dependOn(&check.step);
return test_step;
}
fn testLinksection(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "linksection", opts);
const obj = addObject(b, opts, .{ .name = "main", .zig_source_bytes =
\\export var test_global: u32 linksection(".TestGlobal") = undefined;
\\export fn testFn() linksection(".TestFn") callconv(.c) void {
\\ TestGenericFn("A").f();
\\}
\\fn TestGenericFn(comptime suffix: []const u8) type {
\\ return struct {
\\ fn f() linksection(".TestGenFn" ++ suffix) void {}
\\ };
\\}
});
const check = obj.checkObject();
check.checkInSymtab();
check.checkContains("SECTION LOCAL DEFAULT .TestGlobal");
check.checkInSymtab();
check.checkContains("SECTION LOCAL DEFAULT .TestFn");
check.checkInSymtab();
check.checkContains("SECTION LOCAL DEFAULT .TestGenFnA");
check.checkInSymtab();
check.checkContains("OBJECT GLOBAL DEFAULT test_global");
check.checkInSymtab();
check.checkContains("FUNC GLOBAL DEFAULT testFn");
if (opts.optimize == .Debug) {
check.checkInSymtab();
check.checkContains("FUNC LOCAL DEFAULT main.TestGenericFn(");
}
test_step.dependOn(&check.step);
return test_step;
}
// Adapted from https://github.com/rui314/mold/blob/main/test/elf/mergeable-strings.sh
fn testMergeStrings(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "merge-strings", opts);
const obj1 = addObject(b, opts, .{ .name = "a.o" });
addCSourceBytes(obj1,
\\#include <uchar.h>
\\#include <wchar.h>
\\char *cstr1 = "foo";
\\wchar_t *wide1 = L"foo";
\\char16_t *utf16_1 = u"foo";
\\char32_t *utf32_1 = U"foo";
, &.{"-O2"});
obj1.root_module.link_libc = true;
const obj2 = addObject(b, opts, .{ .name = "b.o" });
addCSourceBytes(obj2,
\\#include <stdio.h>
\\#include <assert.h>
\\#include <uchar.h>
\\#include <wchar.h>
\\extern char *cstr1;
\\extern wchar_t *wide1;
\\extern char16_t *utf16_1;
\\extern char32_t *utf32_1;
\\char *cstr2 = "foo";
\\wchar_t *wide2 = L"foo";
\\char16_t *utf16_2 = u"foo";
\\char32_t *utf32_2 = U"foo";
\\int main() {
\\ printf("%p %p %p %p %p %p %p %p\n",
\\ cstr1, cstr2, wide1, wide2, utf16_1, utf16_2, utf32_1, utf32_2);
\\ assert((void*)cstr1 == (void*)cstr2);
\\ assert((void*)wide1 == (void*)wide2);
\\ assert((void*)utf16_1 == (void*)utf16_2);
\\ assert((void*)utf32_1 == (void*)utf32_2);
\\ assert((void*)wide1 == (void*)utf32_1);
\\ assert((void*)cstr1 != (void*)wide1);
\\ assert((void*)cstr1 != (void*)utf32_1);
\\ assert((void*)wide1 != (void*)utf16_1);
\\}
, &.{"-O2"});
obj2.root_module.link_libc = true;
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(obj1);
exe.root_module.addObject(obj2);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testMergeStrings2(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "merge-strings2", opts);
const obj1 = addObject(b, opts, .{ .name = "a", .zig_source_bytes =
\\const std = @import("std");
\\export fn foo() void {
\\ var arr: [5:0]u16 = [_:0]u16{ 1, 2, 3, 4, 5 };
\\ const slice = std.mem.sliceTo(&arr, 3);
\\ std.testing.expectEqualSlices(u16, arr[0..2], slice) catch unreachable;
\\}
});
const obj2 = addObject(b, opts, .{ .name = "b", .zig_source_bytes =
\\const std = @import("std");
\\extern fn foo() void;
\\pub fn main() void {
\\ foo();
\\ var arr: [5:0]u16 = [_:0]u16{ 5, 4, 3, 2, 1 };
\\ const slice = std.mem.sliceTo(&arr, 3);
\\ std.testing.expectEqualSlices(u16, arr[0..2], slice) catch unreachable;
\\}
});
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(obj1);
exe.root_module.addObject(obj2);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.dumpSection(".rodata.str");
check.checkContains("\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x00\x00");
check.dumpSection(".rodata.str");
check.checkContains("\x05\x00\x04\x00\x03\x00\x02\x00\x01\x00\x00\x00");
test_step.dependOn(&check.step);
}
{
const obj3 = addObject(b, opts, .{ .name = "c" });
obj3.root_module.addObject(obj1);
obj3.root_module.addObject(obj2);
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(obj3);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.dumpSection(".rodata.str");
check.checkContains("\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x00\x00");
check.dumpSection(".rodata.str");
check.checkContains("\x05\x00\x04\x00\x03\x00\x02\x00\x01\x00\x00\x00");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testNoEhFrameHdr(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "no-eh-frame-hdr", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe, "int main() { return 0; }", &.{});
exe.link_eh_frame_hdr = false;
exe.root_module.link_libc = true;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("section headers");
check.checkNotPresent("name .eh_frame_hdr");
test_step.dependOn(&check.step);
return test_step;
}
fn testPie(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "hello-pie", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\int main() {
\\ printf("Hello!\n");
\\ return 0;
\\}
, &.{});
exe.root_module.link_libc = true;
exe.root_module.pic = true;
exe.pie = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello!\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkExact("type DYN");
check.checkInHeaders();
check.checkExact("section headers");
check.checkExact("name .dynamic");
test_step.dependOn(&check.step);
return test_step;
}
fn testPltGot(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "plt-got", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\#include <stdio.h>
\\void ignore(void *foo) {}
\\void hello() {
\\ printf("Hello world\n");
\\}
, &.{});
dso.root_module.link_libc = true;
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\void ignore(void *);
\\int hello();
\\void foo() { ignore(hello); }
\\int main() { hello(); }
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.pic = true;
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testPreinitArray(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "preinit-array", opts);
{
const obj = addObject(b, opts, .{
.name = "obj",
.c_source_bytes = "void _start() {}",
});
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(obj);
const check = exe.checkObject();
check.checkInDynamicSection();
check.checkNotPresent("PREINIT_ARRAY");
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
addCSourceBytes(exe,
\\void preinit_fn() {}
\\int main() {}
\\__attribute__((section(".preinit_array")))
\\void *preinit[] = { preinit_fn };
, &.{});
exe.root_module.link_libc = true;
const check = exe.checkObject();
check.checkInDynamicSection();
check.checkContains("PREINIT_ARRAY");
}
return test_step;
}
fn testRelocatableArchive(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "relocatable-archive", opts);
const obj1 = addObject(b, opts, .{
.name = "obj1",
.c_source_bytes =
\\void bar();
\\void foo() {
\\ bar();
\\}
,
});
const obj2 = addObject(b, opts, .{
.name = "obj2",
.c_source_bytes =
\\void bar() {}
,
});
const obj3 = addObject(b, opts, .{
.name = "obj3",
.c_source_bytes =
\\void baz();
,
});
const obj4 = addObject(b, opts, .{
.name = "obj4",
.c_source_bytes =
\\void foo();
\\int main() {
\\ foo();
\\}
,
});
const lib = addStaticLibrary(b, opts, .{ .name = "lib" });
lib.root_module.addObject(obj1);
lib.root_module.addObject(obj2);
lib.root_module.addObject(obj3);
const obj5 = addObject(b, opts, .{
.name = "obj5",
});
obj5.root_module.addObject(obj4);
obj5.root_module.linkLibrary(lib);
const check = obj5.checkObject();
check.checkInSymtab();
check.checkContains("foo");
check.checkInSymtab();
check.checkContains("bar");
check.checkInSymtab();
check.checkNotPresent("baz");
test_step.dependOn(&check.step);
return test_step;
}
fn testRelocatableEhFrame(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "relocatable-eh-frame", opts);
const obj1 = addObject(b, opts, .{
.name = "obj1",
.cpp_source_bytes =
\\#include <stdexcept>
\\int try_me() {
\\ throw std::runtime_error("Oh no!");
\\}
,
});
obj1.root_module.link_libcpp = true;
const obj2 = addObject(b, opts, .{
.name = "obj2",
.cpp_source_bytes =
\\extern int try_me();
\\int try_again() {
\\ return try_me();
\\}
,
});
obj2.root_module.link_libcpp = true;
const obj3 = addObject(b, opts, .{ .name = "obj3", .cpp_source_bytes =
\\#include <iostream>
\\#include <stdexcept>
\\extern int try_again();
\\int main() {
\\ try {
\\ try_again();
\\ } catch (const std::exception &e) {
\\ std::cout << "exception=" << e.what();
\\ }
\\ return 0;
\\}
});
obj3.root_module.link_libcpp = true;
{
const obj = addObject(b, opts, .{ .name = "obj" });
obj.root_module.addObject(obj1);
obj.root_module.addObject(obj2);
obj.root_module.link_libcpp = true;
const exe = addExecutable(b, opts, .{ .name = "test1" });
exe.root_module.addObject(obj3);
exe.root_module.addObject(obj);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("exception=Oh no!");
test_step.dependOn(&run.step);
}
{
// Flipping the order should not influence the end result.
const obj = addObject(b, opts, .{ .name = "obj" });
obj.root_module.addObject(obj2);
obj.root_module.addObject(obj1);
obj.root_module.link_libcpp = true;
const exe = addExecutable(b, opts, .{ .name = "test2" });
exe.root_module.addObject(obj3);
exe.root_module.addObject(obj);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("exception=Oh no!");
test_step.dependOn(&run.step);
}
return test_step;
}
fn testRelocatableEhFrameComdatHeavy(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "relocatable-eh-frame-comdat-heavy", opts);
const obj1 = addObject(b, opts, .{
.name = "obj1",
.cpp_source_bytes =
\\#include <stdexcept>
\\int try_me() {
\\ throw std::runtime_error("Oh no!");
\\}
,
});
obj1.root_module.link_libcpp = true;
const obj2 = addObject(b, opts, .{
.name = "obj2",
.cpp_source_bytes =
\\extern int try_me();
\\int try_again() {
\\ return try_me();
\\}
,
});
obj2.root_module.link_libcpp = true;
const obj3 = addObject(b, opts, .{
.name = "obj3",
.cpp_source_bytes =
\\#include <iostream>
\\#include <stdexcept>
\\extern int try_again();
\\int main() {
\\ try {
\\ try_again();
\\ } catch (const std::exception &e) {
\\ std::cout << "exception=" << e.what();
\\ }
\\ return 0;
\\}
,
});
obj3.root_module.link_libcpp = true;
const obj = addObject(b, opts, .{ .name = "obj" });
obj.root_module.addObject(obj1);
obj.root_module.addObject(obj2);
obj.root_module.addObject(obj3);
obj.root_module.link_libcpp = true;
const exe = addExecutable(b, opts, .{ .name = "test2" });
exe.root_module.addObject(obj);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("exception=Oh no!");
test_step.dependOn(&run.step);
return test_step;
}
// Adapted from https://github.com/rui314/mold/blob/main/test/elf/relocatable-mergeable-sections.sh
fn testRelocatableMergeStrings(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "relocatable-merge-strings", opts);
const obj1 = addObject(b, opts, .{ .name = "a", .asm_source_bytes =
\\.section .rodata.str1.1,"aMS",@progbits,1
\\val1:
\\.ascii "Hello \0"
\\.section .rodata.str1.1,"aMS",@progbits,1
\\val5:
\\.ascii "World \0"
\\.section .rodata.str1.1,"aMS",@progbits,1
\\val7:
\\.ascii "Hello \0"
});
const obj2 = addObject(b, opts, .{ .name = "b" });
obj2.root_module.addObject(obj1);
const check = obj2.checkObject();
check.dumpSection(".rodata.str1.1");
check.checkExact("Hello \x00World \x00");
test_step.dependOn(&check.step);
return test_step;
}
fn testRelocatableNoEhFrame(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "relocatable-no-eh-frame", opts);
const obj1 = addObject(b, opts, .{
.name = "obj1",
.c_source_bytes = "int bar() { return 42; }",
.c_source_flags = &.{
"-fno-unwind-tables",
"-fno-asynchronous-unwind-tables",
},
});
const obj2 = addObject(b, opts, .{
.name = "obj2",
});
obj2.root_module.addObject(obj1);
const check1 = obj1.checkObject();
check1.checkInHeaders();
check1.checkExact("section headers");
check1.checkNotPresent(".eh_frame");
test_step.dependOn(&check1.step);
const check2 = obj2.checkObject();
check2.checkInHeaders();
check2.checkExact("section headers");
check2.checkNotPresent(".eh_frame");
test_step.dependOn(&check2.step);
return test_step;
}
fn testSharedAbsSymbol(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "shared-abs-symbol", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addAsmSourceBytes(dso,
\\.globl foo
\\foo = 3;
);
const obj = addObject(b, opts, .{
.name = "obj",
.c_source_bytes =
\\#include <stdio.h>
\\extern char foo;
\\int main() { printf("foo=%p\n", &foo); }
,
.pic = true,
});
obj.root_module.link_libc = true;
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(obj);
exe.root_module.linkLibrary(dso);
exe.pie = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("foo=0x3\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkExact("type DYN");
// TODO fix/improve in CheckObject
// check.checkInSymtab();
// check.checkNotPresent("foo");
test_step.dependOn(&check.step);
}
// https://github.com/ziglang/zig/issues/17430
// {
// const exe = addExecutable(b, opts, .{ .name = "main2"});
// exe.root_module.addObject(obj);
// exe.root_module.linkLibrary(dso);
// exe.pie = false;
// const run = addRunArtifact(exe);
// run.expectStdOutEqual("foo=0x3\n");
// test_step.dependOn(&run.step);
// const check = exe.checkObject();
// check.checkInHeaders();
// check.checkExact("header");
// check.checkExact("type EXEC");
// // TODO fix/improve in CheckObject
// // check.checkInSymtab();
// // check.checkNotPresent("foo");
// test_step.dependOn(&check.step);
// }
return test_step;
}
fn testStrip(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "strip", opts);
const obj = addObject(b, opts, .{
.name = "obj",
.c_source_bytes =
\\#include <stdio.h>
\\int main() {
\\ printf("Hello!\n");
\\ return 0;
\\}
,
});
obj.root_module.link_libc = true;
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(obj);
exe.root_module.strip = false;
exe.root_module.link_libc = true;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("section headers");
check.checkExact("name .debug_info");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(obj);
exe.root_module.strip = true;
exe.root_module.link_libc = true;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("section headers");
check.checkNotPresent("name .debug_info");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testThunks(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "thunks", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\void foo();
\\__attribute__((section(".bar"))) void bar() {
\\ return foo();
\\}
\\__attribute__((section(".foo"))) void foo() {
\\ return bar();
\\}
\\int main() {
\\ foo();
\\ bar();
\\ return 0;
\\}
});
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("foo$thunk");
check.checkInSymtab();
check.checkContains("bar$thunk");
test_step.dependOn(&check.step);
return test_step;
}
fn testTlsDfStaticTls(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-df-static-tls", opts);
const obj = addObject(b, opts, .{
.name = "obj",
.c_source_bytes =
\\static _Thread_local int foo = 5;
\\void mutate() { ++foo; }
\\int bar() { return foo; }
,
.c_source_flags = &.{"-ftls-model=initial-exec"},
.pic = true,
});
{
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
dso.root_module.addObject(obj);
// dso.link_relax = true;
const check = dso.checkObject();
check.checkInDynamicSection();
check.checkContains("STATIC_TLS");
test_step.dependOn(&check.step);
}
// TODO add -Wl,--no-relax
// {
// const dso = addSharedLibrary(b, opts, .{ .name = "a"});
// dso.root_module.addObject(obj);
// dso.link_relax = false;
// const check = dso.checkObject();
// check.checkInDynamicSection();
// check.checkContains("STATIC_TLS");
// test_step.dependOn(&check.step);
// }
return test_step;
}
fn testTlsDso(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-dso", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\extern _Thread_local int foo;
\\_Thread_local int bar;
\\int get_foo1() { return foo; }
\\int get_bar1() { return bar; }
, &.{});
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\_Thread_local int foo;
\\extern _Thread_local int bar;
\\int get_foo1();
\\int get_bar1();
\\int get_foo2() { return foo; }
\\int get_bar2() { return bar; }
\\int main() {
\\ foo = 5;
\\ bar = 3;
\\ printf("%d %d %d %d %d %d\n",
\\ foo, bar,
\\ get_foo1(), get_bar1(),
\\ get_foo2(), get_bar2());
\\ return 0;
\\}
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("5 3 5 3 5 3\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testTlsGd(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-gd", opts);
const main_o = addObject(b, opts, .{
.name = "main",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int x1 = 1;
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int x2;
\\__attribute__((tls_model("global-dynamic"))) extern _Thread_local int x3;
\\__attribute__((tls_model("global-dynamic"))) extern _Thread_local int x4;
\\int get_x5();
\\int get_x6();
\\int main() {
\\ x2 = 2;
\\ printf("%d %d %d %d %d %d\n", x1, x2, x3, x4, get_x5(), get_x6());
\\ return 0;
\\}
,
.pic = true,
});
main_o.root_module.link_libc = true;
const a_o = addObject(b, opts, .{
.name = "a",
.c_source_bytes =
\\__attribute__((tls_model("global-dynamic"))) _Thread_local int x3 = 3;
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int x5 = 5;
\\int get_x5() { return x5; }
,
.pic = true,
});
const b_o = addObject(b, opts, .{
.name = "b",
.c_source_bytes =
\\__attribute__((tls_model("global-dynamic"))) _Thread_local int x4 = 4;
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int x6 = 6;
\\int get_x6() { return x6; }
,
.pic = true,
});
const exp_stdout = "1 2 3 4 5 6\n";
const dso1 = addSharedLibrary(b, opts, .{ .name = "a" });
dso1.root_module.addObject(a_o);
const dso2 = addSharedLibrary(b, opts, .{ .name = "b" });
dso2.root_module.addObject(b_o);
// dso2.link_relax = false; // TODO
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(main_o);
exe.root_module.linkLibrary(dso1);
exe.root_module.linkLibrary(dso2);
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(main_o);
// exe.link_relax = false; // TODO
exe.root_module.linkLibrary(dso1);
exe.root_module.linkLibrary(dso2);
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
}
// https://github.com/ziglang/zig/issues/17430 ??
// {
// const exe = addExecutable(b, opts, .{ .name = "main3"});
// exe.root_module.addObject(main_o);
// exe.root_module.linkLibrary(dso1);
// exe.root_module.linkLibrary(dso2);
// exe.linkage = .static;
// const run = addRunArtifact(exe);
// run.expectStdOutEqual(exp_stdout);
// test_step.dependOn(&run.step);
// }
// {
// const exe = addExecutable(b, opts, .{ .name = "main4"});
// exe.root_module.addObject(main_o);
// // exe.link_relax = false; // TODO
// exe.root_module.linkLibrary(dso1);
// exe.root_module.linkLibrary(dso2);
// exe.linkage = .static;
// const run = addRunArtifact(exe);
// run.expectStdOutEqual(exp_stdout);
// test_step.dependOn(&run.step);
// }
return test_step;
}
fn testTlsGdNoPlt(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-gd-no-plt", opts);
const obj = addObject(b, opts, .{
.name = "obj",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int x1 = 1;
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int x2;
\\__attribute__((tls_model("global-dynamic"))) extern _Thread_local int x3;
\\__attribute__((tls_model("global-dynamic"))) extern _Thread_local int x4;
\\int get_x5();
\\int get_x6();
\\int main() {
\\ x2 = 2;
\\
\\ printf("%d %d %d %d %d %d\n", x1, x2, x3, x4, get_x5(), get_x6());
\\ return 0;
\\}
,
.c_source_flags = &.{"-fno-plt"},
.pic = true,
});
obj.root_module.link_libc = true;
const a_so = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(a_so,
\\__attribute__((tls_model("global-dynamic"))) _Thread_local int x3 = 3;
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int x5 = 5;
\\int get_x5() { return x5; }
, &.{"-fno-plt"});
const b_so = addSharedLibrary(b, opts, .{ .name = "b" });
addCSourceBytes(b_so,
\\__attribute__((tls_model("global-dynamic"))) _Thread_local int x4 = 4;
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int x6 = 6;
\\int get_x6() { return x6; }
, &.{"-fno-plt"});
// b_so.link_relax = false; // TODO
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(obj);
exe.root_module.linkLibrary(a_so);
exe.root_module.linkLibrary(b_so);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2 3 4 5 6\n");
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(obj);
exe.root_module.linkLibrary(a_so);
exe.root_module.linkLibrary(b_so);
exe.root_module.link_libc = true;
// exe.link_relax = false; // TODO
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2 3 4 5 6\n");
test_step.dependOn(&run.step);
}
return test_step;
}
fn testTlsGdToIe(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-gd-to-ie", opts);
const a_o = addObject(b, opts, .{
.name = "a",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int x1 = 1;
\\__attribute__((tls_model("global-dynamic"))) _Thread_local int x2 = 2;
\\__attribute__((tls_model("global-dynamic"))) _Thread_local int x3;
\\int foo() {
\\ x3 = 3;
\\
\\ printf("%d %d %d\n", x1, x2, x3);
\\ return 0;
\\}
,
.pic = true,
});
a_o.root_module.link_libc = true;
const b_o = addObject(b, opts, .{
.name = "b",
.c_source_bytes =
\\int foo();
\\int main() { foo(); }
,
.pic = true,
});
{
const dso = addSharedLibrary(b, opts, .{ .name = "a1" });
dso.root_module.addObject(a_o);
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(b_o);
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2 3\n");
test_step.dependOn(&run.step);
}
{
const dso = addSharedLibrary(b, opts, .{ .name = "a2" });
dso.root_module.addObject(a_o);
// dso.link_relax = false; // TODO
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(b_o);
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2 3\n");
test_step.dependOn(&run.step);
}
// {
// const dso = addSharedLibrary(b, opts, .{ .name = "a"});
// dso.root_module.addObject(a_o);
// dso.link_z_nodlopen = true;
// const exe = addExecutable(b, opts, .{ .name = "main"});
// exe.root_module.addObject(b_o);
// exe.root_module.linkLibrary(dso);
// const run = addRunArtifact(exe);
// run.expectStdOutEqual("1 2 3\n");
// test_step.dependOn(&run.step);
// }
// {
// const dso = addSharedLibrary(b, opts, .{ .name = "a"});
// dso.root_module.addObject(a_o);
// dso.link_relax = false;
// dso.link_z_nodlopen = true;
// const exe = addExecutable(b, opts, .{ .name = "main"});
// exe.root_module.addObject(b_o);
// exe.root_module.linkLibrary(dso);
// const run = addRunArtifact(exe);
// run.expectStdOutEqual("1 2 3\n");
// test_step.dependOn(&run.step);
// }
return test_step;
}
fn testTlsIe(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-ie", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\#include <stdio.h>
\\__attribute__((tls_model("initial-exec"))) static _Thread_local int foo;
\\__attribute__((tls_model("initial-exec"))) static _Thread_local int bar;
\\void set() {
\\ foo = 3;
\\ bar = 5;
\\}
\\void print() {
\\ printf("%d %d ", foo, bar);
\\}
, &.{});
dso.root_module.link_libc = true;
const main_o = addObject(b, opts, .{
.name = "main",
.c_source_bytes =
\\#include <stdio.h>
\\_Thread_local int baz;
\\void set();
\\void print();
\\int main() {
\\ baz = 7;
\\ print();
\\ set();
\\ print();
\\ printf("%d\n", baz);
\\}
,
});
main_o.root_module.link_libc = true;
const exp_stdout = "0 0 3 5 7\n";
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(main_o);
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(main_o);
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
// exe.link_relax = false; // TODO
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
}
return test_step;
}
fn testTlsLargeAlignment(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-large-alignment", opts);
const a_o = addObject(b, opts, .{
.name = "a",
.c_source_bytes =
\\__attribute__((section(".tdata1")))
\\_Thread_local int x = 42;
,
.c_source_flags = &.{"-std=c11"},
.pic = true,
});
const b_o = addObject(b, opts, .{
.name = "b",
.c_source_bytes =
\\__attribute__((section(".tdata2")))
\\_Alignas(256) _Thread_local int y[] = { 1, 2, 3 };
,
.c_source_flags = &.{"-std=c11"},
.pic = true,
});
const c_o = addObject(b, opts, .{
.name = "c",
.c_source_bytes =
\\#include <stdio.h>
\\extern _Thread_local int x;
\\extern _Thread_local int y[];
\\int main() {
\\ printf("%d %d %d %d\n", x, y[0], y[1], y[2]);
\\}
,
.pic = true,
});
c_o.root_module.link_libc = true;
{
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
dso.root_module.addObject(a_o);
dso.root_module.addObject(b_o);
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(c_o);
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("42 1 2 3\n");
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.root_module.addObject(c_o);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("42 1 2 3\n");
test_step.dependOn(&run.step);
}
return test_step;
}
fn testTlsLargeTbss(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-large-tbss", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addAsmSourceBytes(exe,
\\.globl x, y
\\.section .tbss,"awT",@nobits
\\x:
\\.zero 1024
\\.section .tcommon,"awT",@nobits
\\y:
\\.zero 1024
);
addCSourceBytes(exe,
\\#include <stdio.h>
\\extern _Thread_local char x[1024000];
\\extern _Thread_local char y[1024000];
\\int main() {
\\ x[0] = 3;
\\ x[1023] = 5;
\\ printf("%d %d %d %d %d %d\n", x[0], x[1], x[1023], y[0], y[1], y[1023]);
\\}
, &.{});
exe.root_module.link_libc = true;
// Disabled to work around the ELF linker crashing.
// Can be reproduced on a x86_64-linux host by commenting out the line below.
exe.root_module.sanitize_c = .off;
const run = addRunArtifact(exe);
run.expectStdOutEqual("3 0 5 0 0 0\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testTlsLargeStaticImage(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-large-static-image", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe, "_Thread_local int x[] = { 1, 2, 3, [10000] = 5 };", &.{});
addCSourceBytes(exe,
\\#include <stdio.h>
\\extern _Thread_local int x[];
\\int main() {
\\ printf("%d %d %d %d %d\n", x[0], x[1], x[2], x[3], x[10000]);
\\}
, &.{});
exe.root_module.pic = true;
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2 3 0 5\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testTlsLd(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-ld", opts);
const main_o = addObject(b, opts, .{
.name = "main",
.c_source_bytes =
\\#include <stdio.h>
\\extern _Thread_local int foo;
\\static _Thread_local int bar;
\\int *get_foo_addr() { return &foo; }
\\int *get_bar_addr() { return &bar; }
\\int main() {
\\ bar = 5;
\\ printf("%d %d %d %d\n", *get_foo_addr(), *get_bar_addr(), foo, bar);
\\ return 0;
\\}
,
.c_source_flags = &.{"-ftls-model=local-dynamic"},
.pic = true,
});
main_o.root_module.link_libc = true;
const a_o = addObject(b, opts, .{
.name = "a",
.c_source_bytes = "_Thread_local int foo = 3;",
.c_source_flags = &.{"-ftls-model=local-dynamic"},
.pic = true,
});
const exp_stdout = "3 5 3 5\n";
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(main_o);
exe.root_module.addObject(a_o);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(main_o);
exe.root_module.addObject(a_o);
exe.root_module.link_libc = true;
// exe.link_relax = false; // TODO
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
}
return test_step;
}
fn testTlsLdDso(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-ld-dso", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\static _Thread_local int def, def1;
\\int f0() { return ++def; }
\\int f1() { return ++def1 + def; }
, &.{"-ftls-model=local-dynamic"});
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\extern int f0();
\\extern int f1();
\\int main() {
\\ int x = f0();
\\ int y = f1();
\\ printf("%d %d\n", x, y);
\\ return 0;
\\}
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testTlsLdNoPlt(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-ld-no-plt", opts);
const a_o = addObject(b, opts, .{
.name = "a",
.c_source_bytes =
\\#include <stdio.h>
\\extern _Thread_local int foo;
\\static _Thread_local int bar;
\\int *get_foo_addr() { return &foo; }
\\int *get_bar_addr() { return &bar; }
\\int main() {
\\ bar = 5;
\\
\\ printf("%d %d %d %d\n", *get_foo_addr(), *get_bar_addr(), foo, bar);
\\ return 0;
\\}
,
.c_source_flags = &.{ "-ftls-model=local-dynamic", "-fno-plt" },
.pic = true,
});
a_o.root_module.link_libc = true;
const b_o = addObject(b, opts, .{
.name = "b",
.c_source_bytes = "_Thread_local int foo = 3;",
.c_source_flags = &.{ "-ftls-model=local-dynamic", "-fno-plt" },
.pic = true,
});
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("3 5 3 5\n");
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.root_module.link_libc = true;
// exe.link_relax = false; // TODO
const run = addRunArtifact(exe);
run.expectStdOutEqual("3 5 3 5\n");
test_step.dependOn(&run.step);
}
return test_step;
}
fn testTlsNoPic(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-no-pic", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\__attribute__((tls_model("global-dynamic"))) extern _Thread_local int foo;
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int bar;
\\int *get_foo_addr() { return &foo; }
\\int *get_bar_addr() { return &bar; }
\\int main() {
\\ foo = 3;
\\ bar = 5;
\\
\\ printf("%d %d %d %d\n", *get_foo_addr(), *get_bar_addr(), foo, bar);
\\ return 0;
\\}
, .{});
addCSourceBytes(exe,
\\__attribute__((tls_model("global-dynamic"))) _Thread_local int foo;
, &.{});
exe.root_module.pic = false;
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("3 5 3 5\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testTlsOffsetAlignment(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-offset-alignment", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\#include <assert.h>
\\#include <stdlib.h>
\\
\\// .tdata
\\_Thread_local int x = 42;
\\// .tbss
\\__attribute__ ((aligned(64)))
\\_Thread_local int y = 0;
\\
\\void *verify(void *unused) {
\\ assert((unsigned long)(&y) % 64 == 0);
\\ return NULL;
\\}
, &.{});
dso.root_module.link_libc = true;
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <pthread.h>
\\#include <dlfcn.h>
\\#include <assert.h>
\\#include <stdio.h>
\\void *(*verify)(void *);
\\
\\int main() {
\\ void *handle = dlopen("liba.so", RTLD_NOW);
\\ if (!handle) {
\\ fprintf(stderr, "dlopen failed: %s\n", dlerror());
\\ return 1;
\\ }
\\ *(void**)(&verify) = dlsym(handle, "verify");
\\ assert(verify);
\\
\\ pthread_t thread;
\\
\\ verify(NULL);
\\
\\ pthread_create(&thread, NULL, verify, NULL);
\\ pthread_join(thread, NULL);
\\}
, &.{});
exe.root_module.addRPath(dso.getEmittedBinDirectory());
exe.root_module.link_libc = true;
exe.root_module.pic = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testTlsPic(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-pic", opts);
const obj = addObject(b, opts, .{
.name = "obj",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((tls_model("global-dynamic"))) extern _Thread_local int foo;
\\__attribute__((tls_model("global-dynamic"))) static _Thread_local int bar;
\\int *get_foo_addr() { return &foo; }
\\int *get_bar_addr() { return &bar; }
\\int main() {
\\ bar = 5;
\\
\\ printf("%d %d %d %d\n", *get_foo_addr(), *get_bar_addr(), foo, bar);
\\ return 0;
\\}
,
.pic = true,
});
obj.root_module.link_libc = true;
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\__attribute__((tls_model("global-dynamic"))) _Thread_local int foo = 3;
, &.{});
exe.root_module.addObject(obj);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("3 5 3 5\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testTlsSmallAlignment(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-small-alignment", opts);
const a_o = addObject(b, opts, .{
.name = "a",
.asm_source_bytes =
\\.text
\\.byte 0
\\
,
.pic = true,
});
const b_o = addObject(b, opts, .{
.name = "b",
.c_source_bytes = "_Thread_local char x = 42;",
.c_source_flags = &.{"-std=c11"},
.pic = true,
});
const c_o = addObject(b, opts, .{
.name = "c",
.c_source_bytes =
\\#include <stdio.h>
\\extern _Thread_local char x;
\\int main() {
\\ printf("%d\n", x);
\\}
,
.pic = true,
});
c_o.root_module.link_libc = true;
{
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.root_module.addObject(c_o);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("42\n");
test_step.dependOn(&run.step);
}
{
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
dso.root_module.addObject(a_o);
dso.root_module.addObject(b_o);
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(c_o);
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("42\n");
test_step.dependOn(&run.step);
}
return test_step;
}
fn testTlsStatic(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-static", opts);
const exe = addExecutable(b, opts, .{ .name = "test" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\_Thread_local int a = 10;
\\_Thread_local int b;
\\_Thread_local char c = 'a';
\\int main(int argc, char* argv[]) {
\\ printf("%d %d %c\n", a, b, c);
\\ a += 1;
\\ b += 1;
\\ c += 1;
\\ printf("%d %d %c\n", a, b, c);
\\ return 0;
\\}
, &.{});
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual(
\\10 0 a
\\11 1 b
\\
);
test_step.dependOn(&run.step);
return test_step;
}
fn testUnknownFileTypeError(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "unknown-file-type-error", opts);
const dylib = addSharedLibrary(b, .{
.target = b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .macos }),
}, .{
.name = "a",
.zig_source_bytes = "export var foo: i32 = 0;",
});
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\extern int foo;
\\int main() {
\\ return foo;
\\}
, &.{});
exe.root_module.linkLibrary(dylib);
exe.root_module.link_libc = true;
expectLinkErrors(exe, test_step, .{
.contains = "error: failed to parse shared library: BadMagic",
});
return test_step;
}
fn testUnresolvedError(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "unresolved-error", opts);
const obj1 = addObject(b, opts, .{
.name = "a",
.c_source_bytes =
\\#include <stdio.h>
\\int foo();
\\int bar() {
\\ return foo() + 1;
\\}
,
.c_source_flags = &.{"-ffunction-sections"},
});
obj1.root_module.link_libc = true;
const obj2 = addObject(b, opts, .{
.name = "b",
.c_source_bytes =
\\#include <stdio.h>
\\int foo();
\\int bar();
\\int main() {
\\ return foo() + bar();
\\}
,
.c_source_flags = &.{"-ffunction-sections"},
});
obj2.root_module.link_libc = true;
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(obj1);
exe.root_module.addObject(obj2);
exe.root_module.link_libc = true;
expectLinkErrors(exe, test_step, .{ .exact = &.{
"error: undefined symbol: foo",
"note: referenced by /?/a.o:.text.bar",
"note: referenced by /?/b.o:.text.main",
} });
return test_step;
}
fn testWeakExports(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "weak-exports", opts);
const obj = addObject(b, opts, .{
.name = "obj",
.c_source_bytes =
\\#include <stdio.h>
\\__attribute__((weak)) int foo();
\\int main() {
\\ printf("%d\n", foo ? foo() : 3);
\\}
,
.pic = true,
});
obj.root_module.link_libc = true;
{
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
dso.root_module.addObject(obj);
dso.root_module.link_libc = true;
const check = dso.checkObject();
check.checkInDynamicSymtab();
check.checkContains("UND NOTYPE WEAK DEFAULT foo");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(obj);
exe.root_module.link_libc = true;
const check = exe.checkObject();
check.checkInDynamicSymtab();
check.checkNotPresent("UND NOTYPE WEAK DEFAULT foo");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectStdOutEqual("3\n");
test_step.dependOn(&run.step);
}
return test_step;
}
fn testWeakUndefsDso(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "weak-undef-dso", opts);
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dso,
\\__attribute__((weak)) int foo();
\\int bar() { return foo ? foo() : -1; }
, &.{});
{
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\int bar();
\\int main() { printf("bar=%d\n", bar()); }
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("bar=-1\n");
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\int foo() { return 5; }
\\int bar();
\\int main() { printf("bar=%d\n", bar()); }
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("bar=5\n");
test_step.dependOn(&run.step);
}
return test_step;
}
fn testZNow(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "z-now", opts);
const obj = addObject(b, opts, .{
.name = "obj",
.c_source_bytes = "int main() { return 0; }",
.pic = true,
});
{
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
dso.root_module.addObject(obj);
const check = dso.checkObject();
check.checkInDynamicSection();
check.checkContains("NOW");
test_step.dependOn(&check.step);
}
{
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
dso.root_module.addObject(obj);
dso.link_z_lazy = true;
const check = dso.checkObject();
check.checkInDynamicSection();
check.checkNotPresent("NOW");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testZStackSize(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "z-stack-size", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe, "int main() { return 0; }", &.{});
exe.stack_size = 0x800000;
exe.root_module.link_libc = true;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("program headers");
check.checkExact("type GNU_STACK");
check.checkExact("memsz 800000");
test_step.dependOn(&check.step);
return test_step;
}
fn testZText(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "z-text", opts);
// Previously, following mold, this test tested text relocs present in a PIE executable.
// However, as we want to cover musl AND glibc, it is now modified to test presence of
// text relocs in a DSO which is then linked with an executable.
// According to Rich and this thread https://www.openwall.com/lists/musl/2020/09/25/4
// musl supports only a very limited number of text relocations and only in DSOs (and
// rightly so!).
const a_o = addObject(b, opts, .{
.name = "a",
.asm_source_bytes =
\\.globl fn1
\\fn1:
\\ sub $8, %rsp
\\ movabs ptr, %rax
\\ call *%rax
\\ add $8, %rsp
\\ ret
\\
,
});
const b_o = addObject(b, opts, .{
.name = "b",
.c_source_bytes =
\\int fn1();
\\int fn2() {
\\ return 3;
\\}
\\void *ptr = fn2;
\\int fnn() {
\\ return fn1();
\\}
,
.pic = true,
});
const dso = addSharedLibrary(b, opts, .{ .name = "a" });
dso.root_module.addObject(a_o);
dso.root_module.addObject(b_o);
dso.link_z_notext = true;
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\#include <stdio.h>
\\int fnn();
\\int main() {
\\ printf("%d\n", fnn());
\\}
, &.{});
exe.root_module.linkLibrary(dso);
exe.root_module.link_libc = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual("3\n");
test_step.dependOn(&run.step);
// Check for DT_TEXTREL in a DSO
const check = dso.checkObject();
check.checkInDynamicSection();
// check.checkExact("TEXTREL 0"); // TODO fix in CheckObject parser
check.checkContains("FLAGS TEXTREL");
test_step.dependOn(&check.step);
return test_step;
}
fn addTestStep(b: *Build, comptime prefix: []const u8, opts: Options) *Step {
return link.addTestStep(b, "elf-" ++ prefix, opts);
}
const addAsmSourceBytes = link.addAsmSourceBytes;
const addCSourceBytes = link.addCSourceBytes;
const addCppSourceBytes = link.addCppSourceBytes;
const addExecutable = link.addExecutable;
const addObject = link.addObject;
const addRunArtifact = link.addRunArtifact;
const addSharedLibrary = link.addSharedLibrary;
const addStaticLibrary = link.addStaticLibrary;
const expectLinkErrors = link.expectLinkErrors;
const link = @import("link.zig");
const std = @import("std");
const builtin = @import("builtin");
const Build = std.Build;
const BuildOptions = link.BuildOptions;
const Options = link.Options;
const Step = Build.Step;
const WriteFile = Step.WriteFile;
@@ -1,4 +0,0 @@
#include "a.h"
int32_t add(int32_t a, int32_t b) {
return a + b;
}
@@ -1,2 +0,0 @@
#include <stdint.h>
int32_t add(int32_t a, int32_t b);
@@ -1,6 +0,0 @@
#include "a.h"
#include "b.h"
int32_t sub(int32_t a, int32_t b) {
return add(a, -1 * b);
}
@@ -1,2 +0,0 @@
#include <stdint.h>
int32_t sub(int32_t a, int32_t b);
@@ -1,50 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const lib_a = b.addLibrary(.{
.linkage = .static,
.name = "a",
.root_module = b.createModule(.{
.root_source_file = null,
.optimize = optimize,
.target = b.graph.host,
}),
});
lib_a.root_module.addCSourceFile(.{ .file = b.path("a.c"), .flags = &[_][]const u8{} });
lib_a.root_module.addIncludePath(b.path("."));
const lib_b = b.addLibrary(.{
.linkage = .static,
.name = "b",
.root_module = b.createModule(.{
.root_source_file = null,
.optimize = optimize,
.target = b.graph.host,
}),
});
lib_b.root_module.addCSourceFile(.{ .file = b.path("b.c"), .flags = &[_][]const u8{} });
lib_b.root_module.addIncludePath(b.path("."));
const test_exe = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.target = b.graph.host,
.optimize = optimize,
}),
});
test_exe.root_module.linkLibrary(lib_a);
test_exe.root_module.linkLibrary(lib_b);
test_exe.root_module.addIncludePath(b.path("."));
test_step.dependOn(&b.addRunArtifact(test_exe).step);
}
@@ -1,9 +0,0 @@
const std = @import("std");
const expect = std.testing.expect;
extern fn sub(a: i32, b: i32) i32;
test "import C sub" {
const result = sub(2, 1);
try expect(result == 1);
}
-171
View File
@@ -1,171 +0,0 @@
pub const BuildOptions = struct {
has_macos_sdk: bool,
has_ios_sdk: bool,
has_symlinks: bool,
};
pub const Options = struct {
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode = .Debug,
use_llvm: bool = true,
use_lld: bool = false,
strip: ?bool = null,
};
pub fn addTestStep(b: *Build, prefix: []const u8, opts: Options) *Step {
const target = opts.target.query.zigTriple(b.allocator) catch @panic("OOM");
const optimize = @tagName(opts.optimize);
const use_llvm = if (opts.use_llvm) "llvm" else "no-llvm";
const use_lld = if (opts.use_lld) "lld" else "no-lld";
if (opts.strip) |strip| {
const s = if (strip) "strip" else "no-strip";
const name = std.fmt.allocPrint(b.allocator, "test-{s}-{s}-{s}-{s}-{s}-{s}", .{
prefix, target, optimize, use_llvm, use_lld, s,
}) catch @panic("OOM");
return b.step(name, "");
}
const name = std.fmt.allocPrint(b.allocator, "test-{s}-{s}-{s}-{s}-{s}", .{
prefix, target, optimize, use_llvm, use_lld,
}) catch @panic("OOM");
return b.step(name, "");
}
const OverlayOptions = struct {
name: []const u8,
asm_source_bytes: ?[]const u8 = null,
c_source_bytes: ?[]const u8 = null,
c_source_flags: []const []const u8 = &.{},
cpp_source_bytes: ?[]const u8 = null,
cpp_source_flags: []const []const u8 = &.{},
objc_source_bytes: ?[]const u8 = null,
objc_source_flags: []const []const u8 = &.{},
objcpp_source_bytes: ?[]const u8 = null,
objcpp_source_flags: []const []const u8 = &.{},
zig_source_bytes: ?[]const u8 = null,
pic: ?bool = null,
strip: ?bool = null,
};
pub fn addExecutable(b: *std.Build, base: Options, overlay: OverlayOptions) *Compile {
return b.addExecutable(.{
.name = overlay.name,
.root_module = createModule(b, base, overlay),
.use_llvm = base.use_llvm,
.use_lld = base.use_lld,
});
}
pub fn addObject(b: *Build, base: Options, overlay: OverlayOptions) *Compile {
return b.addObject(.{
.name = overlay.name,
.root_module = createModule(b, base, overlay),
.use_llvm = base.use_llvm,
.use_lld = base.use_lld,
});
}
pub fn addStaticLibrary(b: *Build, base: Options, overlay: OverlayOptions) *Compile {
return b.addLibrary(.{
.linkage = .static,
.name = overlay.name,
.root_module = createModule(b, base, overlay),
.use_llvm = base.use_llvm,
.use_lld = base.use_lld,
});
}
pub fn addSharedLibrary(b: *Build, base: Options, overlay: OverlayOptions) *Compile {
return b.addLibrary(.{
.linkage = .dynamic,
.name = overlay.name,
.root_module = createModule(b, base, overlay),
.use_llvm = base.use_llvm,
.use_lld = base.use_lld,
});
}
fn createModule(b: *Build, base: Options, overlay: OverlayOptions) *Build.Module {
const write_files = b.addWriteFiles();
const mod = b.createModule(.{
.target = base.target,
.optimize = base.optimize,
.root_source_file = rsf: {
const bytes = overlay.zig_source_bytes orelse break :rsf null;
const name = b.fmt("{s}.zig", .{overlay.name});
break :rsf write_files.add(name, bytes);
},
.pic = overlay.pic,
.strip = if (base.strip) |s| s else overlay.strip,
});
if (overlay.objcpp_source_bytes) |bytes| {
mod.addCSourceFile(.{
.file = write_files.add("a.mm", bytes),
.flags = overlay.objcpp_source_flags,
});
}
if (overlay.objc_source_bytes) |bytes| {
mod.addCSourceFile(.{
.file = write_files.add("a.m", bytes),
.flags = overlay.objc_source_flags,
});
}
if (overlay.cpp_source_bytes) |bytes| {
mod.addCSourceFile(.{
.file = write_files.add("a.cpp", bytes),
.flags = overlay.cpp_source_flags,
});
}
if (overlay.c_source_bytes) |bytes| {
mod.addCSourceFile(.{
.file = write_files.add("a.c", bytes),
.flags = overlay.c_source_flags,
});
}
if (overlay.asm_source_bytes) |bytes| {
mod.addAssemblyFile(write_files.add("a.s", bytes));
}
return mod;
}
pub fn addRunArtifact(comp: *Compile) *Run {
const b = comp.step.owner;
const run = b.addRunArtifact(comp);
run.skip_foreign_checks = true;
return run;
}
pub fn addCSourceBytes(comp: *Compile, bytes: []const u8, flags: []const []const u8) void {
const b = comp.step.owner;
const file = WriteFile.create(b).add("a.c", bytes);
comp.root_module.addCSourceFile(.{ .file = file, .flags = flags });
}
pub fn addCppSourceBytes(comp: *Compile, bytes: []const u8, flags: []const []const u8) void {
const b = comp.step.owner;
const file = WriteFile.create(b).add("a.cpp", bytes);
comp.root_module.addCSourceFile(.{ .file = file, .flags = flags });
}
pub fn addAsmSourceBytes(comp: *Compile, bytes: []const u8) void {
const b = comp.step.owner;
const actual_bytes = std.fmt.allocPrint(b.allocator, "{s}\n", .{bytes}) catch @panic("OOM");
const file = WriteFile.create(b).add("a.s", actual_bytes);
comp.root_module.addAssemblyFile(file);
}
pub fn expectLinkErrors(comp: *Compile, test_step: *Step, expected_errors: Compile.ExpectedCompileErrors) void {
comp.expect_errors = expected_errors;
const bin_file = comp.getEmittedBin();
bin_file.addStepDependencies(test_step);
}
const std = @import("std");
const Build = std.Build;
const Compile = Step.Compile;
const Run = Step.Run;
const Step = Build.Step;
const WriteFile = Step.WriteFile;
-3291
View File
@@ -1,3291 +0,0 @@
//! Here we test our MachO linker for correctness and functionality.
pub fn testAll(b: *Build, build_opts: BuildOptions) *Step {
const macho_step = b.step("test-macho", "Run MachO tests");
// https://github.com/ziglang/zig/issues/25323
if (builtin.os.tag == .freebsd) return macho_step;
// https://github.com/ziglang/zig/issues/25961
if (comptime builtin.cpu.arch.endian() == .big) return macho_step;
const x86_64_target = b.resolveTargetQuery(.{
.cpu_arch = .x86_64,
.os_tag = .macos,
});
const aarch64_target = b.resolveTargetQuery(.{
.cpu_arch = .aarch64,
.os_tag = .macos,
});
const default_target = switch (builtin.cpu.arch) {
.x86_64, .aarch64 => b.resolveTargetQuery(.{
.os_tag = .macos,
}),
else => aarch64_target,
};
// Exercise linker with self-hosted backend (no LLVM)
macho_step.dependOn(testEmptyZig(b, .{ .use_llvm = false, .target = x86_64_target }));
macho_step.dependOn(testHelloZig(b, .{ .use_llvm = false, .target = x86_64_target }));
macho_step.dependOn(testLinkingStaticLib(b, .{ .use_llvm = false, .target = x86_64_target }));
macho_step.dependOn(testReexportsZig(b, .{ .use_llvm = false, .target = x86_64_target }));
macho_step.dependOn(testRelocatableZig(b, .{ .use_llvm = false, .target = x86_64_target }));
macho_step.dependOn(testTlsZig(b, .{ .use_llvm = false, .target = x86_64_target }));
macho_step.dependOn(testUnresolvedError(b, .{ .use_llvm = false, .target = x86_64_target }));
// Exercise linker with LLVM backend
macho_step.dependOn(testDeadStrip(b, .{ .target = default_target }));
macho_step.dependOn(testDuplicateDefinitions(b, .{ .target = default_target }));
macho_step.dependOn(testEmptyObject(b, .{ .target = default_target }));
macho_step.dependOn(testEmptyZig(b, .{ .target = default_target }));
macho_step.dependOn(testEntryPoint(b, .{ .target = default_target }));
macho_step.dependOn(testHeaderWeakFlags(b, .{ .target = default_target }));
macho_step.dependOn(testHelloC(b, .{ .target = default_target }));
macho_step.dependOn(testHelloZig(b, .{ .target = default_target }));
macho_step.dependOn(testLargeBss(b, .{ .target = default_target }));
macho_step.dependOn(testLayout(b, .{ .target = default_target }));
macho_step.dependOn(testLinkingStaticLib(b, .{ .target = default_target }));
macho_step.dependOn(testLinksection(b, .{ .target = default_target }));
macho_step.dependOn(testMergeLiteralsX64(b, .{ .target = x86_64_target }));
macho_step.dependOn(testMergeLiteralsArm64(b, .{ .target = aarch64_target }));
macho_step.dependOn(testMergeLiteralsArm642(b, .{ .target = aarch64_target }));
macho_step.dependOn(testMergeLiteralsAlignment(b, .{ .target = aarch64_target }));
macho_step.dependOn(testMhExecuteHeader(b, .{ .target = default_target }));
macho_step.dependOn(testNoDeadStrip(b, .{ .target = default_target }));
macho_step.dependOn(testNoExportsDylib(b, .{ .target = default_target }));
macho_step.dependOn(testPagezeroSize(b, .{ .target = default_target }));
macho_step.dependOn(testReexportsZig(b, .{ .target = default_target }));
macho_step.dependOn(testRelocatable(b, .{ .target = default_target }));
macho_step.dependOn(testRelocatableZig(b, .{ .target = default_target }));
macho_step.dependOn(testSectionBoundarySymbols(b, .{ .target = default_target }));
macho_step.dependOn(testSectionBoundarySymbols2(b, .{ .target = default_target }));
macho_step.dependOn(testSegmentBoundarySymbols(b, .{ .target = default_target }));
macho_step.dependOn(testSymbolStabs(b, .{ .target = default_target }));
macho_step.dependOn(testStackSize(b, .{ .target = default_target }));
macho_step.dependOn(testTentative(b, .{ .target = default_target }));
macho_step.dependOn(testThunks(b, .{ .target = aarch64_target }));
macho_step.dependOn(testTlsLargeTbss(b, .{ .target = default_target }));
macho_step.dependOn(testTlsZig(b, .{ .target = default_target }));
macho_step.dependOn(testUndefinedFlag(b, .{ .target = default_target }));
macho_step.dependOn(testUndefinedDynamicLookup(b, .{ .target = default_target }));
macho_step.dependOn(testDiscardLocalSymbols(b, .{ .target = default_target }));
macho_step.dependOn(testUnresolvedError(b, .{ .target = default_target }));
macho_step.dependOn(testUnresolvedError2(b, .{ .target = default_target }));
macho_step.dependOn(testUnwindInfo(b, .{ .target = default_target }));
macho_step.dependOn(testUnwindInfoNoSubsectionsX64(b, .{ .target = x86_64_target }));
macho_step.dependOn(testUnwindInfoNoSubsectionsArm64(b, .{ .target = aarch64_target }));
macho_step.dependOn(testEhFramePointerEncodingSdata4(b, .{ .target = aarch64_target }));
macho_step.dependOn(testWeakBind(b, .{ .target = x86_64_target }));
macho_step.dependOn(testWeakRef(b, .{ .target = b.resolveTargetQuery(.{
.cpu_arch = .x86_64,
.os_tag = .macos,
.os_version_min = .{ .semver = .{ .major = 10, .minor = 13, .patch = 0 } },
}) }));
// Tests requiring symlinks
if (build_opts.has_symlinks) {
macho_step.dependOn(testEntryPointArchive(b, .{ .target = default_target }));
macho_step.dependOn(testEntryPointDylib(b, .{ .target = default_target }));
macho_step.dependOn(testDylib(b, .{ .target = default_target }));
macho_step.dependOn(testDylibVersionTbd(b, .{ .target = default_target }));
macho_step.dependOn(testNeededLibrary(b, .{ .target = default_target }));
macho_step.dependOn(testSearchStrategy(b, .{ .target = default_target }));
macho_step.dependOn(testTbdv3(b, .{ .target = default_target }));
macho_step.dependOn(testTls(b, .{ .target = default_target }));
macho_step.dependOn(testTlsPointers(b, .{ .target = default_target }));
macho_step.dependOn(testTwoLevelNamespace(b, .{ .target = default_target }));
macho_step.dependOn(testWeakLibrary(b, .{ .target = default_target }));
// Tests requiring presence of macOS SDK in system path
if (build_opts.has_macos_sdk) {
macho_step.dependOn(testDeadStripDylibs(b, .{ .target = b.graph.host }));
macho_step.dependOn(testHeaderpad(b, .{ .target = b.graph.host }));
macho_step.dependOn(testLinkDirectlyCppTbd(b, .{ .target = b.graph.host }));
macho_step.dependOn(testMergeLiteralsObjc(b, .{ .target = b.graph.host }));
macho_step.dependOn(testNeededFramework(b, .{ .target = b.graph.host }));
macho_step.dependOn(testObjc(b, .{ .target = b.graph.host }));
macho_step.dependOn(testObjcpp(b, .{ .target = b.graph.host }));
macho_step.dependOn(testWeakFramework(b, .{ .target = b.graph.host }));
}
}
return macho_step;
}
fn testDeadStrip(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "dead-strip", opts);
const obj = addObject(b, opts, .{ .name = "a", .cpp_source_bytes =
\\#include <stdio.h>
\\int two() { return 2; }
\\int live_var1 = 1;
\\int live_var2 = two();
\\int dead_var1 = 3;
\\int dead_var2 = 4;
\\void live_fn1() {}
\\void live_fn2() { live_fn1(); }
\\void dead_fn1() {}
\\void dead_fn2() { dead_fn1(); }
\\int main() {
\\ printf("%d %d\n", live_var1, live_var2);
\\ live_fn2();
\\}
});
{
const exe = addExecutable(b, opts, .{ .name = "no_dead_strip" });
exe.root_module.addObject(obj);
exe.link_gc_sections = false;
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("live_var1");
check.checkInSymtab();
check.checkContains("live_var2");
check.checkInSymtab();
check.checkContains("dead_var1");
check.checkInSymtab();
check.checkContains("dead_var2");
check.checkInSymtab();
check.checkContains("live_fn1");
check.checkInSymtab();
check.checkContains("live_fn2");
check.checkInSymtab();
check.checkContains("dead_fn1");
check.checkInSymtab();
check.checkContains("dead_fn2");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2\n");
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "yes_dead_strip" });
exe.root_module.addObject(obj);
exe.link_gc_sections = true;
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("live_var1");
check.checkInSymtab();
check.checkContains("live_var2");
check.checkInSymtab();
check.checkNotPresent("dead_var1");
check.checkInSymtab();
check.checkNotPresent("dead_var2");
check.checkInSymtab();
check.checkContains("live_fn1");
check.checkInSymtab();
check.checkContains("live_fn2");
check.checkInSymtab();
check.checkNotPresent("dead_fn1");
check.checkInSymtab();
check.checkNotPresent("dead_fn2");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectStdOutEqual("1 2\n");
test_step.dependOn(&run.step);
}
return test_step;
}
fn testDuplicateDefinitions(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "duplicate-definitions", opts);
const obj = addObject(b, opts, .{ .name = "a", .zig_source_bytes =
\\var x: usize = 1;
\\export fn strong() void { x += 1; }
\\export fn weak() void { x += 1; }
});
const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes =
\\var x: usize = 1;
\\export fn strong() void { x += 1; }
\\comptime { @export(&weakImpl, .{ .name = "weak", .linkage = .weak }); }
\\fn weakImpl() callconv(.c) void { x += 1; }
\\extern fn weak() void;
\\pub fn main() void {
\\ weak();
\\ strong();
\\}
});
exe.root_module.addObject(obj);
expectLinkErrors(exe, test_step, .{ .exact = &.{
"error: duplicate symbol definition: _strong",
"note: defined by /?/a.o",
"note: defined by /?/main_zcu.o",
} });
return test_step;
}
fn testDeadStripDylibs(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "dead-strip-dylibs", opts);
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <objc/runtime.h>
\\int main() {
\\ if (objc_getClass("NSObject") == 0) {
\\ return -1;
\\ }
\\ if (objc_getClass("NSApplication") == 0) {
\\ return -2;
\\ }
\\ return 0;
\\}
});
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(main_o);
exe.root_module.linkFramework("Cocoa", .{});
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd LOAD_DYLIB");
check.checkContains("Cocoa");
check.checkInHeaders();
check.checkExact("cmd LOAD_DYLIB");
check.checkContains("libobjc");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(main_o);
exe.root_module.linkFramework("Cocoa", .{});
exe.dead_strip_dylibs = true;
const run = addRunArtifact(exe);
run.expectExitCode(@as(u8, @bitCast(@as(i8, -2))));
test_step.dependOn(&run.step);
}
return test_step;
}
fn testDylib(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "dylib", opts);
const dylib = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes =
\\#include<stdio.h>
\\char world[] = "world";
\\char* hello() {
\\ return "Hello";
\\}
});
const check = dylib.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkNotPresent("PIE");
test_step.dependOn(&check.step);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include<stdio.h>
\\char* hello();
\\extern char world[];
\\int main() {
\\ printf("%s %s", hello(), world);
\\ return 0;
\\}
});
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.addLibraryPath(dylib.getEmittedBinDirectory());
exe.root_module.addRPath(dylib.getEmittedBinDirectory());
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world");
test_step.dependOn(&run.step);
return test_step;
}
fn testDylibVersionTbd(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "dylib-version-tbd", opts);
const tbd = tbd: {
const wf = WriteFile.create(b);
break :tbd wf.add("liba.tbd",
\\--- !tapi-tbd
\\tbd-version: 4
\\targets: [ x86_64-macos, arm64-macos ]
\\uuids:
\\ - target: x86_64-macos
\\ value: DEADBEEF
\\ - target: arm64-macos
\\ value: BEEFDEAD
\\install-name: '@rpath/liba.dylib'
\\current-version: 1.2
\\exports:
\\ - targets: [ x86_64-macos, arm64-macos ]
\\ symbols: [ _foo ]
);
};
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() {}" });
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.addLibraryPath(tbd.dirname());
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd LOAD_DYLIB");
check.checkExact("name @rpath/liba.dylib");
check.checkExact("current version 10200");
test_step.dependOn(&check.step);
return test_step;
}
fn testEmptyObject(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "empty-object", opts);
const empty = addObject(b, opts, .{ .name = "empty", .c_source_bytes = "" });
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\int main() {
\\ printf("Hello world!");
\\}
});
exe.root_module.addObject(empty);
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world!");
test_step.dependOn(&run.step);
return test_step;
}
fn testEmptyZig(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "empty-zig", opts);
const exe = addExecutable(b, opts, .{ .name = "empty", .zig_source_bytes = "pub fn main() void {}" });
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testEntryPoint(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "entry-point", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include<stdio.h>
\\int non_main() {
\\ printf("%d", 42);
\\ return 0;
\\}
});
exe.entry = .{ .symbol_name = "_non_main" };
const run = addRunArtifact(exe);
run.expectStdOutEqual("42");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("segname __TEXT");
check.checkExtract("vmaddr {vmaddr}");
check.checkInHeaders();
check.checkExact("cmd MAIN");
check.checkExtract("entryoff {entryoff}");
check.checkInSymtab();
check.checkExtract("{n_value} (__TEXT,__text) external _non_main");
check.checkComputeCompare("vmaddr entryoff +", .{ .op = .eq, .value = .{ .variable = "n_value" } });
test_step.dependOn(&check.step);
return test_step;
}
fn testEntryPointArchive(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "entry-point-archive", opts);
const lib = addStaticLibrary(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
{
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "" });
exe.root_module.linkSystemLibrary("main", .{});
exe.root_module.addLibraryPath(lib.getEmittedBinDirectory());
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "" });
exe.root_module.linkSystemLibrary("main", .{});
exe.root_module.addLibraryPath(lib.getEmittedBinDirectory());
exe.link_gc_sections = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}
return test_step;
}
fn testEntryPointDylib(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "entry-point-dylib", opts);
const dylib = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dylib,
\\extern int my_main();
\\int bootstrap() {
\\ return my_main();
\\}
, &.{});
dylib.linker_allow_shlib_undefined = true;
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(dylib,
\\#include<stdio.h>
\\int my_main() {
\\ fprintf(stdout, "Hello!\n");
\\ return 0;
\\}
, &.{});
exe.root_module.linkLibrary(dylib);
exe.entry = .{ .symbol_name = "_bootstrap" };
exe.forceUndefinedSymbol("_my_main");
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("segname __TEXT");
check.checkExtract("vmaddr {text_vmaddr}");
check.checkInHeaders();
check.checkExact("sectname __stubs");
check.checkExtract("addr {stubs_vmaddr}");
check.checkInHeaders();
check.checkExact("sectname __stubs");
check.checkExtract("size {stubs_vmsize}");
check.checkInHeaders();
check.checkExact("cmd MAIN");
check.checkExtract("entryoff {entryoff}");
check.checkComputeCompare("text_vmaddr entryoff +", .{
.op = .gte,
.value = .{ .variable = "stubs_vmaddr" }, // The entrypoint should be a synthetic stub
});
check.checkComputeCompare("text_vmaddr entryoff + stubs_vmaddr -", .{
.op = .lt,
.value = .{ .variable = "stubs_vmsize" }, // The entrypoint should be a synthetic stub
});
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello!\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testHeaderpad(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "headerpad", opts);
const addExe = struct {
fn addExe(bb: *Build, o: Options, name: []const u8) *Compile {
const exe = addExecutable(bb, o, .{
.name = name,
.c_source_bytes = "int main() { return 0; }",
});
exe.root_module.linkFramework("CoreFoundation", .{});
exe.root_module.linkFramework("Foundation", .{});
exe.root_module.linkFramework("Cocoa", .{});
exe.root_module.linkFramework("CoreGraphics", .{});
exe.root_module.linkFramework("CoreHaptics", .{});
exe.root_module.linkFramework("CoreAudio", .{});
exe.root_module.linkFramework("AVFoundation", .{});
exe.root_module.linkFramework("CoreImage", .{});
exe.root_module.linkFramework("CoreLocation", .{});
exe.root_module.linkFramework("CoreML", .{});
exe.root_module.linkFramework("CoreVideo", .{});
exe.root_module.linkFramework("CoreText", .{});
exe.root_module.linkFramework("CryptoKit", .{});
exe.root_module.linkFramework("GameKit", .{});
exe.root_module.linkFramework("SwiftUI", .{});
exe.root_module.linkFramework("StoreKit", .{});
exe.root_module.linkFramework("SpriteKit", .{});
return exe;
}
}.addExe;
{
const exe = addExe(b, opts, "main1");
exe.headerpad_max_install_names = true;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("sectname __text");
check.checkExtract("offset {offset}");
switch (opts.target.result.cpu.arch) {
.aarch64 => check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x4000 } }),
.x86_64 => check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x1000 } }),
else => unreachable,
}
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}
{
const exe = addExe(b, opts, "main2");
exe.headerpad_size = 0x10000;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("sectname __text");
check.checkExtract("offset {offset}");
check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}
{
const exe = addExe(b, opts, "main3");
exe.headerpad_max_install_names = true;
exe.headerpad_size = 0x10000;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("sectname __text");
check.checkExtract("offset {offset}");
check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x10000 } });
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}
{
const exe = addExe(b, opts, "main4");
exe.headerpad_max_install_names = true;
exe.headerpad_size = 0x1000;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("sectname __text");
check.checkExtract("offset {offset}");
switch (opts.target.result.cpu.arch) {
.aarch64 => check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x4000 } }),
.x86_64 => check.checkComputeCompare("offset", .{ .op = .gte, .value = .{ .literal = 0x1000 } }),
else => unreachable,
}
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}
return test_step;
}
// Adapted from https://github.com/llvm/llvm-project/blob/main/lld/test/MachO/weak-header-flags.s
fn testHeaderWeakFlags(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "header-weak-flags", opts);
const obj1 = addObject(b, opts, .{ .name = "a", .asm_source_bytes =
\\.globl _x
\\.weak_definition _x
\\_x:
\\ ret
});
const lib = addSharedLibrary(b, opts, .{ .name = "a" });
lib.root_module.addObject(obj1);
{
const exe = addExecutable(b, opts, .{ .name = "main1", .c_source_bytes = "int main() { return 0; }" });
exe.root_module.addObject(obj1);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkContains("WEAK_DEFINES");
check.checkInHeaders();
check.checkExact("header");
check.checkContains("BINDS_TO_WEAK");
check.checkInExports();
check.checkExtract("[WEAK] {vmaddr} _x");
test_step.dependOn(&check.step);
}
{
const obj = addObject(b, opts, .{ .name = "b" });
switch (opts.target.result.cpu.arch) {
.aarch64 => addAsmSourceBytes(obj,
\\.globl _main
\\_main:
\\ bl _x
\\ ret
),
.x86_64 => addAsmSourceBytes(obj,
\\.globl _main
\\_main:
\\ callq _x
\\ ret
),
else => unreachable,
}
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.linkLibrary(lib);
exe.root_module.addObject(obj);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkNotPresent("WEAK_DEFINES");
check.checkInHeaders();
check.checkExact("header");
check.checkContains("BINDS_TO_WEAK");
check.checkInExports();
check.checkNotPresent("[WEAK] {vmaddr} _x");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main3", .asm_source_bytes =
\\.globl _main, _x
\\_x:
\\
\\_main:
\\ ret
});
exe.root_module.linkLibrary(lib);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkNotPresent("WEAK_DEFINES");
check.checkInHeaders();
check.checkExact("header");
check.checkNotPresent("BINDS_TO_WEAK");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testHelloC(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "hello-c", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\int main() {
\\ printf("Hello world!\n");
\\ return 0;
\\}
});
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world!\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkContains("PIE");
test_step.dependOn(&check.step);
return test_step;
}
fn testHelloZig(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "hello-zig", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes =
\\const std = @import("std");
\\pub fn main() void {
\\ std.Io.File.stdout().writeStreamingAll(std.Options.debug_io, "Hello world!\n") catch @panic("fail");
\\}
});
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world!\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testLargeBss(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "large-bss", opts);
// TODO this test used use a 4GB zerofill section but this actually fails and causes every
// linker I tried misbehave in different ways. This only happened on arm64. I thought that
// maybe S_GB_ZEROFILL section is an answer to this but it doesn't seem supported by dyld
// anymore. When I get some free time I will re-investigate this.
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\char arr[0x1000000];
\\int main() {
\\ return arr[2000];
\\}
});
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testLayout(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "layout", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\int main() {
\\ printf("Hello world!");
\\ return 0;
\\}
});
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd SEGMENT_64");
check.checkExact("segname __LINKEDIT");
check.checkExtract("fileoff {fileoff}");
check.checkExtract("filesz {filesz}");
check.checkInHeaders();
check.checkExact("cmd DYLD_INFO_ONLY");
check.checkExtract("rebaseoff {rebaseoff}");
check.checkExtract("rebasesize {rebasesize}");
check.checkExtract("bindoff {bindoff}");
check.checkExtract("bindsize {bindsize}");
check.checkExtract("lazybindoff {lazybindoff}");
check.checkExtract("lazybindsize {lazybindsize}");
check.checkExtract("exportoff {exportoff}");
check.checkExtract("exportsize {exportsize}");
check.checkInHeaders();
check.checkExact("cmd FUNCTION_STARTS");
check.checkExtract("dataoff {fstartoff}");
check.checkExtract("datasize {fstartsize}");
check.checkInHeaders();
check.checkExact("cmd DATA_IN_CODE");
check.checkExtract("dataoff {diceoff}");
check.checkExtract("datasize {dicesize}");
check.checkInHeaders();
check.checkExact("cmd SYMTAB");
check.checkExtract("symoff {symoff}");
check.checkExtract("nsyms {symnsyms}");
check.checkExtract("stroff {stroff}");
check.checkExtract("strsize {strsize}");
check.checkInHeaders();
check.checkExact("cmd DYSYMTAB");
check.checkExtract("indirectsymoff {dysymoff}");
check.checkExtract("nindirectsyms {dysymnsyms}");
switch (opts.target.result.cpu.arch) {
.aarch64 => {
check.checkInHeaders();
check.checkExact("cmd CODE_SIGNATURE");
check.checkExtract("dataoff {codesigoff}");
check.checkExtract("datasize {codesigsize}");
},
.x86_64 => {},
else => unreachable,
}
// DYLD_INFO_ONLY subsections are in order: rebase < bind < lazy < export,
// and there are no gaps between them
check.checkComputeCompare("rebaseoff rebasesize +", .{ .op = .eq, .value = .{ .variable = "bindoff" } });
check.checkComputeCompare("bindoff bindsize +", .{ .op = .eq, .value = .{ .variable = "lazybindoff" } });
check.checkComputeCompare("lazybindoff lazybindsize +", .{ .op = .eq, .value = .{ .variable = "exportoff" } });
// FUNCTION_STARTS directly follows DYLD_INFO_ONLY (no gap)
check.checkComputeCompare("exportoff exportsize +", .{ .op = .eq, .value = .{ .variable = "fstartoff" } });
// DATA_IN_CODE directly follows FUNCTION_STARTS (no gap)
check.checkComputeCompare("fstartoff fstartsize +", .{ .op = .eq, .value = .{ .variable = "diceoff" } });
// SYMTAB directly follows DATA_IN_CODE (no gap)
check.checkComputeCompare("diceoff dicesize +", .{ .op = .eq, .value = .{ .variable = "symoff" } });
// DYSYMTAB directly follows SYMTAB (no gap)
check.checkComputeCompare("symnsyms 16 symoff * +", .{ .op = .eq, .value = .{ .variable = "dysymoff" } });
// STRTAB follows DYSYMTAB with possible gap
check.checkComputeCompare("dysymnsyms 4 dysymoff * +", .{ .op = .lte, .value = .{ .variable = "stroff" } });
// all LINKEDIT sections apart from CODE_SIGNATURE are 8-bytes aligned
check.checkComputeCompare("rebaseoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("bindoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("lazybindoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("exportoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("fstartoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("diceoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("symoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("stroff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("dysymoff 8 %", .{ .op = .eq, .value = .{ .literal = 0 } });
switch (opts.target.result.cpu.arch) {
.aarch64 => {
// LINKEDIT segment does not extend beyond, or does not include, CODE_SIGNATURE data
check.checkComputeCompare("fileoff filesz codesigoff codesigsize + - -", .{
.op = .eq,
.value = .{ .literal = 0 },
});
// CODE_SIGNATURE data offset is 16-bytes aligned
check.checkComputeCompare("codesigoff 16 %", .{ .op = .eq, .value = .{ .literal = 0 } });
},
.x86_64 => {
// LINKEDIT segment does not extend beyond, or does not include, strtab data
check.checkComputeCompare("fileoff filesz stroff strsize + - -", .{
.op = .eq,
.value = .{ .literal = 0 },
});
},
else => unreachable,
}
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world!");
test_step.dependOn(&run.step);
return test_step;
}
fn testLinkDirectlyCppTbd(b: *Build, opts: Options) *Step {
const io = b.graph.io;
const test_step = addTestStep(b, "link-directly-cpp-tbd", opts);
const sdk = std.zig.system.darwin.getSdk(b.allocator, io, &opts.target.result) orelse
@panic("macOS SDK is required to run the test");
const exe = addExecutable(b, opts, .{
.name = "main",
.cpp_source_bytes =
\\#include <new>
\\#include <cstdio>
\\int main() {
\\ int *x = new int;
\\ *x = 5;
\\ fprintf(stderr, "x: %d\n", *x);
\\ delete x;
\\}
,
.cpp_source_flags = &.{ "-nostdlib++", "-nostdinc++" },
});
exe.root_module.addSystemIncludePath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/include" }) });
exe.root_module.addIncludePath(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/include/c++/v1" }) });
exe.root_module.addObjectFile(.{ .cwd_relative = b.pathJoin(&.{ sdk, "/usr/lib/libc++.tbd" }) });
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("[referenced dynamically] external __mh_execute_header");
test_step.dependOn(&check.step);
return test_step;
}
fn testLinkingStaticLib(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "linking-static-lib", opts);
const obj = addObject(b, opts, .{
.name = "bobj",
.zig_source_bytes = "export var bar: i32 = -42;",
.strip = true, // TODO for self-hosted, we don't really emit any valid DWARF yet since we only export a global
});
const lib = addStaticLibrary(b, opts, .{
.name = "alib",
.zig_source_bytes =
\\export fn foo() i32 {
\\ return 42;
\\}
,
});
lib.root_module.addObject(obj);
const exe = addExecutable(b, opts, .{
.name = "testlib",
.zig_source_bytes =
\\const std = @import("std");
\\extern fn foo() i32;
\\extern var bar: i32;
\\pub fn main() void {
\\ std.debug.print("{d}\n", .{foo() + bar});
\\}
,
});
exe.root_module.linkLibrary(lib);
const run = addRunArtifact(exe);
run.expectStdErrEqual("0\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testLinksection(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "linksection", opts);
const obj = addObject(b, opts, .{ .name = "main", .zig_source_bytes =
\\export var test_global: u32 linksection("__DATA,__TestGlobal") = undefined;
\\export fn testFn() linksection("__TEXT,__TestFn") callconv(.c) void {
\\ TestGenericFn("A").f();
\\}
\\fn TestGenericFn(comptime suffix: []const u8) type {
\\ return struct {
\\ fn f() linksection("__TEXT,__TestGenFn" ++ suffix) void {}
\\ };
\\}
});
const check = obj.checkObject();
check.checkInSymtab();
check.checkContains("(__DATA,__TestGlobal) external _test_global");
check.checkInSymtab();
check.checkContains("(__TEXT,__TestFn) external _testFn");
if (opts.optimize == .Debug) {
check.checkInSymtab();
check.checkContains("(__TEXT,__TestGenFnA) _main.TestGenericFn(");
}
test_step.dependOn(&check.step);
return test_step;
}
fn testMergeLiteralsX64(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "merge-literals-x64", opts);
const a_o = addObject(b, opts, .{ .name = "a", .asm_source_bytes =
\\.globl _q1
\\.globl _s1
\\
\\.align 4
\\_q1:
\\ lea L._q1(%rip), %rax
\\ mov (%rax), %xmm0
\\ ret
\\
\\.section __TEXT,__cstring,cstring_literals
\\l._s1:
\\ .asciz "hello"
\\
\\.section __TEXT,__literal8,8byte_literals
\\.align 8
\\L._q1:
\\ .double 1.2345
\\
\\.section __DATA,__data
\\.align 8
\\_s1:
\\ .quad l._s1
});
const b_o = addObject(b, opts, .{ .name = "b", .asm_source_bytes =
\\.globl _q2
\\.globl _s2
\\.globl _s3
\\
\\.align 4
\\_q2:
\\ lea L._q2(%rip), %rax
\\ mov (%rax), %xmm0
\\ ret
\\
\\.section __TEXT,__cstring,cstring_literals
\\l._s2:
\\ .asciz "hello"
\\l._s3:
\\ .asciz "world"
\\
\\.section __TEXT,__literal8,8byte_literals
\\.align 8
\\L._q2:
\\ .double 1.2345
\\
\\.section __DATA,__data
\\.align 8
\\_s2:
\\ .quad l._s2
\\_s3:
\\ .quad l._s3
});
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\extern double q1();
\\extern double q2();
\\extern const char* s1;
\\extern const char* s2;
\\extern const char* s3;
\\int main() {
\\ printf("%s, %s, %s, %f, %f", s1, s2, s3, q1(), q2());
\\ return 0;
\\}
});
const runWithChecks = struct {
fn runWithChecks(step: *Step, exe: *Compile) void {
const run = addRunArtifact(exe);
run.expectStdOutEqual("hello, hello, world, 1.234500, 1.234500");
step.dependOn(&run.step);
const check = exe.checkObject();
check.dumpSection("__TEXT,__const");
check.checkContains("\x8d\x97n\x12\x83\xc0\xf3?");
check.dumpSection("__TEXT,__cstring");
check.checkContains("hello\x00world\x00%s, %s, %s, %f, %f\x00");
step.dependOn(&check.step);
}
}.runWithChecks;
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.root_module.addObject(main_o);
runWithChecks(test_step, exe);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(b_o);
exe.root_module.addObject(a_o);
exe.root_module.addObject(main_o);
runWithChecks(test_step, exe);
}
{
const c_o = addObject(b, opts, .{ .name = "c" });
c_o.root_module.addObject(a_o);
c_o.root_module.addObject(b_o);
c_o.root_module.addObject(main_o);
const exe = addExecutable(b, opts, .{ .name = "main3" });
exe.root_module.addObject(c_o);
runWithChecks(test_step, exe);
}
return test_step;
}
fn testMergeLiteralsArm64(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "merge-literals-arm64", opts);
const a_o = addObject(b, opts, .{ .name = "a", .asm_source_bytes =
\\.globl _q1
\\.globl _s1
\\
\\.align 4
\\_q1:
\\ adrp x8, L._q1@PAGE
\\ ldr d0, [x8, L._q1@PAGEOFF]
\\ ret
\\
\\.section __TEXT,__cstring,cstring_literals
\\l._s1:
\\ .asciz "hello"
\\
\\.section __TEXT,__literal8,8byte_literals
\\.align 8
\\L._q1:
\\ .double 1.2345
\\
\\.section __DATA,__data
\\.align 8
\\_s1:
\\ .quad l._s1
});
const b_o = addObject(b, opts, .{ .name = "b", .asm_source_bytes =
\\.globl _q2
\\.globl _s2
\\.globl _s3
\\
\\.align 4
\\_q2:
\\ adrp x8, L._q2@PAGE
\\ ldr d0, [x8, L._q2@PAGEOFF]
\\ ret
\\
\\.section __TEXT,__cstring,cstring_literals
\\l._s2:
\\ .asciz "hello"
\\l._s3:
\\ .asciz "world"
\\
\\.section __TEXT,__literal8,8byte_literals
\\.align 8
\\L._q2:
\\ .double 1.2345
\\
\\.section __DATA,__data
\\.align 8
\\_s2:
\\ .quad l._s2
\\_s3:
\\ .quad l._s3
});
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\extern double q1();
\\extern double q2();
\\extern const char* s1;
\\extern const char* s2;
\\extern const char* s3;
\\int main() {
\\ printf("%s, %s, %s, %f, %f", s1, s2, s3, q1(), q2());
\\ return 0;
\\}
});
const runWithChecks = struct {
fn runWithChecks(step: *Step, exe: *Compile) void {
const run = addRunArtifact(exe);
run.expectStdOutEqual("hello, hello, world, 1.234500, 1.234500");
step.dependOn(&run.step);
const check = exe.checkObject();
check.dumpSection("__TEXT,__const");
check.checkContains("\x8d\x97n\x12\x83\xc0\xf3?");
check.dumpSection("__TEXT,__cstring");
check.checkContains("hello\x00world\x00%s, %s, %s, %f, %f\x00");
step.dependOn(&check.step);
}
}.runWithChecks;
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.root_module.addObject(main_o);
runWithChecks(test_step, exe);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(b_o);
exe.root_module.addObject(a_o);
exe.root_module.addObject(main_o);
runWithChecks(test_step, exe);
}
{
const c_o = addObject(b, opts, .{ .name = "c" });
c_o.root_module.addObject(a_o);
c_o.root_module.addObject(b_o);
c_o.root_module.addObject(main_o);
const exe = addExecutable(b, opts, .{ .name = "main3" });
exe.root_module.addObject(c_o);
runWithChecks(test_step, exe);
}
return test_step;
}
/// This particular test case will generate invalid machine code that will segfault at runtime.
/// However, this is by design as we want to test that the linker does not panic when linking it
/// which is also the case for the system linker and lld - linking succeeds, runtime segfaults.
/// It should also be mentioned that runtime segfault is not due to the linker but faulty input asm.
fn testMergeLiteralsArm642(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "merge-literals-arm64-2", opts);
const a_o = addObject(b, opts, .{ .name = "a", .asm_source_bytes =
\\.globl _q1
\\.globl _s1
\\
\\.align 4
\\_q1:
\\ adrp x0, L._q1@PAGE
\\ ldr x0, [x0, L._q1@PAGEOFF]
\\ ret
\\
\\.section __TEXT,__cstring,cstring_literals
\\_s1:
\\ .asciz "hello"
\\
\\.section __TEXT,__literal8,8byte_literals
\\.align 8
\\L._q1:
\\ .double 1.2345
});
const b_o = addObject(b, opts, .{ .name = "b", .asm_source_bytes =
\\.globl _q2
\\.globl _s2
\\.globl _s3
\\
\\.align 4
\\_q2:
\\ adrp x0, L._q2@PAGE
\\ ldr x0, [x0, L._q2@PAGEOFF]
\\ ret
\\
\\.section __TEXT,__cstring,cstring_literals
\\_s2:
\\ .asciz "hello"
\\_s3:
\\ .asciz "world"
\\
\\.section __TEXT,__literal8,8byte_literals
\\.align 8
\\L._q2:
\\ .double 1.2345
});
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\extern double q1();
\\extern double q2();
\\extern const char* s1;
\\extern const char* s2;
\\extern const char* s3;
\\int main() {
\\ printf("%s, %s, %s, %f, %f", s1, s2, s3, q1(), q2());
\\ return 0;
\\}
});
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.root_module.addObject(main_o);
const check = exe.checkObject();
check.dumpSection("__TEXT,__const");
check.checkContains("\x8d\x97n\x12\x83\xc0\xf3?");
check.dumpSection("__TEXT,__cstring");
check.checkContains("hello\x00world\x00%s, %s, %s, %f, %f\x00");
test_step.dependOn(&check.step);
return test_step;
}
fn testMergeLiteralsAlignment(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "merge-literals-alignment", opts);
const a_o = addObject(b, opts, .{ .name = "a", .asm_source_bytes =
\\.globl _s1
\\.globl _s2
\\
\\.section __TEXT,__cstring,cstring_literals
\\.align 3
\\_s1:
\\ .asciz "str1"
\\_s2:
\\ .asciz "str2"
});
const b_o = addObject(b, opts, .{ .name = "b", .asm_source_bytes =
\\.globl _s3
\\.globl _s4
\\
\\.section __TEXT,__cstring,cstring_literals
\\.align 2
\\_s3:
\\ .asciz "str1"
\\_s4:
\\ .asciz "str2"
});
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <assert.h>
\\#include <stdint.h>
\\#include <stdio.h>
\\extern const char* s1;
\\extern const char* s2;
\\extern const char* s3;
\\extern const char* s4;
\\int main() {
\\ assert((uintptr_t)(&s1) % 8 == 0 && s1 == s3);
\\ assert((uintptr_t)(&s2) % 8 == 0 && s2 == s4);
\\ printf("%s%s%s%s", &s1, &s2, &s3, &s4);
\\ return 0;
\\}
, .c_source_flags = &.{"-Wno-format"} });
const runWithChecks = struct {
fn runWithChecks(step: *Step, exe: *Compile) void {
const run = addRunArtifact(exe);
run.expectStdOutEqual("str1str2str1str2");
step.dependOn(&run.step);
const check = exe.checkObject();
check.dumpSection("__TEXT,__cstring");
check.checkContains("str1\x00\x00\x00\x00str2\x00");
check.checkInHeaders();
check.checkExact("segname __TEXT");
check.checkExact("sectname __cstring");
check.checkExact("align 3");
step.dependOn(&check.step);
}
}.runWithChecks;
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.root_module.addObject(main_o);
runWithChecks(test_step, exe);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(b_o);
exe.root_module.addObject(a_o);
exe.root_module.addObject(main_o);
runWithChecks(test_step, exe);
}
return test_step;
}
fn testMergeLiteralsObjc(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "merge-literals-objc", opts);
const main_o = addObject(b, opts, .{ .name = "main", .objc_source_bytes =
\\#import <Foundation/Foundation.h>;
\\
\\extern void foo();
\\
\\int main() {
\\ NSString *thing = @"aaa";
\\
\\ SEL sel = @selector(lowercaseString);
\\ NSString *lower = (([thing respondsToSelector:sel]) ? @"YES" : @"NO");
\\ NSLog (@"Responds to lowercaseString: %@", lower);
\\ if ([thing respondsToSelector:sel]) //(lower == @"YES")
\\ NSLog(@"lowercaseString is: %@", [thing lowercaseString]);
\\
\\ foo();
\\}
});
const a_o = addObject(b, opts, .{ .name = "a", .objc_source_bytes =
\\#import <Foundation/Foundation.h>;
\\
\\void foo() {
\\ NSString *thing = @"aaa";
\\ SEL sel = @selector(lowercaseString);
\\ NSString *lower = (([thing respondsToSelector:sel]) ? @"YES" : @"NO");
\\ NSLog (@"Responds to lowercaseString in foo(): %@", lower);
\\ if ([thing respondsToSelector:sel]) //(lower == @"YES")
\\ NSLog(@"lowercaseString in foo() is: %@", [thing lowercaseString]);
\\ SEL sel2 = @selector(uppercaseString);
\\ NSString *upper = (([thing respondsToSelector:sel2]) ? @"YES" : @"NO");
\\ NSLog (@"Responds to uppercaseString in foo(): %@", upper);
\\ if ([thing respondsToSelector:sel2]) //(upper == @"YES")
\\ NSLog(@"uppercaseString in foo() is: %@", [thing uppercaseString]);
\\}
});
const runWithChecks = struct {
fn runWithChecks(step: *Step, exe: *Compile) void {
const builder = step.owner;
const run = addRunArtifact(exe);
run.addCheck(.{ .expect_stderr_match = builder.dupe("Responds to lowercaseString: YES") });
run.addCheck(.{ .expect_stderr_match = builder.dupe("lowercaseString is: aaa") });
run.addCheck(.{ .expect_stderr_match = builder.dupe("Responds to lowercaseString in foo(): YES") });
run.addCheck(.{ .expect_stderr_match = builder.dupe("lowercaseString in foo() is: aaa") });
run.addCheck(.{ .expect_stderr_match = builder.dupe("Responds to uppercaseString in foo(): YES") });
run.addCheck(.{ .expect_stderr_match = builder.dupe("uppercaseString in foo() is: AAA") });
step.dependOn(&run.step);
const check = exe.checkObject();
check.dumpSection("__TEXT,__objc_methname");
check.checkContains("lowercaseString\x00");
check.dumpSection("__TEXT,__objc_methname");
check.checkContains("uppercaseString\x00");
step.dependOn(&check.step);
}
}.runWithChecks;
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(main_o);
exe.root_module.addObject(a_o);
exe.root_module.linkFramework("Foundation", .{});
runWithChecks(test_step, exe);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(main_o);
exe.root_module.linkFramework("Foundation", .{});
runWithChecks(test_step, exe);
}
{
const b_o = addObject(b, opts, .{ .name = "b" });
b_o.root_module.addObject(a_o);
b_o.root_module.addObject(main_o);
const exe = addExecutable(b, opts, .{ .name = "main3" });
exe.root_module.addObject(b_o);
exe.root_module.linkFramework("Foundation", .{});
runWithChecks(test_step, exe);
}
return test_step;
}
fn testMhExecuteHeader(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "mh-execute-header", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("[referenced dynamically] external __mh_execute_header");
test_step.dependOn(&check.step);
return test_step;
}
fn testNoDeadStrip(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "no-dead-strip", opts);
const exe = addExecutable(b, opts, .{ .name = "name", .c_source_bytes =
\\__attribute__((used)) int bogus1 = 0;
\\int bogus2 = 0;
\\int foo = 42;
\\int main() {
\\ return foo - 42;
\\}
});
exe.link_gc_sections = true;
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("external _bogus1");
check.checkInSymtab();
check.checkNotPresent("external _bogus2");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testNoExportsDylib(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "no-exports-dylib", opts);
const dylib = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes = "static void abc() {}" });
const check = dylib.checkObject();
check.checkInSymtab();
check.checkNotPresent("external _abc");
test_step.dependOn(&check.step);
return test_step;
}
fn testNeededFramework(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "needed-framework", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
exe.root_module.linkFramework("Cocoa", .{ .needed = true });
exe.dead_strip_dylibs = true;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd LOAD_DYLIB");
check.checkContains("Cocoa");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testNeededLibrary(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "needed-library", opts);
const dylib = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes = "int a = 42;" });
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
exe.root_module.linkSystemLibrary("a", .{ .needed = true });
exe.root_module.addLibraryPath(dylib.getEmittedBinDirectory());
exe.root_module.addRPath(dylib.getEmittedBinDirectory());
exe.dead_strip_dylibs = true;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd LOAD_DYLIB");
check.checkContains("liba.dylib");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testObjc(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "objc", opts);
const lib = addStaticLibrary(b, opts, .{ .name = "a", .objc_source_bytes =
\\#import <Foundation/Foundation.h>
\\@interface Foo : NSObject
\\@end
\\@implementation Foo
\\@end
});
{
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.linkFramework("Foundation", .{});
exe.root_module.addLibraryPath(lib.getEmittedBinDirectory());
const check = exe.checkObject();
check.checkInSymtab();
check.checkNotPresent("_OBJC_");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2", .c_source_bytes = "int main() { return 0; }" });
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.linkFramework("Foundation", .{});
exe.root_module.addLibraryPath(lib.getEmittedBinDirectory());
exe.force_load_objc = true;
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("_OBJC_");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
}
return test_step;
}
fn testObjcpp(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "objcpp", opts);
const foo_h = foo_h: {
const wf = WriteFile.create(b);
break :foo_h wf.add("Foo.h",
\\#import <Foundation/Foundation.h>
\\@interface Foo : NSObject
\\- (NSString *)name;
\\@end
);
};
const foo_o = addObject(b, opts, .{ .name = "foo", .objcpp_source_bytes =
\\#import "Foo.h"
\\@implementation Foo
\\- (NSString *)name
\\{
\\ NSString *str = [[NSString alloc] initWithFormat:@"Zig"];
\\ return str;
\\}
\\@end
});
foo_o.root_module.addIncludePath(foo_h.dirname());
foo_o.root_module.link_libcpp = true;
const exe = addExecutable(b, opts, .{ .name = "main", .objcpp_source_bytes =
\\#import "Foo.h"
\\#import <assert.h>
\\#include <iostream>
\\int main(int argc, char *argv[])
\\{
\\ @autoreleasepool {
\\ Foo *foo = [[Foo alloc] init];
\\ NSString *result = [foo name];
\\ std::cout << "Hello from C++ and " << [result UTF8String];
\\ assert([result isEqualToString:@"Zig"]);
\\ return 0;
\\ }
\\}
});
exe.root_module.addIncludePath(foo_h.dirname());
exe.root_module.addObject(foo_o);
exe.root_module.link_libcpp = true;
exe.root_module.linkFramework("Foundation", .{});
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello from C++ and Zig");
test_step.dependOn(&run.step);
return test_step;
}
fn testPagezeroSize(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "pagezero-size", opts);
{
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main () { return 0; }" });
exe.pagezero_size = 0x4000;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("LC 0");
check.checkExact("segname __PAGEZERO");
check.checkExact("vmaddr 0");
check.checkExact("vmsize 4000");
check.checkInHeaders();
check.checkExact("segname __TEXT");
check.checkExact("vmaddr 4000");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main () { return 0; }" });
exe.pagezero_size = 0;
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("LC 0");
check.checkExact("segname __TEXT");
check.checkExact("vmaddr 0");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testReexportsZig(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "reexports-zig", opts);
const lib = addStaticLibrary(b, opts, .{ .name = "a", .zig_source_bytes =
\\const x: i32 = 42;
\\export fn foo() i32 {
\\ return x;
\\}
\\comptime {
\\ @export(&foo, .{ .name = "bar", .linkage = .strong });
\\}
});
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\extern int foo();
\\extern int bar();
\\int main() {
\\ return bar() - foo();
\\}
});
exe.root_module.linkLibrary(lib);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testRelocatable(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "relocatable", opts);
const a_o = addObject(b, opts, .{ .name = "a", .cpp_source_bytes =
\\#include <stdexcept>
\\int try_me() {
\\ throw std::runtime_error("Oh no!");
\\}
});
a_o.root_module.link_libcpp = true;
const b_o = addObject(b, opts, .{ .name = "b", .cpp_source_bytes =
\\extern int try_me();
\\int try_again() {
\\ return try_me();
\\}
});
const main_o = addObject(b, opts, .{ .name = "main", .cpp_source_bytes =
\\#include <iostream>
\\#include <stdexcept>
\\extern int try_again();
\\int main() {
\\ try {
\\ try_again();
\\ } catch (const std::exception &e) {
\\ std::cout << "exception=" << e.what();
\\ }
\\ return 0;
\\}
});
main_o.root_module.link_libcpp = true;
const exp_stdout = "exception=Oh no!";
{
const c_o = addObject(b, opts, .{ .name = "c" });
c_o.root_module.addObject(a_o);
c_o.root_module.addObject(b_o);
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(main_o);
exe.root_module.addObject(c_o);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
}
{
const d_o = addObject(b, opts, .{ .name = "d" });
d_o.root_module.addObject(a_o);
d_o.root_module.addObject(b_o);
d_o.root_module.addObject(main_o);
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(d_o);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
}
return test_step;
}
fn testRelocatableZig(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "relocatable-zig", opts);
const a_o = addObject(b, opts, .{ .name = "a", .zig_source_bytes =
\\const std = @import("std");
\\export var foo: i32 = 0;
\\export fn incrFoo() void {
\\ foo += 1;
\\ std.debug.print("incrFoo={d}\n", .{foo});
\\}
});
const b_o = addObject(b, opts, .{ .name = "b", .zig_source_bytes =
\\const std = @import("std");
\\extern var foo: i32;
\\export fn decrFoo() void {
\\ foo -= 1;
\\ std.debug.print("decrFoo={d}\n", .{foo});
\\}
});
const main_o = addObject(b, opts, .{ .name = "main", .zig_source_bytes =
\\const std = @import("std");
\\extern var foo: i32;
\\extern fn incrFoo() void;
\\extern fn decrFoo() void;
\\pub fn main() void {
\\ const init = foo;
\\ incrFoo();
\\ decrFoo();
\\ if (init == foo) @panic("Oh no!");
\\}
});
const c_o = addObject(b, opts, .{ .name = "c" });
c_o.root_module.addObject(a_o);
c_o.root_module.addObject(b_o);
c_o.root_module.addObject(main_o);
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(c_o);
const run = addRunArtifact(exe);
run.addCheck(.{ .expect_stderr_match = b.dupe("incrFoo=1") });
run.addCheck(.{ .expect_stderr_match = b.dupe("decrFoo=0") });
run.addCheck(.{ .expect_stderr_match = b.dupe("panic: Oh no!") });
test_step.dependOn(&run.step);
return test_step;
}
fn testSearchStrategy(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "search-strategy", opts);
const obj = addObject(b, opts, .{ .name = "a", .c_source_bytes =
\\#include<stdio.h>
\\char world[] = "world";
\\char* hello() {
\\ return "Hello";
\\}
});
const liba = addStaticLibrary(b, opts, .{ .name = "a" });
liba.root_module.addObject(obj);
const dylib = addSharedLibrary(b, opts, .{ .name = "a" });
dylib.root_module.addObject(obj);
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
\\#include<stdio.h>
\\char* hello();
\\extern char world[];
\\int main() {
\\ printf("%s %s", hello(), world);
\\ return 0;
\\}
});
{
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(main_o);
exe.root_module.linkSystemLibrary("a", .{ .use_pkg_config = .no, .search_strategy = .mode_first });
exe.root_module.addLibraryPath(liba.getEmittedBinDirectory());
exe.root_module.addLibraryPath(dylib.getEmittedBinDirectory());
exe.root_module.addRPath(dylib.getEmittedBinDirectory());
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd LOAD_DYLIB");
check.checkContains("liba.dylib");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(main_o);
exe.root_module.linkSystemLibrary("a", .{ .use_pkg_config = .no, .search_strategy = .paths_first });
exe.root_module.addLibraryPath(liba.getEmittedBinDirectory());
exe.root_module.addLibraryPath(dylib.getEmittedBinDirectory());
exe.root_module.addRPath(dylib.getEmittedBinDirectory());
const run = addRunArtifact(exe);
run.expectStdOutEqual("Hello world");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd LOAD_DYLIB");
check.checkNotPresent("liba.dylib");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testSectionBoundarySymbols(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "section-boundary-symbols", opts);
const obj1 = addObject(b, opts, .{
.name = "obj1",
.cpp_source_bytes =
\\constexpr const char* MESSAGE __attribute__((used, section("__DATA_CONST,__message_ptr"))) = "codebase";
,
});
const main_o = addObject(b, opts, .{
.name = "main",
.zig_source_bytes =
\\const std = @import("std");
\\extern fn interop() ?[*:0]const u8;
\\pub fn main() !void {
\\ std.debug.print("All your {s} are belong to us.\n", .{
\\ if (interop()) |ptr| std.mem.span(ptr) else "(null)",
\\ });
\\}
,
});
{
const obj2 = addObject(b, opts, .{
.name = "obj2",
.cpp_source_bytes =
\\extern const char* message_pointer __asm("section$start$__DATA_CONST$__message_ptr");
\\extern "C" const char* interop() {
\\ return message_pointer;
\\}
,
});
const exe = addExecutable(b, opts, .{ .name = "test" });
exe.root_module.addObject(obj1);
exe.root_module.addObject(obj2);
exe.root_module.addObject(main_o);
const run = b.addRunArtifact(exe);
run.skip_foreign_checks = true;
run.expectStdErrEqual("All your codebase are belong to us.\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkNotPresent("external section$start$__DATA_CONST$__message_ptr");
test_step.dependOn(&check.step);
}
{
const obj3 = addObject(b, opts, .{
.name = "obj3",
.cpp_source_bytes =
\\extern const char* message_pointer __asm("section$start$__DATA_CONST$__not_present");
\\extern "C" const char* interop() {
\\ return message_pointer;
\\}
,
});
const exe = addExecutable(b, opts, .{ .name = "test" });
exe.root_module.addObject(obj1);
exe.root_module.addObject(obj3);
exe.root_module.addObject(main_o);
const run = b.addRunArtifact(exe);
run.skip_foreign_checks = true;
run.expectStdErrEqual("All your (null) are belong to us.\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkNotPresent("external section$start$__DATA_CONST$__not_present");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testSectionBoundarySymbols2(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "section-boundary-symbols-2", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\struct pair { int a; int b; };
\\struct pair first __attribute__((section("__DATA,__pairs"))) = { 1, 2 };
\\struct pair second __attribute__((section("__DATA,__pairs"))) = { 3, 4 };
\\extern struct pair pairs_start __asm("section$start$__DATA$__pairs");
\\extern struct pair pairs_end __asm("section$end$__DATA$__pairs");
\\int main() {
\\ printf("%d,%d\n", first.a, first.b);
\\ printf("%d,%d\n", second.a, second.b);
\\ struct pair* p;
\\ for (p = &pairs_start; p < &pairs_end; p++) {
\\ p->a = 0;
\\ }
\\ printf("%d,%d\n", first.a, first.b);
\\ printf("%d,%d\n", second.a, second.b);
\\ return 0;
\\}
});
const run = b.addRunArtifact(exe);
run.skip_foreign_checks = true;
run.expectStdOutEqual(
\\1,2
\\3,4
\\0,2
\\0,4
\\
);
test_step.dependOn(&run.step);
return test_step;
}
fn testSegmentBoundarySymbols(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "segment-boundary-symbols", opts);
const obj1 = addObject(b, opts, .{ .name = "a", .cpp_source_bytes =
\\constexpr const char* MESSAGE __attribute__((used, section("__DATA_CONST_1,__message_ptr"))) = "codebase";
});
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\const char* interop();
\\int main() {
\\ printf("All your %s are belong to us.\n", interop());
\\ return 0;
\\}
});
{
const obj2 = addObject(b, opts, .{ .name = "b", .cpp_source_bytes =
\\extern const char* message_pointer __asm("segment$start$__DATA_CONST_1");
\\extern "C" const char* interop() {
\\ return message_pointer;
\\}
});
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(obj1);
exe.root_module.addObject(obj2);
exe.root_module.addObject(main_o);
const run = addRunArtifact(exe);
run.expectStdOutEqual("All your codebase are belong to us.\n");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkNotPresent("external segment$start$__DATA_CONST_1");
test_step.dependOn(&check.step);
}
{
const obj2 = addObject(b, opts, .{ .name = "c", .cpp_source_bytes =
\\extern const char* message_pointer __asm("segment$start$__DATA_1");
\\extern "C" const char* interop() {
\\ return message_pointer;
\\}
});
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(obj1);
exe.root_module.addObject(obj2);
exe.root_module.addObject(main_o);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd SEGMENT_64");
check.checkExact("segname __DATA_1");
check.checkExtract("vmsize {vmsize}");
check.checkExtract("filesz {filesz}");
check.checkComputeCompare("vmsize", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkComputeCompare("filesz", .{ .op = .eq, .value = .{ .literal = 0 } });
check.checkInSymtab();
check.checkNotPresent("external segment$start$__DATA_1");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testSymbolStabs(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "symbol-stabs", opts);
const a_o = addObject(b, opts, .{ .name = "a", .c_source_bytes =
\\int foo = 42;
\\int getFoo() {
\\ return foo;
\\}
});
const b_o = addObject(b, opts, .{ .name = "b", .c_source_bytes =
\\int bar = 24;
\\int getBar() {
\\ return bar;
\\}
});
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\extern int getFoo();
\\extern int getBar();
\\int main() {
\\ printf("foo=%d,bar=%d", getFoo(), getBar());
\\ return 0;
\\}
});
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(a_o);
exe.root_module.addObject(b_o);
exe.root_module.addObject(main_o);
const run = addRunArtifact(exe);
run.expectStdOutEqual("foo=42,bar=24");
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("a.o"); // TODO we really should do a fuzzy search like OSO <ignore>/a.o
check.checkInSymtab();
check.checkContains("b.o");
check.checkInSymtab();
check.checkContains("main.o");
test_step.dependOn(&check.step);
return test_step;
}
fn testStackSize(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "stack-size", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
exe.stack_size = 0x100000000;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd MAIN");
check.checkExact("stacksize 100000000");
test_step.dependOn(&check.step);
return test_step;
}
fn testTbdv3(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tbdv3", opts);
const dylib = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes = "int getFoo() { return 42; }" });
const tbd = tbd: {
const wf = WriteFile.create(b);
break :tbd wf.add("liba.tbd",
\\--- !tapi-tbd-v3
\\archs: [ arm64, x86_64 ]
\\uuids: [ 'arm64: DEADBEEF', 'x86_64: BEEFDEAD' ]
\\platform: macos
\\install-name: @rpath/liba.dylib
\\current-version: 0
\\exports:
\\ - archs: [ arm64, x86_64 ]
\\ symbols: [ _getFoo ]
);
};
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\int getFoo();
\\int main() {
\\ return getFoo() - 42;
\\}
});
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.addLibraryPath(tbd.dirname());
exe.root_module.addRPath(dylib.getEmittedBinDirectory());
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testTentative(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tentative", opts);
const exe = addExecutable(b, opts, .{ .name = "main" });
addCSourceBytes(exe,
\\int foo;
\\int bar;
\\int baz = 42;
, &.{"-fcommon"});
addCSourceBytes(exe,
\\#include<stdio.h>
\\int foo;
\\int bar = 5;
\\int baz;
\\int main() {
\\ printf("%d %d %d\n", foo, bar, baz);
\\}
, &.{"-fcommon"});
const run = addRunArtifact(exe);
run.expectStdOutEqual("0 5 42\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testThunks(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "thunks", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\void bar() {
\\ printf("bar");
\\}
\\void foo() {
\\ fprintf(stdout, "foo");
\\}
\\int main() {
\\ foo();
\\ bar();
\\ return 0;
\\}
});
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("_printf__thunk");
check.checkInSymtab();
check.checkContains("_fprintf__thunk");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectStdOutEqual("foobar");
test_step.dependOn(&run.step);
return test_step;
}
fn testTls(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls", opts);
const dylib = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes =
\\_Thread_local int a;
\\int getA() {
\\ return a;
\\}
});
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include<stdio.h>
\\extern _Thread_local int a;
\\extern int getA();
\\int getA2() {
\\ return a;
\\}
\\int main() {
\\ a = 2;
\\ printf("%d %d %d", a, getA(), getA2());
\\ return 0;
\\}
});
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.addLibraryPath(dylib.getEmittedBinDirectory());
exe.root_module.addRPath(dylib.getEmittedBinDirectory());
const run = addRunArtifact(exe);
run.expectStdOutEqual("2 2 2");
test_step.dependOn(&run.step);
return test_step;
}
// https://github.com/ziglang/zig/issues/19221
fn testTlsPointers(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-pointers", opts);
const foo_h = foo_h: {
const wf = WriteFile.create(b);
break :foo_h wf.add("foo.h",
\\template<typename just4fun>
\\struct Foo {
\\
\\public:
\\ static int getVar() {
\\ static int thread_local var = 0;
\\ ++var;
\\ return var;
\\}
\\};
);
};
const bar_o = addObject(b, opts, .{ .name = "bar", .cpp_source_bytes =
\\#include "foo.h"
\\int bar() {
\\ int v1 = Foo<int>::getVar();
\\ return v1;
\\}
});
bar_o.root_module.addIncludePath(foo_h.dirname());
bar_o.root_module.link_libcpp = true;
const baz_o = addObject(b, opts, .{ .name = "baz", .cpp_source_bytes =
\\#include "foo.h"
\\int baz() {
\\ int v1 = Foo<unsigned>::getVar();
\\ return v1;
\\}
});
baz_o.root_module.addIncludePath(foo_h.dirname());
baz_o.root_module.link_libcpp = true;
const main_o = addObject(b, opts, .{ .name = "main", .cpp_source_bytes =
\\extern int bar();
\\extern int baz();
\\int main() {
\\ int v1 = bar();
\\ int v2 = baz();
\\ return v1 != v2;
\\}
});
main_o.root_module.addIncludePath(foo_h.dirname());
main_o.root_module.link_libcpp = true;
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(bar_o);
exe.root_module.addObject(baz_o);
exe.root_module.addObject(main_o);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testTlsLargeTbss(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-large-tbss", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\_Thread_local int x[0x8000];
\\_Thread_local int y[0x8000];
\\int main() {
\\ x[0] = 3;
\\ x[0x7fff] = 5;
\\ printf("%d %d %d %d %d %d\n", x[0], x[1], x[0x7fff], y[0], y[1], y[0x7fff]);
\\}
});
const run = addRunArtifact(exe);
run.expectStdOutEqual("3 0 5 0 0 0\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testTlsZig(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "tls-zig", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes =
\\const std = @import("std");
\\threadlocal var x: i32 = 0;
\\threadlocal var y: i32 = -1;
\\pub fn main() void {
\\ var stdout_writer = std.Io.File.stdout().writerStreaming(std.Options.debug_io, &.{});
\\ stdout_writer.interface.print("{d} {d}\n", .{x, y}) catch unreachable;
\\ x -= 1;
\\ y += 1;
\\ stdout_writer.interface.print("{d} {d}\n", .{x, y}) catch unreachable;
\\}
});
const run = addRunArtifact(exe);
run.expectStdOutEqual(
\\0 -1
\\-1 0
\\
);
test_step.dependOn(&run.step);
return test_step;
}
fn testTwoLevelNamespace(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "two-level-namespace", opts);
const liba = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes =
\\#include <stdio.h>
\\int foo = 1;
\\int* ptr_to_foo = &foo;
\\int getFoo() {
\\ return foo;
\\}
\\void printInA() {
\\ printf("liba: getFoo()=%d, ptr_to_foo=%d\n", getFoo(), *ptr_to_foo);
\\}
});
{
const check = liba.checkObject();
check.checkInDyldLazyBind();
check.checkNotPresent("(flat lookup) _getFoo");
check.checkInIndirectSymtab();
check.checkNotPresent("_getFoo");
test_step.dependOn(&check.step);
}
const libb = addSharedLibrary(b, opts, .{ .name = "b", .c_source_bytes =
\\#include <stdio.h>
\\int foo = 2;
\\int* ptr_to_foo = &foo;
\\int getFoo() {
\\ return foo;
\\}
\\void printInB() {
\\ printf("libb: getFoo()=%d, ptr_to_foo=%d\n", getFoo(), *ptr_to_foo);
\\}
});
{
const check = libb.checkObject();
check.checkInDyldLazyBind();
check.checkNotPresent("(flat lookup) _getFoo");
check.checkInIndirectSymtab();
check.checkNotPresent("_getFoo");
test_step.dependOn(&check.step);
}
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\int getFoo();
\\extern int* ptr_to_foo;
\\void printInA();
\\void printInB();
\\int main() {
\\ printf("main: getFoo()=%d, ptr_to_foo=%d\n", getFoo(), *ptr_to_foo);
\\ printInA();
\\ printInB();
\\ return 0;
\\}
});
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(main_o);
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.linkSystemLibrary("b", .{});
exe.root_module.addLibraryPath(liba.getEmittedBinDirectory());
exe.root_module.addLibraryPath(libb.getEmittedBinDirectory());
exe.root_module.addRPath(liba.getEmittedBinDirectory());
exe.root_module.addRPath(libb.getEmittedBinDirectory());
const check = exe.checkObject();
check.checkInSymtab();
check.checkExact("(undefined) external _getFoo (from liba)");
check.checkInSymtab();
check.checkExact("(undefined) external _printInA (from liba)");
check.checkInSymtab();
check.checkExact("(undefined) external _printInB (from libb)");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectStdOutEqual(
\\main: getFoo()=1, ptr_to_foo=1
\\liba: getFoo()=1, ptr_to_foo=1
\\libb: getFoo()=2, ptr_to_foo=2
\\
);
test_step.dependOn(&run.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(main_o);
exe.root_module.linkSystemLibrary("b", .{});
exe.root_module.linkSystemLibrary("a", .{});
exe.root_module.addLibraryPath(liba.getEmittedBinDirectory());
exe.root_module.addLibraryPath(libb.getEmittedBinDirectory());
exe.root_module.addRPath(liba.getEmittedBinDirectory());
exe.root_module.addRPath(libb.getEmittedBinDirectory());
const check = exe.checkObject();
check.checkInSymtab();
check.checkExact("(undefined) external _getFoo (from libb)");
check.checkInSymtab();
check.checkExact("(undefined) external _printInA (from liba)");
check.checkInSymtab();
check.checkExact("(undefined) external _printInB (from libb)");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectStdOutEqual(
\\main: getFoo()=2, ptr_to_foo=2
\\liba: getFoo()=1, ptr_to_foo=1
\\libb: getFoo()=2, ptr_to_foo=2
\\
);
test_step.dependOn(&run.step);
}
return test_step;
}
fn testDiscardLocalSymbols(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "discard-local-symbols", opts);
const obj = addObject(b, opts, .{ .name = "a", .c_source_bytes = "static int foo = 42;" });
const lib = addStaticLibrary(b, opts, .{ .name = "a" });
lib.root_module.addObject(obj);
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
{
const exe = addExecutable(b, opts, .{ .name = "main3" });
exe.root_module.addObject(main_o);
exe.root_module.addObject(obj);
exe.discard_local_symbols = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkNotPresent("_foo");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main4" });
exe.root_module.addObject(main_o);
exe.root_module.linkLibrary(lib);
exe.discard_local_symbols = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkNotPresent("_foo");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testUndefinedFlag(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "undefined-flag", opts);
const obj = addObject(b, opts, .{ .name = "a", .c_source_bytes = "int foo = 42;" });
const lib = addStaticLibrary(b, opts, .{ .name = "a" });
lib.root_module.addObject(obj);
const main_o = addObject(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
{
const exe = addExecutable(b, opts, .{ .name = "main1" });
exe.root_module.addObject(main_o);
exe.root_module.linkLibrary(lib);
exe.forceUndefinedSymbol("_foo");
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("_foo");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main2" });
exe.root_module.addObject(main_o);
exe.root_module.linkLibrary(lib);
exe.forceUndefinedSymbol("_foo");
exe.link_gc_sections = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("_foo");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main3" });
exe.root_module.addObject(main_o);
exe.root_module.addObject(obj);
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("_foo");
test_step.dependOn(&check.step);
}
{
const exe = addExecutable(b, opts, .{ .name = "main4" });
exe.root_module.addObject(main_o);
exe.root_module.addObject(obj);
exe.link_gc_sections = true;
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkNotPresent("_foo");
test_step.dependOn(&check.step);
}
return test_step;
}
fn testUndefinedDynamicLookup(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "undefined-dynamic-lookup", opts);
// Create a dylib with an undefined external symbol reference
const dylib = addSharedLibrary(b, opts, .{ .name = "a" });
addCSourceBytes(dylib,
\\extern int undefined_symbol(void);
\\int call_undefined(void) {
\\ return undefined_symbol();
\\}
, &.{});
dylib.linker_allow_shlib_undefined = true;
// Verify the Mach-O header does NOT contain NOUNDEFS flag
const check = dylib.checkObject();
check.checkInHeaders();
check.checkExact("header");
check.checkNotPresent("NOUNDEFS");
test_step.dependOn(&check.step);
return test_step;
}
fn testUnresolvedError(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "unresolved-error", opts);
const obj = addObject(b, opts, .{ .name = "a", .zig_source_bytes =
\\extern fn foo() i32;
\\export fn bar() i32 { return foo() + 1; }
});
const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes =
\\const std = @import("std");
\\extern fn foo() i32;
\\extern fn bar() i32;
\\pub fn main() void {
\\ std.debug.print("foo() + bar() = {d}", .{foo() + bar()});
\\}
});
exe.root_module.addObject(obj);
// TODO order should match across backends if possible
if (opts.use_llvm) {
expectLinkErrors(exe, test_step, .{ .exact = &.{
"error: undefined symbol: _foo",
"note: referenced by /?/a.o:_bar",
"note: referenced by /?/main_zcu.o:_main.main",
} });
} else {
expectLinkErrors(exe, test_step, .{ .exact = &.{
"error: undefined symbol: _foo",
"note: referenced by /?/main.o:_main.main",
"note: referenced by /?/a.o:__TEXT$__text_zig",
} });
}
return test_step;
}
fn testUnresolvedError2(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "unresolved-error-2", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .zig_source_bytes =
\\pub fn main() !void {
\\ const msg_send_fn = @extern(
\\ *const fn () callconv(.c) usize,
\\ .{ .name = "objc_msgSend$initWithContentRect:styleMask:backing:defer:screen:" },
\\ );
\\ _ = @call(
\\ .auto,
\\ msg_send_fn,
\\ .{},
\\ );
\\}
});
expectLinkErrors(exe, test_step, .{ .exact = &.{
"error: undefined symbol: _objc_msgSend",
"note: referenced implicitly",
} });
return test_step;
}
fn testUnwindInfo(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "unwind-info", opts);
const all_h = all_h: {
const wf = WriteFile.create(b);
break :all_h wf.add("all.h",
\\#ifndef ALL
\\#define ALL
\\
\\#include <cstddef>
\\#include <string>
\\#include <stdexcept>
\\
\\struct SimpleString {
\\ SimpleString(size_t max_size);
\\ ~SimpleString();
\\
\\ void print(const char* tag) const;
\\ bool append_line(const char* x);
\\
\\private:
\\ size_t max_size;
\\ char* buffer;
\\ size_t length;
\\};
\\
\\struct SimpleStringOwner {
\\ SimpleStringOwner(const char* x);
\\ ~SimpleStringOwner();
\\
\\private:
\\ SimpleString string;
\\};
\\
\\class Error: public std::exception {
\\public:
\\ explicit Error(const char* msg) : msg{ msg } {}
\\ virtual ~Error() noexcept {}
\\ virtual const char* what() const noexcept {
\\ return msg.c_str();
\\ }
\\
\\protected:
\\ std::string msg;
\\};
\\
\\#endif
);
};
const main_o = addObject(b, opts, .{ .name = "main", .cpp_source_bytes =
\\#include "all.h"
\\#include <cstdio>
\\
\\void fn_c() {
\\ SimpleStringOwner c{ "cccccccccc" };
\\}
\\
\\void fn_b() {
\\ SimpleStringOwner b{ "b" };
\\ fn_c();
\\}
\\
\\int main() {
\\ try {
\\ SimpleStringOwner a{ "a" };
\\ fn_b();
\\ SimpleStringOwner d{ "d" };
\\ } catch (const Error& e) {
\\ printf("Error: %s\n", e.what());
\\ } catch(const std::exception& e) {
\\ printf("Exception: %s\n", e.what());
\\ }
\\ return 0;
\\}
});
main_o.root_module.addIncludePath(all_h.dirname());
main_o.root_module.link_libcpp = true;
const simple_string_o = addObject(b, opts, .{ .name = "simple_string", .cpp_source_bytes =
\\#include "all.h"
\\#include <cstdio>
\\#include <cstring>
\\
\\SimpleString::SimpleString(size_t max_size)
\\: max_size{ max_size }, length{} {
\\ if (max_size == 0) {
\\ throw Error{ "Max size must be at least 1." };
\\ }
\\ buffer = new char[max_size];
\\ buffer[0] = 0;
\\}
\\
\\SimpleString::~SimpleString() {
\\ delete[] buffer;
\\}
\\
\\void SimpleString::print(const char* tag) const {
\\ printf("%s: %s", tag, buffer);
\\}
\\
\\bool SimpleString::append_line(const char* x) {
\\ const auto x_len = strlen(x);
\\ if (x_len + length + 2 > max_size) return false;
\\ std::strncpy(buffer + length, x, max_size - length);
\\ length += x_len;
\\ buffer[length++] = '\n';
\\ buffer[length] = 0;
\\ return true;
\\}
});
simple_string_o.root_module.addIncludePath(all_h.dirname());
simple_string_o.root_module.link_libcpp = true;
const simple_string_owner_o = addObject(b, opts, .{ .name = "simple_string_owner", .cpp_source_bytes =
\\#include "all.h"
\\
\\SimpleStringOwner::SimpleStringOwner(const char* x) : string{ 10 } {
\\ if (!string.append_line(x)) {
\\ throw Error{ "Not enough memory!" };
\\ }
\\ string.print("Constructed");
\\}
\\
\\SimpleStringOwner::~SimpleStringOwner() {
\\ string.print("About to destroy");
\\}
});
simple_string_owner_o.root_module.addIncludePath(all_h.dirname());
simple_string_owner_o.root_module.link_libcpp = true;
const exp_stdout =
\\Constructed: a
\\Constructed: b
\\About to destroy: b
\\About to destroy: a
\\Error: Not enough memory!
\\
;
const exe = addExecutable(b, opts, .{ .name = "main" });
exe.root_module.addObject(main_o);
exe.root_module.addObject(simple_string_o);
exe.root_module.addObject(simple_string_owner_o);
exe.root_module.link_libcpp = true;
const run = addRunArtifact(exe);
run.expectStdOutEqual(exp_stdout);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInSymtab();
check.checkContains("(was private external) ___gxx_personality_v0");
test_step.dependOn(&check.step);
return test_step;
}
fn testEhFramePointerEncodingSdata4(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "eh_frame-pointer-encoding-sdata4", opts);
const a_o = addObject(b, opts, .{ .name = "foo", .asm_source_bytes =
\\.global _foo
\\.align 2
\\_foo:
\\ mov w0, #100
\\ ret
\\LEND_foo:
\\
\\.section __TEXT,__gcc_except_tab
\\LLSDA_foo:
\\ .byte 0xff
\\ .byte 0xff
\\ .byte 0x01
\\ .uleb128 0
\\
\\.section __TEXT,__eh_frame,coalesced,no_toc+strip_static_syms+live_support
\\LCIE:
\\ .long LCIE_end - LCIE_start
\\LCIE_start:
\\ .long 0 ; CIE ID
\\ .byte 1 ; Version
\\ .asciz "zLR" ; Augmentation string
\\ .uleb128 1 ; Code alignment factor
\\ .sleb128 -8 ; Data alignment factor
\\ .byte 30 ; Return address register
\\ .uleb128 2 ; Augmentation data length
\\ .byte 0x1b ; LSDA pointer encoding (DW_EH_PE_pcrel | DW_EH_PE_sdata4)
\\ .byte 0x1b ; FDE pointer encoding (DW_EH_PE_pcrel | DW_EH_PE_sdata4)
\\ .byte 0x0c ; DW_CFA_def_cfa
\\ .uleb128 31 ; Reg 31
\\ .uleb128 0 ; Offset 0
\\ .align 3
\\LCIE_end:
\\LFDE:
\\ .long LFDE_end - LFDE_start
\\LFDE_start:
\\ .long LFDE_start - LCIE ; CIE pointer
\\ .long _foo - . ; PC begin
\\ .long LEND_foo - _foo ; PC range
\\ .uleb128 4 ; Augmentation data length
\\ .long LLSDA_foo - . ; LSDA pointer
\\ .align 3
\\LFDE_end:
});
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\int foo();
\\int main() {
\\ printf("%d\n", foo());
\\ return 0;
\\}
});
exe.root_module.addObject(a_o);
const run = addRunArtifact(exe);
run.expectStdOutEqual("100\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testUnwindInfoNoSubsectionsArm64(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "unwind-info-no-subsections-arm64", opts);
const a_o = addObject(b, opts, .{ .name = "a", .asm_source_bytes =
\\.globl _foo
\\.align 4
\\_foo:
\\ .cfi_startproc
\\ stp x29, x30, [sp, #-32]!
\\ .cfi_def_cfa_offset 32
\\ .cfi_offset w30, -24
\\ .cfi_offset w29, -32
\\ mov x29, sp
\\ .cfi_def_cfa w29, 32
\\ bl _bar
\\ ldp x29, x30, [sp], #32
\\ .cfi_restore w29
\\ .cfi_restore w30
\\ .cfi_def_cfa_offset 0
\\ ret
\\ .cfi_endproc
\\
\\.globl _bar
\\.align 4
\\_bar:
\\ .cfi_startproc
\\ sub sp, sp, #32
\\ .cfi_def_cfa_offset -32
\\ stp x29, x30, [sp, #16]
\\ .cfi_offset w30, -24
\\ .cfi_offset w29, -32
\\ mov x29, sp
\\ .cfi_def_cfa w29, 32
\\ mov w0, #4
\\ ldp x29, x30, [sp, #16]
\\ .cfi_restore w29
\\ .cfi_restore w30
\\ add sp, sp, #32
\\ .cfi_def_cfa_offset 0
\\ ret
\\ .cfi_endproc
});
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\int foo();
\\int main() {
\\ printf("%d\n", foo());
\\ return 0;
\\}
});
exe.root_module.addObject(a_o);
const run = addRunArtifact(exe);
run.expectStdOutEqual("4\n");
test_step.dependOn(&run.step);
return test_step;
}
fn testUnwindInfoNoSubsectionsX64(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "unwind-info-no-subsections-x64", opts);
const a_o = addObject(b, opts, .{ .name = "a", .asm_source_bytes =
\\.globl _foo
\\_foo:
\\ .cfi_startproc
\\ push %rbp
\\ .cfi_def_cfa_offset 8
\\ .cfi_offset %rbp, -8
\\ mov %rsp, %rbp
\\ .cfi_def_cfa_register %rbp
\\ call _bar
\\ pop %rbp
\\ .cfi_restore %rbp
\\ .cfi_def_cfa_offset 0
\\ ret
\\ .cfi_endproc
\\
\\.globl _bar
\\_bar:
\\ .cfi_startproc
\\ push %rbp
\\ .cfi_def_cfa_offset 8
\\ .cfi_offset %rbp, -8
\\ mov %rsp, %rbp
\\ .cfi_def_cfa_register %rbp
\\ mov $4, %rax
\\ pop %rbp
\\ .cfi_restore %rbp
\\ .cfi_def_cfa_offset 0
\\ ret
\\ .cfi_endproc
});
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\int foo();
\\int main() {
\\ printf("%d\n", foo());
\\ return 0;
\\}
});
exe.root_module.addObject(a_o);
const run = addRunArtifact(exe);
run.expectStdOutEqual("4\n");
test_step.dependOn(&run.step);
return test_step;
}
// Adapted from https://github.com/llvm/llvm-project/blob/main/lld/test/MachO/weak-binding.s
fn testWeakBind(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "weak-bind", opts);
const lib = addSharedLibrary(b, opts, .{ .name = "foo", .asm_source_bytes =
\\.globl _weak_dysym
\\.weak_definition _weak_dysym
\\_weak_dysym:
\\ .quad 0x1234
\\
\\.globl _weak_dysym_for_gotpcrel
\\.weak_definition _weak_dysym_for_gotpcrel
\\_weak_dysym_for_gotpcrel:
\\ .quad 0x1234
\\
\\.globl _weak_dysym_fn
\\.weak_definition _weak_dysym_fn
\\_weak_dysym_fn:
\\ ret
\\
\\.section __DATA,__thread_vars,thread_local_variables
\\
\\.globl _weak_dysym_tlv
\\.weak_definition _weak_dysym_tlv
\\_weak_dysym_tlv:
\\ .quad 0x1234
});
{
const check = lib.checkObject();
check.checkInExports();
check.checkExtract("[WEAK] {vmaddr1} _weak_dysym");
check.checkExtract("[WEAK] {vmaddr2} _weak_dysym_for_gotpcrel");
check.checkExtract("[WEAK] {vmaddr3} _weak_dysym_fn");
check.checkExtract("[THREAD_LOCAL, WEAK] {vmaddr4} _weak_dysym_tlv");
test_step.dependOn(&check.step);
}
const exe = addExecutable(b, opts, .{ .name = "main", .asm_source_bytes =
\\.globl _main, _weak_external, _weak_external_for_gotpcrel, _weak_external_fn
\\.weak_definition _weak_external, _weak_external_for_gotpcrel, _weak_external_fn, _weak_internal, _weak_internal_for_gotpcrel, _weak_internal_fn
\\
\\_main:
\\ mov _weak_dysym_for_gotpcrel@GOTPCREL(%rip), %rax
\\ mov _weak_external_for_gotpcrel@GOTPCREL(%rip), %rax
\\ mov _weak_internal_for_gotpcrel@GOTPCREL(%rip), %rax
\\ mov _weak_tlv@TLVP(%rip), %rax
\\ mov _weak_dysym_tlv@TLVP(%rip), %rax
\\ mov _weak_internal_tlv@TLVP(%rip), %rax
\\ callq _weak_dysym_fn
\\ callq _weak_external_fn
\\ callq _weak_internal_fn
\\ mov $0, %rax
\\ ret
\\
\\_weak_external:
\\ .quad 0x1234
\\
\\_weak_external_for_gotpcrel:
\\ .quad 0x1234
\\
\\_weak_external_fn:
\\ ret
\\
\\_weak_internal:
\\ .quad 0x1234
\\
\\_weak_internal_for_gotpcrel:
\\ .quad 0x1234
\\
\\_weak_internal_fn:
\\ ret
\\
\\.data
\\ .quad _weak_dysym
\\ .quad _weak_external + 2
\\ .quad _weak_internal
\\
\\.tbss _weak_tlv$tlv$init, 4, 2
\\.tbss _weak_internal_tlv$tlv$init, 4, 2
\\
\\.section __DATA,__thread_vars,thread_local_variables
\\.globl _weak_tlv
\\.weak_definition _weak_tlv, _weak_internal_tlv
\\
\\_weak_tlv:
\\ .quad __tlv_bootstrap
\\ .quad 0
\\ .quad _weak_tlv$tlv$init
\\
\\_weak_internal_tlv:
\\ .quad __tlv_bootstrap
\\ .quad 0
\\ .quad _weak_internal_tlv$tlv$init
});
exe.root_module.linkLibrary(lib);
{
const check = exe.checkObject();
check.checkInExports();
check.checkExtract("[WEAK] {vmaddr1} _weak_external");
check.checkExtract("[WEAK] {vmaddr2} _weak_external_for_gotpcrel");
check.checkExtract("[WEAK] {vmaddr3} _weak_external_fn");
check.checkExtract("[THREAD_LOCAL, WEAK] {vmaddr4} _weak_tlv");
check.checkInDyldBind();
check.checkContains("(libfoo.dylib) _weak_dysym_for_gotpcrel");
check.checkContains("(libfoo.dylib) _weak_dysym_fn");
check.checkContains("(libfoo.dylib) _weak_dysym");
check.checkContains("(libfoo.dylib) _weak_dysym_tlv");
check.checkInDyldWeakBind();
check.checkContains("_weak_external_for_gotpcrel");
check.checkContains("_weak_dysym_for_gotpcrel");
check.checkContains("_weak_external_fn");
check.checkContains("_weak_dysym_fn");
check.checkContains("_weak_dysym");
check.checkContains("_weak_external");
check.checkContains("_weak_tlv");
check.checkContains("_weak_dysym_tlv");
test_step.dependOn(&check.step);
}
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
return test_step;
}
fn testWeakFramework(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "weak-framework", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes = "int main() { return 0; }" });
exe.root_module.linkFramework("Cocoa", .{ .weak = true });
const run = addRunArtifact(exe);
run.expectExitCode(0);
test_step.dependOn(&run.step);
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd LOAD_WEAK_DYLIB");
check.checkContains("Cocoa");
test_step.dependOn(&check.step);
return test_step;
}
fn testWeakLibrary(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "weak-library", opts);
const dylib = addSharedLibrary(b, opts, .{ .name = "a", .c_source_bytes =
\\#include<stdio.h>
\\int a = 42;
\\const char* asStr() {
\\ static char str[3];
\\ sprintf(str, "%d", 42);
\\ return str;
\\}
});
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include<stdio.h>
\\extern int a;
\\extern const char* asStr();
\\int main() {
\\ printf("%d %s", a, asStr());
\\ return 0;
\\}
});
exe.root_module.linkSystemLibrary("a", .{ .weak = true });
exe.root_module.addLibraryPath(dylib.getEmittedBinDirectory());
exe.root_module.addRPath(dylib.getEmittedBinDirectory());
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd LOAD_WEAK_DYLIB");
check.checkContains("liba.dylib");
check.checkInSymtab();
check.checkExact("(undefined) weakref external _a (from liba)");
check.checkInSymtab();
check.checkExact("(undefined) weakref external _asStr (from liba)");
test_step.dependOn(&check.step);
const run = addRunArtifact(exe);
run.expectStdOutEqual("42 42");
test_step.dependOn(&run.step);
return test_step;
}
fn testWeakRef(b: *Build, opts: Options) *Step {
const test_step = addTestStep(b, "weak-ref", opts);
const exe = addExecutable(b, opts, .{ .name = "main", .c_source_bytes =
\\#include <stdio.h>
\\#include <sys/_types/_fd_def.h>
\\int main(int argc, char** argv) {
\\ printf("__darwin_check_fd_set_overflow: %p\n", __darwin_check_fd_set_overflow);
\\}
});
const check = exe.checkObject();
check.checkInSymtab();
check.checkExact("(undefined) weakref external ___darwin_check_fd_set_overflow (from libSystem.B)");
test_step.dependOn(&check.step);
return test_step;
}
fn addTestStep(b: *Build, comptime prefix: []const u8, opts: Options) *Step {
return link.addTestStep(b, "" ++ prefix, opts);
}
const builtin = @import("builtin");
const addAsmSourceBytes = link.addAsmSourceBytes;
const addCSourceBytes = link.addCSourceBytes;
const addRunArtifact = link.addRunArtifact;
const addObject = link.addObject;
const addExecutable = link.addExecutable;
const addStaticLibrary = link.addStaticLibrary;
const addSharedLibrary = link.addSharedLibrary;
const expectLinkErrors = link.expectLinkErrors;
const link = @import("link.zig");
const std = @import("std");
const Build = std.Build;
const BuildOptions = link.BuildOptions;
const Compile = Step.Compile;
const Options = link.Options;
const Step = Build.Step;
const WriteFile = Step.WriteFile;
@@ -1,163 +0,0 @@
const std = @import("std");
const builtin = @import("builtin");
const Build = std.Build;
const LazyPath = Build.LazyPath;
const Step = Build.Step;
const Run = Step.Run;
const WriteFile = Step.WriteFile;
pub fn build(b: *Build) void {
const nb_files = b.option(u32, "nb_files", "Number of c files to generate.") orelse 10;
const test_step = b.step("test", "Test it");
b.default_step = test_step;
// generate c files
const files = b.allocator.alloc(LazyPath, nb_files) catch unreachable;
defer b.allocator.free(files);
{
for (files[0 .. nb_files - 1], 1..nb_files) |*file, i| {
const wf = WriteFile.create(b);
file.* = wf.add(b.fmt("src_{}.c", .{i}), b.fmt(
\\extern int foo_0();
\\extern int bar_{}();
\\extern int one_{};
\\int one_{} = 1;
\\int foo_{}() {{ return one_{} + foo_0(); }}
\\int bar_{}() {{ return bar_{}(); }}
, .{ i - 1, i - 1, i, i, i - 1, i, i - 1 }));
}
{
const wf = WriteFile.create(b);
files[nb_files - 1] = wf.add("src_last.c", b.fmt(
\\extern int foo_0();
\\extern int bar_{}();
\\extern int one_{};
\\int foo_last() {{ return one_{} + foo_0(); }}
\\int bar_last() {{ return bar_{}(); }}
, .{ nb_files - 1, nb_files - 1, nb_files - 1, nb_files - 1 }));
}
}
add(b, test_step, files, .Debug);
add(b, test_step, files, .ReleaseSafe);
add(b, test_step, files, .ReleaseSmall);
add(b, test_step, files, .ReleaseFast);
}
fn add(b: *Build, test_step: *Step, files: []const LazyPath, optimize: std.builtin.OptimizeMode) void {
const flags = [_][]const u8{
"-Wall",
"-std=c11",
};
// all files at once
{
const exe = b.addExecutable(.{
.name = "test1",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.optimize = optimize,
.target = b.graph.host,
}),
});
for (files) |file| {
exe.root_module.addCSourceFile(.{ .file = file, .flags = &flags });
}
const run_cmd = b.addRunArtifact(exe);
run_cmd.skip_foreign_checks = true;
run_cmd.expectExitCode(0);
test_step.dependOn(&run_cmd.step);
}
// using static librairies
{
const mod_a = b.createModule(.{ .target = b.graph.host, .optimize = optimize });
const mod_b = b.createModule(.{ .target = b.graph.host, .optimize = optimize });
for (files, 1..) |file, i| {
const mod = if (i & 1 == 0) mod_a else mod_b;
mod.addCSourceFile(.{ .file = file, .flags = &flags });
}
const lib_a = b.addLibrary(.{
.linkage = .static,
.name = "test2_a",
.root_module = mod_a,
});
const lib_b = b.addLibrary(.{
.linkage = .static,
.name = "test2_b",
.root_module = mod_b,
});
const exe = b.addExecutable(.{
.name = "test2",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.target = b.graph.host,
.optimize = optimize,
}),
});
exe.root_module.linkLibrary(lib_a);
exe.root_module.linkLibrary(lib_b);
const run_cmd = b.addRunArtifact(exe);
run_cmd.skip_foreign_checks = true;
run_cmd.expectExitCode(0);
test_step.dependOn(&run_cmd.step);
}
// using static librairies and object files
{
const mod_a = b.createModule(.{ .target = b.graph.host, .optimize = optimize });
const mod_b = b.createModule(.{ .target = b.graph.host, .optimize = optimize });
for (files, 1..) |file, i| {
const obj_mod = b.createModule(.{ .target = b.graph.host, .optimize = optimize });
obj_mod.addCSourceFile(.{ .file = file, .flags = &flags });
const obj = b.addObject(.{
.name = b.fmt("obj_{}", .{i}),
.root_module = obj_mod,
});
const lib_mod = if (i & 1 == 0) mod_a else mod_b;
lib_mod.addObject(obj);
}
const lib_a = b.addLibrary(.{
.linkage = .static,
.name = "test3_a",
.root_module = mod_a,
});
const lib_b = b.addLibrary(.{
.linkage = .static,
.name = "test3_b",
.root_module = mod_b,
});
const exe = b.addExecutable(.{
.name = "test3",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.target = b.graph.host,
.optimize = optimize,
}),
});
exe.root_module.linkLibrary(lib_a);
exe.root_module.linkLibrary(lib_b);
const run_cmd = b.addRunArtifact(exe);
run_cmd.skip_foreign_checks = true;
run_cmd.expectExitCode(0);
test_step.dependOn(&run_cmd.step);
}
}
@@ -1,20 +0,0 @@
const std = @import("std");
extern fn foo_last() i32;
extern fn bar_last() i32;
export const one_0: i32 = 1;
export fn foo_0() i32 {
return 1234;
}
export fn bar_0() i32 {
return 5678;
}
pub fn main() anyerror!void {
const foo_expected: i32 = 1 + 1234;
const bar_expected: i32 = 5678;
try std.testing.expectEqual(foo_expected, foo_last());
try std.testing.expectEqual(bar_expected, bar_last());
}
-36
View File
@@ -1,36 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
// The code in question will pull-in compiler-rt,
// and therefore link with its archive file.
const lib = b.addExecutable(.{
.name = "main",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.optimize = optimize,
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
.strip = false,
}),
});
lib.entry = .disabled;
lib.use_llvm = false;
lib.use_lld = false;
lib.root_module.export_symbol_names = &.{"foo"};
const check = lib.checkObject();
check.checkInHeaders();
check.checkExact("Section custom");
check.checkExact("name __trunch"); // Ensure it was imported and resolved
test_step.dependOn(&check.step);
}
-7
View File
@@ -1,7 +0,0 @@
export fn foo() void {
var a: f16 = 2.2;
_ = &a;
// this will pull-in compiler-rt
const b = @trunc(a);
_ = b;
}
-32
View File
@@ -1,32 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
// Library with explicitly set cpu features
const lib = b.addExecutable(.{
.name = "lib",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.optimize = .Debug,
.target = b.resolveTargetQuery(.{
.cpu_arch = .wasm32,
.cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp },
.cpu_features_add = std.Target.wasm.featureSet(&.{.atomics}),
.os_tag = .freestanding,
}),
}),
});
lib.entry = .disabled;
lib.use_llvm = false;
lib.use_lld = false;
// Verify the result contains the features explicitly set on the target for the library.
const check = lib.checkObject();
check.checkInHeaders();
check.checkExact("name target_features");
check.checkExact("features 1");
check.checkExact("+ atomics");
const test_step = b.step("test", "Run linker test");
test_step.dependOn(&check.step);
b.default_step = test_step;
}
-1
View File
@@ -1 +0,0 @@
export fn foo() void {}
-30
View File
@@ -1,30 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test");
b.default_step = test_step;
const lib = b.addExecutable(.{
.name = "lib",
.root_module = b.createModule(.{
.root_source_file = b.path("lib.zig"),
.optimize = .Debug,
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
}),
});
lib.entry = .disabled;
// Disabled to work around the Wasm linker crashing.
// Can be reproduced by commenting out the line below.
lib.bundle_ubsan_rt = false;
lib.use_lld = false;
lib.root_module.export_symbol_names = &.{ "foo", "bar" };
// Object being linked has neither functions nor globals named "foo" or "bar" and
// so these names correctly fail to be exported when creating an executable.
lib.expect_errors = .{ .exact = &.{
"error: manually specified export name 'foo' undefined",
"error: manually specified export name 'bar' undefined",
} };
_ = lib.getEmittedBin();
test_step.dependOn(&lib.step);
}
-2
View File
@@ -1,2 +0,0 @@
export const foo: u32 = 0xbbbbbbbb;
export const bar: u32 = 0xbbbbbbbb;
-79
View File
@@ -1,79 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const no_export = b.addExecutable(.{
.name = "no-export",
.root_module = b.createModule(.{
.root_source_file = b.path("main-hidden.zig"),
.optimize = optimize,
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
}),
});
no_export.entry = .disabled;
no_export.use_llvm = false;
no_export.use_lld = false;
// Don't pull in ubsan, since we're just expecting a very minimal executable.
no_export.bundle_ubsan_rt = false;
const dynamic_export = b.addExecutable(.{
.name = "dynamic",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.optimize = optimize,
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
}),
});
dynamic_export.entry = .disabled;
dynamic_export.rdynamic = true;
dynamic_export.use_llvm = false;
dynamic_export.use_lld = false;
// Don't pull in ubsan, since we're just expecting a very minimal executable.
dynamic_export.bundle_ubsan_rt = false;
const force_export = b.addExecutable(.{
.name = "force",
.root_module = b.createModule(.{
.root_source_file = b.path("main-hidden.zig"),
.optimize = optimize,
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
}),
});
force_export.entry = .disabled;
force_export.root_module.export_symbol_names = &.{"foo"};
force_export.use_llvm = false;
force_export.use_lld = false;
// Don't pull in ubsan, since we're just expecting a very minimal executable.
force_export.bundle_ubsan_rt = false;
const check_no_export = no_export.checkObject();
check_no_export.checkInHeaders();
check_no_export.checkExact("Section export");
check_no_export.checkExact("entries 1");
check_no_export.checkExact("name memory");
check_no_export.checkExact("kind memory");
const check_dynamic_export = dynamic_export.checkObject();
check_dynamic_export.checkInHeaders();
check_dynamic_export.checkExact("Section export");
check_dynamic_export.checkExact("entries 2");
check_dynamic_export.checkExact("name foo");
check_dynamic_export.checkExact("kind function");
const check_force_export = force_export.checkObject();
check_force_export.checkInHeaders();
check_force_export.checkExact("Section export");
check_force_export.checkExact("entries 2");
check_force_export.checkExact("name foo");
check_force_export.checkExact("kind function");
test_step.dependOn(&check_no_export.step);
test_step.dependOn(&check_dynamic_export.step);
test_step.dependOn(&check_force_export.step);
}
-4
View File
@@ -1,4 +0,0 @@
fn foo() callconv(.c) void {}
comptime {
@export(&foo, .{ .name = "foo", .visibility = .hidden });
}
-1
View File
@@ -1 +0,0 @@
export fn foo() void {}
-1
View File
@@ -1 +0,0 @@
pub extern "a" fn hello() i32;
-1
View File
@@ -1 +0,0 @@
pub extern "b" fn hello() i32;
-36
View File
@@ -1,36 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const lib = b.addExecutable(.{
.name = "lib",
.root_module = b.createModule(.{
.root_source_file = b.path("lib.zig"),
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
.optimize = optimize,
}),
});
lib.entry = .disabled;
lib.import_symbols = true; // import `a` and `b`
lib.rdynamic = true; // export `foo`
const check_lib = lib.checkObject();
check_lib.checkInHeaders();
check_lib.checkExact("Section import");
check_lib.checkExact("entries 2"); // a.hello & b.hello
check_lib.checkExact("module a");
check_lib.checkExact("name hello");
check_lib.checkExact("module b");
check_lib.checkExact("name hello");
test_step.dependOn(&check_lib.step);
}
-6
View File
@@ -1,6 +0,0 @@
const a = @import("a.zig").hello;
const b = @import("b.zig").hello;
export fn foo() void {
_ = a();
_ = b();
}
-28
View File
@@ -1,28 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const exe = b.addExecutable(.{
.name = "extern",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.optimize = optimize,
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .wasi }),
}),
});
exe.root_module.addCSourceFile(.{ .file = b.path("foo.c"), .flags = &.{} });
exe.use_llvm = false;
exe.use_lld = false;
const run = b.addRunArtifact(exe);
run.skip_foreign_checks = true;
run.expectStdOutEqual("Result: 30");
test_step.dependOn(&run.step);
}
-1
View File
@@ -1 +0,0 @@
int foo = 30;
-8
View File
@@ -1,8 +0,0 @@
const std = @import("std");
extern const foo: u32;
pub fn main() void {
var stdout_writer = std.Io.File.stdout().writerStreaming(std.Options.debug_io, &.{});
stdout_writer.interface.print("Result: {d}", .{foo}) catch {};
}
-67
View File
@@ -1,67 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const export_table = b.addExecutable(.{
.name = "export_table",
.root_module = b.createModule(.{
.root_source_file = b.path("lib.zig"),
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
.optimize = optimize,
}),
});
export_table.entry = .disabled;
export_table.use_llvm = false;
export_table.use_lld = false;
export_table.export_table = true;
export_table.link_gc_sections = false;
// Don't pull in ubsan, since we're just expecting a very minimal executable.
export_table.bundle_ubsan_rt = false;
const regular_table = b.addExecutable(.{
.name = "regular_table",
.root_module = b.createModule(.{
.root_source_file = b.path("lib.zig"),
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
.optimize = optimize,
}),
});
regular_table.entry = .disabled;
regular_table.use_llvm = false;
regular_table.use_lld = false;
regular_table.link_gc_sections = false; // Ensure function table is not empty
// Don't pull in ubsan, since we're just expecting a very minimal executable.
regular_table.bundle_ubsan_rt = false;
const check_export = export_table.checkObject();
const check_regular = regular_table.checkObject();
check_export.checkInHeaders();
check_export.checkExact("Section export");
check_export.checkExact("entries 3");
check_export.checkExact("name __indirect_function_table"); // as per linker specification
check_export.checkExact("kind table");
check_regular.checkInHeaders();
check_regular.checkExact("Section table");
check_regular.checkExact("entries 1");
check_regular.checkExact("type funcref");
check_regular.checkExact("min 2"); // index starts at 1 & 1 function pointer = 2.
check_regular.checkExact("max 2");
check_regular.checkInHeaders();
check_regular.checkExact("Section element");
check_regular.checkExact("entries 1");
check_regular.checkExact("table index 0");
check_regular.checkExact("i32.const 1"); // we want to start function indexes at 1
check_regular.checkExact("indexes 1"); // 1 function pointer
test_step.dependOn(&check_export.step);
test_step.dependOn(&check_regular.step);
}
-7
View File
@@ -1,7 +0,0 @@
var func: *const fn () void = &bar;
export fn foo() void {
func();
}
fn bar() void {}
-44
View File
@@ -1,44 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
// Wasm Object file which we will use to infer the features from
const c_obj = b.addObject(.{
.name = "c_obj",
.root_module = b.createModule(.{
.root_source_file = null,
.optimize = .Debug,
.target = b.resolveTargetQuery(.{
.cpu_arch = .wasm32,
.cpu_model = .{ .explicit = &std.Target.wasm.cpu.bleeding_edge },
.os_tag = .freestanding,
}),
}),
});
c_obj.root_module.addCSourceFile(.{ .file = b.path("foo.c"), .flags = &.{} });
// Wasm library that doesn't have any features specified. This will
// infer its featureset from other linked object files.
const lib = b.addExecutable(.{
.name = "lib",
.root_module = b.createModule(.{
.root_source_file = b.path("main.zig"),
.optimize = .Debug,
.target = b.resolveTargetQuery(.{
.cpu_arch = .wasm32,
.cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp },
.os_tag = .freestanding,
}),
}),
});
lib.entry = .disabled;
lib.use_llvm = false;
lib.use_lld = false;
lib.root_module.addObject(c_obj);
lib.expect_errors = .{ .contains = "error: object requires atomics but specified target features exclude atomics" };
_ = lib.getEmittedBin();
const test_step = b.step("test", "Run linker test");
test_step.dependOn(&lib.step);
b.default_step = test_step;
}
-3
View File
@@ -1,3 +0,0 @@
int foo() {
return 5;
}
-1
View File
@@ -1 +0,0 @@
extern fn foo() c_int;
-45
View File
@@ -1,45 +0,0 @@
const std = @import("std");
const builtin = @import("builtin");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const lib = b.addExecutable(.{
.name = "lib",
.root_module = b.createModule(.{
.root_source_file = b.path("lib.zig"),
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
.optimize = optimize,
.strip = false,
}),
});
lib.entry = .disabled;
lib.use_llvm = false;
lib.use_lld = false;
b.installArtifact(lib);
const version_fmt = "version " ++ builtin.zig_version_string;
const check_lib = lib.checkObject();
check_lib.checkInHeaders();
check_lib.checkExact("name producers");
check_lib.checkExact("fields 2");
check_lib.checkExact("field_name language");
check_lib.checkExact("values 1");
check_lib.checkExact("value_name Zig");
check_lib.checkExact(version_fmt);
check_lib.checkExact("field_name processed-by");
check_lib.checkExact("values 1");
check_lib.checkExact("value_name Zig");
check_lib.checkExact(version_fmt);
test_step.dependOn(&check_lib.step);
}
-1
View File
@@ -1 +0,0 @@
export fn foo() void {}
-99
View File
@@ -1,99 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize_mode: std.builtin.OptimizeMode) void {
const exe = b.addExecutable(.{
.name = "lib",
.root_module = b.createModule(.{
.root_source_file = b.path("lib.zig"),
.target = b.resolveTargetQuery(.{
.cpu_arch = .wasm32,
.cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp },
.cpu_features_add = std.Target.wasm.featureSet(&.{ .atomics, .bulk_memory }),
.os_tag = .freestanding,
}),
.optimize = optimize_mode,
.strip = false,
.single_threaded = false,
}),
});
exe.entry = .disabled;
exe.use_lld = false;
exe.import_memory = true;
exe.export_memory = true;
exe.shared_memory = true;
exe.max_memory = 67108864;
exe.root_module.export_symbol_names = &.{"foo"};
// Don't pull in ubsan, since we're just expecting a very minimal executable.
exe.bundle_ubsan_rt = false;
const check_exe = exe.checkObject();
check_exe.checkInHeaders();
check_exe.checkExact("Section import");
check_exe.checkExact("entries 1");
check_exe.checkExact("module env");
check_exe.checkExact("name memory"); // ensure we are importing memory
check_exe.checkInHeaders();
check_exe.checkExact("Section export");
check_exe.checkExact("entries 2");
check_exe.checkExact("name foo");
check_exe.checkExact("name memory"); // ensure we also export memory again
// This section *must* be emit as the start function is set to the index
// of __wasm_init_memory
// release modes will have the TLS segment optimized out in our test-case.
// This means we won't have __wasm_init_memory in such case, and therefore
// should also not have a section "start"
if (optimize_mode == .Debug) {
check_exe.checkInHeaders();
check_exe.checkExact("Section start");
}
// This section is only and *must* be emit when shared-memory is enabled
// release modes will have the TLS segment optimized out in our test-case.
if (optimize_mode == .Debug) {
check_exe.checkInHeaders();
check_exe.checkExact("Section data_count");
check_exe.checkExact("count 1");
}
check_exe.checkInHeaders();
check_exe.checkExact("Section custom");
check_exe.checkExact("name name");
check_exe.checkExact("type function");
if (optimize_mode == .Debug) {
check_exe.checkExact("name __wasm_init_memory");
check_exe.checkExact("name __wasm_init_tls");
}
check_exe.checkExact("type global");
// In debug mode the symbol __tls_base is resolved to an undefined symbol
// from the object file, hence its placement differs than in release modes
// where the entire tls segment is optimized away, and tls_base will have
// its original position.
if (optimize_mode == .Debug) {
check_exe.checkExact("name __tls_base");
check_exe.checkExact("name __tls_size");
check_exe.checkExact("name __tls_align");
check_exe.checkExact("type data_segment");
check_exe.checkExact("names 1");
check_exe.checkExact("index 0");
check_exe.checkExact("name .tdata");
} else {
check_exe.checkNotPresent("name __tls_base");
check_exe.checkNotPresent("name __tls_size");
check_exe.checkNotPresent("name __tls_align");
}
test_step.dependOn(&check_exe.step);
}
-5
View File
@@ -1,5 +0,0 @@
threadlocal var some_tls_global: u32 = 1;
export fn foo() void {
some_tls_global = 2;
}
-55
View File
@@ -1,55 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
add(b, test_step, .ReleaseFast);
add(b, test_step, .ReleaseSmall);
add(b, test_step, .ReleaseSafe);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const lib = b.addExecutable(.{
.name = "lib",
.root_module = b.createModule(.{
.root_source_file = b.path("lib.zig"),
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
.optimize = optimize,
.strip = false,
}),
});
lib.entry = .disabled;
lib.use_llvm = false;
lib.use_lld = false;
lib.stack_size = std.wasm.page_size * 2; // set an explicit stack size
lib.link_gc_sections = false;
b.installArtifact(lib);
const check_lib = lib.checkObject();
// ensure global exists and its initial value is equal to explitic stack size
check_lib.checkInHeaders();
check_lib.checkExact("Section global");
check_lib.checkExact("entries 1");
check_lib.checkExact("type i32"); // on wasm32 the stack pointer must be i32
check_lib.checkExact("mutable true"); // must be able to mutate the stack pointer
check_lib.checkExtract("i32.const {stack_pointer}");
check_lib.checkComputeCompare("stack_pointer", .{ .op = .eq, .value = .{ .literal = lib.stack_size.? } });
// validate memory section starts after virtual stack
check_lib.checkInHeaders();
check_lib.checkExact("Section data");
check_lib.checkExtract("i32.const {data_start}");
check_lib.checkComputeCompare("data_start", .{ .op = .eq, .value = .{ .variable = "stack_pointer" } });
// validate the name of the stack pointer
check_lib.checkInHeaders();
check_lib.checkExact("Section custom");
check_lib.checkExact("type global");
check_lib.checkExact("names 1");
check_lib.checkExact("index 0");
check_lib.checkExact("name __stack_pointer");
test_step.dependOn(&check_lib.step);
}
-1
View File
@@ -1 +0,0 @@
export fn foo() void {}
-43
View File
@@ -1,43 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
add(b, test_step, .Debug);
}
fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
const exe = b.addExecutable(.{
.name = "lib",
.root_module = b.createModule(.{
.root_source_file = b.path("lib.zig"),
.target = b.resolveTargetQuery(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }),
.optimize = optimize,
.strip = false,
}),
});
exe.entry = .disabled;
exe.use_llvm = false;
exe.use_lld = false;
exe.root_module.export_symbol_names = &.{"foo"};
// Don't pull in ubsan, since we're just expecting a very minimal executable.
exe.bundle_ubsan_rt = false;
b.installArtifact(exe);
const check_exe = exe.checkObject();
check_exe.checkInHeaders();
check_exe.checkExact("Section type");
// only 2 entries, although we have more functions.
// This is to test functions with the same function signature
// have their types deduplicated.
check_exe.checkExact("entries 2");
check_exe.checkExact("params 1");
check_exe.checkExact("type i32");
check_exe.checkExact("returns 1");
check_exe.checkExact("type i64");
check_exe.checkExact("params 0");
check_exe.checkExact("returns 0");
test_step.dependOn(&check_exe.step);
}
-10
View File
@@ -1,10 +0,0 @@
export fn foo(x: u32) u64 {
return bar(x);
}
fn bar(x: u32) u64 {
y();
return x;
}
fn y() void {}
@@ -25,10 +25,4 @@ pub fn build(b: *std.Build) void {
});
exe.link_gc_sections = false;
exe.bundle_compiler_rt = true;
// Verify compiler_rt hasn't pulled in any debug handlers
const check_exe = exe.checkObject();
check_exe.checkInSymtab();
check_exe.checkNotPresent("debug.readElfDebugInfo");
test_step.dependOn(&check_exe.step);
}
-121
View File
@@ -88,69 +88,6 @@ pub fn build(b: *std.Build) void {
test_step.dependOn(&run_cmd.step);
}
}
const check = exe.checkObject();
// __errno_location is always a dynamically linked symbol
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __errno_location");
// before v2.32 fstat redirects through __fxstat, afterwards its a
// normal dynamic symbol
check.checkInDynamicSymtab();
if (glibc_ver.order(.{ .major = 2, .minor = 32, .patch = 0 }) == .lt) {
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __fxstat");
check.checkInSymtab();
check.checkContains("FUNC LOCAL HIDDEN fstat");
} else {
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT fstat");
check.checkInSymtab();
check.checkNotPresent("__fxstat");
}
// before v2.26 reallocarray is not supported
check.checkInDynamicSymtab();
if (glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) {
check.checkNotPresent("reallocarray");
} else {
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT reallocarray");
}
// before v2.38 strlcpy is not supported
check.checkInDynamicSymtab();
if (glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) {
check.checkNotPresent("strlcpy");
} else {
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT strlcpy");
}
// v2.16 introduced getauxval()
check.checkInDynamicSymtab();
if (glibc_ver.order(.{ .major = 2, .minor = 16, .patch = 0 }) == .lt) {
check.checkNotPresent("getauxval");
} else {
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT getauxval");
}
// Always have dynamic "exit", "pow", and "powf" references
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT exit");
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT pow");
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT powf");
if (target.result.cpu.arch != .s390x) {
// An atexit local symbol is defined, and depends on undefined dynamic
// __cxa_atexit.
check.checkInSymtab();
check.checkContains("FUNC LOCAL HIDDEN atexit");
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __cxa_atexit");
}
test_step.dependOn(&check.step);
}
// Build & run a Zig test case against a sampling of supported glibc versions
@@ -236,63 +173,5 @@ pub fn build(b: *std.Build) void {
test_step.dependOn(&run_cmd.step);
}
}
const check = exe.checkObject();
// __errno_location is always a dynamically linked symbol
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __errno_location");
// before v2.32 fstatat redirects through __fxstatat, afterwards its a
// normal dynamic symbol
if (glibc_ver.order(.{ .major = 2, .minor = 32, .patch = 0 }) == .lt) {
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __fxstatat");
check.checkInSymtab();
check.checkContains("FUNC LOCAL HIDDEN fstatat");
} else {
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT fstatat");
check.checkInSymtab();
check.checkNotPresent("FUNC LOCAL HIDDEN fstatat");
}
// before v2.26 reallocarray is not supported
if (glibc_ver.order(.{ .major = 2, .minor = 26, .patch = 0 }) == .lt) {
check.checkInDynamicSymtab();
check.checkNotPresent("reallocarray");
} else {
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT reallocarray");
}
// before v2.38 strlcpy is not supported
if (glibc_ver.order(.{ .major = 2, .minor = 38, .patch = 0 }) == .lt) {
check.checkInDynamicSymtab();
check.checkNotPresent("strlcpy");
} else {
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT strlcpy");
}
// v2.16 introduced getauxval(), so always present
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT getauxval");
// Always have a dynamic "exit" reference
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT exit");
if (target.result.cpu.arch != .s390x) {
// An atexit local symbol is defined, and depends on undefined dynamic
// __cxa_atexit.
check.checkInSymtab();
check.checkContains("FUNC LOCAL HIDDEN atexit");
check.checkInDynamicSymtab();
check.checkExact("0 0 UND FUNC GLOBAL DEFAULT __cxa_atexit");
}
test_step.dependOn(&check.step);
}
}
-6
View File
@@ -37,10 +37,4 @@ pub fn build(b: *std.Build) void {
exe.root_module.addCSourceFile(.{ .file = b.path("main.m"), .flags = &.{} });
exe.root_module.linkFramework("Foundation", .{});
exe.root_module.linkFramework("UIKit", .{});
const check = exe.checkObject();
check.checkInHeaders();
check.checkExact("cmd BUILD_VERSION");
check.checkExact("platform IOS");
test_step.dependOn(&check.step);
}
-21
View File
@@ -2204,27 +2204,6 @@ pub fn addStandaloneTests(
return step;
}
pub fn addLinkTests(
b: *std.Build,
enable_macos_sdk: bool,
enable_ios_sdk: bool,
enable_symlinks_windows: bool,
) *Step {
const step = b.step("test-link", "Run the linker tests");
if (compilerHasPackageManager(b)) {
const test_cases_dep_name = "link_test_cases";
const test_cases_dep = b.dependency(test_cases_dep_name, .{
.enable_ios_sdk = enable_ios_sdk,
.enable_macos_sdk = enable_macos_sdk,
.enable_symlinks_windows = enable_symlinks_windows,
});
const test_cases_dep_step = test_cases_dep.builder.default_step;
test_cases_dep_step.name = b.dupe(test_cases_dep_name);
step.dependOn(test_cases_dep.builder.default_step);
}
return step;
}
pub fn addCliTests(b: *std.Build) *Step {
const step = b.step("test-cli", "Test the command line interface");
const s = std.fs.path.sep_str;