commit cd3288a3f6e48ee381196a6d6437309472c513fe (tree)
parent 914b8f26b03944ac58900aefb3ab21fc988e6c7e
Author: Andrew Kelley <andrew@ziglang.org>
Date: Thu, 25 Jun 2026 19:20:51 +0200
Merge pull request 'Coff linker enhancements, new linker testing framework, and COFF objdump implementation' (#35674) from kcbanner/zig:coff_linker_wip into master
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/35674
Reviewed-by: Jacob Young <jacobly@ziglang.org>
Reviewed-by: Andrew Kelley <andrew@ziglang.org>
Diffstat:
41 files changed, 9753 insertions(+), 845 deletions(-)
diff --git a/build.zig b/build.zig
@@ -256,7 +256,6 @@ pub fn build(b: *std.Build) !void {
const is_debug = optimize == .Debug;
const enable_debug_extensions = b.option(bool, "debug-extensions", "Enable commands and options useful for debugging the compiler") orelse is_debug;
const enable_logging = b.option(bool, "log", "Enable debug logging with --debug-log") orelse is_debug;
- const enable_link_snapshots = b.option(bool, "link-snapshot", "Whether to enable linker state snapshots") orelse false;
const opt_version_string = b.option([]const u8, "version-string", "Override Zig version string. Default is to find out with git.");
const version_slice = if (opt_version_string) |version| version else v: {
@@ -372,7 +371,6 @@ pub fn build(b: *std.Build) !void {
exe_options.addOption(bool, "enable_debug_extensions", enable_debug_extensions);
exe_options.addOption(bool, "enable_logging", enable_logging);
- exe_options.addOption(bool, "enable_link_snapshots", enable_link_snapshots);
exe_options.addOption(bool, "enable_tracy", tracy != null);
exe_options.addOption(bool, "enable_tracy_callstack", tracy_callstack);
exe_options.addOption(bool, "enable_tracy_allocation", tracy_allocation);
@@ -629,6 +627,15 @@ pub fn build(b: *std.Build) !void {
.skip_llvm = skip_llvm,
.max_rss = 3_300_000_000,
}));
+ test_step.dependOn(tests.addLinkTests(b, .{
+ .test_target_filters = test_target_filters,
+ .test_filters = test_filters,
+ .optimize_modes = optimize_modes,
+ .skip_non_native = skip_non_native,
+ .skip_windows = skip_windows,
+ .skip_llvm = skip_llvm,
+ .max_rss = 100_000_000,
+ }));
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));
@@ -724,7 +731,6 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
exe_options.addOption(std.SemanticVersion, "semver", semver);
exe_options.addOption(bool, "enable_debug_extensions", false);
exe_options.addOption(bool, "enable_logging", false);
- exe_options.addOption(bool, "enable_link_snapshots", false);
exe_options.addOption(bool, "enable_tracy", false);
exe_options.addOption(bool, "enable_tracy_callstack", false);
exe_options.addOption(bool, "enable_tracy_allocation", false);
diff --git a/lib/compiler/Maker/Step/Run.zig b/lib/compiler/Maker/Step/Run.zig
@@ -2118,6 +2118,67 @@ fn runCommand(
});
}
}
+ const snapshots: []const ?struct {
+ path: Cache.Path,
+ result: enum { stderr, stdout },
+ } = &.{
+ if (conf_run.expect_stderr_snapshot.value) |path| .{
+ .path = try maker.resolveLazyPathIndex(arena, path, run_index),
+ .result = .stderr,
+ } else null,
+ if (conf_run.expect_stdout_snapshot.value) |path| .{
+ .path = try maker.resolveLazyPathIndex(arena, path, run_index),
+ .result = .stdout,
+ } else null,
+ };
+ for (snapshots) |opt_snapshot| {
+ const snapshot = opt_snapshot orelse continue;
+
+ const file = snapshot.path.root_dir.handle.openFile(io, snapshot.path.sub_path, .{}) catch |err|
+ return step.fail(maker, "unable to open snapshot file {f}: {t}", .{ snapshot.path, err });
+ defer file.close(io);
+
+ var file_reader = file.reader(io, &.{});
+ const snapshot_contents = file_reader.interface.allocRemaining(gpa, .unlimited) catch |err|
+ return step.fail(maker, "unable to read snapshot file {f}: {t}", .{ snapshot.path, err });
+ defer gpa.free(snapshot_contents);
+
+ const result = switch (snapshot.result) {
+ .stderr => generic_result.stderr.?,
+ .stdout => generic_result.stdout.?,
+ };
+ if (std.mem.findDiff(u8, snapshot_contents, result)) |diff_index| {
+ var diff_line_number: usize = 1;
+
+ for (snapshot_contents[0..diff_index]) |value| {
+ if (value == '\n') diff_line_number += 1;
+ }
+
+ return step.fail(maker,
+ \\
+ \\========= snapshot file: =========
+ \\{f}
+ \\========= contained: =============
+ \\{s}
+ \\========= {t} output was: ========
+ \\{s}
+ \\==================================
+ \\first difference on line {d}:
+ \\expected:
+ \\{f}
+ \\found:
+ \\{f}
+ , .{
+ snapshot.path,
+ snapshot_contents,
+ snapshot.result,
+ result,
+ diff_line_number,
+ fmtSnapshotIndicatorLine(snapshot_contents, diff_index),
+ fmtSnapshotIndicatorLine(result, diff_index),
+ });
+ }
+ }
},
else => {
// On failure, report captured stderr like normal standard error output.
@@ -2131,6 +2192,38 @@ fn runCommand(
}
}
+const FmtIndicatorLine = struct {
+ buf: []const u8,
+ index: usize,
+};
+
+fn fmtSnapshotIndicatorLine(buf: []const u8, index: usize) std.fmt.Alt(
+ FmtIndicatorLine,
+ snapshotIndicatorLine,
+) {
+ return .{ .data = .{ .buf = buf, .index = index } };
+}
+
+fn snapshotIndicatorLine(line: FmtIndicatorLine, w: *std.Io.Writer) std.Io.Writer.Error!void {
+ const line_begin_index = if (std.mem.lastIndexOfScalar(u8, line.buf[0..line.index], '\n')) |line_begin|
+ line_begin + 1
+ else
+ 0;
+ const line_end_index = if (std.mem.findScalar(u8, line.buf[line.index..], '\n')) |line_end|
+ (line.index + line_end)
+ else
+ line.buf.len;
+
+ try w.writeAll(line.buf[line_begin_index..line_end_index]);
+ try w.writeByte('\n');
+ try w.splatByteAll(' ', line_end_index - line_begin_index);
+ try w.writeByte('\n');
+ if (line.index >= line.buf.len)
+ try w.writeAll("^ (end of file)")
+ else
+ try w.print("^ ('\\x{x:0>2}')\n", .{line.buf[line.index]});
+}
+
const EvalGenericResult = struct {
term: process.Child.Term,
stdout: ?[]const u8,
@@ -2301,11 +2394,15 @@ fn setColorEnvironmentVariables(
}
fn checksContainStdout(conf_run: *const Configuration.Step.Run) bool {
- return conf_run.expect_stdout_exact.value != null or conf_run.expect_stdout_match.slice.len != 0;
+ return conf_run.expect_stdout_exact.value != null or
+ conf_run.expect_stdout_match.slice.len != 0 or
+ conf_run.expect_stdout_snapshot.value != null;
}
fn checksContainStderr(conf_run: *const Configuration.Step.Run) bool {
- return conf_run.expect_stderr_exact.value != null or conf_run.expect_stderr_match.slice.len != 0;
+ return conf_run.expect_stderr_exact.value != null or
+ conf_run.expect_stderr_match.slice.len != 0 or
+ conf_run.expect_stderr_snapshot.value != null;
}
/// If `path` is absolute, return it unchanged. If `make_absolute` is true, make it absolute.
diff --git a/lib/compiler/configurer.zig b/lib/compiler/configurer.zig
@@ -1011,6 +1011,8 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {
status: Configuration.Step.Run.ExpectTermStatus,
value: u32,
} = null;
+ var expect_stderr_snapshot: ?Configuration.LazyPath.Index = null;
+ var expect_stdout_snapshot: ?Configuration.LazyPath.Index = null;
switch (run.stdio) {
.check => |checks| for (checks.items) |check| switch (check) {
.expect_stderr_exact => |bytes| expect_stderr_exact = try wc.addBytes(bytes),
@@ -1027,6 +1029,8 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {
.stopped => |x| .{ .status = .stopped, .value = @intFromEnum(x) },
.unknown => |x| .{ .status = .unknown, .value = x },
},
+ .expect_stderr_snapshot => |path| expect_stderr_snapshot = try s.addLazyPath(path),
+ .expect_stdout_snapshot => |path| expect_stdout_snapshot = try s.addLazyPath(path),
},
else => {},
}
@@ -1066,6 +1070,8 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {
.expect_stdout_match = expect_stdout_match.items.len != 0,
.expect_term = expect_term != null,
.expect_term_status = if (expect_term) |t| t.status else .exited,
+ .expect_stderr_snapshot = expect_stderr_snapshot != null,
+ .expect_stdout_snapshot = expect_stdout_snapshot != null,
},
.file_inputs = .{ .slice = try s.initLazyPathList(run.file_inputs.items) },
.args = .{ .slice = try s.initArgsList(run.argv.items) },
@@ -1088,6 +1094,8 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {
.expect_stdout_exact = .{ .value = if (expect_stdout_exact) |bytes| bytes else null },
.expect_stderr_match = .{ .slice = expect_stderr_match.items },
.expect_stdout_match = .{ .slice = expect_stdout_match.items },
+ .expect_stderr_snapshot = .{ .value = expect_stderr_snapshot orelse null },
+ .expect_stdout_snapshot = .{ .value = expect_stdout_snapshot orelse null },
.stdin = .{ .u = switch (run.stdin) {
.none => .none,
.bytes => |bytes| .{ .bytes = try wc.addBytes(bytes) },
diff --git a/lib/compiler/objdump.zig b/lib/compiler/objdump.zig
@@ -4,19 +4,149 @@ const fatal = std.process.fatal;
const mem = std.mem;
const assert = std.debug.assert;
+const builtin = @import("builtin");
+const native_endian = builtin.cpu.arch.endian();
+
var stdout_buffer: [4000]u8 = undefined;
+const Options = struct {
+ exports: bool,
+ exports_sort: bool,
+ file_headers: bool,
+ imports: bool,
+ input_path: []const u8,
+ member_filters: []const []const u8 = &.{},
+ member_headers: bool,
+ elements: std.enums.EnumArray(Element, bool),
+ redact: std.enums.EnumArray(FieldKind, bool),
+ relocs: bool,
+ section_filters: []const []const u8 = &.{},
+ section_headers: bool,
+ symbol_filters: []const []const u8 = &.{},
+ strings: bool,
+ symbols: bool,
+ tls: bool,
+
+ // Coff-specific
+ linker_member: ?std.coff.ArchiveMemberHeader.Kind,
+};
+
+const FieldKind = enum {
+ va,
+ rva,
+ ord,
+ size,
+};
+
+const Element = enum {
+ @"file-type",
+ @"header-name",
+ @"member-path",
+ newlines,
+ @"table-header",
+};
+
pub fn main(init: std.process.Init) !void {
const io = init.io;
const args = try init.minimal.args.toSlice(init.arena.allocator());
+ const arena = init.arena.allocator();
- var opt_input_path: ?[]const u8 = null;
var i: usize = 1;
+
+ var opt_exports: ?bool = null;
+ var opt_exports_sort: ?bool = null;
+ var opt_file_headers: ?bool = null;
+ var opt_imports: ?bool = null;
+ var opt_input_path: ?[]const u8 = null;
+ var opt_linker_member: ?std.coff.ArchiveMemberHeader.Kind = null;
+ var opt_member_headers: ?bool = null;
+ var any_elements = false;
+ var elements: ?@FieldType(Options, "elements") = null;
+ var redact: @FieldType(Options, "redact") = .initFill(false);
+ var opt_relocs: ?bool = null;
+ var opt_section_headers: ?bool = null;
+ var opt_strings: ?bool = null;
+ var opt_symbols: ?bool = null;
+ var opt_tls: ?bool = null;
+ var section_filters: std.ArrayList([]const u8) = .empty;
+ var symbol_filters: std.ArrayList([]const u8) = .empty;
+ var member_filters: std.ArrayList([]const u8) = .empty;
while (i < args.len) : (i += 1) {
const arg = args[i];
if (mem.startsWith(u8, arg, "-")) {
if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
return Io.File.stdout().writeStreamingAll(io, usage);
+ } else if (mem.eql(u8, arg, "--all-headers")) {
+ opt_file_headers = true;
+ opt_linker_member = .second_linker;
+ opt_member_headers = true;
+ opt_section_headers = true;
+ opt_symbols = true;
+ opt_relocs = true;
+ } else if (mem.startsWith(u8, arg, "--exports")) {
+ opt_exports = true;
+ opt_linker_member = .second_linker;
+ if (mem.eql(u8, arg["--exports".len..], "=sort"))
+ opt_exports_sort = true;
+ } else if (mem.eql(u8, arg, "--file-headers")) {
+ opt_file_headers = true;
+ } else if (mem.eql(u8, arg, "--imports")) {
+ opt_imports = true;
+ } else if (mem.startsWith(u8, arg, "--linker-member")) {
+ if (mem.eql(u8, arg["--linker-member".len..], "=1"))
+ opt_linker_member = .first_linker
+ else if (mem.eql(u8, arg["--linker-member".len..], "=longnames"))
+ opt_linker_member = .longnames
+ else
+ opt_linker_member = .second_linker;
+ } else if (mem.eql(u8, arg, "--member-headers")) {
+ opt_member_headers = true;
+ } else if (mem.startsWith(u8, arg, "--elements=")) {
+ any_elements = true;
+ var split = std.mem.splitScalar(u8, arg["--elements=".len..], ',');
+ while (split.next()) |element| {
+ const kind, const add = if (element.len > 0 and element[0] == '-')
+ .{ element[1..], false }
+ else
+ .{ element, true };
+
+ if (elements == null) elements = .initFill(false);
+ if (std.meta.stringToEnum(Element, kind)) |format_kind| {
+ elements.?.set(format_kind, add);
+ } else if (std.mem.eql(u8, kind, "all")) {
+ elements.? = .initFill(add);
+ } else {
+ fatal("unrecognized element: '{s}'", .{kind});
+ }
+ }
+ } else if (mem.startsWith(u8, arg, "--only-member=")) {
+ (try member_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-member=".len..]);
+ } else if (mem.startsWith(u8, arg, "--only-section=")) {
+ (try section_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-section=".len..]);
+ } else if (mem.startsWith(u8, arg, "--only-symbol=")) {
+ (try symbol_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-symbol=".len..]);
+ } else if (mem.startsWith(u8, arg, "--redact=")) {
+ const kind = arg["--redact=".len..];
+ if (std.meta.stringToEnum(FieldKind, kind)) |field_kind| {
+ redact.set(field_kind, true);
+ } else if (std.mem.eql(u8, kind, "all")) {
+ redact = .initFill(true);
+ } else {
+ fatal("unrecognized redaction kind: {s}", .{kind});
+ }
+ } else if (mem.eql(u8, arg, "--relocs")) {
+ opt_relocs = true;
+ } else if (mem.eql(u8, arg, "--section-headers")) {
+ opt_section_headers = true;
+ } else if (mem.eql(u8, arg, "-s") or mem.eql(u8, arg, "--snapshot")) {
+ elements = .initFill(false);
+ redact = .initFill(true);
+ } else if (mem.eql(u8, arg, "--strings")) {
+ opt_strings = true;
+ } else if (mem.eql(u8, arg, "--symbols")) {
+ opt_symbols = true;
+ } else if (mem.eql(u8, arg, "--tls")) {
+ opt_tls = true;
} else {
fatal("unrecognized argument: {s}", .{arg});
}
@@ -27,42 +157,134 @@ pub fn main(init: std.process.Init) !void {
}
}
- const input_path = opt_input_path orelse fatal("missing input file path positional argument", .{});
+ const opts: Options = .{
+ .input_path = opt_input_path orelse fatal("missing input file path positional argument", .{}),
+ .exports = opt_exports orelse false,
+ .exports_sort = opt_exports_sort orelse false,
+ .file_headers = opt_file_headers orelse false,
+ .imports = opt_imports orelse false,
+ .linker_member = opt_linker_member,
+ .member_filters = member_filters.items,
+ .member_headers = opt_member_headers orelse false,
+ .elements = elements orelse .initFill(true),
+ .redact = redact,
+ .relocs = opt_relocs orelse false,
+ .section_filters = section_filters.items,
+ .section_headers = opt_section_headers orelse false,
+ .strings = opt_strings orelse false,
+ .symbol_filters = symbol_filters.items,
+ .symbols = opt_symbols orelse false,
+ .tls = opt_tls orelse false,
+ };
- var file = std.Io.Dir.cwd().openFile(io, input_path, .{}) catch |err|
- fatal("failed to open {s}: {t}", .{ input_path, err });
+ var file = std.Io.Dir.cwd().openFile(io, opts.input_path, .{}) catch |err|
+ fatal("failed to open {s}: {t}", .{ opts.input_path, err });
defer file.close(io);
- var buffer: [4000]u8 = undefined;
+ var buffer: [4096]u8 = undefined;
var file_reader = file.reader(io, &buffer);
var stdout_writer = std.Io.File.stdout().writerStreaming(io, &stdout_buffer);
- dump(&file_reader.interface, &stdout_writer.interface) catch |err| switch (err) {
+
+ const ctx: DumpContext = .{
+ .gpa = init.gpa,
+ .opts = &opts,
+ .fr = &file_reader,
+ .w = &stdout_writer.interface,
+ };
+
+ dump(&ctx) catch |err| switch (err) {
error.ReadFailed => return file_reader.err.?,
error.WriteFailed => return stdout_writer.err.?,
- error.UnknownFile => fatal("unrecognized file: {s}", .{input_path}),
+ error.UnknownFile => fatal("unrecognized file: {s}", .{opts.input_path}),
+ error.ParseFailure => {},
else => |e| return e,
};
try stdout_writer.flush();
}
-fn dump(r: *Io.Reader, w: *Io.Writer) !void {
+fn dump(d: *const DumpContext) !void {
+ const r = &d.fr.interface;
try r.fill(4);
elf: {
if (!mem.eql(u8, r.buffered()[0..4], std.elf.MAGIC)) break :elf;
- return elf.dump(r, w);
+ return elf.dump(r, d.w);
}
macho: {
if (mem.readInt(u32, r.buffered()[0..4], .little) != std.macho.MH_MAGIC_64) break :macho;
- return macho.dump(r, w);
+ return macho.dump(r, d.w);
}
wasm: {
comptime assert(std.wasm.magic.len == 4);
if (!mem.eql(u8, r.buffered()[0..4], &std.wasm.magic)) break :wasm;
- return wasm.dump(r, w);
+ return wasm.dump(r, d.w);
+ }
+ coff: {
+ const ext = std.fs.path.extension(d.opts.input_path);
+ const basename = std.fs.path.basename(d.opts.input_path);
+ if (std.mem.eql(u8, ext, ".exe") or std.mem.eql(u8, ext, ".dll")) {
+ if (!mem.eql(u8, r.buffered()[0..2], "MZ")) break :coff;
+ try r.discardAll(std.coff.pe_pointer_offset);
+ const sig_offset = try r.takeInt(u32, .little);
+ try d.fr.seekTo(sig_offset);
+ const sig = try r.take(4);
+
+ if (!std.mem.eql(u8, sig, std.coff.pe_signature)) {
+ try d.w.print("invalid PE signature: {x}", .{sig});
+ return error.ParseFailure;
+ }
+
+ if (d.element(.@"file-type")) {
+ try d.w.print("{s}: PE/COFF image\n\n", .{basename});
+ if (d.element(.newlines)) try d.w.writeByte('\n');
+ }
+
+ return coff.dumpObject(d, true, basename);
+ } else if (std.mem.eql(u8, ext, ".lib")) {
+ r.fill(std.coff.archive_signature.len) catch break :coff;
+ if (!mem.eql(u8, r.buffered()[0..std.coff.archive_signature.len], std.coff.archive_signature)) break :coff;
+ if (d.element(.@"file-type")) {
+ try d.w.print("{s}: COFF archive\n", .{basename});
+ if (d.element(.newlines)) try d.w.writeByte('\n');
+ }
+
+ return coff.dumpArchive(d);
+ } else if (std.mem.eql(u8, ext, ".obj")) {
+ if (d.element(.@"file-type")) {
+ try d.w.print("{s}: COFF object\n", .{basename});
+ if (d.element(.newlines)) try d.w.writeByte('\n');
+ }
+
+ return coff.dumpObject(d, false, basename);
+ }
}
return error.UnknownFile;
}
+const DumpContext = struct {
+ gpa: std.mem.Allocator,
+ opts: *const Options,
+ fr: *Io.File.Reader,
+ w: *Io.Writer,
+
+ fn element(self: *const DumpContext, e: Element) bool {
+ return self.opts.elements.get(e);
+ }
+
+ fn redacted(self: *const DumpContext, opt_kind: ?FieldKind) bool {
+ const kind = opt_kind orelse return false;
+ return self.opts.redact.get(kind);
+ }
+
+ fn failParse(
+ ctx: *const DumpContext,
+ comptime fmt: []const u8,
+ args: anytype,
+ ) noreturn {
+ std.log.err("error parsing '{s}'", .{std.fs.path.basename(ctx.opts.input_path)});
+ fatal(fmt, args);
+ }
+};
+
const elf = struct {
fn dump(r: *Io.Reader, w: *Io.Writer) !void {
_ = r;
@@ -84,10 +306,1478 @@ const wasm = struct {
}
};
+const coff = struct {
+ const DIRECTORY_ENTRY = std.coff.IMAGE.DIRECTORY_ENTRY;
+
+ const Section = struct {
+ header: std.coff.SectionHeader,
+ name: []const u8,
+
+ fn rvaFileOffset(section: *const Section, rva: u32) !u32 {
+ if (rva < section.header.virtual_address or
+ rva >= section.header.virtual_address + section.header.size_of_raw_data)
+ return error.OutOfBounds;
+
+ return section.header.pointer_to_raw_data + (rva - section.header.virtual_address);
+ }
+ };
+
+ const ArchiveHeader = struct {
+ name: []const u8,
+ date: u40,
+ user_id: u20,
+ group_id: u20,
+ file_mode: u24,
+ size: u34,
+
+ pub fn fromRaw(d: *const DumpContext, raw_header: *const std.coff.ArchiveMemberHeader, opt_longnames: ?[]const u8) @This() {
+ const name = raw_header.parseName(opt_longnames) catch |err| switch (err) {
+ error.BadName => d.failParse("malformed member name: '{s}'", .{&raw_header.name}),
+ error.NoLongNames => d.failParse("member uses a long name, but there was no longnames member", .{}),
+ };
+
+ return .{
+ .name = name,
+ .date = raw_header.parseDate() catch |err|
+ d.failParse("unable to parse date '{s}' in member '{s}': {t}", .{ raw_header.date, name, err }),
+ .user_id = raw_header.parseUserId() catch |err|
+ d.failParse("unable to parse user_id '{s}' in member '{s}': {t}", .{ raw_header.user_id, name, err }),
+ .group_id = raw_header.parseGroupId() catch |err|
+ d.failParse("unable to parse group_id '{s}' in member '{s}': {t}", .{ raw_header.group_id, name, err }),
+ .file_mode = raw_header.parseFileMode() catch |err|
+ d.failParse("unable to parse file_mode '{s}' in member '{s}': {t}", .{ raw_header.file_mode, name, err }),
+ .size = raw_header.parseSize() catch |err|
+ d.failParse("unable to parse size '{s}' in member '{s}': {t}", .{ raw_header.size, name, err }),
+ };
+ }
+ };
+
+ fn dumpArchive(d: *const DumpContext) !void {
+ const gpa = d.gpa;
+ const fr = d.fr;
+ const w = d.w;
+
+ const r = &fr.interface;
+ r.toss(std.coff.archive_signature.len);
+
+ const Member = struct {
+ offset: u32,
+ order: ?u32,
+ };
+
+ var members: std.ArrayList(Member) = .empty;
+ defer members.deinit(gpa);
+ var symbol_member_indices: std.ArrayList(u32) = .empty;
+ defer symbol_member_indices.deinit(gpa);
+
+ var opt_expected_kind: ?std.coff.ArchiveMemberHeader.Kind = .first_linker;
+ var opt_longnames: ?[]const u8 = null;
+ defer if (opt_longnames) |l| gpa.free(l);
+
+ var pos = fr.logicalPos();
+ const size = try fr.getSize();
+ while (pos < size) : (pos = fr.logicalPos()) {
+ if ((pos & 1) != 0) try r.discardAll(1);
+ const raw_header = try r.takeStruct(std.coff.ArchiveMemberHeader, .little);
+ const header: ArchiveHeader = .fromRaw(d, &raw_header, opt_longnames);
+
+ if (!std.mem.eql(u8, &raw_header.end_of_header, std.coff.archive_end_of_header))
+ return d.failParse("malformed end-of-header field in member '{s}': {x}", .{ header.name, raw_header.end_of_header });
+
+ const dump_header =
+ (d.opts.member_headers and filterMatches(d.opts.member_filters, header.name)) or
+ (d.opts.linker_member == opt_expected_kind);
+
+ if (dump_header)
+ try dumpArchiveHeader(d, &header, @intCast(pos));
+
+ const member_end = fr.logicalPos() + header.size;
+ if (member_end > size)
+ return d.failParse("out-of-bounds length 0x{x} in member '{s}'", .{ header.size, header.name });
+
+ if (opt_expected_kind) |expected_kind| switch (expected_kind) {
+ .first_linker => {
+ if (!std.mem.eql(u8, header.name, "/"))
+ return d.failParse("expected first linker member, found '{s}'", .{header.name});
+
+ const num_symbols = try r.takeInt(u32, .big);
+ if (dump_header)
+ try w.print(
+ \\{t: >16} type
+ \\ | {d} symbols
+ \\
+ , .{ expected_kind, num_symbols });
+
+ if (d.opts.linker_member == .first_linker) {
+ if (d.element(.@"table-header"))
+ try w.writeAll(
+ \\
+ \\Archive symbols:
+ \\& Member Symbol
+ \\
+ );
+
+ const offsets = try r.readAlloc(gpa, num_symbols * 4);
+ defer gpa.free(offsets);
+
+ for (0..num_symbols) |symbol_i| {
+ const symbol = r.takeDelimiter(0) catch |err|
+ return d.failParse("unable to read first linker member string table: {t}", .{err});
+
+ if (!filterMatches(d.opts.symbol_filters, symbol.?))
+ continue;
+
+ const offset = std.mem.readInt(u32, offsets[symbol_i * 4 ..][0..4], .big);
+ try w.print("{f} {s}\n", .{
+ fmtIntField(d, offset, .{ .kind = .va }),
+ symbol.?,
+ });
+ }
+ }
+ if (dump_header and d.element(.newlines)) try w.writeByte('\n');
+
+ try fr.seekTo(member_end);
+ opt_expected_kind = .second_linker;
+ continue;
+ },
+ .second_linker => {
+ if (!std.mem.eql(u8, header.name, "/"))
+ return d.failParse("expected second linker member, found '{s}'", .{header.name});
+
+ const num_members = try r.takeInt(u32, .little);
+ pos = fr.logicalPos();
+ if (pos + num_members * @sizeOf(u32) > member_end)
+ return d.failParse("invalid member count 0x{x} in second linker member", .{num_members});
+
+ try members.ensureTotalCapacity(gpa, num_members);
+ for (0..num_members) |_|
+ members.addOneAssumeCapacity().* = .{
+ .offset = try r.takeInt(u32, .little),
+ .order = null,
+ };
+
+ const num_symbols = try r.takeInt(u32, .little);
+ pos = fr.logicalPos();
+ if (pos + num_symbols * @sizeOf(u16) > member_end)
+ return d.failParse("invalid symbol count 0x{x} in second linker member", .{num_symbols});
+
+ if (dump_header)
+ try w.print(
+ \\{t: >16} type
+ \\ | {f} symbols
+ \\ | {f} members
+ \\
+ , .{
+ expected_kind,
+ fmtIntField(d, num_symbols, .{ .kind = .size, .width = .auto }),
+ fmtIntField(d, num_members, .{ .kind = .size, .width = .auto }),
+ });
+
+ try symbol_member_indices.ensureTotalCapacity(gpa, num_symbols);
+ for (0..num_symbols) |order| {
+ const index = (try r.takeInt(u16, .little)) - 1;
+ if (index >= members.items.len)
+ return d.failParse("invalid member index 0x{x} in seconds linker member indices array", .{index});
+
+ symbol_member_indices.addOneAssumeCapacity().* = index;
+
+ if (members.items[index].order == null)
+ members.items[index].order = @intCast(order);
+ }
+
+ if (d.opts.exports and d.opts.exports_sort) {
+ std.sort.pdq(Member, members.items, {}, struct {
+ fn lessThan(ctx: void, lhs: Member, rhs: Member) bool {
+ _ = ctx;
+ if (lhs.order == null and rhs.order == null)
+ return lhs.offset < rhs.offset
+ else if (lhs.order) |lhs_order|
+ return if (rhs.order) |rhs_order| lhs_order < rhs_order else false
+ else if (rhs.order) |rhs_order|
+ return if (lhs.order) |lhs_order| lhs_order < rhs_order else true
+ else
+ unreachable;
+ }
+ }.lessThan);
+ }
+
+ if (d.opts.linker_member == .second_linker) {
+ if (d.element(.@"table-header"))
+ try w.writeAll(
+ \\
+ \\Archive Symbols:
+ \\& Member Symbol
+ \\
+ );
+
+ pos = fr.logicalPos();
+ var symbol_i: u32 = 0;
+ while (pos < member_end and symbol_i < num_symbols) : ({
+ pos = fr.logicalPos();
+ symbol_i += 1;
+ }) {
+ const symbol_name = if (r.takeDelimiter(0) catch |err| switch (err) {
+ error.StreamTooLong => null,
+ else => |e| return e,
+ }) |n| n else return d.failParse("unterminated string found in second linker member", .{});
+
+ if (!filterMatches(d.opts.symbol_filters, symbol_name))
+ continue;
+
+ try w.print("{f} {s}\n", .{
+ fmtIntField(
+ d,
+ members.items[symbol_member_indices.items[symbol_i]].offset,
+ .{ .kind = .va },
+ ),
+ symbol_name,
+ });
+ }
+
+ if (symbol_i != num_symbols)
+ return d.failParse(
+ " expected {d} entries in second linker member string table, but found {d}",
+ .{ num_symbols, symbol_i },
+ );
+ }
+
+ if (d.element(.newlines)) try w.writeByte('\n');
+ try fr.seekTo(member_end);
+ opt_expected_kind = .longnames;
+ continue;
+ },
+ .longnames => {
+ // This member is optional
+ if (std.mem.eql(u8, header.name, "//")) {
+ opt_longnames = try r.readAlloc(gpa, header.size);
+ if (dump_header)
+ try w.print("{t: >16} type\n", .{expected_kind});
+
+ if (d.opts.linker_member == .longnames) {
+ if (d.element(.@"table-header"))
+ try w.print(
+ \\
+ \\Longnames (0x{x} bytes):
+ \\
+ , .{opt_longnames.?.len});
+
+ var lr = Io.Reader.fixed(opt_longnames.?);
+ while (try lr.takeDelimiter(0)) |str| {
+ try w.writeAll(str);
+ try w.writeByte('\n');
+ }
+ }
+
+ if (d.element(.newlines)) try w.writeByte('\n');
+ }
+
+ opt_expected_kind = null;
+ break;
+ },
+ else => unreachable,
+ };
+ }
+
+ if (opt_expected_kind) |expected_kind| switch (expected_kind) {
+ .first_linker => d.failParse("missing first linker member", .{}),
+ .second_linker => d.failParse("missing second linker member", .{}),
+ else => {},
+ };
+
+ for (members.items, 0..) |member, member_i| {
+ fr.seekTo(member.offset) catch |err|
+ d.failParse("unable to read member {d} at offset 0x{x}: {t}", .{ member_i, member.offset, err });
+
+ const raw_header = try r.takeStruct(std.coff.ArchiveMemberHeader, .little);
+ const header: ArchiveHeader = .fromRaw(d, &raw_header, opt_longnames);
+ if (!filterMatches(d.opts.member_filters, header.name)) continue;
+
+ const member_sig = try r.peek(4);
+ const machine: std.coff.IMAGE.FILE.MACHINE =
+ @enumFromInt(std.mem.readInt(u16, member_sig[0..2], .little));
+ const sig = std.mem.readInt(u16, member_sig[2..4], .little);
+
+ const is_imp_lib = machine == std.coff.IMAGE.FILE.MACHINE.UNKNOWN and sig == 0xffff;
+ if (d.opts.member_headers)
+ try dumpArchiveHeader(d, &header, member.offset);
+
+ if (d.opts.member_headers or (d.opts.exports and is_imp_lib)) {
+ if (is_imp_lib) {
+ const imp_header = try r.takeStruct(std.coff.ImportHeader, .little);
+ const sym_name = (try r.takeDelimiter(0)).?;
+ const imp_dll = (try r.takeDelimiter(0)).?;
+
+ if (!filterMatches(d.opts.symbol_filters, sym_name))
+ continue;
+
+ if (d.element(.@"header-name"))
+ try w.writeAll("\nImport header:\n");
+
+ try dumpHeader(d, std.coff.ImportHeader, &imp_header, struct {
+ pub fn sig1(_: *const DumpContext, _: *const std.coff.ImportHeader) !void {}
+ pub fn sig2(_: *const DumpContext, _: *const std.coff.ImportHeader) !void {}
+ pub fn types(id: *const DumpContext, h: *const std.coff.ImportHeader) !void {
+ try id.w.print(
+ \\{t: >16} import_type
+ \\{t: >16} name_type
+ \\
+ , .{ h.types.type, h.types.name_type });
+ }
+ });
+
+ const imp_name = imp_name: switch (imp_header.types.name_type) {
+ .NAME_NOPREFIX,
+ .NAME_UNDECORATE,
+ => |tag| {
+ var imp_name = std.mem.trimStart(u8, sym_name, "?@_");
+ if (tag == .NAME_UNDECORATE)
+ imp_name = std.mem.sliceTo(imp_name, '@');
+ break :imp_name imp_name;
+ },
+ else => sym_name,
+ };
+
+ try w.print(
+ \\ symbol name | {s}
+ \\ import name | {s}
+ \\ dll | {s}
+ \\
+ , .{
+ sym_name,
+ imp_name,
+ imp_dll,
+ });
+ } else {
+ try w.writeAll(" COFF object type\n");
+ }
+ if (d.element(.newlines)) try w.writeByte('\n');
+ }
+
+ if (is_imp_lib) continue;
+ if (d.opts.section_headers or
+ d.opts.file_headers or
+ d.opts.relocs or
+ d.opts.strings or
+ d.opts.symbols)
+ {
+ const member_name = if (d.element(.@"member-path"))
+ header.name
+ else
+ std.fs.path.basename(header.name);
+
+ if (d.element(.@"file-type")) {
+ try w.print("{s}({s}): COFF object\n", .{
+ std.fs.path.basename(d.opts.input_path),
+ member_name,
+ });
+ if (d.element(.newlines)) try w.writeByte('\n');
+ }
+ try dumpObject(d, false, member_name);
+ }
+ }
+ }
+
+ fn dumpObject(
+ d: *const DumpContext,
+ is_image: bool,
+ obj_name: []const u8,
+ ) !void {
+ const gpa = d.gpa;
+ const fr = d.fr;
+ const w = d.w;
+
+ const file_location = fr.logicalPos();
+ const r = &fr.interface;
+ const header = r.takeStruct(std.coff.Header, .little) catch |err|
+ return d.failParse("unable to read COFF header: {t}", .{err});
+
+ if (d.opts.file_headers) {
+ if (d.element(.@"header-name")) try w.writeAll("COFF Header:\n");
+ try dumpHeader(d, std.coff.Header, &header, struct {});
+ if (d.element(.newlines)) try w.writeByte('\n');
+ }
+
+ switch (header.machine) {
+ _ => return d.failParse("unknown machine type: {x}", .{header.machine}),
+ else => {},
+ }
+
+ var known_dirs: [DIRECTORY_ENTRY.len]std.coff.ImageDataDirectory = undefined;
+ const needs_data_dirs =
+ d.opts.exports or
+ d.opts.imports or
+ d.opts.tls;
+
+ const ImageInfo = struct {
+ data_dirs: []const std.coff.ImageDataDirectory,
+ magic: std.coff.OptionalHeader.Magic,
+ image_base: u64,
+ };
+
+ const image_info: ?ImageInfo = if (header.size_of_optional_header > 0) image_info: {
+ if (!d.opts.file_headers and !needs_data_dirs) {
+ try fr.seekBy(header.size_of_optional_header);
+ break :image_info null;
+ }
+
+ if (d.opts.file_headers and d.element(.@"header-name"))
+ try w.writeAll("COFF Optional Header:\n");
+
+ const magic: std.coff.OptionalHeader.Magic = @enumFromInt(try r.peekInt(u16, .little));
+ const num_directory_entries, const image_base = switch (magic) {
+ inline .PE32, .@"PE32+" => |v| num_data_dirs: {
+ const OptionalHeader = if (v == .PE32)
+ std.coff.OptionalHeader.PE32
+ else
+ std.coff.OptionalHeader.@"PE32+";
+
+ const optional_header = r.takeStruct(OptionalHeader, .little) catch |err|
+ return d.failParse("unable to read optional header: {t}", .{err});
+
+ if (d.opts.file_headers) {
+ try dumpHeader(d, OptionalHeader, &optional_header, struct {
+ pub fn base_of_code(id: *const DumpContext, h: *const std.coff.OptionalHeader) !void {
+ const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base;
+ try dumpRvaField(id, @src().fn_name, h.base_of_code, base);
+ }
+
+ pub fn address_of_entry_point(id: *const DumpContext, h: *const std.coff.OptionalHeader) !void {
+ const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base;
+ try dumpRvaField(id, @src().fn_name, h.base_of_code, base);
+ }
+
+ pub fn major_linker_version(id: *const DumpContext, h: *const std.coff.OptionalHeader) !void {
+ try dumpVersionField(id.w, "linker_version", h.major_linker_version, h.minor_linker_version);
+ }
+ pub fn minor_linker_version(_: *const DumpContext, _: *const std.coff.OptionalHeader) !void {}
+
+ pub fn major_operating_system_version(id: *const DumpContext, h: *const OptionalHeader) !void {
+ try dumpVersionField(
+ id.w,
+ "operating_system_version",
+ h.major_operating_system_version,
+ h.minor_operating_system_version,
+ );
+ }
+ pub fn minor_operating_system_version(_: *const DumpContext, _: *const OptionalHeader) !void {}
+
+ pub fn major_image_version(id: *const DumpContext, h: *const OptionalHeader) !void {
+ try dumpVersionField(id.w, "image_version", h.major_image_version, h.minor_image_version);
+ }
+ pub fn minor_image_version(_: *const DumpContext, _: *const OptionalHeader) !void {}
+
+ pub fn major_subsystem_version(id: *const DumpContext, h: *const OptionalHeader) !void {
+ try dumpVersionField(id.w, "subsystem_version", h.major_subsystem_version, h.minor_subsystem_version);
+ }
+ pub fn minor_subsystem_version(_: *const DumpContext, _: *const OptionalHeader) !void {}
+ });
+ if (d.element(.newlines)) try w.writeByte('\n');
+ }
+
+ break :num_data_dirs .{
+ optional_header.number_of_rva_and_sizes,
+ optional_header.image_base,
+ };
+ },
+ else => return d.failParse("invalid optional header magic number: {x}", .{magic}),
+ };
+
+ if (d.opts.file_headers and d.element(.@"header-name"))
+ try w.writeAll("Data Directories:\n");
+
+ for (0..num_directory_entries) |dir_i| {
+ const dir = r.takeStruct(std.coff.ImageDataDirectory, .little) catch |err|
+ return d.failParse("unable to read data directory {x}: {t}", .{ dir_i, err });
+
+ if (dir_i < known_dirs.len)
+ known_dirs[dir_i] = dir;
+
+ if (d.opts.file_headers)
+ try w.print(
+ "{x: >16} {x: >8} {t}\n",
+ .{ dir.virtual_address, dir.size, @as(DIRECTORY_ENTRY, @enumFromInt(dir_i)) },
+ );
+ }
+ if (d.opts.file_headers and d.element(.newlines)) try w.writeByte('\n');
+
+ break :image_info .{
+ .data_dirs = known_dirs[0..@min(known_dirs.len, num_directory_entries)],
+ .magic = magic,
+ .image_base = image_base,
+ };
+ } else if (is_image) {
+ return d.failParse("image did not contain an optional header", .{});
+ } else null;
+
+ // Section names in images don't use the string table, as they must fit inline in the header
+ const load_string_table = (d.opts.strings or !is_image) and header.pointer_to_symbol_table > 0;
+ const string_table = if (load_string_table) string_table: {
+ const pos = fr.logicalPos();
+ fr.seekTo(file_location + header.pointer_to_symbol_table + header.number_of_symbols * std.coff.Symbol.sizeOf()) catch |err|
+ return d.failParse("unable to seek to string table: {t}", .{err});
+
+ const string_table_len = r.peekInt(u32, .little) catch |err|
+ return d.failParse("unable to read string table length: {t}", .{err});
+
+ const table = r.readAlloc(gpa, string_table_len) catch |err|
+ return d.failParse("unable to read string table: {t}", .{err});
+
+ try fr.seekTo(pos);
+ break :string_table table;
+ } else &.{};
+ defer gpa.free(string_table);
+
+ if (d.opts.strings) {
+ if (d.element(.@"table-header"))
+ try w.print(
+ \\String Table (0x{x} bytes):
+ \\
+ , .{string_table.len});
+
+ var sr = Io.Reader.fixed(string_table[@sizeOf(u32)..]);
+ while (try sr.takeDelimiter(0)) |str| {
+ try w.writeAll(str);
+ try w.writeByte('\n');
+ }
+
+ if (d.element(.newlines)) try w.writeByte('\n');
+ }
+
+ var sections: std.ArrayList(Section) = .empty;
+ defer sections.deinit(gpa);
+ var sections_with_data: u16 = 0;
+
+ const load_sections =
+ d.opts.section_headers or
+ d.opts.symbols or
+ d.opts.relocs or
+ needs_data_dirs;
+
+ if (load_sections) {
+ if (d.opts.section_headers and d.element(.@"table-header"))
+ try w.print(
+ \\Sections in '{s}':
+ \\Num Name RVA Virt Size Data Size & Data & Relocs & Lines # Relocs # Lines Flags
+ \\
+ , .{obj_name});
+
+ try sections.resize(gpa, header.number_of_sections);
+ for (sections.items, 0..) |*section, section_i| {
+ section.header = r.takeStruct(std.coff.SectionHeader, .little) catch |err|
+ return d.failParse("unable to read section header {x}: {t}", .{ section_i, err });
+ section.name = headerName(§ion.header.name, string_table) catch |err| switch (err) {
+ error.Overflow,
+ error.InvalidCharacter,
+ => return d.failParse("unable to parse section name offset '{s}': {t}", .{
+ section.name,
+ err,
+ }),
+ error.OutOfBounds => return d.failParse("section name offset '{s}' was out of bounds (>= {x})", .{
+ section.name,
+ string_table.len,
+ }),
+ };
+
+ sections_with_data += @intFromBool(section.header.size_of_raw_data > 0);
+ if (d.opts.section_headers) {
+ if (!filterMatches(d.opts.section_filters, section.name)) continue;
+ const raw_name = std.mem.sliceTo(§ion.header.name, 0);
+ try w.print(
+ "{x: >3} {s: <8} {f} {f} {f} {f} {f} {f} {f} {f} {x:0>8} |",
+ .{
+ section_i + 1,
+ raw_name,
+ fmtIntField(d, section.header.virtual_address, .{ .kind = .va }),
+ fmtIntField(d, section.header.virtual_size, .{ .kind = .size, .width = .{ .explicit = 9 } }),
+ fmtIntField(d, section.header.size_of_raw_data, .{ .kind = .size, .width = .{ .explicit = 9 } }),
+ fmtIntField(d, section.header.pointer_to_raw_data, .{ .kind = .va }),
+ fmtIntField(d, section.header.pointer_to_relocations, .{ .kind = .va }),
+ fmtIntField(d, section.header.pointer_to_linenumbers, .{ .kind = .va }),
+ fmtIntField(d, section.header.number_of_relocations, .{ .kind = .va }),
+ fmtIntField(d, section.header.number_of_linenumbers, .{ .kind = .va }),
+ @as(u32, @bitCast(section.header.flags)),
+ },
+ );
+
+ try dumpFlags(w, "{s}", std.coff.SectionHeader.Flags, §ion.header.flags, 1);
+ if (section.name.len > 8)
+ try w.print("\n | {s}", .{section.name});
+
+ try w.writeByte('\n');
+ }
+ }
+
+ if (d.opts.section_headers and d.element(.newlines)) try w.writeByte('\n');
+ }
+
+ var symbols: std.ArrayList(struct {
+ name: []const u8,
+ section_number: std.coff.SectionNumber,
+ }) = .empty;
+ defer symbols.deinit(gpa);
+
+ var name_arena: std.heap.ArenaAllocator = .init(gpa);
+ defer name_arena.deinit();
+
+ if (d.opts.relocs)
+ try symbols.ensureUnusedCapacity(gpa, header.number_of_symbols);
+
+ if (d.opts.symbols or d.opts.relocs) {
+ if (header.pointer_to_symbol_table > 0) {
+ fr.seekTo(file_location + header.pointer_to_symbol_table) catch |err|
+ return d.failParse("unable to seek to symbol table: {t}", .{err});
+
+ if (d.opts.symbols and d.element(.@"table-header"))
+ try w.print(
+ \\Symbols in '{s}':
+ \\ Ord Value Sect Type Storage Name
+ \\
+ , .{obj_name});
+
+ const symbol_size = std.coff.Symbol.sizeOf();
+ var symbol_i: u32 = 0;
+ while (symbol_i < header.number_of_symbols) {
+ var symbol: std.coff.Symbol = undefined;
+ const symbol_bytes = r.take(symbol_size) catch |err|
+ return d.failParse("unable to read symbol {x}: {t}", .{ symbol_i, err });
+
+ @memcpy(std.mem.asBytes(&symbol)[0..symbol_size], symbol_bytes);
+ if (native_endian != .little)
+ std.mem.byteSwapAllFields(std.coff.Symbol, &symbol);
+
+ const aux_symbols = if (symbol.number_of_aux_symbols > 0)
+ try r.take(symbol_size * symbol.number_of_aux_symbols)
+ else
+ &.{};
+ defer symbol_i += symbol.number_of_aux_symbols + 1;
+
+ const name = if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: {
+ const index = std.mem.readInt(u32, symbol.name[4..], .little);
+ if (index >= string_table.len)
+ return d.failParse("invalid name offset for symbol {x} ({x} >= {x})", .{
+ symbol_i,
+ index,
+ string_table.len,
+ });
+ break :name std.mem.sliceTo(string_table[index..], 0);
+ } else try name_arena.allocator().dupe(u8, std.mem.sliceTo(&symbol.name, 0));
+
+ if (d.opts.relocs)
+ symbols.appendNTimesAssumeCapacity(.{
+ .name = name,
+ .section_number = symbol.section_number,
+ }, 1 + symbol.number_of_aux_symbols);
+
+ if (!d.opts.symbols or !filterMatches(d.opts.symbol_filters, name))
+ continue;
+
+ try w.print("{f} {x:0>8} ", .{
+ fmtIntField(d, @as(u16, @intCast(symbol_i)), .{ .kind = .ord }),
+ symbol.value,
+ });
+ try switch (symbol.section_number) {
+ .UNDEFINED => w.writeAll("UNDEF"),
+ .ABSOLUTE => w.writeAll(" ABS"),
+ .DEBUG => w.writeAll("DEBUG"),
+ else => |v| {
+ const backing = @intFromEnum(v);
+ const fmt = "{x: >5}";
+ if (backing >= 0)
+ try w.print(fmt, .{@as(u15, @intCast(backing))})
+ else
+ try w.print(fmt, .{backing});
+ },
+ };
+
+ try w.print("{t: >5}", .{symbol.type.base_type});
+ if (switch (symbol.type.complex_type) {
+ .NULL => " ",
+ .POINTER => "* ",
+ .FUNCTION => "()",
+ .ARRAY => "[]",
+ else => null,
+ }) |suffix| try w.writeAll(suffix) else try w.print("{x}", .{symbol.type.complex_type});
+
+ try w.print("{t: >16} | {s}\n", .{ symbol.storage_class, name });
+
+ for (0..symbol.number_of_aux_symbols) |aux_i| {
+ _ = aux_i;
+ try w.writeAll(" |");
+
+ if (symbol.storage_class == .EXTERNAL and
+ symbol.type == std.coff.SymType{
+ .complex_type = .FUNCTION,
+ .base_type = .NULL,
+ } and
+ @intFromEnum(symbol.section_number) > 0)
+ {
+ try w.writeAll("TODO function aux symbol");
+ } else if (symbol.type == std.coff.SymType{
+ .complex_type = .FUNCTION,
+ .base_type = .NULL,
+ } and
+ (std.mem.eql(u8, name, ".bf") or std.mem.eql(u8, name, ".ef")))
+ {
+ try w.writeAll("TODO bf / ef aux symbol");
+ } else if (symbol.storage_class == .WEAK_EXTERNAL and symbol.section_number == .UNDEFINED) {
+ if (symbol.value != 0)
+ return d.failParse(
+ "invalid value 0x{x} for weak external symbol 0x{x}",
+ .{ symbol.value, symbol_i },
+ );
+
+ var weak_external: std.coff.WeakExternalDefinition = undefined;
+ @memcpy(std.mem.asBytes(&weak_external)[0..symbol_size], aux_symbols[0..symbol_size]);
+ if (native_endian != .little)
+ std.mem.byteSwapAllFields(std.coff.WeakExternalDefinition, &weak_external);
+
+ if (weak_external.tag_index >= header.number_of_symbols)
+ return d.failParse(
+ "invalid tag_index 0x{x} for weak external symbol 0x{x}",
+ .{ weak_external.tag_index, symbol_i },
+ );
+
+ if (d.redacted(.ord))
+ try w.print(" Weak External [falls back to relative ordinal {x:0>8} via {t}]", .{
+ @as(i64, weak_external.tag_index) - symbol_i,
+ weak_external.flag,
+ })
+ else
+ try w.print(" Weak External [falls back to ordinal {x:0>8} via {t}]", .{
+ weak_external.tag_index,
+ weak_external.flag,
+ });
+ } else if (symbol.storage_class == .FILE) {
+ if (!std.mem.eql(u8, name, ".file")) {
+ try w.print(" !! unexpected symbol name '{s}' for file symbol 0x{x}", .{ name, symbol_i });
+ continue;
+ }
+
+ const filename = std.mem.sliceTo(aux_symbols, 0);
+ try w.print(" File '{s}'", .{filename});
+ break;
+ } else if (symbol.storage_class == .STATIC and
+ symbol.type == std.coff.SymType{
+ .complex_type = .NULL,
+ .base_type = .NULL,
+ } and
+ symbol.value == 0 and
+ switch (symbol.section_number) {
+ .UNDEFINED, .DEBUG, .ABSOLUTE => false,
+ else => |sn| @intFromEnum(sn) > 0,
+ })
+ {
+ const section_i: u15 = @intCast(@intFromEnum(symbol.section_number) - 1);
+ try w.writeAll(" Section ");
+
+ if (section_i >= sections.items.len) {
+ try w.print(" !! invalid section number: {x}", .{section_i});
+ continue;
+ }
+
+ var section_def: std.coff.SectionDefinition = undefined;
+ @memcpy(std.mem.asBytes(§ion_def)[0..symbol_size], aux_symbols[0..symbol_size]);
+ if (native_endian != .little)
+ std.mem.byteSwapAllFields(std.coff.SectionDefinition, §ion_def);
+
+ const section = §ions.items[section_i];
+ if (section_def.number_of_relocations != section.header.number_of_relocations) {
+ try w.print(
+ " !! relocation count did not match section header: {d} vs {d}",
+ .{ section_def.number_of_relocations, section.header.number_of_relocations },
+ );
+ continue;
+ }
+
+ if (section_def.number_of_linenumbers != section.header.number_of_linenumbers) {
+ try w.print(
+ " !! line number count did not match section header: {d} vs {d}",
+ .{ section_def.number_of_linenumbers, section.header.number_of_linenumbers },
+ );
+ continue;
+ }
+
+ try w.print(" [size {f} chksum {x:0>8} relocs {x:0>4} lines {x:0>4}]", .{
+ fmtIntField(d, section_def.length, .{ .kind = .size, .zero_fill = true }),
+ section_def.checksum,
+ section_def.number_of_relocations,
+ section_def.number_of_linenumbers,
+ });
+
+ switch (section_def.selection) {
+ .NONE => {},
+ else => |selection| {
+ try w.print(" COMDAT({t}", .{selection});
+ if (selection == .ASSOCIATIVE)
+ try w.print("->{x}", .{section_def.number});
+ try w.writeAll(")");
+ },
+ }
+ }
+
+ try w.writeByte('\n');
+ }
+ }
+
+ if (d.opts.symbols and d.element(.newlines)) try w.writeByte('\n');
+ } else if (d.opts.symbols) {
+ try w.writeAll("No symbol table found\n");
+ }
+ }
+
+ if (d.opts.relocs) {
+ const relocation_size = std.coff.Relocation.sizeOf();
+
+ for (sections.items, 0..) |section, section_i| {
+ if (section.header.pointer_to_relocations == 0) continue;
+
+ if (d.element(.@"table-header"))
+ try w.print(
+ \\Relocs for section {x} '{s}' in {s}:
+ \\ Offset Type Symbol -> Sect Name
+ \\
+ , .{ section_i + 1, section.name, obj_name });
+
+ fr.seekTo(file_location + section.header.pointer_to_relocations) catch |err|
+ return d.failParse("unable to seek to section {x} relocation table: {t}", .{ section_i + 1, err });
+
+ for (0..section.header.number_of_relocations) |reloc_i| {
+ var reloc: std.coff.Relocation = undefined;
+ @memcpy(std.mem.asBytes(&reloc)[0..relocation_size], try r.take(relocation_size));
+ if (native_endian != .little)
+ std.mem.byteSwapAllFields(std.coff.Relocation, &reloc);
+
+ const sym = &symbols.items[reloc.symbol_table_index];
+ if (!filterMatches(d.opts.symbol_filters, sym.name))
+ continue;
+
+ try w.print("{f} ", .{
+ fmtIntField(d, reloc.virtual_address, .{ .kind = .va, .zero_fill = true }),
+ });
+ switch (header.machine) {
+ _ => unreachable,
+ inline else => |m| switch (m.RelocationType()) {
+ void => try w.writeAll("(unknown arch)"),
+ else => |RelocationType| try w.print(
+ "{t: <17} ",
+ .{@as(RelocationType, @enumFromInt(reloc.type))},
+ ),
+ },
+ }
+
+ if (reloc.symbol_table_index >= symbols.items.len)
+ return d.failParse(
+ "reloc {x} in section {x} has out-of-bounds symbol index {x}",
+ .{ reloc_i, section_i + 1, reloc.symbol_table_index },
+ );
+
+ try w.print("{f} {f} | {s}\n", .{
+ fmtIntField(d, reloc.symbol_table_index, .{ .kind = .ord }),
+ fmtSectionNumber(sym.section_number),
+ sym.name,
+ });
+ }
+ if (d.element(.newlines)) try w.writeByte('\n');
+ }
+ }
+
+ // Sections indices with raw data, sorted by RVA
+ const rva_index = if (needs_data_dirs) rva_index: {
+ const rva_index = try gpa.alloc(u16, sections_with_data);
+ var indices_i: u16 = 0;
+ for (sections.items, 0..) |*section, i| {
+ if (section.header.size_of_raw_data == 0) continue;
+ rva_index[indices_i] = @intCast(i);
+ indices_i += 1;
+ }
+
+ const Context = struct {
+ indices: []u16,
+ sections: []const Section,
+
+ pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool {
+ return ctx.sections[ctx.indices[lhs]].header.virtual_address <
+ ctx.sections[ctx.indices[rhs]].header.virtual_address;
+ }
+
+ pub fn swap(ctx: @This(), lhs: usize, rhs: usize) void {
+ std.mem.swap(u16, &ctx.indices[lhs], &ctx.indices[rhs]);
+ }
+ };
+
+ std.sort.pdqContext(0, rva_index.len, Context{
+ .indices = rva_index,
+ .sections = sections.items,
+ });
+
+ break :rva_index rva_index;
+ } else &.{};
+ defer gpa.free(rva_index);
+
+ if (d.opts.exports) exports: {
+ if (try seekToDataDirectory(
+ d,
+ rva_index,
+ sections.items,
+ (image_info orelse {
+ try w.writeAll("COFF objects do not contain an export data directory");
+ break :exports;
+ }).data_dirs,
+ .EXPORT,
+ )) |section_index| {
+ const export_dir = r.takeStruct(std.coff.ExportDirectoryTable, .little) catch |err|
+ return d.failParse("unable to read export directory: {t}", .{err});
+
+ try w.print("Export directory:\n", .{});
+ try dumpHeader(d, std.coff.ExportDirectoryTable, &export_dir, struct {
+ pub fn major_version(id: *const DumpContext, h: *const std.coff.ExportDirectoryTable) !void {
+ try dumpVersionField(id.w, "version", h.major_version, h.minor_version);
+ }
+ pub fn minor_version(_: *const DumpContext, _: *const std.coff.ExportDirectoryTable) !void {}
+ });
+
+ const section = sections.items[section_index];
+ const name_loc = section.rvaFileOffset(export_dir.name_rva) catch
+ return d.failParse(
+ "export name rva 0x{x} was not within the export section",
+ .{export_dir.name_rva},
+ );
+
+ const eat_loc = section.rvaFileOffset(export_dir.export_address_table_rva) catch
+ return d.failParse(
+ "export address table rva 0x{x} was not within the export section",
+ .{export_dir.export_address_table_rva},
+ );
+
+ const name_pointer_loc = section.rvaFileOffset(export_dir.name_pointer_table_rva) catch
+ return d.failParse(
+ "export name pointer table rva 0x{x} was not within the export section",
+ .{export_dir.name_pointer_table_rva},
+ );
+
+ const ord_loc = section.rvaFileOffset(export_dir.ordinal_table_rva) catch
+ return d.failParse(
+ "export ordinal table rva 0x{x} was not within the export section",
+ .{export_dir.ordinal_table_rva},
+ );
+
+ // All the variable length fields should be contained within this directory.
+ // Read it entirely to avoid needing to seek per-name when iterating.
+ const dir = image_info.?.data_dirs[@intFromEnum(DIRECTORY_ENTRY.EXPORT)];
+ const dir_end_rva = dir.virtual_address + dir.size;
+ const dir_loc = fr.logicalPos();
+ const dir_slice = try r.readAlloc(gpa, dir.size);
+ defer gpa.free(dir_slice);
+
+ const dll_name = std.mem.sliceTo(dir_slice[name_loc - dir_loc ..], 0);
+ if (d.element(.@"table-header"))
+ try w.print(
+ \\
+ \\Exports from {s}:
+ \\ Ord Hint RVA Name
+ \\
+ , .{dll_name});
+
+ const name_pointers = dir_slice[name_pointer_loc - dir_loc ..][0 .. export_dir.number_of_names * @sizeOf(u32)];
+ const ords = dir_slice[ord_loc - dir_loc ..][0 .. export_dir.number_of_names * @sizeOf(u16)];
+ const addrs = dir_slice[eat_loc - dir_loc ..][0 .. export_dir.number_of_entries * @sizeOf(u32)];
+ const name_rva_to_offset = dir.virtual_address + @sizeOf(std.coff.ExportDirectoryTable);
+ for (0..export_dir.number_of_names) |name_i| {
+ const name_rva = std.mem.readInt(u32, name_pointers[name_i * @sizeOf(u32) ..][0..@sizeOf(u32)], .little);
+ const name = std.mem.sliceTo(dir_slice[name_rva - name_rva_to_offset ..], 0);
+ if (!filterMatches(d.opts.symbol_filters, name))
+ continue;
+
+ const ord = std.mem.readInt(u16, ords[name_i * @sizeOf(u16) ..][0..@sizeOf(u16)], .little);
+ const addr = std.mem.readInt(u32, addrs[@as(u32, ord) * @sizeOf(u32) ..][0..@sizeOf(u32)], .little);
+
+ try w.print("{f} {f} ", .{
+ fmtIntField(d, @as(u16, @intCast(export_dir.ordinal_base + ord)), .{ .kind = .ord }),
+ fmtIntField(d, @as(u16, @intCast(name_i)), .{ .kind = .ord }),
+ });
+ const is_forwarder = addr >= dir.virtual_address and addr < dir_end_rva;
+ if (is_forwarder) {
+ try w.writeAll("forwards");
+ } else {
+ try w.print("{f}", .{fmtIntField(d, addr, .{ .kind = .rva })});
+ }
+
+ try w.print(" | {s}", .{name});
+ if (is_forwarder)
+ try w.print(" -> {s}", .{std.mem.sliceTo(dir_slice[addr - name_rva_to_offset ..], 0)});
+ try w.writeByte('\n');
+ }
+ }
+ }
+
+ if (d.opts.imports) imports: {
+ if (try seekToDataDirectory(
+ d,
+ rva_index,
+ sections.items,
+ (image_info orelse {
+ try w.writeAll("COFF objects do not contain an import data directory");
+ break :imports;
+ }).data_dirs,
+ .IMPORT,
+ )) |_| {
+ const Entry = std.coff.ImportDirectoryEntry;
+ var directory_entries: std.ArrayList(Entry) = .empty;
+ defer directory_entries.deinit(gpa);
+ while (true) {
+ const entry = r.takeStruct(Entry, .little) catch |err|
+ return d.failParse(
+ "unable to read import directory entry {x}: {t}",
+ .{ directory_entries.items.len, err },
+ );
+
+ if (std.mem.allEqual(u8, std.mem.asBytes(&entry), 0)) break;
+ (try directory_entries.addOne(gpa)).* = entry;
+ }
+
+ for (directory_entries.items) |entry| {
+ const name_section = sectionContainingRva(
+ rva_index,
+ sections.items,
+ entry.name_rva,
+ ) orelse
+ return d.failParse(
+ "import directory entry name rva 0x{x} was not found in any section",
+ .{entry.name_rva},
+ );
+
+ const name_loc = sections.items[name_section].rvaFileOffset(
+ entry.name_rva,
+ ) catch unreachable;
+ fr.seekTo(name_loc) catch |err|
+ return d.failParse(
+ "unable to seek to import directory entry name at 0x{x}: {t}",
+ .{ name_loc, err },
+ );
+
+ const dll_name = (try r.takeDelimiter(0)).?;
+
+ if (d.element(.@"header-name"))
+ try w.print("Import table entry for {s}:\n", .{dll_name});
+ try dumpHeader(d, Entry, &entry, struct {});
+
+ if (d.element(.@"table-header"))
+ try w.print(
+ \\
+ \\ Ord Hint Name
+ \\
+ , .{});
+
+ const ilt_section = sectionContainingRva(
+ rva_index,
+ sections.items,
+ entry.import_lookup_table_rva,
+ ) orelse
+ return d.failParse(
+ "import directory entry ilt rva 0x{x} was not found in any section",
+ .{entry.import_lookup_table_rva},
+ );
+
+ const ilt_loc = sections.items[ilt_section].rvaFileOffset(
+ entry.import_lookup_table_rva,
+ ) catch unreachable;
+ fr.seekTo(ilt_loc) catch |err|
+ return d.failParse(
+ "unable to seek to import directory ilt at 0x{x}: {t}",
+ .{ ilt_loc, err },
+ );
+
+ switch (image_info.?.magic) {
+ _ => try w.writeAll("(unknown magic)"),
+ inline else => |m| {
+ const TableEntry = std.coff.ImportLookupTableEntry(m);
+ const null_entry: TableEntry = @bitCast(@as(@typeInfo(TableEntry).@"struct".backing_integer.?, 0));
+
+ var ilt_entries: std.ArrayList(TableEntry) = .empty;
+ defer ilt_entries.deinit(gpa);
+ while (true) {
+ const table_entry = r.takeStruct(TableEntry, .little) catch |err|
+ return d.failParse(
+ "unable to read ilt entry {s}:{x}: {t}",
+ .{ dll_name, ilt_entries.items.len, err },
+ );
+ if (table_entry == null_entry) break;
+ (try ilt_entries.addOne(gpa)).* = table_entry;
+ }
+
+ for (ilt_entries.items, 0..) |ilt_entry, ilt_entry_i| {
+ if (ilt_entry.is_ordinal) {
+ try w.print("{x: >4}", .{ilt_entry.payload.ordinal.ordinal});
+ } else {
+ const hint_section = sectionContainingRva(
+ rva_index,
+ sections.items,
+ ilt_entry.payload.hint_name_rva,
+ ) orelse
+ return d.failParse(
+ "import directory ilt entry 0x{x}'s hint rva 0x{x} was not found in any section",
+ .{ ilt_entry_i, ilt_entry.payload.hint_name_rva },
+ );
+
+ const hint_loc = sections.items[hint_section].rvaFileOffset(
+ ilt_entry.payload.hint_name_rva,
+ ) catch unreachable;
+ fr.seekTo(hint_loc) catch |err|
+ return d.failParse(
+ "unable to seek to ilt entry 0x{x}'s hint at 0x{x}: {t}",
+ .{ ilt_entry_i, hint_loc, err },
+ );
+
+ const hint = r.takeInt(u16, .little) catch |err|
+ return d.failParse(
+ "unable to read import directory ilt entry 0x{x}'s hint: {t}",
+ .{ ilt_entry_i, err },
+ );
+
+ const name = r.takeDelimiter(0) catch |err|
+ return d.failParse(
+ "unable to read import directory ilt entry 0x{x}'s name: {t}",
+ .{ ilt_entry_i, err },
+ );
+
+ try w.print(" {x: >4} | {s}\n", .{ hint, name.? });
+ }
+ }
+ if (d.element(.newlines)) try w.writeByte('\n');
+ },
+ }
+ }
+ }
+ }
+
+ if (d.opts.tls) tls: {
+ if (try seekToDataDirectory(
+ d,
+ rva_index,
+ sections.items,
+ (image_info orelse {
+ try w.writeAll("COFF objects do not contain a TLS data directory");
+ break :tls;
+ }).data_dirs,
+ .TLS,
+ )) |_| {
+ switch (image_info.?.magic) {
+ _ => try w.writeAll("(unknown magic)"),
+ inline else => |m| {
+ const TlsDirectoryEntry = std.coff.TlsDirectoryEntry(m);
+ const tls_entry = r.takeStruct(TlsDirectoryEntry, .little) catch |err|
+ return d.failParse("unable to read tls directory: {t}", .{err});
+
+ try w.writeAll("TLS Directory:\n");
+ try dumpHeader(d, TlsDirectoryEntry, &tls_entry, struct {});
+
+ try w.writeAll(" | ");
+ if (tls_entry.characteristics.alignment == .NONE) {
+ try w.writeAll("Alignment not specified");
+ } else {
+ try w.print(
+ "Alignment: {d}",
+ .{tls_entry.characteristics.alignment.toByteUnits().?},
+ );
+ }
+
+ try w.writeAll(
+ \\
+ \\
+ \\TLS Callbacks:
+ \\ Address
+ \\
+ );
+
+ const callbacks_rva: u32 = @intCast(tls_entry.callbacks_va - image_info.?.image_base);
+ const section_index = sectionContainingRva(
+ rva_index,
+ sections.items,
+ callbacks_rva,
+ ) orelse
+ return d.failParse(
+ "tls callbacks rva 0x{x} was not found in any section",
+ .{callbacks_rva},
+ );
+
+ const callbacks_loc = sections.items[section_index]
+ .rvaFileOffset(callbacks_rva) catch unreachable;
+
+ fr.seekTo(callbacks_loc) catch |err|
+ return d.failParse(
+ "unable to seek to tls callbacks array at offset 0x{x}: {t}",
+ .{ callbacks_loc, err },
+ );
+
+ while (true) {
+ const callback_va = r.takeInt(@FieldType(TlsDirectoryEntry, "callbacks_va"), .little) catch |err|
+ return d.failParse(
+ "unable to read tls callbacks array: {t}",
+ .{err},
+ );
+
+ try w.print("{f}\n", .{fmtIntField(d, callback_va, .{ .kind = .va })});
+ if (callback_va == 0) break;
+ }
+ if (d.element(.newlines)) try w.writeByte('\n');
+ },
+ }
+ }
+ }
+ }
+
+ fn seekToDataDirectory(
+ d: *const DumpContext,
+ rva_index: []const u16,
+ sections: []const Section,
+ data_dirs: []const std.coff.ImageDataDirectory,
+ entry: DIRECTORY_ENTRY,
+ ) !?u16 {
+ if (@intFromEnum(entry) < data_dirs.len) blk: {
+ const rva = data_dirs[@intFromEnum(entry)].virtual_address;
+ if (rva == 0) break :blk;
+
+ const section_index = sectionContainingRva(rva_index, sections, rva) orelse
+ return d.failParse(
+ "{t} directory rva 0x{x} was not found in any section",
+ .{ entry, rva },
+ );
+
+ const file_offset = sections[section_index].rvaFileOffset(rva) catch unreachable;
+ d.fr.seekTo(file_offset) catch |err|
+ return d.failParse(
+ "unable to seek to {t} directory at offset 0x{x}: {t}",
+ .{ entry, file_offset, err },
+ );
+
+ return section_index;
+ }
+
+ try d.w.print("{t} directory was not present in optional header\n", .{entry});
+ return null;
+ }
+
+ fn sectionContainingRva(
+ /// Indices into `sections` sorted by rva
+ indices: []const u16,
+ sections: []const Section,
+ rva: u32,
+ ) ?u16 {
+ const Context = struct {
+ rva: u32,
+ sections: []const Section,
+
+ fn order(ctx: @This(), section_index: u16) std.math.Order {
+ const h = &ctx.sections[section_index].header;
+ if (ctx.rva < h.virtual_address) return .lt;
+ const end = h.virtual_address + h.size_of_raw_data;
+ if (ctx.rva >= end) return .gt;
+ return .eq;
+ }
+ };
+
+ const indices_index = std.sort.binarySearch(u16, indices, Context{
+ .rva = rva,
+ .sections = sections,
+ }, Context.order) orelse return null;
+ return @intCast(indices[indices_index]);
+ }
+
+ fn headerName(raw: *const [8]u8, string_table: []const u8) ![]const u8 {
+ return if (raw[0] == '/') name: {
+ const name_offset = try std.fmt.parseUnsigned(u24, std.mem.sliceTo(raw[1..], 0), 10);
+ if (name_offset >= string_table.len)
+ return error.OutOfBounds;
+
+ break :name std.mem.sliceTo(string_table[name_offset..], 0);
+ } else std.mem.sliceTo(raw, 0);
+ }
+
+ fn fmtSectionNumber(section_number: std.coff.SectionNumber) std.fmt.Alt(std.coff.SectionNumber, sectionNumberString) {
+ return .{ .data = section_number };
+ }
+
+ fn sectionNumberString(section_number: std.coff.SectionNumber, w: *std.Io.Writer) std.Io.Writer.Error!void {
+ try switch (section_number) {
+ .UNDEFINED => w.writeAll("UNDEF"),
+ .ABSOLUTE => w.writeAll(" ABS"),
+ .DEBUG => w.writeAll("DEBUG"),
+ else => |v| {
+ const backing = @intFromEnum(v);
+ const fmt = "{x: >5}";
+ if (backing >= 0)
+ try w.print(fmt, .{@as(u15, @intCast(backing))})
+ else
+ try w.print(fmt, .{backing});
+ },
+ };
+ }
+
+ const FormatIntField = struct {
+ val: ?u64,
+ width: ?usize,
+ zero_fill: bool,
+ };
+
+ fn fmtIntField(
+ d: *const DumpContext,
+ val: anytype,
+ params: struct {
+ kind: ?FieldKind = null,
+ width: union(enum) {
+ fit_max,
+ auto,
+ explicit: usize,
+ } = .fit_max,
+ zero_fill: bool = false,
+ },
+ ) std.fmt.Alt(FormatIntField, intFieldString) {
+ return .{
+ .data = .{
+ .val = if (d.redacted(params.kind)) null else val,
+ .width = switch (params.width) {
+ .fit_max => @typeInfo(@TypeOf(val)).int.bits / 4,
+ .auto => null,
+ .explicit => |w| w,
+ },
+ .zero_fill = params.zero_fill,
+ },
+ };
+ }
+
+ fn intFieldString(field: FormatIntField, w: *std.Io.Writer) std.Io.Writer.Error!void {
+ if (field.val) |val| {
+ try w.printInt(val, 16, .lower, .{
+ .width = field.width,
+ .alignment = .right,
+ .fill = if (field.zero_fill) '0' else ' ',
+ });
+ } else try w.splatByteAll('x', field.width orelse 1);
+ }
+
+ fn dumpFlags(w: *Io.Writer, comptime fmt: []const u8, comptime T: type, flags: *const T, cols: u32) !void {
+ const s = @typeInfo(T).@"struct";
+ inline for (s.field_names, s.field_types) |field_name, field_type| {
+ if (field_type == bool and @field(flags, field_name)) {
+ try w.splatByteAll(' ', cols);
+ try w.print(fmt, .{field_name});
+ }
+ }
+ }
+
+ fn dumpArchiveHeader(d: *const DumpContext, header: *const ArchiveHeader, pos: u32) !void {
+ if (d.element(.@"header-name"))
+ try d.w.print("Archive member at offset 0x{x}: '{s}'\n", .{ pos, header.name });
+ try dumpHeader(d, ArchiveHeader, header, struct {
+ pub fn name(_: *const DumpContext, _: *const ArchiveHeader) !void {}
+ pub fn file_mode(id: *const DumpContext, h: *const ArchiveHeader) !void {
+ try id.w.print("{o: >16} file_mode\n", .{h.file_mode});
+ }
+ });
+ }
+
+ fn fieldKind(name: []const u8) ?FieldKind {
+ if (std.mem.endsWith(u8, name, "_rva"))
+ return .rva;
+ if (std.mem.endsWith(u8, name, "_va") or
+ std.mem.endsWith(u8, name, "_address") or
+ std.mem.startsWith(u8, name, "pointer_"))
+ return .va;
+ if (std.mem.startsWith(u8, name, "number_") or
+ std.mem.startsWith(u8, name, "size"))
+ return .size;
+ if (std.mem.startsWith(u8, name, "hint"))
+ return .ord;
+ return null;
+ }
+
+ fn dumpHeader(
+ d: *const DumpContext,
+ comptime T: type,
+ header: *const T,
+ Custom: type,
+ ) !void {
+ const s = @typeInfo(T).@"struct";
+ inline for (s.field_names, s.field_types) |field_name, field_type| {
+ const val = &@field(header, field_name);
+ if (@hasDecl(Custom, field_name)) {
+ try @field(Custom, field_name)(d, header);
+ } else {
+ switch (@typeInfo(field_type)) {
+ .int => try d.w.print("{f} {s}\n", .{ fmtIntField(d, val.*, .{
+ .kind = comptime fieldKind(field_name),
+ .width = .{ .explicit = 16 },
+ }), field_name }),
+ .@"enum" => try d.w.print("{x: >16} {s} ({t})\n", .{ val.*, field_name, val.* }),
+ .@"struct" => |s_field| {
+ switch (s_field.layout) {
+ .auto,
+ .@"extern",
+ => try dumpHeader(d, field_type, val, Custom),
+ .@"packed" => {
+ try d.w.print("{x: >16} {s}\n", .{ @as(s_field.backing_integer.?, @bitCast(val.*)), field_name });
+ try dumpFlags(d.w, "| {s}\n", field_type, val, 15);
+ },
+ }
+ },
+ else => unreachable,
+ }
+ }
+ }
+ }
+
+ fn dumpVersionField(w: *Io.Writer, name: []const u8, major: anytype, minor: anytype) !void {
+ try w.print("{d: >13}.{x:0<2} {s}\n", .{ major, minor, name });
+ }
+
+ fn dumpRvaField(d: *const DumpContext, name: []const u8, rva: u64, base: u64) !void {
+ try d.w.print("{f} {s} ({f})\n", .{
+ fmtIntField(d, rva, .{ .kind = .rva }),
+ name,
+ fmtIntField(d, base + rva, .{ .kind = .va }),
+ });
+ }
+};
+
+fn filterMatches(filters: []const []const u8, val: []const u8) bool {
+ return for (filters) |filter| {
+ if (std.mem.containsAtLeast(u8, val, 1, filter)) break true;
+ } else filters.len == 0;
+}
+
const usage =
\\Usage: zig objdump [options] file
\\
\\Options:
- \\ -h, --help Print this help and exit
- \\
+ \\ -h, --help Print this help and exit
+ \\ --all-headers Alias for --file-headers --linker-member=2 --member-headers --section-headers --relocs --symbols
+ \\ --exports[=sort] Display exported symbols.
+ \\ In the case of COFF import libraries, displays the symbol list and import headers.
+ \\ Specify =sort to optionally sort the import headers by symbol name.
+ \\ --file-headers Display file-format specific headers
+ \\ --imports Display imported symbols
+ \\ --linker-member[=1|2|longnames] (Coff) Display contents of the specified archive linker member (default 2)
+ \\ --member-headers Display archive member headers
+ \\ --elements=[e1],[e2],-[e3],... Select which formatting elements are displayed. Intended for snapshot testing.
+ \\ file-type File type summary
+ \\ header-name Name that precedes a header block
+ \\ member-path Display full member paths. If removed, only basenames will be used.
+ \\ newlines Newlines between output sections
+ \\ table-header Table headers with column names
+ \\ all (default) All of the above
+ \\ --only-member=[name] Only consider archive members names that contain [name]. Can be specified multiple times.
+ \\ --only-section=[name] Only consider section names that contain [name]. Can be specified multiple times.
+ \\ --only-symbol=[name] Only consider symbol names that contain [name]. Can be specified multiple times.
+ \\ --redact=[kind] Redact the specified field kind. Intended for snapshot testing.
+ \\ rva Relative virtual addresses
+ \\ va Virtual addresses and file offsets
+ \\ ord Symbol ordinals / hints
+ \\ size Sizes and lengths
+ \\ all All of the above
+ \\ --relocs Display relocations
+ \\ -s, --snapshot Alias for --redact=all --elements=-all
+ \\ --section-headers Display section headers
+ \\ --strings Display string tables
+ \\ --symbols Display symbol tables
+ \\ --tls Display TLS information
;
diff --git a/lib/compiler/resinator/cvtres.zig b/lib/compiler/resinator/cvtres.zig
@@ -383,7 +383,7 @@ pub fn writeCoff(
fn writeSymbol(writer: *std.Io.Writer, symbol: std.coff.Symbol) !void {
try writer.writeAll(&symbol.name);
try writer.writeInt(u32, symbol.value, .little);
- try writer.writeInt(u16, @intFromEnum(symbol.section_number), .little);
+ try writer.writeInt(i16, @intFromEnum(symbol.section_number), .little);
try writer.writeInt(u8, @intFromEnum(symbol.type.base_type), .little);
try writer.writeInt(u8, @intFromEnum(symbol.type.complex_type), .little);
try writer.writeInt(u8, @intFromEnum(symbol.storage_class), .little);
diff --git a/lib/std/Build/Configuration.zig b/lib/std/Build/Configuration.zig
@@ -587,6 +587,8 @@ pub const Step = extern struct {
expect_stderr_match: Storage.FlagLengthPrefixedList(.flags2, .expect_stderr_match, Bytes),
expect_stdout_match: Storage.FlagLengthPrefixedList(.flags2, .expect_stdout_match, Bytes),
expect_term_value: Storage.FlagOptional(.flags2, .expect_term, u32),
+ expect_stdout_snapshot: Storage.FlagOptional(.flags2, .expect_stdout_snapshot, LazyPath.Index),
+ expect_stderr_snapshot: Storage.FlagOptional(.flags2, .expect_stderr_snapshot, LazyPath.Index),
pub const CapturedStream = extern struct {
generated_file: GeneratedFileIndex,
@@ -686,7 +688,9 @@ pub const Step = extern struct {
expect_stdout_match: bool,
expect_term: bool,
expect_term_status: ExpectTermStatus,
- _: u25 = 0,
+ expect_stdout_snapshot: bool,
+ expect_stderr_snapshot: bool,
+ _: u23 = 0,
};
};
diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig
@@ -133,6 +133,8 @@ pub const StdIo = union(enum) {
expect_stdout_exact: []const u8,
expect_stdout_match: []const u8,
expect_term: process.Child.Term,
+ expect_stderr_snapshot: std.Build.LazyPath,
+ expect_stdout_snapshot: std.Build.LazyPath,
};
};
@@ -682,6 +684,13 @@ pub fn addCheck(run: *Run, new_check: StdIo.Check) void {
.check => |*checks| checks.append(b.allocator, new_check) catch @panic("OOM"),
else => @panic("illegal call to addCheck: conflicting helper method calls. Suggest to directly set stdio field of Run instead"),
}
+
+ switch (new_check) {
+ .expect_stderr_snapshot,
+ .expect_stdout_snapshot,
+ => |file| run.addFileInput(file),
+ else => {},
+ }
}
pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath {
diff --git a/lib/std/coff.zig b/lib/std/coff.zig
@@ -2,6 +2,12 @@ const std = @import("std.zig");
const assert = std.debug.assert;
const mem = std.mem;
+pub const archive_signature = "!<arch>\n";
+pub const archive_end_of_header = "`\n";
+
+pub const pe_signature = "PE\x00\x00";
+pub const pe_pointer_offset = 0x3C;
+
pub const Header = extern struct {
/// The number that identifies the type of target machine.
machine: IMAGE.FILE.MACHINE,
@@ -367,6 +373,36 @@ pub const DebugType = enum(u32) {
_,
};
+pub fn TlsDirectoryEntry(comptime magic: std.coff.OptionalHeader.Magic) type {
+ return switch (magic) {
+ _ => comptime unreachable,
+ .PE32 => extern struct {
+ raw_data_start_va: u32,
+ raw_data_end_va: u32,
+ tls_index_va: u32,
+ callbacks_va: u32,
+ size_of_zero_fill: u32,
+ characteristics: packed struct(u32) {
+ _reserved_0: u19,
+ alignment: SectionHeader.Flags.Align,
+ _reserved_1: u9,
+ },
+ },
+ .@"PE32+" => extern struct {
+ raw_data_start_va: u64,
+ raw_data_end_va: u64,
+ tls_index_va: u64,
+ callbacks_va: u64,
+ size_of_zero_fill: u32,
+ characteristics: packed struct(u32) {
+ _reserved_0: u19,
+ alignment: SectionHeader.Flags.Align,
+ _reserved_1: u9,
+ },
+ },
+ };
+}
+
pub const ImportDirectoryEntry = extern struct {
/// The RVA of the import lookup table.
/// This table contains a name or ordinal for each import.
@@ -389,56 +425,28 @@ pub const ImportDirectoryEntry = extern struct {
import_address_table_rva: u32,
};
-pub const ImportLookupEntry32 = struct {
- pub const ByName = packed struct(u32) {
- name_table_rva: u31,
- flag: u1 = 0,
+pub fn ImportLookupTableEntry(comptime magic: std.coff.OptionalHeader.Magic) type {
+ const Payload = packed union(u31) {
+ ordinal: packed struct(u31) {
+ ordinal: u16,
+ _: u15 = 0,
+ },
+ hint_name_rva: u31,
};
- pub const ByOrdinal = packed struct(u32) {
- ordinal_number: u16,
- unused: u15 = 0,
- flag: u1 = 1,
+ return switch (magic) {
+ _ => comptime unreachable,
+ .PE32 => packed struct(u32) {
+ payload: Payload,
+ is_ordinal: bool,
+ },
+ .@"PE32+" => packed struct(u64) {
+ payload: Payload,
+ _: u32 = 0,
+ is_ordinal: bool,
+ },
};
-
- const mask = 0x80000000;
-
- pub fn getImportByName(raw: u32) ?ByName {
- if (mask & raw != 0) return null;
- return @as(ByName, @bitCast(raw));
- }
-
- pub fn getImportByOrdinal(raw: u32) ?ByOrdinal {
- if (mask & raw == 0) return null;
- return @as(ByOrdinal, @bitCast(raw));
- }
-};
-
-pub const ImportLookupEntry64 = struct {
- pub const ByName = packed struct(u64) {
- name_table_rva: u31,
- unused: u32 = 0,
- flag: u1 = 0,
- };
-
- pub const ByOrdinal = packed struct(u64) {
- ordinal_number: u16,
- unused: u47 = 0,
- flag: u1 = 1,
- };
-
- const mask = 0x8000000000000000;
-
- pub fn getImportByName(raw: u64) ?ByName {
- if (mask & raw != 0) return null;
- return @as(ByName, @bitCast(raw));
- }
-
- pub fn getImportByOrdinal(raw: u64) ?ByOrdinal {
- if (mask & raw == 0) return null;
- return @as(ByOrdinal, @bitCast(raw));
- }
-};
+}
/// Every name ends with a NULL byte. IF the NULL byte does not fall on
/// 2byte boundary, the entry structure is padded to ensure 2byte alignment.
@@ -452,6 +460,50 @@ pub const ImportHintNameEntry = extern struct {
name: [1]u8,
};
+pub const ExportDirectoryTable = extern struct {
+ /// Reserved
+ flags: u32,
+
+ /// Creation time of this table
+ time_date_stamp: u32,
+
+ major_version: u16,
+ minor_version: u16,
+
+ /// The address of an ASCII string that contains the name of the DLL.
+ /// This address is relative to the image base.
+ name_rva: u32,
+
+ /// The ordinal of the first export in this image
+ ordinal_base: u32,
+
+ /// Number of entries in the export address table
+ number_of_entries: u32,
+
+ /// Number of entries in the name pointer table and ordinal table
+ number_of_names: u32,
+
+ export_address_table_rva: u32,
+ name_pointer_table_rva: u32,
+ ordinal_table_rva: u32,
+};
+
+pub const ExportAddressTableEntry = extern struct {
+ /// If this address is within the export section, then this is the address of the export
+ /// Otherwise, this is the address of a string that specfies a symbol in another DLL:
+ /// <dll name>.<export name>
+ /// <dll name>.#<export ordinal>
+ export_or_forwarder_rva: u32,
+};
+
+pub const ExportNamePointerTableEntry = extern struct {
+ name_rva: u32,
+};
+
+pub const ExportOrdinalTableEntry = extern struct {
+ unbiased_ordinal: u16,
+};
+
pub const SectionHeader = extern struct {
name: [8]u8,
virtual_size: u32,
@@ -610,11 +662,15 @@ pub const SectionHeader = extern struct {
std.debug.assert(std.math.isPowerOfTwo(n));
return @enumFromInt(@ctz(n) + 1);
}
+
+ pub fn alignment(a: Align) ?std.mem.Alignment {
+ return .fromByteUnitsOptional(a.toByteUnits() orelse null);
+ }
};
};
};
-pub const Symbol = struct {
+pub const Symbol = extern struct {
name: [8]u8,
value: u32,
section_number: SectionNumber,
@@ -622,7 +678,7 @@ pub const Symbol = struct {
storage_class: StorageClass,
number_of_aux_symbols: u8,
- pub fn sizeOf() usize {
+ pub fn sizeOf() comptime_int {
return 18;
}
@@ -639,18 +695,18 @@ pub const Symbol = struct {
}
};
-pub const SectionNumber = enum(u16) {
+pub const SectionNumber = enum(i16) {
/// The symbol record is not yet assigned a section.
/// A value of zero indicates that a reference to an external symbol is defined elsewhere.
/// A value of non-zero is a common symbol with a size that is specified by the value.
UNDEFINED = 0,
/// The symbol has an absolute (non-relocatable) value and is not an address.
- ABSOLUTE = 0xffff,
+ ABSOLUTE = -1,
/// The symbol provides general type or debugging information but does not correspond to a section.
/// Microsoft tools use this setting along with .file records (storage class FILE).
- DEBUG = 0xfffe,
+ DEBUG = -2,
_,
};
@@ -822,7 +878,7 @@ pub const StorageClass = enum(u8) {
_,
};
-pub const FunctionDefinition = struct {
+pub const FunctionDefinition = extern struct {
/// The symbol-table index of the corresponding .bf (begin function) symbol record.
tag_index: u32,
@@ -841,7 +897,7 @@ pub const FunctionDefinition = struct {
unused: [2]u8,
};
-pub const SectionDefinition = struct {
+pub const SectionDefinition = extern struct {
/// The size of section data; the same as SizeOfRawData in the section header.
length: u32,
@@ -863,7 +919,7 @@ pub const SectionDefinition = struct {
unused: [3]u8,
};
-pub const FileDefinition = struct {
+pub const FileDefinition = extern struct {
/// An ANSI string that gives the name of the source file.
/// This is padded with nulls if it is less than the maximum length.
file_name: [18]u8,
@@ -874,7 +930,7 @@ pub const FileDefinition = struct {
}
};
-pub const WeakExternalDefinition = struct {
+pub const WeakExternalDefinition = extern struct {
/// The symbol-table index of sym2, the symbol to be linked if sym1 is not found.
tag_index: u32,
@@ -885,7 +941,7 @@ pub const WeakExternalDefinition = struct {
unused: [10]u8,
- pub fn sizeOf() usize {
+ pub fn sizeOf() comptime_int {
return 18;
}
};
@@ -933,7 +989,7 @@ pub const ComdatSelection = enum(u8) {
_,
};
-pub const DebugInfoDefinition = struct {
+pub const DebugInfoDefinition = extern struct {
unused_1: [4]u8,
/// The actual ordinal line number (1, 2, 3, and so on) within the source file, corresponding to the .bf or .ef record.
@@ -971,13 +1027,10 @@ pub const Coff = struct {
// The lifetime of `data` must be longer than the lifetime of the returned Coff
pub fn init(data: []const u8, is_loaded: bool) error{ EndOfStream, MissingPEHeader }!Coff {
- const pe_pointer_offset = 0x3C;
- const pe_magic = "PE\x00\x00";
-
if (data.len < pe_pointer_offset + 4) return error.EndOfStream;
const header_offset = mem.readInt(u32, data[pe_pointer_offset..][0..4], .little);
if (data.len < header_offset + 4) return error.EndOfStream;
- const is_image = mem.eql(u8, data[header_offset..][0..4], pe_magic);
+ const is_image = mem.eql(u8, data[header_offset..][0..4], pe_signature);
const coff: Coff = .{
.data = data,
@@ -1348,6 +1401,10 @@ pub const Relocation = extern struct {
virtual_address: u32,
symbol_table_index: u32,
type: u16,
+
+ pub fn sizeOf() comptime_int {
+ return 10;
+ }
};
pub const IMAGE = struct {
@@ -1465,6 +1522,36 @@ pub const IMAGE = struct {
_,
/// AXP 64 (Same as Alpha 64)
pub const AXP64: IMAGE.FILE.MACHINE = .ALPHA64;
+
+ pub fn RelocationType(comptime machine: IMAGE.FILE.MACHINE) type {
+ return switch (machine) {
+ .AMD64,
+ => REL.AMD64,
+ .ARM,
+ .ARMNT,
+ => REL.ARM,
+ .ARM64,
+ .ARM64EC,
+ .ARM64X,
+ => REL.ARM64,
+ .I386 => REL.I386,
+ .IA64 => REL.IA64,
+ .M32R => REL.M32R,
+ .MIPS16,
+ .MIPSFPU,
+ .MIPSFPU16,
+ => REL.MIPS,
+ .POWERPC,
+ .POWERPCFP,
+ => REL.PPC,
+ .SH3,
+ .SH3DSP,
+ .SH4,
+ .SH5,
+ => REL.SH,
+ else => void,
+ };
+ }
};
};
@@ -1919,3 +2006,100 @@ pub const IMAGE = struct {
};
};
};
+
+pub const ArchiveMemberHeader = extern struct {
+ /// Left-justified '/' terminated member name
+ name: [16]u8,
+ /// Left-justified ASCII decimal: seconds since January 1st, 1970
+ date: [12]u8,
+ /// Left-justified ASCII decimal: user id
+ user_id: [6]u8,
+ /// Left-justified ASCII decimal: group id
+ group_id: [6]u8,
+ /// Left-justified ASCII octal: file mode
+ file_mode: [8]u8,
+ /// Left-justified ASCII decimal: size of the member following this header,
+ /// not including the size of this header.
+ size: [10]u8,
+ /// The literal string '`\n'
+ end_of_header: [2]u8,
+
+ /// Extracts the name of the member by either reading it directly from
+ /// the header, or by finding it inside the longnames member, if provided.
+ pub fn parseName(
+ self: *const ArchiveMemberHeader,
+ opt_longnames: ?[]const u8,
+ ) ![]const u8 {
+ const trim = std.mem.trimEnd(u8, &self.name, &.{' '});
+
+ if (trim.len == 0) return error.BadName;
+ return if (trim[0] == '/') name: {
+ if (trim.len == 1 or
+ trim.len == 2 and trim[1] == '/')
+ break :name trim;
+
+ const offset = std.fmt.parseUnsigned(u50, trim[1..], 10) catch
+ return error.BadName;
+
+ if (opt_longnames) |longnames| {
+ if (offset >= longnames.len) return error.BadName;
+ break :name std.mem.sliceTo(longnames[@intCast(offset)..], 0);
+ } else return error.NoLongNames;
+ } else if (trim[trim.len - 1] == '/')
+ trim[0 .. trim.len - 1]
+ else
+ return error.BadName;
+ }
+
+ fn parseField(field: []const u8, T: type, base: u8) !T {
+ if (std.mem.allEqual(u8, field, ' ')) return 0;
+ if (field[0] == '-')
+ return @bitCast(try std.fmt.parseInt(
+ @Int(.signed, @typeInfo(T).int.bits),
+ std.mem.trimEnd(u8, field, &.{' '}),
+ base,
+ ));
+
+ return std.fmt.parseUnsigned(T, std.mem.trimEnd(u8, field, &.{' '}), base);
+ }
+
+ pub fn parseDate(self: *const ArchiveMemberHeader) !u40 {
+ return parseField(&self.date, u40, 10);
+ }
+
+ pub fn parseUserId(self: *const ArchiveMemberHeader) !u20 {
+ return parseField(&self.user_id, u20, 10);
+ }
+
+ pub fn parseGroupId(self: *const ArchiveMemberHeader) !u20 {
+ return parseField(&self.group_id, u20, 10);
+ }
+
+ pub fn parseFileMode(self: *const ArchiveMemberHeader) !u20 {
+ return parseField(&self.group_id, u20, 8);
+ }
+
+ pub fn parseSize(self: *const ArchiveMemberHeader) !u34 {
+ return parseField(&self.size, u34, 10);
+ }
+
+ pub const Kind = enum {
+ first_linker,
+ second_linker,
+ longnames,
+ coff,
+ import,
+ };
+};
+
+pub const LineNumber = extern struct {
+ type: extern union {
+ symbol_table_index: u32,
+ virtual_address: u32,
+ },
+ line_number: u16,
+
+ pub fn sizeOf() comptime_int {
+ return 6;
+ }
+};
diff --git a/lib/std/meta.zig b/lib/std/meta.zig
@@ -499,6 +499,15 @@ test DeclEnum {
try expectEqualEnum(enum {}, DeclEnum(D));
}
+pub fn BareUnion(comptime T: type) type {
+ const u = switch (@typeInfo(T)) {
+ .@"union" => |u| u,
+ else => @compileError("expected union type, found '" ++ @typeName(T) ++ "'"),
+ };
+
+ return @Union(u.layout, null, u.field_names, u.field_types[0..], u.field_attrs[0..]);
+}
+
pub fn Tag(comptime T: type) type {
return switch (@typeInfo(T)) {
.@"enum" => |info| info.tag_type,
diff --git a/lib/std/multi_array_list.zig b/lib/std/multi_array_list.zig
@@ -44,7 +44,7 @@ pub fn MultiArrayList(comptime T: type) type {
const Elem = switch (@typeInfo(T)) {
.@"struct" => T,
.@"union" => |u| struct {
- pub const Bare = @Union(u.layout, null, u.field_names, u.field_types[0..], u.field_attrs[0..]);
+ pub const Bare = std.meta.BareUnion(T);
pub const Tag =
u.tag_type orelse @compileError("MultiArrayList does not support untagged unions");
tags: Tag,
diff --git a/lib/std/start.zig b/lib/std/start.zig
@@ -93,7 +93,7 @@ fn DllMainCRTStartup(
fdwReason: std.os.windows.DWORD,
lpReserved: std.os.windows.LPVOID,
) callconv(.winapi) std.os.windows.BOOL {
- if (!builtin.single_threaded and !builtin.link_libc) {
+ if (!builtin.single_threaded) {
_ = @import("os/windows/tls.zig");
}
diff --git a/lib/std/zig.zig b/lib/std/zig.zig
@@ -996,14 +996,11 @@ pub const EmitArtifact = enum {
docs,
pdb,
h,
- compiler_rt_dyn_lib,
/// If using `Server` to communicate with the compiler, it will place requested artifacts in
/// paths under the output directory, where those paths are named according to this function.
/// Returned string is allocated with `gpa` and owned by the caller.
pub fn cacheName(ea: EmitArtifact, gpa: Allocator, opts: BinNameOptions) Allocator.Error![]const u8 {
- // hack for stage2_x86_64 + coff. See Coff.flush.
- if (ea == .compiler_rt_dyn_lib) return "compiler_rt.dll";
const suffix: []const u8 = switch (ea) {
.bin => return binNameAlloc(gpa, opts),
.@"asm" => ".s",
@@ -1013,7 +1010,6 @@ pub const EmitArtifact = enum {
.docs => "-docs",
.pdb => ".pdb",
.h => ".h",
- .compiler_rt_dyn_lib => unreachable,
};
return std.fmt.allocPrint(gpa, "{s}{s}", .{ opts.root_name, suffix });
}
diff --git a/src/Compilation.zig b/src/Compilation.zig
@@ -223,8 +223,6 @@ compiler_rt_lib: ?CrtFile = null,
/// Populated when we build the compiler_rt_obj object. A Job to build this is indicated
/// by setting `queued_jobs.compiler_rt_obj` and resolved before calling linker.flush().
compiler_rt_obj: ?CrtFile = null,
-/// hack for stage2_x86_64 + coff
-compiler_rt_dyn_lib: ?CrtFile = null,
/// Populated when we build the libfuzzer static library. A Job to build this
/// is indicated by setting `queued_jobs.fuzzer_lib` and resolved before
/// calling linker.flush().
@@ -287,8 +285,6 @@ emit_llvm_bc: ?[]const u8,
emit_docs: ?[]const u8,
const QueuedJobs = struct {
- /// hack for stage2_x86_64 + coff
- compiler_rt_dyn_lib: bool = false,
compiler_rt_lib: bool = false,
compiler_rt_obj: bool = false,
ubsan_rt_lib: bool = false,
@@ -1781,7 +1777,7 @@ fn addModuleTableToCacheHash(
}
}
-const RtStrat = enum { none, lib, obj, zcu, dyn_lib };
+const RtStrat = enum { none, lib, obj, zcu };
pub const CreateDiagnostic = union(enum) {
export_table_import_table_conflict,
@@ -1902,12 +1898,6 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
};
if (have_zcu and (!need_llvm or use_llvm)) {
if (output_mode == .Obj) break :s .zcu;
- switch (target_util.zigBackend(target, use_llvm)) {
- else => {},
- .stage2_aarch64, .stage2_x86_64 => if (target.ofmt == .coff) {
- break :s if (is_exe_or_dyn_lib and build_options.have_llvm) .dyn_lib else .zcu;
- },
- }
}
if (need_llvm and !build_options.have_llvm) break :s .none; // impossible to build without llvm
if (is_exe_or_dyn_lib) break :s .lib;
@@ -2628,11 +2618,6 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
log.debug("queuing a job to build compiler_rt_obj", .{});
comp.queued_jobs.compiler_rt_obj = true;
},
- .dyn_lib => {
- // hack for stage2_x86_64 + coff
- log.debug("queuing a job to build compiler_rt_dyn_lib", .{});
- comp.queued_jobs.compiler_rt_dyn_lib = true;
- },
}
switch (comp.ubsan_rt_strat) {
@@ -2645,7 +2630,6 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
log.debug("queuing a job to build ubsan_rt_obj", .{});
comp.queued_jobs.ubsan_rt_obj = true;
},
- .dyn_lib => unreachable, // hack for compiler_rt only
}
switch (comp.zigc_strat) {
@@ -2654,7 +2638,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
log.debug("queuing a job to build libzigc", .{});
comp.queued_jobs.zigc_lib = true;
},
- .obj, .dyn_lib => unreachable, // only available as a static library or inside an existing ZCU
+ .obj => unreachable, // only available as a static library or inside an existing ZCU
}
if (is_exe_or_dyn_lib and comp.config.any_fuzz) {
@@ -2713,7 +2697,6 @@ pub fn destroy(comp: *Compilation) void {
if (comp.zigc_static_lib) |*crt_file| crt_file.deinit(gpa, io);
if (comp.compiler_rt_lib) |*crt_file| crt_file.deinit(gpa, io);
if (comp.compiler_rt_obj) |*crt_file| crt_file.deinit(gpa, io);
- if (comp.compiler_rt_dyn_lib) |*crt_file| crt_file.deinit(gpa, io);
if (comp.fuzzer_lib) |*crt_file| crt_file.deinit(gpa, io);
if (comp.glibc_so_files) |*glibc_file| {
@@ -4492,22 +4475,16 @@ fn performAllTheWork(
comp.link_queue.finishZcuQueue(comp);
- // This has to happen after the main semantic analysis loop because it is possible for Sema to
+ // Main thread work is all done, now just wait for all async work.
+ try misc_group.await(io);
+
+ // This has to happen again after the main semantic analysis loop because it is possible for Sema to
// call `addLinkLib` and hence add more items to `comp.windows_libs`.
- for (comp.windows_libs.keys()[comp.windows_libs_num_done..]) |link_lib| {
- mingw.buildImportLib(comp, link_lib) catch |err| {
- // TODO Surface more error details.
- comp.lockAndSetMiscFailure(
- .windows_import_lib,
- "unable to generate DLL import .lib file for {s}: {t}",
- .{ link_lib, err },
- );
- };
- }
+ for (comp.windows_libs.keys()[comp.windows_libs_num_done..]) |lib_name|
+ misc_group.async(io, buildMingwImportLib, .{ comp, lib_name, false, main_progress_node });
comp.windows_libs_num_done = @intCast(comp.windows_libs.count());
-
- // Main thread work is all done, now just wait for all async work.
try misc_group.await(io);
+
comp.link_queue.wait(io);
}
@@ -4566,24 +4543,6 @@ fn dispatchPrelinkWork(comp: *Compilation, main_progress_node: std.Progress.Node
});
}
- // hack for stage2_x86_64 + coff
- if (comp.queued_jobs.compiler_rt_dyn_lib and comp.compiler_rt_dyn_lib == null) {
- prelink_group.async(io, buildRt, .{
- comp,
- "compiler_rt.zig",
- "compiler_rt",
- .Lib,
- .dynamic,
- .compiler_rt,
- main_progress_node,
- RtOptions{
- .checks_valgrind = true,
- .allow_lto = false,
- },
- &comp.compiler_rt_dyn_lib,
- });
- }
-
if (comp.queued_jobs.fuzzer_lib and comp.fuzzer_lib == null) {
prelink_group.async(io, buildRt, .{
comp,
@@ -4727,6 +4686,16 @@ fn dispatchPrelinkWork(comp: *Compilation, main_progress_node: std.Progress.Node
});
}
+ while (comp.windows_libs_num_done < comp.windows_libs.count()) {
+ prelink_group.async(io, buildMingwImportLib, .{
+ comp,
+ comp.windows_libs.keys()[comp.windows_libs_num_done],
+ true,
+ main_progress_node,
+ });
+ comp.windows_libs_num_done += 1;
+ }
+
prelink_group.await(io) catch |err| switch (err) {
error.Canceled => unreachable, // see swapCancelProtection above
};
@@ -5412,6 +5381,39 @@ fn buildMingwCrtFile(comp: *Compilation, crt_file: mingw.CrtFile, prog_node: std
}
}
+fn buildMingwImportLib(comp: *Compilation, lib_name: []const u8, is_prelink: bool, prog_node: std.Progress.Node) void {
+ const crt_file_path = mingw.buildImportLib(comp, lib_name, prog_node) catch |err| switch (err) {
+ // TODO: This isn't actually true for self-hosted
+ // In the non-prelink case we will end up putting foo.lib onto the linker line and letting the linker
+ // use its library paths to look for libraries and report any problems.
+ error.DefNotFound => return if (is_prelink) {
+ comp.lockAndSetMiscFailure(
+ .windows_import_lib,
+ "definition not found for required mingw DLL import .lib {s}",
+ .{lib_name},
+ );
+ },
+ // TODO Surface more error details.
+ else => |e| return comp.lockAndSetMiscFailure(
+ .windows_import_lib,
+ "unable to generate mingw DLL import .lib file for {s}: {t}",
+ .{ lib_name, e },
+ ),
+ };
+
+ if (is_prelink)
+ comp.queuePrelinkTasks(&.{.{
+ .load_archive = .{
+ .path = crt_file_path,
+ .must_link = false,
+ },
+ }}) catch |err| comp.lockAndSetMiscFailure(
+ .windows_import_lib,
+ "unable to queue prelink task for mingw import lib {f}: {t}",
+ .{ crt_file_path, err },
+ );
+}
+
fn buildWasiLibcCrtFile(comp: *Compilation, crt_file: wasi_libc.CrtFile, prog_node: std.Progress.Node) void {
if (wasi_libc.buildCrtFile(comp, crt_file, prog_node)) |_| {
comp.queued_jobs.wasi_libc_crt_file[@intFromEnum(crt_file)] = false;
diff --git a/src/codegen/x86_64/Emit.zig b/src/codegen/x86_64/Emit.zig
@@ -113,6 +113,7 @@ pub fn emitMir(emit: *Emit) Error!void {
.default => true,
.hidden, .protected => false,
},
+ .is_dll_import = @"extern".is_dll_import,
.force_pcrel_direct = switch (@"extern".relocation) {
.any => false,
.pcrel => true,
@@ -154,20 +155,13 @@ pub fn emitMir(emit: *Emit) Error!void {
@enumFromInt(try elf_file.getGlobalSymbol(extern_func.toSlice(&emit.lower.mir).?, null))
else if (emit.bin_file.cast(.elf2)) |elf| try elf.externSymbol(.{
.name = extern_func.toSlice(&emit.lower.mir).?,
- .lib_name = switch (comp.compiler_rt_strat) {
- .none, .lib, .obj, .zcu => null,
- .dyn_lib => "compiler_rt",
- },
+ .lib_name = null,
.type = .FUNC,
}) else if (emit.bin_file.cast(.macho)) |macho_file|
@enumFromInt(try macho_file.getGlobalSymbol(extern_func.toSlice(&emit.lower.mir).?, null))
- else if (emit.bin_file.cast(.coff2)) |coff| @enumFromInt(@intFromEnum(try coff.globalSymbol(
- extern_func.toSlice(&emit.lower.mir).?,
- switch (comp.compiler_rt_strat) {
- .none, .lib, .obj, .zcu => null,
- .dyn_lib => "compiler_rt",
- },
- ))) else return emit.fail("external symbol unimplemented for {s}", .{@tagName(emit.bin_file.tag)}),
+ else if (emit.bin_file.cast(.coff2)) |coff| @enumFromInt(@intFromEnum(try coff.globalSymbol(.{
+ .name = extern_func.toSlice(&emit.lower.mir).?,
+ }))) else return emit.fail("external symbol unimplemented for {s}", .{@tagName(emit.bin_file.tag)}),
.is_extern = true,
} },
},
@@ -179,7 +173,13 @@ pub fn emitMir(emit: *Emit) Error!void {
switch (lowered_inst.encoding.mnemonic) {
.call => {
reloc.target = .{ .branch = target };
- try emit.encodeInst(lowered_inst, reloc_info);
+ if (target.is_dll_import and emit.bin_file.cast(.coff2) != null) {
+ try emit.encodeInst(try .new(.none, .call, &.{
+ .{ .mem = .initRip(.ptr, 0) },
+ }, emit.lower.target), reloc_info);
+ } else {
+ try emit.encodeInst(lowered_inst, reloc_info);
+ }
continue :lowered_inst;
},
else => {},
@@ -255,7 +255,25 @@ pub fn emitMir(emit: *Emit) Error!void {
else => unreachable,
}
} else if (emit.bin_file.cast(.coff2)) |_| {
- switch (lowered_inst.encoding.mnemonic) {
+ if (target.is_dll_import) switch (lowered_inst.encoding.mnemonic) {
+ .lea => try emit.encodeInst(try .new(.none, .mov, &.{
+ lowered_inst.ops[0],
+ .{ .mem = .initRip(.ptr, 0) },
+ }, emit.lower.target), reloc_info),
+ .mov => {
+ try emit.encodeInst(try .new(.none, .mov, &.{
+ lowered_inst.ops[0],
+ .{ .mem = .initRip(.ptr, 0) },
+ }, emit.lower.target), reloc_info);
+ try emit.encodeInst(try .new(.none, .mov, &.{
+ lowered_inst.ops[0],
+ .{ .mem = .initSib(lowered_inst.ops[reloc.op_index].mem.sib.ptr_size, .{ .base = .{
+ .reg = lowered_inst.ops[0].reg.to64(),
+ } }) },
+ }, emit.lower.target), &.{});
+ },
+ else => unreachable,
+ } else switch (lowered_inst.encoding.mnemonic) {
.lea => try emit.encodeInst(try .new(.none, .lea, &.{
lowered_inst.ops[0],
.{ .mem = .initRip(.none, 0) },
@@ -374,7 +392,7 @@ pub fn emitMir(emit: *Emit) Error!void {
.op_index = 1,
.target = .{ .symbol = .{
.symbol = @enumFromInt(@intFromEnum(
- try coff.globalSymbol("__tls_index", null),
+ try coff.globalSymbol(.{ .name = "__tls_index" }),
)),
.is_extern = false,
} },
@@ -409,7 +427,7 @@ pub fn emitMir(emit: *Emit) Error!void {
.op_index = 1,
.target = .{ .symbol = .{
.symbol = @enumFromInt(@intFromEnum(
- try coff.globalSymbol("_tls_index", null),
+ try coff.globalSymbol(.{ .name = "_tls_index" }),
)),
.is_extern = false,
} },
@@ -725,6 +743,7 @@ const RelocInfo = struct {
const Symbol = struct {
symbol: link.File.SymbolId,
is_extern: bool,
+ is_dll_import: bool = false,
force_pcrel_direct: bool = false,
};
};
@@ -816,7 +835,7 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI
@enumFromInt(@intFromEnum(emit.atom_id)),
end_offset - 4,
@enumFromInt(@intFromEnum(target.symbol)),
- reloc.off,
+ .{ .known = reloc.off },
.{ .AMD64 = .REL32 },
) else unreachable,
.branch => |target| if (emit.bin_file.cast(.elf)) |elf_file| {
@@ -854,7 +873,7 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI
@enumFromInt(@intFromEnum(emit.atom_id)),
end_offset - 4,
@enumFromInt(@intFromEnum(target.symbol)),
- reloc.off,
+ .{ .known = reloc.off },
.{ .AMD64 = .REL32 },
) else return emit.fail("TODO implement {s} reloc for {s}", .{
@tagName(reloc.target), @tagName(emit.bin_file.tag),
@@ -912,7 +931,7 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI
@enumFromInt(@intFromEnum(emit.atom_id)),
end_offset - 4,
@enumFromInt(@intFromEnum(target.symbol)),
- reloc.off,
+ .{ .known = reloc.off },
.{ .AMD64 = .SECREL },
) else return emit.fail("TODO implement {s} reloc for {s}", .{
@tagName(reloc.target), @tagName(emit.bin_file.tag),
diff --git a/src/crash_report.zig b/src/crash_report.zig
@@ -84,6 +84,25 @@ pub const CodegenFunc = if (enabled) struct {
pub fn stop(_: InternPool.Index) void {}
};
+pub const LinkerOp = if (enabled) struct {
+ lf: *link.File,
+ tid: Zcu.PerThread.Id,
+ threadlocal var current: ?LinkerOp = null;
+ pub fn start(lf: *link.File, tid: Zcu.PerThread.Id) void {
+ std.debug.assert(current == null);
+ current = .{ .lf = lf, .tid = tid };
+ }
+ pub fn stop(lf: *link.File, tid: Zcu.PerThread.Id) void {
+ std.debug.assert(current.?.lf == lf and current.?.tid == tid);
+ current = null;
+ }
+} else struct {
+ const current: ?noreturn = null;
+ // Dummy implementation
+ pub fn start(_: *link.File, _: Zcu.PerThread.Id) void {}
+ pub fn stop(_: *link.File, _: Zcu.PerThread.Id) void {}
+};
+
fn dumpCrashContext() Io.Writer.Error!void {
const S = struct {
/// In the case of recursive panics or segfaults, don't print the context for a second time.
@@ -111,6 +130,15 @@ fn dumpCrashContext() Io.Writer.Error!void {
try w.print("Generating function '{f}'\n\n", .{func_fqn.fmt(&cg.zcu.intern_pool)});
} else if (AnalyzeBody.current) |anal| {
try dumpCrashContextSema(anal, w, &S.crash_heap);
+ } else if (LinkerOp.current) |linker_op| {
+ try w.writeAll("Linker snapshot:\n");
+ switch (try linker_op.lf.dump(w, linker_op.tid)) {
+ .unimplemented => try w.writeAll("(backend does not support link snapshots)"),
+ .needs_extensions => try w.writeAll("(build with -Ddebug-extensions to dump linker state)"),
+ .disabled => try w.writeAll("(run with --debug-link-snapshot to dump linker state)"),
+ .enabled => {},
+ }
+ try w.writeAll("\n\n");
} else {
try w.writeAll("(no context)\n\n");
}
@@ -185,6 +213,7 @@ const Zir = std.zig.Zir;
const Sema = @import("Sema.zig");
const Zcu = @import("Zcu.zig");
+const link = @import("link.zig");
const InternPool = @import("InternPool.zig");
const dev = @import("dev.zig");
const print_zir = @import("print_zir.zig");
diff --git a/src/libs/mingw.zig b/src/libs/mingw.zig
@@ -207,9 +207,14 @@ fn addCrtCcArgs(
});
}
-pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
+pub fn buildImportLib(comp: *Compilation, lib_name: []const u8, prog_node: std.Progress.Node) !Cache.Path {
dev.check(.build_import_lib);
+ log.debug("buildImportLib({s})", .{lib_name});
+
+ const sub_node = prog_node.start(lib_name, 0);
+ defer sub_node.end();
+
const gpa = comp.gpa;
const io = comp.io;
@@ -218,12 +223,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
const arena = arena_allocator.allocator();
const def_file_path = findDef(arena, io, comp.getTarget(), comp.dirs.zig_lib, lib_name) catch |err| switch (err) {
- error.FileNotFound => {
- log.debug("no {s}.def file available to make a DLL import {s}.lib", .{ lib_name, lib_name });
- // In this case we will end up putting foo.lib onto the linker line and letting the linker
- // use its library paths to look for libraries and report any problems.
- return;
- },
+ error.FileNotFound => return error.DefNotFound,
else => |e| return e,
};
// Only .def.in files need preprocessing
@@ -263,14 +263,16 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
comp.mutex.lockUncancelable(io);
defer comp.mutex.unlock(io);
try comp.crt_files.ensureUnusedCapacity(gpa, 1);
+
+ const crt_file_path: Cache.Path = .{
+ .root_dir = comp.dirs.global_cache,
+ .sub_path = sub_path,
+ };
comp.crt_files.putAssumeCapacityNoClobber(final_lib_basename, .{
- .full_object_path = .{
- .root_dir = comp.dirs.global_cache,
- .sub_path = sub_path,
- },
+ .full_object_path = crt_file_path,
.lock = man.toOwnedLock(),
});
- return;
+ return crt_file_path;
}
const digest = man.final();
@@ -294,6 +296,9 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
}
const members = members: {
+ const members_node = sub_node.start("Members", 0);
+ defer members_node.end();
+
const input = switch (def_needs_preprocessing) {
true => pp: {
var aw: Io.Writer.Allocating = .init(gpa);
@@ -357,13 +362,15 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
comp.mutex.lockUncancelable(io);
defer comp.mutex.unlock(io);
+ const crt_file_path: Cache.Path = .{
+ .root_dir = comp.dirs.global_cache,
+ .sub_path = lib_final_path,
+ };
try comp.crt_files.putNoClobber(gpa, final_lib_basename, .{
- .full_object_path = .{
- .root_dir = comp.dirs.global_cache,
- .sub_path = lib_final_path,
- },
+ .full_object_path = crt_file_path,
.lock = man.toOwnedLock(),
});
+ return crt_file_path;
}
pub fn libExists(
diff --git a/src/libs/mingw/implib.zig b/src/libs/mingw/implib.zig
@@ -1012,7 +1012,7 @@ fn getShortImport(
fn writeSymbol(writer: *std.Io.Writer, symbol: std.coff.Symbol) !void {
try writer.writeAll(&symbol.name);
try writer.writeInt(u32, symbol.value, .little);
- try writer.writeInt(u16, @intFromEnum(symbol.section_number), .little);
+ try writer.writeInt(i16, @intFromEnum(symbol.section_number), .little);
try writer.writeInt(u8, @intFromEnum(symbol.type.base_type), .little);
try writer.writeInt(u8, @intFromEnum(symbol.type.complex_type), .little);
try writer.writeInt(u8, @intFromEnum(symbol.storage_class), .little);
diff --git a/src/link.zig b/src/link.zig
@@ -25,6 +25,7 @@ const Package = @import("Package.zig");
const dev = @import("dev.zig");
const target_util = @import("target.zig");
const codegen = @import("codegen.zig");
+const crash_report = @import("crash_report.zig");
pub const aarch64 = @import("link/aarch64.zig");
pub const LdScript = @import("link/LdScript.zig");
@@ -481,7 +482,7 @@ pub const File = struct {
rpath_list: []const []const u8,
/// Zig compiler development linker flags.
- /// Enable dumping of linker's state as JSON.
+ /// Enable dumping of linker's state.
enable_link_snapshots: bool,
/// Darwin-specific linker flags:
@@ -790,6 +791,7 @@ pub const File = struct {
assert(base.comp.zcu.?.llvm_object == null);
const nav = pt.zcu.intern_pool.getNav(nav_index);
assert(nav.resolved.?.value != .none);
+
switch (base.tag) {
.lld => unreachable,
.plan9 => unreachable,
@@ -924,6 +926,9 @@ pub const File = struct {
/// Commit pending changes and write headers. Takes into account final output mode.
/// `arena` has the lifetime of the call to `Compilation.update`.
pub fn flush(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) Error!void {
+ crash_report.LinkerOp.start(base, tid);
+ defer crash_report.LinkerOp.stop(base, tid);
+
const comp = base.comp;
const io = comp.io;
if (comp.clang_preprocessor_mode == .yes or comp.clang_preprocessor_mode == .pch) {
@@ -975,6 +980,10 @@ pub const File = struct {
export_indices: []const Zcu.Export.Index,
) Error!void {
assert(base.comp.zcu.?.llvm_object == null);
+
+ crash_report.LinkerOp.start(base, pt.tid);
+ defer crash_report.LinkerOp.stop(base, pt.tid);
+
switch (base.tag) {
.lld => unreachable,
.plan9 => unreachable,
@@ -1006,6 +1015,7 @@ pub const File = struct {
/// Never called when LLVM is codegenning the ZCU.
pub fn getNavVAddr(base: *File, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, reloc_info: RelocInfo) Error!u64 {
assert(base.comp.zcu.?.llvm_object == null);
+
switch (base.tag) {
.lld => unreachable,
.c => unreachable,
@@ -1027,6 +1037,7 @@ pub const File = struct {
decl_align: InternPool.Alignment,
) Error!SymbolId {
assert(base.comp.zcu.?.llvm_object == null);
+
switch (base.tag) {
.lld => unreachable,
.c => unreachable,
@@ -1043,6 +1054,7 @@ pub const File = struct {
/// Never called when LLVM is codegenning the ZCU.
pub fn getUavVAddr(base: *File, decl_val: InternPool.Index, reloc_info: RelocInfo) Error!u64 {
assert(base.comp.zcu.?.llvm_object == null);
+
switch (base.tag) {
.lld => unreachable,
.c => unreachable,
@@ -1063,6 +1075,7 @@ pub const File = struct {
name: InternPool.NullTerminatedString,
) void {
assert(base.comp.zcu.?.llvm_object == null);
+
switch (base.tag) {
.lld => unreachable,
.plan9 => unreachable,
@@ -1077,6 +1090,31 @@ pub const File = struct {
}
}
+ pub const DumpResult = enum {
+ unimplemented,
+ needs_extensions,
+ disabled,
+ enabled,
+ };
+
+ pub fn dump(base: *File, w: *Io.Writer, tid: Zcu.PerThread.Id) !DumpResult {
+ if (!build_options.enable_debug_extensions) return .not_built;
+ switch (base.tag) {
+ .elf,
+ .macho,
+ .c,
+ .wasm,
+ .spirv,
+ .plan9,
+ .lld,
+ => return .unimplemented,
+ inline else => |tag| {
+ dev.check(tag.devFeature());
+ return @as(*tag.Type(), @fieldParentPtr("base", base)).dump(w, tid);
+ },
+ }
+ }
+
/// Opens a path as an object file and parses it into the linker.
fn openLoadObject(base: *File, path: Path) anyerror!void {
if (base.tag == .lld) return;
@@ -1178,8 +1216,9 @@ pub const File = struct {
pub fn loadInput(base: *File, input: Input) anyerror!void {
if (base.tag == .lld) return;
assert(!base.post_prelink);
+
switch (base.tag) {
- inline .elf, .elf2, .wasm, .spirv => |tag| {
+ inline .coff2, .elf, .elf2, .wasm, .spirv => |tag| {
dev.check(tag.devFeature());
return @as(*tag.Type(), @fieldParentPtr("base", base)).loadInput(input);
},
@@ -1441,7 +1480,8 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {
const target = &comp.root_mod.resolved_target.result;
const flags = target_util.libcFullLinkFlags(target);
- const crt_dir = comp.libc_installation.?.crt_dir.?;
+ const libc_installation = comp.libc_installation.?;
+ const crt_dir = libc_installation.crt_dir.?;
const sep = std.fs.path.sep_str;
for (flags) |flag| {
assert(mem.startsWith(u8, flag, "-l"));
@@ -1493,6 +1533,54 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {
},
}
}
+
+ if (target.os.tag == .windows and target.abi == .msvc) {
+ const inputs: []const struct {
+ dir: enum { crt, msvc_lib, kernel32_lib },
+ name: []const u8,
+ } = switch (comp.config.link_mode) {
+ .dynamic => &.{
+ .{ .dir = .msvc_lib, .name = "msvcrt.lib" },
+ .{ .dir = .msvc_lib, .name = "vcruntime.lib" },
+ .{ .dir = .msvc_lib, .name = "legacy_stdio_definitions.lib" },
+ .{ .dir = .crt, .name = "ucrt.lib" },
+ .{ .dir = .kernel32_lib, .name = "kernel32.lib" },
+ .{ .dir = .kernel32_lib, .name = "ntdll.lib" },
+ },
+ .static => &.{
+ .{ .dir = .msvc_lib, .name = "libcmt.lib" },
+ .{ .dir = .msvc_lib, .name = "libvcruntime.lib" },
+ .{ .dir = .msvc_lib, .name = "legacy_stdio_definitions.lib" },
+ .{ .dir = .crt, .name = "libucrt.lib" },
+ .{ .dir = .kernel32_lib, .name = "kernel32.lib" },
+ .{ .dir = .kernel32_lib, .name = "ntdll.lib" },
+ },
+ };
+
+ for (inputs) |lib| {
+ const path = Path.initCwd(
+ std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}", .{
+ switch (lib.dir) {
+ .crt => crt_dir,
+ .msvc_lib => libc_installation.msvc_lib_dir.?,
+ .kernel32_lib => libc_installation.kernel32_lib_dir.?,
+ },
+ lib.name,
+ }) catch return diags.setAllocFailure(),
+ );
+ if (std.mem.endsWith(u8, lib.name, "lib")) {
+ base.openLoadArchive(path, false) catch |err| switch (err) {
+ error.LinkFailure => return, // error reported via diags
+ else => |e| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}),
+ };
+ } else {
+ base.openLoadObject(path) catch |err| switch (err) {
+ error.LinkFailure => return, // error reported via diags
+ else => |e| diags.addParseError(path, "failed to parse object: {s}", .{@errorName(e)}),
+ };
+ }
+ }
+ }
},
.load_object => |path| {
const prog_node = comp.link_prog_node.start("Parse Object", 0);
diff --git a/src/link/Coff.zig b/src/link/Coff.zig
@@ -17,11 +17,40 @@ const target_util = @import("../target.zig");
const Type = @import("../Type.zig");
const Value = @import("../Value.zig");
const Zcu = @import("../Zcu.zig");
+const ModuleDefinition = @import("../libs/mingw/def.zig").ModuleDefinition;
+const implib = @import("../libs/mingw/implib.zig");
+const Path = std.Build.Cache.Path;
base: link.File,
+options: link.File.OpenOptions,
mf: MappedFile,
nodes: std.MultiArrayList(Node),
+members: std.ArrayList(Member),
+pending_members: std.array_hash_map.Auto(Member.Index, void),
+lib_string_table: std.ArrayList(String),
+lib_string_len: u32,
+long_names_table: LongNamesTable,
import_table: ImportTable,
+export_table: ExportTable,
+symbol_table: SymbolTable,
+inputs: std.array_hash_map.Custom(std.Build.Cache.Path, void, std.Build.Cache.Path.TableAdapter, false),
+input_archives: std.ArrayList(InputArchive),
+input_archive_members: std.ArrayList(InputArchive.Member),
+input_archive_symbols: std.ArrayList(InputArchive.Member.Symbol),
+input_archive_symbol_indices: std.array_hash_map.Auto(String, InputArchive.SearchList),
+pending_input: ?InputArchive.Member.Index,
+pending_default_libs: std.ArrayList(struct {
+ path: []const u8,
+ ioi: InputObject.Index,
+}),
+alternate_names: std.array_hash_map.Auto(String, String),
+input_objects: std.ArrayList(InputObject),
+input_symbols: std.ArrayList(struct { si: Symbol.Index, name: String }),
+input_sections: std.ArrayList(Node.InputSection),
+input_section_pending_index: u32,
+inputs_complete: bool,
+exports_complete: bool,
+pending_special_symbol: SpecialSymbol,
strings: std.HashMapUnmanaged(
u32,
void,
@@ -29,11 +58,13 @@ strings: std.HashMapUnmanaged(
std.hash_map.default_max_load_percentage,
),
string_bytes: std.ArrayList(u8),
-image_section_table: std.ArrayList(Symbol.Index),
+section_table: std.array_hash_map.Auto(String, Section),
pseudo_section_table: std.array_hash_map.Auto(String, Symbol.Index),
object_section_table: std.array_hash_map.Auto(String, Symbol.Index),
-symbol_table: std.ArrayList(Symbol),
-globals: std.array_hash_map.Auto(GlobalName, Symbol.Index),
+section_merges: std.array_hash_map.Auto(String, String),
+section_merge_pending_index: u32,
+symbols: std.ArrayList(Symbol),
+globals: std.array_hash_map.Auto(String, Global),
global_pending_index: u32,
navs: std.array_hash_map.Auto(InternPool.Nav.Index, Symbol.Index),
uavs: std.array_hash_map.Auto(InternPool.Index, Symbol.Index),
@@ -45,8 +76,13 @@ pending_uavs: std.array_hash_map.Auto(Node.UavMapIndex, struct {
alignment: InternPool.Alignment,
}),
relocs: std.ArrayList(Reloc),
+first_free_reloc: Reloc.Index,
+last_free_reloc: Reloc.Index,
const_prog_node: std.Progress.Node,
synth_prog_node: std.Progress.Node,
+symbol_prog_node: std.Progress.Node,
+member_prog_node: std.Progress.Node,
+input_prog_node: std.Progress.Node,
pub const default_file_alignment: u16 = 0x200;
pub const default_size_of_stack_reserve: u32 = 0x1000000;
@@ -54,6 +90,17 @@ pub const default_size_of_stack_commit: u32 = 0x1000;
pub const default_size_of_heap_reserve: u32 = 0x100000;
pub const default_size_of_heap_commit: u32 = 0x1000;
+pub const imp_prefix = "__imp_";
+
+const header_name_max_len = @typeInfo(@FieldType(std.coff.SectionHeader, "name")).array.len;
+
+const Error = link.Error || error{MappedFileIo};
+const LoadInputError = Error ||
+ Io.File.SeekError ||
+ Io.File.Reader.SizeError ||
+ Io.Reader.Error ||
+ MappedFile.Error;
+
/// This is the start of a Portable Executable (PE) file.
/// It starts with a MS-DOS header followed by a MS-DOS stub program.
/// This data does not change so we include it as follows in all binaries.
@@ -134,25 +181,53 @@ pub const msdos_stub: [120]u8 = .{
pub const Node = union(enum) {
file,
header,
+ /// Images and archives only.
signature,
+ /// Archives only.
+ archive_member_header: Member.Index,
+ archive_member: Member.Index,
+
coff_header,
+
+ /// Image only
optional_header,
data_directories,
+
section_table,
+
+ /// Archives and objects only
+ symbol_table,
+ string_table,
+ relocation_table: Symbol.SectionNumber,
+ relocation_table_entry: Reloc.Index,
+
image_section: Symbol.Index,
+ /// Images only
import_directory_table,
import_lookup_table: ImportTable.Index,
import_address_table: ImportTable.Index,
import_hint_name_table: ImportTable.Index,
+ /// Images only
+ export_directory_table,
+ export_address_table,
+ export_name_pointer_table,
+ export_ordinal_table,
+ export_name_table,
+
pseudo_section: PseudoSectionMapIndex,
object_section: ObjectSectionMapIndex,
- global: GlobalMapIndex,
+ input_section: InputSection.Index,
+ import_thunk: GlobalMapIndex,
nav: NavMapIndex,
uav: UavMapIndex,
lazy_code: LazyMapRef.Index(.code),
lazy_const_data: LazyMapRef.Index(.const_data),
+ builtin: Symbol.Index,
+
+ /// Takes the place of a known node index when that node is not present in the output
+ placeholder,
pub const PseudoSectionMapIndex = enum(u32) {
_,
@@ -179,14 +254,30 @@ pub const Node = union(enum) {
};
pub const GlobalMapIndex = enum(u32) {
+ none,
_,
- pub fn globalName(gmi: GlobalMapIndex, coff: *const Coff) GlobalName {
- return coff.globals.keys()[@intFromEnum(gmi)];
+ pub fn wrap(i: ?u32) GlobalMapIndex {
+ return @enumFromInt((i orelse return .none) + 1);
+ }
+
+ pub fn unwrap(gmi: GlobalMapIndex) ?u32 {
+ return switch (gmi) {
+ .none => null,
+ _ => @intFromEnum(gmi) - 1,
+ };
+ }
+
+ pub fn name(gmi: GlobalMapIndex, coff: *const Coff) String {
+ return coff.globals.keys()[gmi.unwrap().?];
}
pub fn symbol(gmi: GlobalMapIndex, coff: *const Coff) Symbol.Index {
- return coff.globals.values()[@intFromEnum(gmi)];
+ return coff.globals.values()[gmi.unwrap().?].si;
+ }
+
+ pub fn libName(gmi: GlobalMapIndex, coff: *const Coff) String.Optional {
+ return coff.globals.values()[gmi.unwrap().?].lib_name;
}
};
@@ -214,6 +305,47 @@ pub const Node = union(enum) {
}
};
+ const InputSection = struct {
+ ioi: InputObject.Index,
+ si: Symbol.Index,
+ comdat_si: Symbol.Index,
+ file_location: MappedFile.Node.FileLocation,
+ first_li: Node.InputSection.LocalIndex,
+ crc: u32,
+
+ pub const Index = enum(u32) {
+ _,
+
+ pub fn inputSection(isi: Index, coff: *const Coff) *InputSection {
+ return &coff.input_sections.items[@intFromEnum(isi)];
+ }
+
+ pub fn input(isi: Index, coff: *const Coff) InputObject.Index {
+ return coff.input_sections.items[@intFromEnum(isi)].ioi;
+ }
+
+ pub fn fileLocation(isi: Index, coff: *const Coff) MappedFile.Node.FileLocation {
+ return coff.input_sections.items[@intFromEnum(isi)].file_location;
+ }
+
+ pub fn symbol(isi: Index, coff: *const Coff) Symbol.Index {
+ return coff.input_sections.items[@intFromEnum(isi)].si;
+ }
+
+ pub fn firstSymbol(isi: Index, coff: *const Coff) LocalIndex {
+ return coff.input_sections.items[@intFromEnum(isi)].first_li;
+ }
+ };
+
+ const LocalIndex = enum(u32) {
+ _,
+
+ pub fn name(isli: LocalIndex, coff: *const Coff) String {
+ return coff.input_symbols.items[@intFromEnum(isli)].name;
+ }
+ };
+ };
+
pub const LazyMapRef = struct {
kind: link.File.LazySymbol.Kind,
index: u32,
@@ -253,6 +385,14 @@ pub const Node = union(enum) {
file,
header,
signature,
+ first_linker_member_header,
+ first_linker_member,
+ second_linker_member_header,
+ second_linker_member,
+ longnames_member_header,
+ longnames_member,
+ zcu_member_header,
+ zcu_member,
coff_header,
optional_header,
data_directories,
@@ -270,14 +410,338 @@ pub const Node = union(enum) {
}
};
+pub const InputArchive = struct {
+ path: std.Build.Cache.Path,
+
+ const Index = enum(u32) {
+ _,
+
+ pub fn path(iai: InputArchive.Index, coff: *Coff) std.Build.Cache.Path {
+ return coff.input_archives.items[@intFromEnum(iai)].path;
+ }
+ };
+
+ pub const Member = struct {
+ iai: InputArchive.Index,
+ name: String,
+ content: union(enum) {
+ // This range includes the member header
+ object: MappedFile.Node.FileLocation,
+ import: struct {
+ symbol_name: String,
+ lib_name: String,
+ // Either ordinal or hint, depending on value of name_type
+ import_ordinal_hint: u16,
+ type: std.coff.ImportType,
+ name_type: std.coff.ImportNameType,
+ },
+ },
+ flags: packed struct {
+ // Set if an attempt was made to load this member
+ is_loaded: bool,
+ },
+
+ const Index = enum(u32) {
+ _,
+
+ pub fn member(iami: InputArchive.Member.Index, coff: *Coff) *InputArchive.Member {
+ return &coff.input_archive_members.items[@intFromEnum(iami)];
+ }
+ };
+
+ pub const Symbol = struct {
+ iami: InputArchive.Member.Index,
+ // Set to its own index to indicate its the last in the list
+ next: InputArchive.Member.Symbol.Index,
+
+ const Index = enum(u32) {
+ _,
+ };
+ };
+ };
+
+ pub const SearchList = struct {
+ first: InputArchive.Member.Symbol.Index,
+ last: InputArchive.Member.Symbol.Index,
+ };
+};
+
+pub const InputObject = struct {
+ path: std.Build.Cache.Path,
+ member_name: ?[]const u8,
+ source_name: String.Optional,
+
+ pub const Index = enum(u32) {
+ _,
+
+ pub fn path(ioi: Index, coff: *const Coff) std.Build.Cache.Path {
+ return coff.input_objects.items[@intFromEnum(ioi)].path;
+ }
+
+ pub fn memberName(ioi: Index, coff: *const Coff) ?[]const u8 {
+ return coff.input_objects.items[@intFromEnum(ioi)].member_name;
+ }
+ };
+};
+
+pub const Member = struct {
+ kind: std.coff.ArchiveMemberHeader.Kind,
+ header_ni: MappedFile.Node.Index,
+ content_ni: MappedFile.Node.Index,
+ first_linker_indices: std.array_hash_map.Auto(struct {
+ mi: Member.Index,
+ name: String,
+ }, FirstLinkerIndex),
+
+ pub const Index = enum(u16) {
+ first,
+ second,
+ longnames,
+ _,
+
+ const known_count = @typeInfo(Index).@"enum".field_names.len;
+
+ pub fn get(member_index: Member.Index, coff: *Coff) *Member {
+ return &coff.members.items[@intFromEnum(member_index)];
+ }
+ };
+
+ pub const FirstLinkerIndex = enum(u32) {
+ _,
+ };
+
+ pub fn headerPtr(member: *Member, coff: *Coff) *std.coff.ArchiveMemberHeader {
+ return @ptrCast(@alignCast(member.header_ni.slice(&coff.mf)));
+ }
+
+ /// Sets `name` as the name field of this member's header, either directly (if it's short enough),
+ /// or by creating an entry in the longnames member and storing a reference to that entry.
+ pub fn initHeader(member: *Member, coff: *Coff, name: []const u8, timestamp: u32) !void {
+ const max_name_len = @typeInfo(@FieldType(std.coff.ArchiveMemberHeader, "name")).array.len;
+ const opt_name_offset = if (name.len >= max_name_len) offset: {
+ const gpa = coff.base.comp.gpa;
+ const entries_ctx = LongNamesTable.Adapter{ .coff = coff };
+ const gop = try coff.long_names_table.entries.getOrPutAdapted(
+ gpa,
+ name,
+ entries_ctx,
+ );
+
+ if (!gop.found_existing) {
+ errdefer _ = coff.export_table.entries.pop();
+
+ _, const old_size = Node.known.longnames_member.location(&coff.mf).resolve(&coff.mf);
+ const new_size = old_size + name.len + 1;
+ assert(new_size < comptime try std.math.powi(u64, 10, max_name_len - 1));
+
+ try Node.known.longnames_member.resize(&coff.mf, gpa, new_size);
+ const name_table_slice = Node.known.longnames_member.slice(&coff.mf);
+ const name_slice = name_table_slice[@intCast(old_size)..][0 .. name.len + 1];
+ @memcpy(name_slice[0..name.len], name);
+ name_slice[name.len] = 0;
+
+ gop.value_ptr.* = .{
+ .offset = old_size,
+ .len = name.len,
+ };
+ }
+
+ break :offset gop.value_ptr.offset;
+ } else null;
+
+ const header = member.headerPtr(coff);
+ if (opt_name_offset) |name_offset| {
+ header.name[0] = '/';
+ storeHeaderDecimalStr(header.name[1..], name_offset);
+ } else {
+ @memcpy(header.name[0..name.len], name);
+ header.name[name.len] = '/';
+ const padding = max_name_len - name.len - 1;
+ @memset(header.name[max_name_len - padding ..], ' ');
+ }
+
+ storeHeaderDecimalStr(&header.date, timestamp);
+
+ // Matching the Microsoft behaviour of emitting blanks for these fields
+ header.user_id = @splat(' ');
+ header.group_id = @splat(' ');
+
+ // file_mode is actually octal, but we only ever write 0 to it
+ storeHeaderDecimalStr(&header.file_mode, 0);
+ if (!member.content_ni.hasResized(&coff.mf))
+ storeHeaderDecimalStr(
+ &header.size,
+ member.content_ni.location(&coff.mf).resolve(&coff.mf)[1],
+ );
+
+ @memcpy(&header.end_of_header, std.coff.archive_end_of_header);
+ }
+
+ pub fn storeHeaderDecimalStr(field_ptr: anytype, value: u64) void {
+ const array_info = @typeInfo(@typeInfo(@TypeOf(field_ptr)).pointer.child).array;
+ assert(array_info.child == u8);
+ assert(value < comptime try std.math.powi(u64, 10, array_info.len));
+ _ = std.fmt.printInt(field_ptr, value, 10, .lower, .{
+ .width = array_info.len,
+ .alignment = .left,
+ .fill = ' ',
+ });
+ }
+
+ pub fn loadHeaderDecimalStr(field_ptr: anytype, value: u64) void {
+ const array_info = @typeInfo(@typeInfo(@TypeOf(field_ptr)).pointer.child).array;
+ assert(array_info.child == u8);
+ assert(value < comptime try std.math.powi(u64, 10, array_info.len));
+ _ = std.fmt.printInt(field_ptr, value, 10, .lower, .{
+ .width = array_info.len,
+ .alignment = .left,
+ .fill = ' ',
+ });
+ }
+};
+
+pub const LongNamesTable = struct {
+ ni: MappedFile.Node.Index = .none,
+ entries: std.array_hash_map.Auto(void, Entry),
+
+ pub const Entry = struct {
+ offset: u64,
+ len: u64,
+ };
+
+ const Adapter = struct {
+ coff: *Coff,
+
+ pub fn eql(adapter: Adapter, lhs_key: []const u8, _: void, rhs_index: usize) bool {
+ assert(adapter.coff.isArchive());
+ const longnames_slice = Node.known.longnames_member.slice(&adapter.coff.mf);
+ const rhs = adapter.coff.long_names_table.entries.values()[rhs_index];
+ return std.mem.eql(u8, longnames_slice[@intCast(rhs.offset)..][0..@intCast(rhs.len)], lhs_key);
+ }
+
+ pub fn hash(_: Adapter, key: []const u8) u32 {
+ assert(std.mem.indexOfScalar(u8, key, 0) == null);
+ return std.array_hash_map.hashString(key);
+ }
+ };
+};
+
+pub const SymbolTable = struct {
+ ni: MappedFile.Node.Index,
+ strings_ni: MappedFile.Node.Index,
+ strings: std.array_hash_map.Auto(String, StringIndex),
+ symbols: std.array_hash_map.Auto(Symbol.Index, SymbolTable.Index),
+ pending_symbol_index: u32,
+
+ // Resizing the symbol table node has the result of accumulating padding
+ // between the last symbol in the symbol table node and the start of the
+ // string table node, due to the shifting method when resizing the parent in MappedFile.
+ // The spec requires the string table begin immediately after the last symbol,
+ // so we compact the symbol table node and move the string table back if needed.
+ pending_shrink: bool,
+
+ pub const StringIndex = enum(u32) {
+ _,
+ };
+
+ pub const SymbolName = union(enum) {
+ short: []const u8,
+ long: StringIndex,
+
+ pub fn store(name: SymbolName, coff: *const Coff, field: *[8]u8) void {
+ switch (name) {
+ .short => |s| {
+ @memcpy(field[0..s.len], s);
+ @memset(field[s.len..], 0);
+ },
+ .long => |l| {
+ @memset(field[0..4], 0);
+ std.mem.writePackedInt(u32, field[4..], 0, @intFromEnum(l), coff.targetEndian());
+ },
+ }
+ }
+ };
+
+ // Symbol.Index does not map 1:1 with SymbolTable.Index:
+ // - Not all symbols need a symbol table entry
+ // - A variable number of auxiliary entries may trail each symbol
+ pub const Index = enum(u32) {
+ none,
+ _,
+
+ pub fn wrap(i: u32) Index {
+ return @enumFromInt(i + 1);
+ }
+
+ pub fn unwrap(sti: Index) ?u32 {
+ return switch (sti) {
+ .none => null,
+ _ => @intFromEnum(sti) - 1,
+ };
+ }
+ };
+};
+
+pub const ExportTable = struct {
+ ni: MappedFile.Node.Index,
+ export_directory_table_ni: MappedFile.Node.Index,
+ export_address_table_si: Symbol.Index,
+ name_pointer_table_ni: MappedFile.Node.Index,
+ ordinal_table_ni: MappedFile.Node.Index,
+ name_table_ni: MappedFile.Node.Index,
+ entries: std.array_hash_map.Auto(void, Entry),
+ pending_sort: bool = false,
+
+ pub const Entry = struct {
+ si: Symbol.Index,
+ name_index: u32,
+ name_len: u32,
+ export_address_table_ri: Reloc.Index,
+ };
+
+ const Adapter = struct {
+ coff: *Coff,
+
+ pub fn eql(adapter: Adapter, lhs_key: []const u8, _: void, rhs_index: usize) bool {
+ const coff = adapter.coff;
+ const name_table_slice = coff.export_table.name_table_ni.slice(&coff.mf);
+ const rhs = coff.export_table.entries.values()[rhs_index];
+ return std.mem.eql(u8, name_table_slice[rhs.name_index..][0..rhs.name_len], lhs_key);
+ }
+
+ pub fn hash(_: Adapter, key: []const u8) u32 {
+ assert(std.mem.indexOfScalar(u8, key, 0) == null);
+ return std.array_hash_map.hashString(key);
+ }
+ };
+
+ pub const Ordinal = enum(u16) {
+ _,
+
+ pub fn get(export_index: ExportTable.Ordinal, coff: *Coff) *Entry {
+ return &coff.export_table.entries.values()[@intFromEnum(export_index)];
+ }
+ };
+};
+
pub const ImportTable = struct {
ni: MappedFile.Node.Index,
entries: std.array_hash_map.Auto(void, Entry),
+ iat_symbol_indices: std.array_hash_map.Auto(struct {
+ iti: ImportTable.Index,
+ name: String.Optional,
+ // If name == .none this is the ordinal, otherwise the hint
+ ordinal_hint: u16,
+ }, u32),
pub const Entry = struct {
import_lookup_table_ni: MappedFile.Node.Index,
import_address_table_si: Symbol.Index,
import_hint_name_table_ni: MappedFile.Node.Index,
+ // All .iat_ptr globals that reference this table.
+ // This is separate from `iat_symbol_indices` because multiple symbols
+ // can reference to the same iat entry, after name demangling.
+ import_address_table_symbols: std.ArrayList(Symbol.Index),
len: u32,
hint_name_len: u32,
};
@@ -314,13 +778,32 @@ pub const String = enum(u32) {
@".rdata" = 13,
@".text" = 20,
@".tls$" = 26,
+ @".edata" = 32,
+ @".ctors" = 39,
+ @".ctors$ZZZ" = 46,
+ @".dtors" = 57,
+ @".dtors$ZZZ" = 64,
+ @".bss" = 75,
+ @".fptable" = 80,
+ @".tls" = 89,
+ @".thunks" = 94,
_,
pub const Optional = enum(u32) {
@".data" = @intFromEnum(String.@".data"),
+ @".idata" = @intFromEnum(String.@".idata"),
@".rdata" = @intFromEnum(String.@".rdata"),
@".text" = @intFromEnum(String.@".text"),
@".tls$" = @intFromEnum(String.@".tls$"),
+ @".edata" = @intFromEnum(String.@".edata"),
+ @".ctors" = @intFromEnum(String.@".ctors"),
+ @".ctors$ZZZ" = @intFromEnum(String.@".ctors$ZZZ"),
+ @".dtors" = @intFromEnum(String.@".dtors"),
+ @".dtors$ZZZ" = @intFromEnum(String.@".dtors$ZZZ"),
+ @".bss" = @intFromEnum(String.@".bss"),
+ @".fptable" = @intFromEnum(String.@".fptable"),
+ @".tls" = @intFromEnum(String.@".tls"),
+ @".thunks" = @intFromEnum(String.@".thunks"),
none = std.math.maxInt(u32),
_,
@@ -346,20 +829,176 @@ pub const String = enum(u32) {
}
};
-pub const GlobalName = struct { name: String, lib_name: String.Optional };
+pub const Section = struct {
+ si: Symbol.Index,
+ relocation_table_ni: MappedFile.Node.Index,
+
+ pub const RelocationIndex = enum(u16) {
+ none,
+ _,
+
+ pub fn wrap(i: ?u16) RelocationIndex {
+ return @enumFromInt((i orelse return .none) + 1);
+ }
+
+ pub fn unwrap(sri: RelocationIndex) ?u16 {
+ return switch (sri) {
+ .none => null,
+ _ => @intFromEnum(sri) - 1,
+ };
+ }
+
+ pub fn entry(
+ sri: RelocationIndex,
+ coff: *Coff,
+ sn: Symbol.SectionNumber,
+ ) ?*align(2) std.coff.Relocation {
+ if (sri == .none) return null;
+ const table_slice = sn.section(coff).relocation_table_ni.slice(&coff.mf);
+ return @ptrCast(@alignCast(&table_slice[@as(u32, sri.unwrap().?) * std.coff.Relocation.sizeOf()]));
+ }
+ };
+};
+
+pub const Global = struct {
+ si: Symbol.Index,
+ lib_name: String.Optional,
+};
+
+pub const WeakExternalStrat = enum(u3) {
+ none,
+ no_library,
+ library,
+ alias,
+ anti_dependency,
+
+ pub fn fromFlag(flag: std.coff.WeakExternalFlag) WeakExternalStrat {
+ return switch (flag) {
+ .SEARCH_NOLIBRARY => .no_library,
+ .SEARCH_LIBRARY => .library,
+ .SEARCH_ALIAS => .alias,
+ .ANTI_DEPENDENCY => .anti_dependency,
+ _ => unreachable,
+ };
+ }
+};
+
+const SpecialSymbol = enum {
+ entry,
+ tls,
+ none,
+};
pub const Symbol = struct {
ni: MappedFile.Node.Index,
rva: u32,
- size: u32,
+ value: std.meta.BareUnion(Symbol.Value),
+ extra: std.meta.BareUnion(Symbol.Extra),
+ flags: packed struct(u16) {
+ value_tag: ValueTag,
+ extra_tag: ExtraTag,
+ type: Symbol.Type,
+ dll_storage_class: DllStorageClass,
+ weak_external_strat: WeakExternalStrat,
+ _: u5 = 0,
+ },
/// Relocations contained within this symbol
loc_relocs: Reloc.Index,
/// Relocations targeting this symbol
target_relocs: Reloc.Index,
section_number: SectionNumber,
- unused0: u32 = 0,
- unused1: u32 = 0,
- unused2: u16 = 0,
+ gmi: Node.GlobalMapIndex,
+
+ pub const DllStorageClass = enum(u2) {
+ default,
+ dllimport,
+ dllexport,
+ };
+
+ pub const Type = enum(u2) {
+ unknown,
+ code,
+ data,
+ };
+
+ const ValueTag = enum(u2) {
+ none,
+ node_offset,
+ weak_alias_si,
+ weak_alias_name,
+ };
+
+ pub const Value = union(ValueTag) {
+ none,
+ /// The offset of the symbol within its node. Used with symbols that
+ /// don't create their own nodes: .input_section, .import_address_table
+ /// Images only.
+ node_offset: u32,
+ /// Images: the weak alias that should replace this symbol if it is not resolved.
+ /// Objects: he target of a weak external that hasn't been assigned an sti yet.
+ /// Globals only.
+ weak_alias_si: Symbol.Index,
+ /// For weak externals that have an alias that is also an undef
+ /// external, this is the name of the alias global that should
+ /// be generated and resolved if this symbol is not resolved.
+ /// Globals only, images only.
+ weak_alias_name: String,
+ };
+
+ const ExtraTag = enum(u2) {
+ size,
+ isli,
+ next_alias_si,
+ };
+
+ pub const Extra = union(ExtraTag) {
+ // The size of the symbol
+ size: u32,
+ /// Only valid when .ni == .input_section and .value_tag == .node_offset
+ isli: Node.InputSection.LocalIndex,
+ /// The next symbol in the list of aliases of this symbol.
+ next_alias_si: Symbol.Index,
+ };
+
+ pub fn setValue(sym: *Symbol, value: Symbol.Value) void {
+ sym.flags.value_tag = std.meta.activeTag(value);
+ sym.value = switch (sym.flags.value_tag) {
+ inline else => |t| @unionInit(
+ @FieldType(Symbol, "value"),
+ @tagName(t),
+ @field(value, @tagName(t)),
+ ),
+ };
+ }
+
+ pub fn setExtra(sym: *Symbol, extra: Symbol.Extra) void {
+ sym.flags.extra_tag = std.meta.activeTag(extra);
+ sym.extra = switch (sym.flags.extra_tag) {
+ inline else => |t| @unionInit(
+ @FieldType(Symbol, "extra"),
+ @tagName(t),
+ @field(extra, @tagName(t)),
+ ),
+ };
+ }
+
+ pub fn nodeOffset(sym: *const Symbol, coff: *Coff) u32 {
+ return switch (sym.flags.value_tag) {
+ .node_offset => offset: {
+ assert(switch (coff.getNode(sym.ni)) {
+ // Separate nodes are not created for these entries per-symbol
+ .input_section, .import_address_table => true,
+ else => false,
+ });
+ break :offset sym.value.node_offset;
+ },
+ else => 0,
+ };
+ }
+
+ pub fn size(sym: *const Symbol) u32 {
+ return if (sym.flags.extra_tag == .size) sym.extra.size else 0;
+ }
pub const SectionNumber = enum(i16) {
UNDEFINED = 0,
@@ -371,8 +1010,20 @@ pub const Symbol = struct {
return @intCast(@intFromEnum(sn) - 1);
}
+ fn hasIndex(sn: SectionNumber) bool {
+ return @intFromEnum(sn) > 0;
+ }
+
pub fn symbol(sn: SectionNumber, coff: *const Coff) Symbol.Index {
- return coff.image_section_table.items[sn.toIndex()];
+ return sn.section(coff).si;
+ }
+
+ pub fn name(sn: SectionNumber, coff: *const Coff) String {
+ return coff.section_table.keys()[sn.toIndex()];
+ }
+
+ pub fn section(sn: SectionNumber, coff: *const Coff) *Section {
+ return &coff.section_table.values()[sn.toIndex()];
}
pub fn header(sn: SectionNumber, coff: *Coff) *std.coff.SectionHeader {
@@ -382,6 +1033,7 @@ pub const Symbol = struct {
pub const Index = enum(u32) {
null,
+ bss,
data,
rdata,
text,
@@ -390,7 +1042,12 @@ pub const Symbol = struct {
const known_count = @typeInfo(Index).@"enum".field_names.len;
pub fn get(si: Symbol.Index, coff: *Coff) *Symbol {
- return &coff.symbol_table.items[@intFromEnum(si)];
+ return &coff.symbols.items[@intFromEnum(si)];
+ }
+
+ pub fn unwrap(si: Symbol.Index) ?Symbol.Index {
+ if (si == .null) return null;
+ return si;
}
pub fn node(si: Symbol.Index, coff: *Coff) MappedFile.Node.Index {
@@ -399,37 +1056,92 @@ pub const Symbol = struct {
return ni;
}
- pub fn flushMoved(si: Symbol.Index, coff: *Coff) void {
- const sym = si.get(coff);
- sym.rva = coff.computeNodeRva(sym.ni);
- si.applyLocationRelocs(coff);
- si.applyTargetRelocs(coff);
+ pub fn sti(si: Symbol.Index, coff: *Coff) SymbolTable.Index {
+ assert(!coff.isImage());
+ return coff.symbol_table.symbols.get(si) orelse .none;
+ }
+
+ pub fn next(si: Symbol.Index) Symbol.Index {
+ return @enumFromInt(@intFromEnum(si) + 1);
+ }
+
+ pub fn knownString(si: Symbol.Index) String.Optional {
+ return switch (si) {
+ .null, _ => .none,
+ inline else => |tag| @field(String.Optional, "." ++ @tagName(tag)),
+ };
}
- pub fn applyLocationRelocs(si: Symbol.Index, coff: *Coff) void {
- for (coff.relocs.items[@intFromEnum(si.get(coff).loc_relocs)..]) |*reloc| {
- if (reloc.loc != si) break;
- reloc.apply(coff);
+ pub fn flushMoved(si: Symbol.Index, coff: *Coff) !void {
+ const sym = si.get(coff);
+ sym.rva = coff.computeNodeRva(sym.ni) + sym.nodeOffset(coff);
+ try si.applyLocationRelocs(coff);
+ try si.applyTargetRelocs(coff, .none);
+
+ var alias_sym = sym;
+ while (alias_sym.flags.extra_tag == .next_alias_si) {
+ const alias_si = alias_sym.extra.next_alias_si;
+ alias_sym = alias_si.get(coff);
+ assert(alias_sym.ni == sym.ni);
+ alias_sym.rva = sym.rva;
+ try alias_si.applyTargetRelocs(coff, .none);
}
}
- pub fn applyTargetRelocs(si: Symbol.Index, coff: *Coff) void {
- var ri = si.get(coff).target_relocs;
+ pub fn flushSymbolTableIndex(si: Symbol.Index, coff: *Coff) void {
+ const sym = si.get(coff);
+ const index = si.sti(coff).unwrap().?;
+ var ri = sym.target_relocs;
while (ri != .none) {
const reloc = ri.get(coff);
assert(reloc.target == si);
- reloc.apply(coff);
+ if (reloc.sri.entry(coff, reloc.loc.get(coff).section_number)) |entry|
+ coff.targetStore(&entry.symbol_table_index, index);
+ ri = reloc.next;
+ }
+ }
+
+ pub fn applyLocationRelocs(si: Symbol.Index, coff: *Coff) !void {
+ const sym = si.get(coff);
+ switch (sym.loc_relocs) {
+ .none => {},
+ else => |loc_relocs| {
+ for (coff.relocs.items[@intFromEnum(loc_relocs)..]) |*reloc| {
+ if (reloc.loc != si) break;
+ if (reloc.sri.entry(coff, sym.section_number)) |entry| coff.targetStore(
+ &entry.virtual_address,
+ @intCast(coff.computeSymbolSectionOffset(sym, .image) + reloc.offset),
+ );
+ try reloc.apply(coff);
+ }
+ },
+ }
+ }
+
+ pub fn applyTargetRelocs(si: Symbol.Index, coff: *Coff, end: Reloc.Index) !void {
+ const sym = si.get(coff);
+
+ var ri = sym.target_relocs;
+ while (ri != end) {
+ const reloc = ri.get(coff);
+ assert(reloc.target == si);
+ try reloc.apply(coff);
ri = reloc.next;
}
}
pub fn deleteLocationRelocs(si: Symbol.Index, coff: *Coff) void {
const sym = si.get(coff);
- for (coff.relocs.items[@intFromEnum(sym.loc_relocs)..]) |*reloc| {
- if (reloc.loc != si) break;
- reloc.delete(coff);
+ switch (sym.loc_relocs) {
+ .none => {},
+ else => |loc_relocs| {
+ for (coff.relocs.items[@intFromEnum(loc_relocs)..]) |*reloc| {
+ if (reloc.loc != si) break;
+ reloc.delete(coff);
+ }
+ sym.loc_relocs = .none;
+ },
}
- sym.loc_relocs = .none;
}
};
@@ -439,14 +1151,24 @@ pub const Symbol = struct {
};
pub const Reloc = extern struct {
- type: Reloc.Type,
+ offset: u64,
+ addend: i64,
+ type: Reloc.Type,
+ sri: Section.RelocationIndex,
prev: Reloc.Index,
next: Reloc.Index,
loc: Symbol.Index,
target: Symbol.Index,
- unused: u32,
- offset: u64,
- addend: i64,
+ flags: packed struct(u8) {
+ /// Indicates the addend is not known and should be recovered from the location itself.
+ /// COFF relocation tables don't encode the addend, only the location.
+ recover_addend: bool,
+ /// Set if this reloc is in the free list.
+ /// When set, `prev` / `next` refer to other relocs in the free list.
+ /// All other fields are undefined.
+ free: bool,
+ _: u6 = 0,
+ },
pub const Type = extern union {
AMD64: std.coff.IMAGE.REL.AMD64,
@@ -464,135 +1186,319 @@ pub const Reloc = extern struct {
none = std.math.maxInt(u32),
_,
- pub fn get(si: Reloc.Index, coff: *Coff) *Reloc {
- return &coff.relocs.items[@intFromEnum(si)];
+ pub fn wrap(i: ?u32) Reloc.Index {
+ return @enumFromInt((i orelse return .none) + 1);
+ }
+
+ pub fn get(ri: Reloc.Index, coff: *Coff) *Reloc {
+ return &coff.relocs.items[@intFromEnum(ri)];
}
};
- pub fn apply(reloc: *const Reloc, coff: *Coff) void {
+ pub fn apply(reloc: *Reloc, coff: *Coff) !void {
const loc_sym = reloc.loc.get(coff);
switch (loc_sym.ni) {
.none => return,
else => |ni| if (ni.hasMoved(&coff.mf)) return,
}
- const target_sym = reloc.target.get(coff);
- switch (target_sym.ni) {
- .none => return,
- else => |ni| if (ni.hasMoved(&coff.mf)) return,
- }
+
const loc_slice = loc_sym.ni.slice(&coff.mf)[@intCast(reloc.offset)..];
- const target_rva = target_sym.rva +% @as(u64, @bitCast(reloc.addend));
const target_endian = coff.targetEndian();
- switch (coff.targetLoad(&coff.headerPtr().machine)) {
- else => |machine| @panic(@tagName(machine)),
- .AMD64 => switch (reloc.type.AMD64) {
- else => |kind| @panic(@tagName(kind)),
- .ABSOLUTE => {},
- .ADDR64 => std.mem.writeInt(
- u64,
- loc_slice[0..8],
- coff.optionalHeaderField(.image_base) + target_rva,
- target_endian,
- ),
- .ADDR32 => std.mem.writeInt(
- u32,
- loc_slice[0..4],
- @intCast(coff.optionalHeaderField(.image_base) + target_rva),
- target_endian,
- ),
- .ADDR32NB => std.mem.writeInt(
- u32,
- loc_slice[0..4],
- @intCast(target_rva),
- target_endian,
- ),
- .REL32 => std.mem.writeInt(
- i32,
- loc_slice[0..4],
- @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 4)))),
- target_endian,
- ),
- .REL32_1 => std.mem.writeInt(
- i32,
- loc_slice[0..4],
- @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 5)))),
- target_endian,
- ),
- .REL32_2 => std.mem.writeInt(
- i32,
- loc_slice[0..4],
- @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 6)))),
- target_endian,
- ),
- .REL32_3 => std.mem.writeInt(
- i32,
- loc_slice[0..4],
- @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 7)))),
- target_endian,
- ),
- .REL32_4 => std.mem.writeInt(
- i32,
- loc_slice[0..4],
- @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 8)))),
- target_endian,
- ),
- .REL32_5 => std.mem.writeInt(
- i32,
- loc_slice[0..4],
- @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 9)))),
- target_endian,
- ),
- .SECREL => std.mem.writeInt(
- u32,
- loc_slice[0..4],
- coff.computeNodeSectionOffset(target_sym.ni),
- target_endian,
- ),
- },
- .I386 => switch (reloc.type.I386) {
- else => |kind| @panic(@tagName(kind)),
- .ABSOLUTE => {},
- .DIR16 => std.mem.writeInt(
- u16,
- loc_slice[0..2],
- @intCast(coff.optionalHeaderField(.image_base) + target_rva),
- target_endian,
- ),
- .REL16 => std.mem.writeInt(
- i16,
- loc_slice[0..2],
- @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 2)))),
- target_endian,
- ),
- .DIR32 => std.mem.writeInt(
- u32,
- loc_slice[0..4],
- @intCast(coff.optionalHeaderField(.image_base) + target_rva),
- target_endian,
- ),
- .DIR32NB => std.mem.writeInt(
- u32,
- loc_slice[0..4],
- @intCast(target_rva),
- target_endian,
- ),
- .REL32 => std.mem.writeInt(
- i32,
- loc_slice[0..4],
- @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 4)))),
- target_endian,
- ),
- .SECREL => std.mem.writeInt(
- u32,
- loc_slice[0..4],
- coff.computeNodeSectionOffset(target_sym.ni),
- target_endian,
- ),
- },
+ const target_machine = coff.targetLoad(&coff.headerPtr().machine);
+
+ if (!coff.isImage()) {
+ assert(!reloc.flags.recover_addend);
+ switch (target_machine) {
+ else => |machine| @panic(@tagName(machine)),
+ .AMD64 => switch (reloc.type.AMD64) {
+ else => |kind| @panic(@tagName(kind)),
+ .ABSOLUTE => {},
+ .ADDR64 => std.mem.writeInt(
+ u64,
+ loc_slice[0..8],
+ @intCast(reloc.addend),
+ target_endian,
+ ),
+ .ADDR32,
+ .ADDR32NB,
+ .SECREL,
+ => std.mem.writeInt(
+ u32,
+ loc_slice[0..4],
+ @intCast(reloc.addend),
+ target_endian,
+ ),
+ .REL32,
+ .REL32_1,
+ .REL32_2,
+ .REL32_3,
+ .REL32_4,
+ .REL32_5,
+ => std.mem.writeInt(
+ i32,
+ loc_slice[0..4],
+ @intCast(reloc.addend),
+ target_endian,
+ ),
+ },
+ .I386 => switch (reloc.type.I386) {
+ else => |kind| @panic(@tagName(kind)),
+ .ABSOLUTE => {},
+ .DIR16,
+ => std.mem.writeInt(
+ u16,
+ loc_slice[0..2],
+ @intCast(reloc.addend),
+ target_endian,
+ ),
+ .REL16,
+ => std.mem.writeInt(
+ i16,
+ loc_slice[0..2],
+ @intCast(reloc.addend),
+ target_endian,
+ ),
+ .DIR32,
+ .DIR32NB,
+ .SECREL,
+ => std.mem.writeInt(
+ u32,
+ loc_slice[0..4],
+ @intCast(reloc.addend),
+ target_endian,
+ ),
+ .REL32,
+ => std.mem.writeInt(
+ i32,
+ loc_slice[0..4],
+ @intCast(reloc.addend),
+ target_endian,
+ ),
+ },
+ }
+
+ return;
+ } else if (reloc.flags.recover_addend) {
+ reloc.flags.recover_addend = false;
+ reloc.addend = switch (target_machine) {
+ else => |machine| @panic(@tagName(machine)),
+ .AMD64 => switch (reloc.type.AMD64) {
+ else => |kind| @panic(@tagName(kind)),
+ .ABSOLUTE => 0,
+ .ADDR64 => @bitCast(std.mem.readInt(
+ u64,
+ loc_slice[0..8],
+ target_endian,
+ )),
+ .ADDR32,
+ .ADDR32NB,
+ .SECREL,
+ .REL32,
+ .REL32_1,
+ .REL32_2,
+ .REL32_3,
+ .REL32_4,
+ .REL32_5,
+ => std.mem.readInt(
+ i32,
+ loc_slice[0..4],
+ target_endian,
+ ),
+ },
+ .I386 => switch (reloc.type.I386) {
+ else => |kind| @panic(@tagName(kind)),
+ .ABSOLUTE => 0,
+ .DIR16,
+ .REL16,
+ => std.mem.readInt(
+ i16,
+ loc_slice[0..2],
+ target_endian,
+ ),
+ .DIR32,
+ .DIR32NB,
+ .SECREL,
+ .REL32,
+ => std.mem.readInt(
+ i32,
+ loc_slice[0..4],
+ target_endian,
+ ),
+ },
+ };
+ }
+
+ const target_sym = reloc.target.get(coff);
+ const is_abs = switch (target_sym.ni) {
+ .none => if (target_sym.section_number == .ABSOLUTE) true else return,
+ else => |ni| if (ni.hasMoved(&coff.mf)) return else false,
+ };
+
+ const target_rva = target_sym.rva +% @as(u64, @bitCast(reloc.addend));
+ if (is_abs) {
+ switch (target_machine) {
+ else => |machine| @panic(@tagName(machine)),
+ .AMD64 => switch (reloc.type.AMD64) {
+ // TODO: Could wait to report these later, in reportUndefs -> reportRelocErrs,
+ // so that this function doesn't return an err
+ else => |kind| return coff.base.comp.link_diags.fail(
+ "absolute symbol '{s}' targeted by invalid relocation type: {t}",
+ .{ target_sym.gmi.name(coff).toSlice(coff), kind },
+ ),
+ .ABSOLUTE => {},
+ .ADDR64 => std.mem.writeInt(
+ u64,
+ loc_slice[0..8],
+ target_rva,
+ target_endian,
+ ),
+ .ADDR32 => std.mem.writeInt(
+ u32,
+ loc_slice[0..4],
+ @intCast(target_rva),
+ target_endian,
+ ),
+ },
+ .I386 => switch (reloc.type.I386) {
+ else => |kind| return coff.base.comp.link_diags.fail(
+ "absolute symbol '{s}' targeted by invalid relocation type: {t}",
+ .{ target_sym.gmi.name(coff).toSlice(coff), kind },
+ ),
+ .ABSOLUTE => {},
+ .DIR16 => std.mem.writeInt(
+ u16,
+ loc_slice[0..2],
+ @intCast(target_rva),
+ target_endian,
+ ),
+ .DIR32 => std.mem.writeInt(
+ u32,
+ loc_slice[0..4],
+ @intCast(target_rva),
+ target_endian,
+ ),
+ },
+ }
+ } else {
+ switch (target_machine) {
+ else => |machine| @panic(@tagName(machine)),
+ .AMD64 => switch (reloc.type.AMD64) {
+ else => |kind| @panic(@tagName(kind)),
+ .ABSOLUTE => {},
+ .ADDR64 => std.mem.writeInt(
+ u64,
+ loc_slice[0..8],
+ coff.optionalHeaderField(.image_base) + target_rva,
+ target_endian,
+ ),
+ .ADDR32 => std.mem.writeInt(
+ u32,
+ loc_slice[0..4],
+ @intCast(coff.optionalHeaderField(.image_base) + target_rva),
+ target_endian,
+ ),
+ .ADDR32NB => std.mem.writeInt(
+ u32,
+ loc_slice[0..4],
+ @intCast(target_rva),
+ target_endian,
+ ),
+ .REL32 => std.mem.writeInt(
+ i32,
+ loc_slice[0..4],
+ @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 4)))),
+ target_endian,
+ ),
+ .REL32_1 => std.mem.writeInt(
+ i32,
+ loc_slice[0..4],
+ @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 5)))),
+ target_endian,
+ ),
+ .REL32_2 => std.mem.writeInt(
+ i32,
+ loc_slice[0..4],
+ @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 6)))),
+ target_endian,
+ ),
+ .REL32_3 => std.mem.writeInt(
+ i32,
+ loc_slice[0..4],
+ @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 7)))),
+ target_endian,
+ ),
+ .REL32_4 => std.mem.writeInt(
+ i32,
+ loc_slice[0..4],
+ @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 8)))),
+ target_endian,
+ ),
+ .REL32_5 => std.mem.writeInt(
+ i32,
+ loc_slice[0..4],
+ @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 9)))),
+ target_endian,
+ ),
+ .SECREL => std.mem.writeInt(
+ u32,
+ loc_slice[0..4],
+ @intCast(coff.computeSymbolSectionOffset(target_sym, .pseudo) + reloc.addend),
+ target_endian,
+ ),
+ },
+ .I386 => switch (reloc.type.I386) {
+ else => |kind| @panic(@tagName(kind)),
+ .ABSOLUTE => {},
+ .DIR16 => std.mem.writeInt(
+ u16,
+ loc_slice[0..2],
+ @intCast(coff.optionalHeaderField(.image_base) + target_rva),
+ target_endian,
+ ),
+ .REL16 => std.mem.writeInt(
+ i16,
+ loc_slice[0..2],
+ @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 2)))),
+ target_endian,
+ ),
+ .DIR32 => std.mem.writeInt(
+ u32,
+ loc_slice[0..4],
+ @intCast(coff.optionalHeaderField(.image_base) + target_rva),
+ target_endian,
+ ),
+ .DIR32NB => std.mem.writeInt(
+ u32,
+ loc_slice[0..4],
+ @intCast(target_rva),
+ target_endian,
+ ),
+ .REL32 => std.mem.writeInt(
+ i32,
+ loc_slice[0..4],
+ @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 4)))),
+ target_endian,
+ ),
+ .SECREL => std.mem.writeInt(
+ u32,
+ loc_slice[0..4],
+ @intCast(coff.computeSymbolSectionOffset(target_sym, .pseudo) + reloc.addend),
+ target_endian,
+ ),
+ },
+ }
}
}
pub fn delete(reloc: *Reloc, coff: *Coff) void {
+ if (reloc.sri != .none) {
+ // TODO: Need to remove this from the COFF relocation table (maybe removeswap?)
+ // TODO: If this was the last reloc causing something to be in the symbol table, we should remove
+ // the symbol table entry (and unset sti). That will require flushSymbolTableIndex on the
+ // swapped symbol if we exchange indices
+ @panic("TODO implement symbol table reloc deletions");
+ }
+
switch (reloc.prev) {
.none => {
const target = reloc.target.get(coff);
@@ -605,7 +1511,24 @@ pub const Reloc = extern struct {
.none => {},
else => |next| next.get(coff).prev = reloc.prev,
}
+
reloc.* = undefined;
+ reloc.flags = .{
+ .recover_addend = false,
+ .free = true,
+ };
+
+ const ri: Reloc.Index = .wrap(@intCast(reloc - coff.relocs.items.ptr));
+ if (coff.last_free_reloc == .none) {
+ assert(coff.first_free_reloc == .none);
+ coff.first_free_reloc = ri;
+ coff.last_free_reloc = ri;
+ } else {
+ coff.last_free_reloc.get(coff).next = ri;
+ reloc.prev = coff.last_free_reloc;
+ reloc.next = .none;
+ coff.last_free_reloc = ri;
+ }
}
comptime {
@@ -639,14 +1562,6 @@ fn create(
assert(target.ofmt == .coff);
if (target.cpu.arch.endian() != comptime targetEndian(undefined))
return error.UnsupportedCOFFArchitecture;
- const is_image = switch (comp.config.output_mode) {
- .Exe => true,
- .Lib => switch (comp.config.link_mode) {
- .static => false,
- .dynamic => true,
- },
- .Obj => false,
- };
const machine = target.toCoffMachine();
const timestamp: u32 = 0;
const major_subsystem_version = options.major_subsystem_version orelse 6;
@@ -689,18 +1604,61 @@ fn create(
.allow_shlib_undefined = false,
.stack_size = 0,
},
+ .options = options,
.mf = try .init(file, comp.gpa, io),
.nodes = .empty,
+ .members = .empty,
+ .pending_members = .empty,
+ .lib_string_table = .empty,
+ .lib_string_len = 0,
+ .long_names_table = .{
+ .entries = .empty,
+ },
.import_table = .{
.ni = .none,
.entries = .empty,
+ .iat_symbol_indices = .empty,
+ },
+ .export_table = .{
+ .ni = .none,
+ .export_directory_table_ni = .none,
+ .export_address_table_si = .null,
+ .name_pointer_table_ni = .none,
+ .ordinal_table_ni = .none,
+ .name_table_ni = .none,
+ .entries = .empty,
+ },
+ .symbol_table = .{
+ .ni = .none,
+ .strings_ni = .none,
+ .strings = .empty,
+ .symbols = .empty,
+ .pending_symbol_index = 0,
+ .pending_shrink = false,
},
+ .inputs = .empty,
+ .input_archives = .empty,
+ .input_archive_members = .empty,
+ .input_archive_symbols = .empty,
+ .input_archive_symbol_indices = .empty,
+ .pending_input = null,
+ .pending_default_libs = .empty,
+ .alternate_names = .empty,
+ .input_objects = .empty,
+ .input_symbols = .empty,
+ .input_sections = .empty,
+ .input_section_pending_index = 0,
+ .inputs_complete = false,
+ .exports_complete = false,
+ .pending_special_symbol = .entry,
.strings = .empty,
.string_bytes = .empty,
- .image_section_table = .empty,
+ .section_table = .empty,
.pseudo_section_table = .empty,
.object_section_table = .empty,
- .symbol_table = .empty,
+ .section_merges = .empty,
+ .section_merge_pending_index = 0,
+ .symbols = .empty,
.globals = .empty,
.global_pending_index = 0,
.navs = .empty,
@@ -711,8 +1669,13 @@ fn create(
}),
.pending_uavs = .empty,
.relocs = .empty,
+ .first_free_reloc = .none,
+ .last_free_reloc = .none,
.const_prog_node = .none,
.synth_prog_node = .none,
+ .symbol_prog_node = .none,
+ .member_prog_node = .none,
+ .input_prog_node = .none,
};
errdefer coff.deinit();
@@ -725,14 +1688,20 @@ fn create(
}
try coff.initHeaders(
- is_image,
machine,
timestamp,
major_subsystem_version,
minor_subsystem_version,
magic,
+ if (options.subsystem) |s| switch (s) {
+ .console => .WINDOWS_CUI,
+ .windows => .WINDOWS_GUI,
+ else => return error.UnsupportedCOFFSubsystem,
+ } else .WINDOWS_CUI,
section_align,
+ std.fs.path.basename(path.sub_path),
);
+ try coff.initBuiltins();
return coff;
}
@@ -740,13 +1709,31 @@ pub fn deinit(coff: *Coff) void {
const gpa = coff.base.comp.gpa;
coff.mf.deinit(gpa);
coff.nodes.deinit(gpa);
+ coff.pending_members.deinit(gpa);
+ coff.lib_string_table.deinit(gpa);
+ coff.long_names_table.entries.deinit(gpa);
coff.import_table.entries.deinit(gpa);
+ coff.import_table.iat_symbol_indices.deinit(gpa);
+ coff.export_table.entries.deinit(gpa);
+ coff.symbol_table.strings.deinit(gpa);
+ coff.symbol_table.symbols.deinit(gpa);
+ coff.inputs.deinit(gpa);
+ coff.input_archives.deinit(gpa);
+ coff.input_archive_members.deinit(gpa);
+ coff.input_archive_symbols.deinit(gpa);
+ coff.input_archive_symbol_indices.deinit(gpa);
+ for (coff.pending_default_libs.items) |l| gpa.free(l.path);
+ coff.pending_default_libs.deinit(gpa);
+ coff.alternate_names.deinit(gpa);
+ coff.input_objects.deinit(gpa);
+ coff.input_symbols.deinit(gpa);
+ coff.input_sections.deinit(gpa);
coff.strings.deinit(gpa);
coff.string_bytes.deinit(gpa);
- coff.image_section_table.deinit(gpa);
+ coff.section_table.deinit(gpa);
coff.pseudo_section_table.deinit(gpa);
coff.object_section_table.deinit(gpa);
- coff.symbol_table.deinit(gpa);
+ coff.symbols.deinit(gpa);
coff.globals.deinit(gpa);
coff.navs.deinit(gpa);
coff.uavs.deinit(gpa);
@@ -756,21 +1743,65 @@ pub fn deinit(coff: *Coff) void {
coff.* = undefined;
}
+fn isImage(coff: *const Coff) bool {
+ const comp = coff.base.comp;
+ return switch (comp.config.output_mode) {
+ .Exe => true,
+ .Lib => switch (comp.config.link_mode) {
+ .static => false,
+ .dynamic => true,
+ },
+ .Obj => false,
+ };
+}
+
+fn isArchive(coff: *const Coff) bool {
+ const comp = coff.base.comp;
+ return switch (comp.config.output_mode) {
+ .Exe => false,
+ .Lib => switch (comp.config.link_mode) {
+ .static => true,
+ .dynamic => false,
+ },
+ .Obj => false,
+ };
+}
+
+fn isExe(coff: *const Coff) bool {
+ return coff.base.comp.config.output_mode == .Exe;
+}
+
+fn isObj(coff: *const Coff) bool {
+ return coff.base.comp.config.output_mode == .Obj;
+}
+
+fn hasCoffHeader(coff: *const Coff) bool {
+ return coff.base.comp.zcu != null or !coff.isArchive();
+}
+
+fn sectionParent(coff: *Coff) MappedFile.Node.Index {
+ assert(coff.hasCoffHeader());
+ return if (coff.isArchive()) Node.known.zcu_member else Node.known.file;
+}
+
fn initHeaders(
coff: *Coff,
- is_image: bool,
machine: std.coff.IMAGE.FILE.MACHINE,
timestamp: u32,
major_subsystem_version: u16,
minor_subsystem_version: u16,
magic: std.coff.OptionalHeader.Magic,
+ subsystem: std.coff.Subsystem,
section_align: std.mem.Alignment,
+ file_name: []const u8,
) !void {
const comp = coff.base.comp;
const gpa = comp.gpa;
const target_endian = coff.targetEndian();
const file_align: std.mem.Alignment = comptime .fromByteUnits(default_file_alignment);
-
+ const is_image = coff.isImage();
+ const is_archive = coff.isArchive();
+ const target = &comp.root_mod.resolved_target.result;
const optional_header_size: u16 = if (is_image) switch (magic) {
_ => unreachable,
inline else => |ct_magic| @sizeOf(@field(std.coff.OptionalHeader, @tagName(ct_magic))),
@@ -780,33 +1811,120 @@ fn initHeaders(
else
0;
- const expected_nodes_len = Node.known_count + 6 +
- @as(usize, @intFromBool(comp.config.any_non_single_threaded)) * 2;
+ var expected_nodes_len: usize = Node.known_count;
+ if (coff.hasCoffHeader()) {
+ // Sections
+ expected_nodes_len += 4;
+
+ if (is_image) {
+ // Pseudo-sections and import / export table
+ expected_nodes_len += 9;
+ if (comp.config.link_libc and target.abi == .msvc)
+ expected_nodes_len += 1;
+ } else
+ // Symbol table
+ expected_nodes_len += 2;
+
+ // TLS section
+ if (comp.config.any_non_single_threaded) {
+ if (!is_image) expected_nodes_len += 1;
+ expected_nodes_len += 1;
+ }
+ }
+ defer assert(coff.nodes.len == expected_nodes_len);
+
try coff.nodes.ensureTotalCapacity(gpa, expected_nodes_len);
coff.nodes.appendAssumeCapacity(.file);
const header_ni = Node.known.header;
- assert(header_ni == try coff.mf.addOnlyChildNode(gpa, .root, .{
+ assert(header_ni == try coff.mf.addOnlyChildNode(gpa, Node.known.file, .{
.alignment = coff.mf.flags.block_size,
.fixed = true,
}));
coff.nodes.appendAssumeCapacity(.header);
const signature_ni = Node.known.signature;
- assert(signature_ni == try coff.mf.addOnlyChildNode(gpa, header_ni, .{
- .size = (if (is_image) msdos_stub.len else 0) + "PE\x00\x00".len,
+ assert(signature_ni == try coff.mf.addLastChildNode(gpa, if (is_image or !is_archive) header_ni else Node.known.file, .{
+ .size = if (is_image)
+ msdos_stub.len + std.coff.pe_signature.len
+ else if (is_archive)
+ std.coff.archive_signature.len
+ else
+ 0,
.alignment = .@"4",
.fixed = true,
}));
coff.nodes.appendAssumeCapacity(.signature);
- {
- const signature_slice = signature_ni.slice(&coff.mf);
- if (is_image) @memcpy(signature_slice[0..msdos_stub.len], &msdos_stub);
- @memcpy(signature_slice[signature_slice.len - 4 ..], "PE\x00\x00");
+
+ const signature_slice = signature_ni.slice(&coff.mf);
+ if (is_image) {
+ @memcpy(signature_slice[0..msdos_stub.len], &msdos_stub);
+ @memcpy(signature_slice[signature_slice.len - std.coff.pe_signature.len ..], std.coff.pe_signature);
+ } else if (is_archive) {
+ @memcpy(signature_slice, std.coff.archive_signature);
}
+ const opt_coff_parent_ni = if (is_archive) parent: {
+ const initial_member_count = Member.Index.known_count + @intFromBool(comp.zcu != null);
+ try coff.members.ensureTotalCapacity(gpa, initial_member_count);
+
+ assert(Member.Index.first == try coff.addMemberAssumeCapacity(.first_linker, @sizeOf(u32)));
+ coff.targetStore(coff.firstLinkerMemberNumSymbolsPtr(), 0);
+
+ assert(Member.Index.second == try coff.addMemberAssumeCapacity(.second_linker, 2 * @sizeOf(u32)));
+ coff.targetStore(coff.secondLinkerMemberNumMembersPtr(), 0);
+ coff.targetStore(coff.secondLinkerMemberNumSymbolsPtr(), 0);
+
+ assert(Member.Index.longnames == try coff.addMemberAssumeCapacity(.longnames, 0));
+
+ const first_linker_member = Member.Index.first.get(coff);
+ const second_linker_member = Member.Index.second.get(coff);
+ const longnames_member = Member.Index.longnames.get(coff);
+
+ try first_linker_member.initHeader(coff, "", timestamp);
+ try second_linker_member.initHeader(coff, "", timestamp);
+ try longnames_member.initHeader(coff, "/", timestamp);
+
+ if (comp.zcu) |zcu| {
+ const zcu_mi = try coff.addMemberAssumeCapacity(.coff, @sizeOf(std.coff.Header));
+ const zcu_member = zcu_mi.get(coff);
+ try zcu_member.initHeader(coff, zcu.main_mod.fully_qualified_name, timestamp);
+
+ break :parent zcu_member.content_ni;
+ }
+
+ // These placeholder nodes are placed before the first member - if there are
+ // no other members then the last linker member (longnames) needs to expand
+ // to fill the padding at the end of the file.
+ assert(Node.known.zcu_member_header == try coff.mf.addNodeAfter(gpa, Node.known.header, .{}));
+ assert(Node.known.zcu_member == try coff.mf.addNodeAfter(gpa, Node.known.header, .{}));
+ coff.nodes.appendAssumeCapacity(.placeholder);
+ coff.nodes.appendAssumeCapacity(.placeholder);
+
+ break :parent null;
+ } else parent: {
+ // TODO: Not ideal to have this many placeholder nodes - use two distinct `Node.known` types?
+ while (true) {
+ const placeholder_ni = try coff.mf.addLastChildNode(gpa, Node.known.file, .{});
+ coff.nodes.appendAssumeCapacity(.placeholder);
+ if (placeholder_ni == Node.known.zcu_member) break;
+ }
+
+ break :parent Node.known.header;
+ };
+
+ const coff_parent_ni = opt_coff_parent_ni orelse {
+ // If we're not generating any code, no more known nodes are used
+ while (coff.nodes.len < Node.known_count) {
+ _ = try coff.mf.addNodeAfter(gpa, Node.known.header, .{});
+ coff.nodes.appendAssumeCapacity(.placeholder);
+ }
+
+ return;
+ };
+
const coff_header_ni = Node.known.coff_header;
- assert(coff_header_ni == try coff.mf.addLastChildNode(gpa, header_ni, .{
+ assert(coff_header_ni == try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{
.size = @sizeOf(std.coff.Header),
.alignment = .@"4",
.fixed = true,
@@ -834,7 +1952,7 @@ fn initHeaders(
}
const optional_header_ni = Node.known.optional_header;
- assert(optional_header_ni == try coff.mf.addLastChildNode(gpa, header_ni, .{
+ assert(optional_header_ni == try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{
.size = optional_header_size,
.alignment = .@"4",
.fixed = true,
@@ -876,7 +1994,7 @@ fn initHeaders(
.size_of_image = 0,
.size_of_headers = 0,
.checksum = 0,
- .subsystem = .WINDOWS_CUI,
+ .subsystem = subsystem,
.dll_flags = .{
.HIGH_ENTROPY_VA = true,
.DYNAMIC_BASE = true,
@@ -925,7 +2043,7 @@ fn initHeaders(
.size_of_image = 0,
.size_of_headers = 0,
.checksum = 0,
- .subsystem = .WINDOWS_CUI,
+ .subsystem = subsystem,
.dll_flags = .{
.HIGH_ENTROPY_VA = true,
.DYNAMIC_BASE = true,
@@ -946,13 +2064,13 @@ fn initHeaders(
}
const data_directories_ni = Node.known.data_directories;
- assert(data_directories_ni == try coff.mf.addLastChildNode(gpa, header_ni, .{
+ assert(data_directories_ni == try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{
.size = data_directories_size,
.alignment = .@"4",
.fixed = true,
}));
coff.nodes.appendAssumeCapacity(.data_directories);
- {
+ if (is_image) {
const data_directories = coff.dataDirectorySlice();
@memset(data_directories, .{ .virtual_address = 0, .size = 0 });
if (target_endian != native_endian) std.mem.byteSwapAllFields(
@@ -962,7 +2080,7 @@ fn initHeaders(
}
const section_table_ni = Node.known.section_table;
- assert(section_table_ni == try coff.mf.addLastChildNode(gpa, header_ni, .{
+ assert(section_table_ni == try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{
.alignment = .@"4",
.fixed = true,
}));
@@ -970,65 +2088,292 @@ fn initHeaders(
assert(coff.nodes.len == Node.known_count);
- try coff.symbol_table.ensureTotalCapacity(gpa, Symbol.Index.known_count);
- coff.symbol_table.addOneAssumeCapacity().* = .{
- .ni = .none,
- .rva = 0,
- .size = 0,
- .loc_relocs = .none,
- .target_relocs = .none,
- .section_number = .UNDEFINED,
- };
- assert(try coff.addSection(".data", .{
+ if (!is_image) {
+ // TODO: These two nodes could be inside one movable node?
+ coff.symbol_table.ni = try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{
+ .alignment = .@"2",
+ .fixed = true,
+ .moved = true,
+ });
+ coff.nodes.appendAssumeCapacity(.symbol_table);
+
+ coff.symbol_table.strings_ni = try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{
+ .size = @sizeOf(u32),
+ .fixed = true,
+ .resized = true,
+ });
+ coff.nodes.appendAssumeCapacity(.string_table);
+ coff.targetStore(coff.symbolTableStringLenPtr(), @sizeOf(u32));
+ }
+
+ try coff.symbols.ensureTotalCapacity(gpa, Symbol.Index.known_count);
+ assert(coff.addSymbolAssumeCapacity() == .null);
+
+ // TODO: How do we tell MappedFile not to allocate physical space for .bss?
+ // TODO: Could have a node flag 'virtual' that can never have slice* or fileLocation called on it
+ // TODO: Instead of it's own section, place .bss as a pseudo-section at the end of .text in the extra space
+ assert(try coff.addSection(.@".bss", .{
+ .CNT_UNINITIALIZED_DATA = true,
+ .MEM_READ = true,
+ .MEM_WRITE = true,
+ }) == .bss);
+ assert(try coff.addSection(.@".data", .{
.CNT_INITIALIZED_DATA = true,
.MEM_READ = true,
.MEM_WRITE = true,
}) == .data);
- assert(try coff.addSection(".rdata", .{
+ assert(try coff.addSection(.@".rdata", .{
.CNT_INITIALIZED_DATA = true,
.MEM_READ = true,
}) == .rdata);
- assert(try coff.addSection(".text", .{
+ assert(try coff.addSection(.@".text", .{
.CNT_CODE = true,
.MEM_EXECUTE = true,
.MEM_READ = true,
}) == .text);
- coff.import_table.ni = try coff.mf.addLastChildNode(
- gpa,
- (try coff.objectSectionMapIndex(
- .@".idata",
+ if (is_image) {
+ if (comp.config.link_libc and target.abi == .msvc) {
+ // This section contains a function pointer table used by control flow guard:
+ // https://learn.microsoft.com/en-us/windows/win32/secbp/control-flow-guard
+ // The page containing it is set to PAGE_READONLY during startup, so this can't
+ // be merged into .data this protection would overlap writable memory.
+ _ = try coff.addSection(.@".fptable", .{
+ .CNT_INITIALIZED_DATA = true,
+ .MEM_READ = true,
+ .MEM_WRITE = true,
+ });
+ }
+
+ // TODO: Lazily initialize this instead, avoid the extra logic for this in flushMoved / flushResized
+ coff.import_table.ni = try coff.mf.addLastChildNode(
+ gpa,
+ (try coff.objectSectionMapIndex(
+ .@".idata",
+ coff.mf.flags.block_size,
+ .{ .read = true, .initialized = true },
+ )).symbol(coff).node(coff),
+ .{ .alignment = .@"4" },
+ );
+ coff.nodes.appendAssumeCapacity(.import_directory_table);
+
+ coff.export_table.ni = (try coff.pseudoSectionMapIndex(
+ .@".edata",
+ .of(std.coff.ExportDirectoryTable),
+ .{ .read = true, .initialized = true },
+ )).symbol(coff).node(coff);
+
+ coff.export_table.export_directory_table_ni = try coff.mf.addLastChildNode(
+ gpa,
+ coff.export_table.ni,
+ .{
+ .size = @sizeOf(std.coff.ExportDirectoryTable) + file_name.len + 1,
+ .moved = true,
+ .fixed = true,
+ },
+ );
+ coff.nodes.appendAssumeCapacity(.export_directory_table);
+
+ const name_index = @sizeOf(std.coff.ExportDirectoryTable);
+ const table_slice = coff.export_table.export_directory_table_ni.slice(&coff.mf);
+ @memcpy(table_slice[name_index..][0..file_name.len], file_name[0..file_name.len]);
+ @memset(table_slice[name_index + file_name.len ..], 0);
+
+ const export_address_table_ni = try coff.mf.addLastChildNode(gpa, coff.export_table.ni, .{
+ .alignment = .of(std.coff.ExportAddressTableEntry),
+ .moved = true,
+ });
+ coff.nodes.appendAssumeCapacity(.export_address_table);
+
+ try coff.symbols.ensureUnusedCapacity(gpa, 1);
+ coff.export_table.export_address_table_si = coff.addSymbolAssumeCapacity();
+
+ const export_address_table_sym = coff.export_table.export_address_table_si.get(coff);
+ export_address_table_sym.ni = export_address_table_ni;
+ assert(export_address_table_sym.loc_relocs == .none);
+ export_address_table_sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
+ export_address_table_sym.section_number =
+ coff.getNode(coff.export_table.ni).pseudo_section.symbol(coff).get(coff).section_number;
+
+ coff.export_table.name_pointer_table_ni = try coff.mf.addLastChildNode(gpa, coff.export_table.ni, .{
+ .alignment = .of(std.coff.ExportNamePointerTableEntry),
+ .moved = true,
+ });
+ coff.nodes.appendAssumeCapacity(.export_name_pointer_table);
+
+ coff.export_table.ordinal_table_ni = try coff.mf.addLastChildNode(gpa, coff.export_table.ni, .{
+ .alignment = .of(std.coff.ExportOrdinalTableEntry),
+ .moved = true,
+ });
+ coff.nodes.appendAssumeCapacity(.export_ordinal_table);
+
+ coff.export_table.name_table_ni = try coff.mf.addLastChildNode(gpa, coff.export_table.ni, .{
+ .alignment = .of(u8),
+ .moved = true,
+ });
+ coff.nodes.appendAssumeCapacity(.export_name_table);
+
+ const export_directory_table = coff.exportDirectoryTable();
+ export_directory_table.* = .{
+ .flags = 0,
+ .time_date_stamp = timestamp,
+ .major_version = 0,
+ .minor_version = 0,
+ .name_rva = 0,
+ .ordinal_base = 1,
+ .number_of_entries = 0,
+ .number_of_names = 0,
+ .export_address_table_rva = 0,
+ .name_pointer_table_rva = 0,
+ .ordinal_table_rva = 0,
+ };
+ if (target_endian != native_endian)
+ std.mem.byteSwapAllFields(std.coff.ExportDirectoryTable, export_directory_table);
+ }
+
+ if (comp.config.any_non_single_threaded) {
+ if (!is_image)
+ _ = try coff.addSection(.@".tls$", .{
+ .CNT_INITIALIZED_DATA = true,
+ .MEM_READ = true,
+ .MEM_WRITE = true,
+ });
+
+ // While tls variables allocated at runtime are writable, the template itself is not.
+ // In images, the template is in a .tls pseudo section in .rdata.
+ // In objects / archives, this section is part of the above .tls$ section. The suffix
+ // is maintained so merging can occur with other input tls symbols when linked later.
+ _ = try coff.pseudoSectionMapIndex(
+ if (is_image) .@".tls" else .@".tls$",
coff.mf.flags.block_size,
- .{ .read = true },
- )).symbol(coff).node(coff),
- .{ .alignment = .@"4", .moved = true },
- );
- coff.nodes.appendAssumeCapacity(.import_directory_table);
+ .{ .read = true, .write = !is_image, .initialized = true },
+ );
+ }
+}
- // While tls variables allocated at runtime are writable, the template itself is not
- if (comp.config.any_non_single_threaded) _ = try coff.objectSectionMapIndex(
- .@".tls$",
- coff.mf.flags.block_size,
- .{ .read = true },
- );
+pub fn initBuiltins(coff: *Coff) !void {
+ const comp = coff.base.comp;
+ const gpa = comp.gpa;
+ const target = &comp.root_mod.resolved_target.result;
+ if (coff.isImage()) {
+ const si = try coff.globalSymbol(.{ .name = "__ImageBase", .type = .data });
+ const sym = si.get(coff);
+ sym.ni = Node.known.header;
+ }
+
+ defer coff.flushSectionMerges() catch unreachable;
+ if (coff.isImage() and target.isMinGW() and comp.config.link_libc) {
+ try coff.symbols.ensureUnusedCapacity(gpa, 8);
+ try coff.globals.ensureUnusedCapacity(gpa, 2);
+ try coff.nodes.ensureUnusedCapacity(gpa, 8);
+ try coff.section_merges.ensureUnusedCapacity(gpa, 2);
+
+ const lists: []const struct { global: []const u8, start: String, end: String } = &.{
+ .{ .global = "__CTOR_LIST__", .start = .@".ctors", .end = .@".ctors$ZZZ" },
+ .{ .global = "__DTOR_LIST__", .start = .@".dtors", .end = .@".dtors$ZZZ" },
+ };
+
+ // We need to explicitly merge these into .rdata as in objects they can be marked
+ // as MEM_WRITE, and would have mismatced section flags.
+ try coff.section_merges.put(gpa, .@".ctors", .@".rdata");
+ try coff.section_merges.put(gpa, .@".dtors", .@".rdata");
+
+ for (lists) |list| {
+ const addr_info = coff.targetAddrInfo();
+
+ // Any .(c|d)tor$(.*) input sections will merge in between these sections
+ const start_osmi = try coff.objectSectionMapIndex(
+ list.start,
+ addr_info.alignment,
+ .{ .read = true, .initialized = true },
+ );
+ const end_osmi = try coff.objectSectionMapIndex(
+ list.end,
+ addr_info.alignment,
+ .{ .read = true, .initialized = true },
+ );
+
+ // Additional nodes are used here, instead of just adding the sentinel
+ // directly to the section data, since once input sections are added
+ // as children, they would overwrite that data.
+ const start_sym = start_osmi.symbol(coff).get(coff);
+ const list_len_si = try coff.globalSymbol(.{ .name = list.global, .type = .data });
+ const list_len_sym = list_len_si.get(coff);
+ list_len_sym.setExtra(.{ .size = addr_info.size });
+ list_len_sym.ni = try coff.mf.addFirstChildNode(gpa, start_sym.ni, .{
+ .size = addr_info.size,
+ .fixed = true,
+ });
+ coff.nodes.appendAssumeCapacity(.{ .builtin = list_len_si });
+ list_len_sym.section_number = start_sym.section_number;
+
+ const start_slice = list_len_sym.ni.slice(&coff.mf);
+ switch (addr_info.magic) {
+ _ => unreachable,
+ inline .PE32, .@"PE32+" => |t| {
+ const addr: *TargetAddr(t) = @ptrCast(@alignCast(start_slice));
+ // For __CTOR_LIST__ -1 indicates that the list is null terminated.
+ // For __DTOR_LIST__, this value is ignored, the list is always null terminated
+ coff.targetStore(addr, std.math.maxInt(TargetAddr(t)));
+ },
+ }
+
+ const end_sym = end_osmi.symbol(coff).get(coff);
+ const list_end_si = coff.addSymbolAssumeCapacity();
+ const list_end_sym = list_end_si.get(coff);
+ list_end_sym.setExtra(.{ .size = addr_info.size });
+ list_end_sym.ni = try coff.mf.addFirstChildNode(gpa, end_sym.ni, .{
+ .size = addr_info.size,
+ .fixed = true,
+ });
+ coff.nodes.appendAssumeCapacity(.{ .builtin = list_end_si });
+ list_end_sym.section_number = start_sym.section_number;
- assert(coff.nodes.len == expected_nodes_len);
+ @memset(list_end_sym.ni.slice(&coff.mf), 0);
+
+ try list_len_si.flushMoved(coff);
+ try list_end_si.flushMoved(coff);
+ }
+ }
}
pub fn startProgress(coff: *Coff, prog_node: std.Progress.Node) void {
prog_node.increaseEstimatedTotalItems(3);
coff.const_prog_node = prog_node.start("Constants", coff.pending_uavs.count());
coff.synth_prog_node = prog_node.start("Synthetics", count: {
- var count = coff.globals.count() - coff.global_pending_index;
+ var count =
+ coff.globals.count() - coff.global_pending_index +
+ coff.section_merges.count() - coff.section_merge_pending_index;
+
for (&coff.lazy.values) |*lazy| count += lazy.map.count() - lazy.pending_index;
break :count count;
});
+ if (!isImage(coff)) {
+ prog_node.increaseEstimatedTotalItems(2);
+ coff.symbol_prog_node = prog_node.start(
+ "Symbols",
+ coff.symbol_table.symbols.count() - coff.symbol_table.pending_symbol_index,
+ );
+ coff.member_prog_node = prog_node.start("Members", coff.pending_members.count());
+ }
+ coff.input_prog_node = prog_node.start(
+ "Inputs",
+ coff.input_sections.items.len - coff.input_section_pending_index,
+ );
coff.mf.update_prog_node = prog_node.start("Relocations", coff.mf.updates.items.len);
}
pub fn endProgress(coff: *Coff) void {
coff.mf.update_prog_node.end();
coff.mf.update_prog_node = .none;
+ coff.input_prog_node.end();
+ coff.input_prog_node = .none;
+ if (!coff.isImage()) {
+ coff.member_prog_node.end();
+ coff.member_prog_node = .none;
+ coff.symbol_prog_node.end();
+ coff.symbol_prog_node = .none;
+ }
coff.synth_prog_node.end();
coff.synth_prog_node = .none;
coff.const_prog_node.end();
@@ -1044,10 +2389,20 @@ fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 {
.file,
.header,
.signature,
+ .archive_member_header,
+ .archive_member,
.coff_header,
.optional_header,
.data_directories,
.section_table,
+ .export_name_table,
+ .placeholder,
+ .symbol_table,
+ .string_table,
+ .relocation_table,
+ .relocation_table_entry,
+ .input_section,
+ .builtin,
=> unreachable,
.image_section => |si| si,
.import_directory_table => break :parent_rva coff.targetLoad(
@@ -1062,9 +2417,21 @@ fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 {
.import_hint_name_table => |import_index| break :parent_rva coff.targetLoad(
&coff.importDirectoryEntryPtr(import_index).name_rva,
),
+ .export_directory_table => break :parent_rva coff.targetLoad(
+ &coff.dataDirectoryPtr(.EXPORT).virtual_address,
+ ),
+ .export_address_table => break :parent_rva coff.targetLoad(
+ &coff.exportDirectoryTable().export_address_table_rva,
+ ),
+ .export_name_pointer_table => break :parent_rva coff.targetLoad(
+ &coff.exportDirectoryTable().name_pointer_table_rva,
+ ),
+ .export_ordinal_table => break :parent_rva coff.targetLoad(
+ &coff.exportDirectoryTable().ordinal_table_rva,
+ ),
inline .pseudo_section,
.object_section,
- .global,
+ .import_thunk,
.nav,
.uav,
.lazy_code,
@@ -1076,24 +2443,55 @@ fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 {
const offset, _ = ni.location(&coff.mf).resolve(&coff.mf);
return @intCast(parent_rva + offset);
}
-fn computeNodeSectionOffset(coff: *Coff, ni: MappedFile.Node.Index) u32 {
- var section_offset: u32 = 0;
- var parent_ni = ni;
+
+fn computeSymbolSectionOffset(
+ coff: *Coff,
+ sym: *const Symbol,
+ relative_to: enum { image, pseudo },
+) u32 {
+ var section_offset: u32 = sym.nodeOffset(coff);
+ var parent_ni = sym.ni;
while (true) {
const offset, _ = parent_ni.location(&coff.mf).resolve(&coff.mf);
section_offset += @intCast(offset);
parent_ni = parent_ni.parent(&coff.mf);
switch (coff.getNode(parent_ni)) {
else => unreachable,
- .image_section, .pseudo_section => return section_offset,
- .object_section => {},
+ .image_section => break,
+ .pseudo_section => if (relative_to == .pseudo) break,
+ .object_section,
+ => {},
}
}
+
+ return section_offset;
}
pub inline fn targetEndian(_: *const Coff) std.lang.Endian {
return .little;
}
+
+fn targetAddrInfo(coff: *Coff) struct {
+ size: u8,
+ alignment: std.mem.Alignment,
+ magic: std.coff.OptionalHeader.Magic,
+} {
+ const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic);
+ switch (magic) {
+ _ => unreachable,
+ .PE32 => return .{ .size = 4, .alignment = .@"4", .magic = magic },
+ .@"PE32+" => return .{ .size = 8, .alignment = .@"8", .magic = magic },
+ }
+}
+
+fn TargetAddr(comptime magic: std.coff.OptionalHeader.Magic) type {
+ return switch (magic) {
+ _ => comptime unreachable,
+ .PE32 => u32,
+ .@"PE32+" => u64,
+ };
+}
+
fn targetLoad(coff: *const Coff, ptr: anytype) @typeInfo(@TypeOf(ptr)).pointer.child {
const Child = @typeInfo(@TypeOf(ptr)).pointer.child;
return switch (@typeInfo(Child)) {
@@ -1122,9 +2520,55 @@ fn targetStore(coff: *const Coff, ptr: anytype, val: @typeInfo(@TypeOf(ptr)).poi
}
pub fn headerPtr(coff: *Coff) *std.coff.Header {
+ assert(coff.hasCoffHeader());
return @ptrCast(@alignCast(Node.known.coff_header.slice(&coff.mf)));
}
+pub fn firstLinkerMemberNumSymbolsPtr(coff: *Coff) *u32 {
+ assert(coff.isArchive());
+ return @ptrCast(@alignCast(Node.known.first_linker_member.slice(&coff.mf)));
+}
+
+pub fn firstLinkerMemberOffsetsSlice(coff: *Coff) []u32 {
+ const len = std.mem.toNative(u32, coff.firstLinkerMemberNumSymbolsPtr().*, .big);
+ return @ptrCast(@alignCast(Node.known.first_linker_member.slice(&coff.mf)[@sizeOf(u32)..][0 .. len * @sizeOf(u32)]));
+}
+
+pub fn secondLinkerMemberNumMembersPtr(coff: *Coff) *align(2) u32 {
+ assert(coff.isArchive());
+ return @ptrCast(@alignCast(Node.known.second_linker_member.slice(&coff.mf)));
+}
+
+pub fn secondLinkerMemberOffsetsSlice(coff: *Coff) []align(2) u32 {
+ const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr());
+ return @ptrCast(@alignCast(
+ Node.known.second_linker_member.slice(&coff.mf)[@sizeOf(u32)..][0 .. num_members * @sizeOf(u32)],
+ ));
+}
+
+pub fn secondLinkerMemberNumSymbolsPtr(coff: *Coff) *align(2) u32 {
+ const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr());
+ return @ptrCast(@alignCast(
+ Node.known.second_linker_member.slice(&coff.mf)[(1 + num_members) * @sizeOf(u32) ..],
+ ));
+}
+
+pub fn secondLinkerMemberIndicesSlice(coff: *Coff) []u16 {
+ const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr());
+ const num_symbols = coff.targetLoad(coff.secondLinkerMemberNumSymbolsPtr());
+ return @ptrCast(@alignCast(
+ Node.known.second_linker_member.slice(&coff.mf)[(2 + num_members) * @sizeOf(u32) ..][0 .. num_symbols * @sizeOf(u16)],
+ ));
+}
+
+pub fn secondLinkerMemberStringsSlice(coff: *Coff) []u8 {
+ const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr());
+ const num_symbols = coff.targetLoad(coff.secondLinkerMemberNumSymbolsPtr());
+ return @ptrCast(@alignCast(
+ Node.known.second_linker_member.slice(&coff.mf)[(2 + num_members) * @sizeOf(u32) + num_symbols * @sizeOf(u16) ..],
+ ));
+}
+
pub fn optionalHeaderStandardPtr(coff: *Coff) *std.coff.OptionalHeader {
return @ptrCast(@alignCast(
Node.known.optional_header.slice(&coff.mf)[0..@sizeOf(std.coff.OptionalHeader)],
@@ -1136,6 +2580,7 @@ pub const OptionalHeaderPtr = union(std.coff.OptionalHeader.Magic) {
@"PE32+": *std.coff.OptionalHeader.@"PE32+",
};
pub fn optionalHeaderPtr(coff: *Coff) OptionalHeaderPtr {
+ assert(coff.isImage());
const slice = Node.known.optional_header.slice(&coff.mf);
return switch (coff.targetLoad(&coff.optionalHeaderStandardPtr().magic)) {
_ => unreachable,
@@ -1150,6 +2595,7 @@ pub fn optionalHeaderField(
coff: *Coff,
comptime field: std.meta.FieldEnum(std.coff.OptionalHeader.@"PE32+"),
) @FieldType(std.coff.OptionalHeader.@"PE32+", @tagName(field)) {
+ assert(coff.isImage());
return switch (coff.optionalHeaderPtr()) {
inline else => |optional_header| coff.targetLoad(&@field(optional_header, @tagName(field))),
};
@@ -1158,6 +2604,7 @@ pub fn optionalHeaderField(
pub fn dataDirectorySlice(
coff: *Coff,
) *[std.coff.IMAGE.DIRECTORY_ENTRY.len]std.coff.ImageDataDirectory {
+ assert(coff.isImage());
return @ptrCast(@alignCast(Node.known.data_directories.slice(&coff.mf)));
}
pub fn dataDirectoryPtr(
@@ -1168,10 +2615,48 @@ pub fn dataDirectoryPtr(
}
pub fn sectionTableSlice(coff: *Coff) []std.coff.SectionHeader {
- return @ptrCast(@alignCast(Node.known.section_table.slice(&coff.mf)));
+ return @ptrCast(@alignCast(
+ Node.known.section_table.slice(&coff.mf)[0 .. coff.section_table.count() * @sizeOf(std.coff.SectionHeader)],
+ ));
+}
+
+pub fn symbolTableEntryStoragePtr(coff: *Coff, index: u32) *[std.coff.Symbol.sizeOf()]u8 {
+ assert(!coff.isImage());
+ const offset = index * std.coff.Symbol.sizeOf();
+ return @ptrCast(@alignCast(coff.symbol_table.ni.slice(&coff.mf)[offset..][0..std.coff.Symbol.sizeOf()]));
+}
+
+pub fn symbolTableEntryPtr(coff: *Coff, sti: SymbolTable.Index) ?*align(2) std.coff.Symbol {
+ if (sti.unwrap()) |index|
+ return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, index)))
+ else
+ return null;
+}
+
+pub fn symbolTableSectionAuxEntryPtr(coff: *Coff, sti: SymbolTable.Index) ?*align(2) std.coff.SectionDefinition {
+ if (symbolTableEntryPtr(coff, sti)) |entry| {
+ assert(entry.storage_class == .STATIC and entry.number_of_aux_symbols == 1);
+ return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, sti.unwrap().? + 1)));
+ } else {
+ return null;
+ }
+}
+
+pub fn symbolTableWeakExternalAuxEntryPtr(coff: *Coff, sti: SymbolTable.Index) ?*align(2) std.coff.WeakExternalDefinition {
+ if (symbolTableEntryPtr(coff, sti)) |entry| {
+ assert(entry.storage_class == .WEAK_EXTERNAL and entry.number_of_aux_symbols == 1);
+ return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, sti.unwrap().? + 1)));
+ } else {
+ return null;
+ }
+}
+
+pub fn symbolTableStringLenPtr(coff: *Coff) *align(1) u32 {
+ return @ptrCast(@alignCast(coff.symbol_table.strings_ni.slice(&coff.mf)[0..@sizeOf(u32)]));
}
pub fn importDirectoryTableSlice(coff: *Coff) []std.coff.ImportDirectoryEntry {
+ assert(coff.isImage());
return @ptrCast(@alignCast(coff.import_table.ni.slice(&coff.mf)));
}
pub fn importDirectoryEntryPtr(
@@ -1181,16 +2666,40 @@ pub fn importDirectoryEntryPtr(
return &coff.importDirectoryTableSlice()[@intFromEnum(import_index)];
}
+pub fn exportDirectoryTable(coff: *Coff) *std.coff.ExportDirectoryTable {
+ return @ptrCast(@alignCast(coff.export_table.export_directory_table_ni.slice(&coff.mf)));
+}
+
+pub fn exportNamePointerTableSlice(coff: *Coff) []std.coff.ExportNamePointerTableEntry {
+ const debug = coff.export_table.name_pointer_table_ni.slice(&coff.mf);
+ _ = debug;
+
+ return @ptrCast(@alignCast(coff.export_table.name_pointer_table_ni.slice(&coff.mf)));
+}
+
+pub fn exportOrdinalTableSlice(coff: *Coff) []std.coff.ExportOrdinalTableEntry {
+ return @ptrCast(@alignCast(coff.export_table.ordinal_table_ni.slice(&coff.mf)));
+}
+
fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index {
- defer coff.symbol_table.addOneAssumeCapacity().* = .{
+ defer coff.symbols.addOneAssumeCapacity().* = .{
.ni = .none,
.rva = 0,
- .size = 0,
+ .value = .{ .none = {} },
+ .extra = .{ .size = 0 },
+ .flags = .{
+ .value_tag = .none,
+ .extra_tag = .size,
+ .type = .unknown,
+ .dll_storage_class = .default,
+ .weak_external_strat = .none,
+ },
.loc_relocs = .none,
.target_relocs = .none,
.section_number = .UNDEFINED,
+ .gmi = .none,
};
- return @enumFromInt(coff.symbol_table.items.len);
+ return @enumFromInt(coff.symbols.items.len);
}
fn initSymbolAssumeCapacity(coff: *Coff) !Symbol.Index {
@@ -1205,12 +2714,55 @@ fn getOrPutString(coff: *Coff, string: []const u8) !String {
fn getOrPutOptionalString(coff: *Coff, string: ?[]const u8) !String.Optional {
return (try coff.getOrPutString(string orelse return .none)).toOptional();
}
+fn getString(coff: *Coff, string: []const u8) String.Optional {
+ if (coff.strings.getKeyAdapted(
+ string,
+ std.hash_map.StringIndexAdapter{ .bytes = &coff.string_bytes },
+ )) |key|
+ return @as(String, @enumFromInt(key)).toOptional()
+ else
+ return .none;
+}
+
+/// If the name does not fit in the symbol header, adds it to the symbol table string table.
+/// If the caller knows this name already has a String associated with it, they can avoid
+/// a redundant call to `getOrPutString` by specifying `opt_string`.
+/// The lifetime of the return value matches that of `name`.
+fn getOrPutSymbolName(coff: *Coff, name: []const u8, opt_string: ?String) !SymbolTable.SymbolName {
+ assert(!coff.isImage());
+ const gpa = coff.base.comp.gpa;
+
+ return if (name.len > header_name_max_len) name: {
+ const string = opt_string orelse try coff.getOrPutString(name);
+ const string_gop = try coff.symbol_table.strings.getOrPut(gpa, string);
+ if (!string_gop.found_existing) {
+ const string_index = coff.symbol_table.strings_ni.location(&coff.mf).resolve(&coff.mf)[1];
+ string_gop.value_ptr.* = @enumFromInt(string_index);
+
+ try coff.symbol_table.strings_ni.resize(&coff.mf, gpa, string_index + name.len + 1);
+ const slice = coff.symbol_table.strings_ni.slice(&coff.mf);
+ @memcpy(slice[@intCast(string_index)..][0..name.len], name);
+ slice[@intCast(string_index + name.len)] = 0;
+ }
+
+ break :name .{ .long = string_gop.value_ptr.* };
+ } else .{ .short = name };
+}
+/// `len` does not include null terminators
fn ensureUnusedStringCapacity(coff: *Coff, len: usize) !void {
const gpa = coff.base.comp.gpa;
try coff.strings.ensureUnusedCapacityContext(gpa, 1, .{ .bytes = &coff.string_bytes });
try coff.string_bytes.ensureUnusedCapacity(gpa, len + 1);
}
+
+/// `total_len` includes null terminators
+fn ensureManyUnusedStringCapacity(coff: *Coff, num_strings: u32, total_len: usize) !void {
+ const gpa = coff.base.comp.gpa;
+ try coff.strings.ensureUnusedCapacityContext(gpa, num_strings, .{ .bytes = &coff.string_bytes });
+ try coff.string_bytes.ensureUnusedCapacity(gpa, total_len + num_strings);
+}
+
fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String {
const gop = coff.strings.getOrPutAssumeCapacityAdapted(
string,
@@ -1225,18 +2777,78 @@ fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String {
return @enumFromInt(gop.key_ptr.*);
}
-pub fn globalSymbol(coff: *Coff, name: []const u8, lib_name: ?[]const u8) !Symbol.Index {
- const gpa = coff.base.comp.gpa;
- try coff.symbol_table.ensureUnusedCapacity(gpa, 1);
- const sym_gop = try coff.globals.getOrPut(gpa, .{
- .name = try coff.getOrPutString(name),
- .lib_name = try coff.getOrPutOptionalString(lib_name),
- });
+const GlobalOptions = struct {
+ name: []const u8,
+ lib_name: ?[]const u8 = null,
+ type: Symbol.Type = .unknown,
+ dll_storage_class: Symbol.DllStorageClass = .default,
+};
+
+fn getOrPutGlobalSymbol(
+ coff: *Coff,
+ opts: GlobalOptions,
+) !std.array_hash_map.Auto(String, Global).GetOrPutResult {
+ const comp = coff.base.comp;
+ const gpa = comp.gpa;
+ try coff.symbols.ensureUnusedCapacity(gpa, 1);
+
+ const lib_name: String.Optional = if (opts.lib_name) |lib_name| lib_name: {
+ const is_libc = std.zig.target.isLibCLibName(&comp.root_mod.resolved_target.result, lib_name);
+ if (is_libc) {
+ // This is guaranteed by Sema.handleExternLibName
+ if (!comp.config.link_libc) unreachable;
+
+ // TODO: The user has requested this symbol come from libc, but this logic allows
+ // it to come from anywhere. We need to know what inputs are libc inputs,
+ // and set a flag to only search them for this symbol.
+ break :lib_name .none;
+ }
+
+ break :lib_name (try coff.getOrPutString(lib_name)).toOptional();
+ } else .none;
+
+ const sym_gop = try coff.globals.getOrPut(gpa, try coff.getOrPutString(opts.name));
if (!sym_gop.found_existing) {
- sym_gop.value_ptr.* = coff.addSymbolAssumeCapacity();
+ const si = coff.addSymbolAssumeCapacity();
+ const sym = si.get(coff);
+ sym.gmi = .wrap(@intCast(sym_gop.index));
+ sym.flags.type = opts.type;
+ sym.flags.dll_storage_class = opts.dll_storage_class;
+ sym_gop.value_ptr.* = .{
+ .si = si,
+ .lib_name = lib_name,
+ };
coff.synth_prog_node.increaseEstimatedTotalItems(1);
+
+ log.debug("globalSymbol({s}, {?s}) = {d}", .{ opts.name, opts.lib_name, si });
+ }
+
+ return sym_gop;
+}
+
+fn getDefinedGlobal(coff: *Coff, name: []const u8) Symbol.Index {
+ if (coff.globals.get(
+ coff.getString(name).unwrap() orelse return .null,
+ )) |global| if (global.si.get(coff).ni != .none) return global.si;
+ return .null;
+}
+
+pub fn globalSymbol(coff: *Coff, opts: GlobalOptions) !Symbol.Index {
+ const gop = try coff.getOrPutGlobalSymbol(opts);
+ return gop.value_ptr.si;
+}
+
+pub fn pendingSymbolTableEntry(coff: *Coff, si: Symbol.Index) !void {
+ assert(!coff.isImage());
+ const sym = si.get(coff);
+
+ assert(sym.ni != .none or sym.gmi != .none);
+ const gpa = coff.base.comp.gpa;
+ const gop = try coff.symbol_table.symbols.getOrPut(gpa, si);
+ if (!gop.found_existing) {
+ coff.symbol_prog_node.increaseEstimatedTotalItems(1);
+ gop.value_ptr.* = .none;
}
- return sym_gop.value_ptr.*;
}
fn navSection(
@@ -1247,13 +2859,13 @@ fn navSection(
const ip = &zcu.intern_pool;
const default: String, const attributes: ObjectSectionAttributes =
if (nav_resolved.@"threadlocal" and coff.base.comp.config.any_non_single_threaded) .{
- .@".tls$", .{ .read = true, .write = true },
+ .@".tls$", .{ .read = true, .write = true, .initialized = true },
} else if (ip.isFunctionType(nav_resolved.type)) .{
.@".text", .{ .read = true, .execute = true },
} else if (nav_resolved.@"const") .{
- .@".rdata", .{ .read = true },
+ .@".rdata", .{ .read = true, .initialized = true },
} else .{
- .@".data", .{ .read = true, .write = true },
+ .@".data", .{ .read = true, .write = true, .initialized = true },
};
return (try coff.objectSectionMapIndex(
@@ -1270,7 +2882,7 @@ fn navSection(
}
fn navMapIndex(coff: *Coff, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Node.NavMapIndex {
const gpa = zcu.gpa;
- try coff.symbol_table.ensureUnusedCapacity(gpa, 1);
+ try coff.symbols.ensureUnusedCapacity(gpa, 1);
const sym_gop = try coff.navs.getOrPut(gpa, nav_index);
if (!sym_gop.found_existing) sym_gop.value_ptr.* = coff.addSymbolAssumeCapacity();
return @enumFromInt(sym_gop.index);
@@ -1278,17 +2890,20 @@ fn navMapIndex(coff: *Coff, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Node.Na
pub fn navSymbol(coff: *Coff, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Symbol.Index {
const ip = &zcu.intern_pool;
const nav = ip.getNav(nav_index);
- if (nav.getExtern(ip)) |@"extern"| return coff.globalSymbol(
- @"extern".name.toSlice(ip),
- @"extern".lib_name.toSlice(ip),
- );
+ if (nav.getExtern(ip)) |@"extern"| return coff.globalSymbol(.{
+ .name = @"extern".name.toSlice(ip),
+ .lib_name = @"extern".lib_name.toSlice(ip),
+ // TODO: Threadlocal as well?
+ .type = if (ip.isFunctionType(nav.resolved.?.type)) .code else .data,
+ .dll_storage_class = if (@"extern".is_dll_import) .dllimport else .default,
+ });
const nmi = try coff.navMapIndex(zcu, nav_index);
return nmi.symbol(coff);
}
fn uavMapIndex(coff: *Coff, uav_val: InternPool.Index) !Node.UavMapIndex {
const gpa = coff.base.comp.gpa;
- try coff.symbol_table.ensureUnusedCapacity(gpa, 1);
+ try coff.symbols.ensureUnusedCapacity(gpa, 1);
const sym_gop = try coff.uavs.getOrPut(gpa, uav_val);
if (!sym_gop.found_existing) sym_gop.value_ptr.* = coff.addSymbolAssumeCapacity();
return @enumFromInt(sym_gop.index);
@@ -1300,7 +2915,7 @@ pub fn uavSymbol(coff: *Coff, uav_val: InternPool.Index) !Symbol.Index {
pub fn lazySymbol(coff: *Coff, lazy: link.File.LazySymbol) !Symbol.Index {
const gpa = coff.base.comp.gpa;
- try coff.symbol_table.ensureUnusedCapacity(gpa, 1);
+ try coff.symbols.ensureUnusedCapacity(gpa, 1);
const sym_gop = try coff.lazy.getPtr(lazy.kind).map.getOrPut(gpa, lazy.ty);
if (!sym_gop.found_existing) {
sym_gop.value_ptr.* = try coff.initSymbolAssumeCapacity();
@@ -1314,7 +2929,7 @@ pub fn getNavVAddr(
pt: Zcu.PerThread,
nav: InternPool.Nav.Index,
reloc_info: link.File.RelocInfo,
-) !u64 {
+) link.Error!u64 {
return coff.getVAddr(reloc_info, try coff.navSymbol(pt.zcu, nav));
}
@@ -1322,30 +2937,426 @@ pub fn getUavVAddr(
coff: *Coff,
uav: InternPool.Index,
reloc_info: link.File.RelocInfo,
-) !u64 {
+) link.Error!u64 {
return coff.getVAddr(reloc_info, try coff.uavSymbol(uav));
}
-pub fn getVAddr(coff: *Coff, reloc_info: link.File.RelocInfo, target_si: Symbol.Index) !u64 {
+pub fn getVAddr(coff: *Coff, reloc_info: link.File.RelocInfo, target_si: Symbol.Index) link.Error!u64 {
try coff.addReloc(
@enumFromInt(@intFromEnum(reloc_info.parent.atom_index)),
reloc_info.offset,
target_si,
- reloc_info.addend,
+ .{ .known = reloc_info.addend },
switch (coff.targetLoad(&coff.headerPtr().machine)) {
else => unreachable,
.AMD64 => .{ .AMD64 = .ADDR64 },
.I386 => .{ .I386 = .DIR32 },
},
);
- return coff.optionalHeaderField(.image_base) + target_si.get(coff).rva;
+
+ var vaddr: u64 = target_si.get(coff).rva;
+ if (coff.isImage()) vaddr += coff.optionalHeaderField(.image_base);
+ return vaddr;
+}
+
+/// Caller guarantees there is capacity for one member and two nodes
+fn addMemberAssumeCapacity(coff: *Coff, kind: std.coff.ArchiveMemberHeader.Kind, size: u64) !Member.Index {
+ const comp = coff.base.comp;
+ const gpa = comp.gpa;
+
+ // TODO: These two nodes could to be inside a movable node if kind == .coff|.import
+ const header_ni = try coff.mf.addLastChildNode(gpa, Node.known.file, .{
+ .size = @sizeOf(std.coff.ArchiveMemberHeader),
+ .alignment = .@"2",
+ .fixed = true,
+ .moved = true,
+ });
+
+ const content_ni = try coff.mf.addLastChildNode(gpa, Node.known.file, .{
+ // The actual alignment required by the spec is 2, but to allow aligned access to
+ // the various COFF data structures in-place during linking we overalign
+ .alignment = switch (kind) {
+ .coff => .@"4",
+ else => .@"2",
+ },
+ .size = size,
+ .resized = size > 0,
+ .fixed = true,
+ });
+
+ const mi: Member.Index = @enumFromInt(coff.members.items.len);
+ coff.members.appendAssumeCapacity(.{
+ .kind = kind,
+ .header_ni = header_ni,
+ .content_ni = content_ni,
+ .first_linker_indices = .empty,
+ });
+
+ coff.nodes.appendAssumeCapacity(.{ .archive_member_header = mi });
+ coff.nodes.appendAssumeCapacity(.{ .archive_member = mi });
+
+ switch (kind) {
+ .first_linker, .second_linker, .longnames => {},
+ else => {
+ const new_num_members = coff.members.items.len - Member.Index.known_count;
+ coff.targetStore(
+ coff.secondLinkerMemberNumMembersPtr(),
+ @intCast(new_num_members),
+ );
+
+ const old_size = Node.known.second_linker_member.location(&coff.mf).resolve(&coff.mf)[1];
+ const old_header_size = new_num_members * @sizeOf(u32);
+ const trailing_size: usize = @intCast(old_size - old_header_size);
+ try Node.known.second_linker_member.resize(&coff.mf, gpa, old_size + @sizeOf(u32));
+
+ const slice = Node.known.second_linker_member.slice(&coff.mf);
+ @memmove(
+ slice[old_header_size + @sizeOf(u32) ..][0..trailing_size],
+ slice[old_header_size..][0..trailing_size],
+ );
+
+ // Offset will be written by flushMoved on header_ni
+ },
+ }
+
+ switch (kind) {
+ .first_linker,
+ .longnames,
+ .import,
+ => {},
+ .second_linker,
+ .coff,
+ => {
+ try coff.pending_members.ensureTotalCapacity(
+ gpa,
+ coff.pending_members.capacity() + 1,
+ );
+ coff.member_prog_node.increaseEstimatedTotalItems(1);
+ },
+ }
+
+ return mi;
+}
+
+fn appendMemberSymbolString(
+ coff: *Coff,
+ strings_ni: MappedFile.Node.Index,
+ new_size: u64,
+ name: []const u8,
+ offset: u64,
+) !void {
+ try strings_ni.resize(&coff.mf, coff.base.comp.gpa, new_size);
+ const name_slice = strings_ni.slice(&coff.mf)[offset..][0 .. name.len + 1];
+ @memcpy(name_slice[0..name.len], name);
+ name_slice[name.len] = 0;
+}
+
+fn ensureMemberSymbol(coff: *Coff, mi: Member.Index, name: String) !void {
+ const gpa = coff.base.comp.gpa;
+ const member = mi.get(coff);
+ assert(member.kind == .coff);
+
+ const gop = try member.first_linker_indices.getOrPut(gpa, .{ .mi = mi, .name = name });
+ if (gop.found_existing) return;
+
+ const mfli: Member.FirstLinkerIndex = blk: {
+ const num_symbols_ptr = coff.firstLinkerMemberNumSymbolsPtr();
+ const num_symbols = std.mem.toNative(u32, num_symbols_ptr.*, .big);
+ num_symbols_ptr.* = std.mem.nativeTo(u32, num_symbols + 1, .big);
+ break :blk @enumFromInt(num_symbols);
+ };
+
+ gop.value_ptr.* = mfli;
+
+ // Linker member fields are not modeled as nodes because MappedFile
+ // can't guarantee that they will be tightly packed after resizing
+
+ const name_slice = name.toSlice(coff);
+ const new_string_table_size: u32 = @intCast(coff.lib_string_len + name_slice.len + 1);
+ defer coff.lib_string_len = new_string_table_size;
+
+ {
+ const old_header_size: usize = @intCast(@sizeOf(u32) + @intFromEnum(mfli) * @sizeOf(u32));
+ const new_header_size: usize = @intCast(old_header_size + @sizeOf(u32));
+ try Node.known.first_linker_member.resize(&coff.mf, gpa, new_header_size + new_string_table_size);
+
+ const slice = Node.known.first_linker_member.slice(&coff.mf);
+ @memmove(slice[new_header_size..][0..coff.lib_string_len], slice[old_header_size..][0..coff.lib_string_len]);
+ @memcpy(slice[new_header_size + coff.lib_string_len ..][0..name_slice.len], name_slice[0..name_slice.len]);
+ slice[new_header_size + coff.lib_string_len + name_slice.len] = 0;
+
+ // New offset entry is written in flushMember
+ }
+
+ {
+ const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr());
+ const old_header_size = 2 * @sizeOf(u32) + num_members * @sizeOf(u32) + @intFromEnum(mfli) * @sizeOf(u16);
+ const new_header_size = old_header_size + @sizeOf(u16);
+ try Node.known.second_linker_member.resize(&coff.mf, gpa, new_header_size + new_string_table_size);
+
+ const old_needs_sort = coff.pending_members.get(Member.Index.second) != null;
+ const needs_sort = old_needs_sort or (if (coff.lib_string_table.items.len > 0)
+ std.mem.lessThan(
+ u8,
+ name_slice,
+ coff.lib_string_table.items[coff.lib_string_table.items.len - 1].toSlice(coff),
+ )
+ else
+ false);
+
+ try coff.lib_string_table.append(gpa, name);
+
+ const slice = Node.known.second_linker_member.slice(&coff.mf);
+ coff.targetStore(coff.secondLinkerMemberNumSymbolsPtr(), @intFromEnum(mfli) + 1);
+ if (!needs_sort) {
+ @memmove(slice[new_header_size..][0..coff.lib_string_len], slice[old_header_size..][0..coff.lib_string_len]);
+ @memcpy(slice[new_header_size + coff.lib_string_len ..][0..name_slice.len], name_slice[0..name_slice.len]);
+ slice[new_header_size + coff.lib_string_len + name_slice.len] = 0;
+ } else if (!old_needs_sort) {
+ // The entire string table is rebuilt in flushMember after sorting
+ coff.pending_members.putAssumeCapacity(Member.Index.second, {});
+ }
+
+ // Indices in this table are 1-based
+ const index_ptr: *u16 = @ptrCast(@alignCast(slice[old_header_size..]));
+ coff.targetStore(index_ptr, @intCast(@intFromEnum(mi) - Member.Index.known_count + 1));
+ }
+
+ coff.pending_members.putAssumeCapacity(mi, {});
+ coff.member_prog_node.increaseEstimatedTotalItems(1);
+}
+
+fn flushSymbolTableEntry(coff: *Coff, index: u32, pt: Zcu.PerThread) !void {
+ assert(!coff.isImage());
+ const gpa = coff.base.comp.gpa;
+
+ const si = coff.symbol_table.symbols.keys()[index];
+ const sti = &coff.symbol_table.symbols.values()[index];
+
+ const sym = si.get(coff);
+ assert(sym.ni != .none or sym.gmi != .none);
+
+ const entry = coff.symbolTableEntryPtr(sti.*) orelse entry: {
+ var buf: [15]u8 = undefined;
+ const symbol_name, const num_aux_symbols: u8, const complex_type: std.coff.ComplexType =
+ if (sym.gmi != .none) blk: {
+ const name = sym.gmi.name(coff);
+ break :blk .{
+ try coff.getOrPutSymbolName(name.toSlice(coff), name),
+ @intFromBool(sym.flags.weak_external_strat != .none),
+ if (Symbol.Index.text.get(coff).section_number == sym.section_number)
+ .FUNCTION
+ else
+ .NULL,
+ };
+ } else blk: switch (coff.getNode(sym.ni)) {
+ .image_section => .{
+ try coff.getOrPutSymbolName(&sym.section_number.header(coff).name, null),
+ 1,
+ .NULL,
+ },
+ .nav => |nmi| {
+ const zcu = coff.base.comp.zcu.?;
+ const ip = &zcu.intern_pool;
+ const nav = ip.getNav(nmi.navIndex(coff));
+ break :blk .{
+ try coff.getOrPutSymbolName(nav.fqn.toSlice(ip), null),
+ 0,
+ if (ip.isFunctionType(nav.resolved.?.type)) .FUNCTION else .NULL,
+ };
+ },
+ .uav => |umi| {
+ var w = Io.Writer.fixed(&buf);
+ w.print("__anon_{x}", .{umi.uavValue(coff)}) catch unreachable;
+ break :blk .{
+ try coff.getOrPutSymbolName(w.buffered(), null),
+ 0,
+ .NULL,
+ };
+ },
+ inline .lazy_code, .lazy_const_data => |mi, tag| {
+ const lazy_sym = mi.lazySymbol(coff);
+ const name = try std.fmt.allocPrint(gpa, "__lazy_{s}_{f}", .{
+ @tagName(lazy_sym.kind),
+ Type.fromInterned(lazy_sym.ty).fmt(pt),
+ });
+ defer gpa.free(name);
+
+ const string = try coff.getOrPutString(name);
+ break :blk .{
+ try coff.getOrPutSymbolName(string.toSlice(coff), string),
+ 0,
+ if (tag == .lazy_code) .FUNCTION else .NULL,
+ };
+ },
+ else => {
+ log.err("TODO implement symbol table init for {s} ({d})", .{ @tagName(coff.getNode(sym.ni)), si });
+ unreachable;
+ },
+ };
+
+ const old_num_symbols = coff.targetLoad(&coff.headerPtr().number_of_symbols);
+ const new_num_symbols = old_num_symbols + 1 + num_aux_symbols;
+ coff.targetStore(&coff.headerPtr().number_of_symbols, new_num_symbols);
+
+ try coff.symbol_table.ni.resize(&coff.mf, gpa, new_num_symbols * std.coff.Symbol.sizeOf());
+
+ sti.* = .wrap(old_num_symbols);
+ si.flushSymbolTableIndex(coff);
+
+ const entry = coff.symbolTableEntryPtr(sti.*).?;
+ symbol_name.store(coff, &entry.name);
+
+ entry.section_number = @enumFromInt(@intFromEnum(sym.section_number));
+ entry.type = .{
+ .complex_type = complex_type,
+ .base_type = .NULL,
+ };
+
+ entry.storage_class = if (sym.gmi != .none)
+ .EXTERNAL
+ else if (sym.flags.extra_tag == .next_alias_si) storage: {
+ var alias_sym = sym;
+ const weak_external = while (alias_sym.flags.extra_tag == .next_alias_si) {
+ const alias_si = alias_sym.extra.next_alias_si;
+ alias_sym = alias_si.get(coff);
+ assert(alias_sym.ni == sym.ni);
+ if (alias_sym.flags.weak_external_strat != .none)
+ break true;
+ } else false;
+ break :storage if (weak_external) .EXTERNAL else .STATIC;
+ } else .STATIC;
+
+ entry.number_of_aux_symbols = num_aux_symbols;
+ if (coff.targetEndian() != native_endian)
+ std.mem.byteSwapAllFieldsAligned(std.coff.Symbol, .@"2", entry);
+
+ if (num_aux_symbols > 0) aux_init: {
+ if (sym.gmi != .none) {
+ entry.section_number = .UNDEFINED;
+ entry.storage_class = .WEAK_EXTERNAL;
+
+ const tag_index = sym.value.weak_alias_si.sti(coff).unwrap().?;
+ const aux_ptr = coff.symbolTableWeakExternalAuxEntryPtr(sti.*).?;
+ aux_ptr.* = .{
+ .tag_index = tag_index,
+ .flag = switch (sym.flags.weak_external_strat) {
+ .none => unreachable,
+ .no_library => .SEARCH_NOLIBRARY,
+ .library => .SEARCH_LIBRARY,
+ .alias => .SEARCH_ALIAS,
+ .anti_dependency => .ANTI_DEPENDENCY,
+ },
+ .unused = @splat(0),
+ };
+ if (coff.targetEndian() != native_endian)
+ std.mem.byteSwapAllFieldsAligned(std.coff.WeakExternalDefinition, .@"2", aux_ptr);
+
+ break :aux_init;
+ } else switch (coff.getNode(sym.ni)) {
+ .image_section => |sec_si| {
+ assert(si == sec_si);
+ const header = sym.section_number.header(coff);
+ const aux_ptr = coff.symbolTableSectionAuxEntryPtr(sti.*).?;
+ aux_ptr.* = .{
+ .length = @intCast(sym.ni.location(&coff.mf).resolve(&coff.mf)[1]),
+ .number_of_relocations = header.number_of_relocations,
+ .number_of_linenumbers = header.number_of_linenumbers,
+ .checksum = 0,
+ .number = 0,
+ .selection = .NONE,
+ .unused = @splat(0),
+ };
+ if (coff.targetEndian() != native_endian)
+ std.mem.byteSwapAllFieldsAligned(std.coff.SectionDefinition, .@"2", aux_ptr);
+
+ break :aux_init;
+ },
+ else => {},
+ }
+
+ unreachable;
+ }
+
+ break :entry entry;
+ };
+
+ coff.targetStore(&entry.value, switch (sym.section_number) {
+ .UNDEFINED => if (entry.storage_class == .WEAK_EXTERNAL) 0 else sym.size(),
+ .ABSOLUTE,
+ .DEBUG,
+ => unreachable,
+ else => switch (coff.getNode(sym.ni)) {
+ .image_section => 0,
+ else => coff.computeSymbolSectionOffset(sym, .image),
+ },
+ });
+
+ log.debug("flushSymbolTableEntry({d}) = {d}", .{ si, sti.* });
+}
+
+fn flushInputMember(coff: *Coff, iami: InputArchive.Member.Index) !void {
+ const member = iami.member(coff);
+ assert(!member.flags.is_loaded);
+ defer member.flags.is_loaded = true;
+ switch (member.content) {
+ .import => unreachable,
+ .object => |file_location| {
+ if (file_location.size == 0) return;
+ const comp = coff.base.comp;
+ const io = comp.io;
+ const path = member.iai.path(coff);
+ const file = try path.root_dir.handle.openFile(io, path.sub_path, .{});
+ defer file.close(io);
+ var buffer: [4096]u8 = undefined;
+ var fr = file.reader(io, &buffer);
+ const offset = file_location.offset + @sizeOf(std.coff.ArchiveMemberHeader);
+ try fr.seekTo(offset);
+ log.debug("flushInputMember({f}({s}))", .{ path, member.name.toSlice(coff) });
+ try coff.loadObject(path, member.name.toSlice(coff), &fr, .{
+ .offset = offset,
+ .size = file_location.size,
+ });
+ },
+ }
+}
+
+fn flushInputSection(coff: *Coff, isi: Node.InputSection.Index) !void {
+ const file_loc = isi.fileLocation(coff);
+ if (file_loc.size == 0) return;
+ const comp = coff.base.comp;
+ const io = comp.io;
+ const gpa = comp.gpa;
+ const ioi = isi.input(coff);
+ const path = ioi.path(coff);
+ const file = try path.root_dir.handle.openFile(io, path.sub_path, .{});
+ defer file.close(io);
+ var fr = file.reader(io, &.{});
+ try fr.seekTo(file_loc.offset);
+ var nw: MappedFile.Node.Writer = undefined;
+ const si = isi.symbol(coff);
+ si.node(coff).writer(&coff.mf, gpa, &nw);
+ defer nw.deinit();
+ log.debug("flushInputSection({f}{f}, {s}, {d}, n{d})", .{
+ path,
+ fmtMemberNameString(ioi.memberName(coff)),
+ si.get(coff).section_number.name(coff).toSlice(coff),
+ si,
+ si.node(coff),
+ });
+ if (try nw.interface.sendFileAll(&fr, .limited(@intCast(file_loc.size))) != file_loc.size)
+ return error.EndOfStream;
+ try si.applyLocationRelocs(coff);
}
-fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags) !Symbol.Index {
+fn addSection(coff: *Coff, name: String, flags: std.coff.SectionHeader.Flags) !Symbol.Index {
+ assert(coff.hasCoffHeader());
+
const gpa = coff.base.comp.gpa;
try coff.nodes.ensureUnusedCapacity(gpa, 1);
- try coff.image_section_table.ensureUnusedCapacity(gpa, 1);
- try coff.symbol_table.ensureUnusedCapacity(gpa, 1);
+ try coff.section_table.ensureUnusedCapacity(gpa, 1);
+ try coff.symbols.ensureUnusedCapacity(gpa, 1);
+ if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1);
const coff_header = coff.headerPtr();
const section_index = coff.targetLoad(&coff_header.number_of_sections);
@@ -1356,21 +3367,32 @@ fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags
gpa,
@sizeOf(std.coff.SectionHeader) * section_table_len,
);
- const ni = try coff.mf.addLastChildNode(gpa, .root, .{
+
+ const ni = try coff.mf.addLastChildNode(gpa, coff.sectionParent(), .{
.alignment = coff.mf.flags.block_size,
.moved = true,
.bubbles_moved = false,
});
+
const si = coff.addSymbolAssumeCapacity();
- coff.image_section_table.appendAssumeCapacity(si);
+ coff.section_table.putAssumeCapacity(name, .{
+ .si = si,
+ .relocation_table_ni = .none,
+ });
coff.nodes.appendAssumeCapacity(.{ .image_section = si });
const section_table = coff.sectionTableSlice();
- const virtual_size = coff.optionalHeaderField(.section_alignment);
- const rva: u32 = switch (section_index) {
- 0 => @intCast(Node.known.header.location(&coff.mf).resolve(&coff.mf)[1]),
- else => coff.image_section_table.items[section_index - 1].get(coff).rva +
- coff.targetLoad(§ion_table[section_index - 1].virtual_size),
- };
+
+ const virtual_size, const rva = if (coff.isImage()) block: {
+ const virtual_size = coff.optionalHeaderField(.section_alignment);
+ const rva: u32 = switch (section_index) {
+ 0 => @intCast(Node.known.header.location(&coff.mf).resolve(&coff.mf)[1]),
+ else => coff.section_table.values()[section_index - 1].si.get(coff).rva +
+ coff.targetLoad(§ion_table[section_index - 1].virtual_size),
+ };
+
+ break :block .{ virtual_size, rva };
+ } else .{ 0, 0 };
+
{
const sym = si.get(coff);
sym.ni = ni;
@@ -1390,16 +3412,24 @@ fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags
.number_of_linenumbers = 0,
.flags = flags,
};
- @memcpy(section.name[0..name.len], name);
- @memset(section.name[name.len..], 0);
if (coff.targetEndian() != native_endian)
std.mem.byteSwapAllFields(std.coff.SectionHeader, section);
- switch (coff.optionalHeaderPtr()) {
- inline else => |optional_header| coff.targetStore(
- &optional_header.size_of_image,
- @intCast(rva + virtual_size),
- ),
+
+ const name_slice = name.toSlice(coff);
+ if (coff.isImage()) {
+ @memcpy(section.name[0..name_slice.len], name_slice);
+ @memset(section.name[name_slice.len..], 0);
+ switch (coff.optionalHeaderPtr()) {
+ inline else => |optional_header| coff.targetStore(
+ &optional_header.size_of_image,
+ @intCast(rva + virtual_size),
+ ),
+ }
+ } else {
+ (try coff.getOrPutSymbolName(name_slice, name)).store(coff, §ion.name);
+ try coff.pendingSymbolTableEntry(si);
}
+
return si;
}
@@ -1412,7 +3442,40 @@ const ObjectSectionAttributes = packed struct {
nocache: bool = false,
discard: bool = false,
remove: bool = false,
+ initialized: bool = false,
+ uninitialized: bool = false,
+
+ pub fn fromFlags(flags: std.coff.SectionHeader.Flags) ObjectSectionAttributes {
+ return .{
+ .read = flags.MEM_READ,
+ .write = flags.MEM_WRITE,
+ .execute = flags.MEM_EXECUTE,
+ .shared = flags.MEM_SHARED,
+ .nopage = flags.MEM_NOT_PAGED,
+ .nocache = flags.MEM_NOT_CACHED,
+ .discard = flags.MEM_DISCARDABLE,
+ .remove = flags.LNK_REMOVE,
+ .initialized = flags.CNT_INITIALIZED_DATA,
+ .uninitialized = flags.CNT_UNINITIALIZED_DATA,
+ };
+ }
+
+ pub fn asFlags(attr: ObjectSectionAttributes) std.coff.SectionHeader.Flags {
+ return .{
+ .MEM_READ = attr.read,
+ .MEM_WRITE = attr.write,
+ .MEM_EXECUTE = attr.execute,
+ .MEM_SHARED = attr.shared,
+ .MEM_NOT_PAGED = attr.nopage,
+ .MEM_NOT_CACHED = attr.nocache,
+ .MEM_DISCARDABLE = attr.discard,
+ .LNK_REMOVE = attr.remove,
+ .CNT_INITIALIZED_DATA = attr.uninitialized,
+ .CNT_UNINITIALIZED_DATA = attr.uninitialized,
+ };
+ }
};
+
fn pseudoSectionMapIndex(
coff: *Coff,
name: String,
@@ -1422,15 +3485,25 @@ fn pseudoSectionMapIndex(
const gpa = coff.base.comp.gpa;
const pseudo_section_gop = try coff.pseudo_section_table.getOrPut(gpa, name);
const psmi: Node.PseudoSectionMapIndex = @enumFromInt(pseudo_section_gop.index);
- if (!pseudo_section_gop.found_existing) {
- const parent: Symbol.Index = if (attributes.execute)
- .text
- else if (attributes.write)
- .data
- else
- .rdata;
+ const parent_sn = if (!pseudo_section_gop.found_existing) sn: {
+ const effective_name = coff.section_merges.get(name) orelse name;
+ const parent = if (coff.section_table.get(effective_name)) |existing_sec|
+ existing_sec.si
+ else if (coff.isImage()) parent: {
+ const parent: Symbol.Index = if (attributes.uninitialized)
+ .bss
+ else if (attributes.execute)
+ .text
+ else if (attributes.write)
+ .data
+ else
+ .rdata;
+
+ break :parent parent;
+ } else try coff.addSection(effective_name, attributes.asFlags());
+
try coff.nodes.ensureUnusedCapacity(gpa, 1);
- try coff.symbol_table.ensureUnusedCapacity(gpa, 1);
+ try coff.symbols.ensureUnusedCapacity(gpa, 1);
const ni = try coff.mf.addLastChildNode(gpa, parent.node(coff), .{ .alignment = alignment });
const si = coff.addSymbolAssumeCapacity();
pseudo_section_gop.value_ptr.* = si;
@@ -1441,9 +3514,30 @@ fn pseudoSectionMapIndex(
assert(sym.loc_relocs == .none);
sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
coff.nodes.appendAssumeCapacity(.{ .pseudo_section = psmi });
- }
+ break :sn sym.section_number;
+ } else pseudo_section_gop.value_ptr.get(coff).section_number;
+
+ try coff.verifyParentSectionAttributes(
+ parent_sn,
+ name,
+ .pseudo,
+ .fromFlags(parent_sn.header(coff).flags),
+ attributes,
+ );
+
return psmi;
}
+
+fn objectSectionParentName(coff: *Coff, name: []const u8) []const u8 {
+ // In images we want to sort object sections into the final root section name.
+ // Otherwise, we want to keep the full name so that this sort can occur correctly when
+ // the object is finally linked into an image.
+ return if (coff.isImage())
+ name[0 .. std.mem.indexOfScalar(u8, name, '$') orelse name.len]
+ else
+ name;
+}
+
fn objectSectionMapIndex(
coff: *Coff,
name: String,
@@ -1451,16 +3545,23 @@ fn objectSectionMapIndex(
attributes: ObjectSectionAttributes,
) !Node.ObjectSectionMapIndex {
const gpa = coff.base.comp.gpa;
+ const name_slice = name.toSlice(coff);
+ // TODO: Should this be a section merge instead?
+ const effective_attributes = if (coff.isImage() and std.mem.startsWith(u8, name_slice, ".tls")) attr: {
+ // In images, the .tls section is a read-only template
+ var attr = attributes;
+ attr.write = false;
+ break :attr attr;
+ } else attributes;
+
const object_section_gop = try coff.object_section_table.getOrPut(gpa, name);
const osmi: Node.ObjectSectionMapIndex = @enumFromInt(object_section_gop.index);
- if (!object_section_gop.found_existing) {
- try coff.ensureUnusedStringCapacity(name.toSlice(coff).len);
- const name_slice = name.toSlice(coff);
- const parent = (try coff.pseudoSectionMapIndex(coff.getOrPutStringAssumeCapacity(
- name_slice[0 .. std.mem.indexOfScalar(u8, name_slice, '$') orelse name_slice.len],
- ), alignment, attributes)).symbol(coff);
+ const sym = if (!object_section_gop.found_existing) sym: {
+ try coff.ensureUnusedStringCapacity(name_slice.len);
+ const parent_name = coff.getOrPutStringAssumeCapacity(coff.objectSectionParentName(name_slice));
+ const parent = (try coff.pseudoSectionMapIndex(parent_name, alignment, effective_attributes)).symbol(coff);
try coff.nodes.ensureUnusedCapacity(gpa, 1);
- try coff.symbol_table.ensureUnusedCapacity(gpa, 1);
+ try coff.symbols.ensureUnusedCapacity(gpa, 1);
const parent_ni = parent.node(coff);
var prev_ni: MappedFile.Node.Index = .none;
var next_it = parent_ni.children(&coff.mf);
@@ -1492,30 +3593,219 @@ fn objectSectionMapIndex(
assert(sym.loc_relocs == .none);
sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
coff.nodes.appendAssumeCapacity(.{ .object_section = osmi });
+ break :sym sym;
+ } else object_section_gop.value_ptr.get(coff);
+
+ const parent_ni = sym.ni.parent(&coff.mf);
+ const parent_alignment = parent_ni.alignment(&coff.mf);
+ if (alignment.compare(.gt, parent_alignment)) {
+ log.debug("realignParent({s}, {d}) {d}->{d}", .{ name.toSlice(coff), parent_ni, parent_alignment, alignment });
+ try parent_ni.realign(&coff.mf, gpa, alignment, .{ .set_alignment = true });
+ }
+
+ const old_alignment = sym.ni.alignment(&coff.mf);
+ if (alignment.compare(.gt, old_alignment)) {
+ log.debug("realignObject({s}) {d}->{d}", .{ name.toSlice(coff), old_alignment, alignment });
+ try sym.ni.realign(&coff.mf, gpa, alignment, .{ .set_alignment = true });
}
+
+ try coff.verifyParentSectionAttributes(
+ sym.section_number,
+ name,
+ .object,
+ .fromFlags(sym.section_number.header(coff).flags),
+ effective_attributes,
+ );
+
return osmi;
}
+fn verifyParentSectionAttributes(
+ coff: *Coff,
+ parent: Symbol.SectionNumber,
+ child_name: String,
+ child_kind: enum { pseudo, object },
+ parent_attrs: ObjectSectionAttributes,
+ child_attrs: ObjectSectionAttributes,
+) !void {
+ if (parent_attrs == child_attrs) return;
+
+ const was_merged = switch (child_kind) {
+ .pseudo => coff.section_merges.contains(child_name),
+ .object => if (coff.getString(
+ coff.objectSectionParentName(child_name.toSlice(coff)),
+ ).unwrap()) |pseudo_name|
+ coff.section_merges.contains(pseudo_name)
+ else
+ false,
+ };
+
+ // The section was intentionally merged by the user or builtin rule
+ if (was_merged) return;
+
+ const BackingT = @typeInfo(ObjectSectionAttributes).@"struct".backing_integer.?;
+ const num_notes = @popCount(@as(BackingT, @bitCast(parent_attrs)) ^ @as(BackingT, @bitCast(child_attrs)));
+ var err = try coff.base.comp.link_diags.addErrorWithNotes(num_notes);
+ try err.addMsg("{t} section '{s}' was placed in parent section '{s}' with mismatched flags", .{
+ child_kind,
+ child_name.toSlice(coff),
+ parent.name(coff).toSlice(coff),
+ });
+
+ inline for (comptime std.meta.fieldNames(ObjectSectionAttributes)) |field| {
+ if (@field(child_attrs, field) != @field(parent_attrs, field)) {
+ err.addNote("flags.{s} was {d} in {s}, but {d} in {s}", .{
+ field,
+ @intFromBool(@field(child_attrs, field)),
+ child_name.toSlice(coff),
+ @intFromBool(@field(parent_attrs, field)),
+ parent.name(coff).toSlice(coff),
+ });
+ }
+ }
+
+ return error.AlreadyReported;
+}
+
+const RelocAddend = union(enum) {
+ known: i64,
+ /// Relocs tables in input objects don't include the addend.
+ /// The value needs to be recovered from the reloc location.
+ pending: void,
+};
+
+// TODO: There should be an API where the caller can indicate how many contiguous relocs they need
+// and it should attempt to allocate these from from the free list if available. We can cache
+// the run length of each segment on Reloc when `free` is set.
pub fn addReloc(
coff: *Coff,
loc_si: Symbol.Index,
offset: u64,
target_si: Symbol.Index,
- addend: i64,
+ addend: RelocAddend,
+ @"type": Reloc.Type,
+) link.Error!void {
+ const diags = &coff.base.comp.link_diags;
+ try coff.ensureUnusedRelocCapacity(loc_si, 1);
+ coff.addRelocAssumeCapacity(loc_si, offset, target_si, addend, @"type") catch |err| switch (err) {
+ error.MappedFileIo => return diags.fail(
+ "failed to write output file: {t}",
+ .{coff.mf.io_err.?},
+ ),
+ else => |e| return e,
+ };
+}
+
+fn ensureUnusedRelocCapacity(coff: *Coff, loc_si: Symbol.Index, len: usize) !void {
+ const gpa = coff.base.comp.gpa;
+ try coff.relocs.ensureUnusedCapacity(gpa, len);
+ if (isImage(coff)) return;
+ switch (loc_si.get(coff).section_number) {
+ .UNDEFINED, .ABSOLUTE, .DEBUG => {},
+ else => |loc_sn| {
+ const section = loc_sn.section(coff);
+ if (section.relocation_table_ni == .none)
+ try coff.nodes.ensureUnusedCapacity(gpa, 1);
+ },
+ }
+}
+
+fn addRelocAssumeCapacity(
+ coff: *Coff,
+ loc_si: Symbol.Index,
+ offset: u64,
+ target_si: Symbol.Index,
+ addend: RelocAddend,
@"type": Reloc.Type,
) !void {
const gpa = coff.base.comp.gpa;
const target = target_si.get(coff);
+
const ri: Reloc.Index = @enumFromInt(coff.relocs.items.len);
- (try coff.relocs.addOne(gpa)).* = .{
+ log.debug("addReloc({d}@{d}+0x{x} -> {d}@{d}+0x{x}{s}) = {d}", .{
+ loc_si,
+ loc_si.get(coff).section_number,
+ offset,
+ target_si,
+ target_si.get(coff).section_number,
+ if (addend == .pending) 0 else addend.known,
+ if (addend == .pending) "p" else "k",
+ ri,
+ });
+
+ const sri: Section.RelocationIndex = if (isImage(coff))
+ .none
+ else switch (loc_si.get(coff).section_number) {
+ .UNDEFINED,
+ .ABSOLUTE,
+ .DEBUG,
+ => .none,
+ else => |loc_sn| sri: {
+ // The target may not have a node yet, or it could be an extern that will never
+ // have a node. In that case, flushGlobal will create the symbol table entry.
+ const existing_sti = target_si.sti(coff);
+ const sti: SymbolTable.Index = if (existing_sti != .none)
+ existing_sti
+ else if (target.ni != .none) sti: {
+ try coff.pendingSymbolTableEntry(target_si);
+ break :sti .none;
+ } else .none;
+
+ const sri: Section.RelocationIndex = blk: {
+ const section = loc_sn.section(coff);
+ const header = loc_sn.header(coff);
+ const old_num_relocations = coff.targetLoad(&header.number_of_relocations);
+ const new_num_relocations = old_num_relocations + 1;
+ const new_size = @as(u32, new_num_relocations) * std.coff.Relocation.sizeOf();
+
+ coff.targetStore(&header.number_of_relocations, new_num_relocations);
+ if (coff.symbolTableSectionAuxEntryPtr(loc_sn.symbol(coff).sti(coff))) |aux_ptr|
+ coff.targetStore(&aux_ptr.number_of_relocations, new_num_relocations);
+
+ if (section.relocation_table_ni == .none) {
+ section.relocation_table_ni = try coff.mf.addLastChildNode(
+ gpa,
+ coff.sectionParent(),
+ .{
+ .size = new_size,
+ .alignment = .@"2",
+ .moved = true,
+ .resized = true,
+ },
+ );
+ coff.nodes.appendAssumeCapacity(.{ .relocation_table = loc_sn });
+ } else {
+ try section.relocation_table_ni.resize(&coff.mf, gpa, new_size);
+ }
+
+ // TODO: These need to allocate from a free list, once deleting relocs from the table is supported
+ break :blk .wrap(old_num_relocations);
+ };
+
+ const entry = sri.entry(coff, loc_sn).?;
+ if (sti.unwrap()) |index| coff.targetStore(&entry.symbol_table_index, index);
+
+ // applyLocationRelocs updates `virtual_address`
+ // flushSymbolTableIndex updates `symbol_table_index`
+ coff.targetStore(&entry.type, @bitCast(@"type"));
+
+ break :sri sri;
+ },
+ };
+
+ coff.relocs.addOneAssumeCapacity().* = .{
.type = @"type",
.prev = .none,
.next = target.target_relocs,
.loc = loc_si,
.target = target_si,
- .unused = 0,
+ .sri = sri,
.offset = offset,
- .addend = addend,
+ .addend = if (addend == .pending) 0 else addend.known,
+ .flags = .{
+ .recover_addend = addend == .pending,
+ .free = false,
+ },
};
switch (target.target_relocs) {
.none => {},
@@ -1524,15 +3814,1641 @@ pub fn addReloc(
target.target_relocs = ri;
}
+fn failLoadInput(
+ coff: *Coff,
+ err: LoadInputError,
+ fr: *Io.File.Reader,
+ path: std.Build.Cache.Path,
+) link.Error {
+ const diags = &coff.base.comp.link_diags;
+ switch (err) {
+ else => |e| return e,
+ error.MappedFileIo => return diags.fail(
+ "failed to write output file: {t}",
+ .{coff.mf.io_err.?},
+ ),
+ error.EndOfStream => return diags.failParse(
+ path,
+ "unexpected eof",
+ .{},
+ ),
+ error.AccessDenied,
+ error.Unexpected,
+ error.Unseekable,
+ => |e| return diags.fail(
+ "failed to read \"{f}\": {t}",
+ .{ path.fmtEscapeString(), e },
+ ),
+ error.PermissionDenied,
+ error.SystemResources,
+ error.Streaming,
+ => |e| return diags.fail(
+ "failed to stat \"{f}\": {t}",
+ .{ path.fmtEscapeString(), e },
+ ),
+ error.ReadFailed => switch (fr.err.?) {
+ error.Canceled => |e| return e,
+ else => |e| return diags.fail(
+ "failed to read \"{f}\": {t}",
+ .{ path.fmtEscapeString(), e },
+ ),
+ },
+ }
+}
+
+pub fn loadInput(coff: *Coff, input: link.Input) link.Error!void {
+ const comp = coff.base.comp;
+ const io = comp.io;
+
+ const path = input.path() orelse unreachable;
+ const gop = try coff.inputs.getOrPut(comp.gpa, path);
+ if (gop.found_existing) return;
+ errdefer _ = coff.inputs.swapRemove(path);
+
+ var buf: [4096]u8 = undefined;
+ switch (input) {
+ .object => |object| {
+ var fr = object.file.reader(io, &buf);
+ coff.loadObject(object.path, null, &fr, .{
+ .offset = fr.logicalPos(),
+ .size = fr.getSize() catch |err|
+ return coff.failLoadInput(err, &fr, object.path),
+ }) catch |err| return coff.failLoadInput(err, &fr, object.path);
+ },
+ .archive => |archive| {
+ var fr = archive.file.reader(io, &buf);
+ coff.loadArchive(archive.path, &fr) catch |err|
+ return coff.failLoadInput(err, &fr, archive.path);
+ },
+ .res => |res| {
+ var fr = res.file.reader(io, &buf);
+ coff.loadRes(res.path, &fr) catch |err|
+ return coff.failLoadInput(err, &fr, res.path);
+ },
+ .dso => |dso| {
+ var fr = dso.file.reader(io, &buf);
+ coff.loadDll(dso.path, &fr) catch |err|
+ return coff.failLoadInput(err, &fr, dso.path);
+ },
+ .dso_exact => unreachable,
+ }
+}
+
+fn fmtMemberNameString(memberName: ?[]const u8) std.fmt.Alt(?[]const u8, memberNameStringEscape) {
+ return .{ .data = memberName };
+}
+
+fn memberNameStringEscape(memberName: ?[]const u8, w: *std.Io.Writer) std.Io.Writer.Error!void {
+ try w.print("({f})", .{std.zig.fmtString(memberName orelse return)});
+}
+
+fn inputSectionHeaderNameSlice(
+ coff: *Coff,
+ header: *const std.coff.SectionHeader,
+ string_table: []const u8,
+ path: std.Build.Cache.Path,
+ section_i: usize,
+) ![]const u8 {
+ const diags = &coff.base.comp.link_diags;
+ return if (header.name[0] == '/') name: {
+ const offset_str = std.mem.sliceTo(header.name[1..], 0);
+ const name_offset = std.fmt.parseUnsigned(u24, offset_str, 10) catch
+ return diags.failParse(path, "ill-formed section name in section {d}: '{s}'", .{
+ section_i,
+ header.name[0 .. offset_str.len + 1],
+ });
+
+ if (name_offset > string_table.len)
+ return diags.failParse(path, "out-of-bounds section name offset in section {d}: {d}", .{ section_i, name_offset });
+
+ break :name std.mem.sliceTo(string_table[name_offset..], 0);
+ } else std.mem.sliceTo(&header.name, 0);
+}
+
+fn loadObject(
+ coff: *Coff,
+ path: std.Build.Cache.Path,
+ member_name: ?[]const u8,
+ fr: *Io.File.Reader,
+ fl: MappedFile.Node.FileLocation,
+) LoadInputError!void {
+ const comp = coff.base.comp;
+ const gpa = comp.gpa;
+ const diags = &comp.link_diags;
+ const r = &fr.interface;
+ const target = &comp.root_mod.resolved_target.result;
+ const target_endian = coff.targetEndian();
+ const is_archive = coff.isArchive();
+ assert(!coff.isObj());
+ // We want to evaluate new merges as we see them in .drectve sections to avoid redundant work
+ assert(coff.section_merge_pending_index == coff.section_merges.count());
+
+ log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtMemberNameString(member_name) });
+
+ const header = try r.peekStruct(std.coff.Header, .little);
+ if (header.machine != target.toCoffMachine())
+ return diags.failParse(path, "machine mismatch: expected {t}, found {t}", .{
+ target.toCoffMachine(),
+ header.machine,
+ });
+ if (header.number_of_sections == 0) return;
+ if (@sizeOf(std.coff.Header) + @as(usize, header.number_of_sections) * @sizeOf(std.coff.SectionHeader) > fl.size)
+ return diags.failParse(path, "invalid section table", .{});
+ const unexpected_header_flags: []const std.meta.FieldEnum(std.coff.Header.Flags) = &.{
+ .RELOCS_STRIPPED,
+ .EXECUTABLE_IMAGE,
+ .AGGRESSIVE_WS_TRIM,
+ .RESERVED,
+ .BYTES_REVERSED_LO,
+ .DLL,
+ .BYTES_REVERSED_HI,
+ };
+ inline for (unexpected_header_flags) |flag|
+ if (@field(header.flags, @tagName(flag)))
+ return diags.failParse(path, "unexpected flag set: {t}", .{flag});
+
+ if (header.size_of_optional_header != 0)
+ return diags.failParse(path, "unexpected optional header", .{});
+
+ const symbol_table_len = header.number_of_symbols * std.coff.Symbol.sizeOf();
+ const symbol_table_end = header.pointer_to_symbol_table + symbol_table_len;
+ // String table length (which includes the length field) immediately trails the symbol table
+ if (symbol_table_end + @sizeOf(u32) > fl.size)
+ return diags.failParse(path, "bad symbol table location", .{});
+
+ try fr.seekTo(fl.offset + symbol_table_end);
+ const string_table_len = try r.peekInt(u32, target_endian);
+ if (string_table_len < @sizeOf(u32) or
+ symbol_table_end + string_table_len > fl.size)
+ return diags.failParse(path, "bad string table length: 0x{x}", .{string_table_len});
+
+ const ioi: InputObject.Index = @enumFromInt(coff.input_objects.items.len);
+ try coff.input_objects.ensureUnusedCapacity(gpa, 1);
+ const input = coff.input_objects.addOneAssumeCapacity();
+ input.* = .{
+ .path = path,
+ .member_name = if (member_name) |m| try gpa.dupe(u8, m) else null,
+ .source_name = .none,
+ };
+
+ const string_table = string_table: {
+ const string_table = try gpa.alloc(u8, string_table_len);
+ errdefer gpa.free(string_table);
+ try r.readSliceAll(string_table);
+ break :string_table string_table;
+ };
+ defer gpa.free(string_table);
+
+ try coff.ensureManyUnusedStringCapacity(
+ header.number_of_sections + header.number_of_symbols,
+ header.number_of_sections * 9 +
+ header.number_of_symbols * 9 +
+ string_table_len - @sizeOf(u32),
+ );
+
+ const PendingSymbolIndex = enum(u32) {
+ none,
+ _,
+
+ pub fn wrap(i: ?u32) @This() {
+ return @enumFromInt((i orelse return .none) + 1);
+ }
+
+ pub fn unwrap(i: @This()) ?u32 {
+ return switch (i) {
+ .none => null,
+ _ => @intFromEnum(i) - 1,
+ };
+ }
+ };
+
+ const PendingInputSection = struct {
+ header: std.coff.SectionHeader,
+ name: String,
+ si: Symbol.Index,
+ parent_si: Symbol.Index,
+ psi: PendingSymbolIndex,
+ num_symbols: u32,
+ comdat: std.coff.ComdatSelection,
+ comdat_psi: PendingSymbolIndex,
+ comdat_crc: u32,
+ comdat_association: Symbol.SectionNumber,
+ comdat_result: union(enum) {
+ pending,
+ // Root of the association chain
+ pending_association: Symbol.SectionNumber,
+ include,
+ skip,
+ },
+ };
+
+ const sections: []PendingInputSection = if (coff.isImage()) sections: {
+ const sections = try gpa.alloc(PendingInputSection, header.number_of_sections);
+ errdefer gpa.free(sections);
+
+ try fr.seekTo(fl.offset + @sizeOf(std.coff.Header));
+ for (sections, 0..) |*section, section_i| {
+ section.* = .{
+ .header = try r.takeStruct(std.coff.SectionHeader, target_endian),
+ .name = undefined,
+ .si = .null,
+ .parent_si = .null,
+ .psi = .none,
+ .num_symbols = 0,
+ .comdat = .NONE,
+ .comdat_psi = .none,
+ .comdat_crc = 0,
+ .comdat_association = .UNDEFINED,
+ .comdat_result = .pending,
+ };
+
+ const section_name_slice = if (section.header.name[0] == '/') name: {
+ const offset_str = std.mem.sliceTo(section.header.name[1..], 0);
+ const name_offset = std.fmt.parseUnsigned(u24, offset_str, 10) catch
+ return diags.failParse(path, "ill-formed section name offset in section {d}: '{s}'", .{
+ section_i,
+ section.header.name[0 .. offset_str.len + 1],
+ });
+
+ if (name_offset > string_table.len)
+ return diags.failParse(
+ path,
+ "out-of-bounds section name offset in section {d}: {d}",
+ .{ section_i, name_offset },
+ );
+
+ break :name std.mem.sliceTo(string_table[name_offset..], 0);
+ } else std.mem.sliceTo(§ion.header.name, 0);
+ section.name = coff.getOrPutStringAssumeCapacity(section_name_slice);
+
+ if (section.header.pointer_to_linenumbers +
+ @as(u32, section.header.number_of_linenumbers) * std.coff.LineNumber.sizeOf() > fl.size)
+ return diags.failParse(path, "bad line numbers location in section {d} `{s}`", .{
+ section_i,
+ section_name_slice,
+ });
+
+ if (section.header.pointer_to_relocations +
+ @as(u32, section.header.number_of_relocations) * std.coff.Relocation.sizeOf() > fl.size)
+ return diags.failParse(path, "bad relocations location in section {d} `{s}`", .{
+ section_i,
+ section_name_slice,
+ });
+
+ if (section.header.pointer_to_raw_data + section.header.size_of_raw_data > fl.size)
+ return diags.failParse(path, "bad raw data location in section {d} `{s}`", .{
+ section_i,
+ section_name_slice,
+ });
+ }
+
+ break :sections sections;
+ } else &.{};
+ defer gpa.free(sections);
+
+ const mi = if (is_archive) mi: {
+ try coff.nodes.ensureUnusedCapacity(gpa, 2);
+ try coff.members.ensureUnusedCapacity(gpa, 1);
+ const path_str = try path.toString(gpa);
+ defer gpa.free(path_str);
+
+ const mi = try coff.addMemberAssumeCapacity(.coff, fl.size);
+ const member = mi.get(coff);
+ try member.initHeader(coff, path_str, header.time_date_stamp);
+
+ {
+ // TODO: This should be deferred to an idle task (but resize it here!)
+ var nw: MappedFile.Node.Writer = undefined;
+ member.content_ni.writer(&coff.mf, gpa, &nw);
+ defer nw.deinit();
+
+ try fr.seekTo(fl.offset);
+ const written = nw.interface.sendFileAll(fr, .limited64(fl.size)) catch |err| switch (err) {
+ error.WriteFailed => return nw.err.?,
+ else => |e| return e,
+ };
+
+ if (written != fl.size) return error.EndOfStream;
+ }
+
+ break :mi mi;
+ } else undefined;
+
+ try fr.seekTo(fl.offset + header.pointer_to_symbol_table);
+ const symbol_size = std.coff.Symbol.sizeOf();
+
+ const PendingSymbol = struct {
+ name: String,
+ value: union(enum) {
+ // Size of the section
+ section: u32,
+ // If section is absolute, the symbol value.
+ // Otherwise, offset within the section.
+ static: u32,
+ // If section is undefined, the symbol size.
+ // If section is absolute, the symbol value.
+ // Otherwise offset within the section.
+ external: u32,
+ // The index of the target symbol of this weak external
+ weak_external: u32,
+ // Trails .weak_external
+ weak_external_aux: WeakExternalStrat,
+ },
+ section_number: Symbol.SectionNumber,
+ si: Symbol.Index,
+ // If a weak external targets this symbol, the index of the weak external
+ weak_external_psi: PendingSymbolIndex,
+ };
+
+ var num_global_symbols: u32 = 0;
+ var pending_symbols: std.array_hash_map.Auto(u32, PendingSymbol) = .empty;
+ defer pending_symbols.deinit(gpa);
+ if (!is_archive)
+ try pending_symbols.ensureUnusedCapacity(gpa, header.number_of_symbols);
+
+ var section_merges: std.ArrayList(struct {
+ from: String,
+ to: String,
+ }) = .empty;
+ defer section_merges.deinit(gpa);
+
+ // Discover symbol names and COMDAT symbol mappings
+ var symbol_i: u32 = 0;
+ var num_included_symbols: u32 = 0;
+ while (symbol_i < header.number_of_symbols) {
+ var symbol: std.coff.Symbol = undefined;
+ @memcpy(std.mem.asBytes(&symbol)[0..symbol_size], try r.take(symbol_size));
+ if (target_endian != native_endian)
+ std.mem.byteSwapAllFields(std.coff.Symbol, &symbol);
+
+ const aux_symbols = if (symbol.number_of_aux_symbols > 0)
+ try r.take(symbol_size * symbol.number_of_aux_symbols)
+ else
+ &.{};
+ defer symbol_i += symbol.number_of_aux_symbols + 1;
+
+ const name = std.mem.sliceTo(if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: {
+ const index = std.mem.readInt(u32, symbol.name[4..], target_endian);
+ if (index >= string_table.len)
+ return diags.failParse(path, "bad string offset for symbol 0x{x}", .{symbol_i});
+ break :name string_table[index..];
+ } else &symbol.name, 0);
+
+ if (is_archive) {
+ if (switch (symbol.storage_class) {
+ .WEAK_EXTERNAL => true,
+ .EXTERNAL => symbol.section_number != .UNDEFINED,
+ else => false,
+ }) try coff.ensureMemberSymbol(mi, coff.getOrPutStringAssumeCapacity(name));
+
+ continue;
+ }
+
+ switch (symbol.section_number) {
+ .UNDEFINED, .DEBUG, .ABSOLUTE => {},
+ else => |sn| if (@intFromEnum(sn) > sections.len)
+ return diags.failParse(path, "out-of-bounds section number {d} in symbol 0x{x}", .{ sn, symbol_i }),
+ }
+
+ const psi: PendingSymbolIndex = .wrap(@intCast(pending_symbols.count()));
+ const section_number: Symbol.SectionNumber = @enumFromInt(@intFromEnum(symbol.section_number));
+
+ const values: []const @FieldType(PendingSymbol, "value") = pending_symbols: switch (symbol.storage_class) {
+ .STATIC, .LABEL => |storage_class| switch (section_number) {
+ // TODO: Do we need to do anything with @feat.00?
+ // https://llvm.org/doxygen/namespacellvm_1_1COFF.html#aeffa16735e18df727a173beaf748c392
+ .UNDEFINED,
+ .DEBUG,
+ => &.{},
+ .ABSOLUTE => &.{.{ .static = symbol.value }},
+ else => |sn| {
+ const section = §ions[sn.toIndex()];
+
+ // Section symbol
+ const is_section = storage_class == .STATIC and
+ symbol.value == 0 and
+ symbol.type == std.coff.SymType{
+ .complex_type = .NULL,
+ .base_type = .NULL,
+ } and
+ symbol.number_of_aux_symbols > 0;
+
+ if (is_section) {
+ if (symbol.number_of_aux_symbols > 1)
+ return diags.failParse(path, "invalid number of aux symbols for section symbol 0x{x}: {d}", .{
+ symbol_i,
+ symbol.number_of_aux_symbols,
+ });
+
+ var section_def: std.coff.SectionDefinition = undefined;
+ @memcpy(std.mem.asBytes(§ion_def)[0..symbol_size], aux_symbols[0..symbol_size]);
+ if (target_endian != native_endian)
+ std.mem.byteSwapAllFields(std.coff.SectionDefinition, §ion_def);
+
+ if (section_def.number_of_relocations != section.header.number_of_relocations)
+ return diags.failParse(
+ path,
+ "section aux symbol 0x{x} for '{s}' relocation count did not match section header: {d} vs {d}",
+ .{ symbol_i + 1, name, section_def.number_of_relocations, section.header.number_of_relocations },
+ );
+
+ if (section_def.number_of_linenumbers != section.header.number_of_linenumbers)
+ return diags.failParse(
+ path,
+ "section aux symbol 0x{x} for '{s}' line number count did not match section header: {d} vs {d}",
+ .{ symbol_i + 1, name, section_def.number_of_linenumbers, section.header.number_of_linenumbers },
+ );
+
+ if (section.header.flags.LNK_COMDAT) {
+ if (section_def.selection == .ASSOCIATIVE) {
+ if (section_def.number == 0 or section_def.number > sections.len)
+ return diags.failParse(
+ path,
+ "section aux symbol 0x{x} for '{s}' contained an invalid associated section number: 0x{x}",
+ .{ symbol_i + 1, name, section_def.number },
+ );
+
+ section.comdat_association = @enumFromInt(section_def.number);
+ }
+
+ section.comdat = section_def.selection;
+ section.comdat_crc = section_def.checksum;
+ }
+
+ section.psi = psi;
+ }
+
+ break :pending_symbols &.{if (is_section)
+ .{ .section = section.header.size_of_raw_data }
+ else
+ .{ .static = symbol.value }};
+ },
+ },
+ .WEAK_EXTERNAL => switch (symbol.section_number) {
+ .UNDEFINED => {
+ if (symbol.value != 0)
+ return diags.failParse(
+ path,
+ "invalid value {d} for weak external symbol 0x{x}",
+ .{ symbol.value, symbol_i },
+ );
+
+ var weak_external: std.coff.WeakExternalDefinition = undefined;
+ @memcpy(std.mem.asBytes(&weak_external)[0..symbol_size], aux_symbols[0..symbol_size]);
+ if (target_endian != native_endian)
+ std.mem.byteSwapAllFields(std.coff.WeakExternalDefinition, &weak_external);
+
+ if (weak_external.tag_index >= header.number_of_symbols)
+ return diags.failParse(
+ path,
+ "invalid tag_index 0x{x} for weak external symbol 0x{x}",
+ .{ weak_external.tag_index, symbol_i },
+ );
+
+ break :pending_symbols switch (weak_external.flag) {
+ else => |flag| &.{
+ .{ .weak_external = weak_external.tag_index },
+ .{ .weak_external_aux = WeakExternalStrat.fromFlag(flag) },
+ },
+ _ => return diags.failParse(
+ path,
+ "encountered unknown weak external characteristic 0x{x} for symbol 0x{x}",
+ .{ weak_external.flag, symbol_i },
+ ),
+ };
+ },
+ else => |sn| return diags.failParse(
+ path,
+ "invalid section number {d} for weak external symbol 0x{x}",
+ .{ sn, symbol_i },
+ ),
+ },
+ .EXTERNAL => switch (section_number) {
+ .UNDEFINED,
+ .ABSOLUTE,
+ => &.{.{ .external = symbol.value }},
+ .DEBUG => return diags.failParse(
+ path,
+ "unexpected external symbol 0x{x} in DEBUG section: '{s}'",
+ .{ symbol_i, name },
+ ),
+ else => &.{.{ .external = symbol.value }},
+ },
+ .FILE => {
+ if (!std.mem.eql(u8, name, ".file"))
+ return diags.failParse(
+ path,
+ "unexpected symbol name '{s}' for file symbol 0x{x}",
+ .{ name, symbol_i },
+ );
+
+ var file: std.coff.FileDefinition = undefined;
+ @memcpy(std.mem.asBytes(&file)[0..symbol_size], aux_symbols[0..symbol_size]);
+
+ input.source_name = (try coff.getOrPutString(file.getFileName())).toOptional();
+ break :pending_symbols &.{};
+ },
+ else => |storage_class| return diags.failParse(
+ path,
+ "TODO handle storage class {t} for symbol 0x{x}",
+ .{ storage_class, symbol_i },
+ ),
+ };
+
+ for (values, 0..) |value, i| {
+ if (section_number == .ABSOLUTE)
+ num_included_symbols += 1;
+
+ switch (value) {
+ .section => {},
+ .static,
+ .external,
+ .weak_external,
+ => {
+ num_global_symbols += 1;
+ if (section_number.hasIndex()) {
+ const section = §ions[section_number.toIndex()];
+ section.num_symbols += 1;
+ if (section.header.flags.LNK_COMDAT and section.comdat_psi == .none)
+ section.comdat_psi = psi;
+ }
+ },
+ .weak_external_aux => {},
+ }
+
+ const symbol_name = coff.getOrPutStringAssumeCapacity(name);
+ pending_symbols.putAssumeCapacity(symbol_i + @as(u32, @intCast(i)), .{
+ .name = symbol_name,
+ .value = value,
+ .section_number = section_number,
+ .si = .null,
+ .weak_external_psi = .none,
+ });
+ }
+ }
+
+ try coff.globals.ensureUnusedCapacity(gpa, num_global_symbols);
+ for (sections) |*section| {
+ if (section.header.flags.LNK_INFO) {
+ if (std.mem.eql(u8, §ion.header.name, ".drectve")) {
+ try fr.seekTo(fl.offset + section.header.pointer_to_raw_data);
+ // TODO: Don't really want an additional buffer here, but want to limit to size_of_raw_data
+ var buf: [128]u8 = undefined;
+ var section_r = r.limited(.limited(section.header.size_of_raw_data), &buf);
+ while (section_r.interface.takeDelimiter(' ') catch |err| switch (err) {
+ error.StreamTooLong => return diags.failParse(path, "unexpectedly long .drectve argument", .{}),
+ else => |e| return e,
+ }) |arg| {
+ // Microsoft tools emit 3 space characters into this section even with /Zl
+ if (arg.len == 0) continue;
+
+ if (std.ascii.startsWithIgnoreCase(arg, "-exclude-symbols:")) {
+ // TODO: When implementing mingw auto-exports (if at all?), track this to not export this symbol
+ } else if (std.ascii.startsWithIgnoreCase(arg, "/include:")) {
+ _ = try coff.globalSymbol(.{ .name = arg["/include:".len..] });
+ } else if (std.ascii.startsWithIgnoreCase(arg, "/alternatename:")) {
+ var split = std.mem.splitScalar(u8, arg["/alternatename:".len..], '=');
+ const orig = split.first();
+ const alt = split.next() orelse
+ return diags.failParse(path, "malformed .drectve argument: '{s}'", .{arg});
+
+ try coff.ensureManyUnusedStringCapacity(2, orig.len + alt.len + 2);
+ const orig_str = coff.getOrPutStringAssumeCapacity(orig);
+ const alt_str = coff.getOrPutStringAssumeCapacity(alt);
+ const gop = try coff.alternate_names.getOrPut(gpa, orig_str);
+ if (!gop.found_existing) {
+ log.debug("alternateName({s}={s})", .{ orig, alt });
+ gop.value_ptr.* = alt_str;
+ } else if (gop.value_ptr.* != alt_str)
+ return diags.failParse(
+ path,
+ "conflicting /alternatename .drectve arguments: first seen as {s}={s}, now seen as {s}={s}",
+ .{ orig, gop.value_ptr.toSlice(coff), orig, alt },
+ );
+ } else if (std.ascii.startsWithIgnoreCase(arg, "/guardsym:")) {
+ // TODO: https://learn.microsoft.com/en-us/windows/win32/secbp/pe-metadata
+ } else if (std.ascii.startsWithIgnoreCase(arg, "/merge:")) merge: {
+ var split = std.mem.splitScalar(u8, arg["/merge:".len..], '=');
+ const from = split.first();
+ const to = split.next() orelse
+ return diags.failParse(path, "malformed .drectve argument: '{s}'", .{arg});
+ if (to.len > header_name_max_len)
+ return diags.failParse(
+ path,
+ "/merge .drectve target exceeds max length of {d}: '{s}'",
+ .{ header_name_max_len, arg },
+ );
+ if (std.mem.eql(u8, from, to)) break :merge;
+
+ try coff.ensureManyUnusedStringCapacity(2, from.len + to.len + 2);
+ const from_str = coff.getOrPutStringAssumeCapacity(from);
+ const to_str = coff.getOrPutStringAssumeCapacity(to);
+
+ {
+ var iter = to_str;
+ while (coff.section_merges.get(iter)) |next_to| {
+ if (next_to == from_str)
+ return diags.failParse(
+ path,
+ "/merge .drectve argument would create a cycle: {s}={s} leads to {s}={s}",
+ .{ from, to, iter.toSlice(coff), to },
+ );
+
+ iter = next_to;
+ }
+ }
+
+ try coff.section_merges.ensureUnusedCapacity(gpa, 1);
+ const gop = coff.section_merges.getOrPutAssumeCapacity(from_str);
+ if (!gop.found_existing) {
+ coff.synth_prog_node.increaseEstimatedTotalItems(1);
+ gop.value_ptr.* = to_str;
+ } else if (gop.value_ptr.* != to_str)
+ return diags.failParse(
+ path,
+ "conflicting /merge .drectve arguments: first seen as {s}={s}, now seen as {s}={s}",
+ .{ from, gop.value_ptr.toSlice(coff), from, to },
+ );
+ } else if (std.ascii.startsWithIgnoreCase(arg, "/disallowlib:")) {
+ const lib_name = arg["/disallowlib:".len..];
+ // TODO: Track these and issue error in prelink if any match
+ _ = lib_name;
+ } else if (std.ascii.startsWithIgnoreCase(arg, "/defaultlib:")) {
+ const lib_path = arg["/defaultlib:".len..];
+ const trim = std.mem.trim(u8, lib_path, "\"");
+ if (lib_path.len == trim.len or lib_path.len - 2 == trim.len) {
+ if (!comp.config.link_libc or comp.libc_installation == null)
+ return diags.failParse(path, "encountered /DEFAULTLIB .drectve argument when libc was not available: {s}", .{arg});
+
+ (try coff.pending_default_libs.addOne(gpa)).* = .{
+ .path = try gpa.dupe(u8, lib_path),
+ .ioi = ioi,
+ };
+ } else return diags.failParse(
+ path,
+ "malformed /DEFAULTLIB .drectve argument: `{s}`",
+ .{arg},
+ );
+ } else return diags.failParse(path, "unsupported argument in .drectve section: `{s}`", .{arg});
+ }
+ }
+
+ section.comdat_result = .skip;
+ continue;
+ }
+
+ if (section.header.flags.LNK_REMOVE or
+ section.header.flags.MEM_DISCARDABLE)
+ {
+ // TODO: Convert .debug$* sections into PDB
+ section.comdat_result = .skip;
+ continue;
+ }
+
+ section.comdat_result = comdat: switch (section.comdat) {
+ .NONE => .include,
+ .ASSOCIATIVE => {
+ // Associative COMDAT sections have no COMDAT symbol.
+ // They are linked if the assocated section is linked.
+ var iter = section;
+ var iter_sn = iter.comdat_association;
+ while (iter.comdat == .ASSOCIATIVE) {
+ iter = §ions[iter_sn.toIndex()];
+ iter_sn = iter.comdat_association;
+ if (iter == section)
+ return diags.failParse(
+ path,
+ "circular COMDAT association loop detected, starting at symbol 0x{x}",
+ .{pending_symbols.keys()[section.psi.unwrap().?]},
+ );
+ }
+
+ assert(iter != section);
+ break :comdat switch (iter.comdat_result) {
+ .pending => .{ .pending_association = iter_sn },
+ else => |iter_result| iter_result,
+ };
+ },
+ else => |comdat| {
+ const psi = section.comdat_psi.unwrap() orelse section.psi.unwrap().?;
+ const symbol = &pending_symbols.values()[psi];
+ const si = existing: switch (symbol.value) {
+ .weak_external => unreachable,
+ .weak_external_aux => unreachable,
+ .static => break :comdat .include,
+ .section => {
+ assert(section.comdat_psi == .none);
+ if (coff.object_section_table.get(section.name)) |si|
+ break :existing si
+ else if (coff.pseudo_section_table.get(section.name)) |si|
+ break :existing si
+ else if (coff.section_table.get(section.name)) |s|
+ break :existing s.si
+ else
+ break :comdat .include;
+ },
+ .external => {
+ const global_gop = try coff.getOrPutGlobalSymbol(.{
+ .name = symbol.name.toSlice(coff),
+ });
+
+ // TODO: What if the same symbol is incorrectly defined twice in this obj?
+ // Would need to mark this global as pending, or notice it later when .ni != none
+ if (!global_gop.found_existing or global_gop.value_ptr.si.get(coff).ni == .none) {
+ symbol.si = global_gop.value_ptr.si;
+ break :comdat .include;
+ }
+
+ break :existing global_gop.value_ptr.si;
+ },
+ };
+
+ const index = pending_symbols.keys()[psi];
+ switch (comdat) {
+ .NODUPLICATES => return coff.failMultipleDefinitions(
+ path,
+ member_name,
+ symbol.name,
+ index,
+ si,
+ .duplicate,
+ ),
+ .ANY => {
+ symbol.si = si;
+ break :comdat .skip;
+ },
+ .SAME_SIZE => {
+ // TODO: Verify that this node isn't resized after creation
+ _, const size = si.get(coff).ni.location(&coff.mf).resolve(&coff.mf);
+ if (size == section.header.size_of_raw_data) {
+ symbol.si = si;
+ break :comdat .skip;
+ }
+
+ return coff.failMultipleDefinitions(
+ path,
+ member_name,
+ symbol.name,
+ index,
+ si,
+ .{ .size = .{ .a = size, .b = section.header.size_of_raw_data } },
+ );
+ },
+ .EXACT_MATCH => {
+ const sym = si.get(coff);
+ const existing_crc = switch (coff.getNode(sym.ni)) {
+ .input_section => |isi| isi.inputSection(coff).crc,
+ else => std.hash.crc.Crc32Jamcrc.hash(sym.ni.sliceConst(&coff.mf)),
+ };
+
+ if (existing_crc == section.comdat_crc) {
+ symbol.si = si;
+ break :comdat .skip;
+ }
+
+ return coff.failMultipleDefinitions(
+ path,
+ member_name,
+ symbol.name,
+ index,
+ si,
+ .{ .crc = .{ .a = existing_crc, .b = section.comdat_crc } },
+ );
+ },
+ .LARGEST => {
+ // TODO: Resize existing .ni and replace with this section's contents
+ // TODO: This will be tricky, what to do about existing InputSection?
+ unreachable;
+ },
+ .NONE, .ASSOCIATIVE, _ => unreachable,
+ }
+ },
+ };
+ }
+
+ try coff.flushSectionMerges();
+
+ // Resolve pending associations, create parent sections
+ var num_included_sections: u16 = 0;
+ var num_included_relocs: u32 = 0;
+ for (sections) |*section| {
+ comdat: switch (section.comdat_result) {
+ .pending_association => |root_assoc_sn| {
+ const root_result = sections[root_assoc_sn.toIndex()].comdat_result;
+ assert(root_result != .pending_association);
+ section.comdat_result = root_result;
+ continue :comdat root_result;
+ },
+ .include => {},
+ .skip => {
+ assert(switch (section.comdat) {
+ .NONE, .ASSOCIATIVE => true,
+ else => if (section.comdat_psi.unwrap()) |psi|
+ pending_symbols.values()[psi].si != .null
+ else
+ pending_symbols.values()[section.psi.unwrap().?].si != .null,
+ });
+ continue;
+ },
+ .pending => unreachable,
+ }
+
+ // Until we support sorting .pdata, we shouldn't merge these in, the result would be invalid
+ const section_name = section.name.toSlice(coff);
+ if (std.mem.startsWith(u8, section_name, ".pdata"))
+ continue;
+
+ num_included_sections += 1;
+ num_included_symbols += section.num_symbols;
+ num_included_relocs += section.header.number_of_relocations;
+
+ section.parent_si = (try coff.objectSectionMapIndex(
+ section.name,
+ section.header.flags.ALIGN.alignment() orelse .@"1",
+ .fromFlags(section.header.flags),
+ )).symbol(coff);
+ }
+
+ try coff.nodes.ensureUnusedCapacity(gpa, num_included_sections);
+ try coff.relocs.ensureUnusedCapacity(gpa, num_included_relocs);
+ try coff.symbols.ensureUnusedCapacity(gpa, num_included_symbols + num_included_sections);
+ try coff.input_sections.ensureUnusedCapacity(gpa, num_included_sections);
+
+ for (sections) |*section| {
+ if (section.parent_si == .null) continue;
+
+ const ni = try coff.mf.addLastChildNode(gpa, section.parent_si.node(coff), .{
+ .size = section.header.size_of_raw_data,
+ .alignment = section.header.flags.ALIGN.alignment() orelse .@"1",
+ .moved = true,
+ });
+ coff.nodes.appendAssumeCapacity(.{ .input_section = @enumFromInt(coff.input_sections.items.len) });
+
+ section.si = coff.addSymbolAssumeCapacity();
+ if (section.psi.unwrap()) |psi|
+ pending_symbols.values()[psi].si = section.si;
+
+ const sym = section.si.get(coff);
+ sym.ni = ni;
+ sym.section_number = section.parent_si.get(coff).section_number;
+
+ coff.input_sections.addOneAssumeCapacity().* = .{
+ .ioi = ioi,
+ .si = section.si,
+ .file_location = .{
+ .offset = fl.offset + section.header.pointer_to_raw_data,
+ .size = section.header.size_of_raw_data,
+ },
+ .first_li = @enumFromInt(coff.input_symbols.items.len),
+ .crc = section.comdat_crc,
+ .comdat_si = if (section.comdat_psi.unwrap()) |psi|
+ pending_symbols.values()[psi].si
+ else
+ .null,
+ };
+
+ log.debug(
+ "addInputSection({s}, 0x{x}) = {d}@{d}",
+ .{ section.name.toSlice(coff), section.comdat_crc, section.si, sym.section_number },
+ );
+ coff.synth_prog_node.increaseEstimatedTotalItems(1);
+ }
+
+ for (pending_symbols.values(), pending_symbols.keys(), 0..) |*symbol, index, i| {
+ switch (symbol.value) {
+ .weak_external_aux => continue,
+ else => {},
+ }
+
+ defer log.debug("addInputSymbol({s}, 0x{x}@{d}, {t}=0x{x}) = n{d} {d}@{d}", .{
+ symbol.name.toSlice(coff),
+ index,
+ symbol.section_number,
+ symbol.value,
+ switch (symbol.value) {
+ .weak_external_aux => unreachable,
+ inline else => |v| v,
+ },
+ symbol.si.get(coff).ni,
+ symbol.si,
+ symbol.si.get(coff).section_number,
+ });
+
+ const section = switch (symbol.section_number) {
+ .UNDEFINED => switch (symbol.value) {
+ .section,
+ .static,
+ .weak_external_aux,
+ => unreachable,
+ .external => {
+ if (symbol.weak_external_psi.unwrap()) |weak_external_i| {
+ // If the alias itself is an undef external, we need to wait until flushing the weak
+ // external global before creating a global for the alias, as another input could
+ // still provide the weak external.
+ const weak_sym = pending_symbols.values()[weak_external_i].si.get(coff);
+ weak_sym.setValue(.{ .weak_alias_name = symbol.name });
+ weak_sym.flags.weak_external_strat = pending_symbols.values()[weak_external_i + 1].value.weak_external_aux;
+ }
+
+ // Deferred until referenced by a reloc in this object.
+ // vcruntime.lib defines symbols like this (ie. memcpy_$fo$) that are not referenced
+ continue;
+ },
+ .weak_external => |alias_index| {
+ const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) });
+ symbol.si = global_gop.value_ptr.si;
+ if (!global_gop.found_existing or symbol.si.get(coff).ni == .none) {
+ const sym = symbol.si.get(coff);
+ const alias = pending_symbols.getPtr(alias_index) orelse
+ return diags.failParse(
+ path,
+ "weak external 0x{x} {s}{f} targets unknown symbol index 0x{x}",
+ .{
+ index,
+ symbol.name.toSlice(coff),
+ fmtMemberNameString(member_name),
+ alias_index,
+ },
+ );
+
+ if (alias.si == .null and alias_index > index) {
+ // Resolve this once we see alias
+ alias.weak_external_psi = .wrap(@intCast(i));
+ } else {
+ sym.setValue(if (alias.si.unwrap()) |alias_si| .{
+ .weak_alias_si = alias_si,
+ } else .{
+ .weak_alias_name = alias.name,
+ });
+ sym.flags.weak_external_strat = pending_symbols.values()[i + 1].value.weak_external_aux;
+ }
+ }
+
+ continue;
+ },
+ },
+ .ABSOLUTE => {
+ const value = sym: switch (symbol.value) {
+ .static => |value| {
+ symbol.si = coff.addSymbolAssumeCapacity();
+ break :sym value;
+ },
+ .external => |value| {
+ const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) });
+ symbol.si = global_gop.value_ptr.si;
+ if (global_gop.found_existing)
+ return coff.failMultipleDefinitions(
+ path,
+ member_name,
+ symbol.name,
+ index,
+ global_gop.value_ptr.si,
+ .none,
+ );
+ break :sym value;
+ },
+ else => unreachable,
+ };
+
+ const sym = symbol.si.get(coff);
+ sym.rva = value;
+ sym.section_number = .ABSOLUTE;
+ continue;
+ },
+ .DEBUG => continue,
+ else => |sn| §ions[sn.toIndex()],
+ };
+
+ if (section.si == .null)
+ continue;
+
+ if (symbol.si == .null) {
+ switch (symbol.value) {
+ .section => unreachable,
+ .static => {
+ symbol.si = coff.addSymbolAssumeCapacity();
+ },
+ .external => {
+ assert(index != section.comdat_psi.unwrap());
+ const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) });
+ symbol.si = global_gop.value_ptr.si;
+
+ const sym = symbol.si.get(coff);
+ if (global_gop.found_existing and sym.ni != .none)
+ return coff.failMultipleDefinitions(
+ path,
+ member_name,
+ symbol.name,
+ index,
+ global_gop.value_ptr.si,
+ .none,
+ );
+ },
+ .weak_external,
+ .weak_external_aux,
+ => unreachable,
+ }
+
+ if (section.comdat_psi.unwrap() == @as(u32, @intCast(i)))
+ coff.getNode(section.si.get(coff).ni).input_section.inputSection(coff).comdat_si = symbol.si;
+ }
+
+ if (symbol.weak_external_psi.unwrap()) |weak_external_i| {
+ assert(symbol.si != .null);
+ const weak_sym = pending_symbols.values()[weak_external_i].si.get(coff);
+ weak_sym.setValue(.{ .weak_alias_si = symbol.si });
+ weak_sym.flags.weak_external_strat = pending_symbols.values()[weak_external_i + 1].value.weak_external_aux;
+ }
+
+ if (section.si != symbol.si) {
+ const sym = symbol.si.get(coff);
+ assert(sym.ni == .none);
+ sym.ni = section.si.get(coff).ni;
+ switch (symbol.value) {
+ .section => |v| sym.setExtra(.{ .size = v }),
+ .static => |v| sym.setValue(.{ .node_offset = v }),
+ .external => |v| switch (symbol.section_number) {
+ .UNDEFINED, .ABSOLUTE, .DEBUG => unreachable,
+ else => sym.setValue(.{ .node_offset = v }),
+ },
+ .weak_external,
+ .weak_external_aux,
+ => unreachable,
+ }
+
+ sym.section_number = section.si.get(coff).section_number;
+ }
+ }
+
+ const relocation_size = std.coff.Relocation.sizeOf();
+ for (sections) |section| {
+ if (section.si == .null) continue;
+
+ const loc_sym = section.si.get(coff);
+ assert(loc_sym.loc_relocs == .none);
+ loc_sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
+
+ if (section.header.number_of_relocations == 0) continue;
+
+ try fr.seekTo(fl.offset + section.header.pointer_to_relocations);
+ for (0..section.header.number_of_relocations) |reloc_i| {
+ var reloc: std.coff.Relocation = undefined;
+ @memcpy(std.mem.asBytes(&reloc)[0..relocation_size], try r.take(relocation_size));
+ if (target_endian != native_endian)
+ std.mem.byteSwapAllFields(std.coff.Relocation, &reloc);
+
+ const symbol = pending_symbols.getPtr(reloc.symbol_table_index) orelse
+ return diags.failParse(
+ path,
+ "relocation 0x{x} in section '{s}' of {f}{f} targets invalid symbol index 0x{x}",
+ .{
+ reloc_i,
+ section.name.toSlice(coff),
+ path.fmtEscapeString(),
+ fmtMemberNameString(member_name),
+ reloc.symbol_table_index,
+ },
+ );
+
+ if (symbol.si == .null) {
+ assert(symbol.section_number == .UNDEFINED);
+ switch (symbol.value) {
+ .external => |size| {
+ const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) });
+ symbol.si = global_gop.value_ptr.si;
+ if (!global_gop.found_existing or symbol.si.get(coff).ni == .none) {
+ const sym = symbol.si.get(coff);
+ sym.setExtra(.{ .size = @max(sym.size(), size) });
+ }
+ },
+ else => unreachable,
+ }
+ }
+
+ assert(symbol.si != .null);
+ try coff.addReloc(
+ section.si,
+ reloc.virtual_address - section.header.virtual_address,
+ symbol.si,
+ .pending,
+ @bitCast(reloc.type),
+ );
+ }
+ }
+
+ // Set up contiguous symbol ranges in `input_symbols` for both symbols we just created,
+ // and symbols that were previously created as undefined, but we just defined.
+ const SortContext = struct {
+ v: []const PendingSymbol,
+
+ pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
+ const lhs = &ctx.v[a_index];
+ const rhs = &ctx.v[b_index];
+ if (lhs.section_number == rhs.section_number)
+ return @intFromEnum(lhs.si) < @intFromEnum(rhs.si);
+ return @intFromEnum(lhs.section_number) < @intFromEnum(rhs.section_number);
+ }
+ };
+
+ pending_symbols.sortUnstable(SortContext{ .v = pending_symbols.values() });
+
+ try coff.input_symbols.ensureUnusedCapacity(gpa, num_included_symbols + num_included_sections);
+ var prev_sn: Symbol.SectionNumber = .DEBUG;
+ var include_section = false;
+ for (pending_symbols.values()) |symbol| {
+ // The symbol may have not been included, or it's an undefined external / aux
+ if (symbol.si == .null or symbol.si.get(coff).ni == .none) continue;
+
+ if (prev_sn != symbol.section_number) {
+ prev_sn = symbol.section_number;
+ if (symbol.section_number.hasIndex()) {
+ const section = §ions[symbol.section_number.toIndex()];
+ include_section = section.comdat_result == .include;
+ if (include_section) {
+ const isi = coff.getNode(section.si.get(coff).ni).input_section;
+ isi.inputSection(coff).first_li = @enumFromInt(coff.input_symbols.items.len);
+ }
+ }
+ }
+
+ if (include_section) {
+ assert(coff.getNode(symbol.si.get(coff).ni) == .input_section);
+ symbol.si.get(coff).setExtra(.{ .isli = @enumFromInt(coff.input_symbols.items.len) });
+ coff.input_symbols.addOneAssumeCapacity().* = .{
+ .si = symbol.si,
+ .name = symbol.name,
+ };
+ }
+ }
+}
+
+fn failMultipleDefinitions(
+ coff: *Coff,
+ path: std.Build.Cache.Path,
+ member_name: ?[]const u8,
+ name: String,
+ index: u32,
+ existing_si: Symbol.Index,
+ comdat_reason: union(enum) {
+ none: void,
+ duplicate: void,
+ size: struct { a: u64, b: u64 },
+ crc: struct { a: u32, b: u32 },
+ },
+) error{ AlreadyReported, OutOfMemory } {
+ const num_notes: usize = 2 + @as(usize, @intFromBool(comdat_reason != .none));
+ var err = try coff.base.comp.link_diags.addErrorWithNotes(num_notes);
+ try err.addMsg("multiple definitions of '{s}'", .{name.toSlice(coff)});
+
+ switch (coff.getNode(existing_si.get(coff).ni)) {
+ .input_section => |isi| {
+ const other_ioi = isi.input(coff);
+ err.addNote("first seen in input '{f}{f}'", .{
+ other_ioi.path(coff).fmtEscapeString(),
+ fmtMemberNameString(other_ioi.memberName(coff)),
+ });
+ },
+ .nav, .uav => err.addNote("first seen in module '{s}'", .{
+ coff.base.comp.zcu.?.root_mod.fully_qualified_name,
+ }),
+ else => unreachable,
+ }
+
+ err.addNote("defined again in input '{f}{f}' (0x{x}))", .{ path, fmtMemberNameString(member_name), index });
+ switch (comdat_reason) {
+ .none => {},
+ .duplicate => err.addNote("COMDAT rule requires no duplicates", .{}),
+ .size => |s| err.addNote(
+ "COMDAT rule require duplicates to have the same size ({d} vs {d})",
+ .{ s.a, s.b },
+ ),
+ .crc => |s| err.addNote(
+ "COMDAT rule require duplicates to have the same CRC (0x{x} vs 0x{x})",
+ .{ s.a, s.b },
+ ),
+ }
+
+ return error.AlreadyReported;
+}
+
+const ArchiveMemberHeader = struct {
+ name: []const u8,
+ size: u34,
+};
+
+/// Return value lifetime is that of `header`
+fn parseArchiveMemberHeader(
+ diags: *link.Diags,
+ path: std.Build.Cache.Path,
+ header: *const std.coff.ArchiveMemberHeader,
+ opt_longnames: ?[]const u8,
+) !ArchiveMemberHeader {
+ return parseArchiveMemberHeaderInner(header, opt_longnames) catch |err| switch (err) {
+ error.BadName => return diags.failParse(path, "malformed member name: '{s}'", .{&header.name}),
+ error.BadSize => return diags.failParse(path, "malformed member size: '{s}'", .{&header.size}),
+ error.BadEndOfHeader => return diags.failParse(path, "end of header was invalid", .{}),
+ error.NoLongNames => return diags.failParse(path, "long name used without longnames member", .{}),
+ };
+}
+
+fn parseArchiveMemberHeaderInner(
+ header: *const std.coff.ArchiveMemberHeader,
+ opt_longnames: ?[]const u8,
+) !ArchiveMemberHeader {
+ const name = try header.parseName(opt_longnames);
+ const size = header.parseSize() catch return error.BadSize;
+
+ if (!std.mem.eql(u8, &header.end_of_header, std.coff.archive_end_of_header))
+ return error.BadEndOfHeader;
+
+ return .{
+ .name = name,
+ .size = size,
+ };
+}
+
+fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) LoadInputError!void {
+ const comp = coff.base.comp;
+ const gpa = comp.gpa;
+ const diags = &comp.link_diags;
+ const r = &fr.interface;
+ const target_endian = coff.targetEndian();
+
+ log.debug("loadArchive({f})", .{path.fmtEscapeString()});
+
+ const signature = try r.take(std.coff.archive_signature.len);
+ if (!std.mem.eql(u8, signature, std.coff.archive_signature))
+ return diags.failParse(path, "bad signature", .{});
+
+ var opt_expected_kind: ?std.coff.ArchiveMemberHeader.Kind = .first_linker;
+ var opt_longnames: ?[]const u8 = null;
+ defer if (opt_longnames) |l| gpa.free(l);
+
+ var members: std.ArrayList(struct {
+ offset: u32,
+ iami: ?InputArchive.Member.Index,
+ }) = .empty;
+ var symbol_member_indices: std.ArrayList(u32) = .empty;
+
+ const iai: InputArchive.Index = @enumFromInt(coff.input_archives.items.len);
+ (try coff.input_archives.addOne(gpa)).* = .{
+ .path = path,
+ };
+
+ const first_iami = coff.input_archive_members.items.len;
+ const first_iamsi = coff.input_archive_symbols.items.len;
+ const first_symbol_indices_index = coff.input_archive_symbol_indices.count();
+
+ errdefer {
+ for (coff.input_archive_symbol_indices.values()) |*v| {
+ if (@intFromEnum(v.last) < first_iamsi) continue;
+ if (@intFromEnum(v.first) >= first_iamsi) continue;
+
+ var iter = v.first;
+ v.last = while (iter != v.last) {
+ const sym = &coff.input_archive_symbols.items[@intFromEnum(iter)];
+ if (@intFromEnum(sym.next) >= first_iamsi) {
+ sym.next = iter;
+ break iter;
+ }
+
+ iter = sym.next;
+ } else unreachable;
+ }
+
+ // New entries in this map will only have pointed to iamsi we also just added
+ coff.input_archive_symbol_indices.shrinkRetainingCapacity(first_symbol_indices_index);
+ coff.input_archive_symbols.shrinkRetainingCapacity(first_iamsi);
+ coff.input_archive_members.shrinkRetainingCapacity(first_iami);
+ _ = coff.input_archives.pop();
+ }
+
+ var pos = fr.logicalPos();
+ const size = try fr.getSize();
+ while (pos < size) : (pos = fr.logicalPos()) {
+ if ((pos & 1) != 0) try r.discardAll(1);
+ const header = try r.takeStruct(std.coff.ArchiveMemberHeader, target_endian);
+ const res = try parseArchiveMemberHeader(diags, path, &header, opt_longnames);
+
+ const member_end = fr.logicalPos() + res.size;
+ if (member_end > size)
+ return diags.failParse(path, "out-of-bounds length 0x{x} in member '{s}'", .{ res.size, res.name });
+
+ log.debug("loadArchiveMember({s})", .{res.name});
+
+ if (opt_expected_kind) |expected_kind| switch (expected_kind) {
+ .first_linker => {
+ if (!std.mem.eql(u8, res.name, "/"))
+ return diags.failParse(path, "expected first linker member, found '{s}'", .{res.name});
+
+ try fr.seekTo(fr.logicalPos() + res.size);
+ opt_expected_kind = .second_linker;
+ continue;
+ },
+ .second_linker => {
+ if (!std.mem.eql(u8, res.name, "/"))
+ return diags.failParse(path, "expected second linker member, found '{s}'", .{res.name});
+
+ const num_members = try r.takeInt(u32, target_endian);
+ pos = fr.logicalPos();
+ if (pos + num_members * @sizeOf(u32) > member_end)
+ return diags.failParse(path, "invalid member count 0x{x} in second linker member", .{num_members});
+
+ try members.ensureTotalCapacity(gpa, num_members);
+ for (0..num_members) |_|
+ members.addOneAssumeCapacity().* = .{
+ .offset = try r.takeInt(u32, target_endian),
+ .iami = null,
+ };
+
+ const num_symbols = try r.takeInt(u32, target_endian);
+ pos = fr.logicalPos();
+ if (pos + num_symbols * @sizeOf(u16) > member_end)
+ return diags.failParse(path, "invalid symbol count 0x{x} in second linker member", .{num_symbols});
+
+ try symbol_member_indices.ensureTotalCapacity(gpa, num_symbols);
+ for (0..num_symbols) |_|
+ symbol_member_indices.addOneAssumeCapacity().* = (try r.takeInt(u16, target_endian)) - 1;
+
+ pos = fr.logicalPos();
+ try coff.ensureManyUnusedStringCapacity(num_symbols, @intCast(member_end - pos));
+ try coff.input_archive_members.ensureUnusedCapacity(gpa, num_members);
+ try coff.input_archive_symbols.ensureUnusedCapacity(gpa, num_symbols);
+ try coff.input_archive_symbol_indices.ensureUnusedCapacity(gpa, num_symbols);
+
+ var symbol_i: u32 = 0;
+ while (pos < member_end and symbol_i < num_symbols) : ({
+ pos = fr.logicalPos();
+ symbol_i += 1;
+ }) {
+ const name = if (r.takeDelimiter(0) catch |err| switch (err) {
+ error.StreamTooLong => null,
+ else => |e| return e,
+ }) |n| n else return diags.failParse(path, "unterminated string found in second linker member", .{});
+
+ const string = coff.getOrPutStringAssumeCapacity(name);
+ const iamsi: InputArchive.Member.Symbol.Index = @enumFromInt(coff.input_archive_symbols.items.len);
+ const symbol_gop = coff.input_archive_symbol_indices.getOrPutAssumeCapacity(string);
+ if (!symbol_gop.found_existing) {
+ symbol_gop.value_ptr.* = .{
+ .first = iamsi,
+ .last = iamsi,
+ };
+ } else {
+ coff.input_archive_symbols.items[@intFromEnum(symbol_gop.value_ptr.last)].next = iamsi;
+ symbol_gop.value_ptr.last = iamsi;
+ }
+
+ const iami = members.items[symbol_member_indices.items[symbol_i]].iami orelse iami: {
+ const iami: InputArchive.Member.Index = @enumFromInt(coff.input_archive_members.items.len);
+ const member_offset = members.items[symbol_member_indices.items[symbol_i]].offset;
+ coff.input_archive_members.addOneAssumeCapacity().* = .{
+ .iai = iai,
+ .name = undefined,
+ .content = .{
+ .object = .{
+ .offset = member_offset,
+ .size = undefined,
+ },
+ },
+ .flags = .{
+ .is_loaded = false,
+ },
+ };
+
+ members.items[symbol_member_indices.items[symbol_i]].iami = iami;
+ break :iami iami;
+ };
+
+ log.debug("loadArchiveMemberSymbol({s}) = ({d}, {d}, {d})", .{ name, iai, iami, iamsi });
+
+ coff.input_archive_symbols.addOneAssumeCapacity().* = .{
+ .iami = iami,
+ .next = iamsi,
+ };
+ }
+
+ if (symbol_i != num_symbols)
+ return diags.failParse(
+ path,
+ " expected {d} entries in second linker member string table, but found {d}",
+ .{ num_symbols, symbol_i },
+ );
+
+ try fr.seekTo(member_end);
+ opt_expected_kind = .longnames;
+ continue;
+ },
+ .longnames => {
+ // This member is optional
+ if (std.mem.eql(u8, res.name, "//"))
+ opt_longnames = try r.readAlloc(gpa, @intCast(res.size));
+
+ opt_expected_kind = null;
+ break;
+ },
+ else => unreachable,
+ };
+ }
+
+ if (opt_expected_kind) |expected_kind| switch (expected_kind) {
+ .first_linker => return diags.failParse(path, "missing first linker member", .{}),
+ .second_linker => return diags.failParse(path, "missing second linker member", .{}),
+ else => {},
+ };
+
+ // Validate / read names and sizes of all the referenced members, enumerate imports
+ for (coff.input_archive_members.items[first_iami..]) |*member| {
+ try fr.seekTo(member.content.object.offset);
+
+ const header = try r.takeStruct(std.coff.ArchiveMemberHeader, target_endian);
+ const res = try parseArchiveMemberHeader(diags, path, &header, opt_longnames);
+
+ try coff.ensureUnusedStringCapacity(res.name.len);
+ member.name = coff.getOrPutStringAssumeCapacity(res.name);
+
+ const member_sig = try r.peek(4);
+ const machine: std.coff.IMAGE.FILE.MACHINE =
+ @enumFromInt(std.mem.readInt(u16, member_sig[0..2], target_endian));
+ const sig = std.mem.readInt(u16, member_sig[2..4], target_endian);
+
+ log.debug("verifyArchiveMember({s}) = 0x{x}+{x}", .{
+ res.name,
+ member.content.object.offset,
+ res.size,
+ });
+
+ const expected_machine = comp.root_mod.resolved_target.result.toCoffMachine();
+ if (machine == std.coff.IMAGE.FILE.MACHINE.UNKNOWN and sig == 0xffff) {
+ const import_header = try r.takeStruct(std.coff.ImportHeader, target_endian);
+ const strings = r.take(import_header.size_of_data) catch |err| switch (err) {
+ error.EndOfStream => return diags.failParse(path, "invalid data size in import header '{s}'", .{res.name}),
+ else => |e| return e,
+ };
+
+ var split = std.mem.splitScalar(u8, strings, 0);
+ const symbol_name = split.next() orelse
+ return diags.failParse(path, "invalid symbol name string in import header '{s}'", .{res.name});
+ var lib_name = split.next() orelse
+ return diags.failParse(path, "invalid dll name string in import header '{s}' ('{s}')", .{ res.name, symbol_name });
+
+ if (import_header.machine != expected_machine)
+ return diags.failParse(path, "machine mismatch in import header '{s}' ('{s}'): expected {t}, found {t}", .{
+ res.name,
+ symbol_name,
+ expected_machine,
+ machine,
+ });
+
+ const ext = ".dll";
+ if (!std.mem.endsWith(u8, lib_name, ext))
+ return diags.failParse(
+ path,
+ "unexpected extension for import '{s} ('{s}'): '{s}'",
+ .{ res.name, symbol_name, lib_name },
+ );
+
+ lib_name = lib_name[0 .. lib_name.len - ext.len];
+ log.debug("verifyArchiveImportHeader({s}, {s}, {s}) = {t} ({t})", .{
+ res.name,
+ symbol_name,
+ lib_name,
+ import_header.types.type,
+ import_header.types.name_type,
+ });
+
+ try coff.ensureManyUnusedStringCapacity(2, strings.len - ext.len);
+ member.content = .{
+ .import = .{
+ .symbol_name = coff.getOrPutStringAssumeCapacity(symbol_name),
+ .lib_name = coff.getOrPutStringAssumeCapacity(lib_name),
+ .import_ordinal_hint = import_header.hint,
+ .type = import_header.types.type,
+ .name_type = import_header.types.name_type,
+ },
+ };
+ } else {
+ member.content.object.size = res.size;
+ // Microsoft's CRT contains members that set .UNKNOWN but do have undef symbols
+ if (machine != expected_machine and machine != .UNKNOWN) {
+ return diags.failParse(path, "machine mismatch in member header '{s}': expected {t}, found {t}", .{
+ res.name,
+ expected_machine,
+ machine,
+ });
+ }
+ }
+ }
+}
+
+fn loadRes(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) LoadInputError!void {
+ const comp = coff.base.comp;
+ const gpa = comp.gpa;
+ const diags = &comp.link_diags;
+ const r = &fr.interface;
+
+ log.debug("loadRes({f})", .{path.fmtEscapeString()});
+
+ _ = gpa;
+ _ = diags;
+ _ = r;
+}
+
+fn loadDll(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) LoadInputError!void {
+ const comp = coff.base.comp;
+ const gpa = comp.gpa;
+ const diags = &comp.link_diags;
+ const r = &fr.interface;
+
+ log.debug("loadDll({f})", .{path.fmtEscapeString()});
+
+ _ = gpa;
+ _ = diags;
+ _ = r;
+}
+
pub fn prelink(coff: *Coff, prog_node: std.Progress.Node) link.Error!void {
- _ = coff;
_ = prog_node;
+ const base = coff.base;
+ const comp = base.comp;
+
+ log.debug("prelink()", .{});
+
+ if (coff.pending_default_libs.items.len > 0) {
+ // Libs provided by /DEFAULTLIB arguments in objects are searched after all other inputs
+ const gpa = comp.gpa;
+ const arena = comp.arena;
+ const target = &comp.root_mod.resolved_target.result;
+
+ defer {
+ for (coff.pending_default_libs.items) |l| gpa.free(l.path);
+ coff.pending_default_libs.clearAndFree(gpa);
+ }
+
+ assert(comp.config.link_libc);
+ const libc_installation = comp.libc_installation.?;
+ const all_paths: [3]?[]const u8 = .{
+ libc_installation.crt_dir,
+ libc_installation.msvc_lib_dir,
+ libc_installation.kernel32_lib_dir,
+ };
+ const search_paths = all_paths[0..if (target.abi == .msvc or target.abi == .itanium) 3 else 1];
+ lib: for (coff.pending_default_libs.items) |lib| {
+ if (!std.mem.eql(u8, std.fs.path.extension(lib.path), ".lib"))
+ return comp.link_diags.failParse(
+ lib.ioi.path(coff),
+ "/DEFAULTLIB library '{s}' had unexpected extension",
+ .{lib.path},
+ );
+
+ log.debug("loadDefaultLib({s}, {f})", .{ lib.path, lib.ioi.path(coff) });
+ for (search_paths) |opt_path| if (opt_path) |search_path| {
+ const lib_path = try Path.initCwd(search_path).join(arena, lib.path);
+ const archive = link.openObject(comp.io, lib_path, false, false) catch |err| switch (err) {
+ error.FileNotFound => {
+ arena.free(lib_path.sub_path);
+ continue;
+ },
+ else => |e| return comp.link_diags.failParse(
+ lib.ioi.path(coff),
+ "error opening /DEFAULTLIB library '{s}': {t}",
+ .{ lib.path, e },
+ ),
+ };
+ errdefer archive.file.close(comp.io);
+
+ coff.loadInput(.{ .archive = archive }) catch |err| switch (err) {
+ else => |e| return comp.link_diags.failParse(
+ lib.ioi.path(coff),
+ "error loading /DEFAULTLIB library '{s}': {t}",
+ .{ lib.path, e },
+ ),
+ };
+
+ break :lib;
+ };
+
+ return comp.link_diags.failParse(
+ lib.ioi.path(coff),
+ "/DEFAULTLIB library '{s}' was not found",
+ .{lib.path},
+ );
+ }
+ }
+
+ coff.inputs_complete = true;
+ if (comp.zcu == null)
+ coff.exports_complete = true;
}
-pub fn updateNav(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void {
+pub fn updateNav(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) link.Error!void {
coff.updateNavInner(pt, nav_index) catch |err| switch (err) {
+ error.MappedFileIo => return coff.base.cgFail(
+ nav_index,
+ "linker failed to update variable: {t}",
+ .{coff.mf.io_err.?},
+ ),
else => |e| return e,
- error.MappedFileIo => return coff.base.cgFail(nav_index, "linker failed to update variable: {t}", .{coff.mf.io_err.?}),
};
}
fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void {
@@ -1546,11 +5462,13 @@ fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde
const nmi = try coff.navMapIndex(zcu, nav_index);
const si = nmi.symbol(coff);
+ log.debug("updateNav({f}) = {d}", .{ nav.fqn.fmt(ip), si });
const ni = ni: {
switch (si.get(coff).ni) {
.none => {
const sec_si = try coff.navSection(zcu, nav.resolved.?);
try coff.nodes.ensureUnusedCapacity(gpa, 1);
+ if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1);
const ni = try coff.mf.addLastChildNode(gpa, sec_si.node(coff), .{
.alignment = zcu.navAlignment(nav_index).toStdMem(),
.moved = true,
@@ -1565,6 +5483,9 @@ fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde
const sym = si.get(coff);
assert(sym.loc_relocs == .none);
sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
+ if (!isImage(coff) and sym.target_relocs != .none)
+ try coff.pendingSymbolTableEntry(si);
+
break :ni sym.ni;
};
@@ -1582,12 +5503,12 @@ fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde
error.WriteFailed => return nw.err.?,
else => |e| return e,
};
- si.get(coff).size = @intCast(nw.interface.end);
- si.applyLocationRelocs(coff);
+ si.get(coff).extra.size = @intCast(nw.interface.end);
+ try si.applyLocationRelocs(coff);
}
if (nav.resolved.?.@"linksection".unwrap()) |_| {
- try ni.resize(&coff.mf, gpa, si.get(coff).size);
+ try ni.resize(&coff.mf, gpa, si.get(coff).extra.size);
var parent_ni = ni;
while (true) {
parent_ni = parent_ni.parent(&coff.mf);
@@ -1610,7 +5531,7 @@ pub fn lowerUav(
pt: Zcu.PerThread,
uav_val: InternPool.Index,
uav_align: InternPool.Alignment,
-) !link.File.SymbolId {
+) link.Error!link.File.SymbolId {
const zcu = pt.zcu;
const gpa = zcu.gpa;
@@ -1639,7 +5560,7 @@ pub fn updateFunc(
pt: Zcu.PerThread,
func_index: InternPool.Index,
mir: *const codegen.AnyMir,
-) !void {
+) link.Error!void {
coff.updateFuncInner(pt, func_index, mir) catch |err| switch (err) {
else => |e| return e,
error.MappedFileIo => return coff.base.cgFail(
@@ -1669,6 +5590,7 @@ fn updateFuncInner(
.none => {
const sec_si = try coff.navSection(zcu, nav.resolved.?);
try coff.nodes.ensureUnusedCapacity(gpa, 1);
+ if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1);
const mod = zcu.navFileScope(func.owner_nav).mod.?;
const target = &mod.resolved_target.result;
const ni = try coff.mf.addLastChildNode(gpa, sec_si.node(coff), .{
@@ -1694,6 +5616,8 @@ fn updateFuncInner(
const sym = si.get(coff);
assert(sym.loc_relocs == .none);
sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
+ if (!isImage(coff) and sym.target_relocs != .none)
+ try coff.pendingSymbolTableEntry(si);
break :ni sym.ni;
};
@@ -1712,21 +5636,245 @@ fn updateFuncInner(
error.WriteFailed => return nw.err.?,
else => |e| return e,
};
- si.get(coff).size = @intCast(nw.interface.end);
- si.applyLocationRelocs(coff);
+ si.get(coff).extra.size = @intCast(nw.interface.end);
+ try si.applyLocationRelocs(coff);
+}
+
+pub fn updateErrorData(coff: *Coff, pt: Zcu.PerThread) !void {
+ coff.flushLazy(pt, .{
+ .kind = .const_data,
+ .index = @intCast(coff.lazy.getPtr(.const_data).map.getIndex(.anyerror_type) orelse return),
+ }) catch |err| switch (err) {
+ else => |e| return e,
+ error.MappedFileIo => return coff.base.comp.link_diags.fail(
+ "updateErrorData failed: {t}",
+ .{coff.mf.io_err.?},
+ ),
+ };
+}
+
+fn flushImplib(
+ coff: *Coff,
+ implib_file: []const u8,
+) !void {
+ // Emitting implibs is only valid for images
+ assert(coff.export_table.ni != .none);
+
+ const comp = coff.base.comp;
+ const gpa = comp.gpa;
+ const io = comp.io;
+
+ const image_name = std.mem.sliceTo(
+ coff.export_table.ni.slice(&coff.mf)[@sizeOf(std.coff.ExportDirectoryTable)..],
+ 0,
+ );
+ const machine_type = coff.targetLoad(&coff.headerPtr().machine);
+ const members = members: {
+ const def_arena: std.heap.ArenaAllocator = .init(gpa);
+ var def: ModuleDefinition = .{
+ .name = image_name,
+ .arena = def_arena,
+ .type = .mingw,
+ };
+ defer def.deinit();
+
+ try def.exports.ensureUnusedCapacity(
+ def.arena.allocator(),
+ coff.export_table.entries.count(),
+ );
+
+ const name_table_slice = coff.export_table.name_table_ni.slice(&coff.mf);
+ for (coff.export_table.entries.values(), 0..) |entry, ord| {
+ const name = name_table_slice[entry.name_index..][0..entry.name_len];
+ const section_number = entry.si.get(coff).section_number;
+ const import_type: std.coff.ImportType = switch (section_number.symbol(coff)) {
+ .data, .rdata => .DATA,
+ .text => .CODE,
+ else => return comp.link_diags.fail(
+ "unsupported section for export '{s}': {s}",
+ .{ name, §ion_number.header(coff).name },
+ ),
+ };
+
+ def.exports.appendAssumeCapacity(.{
+ .name = name,
+ .mangled_symbol_name = null,
+ .ext_name = null,
+ .import_name = null,
+ .export_as = null,
+ .no_name = false,
+ .ordinal = @intCast(ord),
+ .type = import_type,
+ .private = false,
+ });
+ }
+
+ def.fixupForImportLibraryGeneration(machine_type);
+ break :members try implib.getMembers(gpa, def, machine_type);
+ };
+ defer members.deinit();
+
+ const lib_sub_path = try std.fs.path.join(gpa, &.{
+ std.fs.path.dirname(coff.base.emit.sub_path) orelse "",
+ implib_file,
+ });
+ defer gpa.free(lib_sub_path);
+
+ const lib_final_file = try coff.base.emit.root_dir.handle.createFile(io, lib_sub_path, .{ .truncate = true });
+ defer lib_final_file.close(io);
+ var buffer: [1024]u8 = undefined;
+ var file_writer = lib_final_file.writer(io, &buffer);
+ try implib.writeCoffArchive(gpa, &file_writer.interface, members);
+ try file_writer.interface.flush();
}
-pub fn updateErrorData(coff: *Coff, pt: Zcu.PerThread) !void {
- coff.flushLazy(pt, .{
- .kind = .const_data,
- .index = @intCast(coff.lazy.getPtr(.const_data).map.getIndex(.anyerror_type) orelse return),
- }) catch |err| switch (err) {
- else => |e| return e,
- error.MappedFileIo => return coff.base.comp.link_diags.fail(
- "updateErrorData failed: {t}",
- .{coff.mf.io_err.?},
- ),
- };
+fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void {
+ const comp = coff.base.comp;
+ const gpa = comp.gpa;
+ const max_notes = 4;
+
+ var undef_indices: std.ArrayListUnmanaged(u32) = .empty;
+ for (coff.relocs.items, 0..) |reloc, reloc_i| {
+ if (reloc.flags.free) continue;
+ const target_sym = reloc.target.get(coff);
+ switch (target_sym.ni) {
+ .none => {
+ assert(target_sym.gmi != .none);
+ if (target_sym.section_number == .ABSOLUTE) continue;
+ (try undef_indices.addOne(gpa)).* = @intCast(reloc_i);
+ },
+ else => continue,
+ }
+ }
+
+ if (undef_indices.items.len == 0) return;
+
+ const undefLessThan = struct {
+ fn lessThan(ctx: *const Coff, lhs: u32, rhs: u32) bool {
+ const reloc_l = &ctx.relocs.items[lhs];
+ const reloc_r = &ctx.relocs.items[rhs];
+ if (reloc_l.target == reloc_r.target)
+ return @intFromEnum(reloc_l.loc) < @intFromEnum(reloc_r.loc)
+ else
+ return @intFromEnum(reloc_l.target) < @intFromEnum(reloc_r.target);
+ }
+ }.lessThan;
+
+ std.mem.sortUnstable(u32, undef_indices.items, coff, undefLessThan);
+
+ var start_i: usize = 0;
+ var num_unique_references: usize = 1;
+ for (0..undef_indices.items.len) |i| {
+ const target = coff.relocs.items[undef_indices.items[start_i]].target;
+ if (i == undef_indices.items.len - 1 or target != coff.relocs.items[undef_indices.items[i + 1]].target) {
+ defer {
+ start_i = i + 1;
+ num_unique_references = 1;
+ }
+
+ const num_full_notes = @min(max_notes, num_unique_references);
+ var err = try comp.link_diags.addErrorWithNotes(
+ num_full_notes + @intFromBool(num_unique_references > max_notes),
+ );
+ const target_sym = target.get(coff);
+ try err.addMsg("undefined symbol: {s}", .{target_sym.gmi.name(coff).toSlice(coff)});
+
+ // TODO: If lib_name is set, show the user
+
+ var prev_loc_si: Symbol.Index = .null;
+ for (undef_indices.items[start_i .. i + 1]) |reference_i| {
+ if (err.note_slot == num_full_notes) break;
+
+ const reloc = &coff.relocs.items[reference_i];
+ const loc_si = reloc.loc;
+ if (loc_si == prev_loc_si) continue;
+ defer prev_loc_si = loc_si;
+
+ const loc_sym = loc_si.get(coff);
+
+ // TODO: Make this a helper for anything that needs to report "referenced by" notes
+ switch (coff.getNode(loc_sym.ni)) {
+ .data_directories => {
+ const dir: std.coff.IMAGE.DIRECTORY_ENTRY =
+ @enumFromInt(reloc.offset / @sizeOf(std.coff.ImageDataDirectory));
+ err.addNote("referenced by data directory entry: {t}", .{dir});
+ },
+ .optional_header => err.addNote("referenced by optional header field", .{}),
+ .input_section => |isi| {
+ const other_ioi = isi.input(coff);
+ if (loc_sym.gmi == .none) {
+ const section = isi.inputSection(coff);
+ const section_name = coff.getNode(loc_sym.ni.parent(&coff.mf))
+ .object_section.name(coff).toSlice(coff);
+
+ if (section.comdat_si != .null) {
+ const comdat_sym = section.comdat_si.get(coff);
+ const comdat_name = if (comdat_sym.gmi != .none)
+ comdat_sym.gmi.name(coff).toSlice(coff)
+ else
+ comdat_sym.extra.isli.name(coff).toSlice(coff);
+
+ err.addNote("referenced by input COMDAT section '{s}={s}' '{f}{f}'", .{
+ section_name,
+ comdat_name,
+ other_ioi.path(coff).fmtEscapeString(),
+ fmtMemberNameString(other_ioi.memberName(coff)),
+ });
+ } else {
+ err.addNote("referenced by input section '{s}' '{f}{f}'", .{
+ section_name,
+ other_ioi.path(coff).fmtEscapeString(),
+ fmtMemberNameString(other_ioi.memberName(coff)),
+ });
+ }
+ } else {
+ err.addNote("referenced by input symbol '{s}' from '{f}{f}'", .{
+ loc_sym.gmi.name(coff).toSlice(coff),
+ other_ioi.path(coff).fmtEscapeString(),
+ fmtMemberNameString(other_ioi.memberName(coff)),
+ });
+ }
+ },
+ .import_thunk => |gmi| err.addNote("referenced by import thunk for '{s}'", .{
+ gmi.name(coff).toSlice(coff),
+ }),
+ inline .nav,
+ .uav,
+ .lazy_code,
+ .lazy_const_data,
+ => |val, tag| {
+ err.addNote("referenced by '{f}'", .{
+ format: switch (tag) {
+ .nav => {
+ const ip = &comp.zcu.?.intern_pool;
+ break :format ip.getNav(val.navIndex(coff)).fqn.fmt(ip);
+ },
+ .uav => Value.fromInterned(val.uavValue(coff)).fmtValue(.{
+ .zcu = coff.base.comp.zcu.?,
+ .tid = tid,
+ }),
+ inline .lazy_code, .lazy_const_data => Type.fromInterned(val.lazySymbol(coff).ty).fmt(.{
+ .zcu = coff.base.comp.zcu.?,
+ .tid = tid,
+ }),
+ else => unreachable,
+ },
+ });
+ },
+ else => unreachable,
+ }
+ }
+
+ if (num_unique_references > max_notes)
+ err.addNote("referenced {d} more times", .{num_unique_references - max_notes});
+ } else if (i != start_i and
+ coff.relocs.items[undef_indices.items[i - 1]].loc != coff.relocs.items[undef_indices.items[i]].loc)
+ {
+ num_unique_references += 1;
+ }
+ }
+
+ return error.AlreadyReported;
}
pub fn flush(
@@ -1734,35 +5882,79 @@ pub fn flush(
arena: std.mem.Allocator,
tid: Zcu.PerThread.Id,
prog_node: std.Progress.Node,
-) !void {
+) link.Error!void {
_ = arena;
_ = prog_node;
+ const comp = coff.base.comp;
+
+ // TODO: When https://github.com/ziglang/zig/issues/23617 is in,
+ // this should be set after updateExports instead
+ coff.exports_complete = true;
+
+ while (try coff.resolve(tid)) {}
while (try coff.idle(tid)) {}
- // hack for stage2_x86_64 + coff
- const comp = coff.base.comp;
- if (comp.compiler_rt_dyn_lib) |crt_file| {
- const gpa = comp.gpa;
- const io = comp.io;
- const compiler_rt_sub_path = try std.fs.path.join(gpa, &.{
- std.fs.path.dirname(coff.base.emit.sub_path) orelse "",
- std.fs.path.basename(crt_file.full_object_path.sub_path),
- });
- defer gpa.free(compiler_rt_sub_path);
- std.Io.Dir.copyFile(
- crt_file.full_object_path.root_dir.handle,
- crt_file.full_object_path.sub_path,
- coff.base.emit.root_dir.handle,
- compiler_rt_sub_path,
- io,
- .{},
- ) catch |err| return comp.link_diags.fail("copy '{s}' failed: {t}", .{ compiler_rt_sub_path, err });
+ // This has to occur after all other flushMoved / flushResized have resolved,
+ // but it will also generate one more set of resizes and moves.
+ if (coff.symbol_table.pending_shrink) {
+ coff.symbol_table.pending_shrink = false;
+
+ const number_of_symbols = coff.targetLoad(&coff.headerPtr().number_of_symbols);
+ coff.symbol_table.ni.shrink(
+ &coff.mf,
+ comp.gpa,
+ number_of_symbols * std.coff.Symbol.sizeOf(),
+ true,
+ ) catch |err| return comp.link_diags.fail(
+ "linker failed to compact symbol table: {t}",
+ .{err},
+ );
}
+ while (try coff.idle(tid)) {}
+
+ if (coff.isImage())
+ try coff.reportUndefs(tid);
+
+ if (comp.emit_implib) |implib_file|
+ coff.flushImplib(implib_file) catch |err|
+ return comp.link_diags.fail("flushing implib '{s}' failed: {t}", .{ implib_file, err });
+
+ coff.mf.flush() catch |err| switch (err) {
+ error.Canceled => |e| return e,
+ else => |e| return comp.link_diags.fail("flush write failed: {t}", .{e}),
+ };
+
+ if (coff.options.enable_link_snapshots)
+ coff.dumpStderr(tid) catch |err|
+ return comp.link_diags.fail("dumping link snapshot failed: {t}", .{err});
}
-pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
+/// Runs a single "resolution" task.
+/// These are tasks that need to modify the node structure in some way.
+/// They must run in a defined order with respect to linker tasks.
+fn resolve(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
const comp = coff.base.comp;
task: {
+ while (coff.section_merge_pending_index < coff.section_merges.count()) {
+ defer coff.section_merge_pending_index += 1;
+ const sub_prog_node = coff.synth_prog_node.start(
+ coff.section_merges.keys()[coff.section_merge_pending_index].toSlice(coff),
+ 0,
+ );
+ defer sub_prog_node.end();
+ coff.flushSectionMerge(coff.section_merge_pending_index) catch |err| switch (err) {
+ //error.OutOfMemory => |e| return e,
+ else => |e| return comp.link_diags.fail(
+ "linker failed to merge section {s} into {s}: {t}",
+ .{
+ coff.section_merges.keys()[coff.section_merge_pending_index].toSlice(coff),
+ coff.section_merges.values()[coff.section_merge_pending_index].toSlice(coff),
+ e,
+ },
+ ),
+ };
+ break :task;
+ }
while (coff.pending_uavs.pop()) |pending_uav| {
const sub_prog_node = coff.idleProgNode(tid, coff.const_prog_node, .{ .uav = pending_uav.key });
defer sub_prog_node.end();
@@ -1779,22 +5971,52 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
};
break :task;
}
- if (coff.global_pending_index < coff.globals.count()) {
- const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = tid };
- const gmi: Node.GlobalMapIndex = @enumFromInt(coff.global_pending_index);
- coff.global_pending_index += 1;
+ if (coff.pending_input) |pending_iami| {
+ const name_slice = pending_iami.member(coff).name.toSlice(coff);
+ const sub_prog_node = coff.input_prog_node.start(
+ name_slice,
+ 0,
+ );
+ defer sub_prog_node.end();
+ coff.pending_input = null;
+ coff.flushInputMember(pending_iami) catch |err| switch (err) {
+ error.OutOfMemory => return error.OutOfMemory,
+ else => |e| return comp.link_diags.fail(
+ "linker failed to load archive member '{f}{f}': {t}",
+ .{
+ pending_iami.member(coff).iai.path(coff),
+ fmtMemberNameString(name_slice),
+ e,
+ },
+ ),
+ };
+ break :task;
+ }
+ if (coff.exports_complete and coff.global_pending_index < coff.globals.count()) {
+ const gmi: Node.GlobalMapIndex = .wrap(coff.global_pending_index);
const sub_prog_node = coff.synth_prog_node.start(
- gmi.globalName(coff).name.toSlice(coff),
+ gmi.name(coff).toSlice(coff),
0,
);
defer sub_prog_node.end();
- coff.flushGlobal(pt, gmi) catch |err| switch (err) {
+ if (coff.flushGlobal(gmi) catch |err| switch (err) {
else => |e| return e,
error.MappedFileIo => return comp.link_diags.fail(
"linker failed to lower constant: {t}",
.{coff.mf.io_err.?},
),
- };
+ }) coff.global_pending_index += 1;
+ break :task;
+ }
+ if (coff.exports_complete and coff.pending_special_symbol != .none) {
+ coff.pending_special_symbol = coff.flushSpecialSymbol(coff.pending_special_symbol) catch |err|
+ switch (err) {
+ error.OutOfMemory => |e| return e,
+ else => |e| return comp.link_diags.fail(
+ "linker failed to flush special symbols: {t}",
+ .{e},
+ ),
+ };
break :task;
}
var lazy_it = coff.lazy.iterator();
@@ -1824,6 +6046,73 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
};
break :task;
};
+ if (coff.symbol_table.pending_symbol_index < coff.symbol_table.symbols.count()) {
+ defer coff.symbol_table.pending_symbol_index += 1;
+ const si = coff.symbol_table.symbols.keys()[coff.symbol_table.pending_symbol_index];
+ const sym = si.get(coff);
+ const sub_prog_node = coff.idleProgNode(
+ tid,
+ coff.symbol_prog_node,
+ if (sym.ni != .none)
+ coff.getNode(sym.ni)
+ else
+ .{ .import_thunk = sym.gmi },
+ );
+ defer sub_prog_node.end();
+ coff.flushSymbolTableEntry(
+ coff.symbol_table.pending_symbol_index,
+ .{ .zcu = comp.zcu.?, .tid = tid },
+ ) catch |err| switch (err) {
+ error.OutOfMemory => return error.OutOfMemory,
+ else => |e| return comp.link_diags.fail(
+ "linker failed to flush symbol table entry: {t}",
+ .{e},
+ ),
+ };
+ break :task;
+ }
+ }
+
+ if (coff.section_merge_pending_index < coff.section_merges.count()) return true;
+ if (coff.pending_uavs.count() > 0) return true;
+ if (coff.pending_input != null) return true;
+ if (coff.exports_complete and coff.globals.count() > coff.global_pending_index) return true;
+ assert(!coff.exports_complete or coff.inputs_complete);
+ if (coff.exports_complete and coff.pending_special_symbol != .none) return true;
+ for (&coff.lazy.values) |lazy| if (lazy.map.count() > lazy.pending_index) return true;
+ if (coff.symbol_table.pending_symbol_index < coff.symbol_table.symbols.count()) return true;
+ return false;
+}
+
+pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
+ // Idle tasks should not modify create / modify nodes, otherwise the output is not reproducible.
+ coff.mf.nodes_lock.lock();
+ defer coff.mf.nodes_lock.unlock();
+
+ const comp = coff.base.comp;
+ task: {
+ // TODO: Idle task for flushing obj into lib
+ if (coff.input_section_pending_index < coff.input_sections.items.len) {
+ const isi: Node.InputSection.Index = @enumFromInt(coff.input_section_pending_index);
+ coff.input_section_pending_index += 1;
+ const sub_prog_node = coff.idleProgNode(tid, coff.input_prog_node, coff.getNode(isi.symbol(coff).node(coff)));
+ defer sub_prog_node.end();
+ coff.flushInputSection(isi) catch |err| switch (err) {
+ else => |e| {
+ const ioi = isi.input(coff);
+ return comp.link_diags.fail(
+ "linker failed to read input section '{s}' from \"{f}{f}\": {t}",
+ .{
+ isi.symbol(coff).get(coff).section_number.name(coff).toSlice(coff),
+ ioi.path(coff).fmtEscapeString(),
+ fmtMemberNameString(ioi.memberName(coff)),
+ e,
+ },
+ );
+ },
+ };
+ break :task;
+ }
while (coff.mf.updates.pop()) |ni| {
const clean_moved = ni.cleanMoved(&coff.mf);
const clean_resized = ni.cleanResized(&coff.mf);
@@ -1836,11 +6125,33 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
break :task;
} else coff.mf.update_prog_node.completeOne();
}
+ while (coff.pending_members.pop()) |pending_mi| {
+ const sub_prog_node = coff.idleProgNode(
+ tid,
+ coff.symbol_prog_node,
+ coff.getNode(pending_mi.key.get(coff).content_ni),
+ );
+ defer sub_prog_node.end();
+ try coff.flushMember(pending_mi.key);
+ break :task;
+ }
+ if (coff.exports_complete and coff.export_table.pending_sort) {
+ defer coff.export_table.pending_sort = false;
+ const sub_prog_node = coff.idleProgNode(
+ tid,
+ coff.synth_prog_node,
+ coff.getNode(coff.export_table.ni),
+ );
+ defer sub_prog_node.end();
+
+ coff.flushExportsSort();
+ break :task;
+ }
}
- if (coff.pending_uavs.count() > 0) return true;
- if (coff.globals.count() > coff.global_pending_index) return true;
- for (&coff.lazy.values) |lazy| if (lazy.map.count() > lazy.pending_index) return true;
+ if (coff.input_sections.items.len > coff.input_section_pending_index) return true;
if (coff.mf.updates.items.len > 0) return true;
+ if (coff.pending_members.count() > 0) return true;
+ if (coff.exports_complete and coff.export_table.pending_sort) return true;
return false;
}
@@ -1855,7 +6166,15 @@ fn idleProgNode(
else => |tag| @tagName(tag),
.image_section => |si| std.mem.sliceTo(&si.get(coff).section_number.header(coff).name, 0),
inline .pseudo_section, .object_section => |smi| smi.name(coff).toSlice(coff),
- .global => |gmi| gmi.globalName(coff).name.toSlice(coff),
+ .input_section => |isi| {
+ const ioi = isi.input(coff);
+ break :name std.fmt.bufPrint(&name, "{f}{f} {s}", .{
+ ioi.path(coff).fmtEscapeString(),
+ fmtMemberNameString(ioi.memberName(coff)),
+ coff.getNode(isi.symbol(coff).node(coff).parent(&coff.mf)).object_section.name(coff).toSlice(coff),
+ }) catch &name;
+ },
+ .import_thunk => |gmi| gmi.name(coff).toSlice(coff),
.nav => |nmi| {
const ip = &coff.base.comp.zcu.?.intern_pool;
break :name ip.getNav(nmi.navIndex(coff)).fqn.toSlice(ip);
@@ -1866,6 +6185,7 @@ fn idleProgNode(
.tid = tid,
}),
}) catch &name,
+ .archive_member => |mi| &mi.get(coff).headerPtr(coff).name,
}, 0);
}
@@ -1886,9 +6206,10 @@ fn flushUav(
const sec_si = (try coff.objectSectionMapIndex(
.@".rdata",
coff.mf.flags.block_size,
- .{ .read = true },
+ .{ .read = true, .initialized = true },
)).symbol(coff);
try coff.nodes.ensureUnusedCapacity(gpa, 1);
+ if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1);
const sym = si.get(coff);
const ni = try coff.mf.addLastChildNode(gpa, sec_si.node(coff), .{
.alignment = uav_align.toStdMem(),
@@ -1907,6 +6228,9 @@ fn flushUav(
const sym = si.get(coff);
assert(sym.loc_relocs == .none);
sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
+ if (!isImage(coff) and sym.target_relocs != .none)
+ try coff.pendingSymbolTableEntry(si);
+
break :ni sym.ni;
};
@@ -1923,173 +6247,561 @@ fn flushUav(
error.WriteFailed => return nw.err.?,
else => |e| return e,
};
- si.get(coff).size = @intCast(nw.interface.end);
- si.applyLocationRelocs(coff);
+ si.get(coff).extra.size = @intCast(nw.interface.end);
+ try si.applyLocationRelocs(coff);
}
-fn flushGlobal(coff: *Coff, pt: Zcu.PerThread, gmi: Node.GlobalMapIndex) !void {
- const zcu = pt.zcu;
- const comp = zcu.comp;
- const gpa = zcu.gpa;
- const gn = gmi.globalName(coff);
- if (gn.lib_name.toSlice(coff)) |lib_name| {
- const name = gn.name.toSlice(coff);
- try coff.nodes.ensureUnusedCapacity(gpa, 4);
- try coff.symbol_table.ensureUnusedCapacity(gpa, 1);
+fn aliasGlobal(coff: *Coff, gmi: Node.GlobalMapIndex, alias_si: Symbol.Index) !void {
+ const si = gmi.symbol(coff);
+ const sym = si.get(coff);
+ const alias_sym = alias_si.get(coff);
+ assert(sym.section_number == .UNDEFINED);
+ assert(sym.loc_relocs == .none);
+
+ log.debug("aliasGlobal({s}, {?s}) {d}->{d} ({?s})", .{
+ gmi.name(coff).toSlice(coff),
+ gmi.libName(coff).toSlice(coff),
+ si,
+ alias_si,
+ if (alias_sym.gmi != .none) alias_sym.gmi.name(coff).toSlice(coff) else null,
+ });
- const target_endian = coff.targetEndian();
- const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic);
- const addr_size: u64, const addr_align: std.mem.Alignment = switch (magic) {
- _ => unreachable,
- .PE32 => .{ 4, .@"4" },
- .@"PE32+" => .{ 8, .@"8" },
- };
+ var ri = sym.target_relocs;
+ while (ri != .none) {
+ const reloc = ri.get(coff);
+ assert(reloc.target == si);
+ reloc.target = alias_si;
+ if (reloc.next == .none) {
+ reloc.next = alias_sym.target_relocs;
+ if (alias_sym.target_relocs != .none)
+ alias_sym.target_relocs.get(coff).prev = ri;
+ break;
+ }
+ ri = reloc.next;
+ }
- const gop = try coff.import_table.entries.getOrPutAdapted(
- gpa,
- lib_name,
- ImportTable.Adapter{ .coff = coff },
- );
- const import_hint_name_align: std.mem.Alignment = .@"2";
- if (!gop.found_existing) {
- errdefer _ = coff.import_table.entries.pop();
- try coff.import_table.ni.resize(
- &coff.mf,
- gpa,
- @sizeOf(std.coff.ImportDirectoryEntry) * (gop.index + 2),
+ const prev_target_relocs = alias_sym.target_relocs;
+ if (sym.target_relocs != .none)
+ alias_sym.target_relocs = sym.target_relocs;
+ sym.target_relocs = .none;
+ sym.gmi = alias_sym.gmi;
+ coff.globals.values()[gmi.unwrap().?].si = alias_si;
+ // Only apply the new relocs
+ try alias_si.applyTargetRelocs(coff, prev_target_relocs);
+}
+
+fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
+ const comp = coff.base.comp;
+ const gpa = comp.gpa;
+ const name = gmi.name(coff);
+ const si = gmi.symbol(coff);
+
+ log.debug(
+ "flushGlobal({s}, {?s}) = n{d} {d}@{d}",
+ .{
+ name.toSlice(coff),
+ gmi.libName(coff).toSlice(coff),
+ si.get(coff).ni,
+ si,
+ si.get(coff).section_number,
+ },
+ );
+
+ if (!coff.isImage()) {
+ try coff.pendingSymbolTableEntry(si);
+ if (coff.isArchive() and si.get(coff).ni != .none)
+ try coff.ensureMemberSymbol(
+ coff.getNode(Node.known.zcu_member).archive_member,
+ name,
);
- const import_hint_name_table_len =
- import_hint_name_align.forward(lib_name.len + ".dll".len + 1);
- const idata_section_ni = coff.import_table.ni.parent(&coff.mf);
- const import_lookup_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{
- .size = addr_size * 2,
- .alignment = addr_align,
- .moved = true,
- });
- const import_address_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{
- .size = addr_size * 2,
- .alignment = addr_align,
- .moved = true,
- });
- const import_address_table_si = coff.addSymbolAssumeCapacity();
- {
- const import_address_table_sym = import_address_table_si.get(coff);
- import_address_table_sym.ni = import_address_table_ni;
- assert(import_address_table_sym.loc_relocs == .none);
- import_address_table_sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
- import_address_table_sym.section_number =
- coff.getNode(idata_section_ni).object_section.symbol(coff).get(coff).section_number;
+
+ return true;
+ }
+
+ if (si.get(coff).ni != .none)
+ return true;
+
+ const Import = struct {
+ lib_name: String,
+ name: String.Optional,
+ ordinal_hint: u16,
+ kind: enum {
+ iat_ptr,
+ thunk,
+ },
+ };
+
+ const import: Import = import: {
+ const sym = si.get(coff);
+ const name_slice = name.toSlice(coff);
+ const imp_match = std.mem.startsWith(u8, name_slice, imp_prefix);
+
+ // Globals may have the __imp_ prefix already if they are undef externals from another input.
+ assert(sym.flags.dll_storage_class != .dllexport);
+ const search_name, const is_imp = if (imp_match or sym.flags.dll_storage_class != .dllimport)
+ .{ name, imp_match }
+ else name: {
+ try coff.ensureUnusedStringCapacity(imp_prefix.len + name_slice.len);
+ const imp_name = try std.fmt.allocPrint(gpa, imp_prefix ++ "{s}", .{name_slice});
+ defer gpa.free(imp_name);
+ break :name .{ coff.getOrPutStringAssumeCapacity(imp_name), true };
+ };
+
+ const opt_alt_search_name = coff.alternate_names.get(search_name);
+ const search_libs = switch (sym.flags.value_tag) {
+ .weak_alias_si, .weak_alias_name => switch (sym.flags.weak_external_strat) {
+ .none => unreachable,
+ .no_library => false,
+ .library,
+ .alias,
+ => true,
+ .anti_dependency => return comp.link_diags.fail(
+ // TODO: Figure out what the purpose of this is
+ "TODO support anti_dependency weak external: {s}",
+ .{name.toSlice(coff)},
+ ),
+ },
+ else => true,
+ };
+
+ const opt_indices_lists: []const ?InputArchive.SearchList = if (search_libs) &.{
+ coff.input_archive_symbol_indices.get(search_name),
+ if (opt_alt_search_name) |alt| coff.input_archive_symbol_indices.get(alt) else null,
+ } else &.{};
+
+ for (opt_indices_lists) |opt_indices_list| {
+ const indices_list = opt_indices_list orelse continue;
+ var iter: InputArchive.Member.Symbol.Index = indices_list.first;
+ while (true) {
+ const archive_sym = &coff.input_archive_symbols.items[@intFromEnum(iter)];
+ const member = &coff.input_archive_members.items[@intFromEnum(archive_sym.iami)];
+ member: switch (member.content) {
+ .object => if (!member.flags.is_loaded) {
+ if (gmi.libName(coff).unwrap()) |lib_name|
+ if (!std.ascii.eqlIgnoreCase(
+ lib_name.toSlice(coff),
+ member.iai.path(coff).stem(),
+ )) break :member;
+
+ // Try loading the input member and then retry.
+ // This could still be a member containing imports
+ // that use the older non-IMPORT_HEADER method.
+ coff.pending_input = archive_sym.iami;
+ return false;
+ },
+ .import => |import| {
+ if (gmi.libName(coff).unwrap()) |lib_name|
+ if (!std.ascii.eqlIgnoreCase(
+ import.lib_name.toSlice(coff),
+ lib_name.toSlice(coff),
+ )) break :member;
+
+ const imp_name: String.Optional = name: switch (import.name_type) {
+ .NAME,
+ .NAME_NOPREFIX,
+ .NAME_UNDECORATE,
+ => |tag| {
+ const symbol_name: []const u8 = import.symbol_name.toSlice(coff);
+ const end_match = std.mem.endsWith(u8, name_slice, symbol_name);
+ const len_delta = name_slice.len -% symbol_name.len;
+ if (!end_match or
+ (!imp_match and len_delta != 0) or
+ (imp_match and len_delta != imp_prefix.len))
+ return comp.link_diags.fail(
+ "global '{s}' has mismatched symbol name in import header: '{s}'",
+ .{
+ name.toSlice(coff),
+ import.symbol_name.toSlice(coff),
+ },
+ );
+
+ const imp_name = if (tag == .NAME) import.symbol_name else undecorated: {
+ var imp_name = std.mem.trimStart(u8, symbol_name, "?@_");
+ if (tag == .NAME_UNDECORATE)
+ imp_name = std.mem.sliceTo(imp_name, '@');
+
+ try coff.ensureUnusedStringCapacity(imp_name.len);
+ break :undecorated coff.getOrPutStringAssumeCapacity(imp_name);
+ };
+
+ break :name imp_name.toOptional();
+ },
+ .ORDINAL => break :name .none,
+ else => |t| return comp.link_diags.fail("TODO handle name_type {t}", .{t}),
+ };
+
+ break :import .{
+ .lib_name = import.lib_name,
+ .name = imp_name,
+ .ordinal_hint = import.import_ordinal_hint,
+ .kind = if (import.type == .CODE and !is_imp) .thunk else .iat_ptr,
+ };
+ },
+ }
+
+ if (archive_sym.next == iter) break;
+ iter = archive_sym.next;
}
- const import_hint_name_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{
- .size = import_hint_name_table_len,
- .alignment = import_hint_name_align,
- .moved = true,
- });
- gop.value_ptr.* = .{
- .import_lookup_table_ni = import_lookup_table_ni,
- .import_address_table_si = import_address_table_si,
- .import_hint_name_table_ni = import_hint_name_table_ni,
- .len = 0,
- .hint_name_len = @intCast(import_hint_name_table_len),
+ }
+
+ switch (sym.flags.value_tag) {
+ .weak_alias_si => {
+ try coff.aliasGlobal(gmi, sym.value.weak_alias_si);
+ return true;
+ },
+ .weak_alias_name => {
+ // Convert an unresolved weak external that itself refers to an undef external
+ // into a (possibly new) global, so it can be resolved separately.
+ const alias_gop = try coff.getOrPutGlobalSymbol(.{
+ .name = sym.value.weak_alias_name.toSlice(coff),
+ });
+ try coff.aliasGlobal(gmi, alias_gop.value_ptr.si);
+ return true;
+ },
+ else => {},
+ }
+
+ // If there was an object that had the alternate name, we've attempted to load it
+ if (opt_alt_search_name) |alt_search_name| {
+ if (coff.globals.get(alt_search_name)) |alias_global| {
+ try coff.aliasGlobal(gmi, alias_global.si);
+ return true;
+ }
+ }
+
+ // Allow importing symbols with no implib entry, if a lib_name was specified.
+ // This is necessary for certain ntdll symbols, such as LdrRegisterDllNotification,
+ // which are not in the implib.
+ if (sym.flags.type != .unknown) {
+ if (gmi.libName(coff).unwrap()) |lib_name| break :import .{
+ .lib_name = lib_name,
+ .name = name.toOptional(),
+ .ordinal_hint = 0,
+ .kind = if (sym.flags.type == .code) .thunk else .iat_ptr,
};
- const import_hint_name_slice = import_hint_name_table_ni.slice(&coff.mf);
- @memcpy(import_hint_name_slice[0..lib_name.len], lib_name);
- @memcpy(import_hint_name_slice[lib_name.len..][0..".dll".len], ".dll");
- @memset(import_hint_name_slice[lib_name.len + ".dll".len ..], 0);
- coff.nodes.appendAssumeCapacity(.{ .import_lookup_table = @enumFromInt(gop.index) });
- coff.nodes.appendAssumeCapacity(.{ .import_address_table = @enumFromInt(gop.index) });
- coff.nodes.appendAssumeCapacity(.{ .import_hint_name_table = @enumFromInt(gop.index) });
-
- const import_directory_entries = coff.importDirectoryTableSlice()[gop.index..][0..2];
- import_directory_entries.* = .{ .{
- .import_lookup_table_rva = coff.computeNodeRva(import_lookup_table_ni),
- .time_date_stamp = 0,
- .forwarder_chain = 0,
- .name_rva = coff.computeNodeRva(import_hint_name_table_ni),
- .import_address_table_rva = coff.computeNodeRva(import_address_table_ni),
- }, .{
- .import_lookup_table_rva = 0,
- .time_date_stamp = 0,
- .forwarder_chain = 0,
- .name_rva = 0,
- .import_address_table_rva = 0,
- } };
- if (target_endian != native_endian)
- std.mem.byteSwapAllFields([2]std.coff.ImportDirectoryEntry, import_directory_entries);
}
+
+ return true;
+ };
+
+ try coff.nodes.ensureUnusedCapacity(gpa, 4);
+ try coff.symbols.ensureUnusedCapacity(gpa, 2);
+
+ const target_endian = coff.targetEndian();
+ const addr_info = coff.targetAddrInfo();
+ const lib_name = import.lib_name.toSlice(coff);
+ const gop = try coff.import_table.entries.getOrPutAdapted(
+ gpa,
+ lib_name,
+ ImportTable.Adapter{ .coff = coff },
+ );
+ const import_hint_name_align: std.mem.Alignment = .@"2";
+ if (!gop.found_existing) {
+ errdefer _ = coff.import_table.entries.pop();
+ try coff.import_table.ni.resize(
+ &coff.mf,
+ gpa,
+ @sizeOf(std.coff.ImportDirectoryEntry) * (gop.index + 2),
+ );
+ const import_hint_name_table_len =
+ import_hint_name_align.forward(lib_name.len + ".dll".len + 1);
+ const idata_section_ni = coff.import_table.ni.parent(&coff.mf);
+ const import_lookup_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{
+ .size = addr_info.size * 2,
+ .alignment = addr_info.alignment,
+ .moved = true,
+ });
+ const import_address_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{
+ .size = addr_info.size * 2,
+ .alignment = addr_info.alignment,
+ .moved = true,
+ });
+ const import_address_table_si = coff.addSymbolAssumeCapacity();
+ {
+ const import_address_table_sym = import_address_table_si.get(coff);
+ import_address_table_sym.ni = import_address_table_ni;
+ assert(import_address_table_sym.loc_relocs == .none);
+ import_address_table_sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
+ import_address_table_sym.section_number =
+ coff.getNode(idata_section_ni).object_section.symbol(coff).get(coff).section_number;
+ }
+ const import_hint_name_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{
+ .size = import_hint_name_table_len,
+ .alignment = import_hint_name_align,
+ .moved = true,
+ });
+ gop.value_ptr.* = .{
+ .import_lookup_table_ni = import_lookup_table_ni,
+ .import_address_table_si = import_address_table_si,
+ .import_hint_name_table_ni = import_hint_name_table_ni,
+ .import_address_table_symbols = .empty,
+ .len = 0,
+ .hint_name_len = @intCast(import_hint_name_table_len),
+ };
+ const import_hint_name_slice = import_hint_name_table_ni.slice(&coff.mf);
+ @memcpy(import_hint_name_slice[0..lib_name.len], lib_name);
+ @memcpy(import_hint_name_slice[lib_name.len..][0..".dll".len], ".dll");
+ @memset(import_hint_name_slice[lib_name.len + ".dll".len ..], 0);
+ coff.nodes.appendAssumeCapacity(.{ .import_lookup_table = @enumFromInt(gop.index) });
+ coff.nodes.appendAssumeCapacity(.{ .import_address_table = @enumFromInt(gop.index) });
+ coff.nodes.appendAssumeCapacity(.{ .import_hint_name_table = @enumFromInt(gop.index) });
+
+ const import_directory_entries = coff.importDirectoryTableSlice()[gop.index..][0..2];
+ import_directory_entries.* = .{ .{
+ .import_lookup_table_rva = coff.computeNodeRva(import_lookup_table_ni),
+ .time_date_stamp = 0,
+ .forwarder_chain = 0,
+ .name_rva = coff.computeNodeRva(import_hint_name_table_ni),
+ .import_address_table_rva = coff.computeNodeRva(import_address_table_ni),
+ }, .{
+ .import_lookup_table_rva = 0,
+ .time_date_stamp = 0,
+ .forwarder_chain = 0,
+ .name_rva = 0,
+ .import_address_table_rva = 0,
+ } };
+ if (target_endian != native_endian)
+ std.mem.byteSwapAllFields([2]std.coff.ImportDirectoryEntry, import_directory_entries);
+ }
+
+ log.debug(
+ "flushGlobalImport({s}, {?s}, {d}, {s})",
+ .{ name.toSlice(coff), import.name.toSlice(coff), import.ordinal_hint, lib_name },
+ );
+
+ const iat_symbol_gop = try coff.import_table.iat_symbol_indices.getOrPut(gpa, .{
+ .iti = @enumFromInt(gop.index),
+ .name = import.name,
+ .ordinal_hint = import.ordinal_hint,
+ });
+ if (!iat_symbol_gop.found_existing) {
const import_symbol_index = gop.value_ptr.len;
+ iat_symbol_gop.value_ptr.* = import_symbol_index;
+
gop.value_ptr.len = import_symbol_index + 1;
- const new_symbol_table_size = addr_size * (import_symbol_index + 2);
- const import_hint_name_index = gop.value_ptr.hint_name_len;
- gop.value_ptr.hint_name_len = @intCast(
- import_hint_name_align.forward(import_hint_name_index + 2 + name.len + 1),
- );
+ const new_symbol_table_size = addr_info.size * (import_symbol_index + 2);
+
try gop.value_ptr.import_lookup_table_ni.resize(&coff.mf, gpa, new_symbol_table_size);
const import_address_table_ni = gop.value_ptr.import_address_table_si.node(coff);
try import_address_table_ni.resize(&coff.mf, gpa, new_symbol_table_size);
- try gop.value_ptr.import_hint_name_table_ni.resize(&coff.mf, gpa, gop.value_ptr.hint_name_len);
+
+ const opt_imp_name = import.name.toSlice(coff);
+ const opt_import_hint_name_index = if (opt_imp_name) |imp_name| blk: {
+ const import_hint_name_index = gop.value_ptr.hint_name_len;
+ gop.value_ptr.hint_name_len = @intCast(
+ import_hint_name_align.forward(import_hint_name_index + 2 + imp_name.len + 1),
+ );
+ try gop.value_ptr.import_hint_name_table_ni.resize(&coff.mf, gpa, gop.value_ptr.hint_name_len);
+ break :blk import_hint_name_index;
+ } else null;
+
+ const import_hint_name_rva = if (opt_import_hint_name_index) |import_hint_name_index| blk: {
+ const import_hint_name_slice = gop.value_ptr.import_hint_name_table_ni.slice(&coff.mf);
+ const ordinal_hint: *u16 = @ptrCast(@alignCast(import_hint_name_slice[import_hint_name_index..][0..2]));
+ ordinal_hint.* = std.mem.nativeTo(u16, import.ordinal_hint, target_endian);
+ @memcpy(import_hint_name_slice[import_hint_name_index + 2 ..][0..opt_imp_name.?.len], opt_imp_name.?);
+ @memset(import_hint_name_slice[import_hint_name_index + 2 + opt_imp_name.?.len ..], 0);
+ break :blk coff.computeNodeRva(gop.value_ptr.import_hint_name_table_ni) + import_hint_name_index;
+ } else 0;
+
const import_lookup_slice = gop.value_ptr.import_lookup_table_ni.slice(&coff.mf);
const import_address_slice = import_address_table_ni.slice(&coff.mf);
- const import_hint_name_slice = gop.value_ptr.import_hint_name_table_ni.slice(&coff.mf);
- @memset(import_hint_name_slice[import_hint_name_index..][0..2], 0);
- @memcpy(import_hint_name_slice[import_hint_name_index + 2 ..][0..name.len], name);
- @memset(import_hint_name_slice[import_hint_name_index + 2 + name.len ..], 0);
- const import_hint_name_rva =
- coff.computeNodeRva(gop.value_ptr.import_hint_name_table_ni) + import_hint_name_index;
- switch (magic) {
+ switch (addr_info.magic) {
_ => unreachable,
inline .PE32, .@"PE32+" => |ct_magic| {
- const Addr = switch (ct_magic) {
- _ => comptime unreachable,
- .PE32 => u32,
- .@"PE32+" => u64,
- };
- const import_lookup_table: []Addr = @ptrCast(@alignCast(import_lookup_slice));
- const import_address_table: []Addr = @ptrCast(@alignCast(import_address_slice));
- const import_hint_name_rvas: [2]Addr = .{
- std.mem.nativeTo(Addr, @intCast(import_hint_name_rva), target_endian),
- std.mem.nativeTo(Addr, 0, target_endian),
+ const Entry = std.coff.ImportLookupTableEntry(ct_magic);
+ const import_lookup_table: []Entry = @ptrCast(@alignCast(import_lookup_slice));
+ const import_address_table: []Entry = @ptrCast(@alignCast(import_address_slice));
+ var import_hint_name_rvas: [2]Entry = .{
+ .{
+ .payload = if (import.name == .none)
+ .{ .ordinal = .{ .ordinal = import.ordinal_hint } }
+ else
+ .{ .hint_name_rva = @intCast(import_hint_name_rva) },
+ .is_ordinal = import.name == .none,
+ },
+ @bitCast(@as(@typeInfo(Entry).@"struct".backing_integer.?, 0)),
};
+ if (native_endian != target_endian)
+ for (&import_hint_name_rvas) |*v| std.mem.byteSwapAllFields(Entry, v);
+
import_lookup_table[import_symbol_index..][0..2].* = import_hint_name_rvas;
import_address_table[import_symbol_index..][0..2].* = import_hint_name_rvas;
},
}
- const si = gmi.symbol(coff);
- const sym = si.get(coff);
- sym.section_number = Symbol.Index.text.get(coff).section_number;
- assert(sym.loc_relocs == .none);
- sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
- switch (coff.targetLoad(&coff.headerPtr().machine)) {
- else => |tag| @panic(@tagName(tag)),
- .AMD64 => {
- const init = [_]u8{ 0xff, 0x25, 0x00, 0x00, 0x00, 0x00 };
- const target = &comp.root_mod.resolved_target.result;
- const ni = try coff.mf.addLastChildNode(gpa, Symbol.Index.text.node(coff), .{
- .alignment = switch (comp.root_mod.optimize_mode) {
- .Debug,
- .ReleaseSafe,
- .ReleaseFast,
- => target_util.defaultFunctionAlignment(target),
- .ReleaseSmall => target_util.minFunctionAlignment(target),
- }.toStdMem(),
- .size = init.len,
- });
- @memcpy(ni.slice(&coff.mf)[0..init.len], &init);
- sym.ni = ni;
- sym.size = init.len;
+ }
+
+ const sym = si.get(coff);
+ assert(sym.loc_relocs == .none);
+ const iat_offset: u32 = @intCast(addr_info.size * iat_symbol_gop.value_ptr.*);
+ switch (import.kind) {
+ .iat_ptr => {
+ const iat_sym = gop.value_ptr.import_address_table_si.get(coff);
+ sym.section_number = iat_sym.section_number;
+ sym.ni = iat_sym.ni;
+ sym.setValue(.{ .node_offset = iat_offset });
+ (try gop.value_ptr.import_address_table_symbols.addOne(gpa)).* = si;
+ },
+ .thunk => {
+ sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
+
+ const target = &comp.root_mod.resolved_target.result;
+ const alignment = switch (comp.root_mod.optimize_mode) {
+ .Debug,
+ .ReleaseSafe,
+ .ReleaseFast,
+ => target_util.defaultFunctionAlignment(target),
+ .ReleaseSmall => target_util.minFunctionAlignment(target),
+ }.toStdMem();
+ const parent_si = (try coff.pseudoSectionMapIndex(
+ .@".thunks",
+ alignment,
+ .{ .execute = true, .read = true },
+ )).symbol(coff);
+
+ const parent_sym = parent_si.get(coff);
+ sym.section_number = parent_sym.section_number;
+
+ switch (coff.targetLoad(&coff.headerPtr().machine)) {
+ else => |tag| @panic(@tagName(tag)),
+ .AMD64 => {
+ const init = [_]u8{ 0xff, 0x25, 0x00, 0x00, 0x00, 0x00 };
+ const ni = try coff.mf.addLastChildNode(gpa, parent_sym.ni, .{
+ .alignment = alignment,
+ .size = init.len,
+ });
+ @memcpy(ni.slice(&coff.mf)[0..init.len], &init);
+ sym.ni = ni;
+ sym.extra.size = init.len;
+ try coff.addReloc(
+ si,
+ init.len - 4,
+ gop.value_ptr.import_address_table_si,
+ .{ .known = iat_offset },
+ .{ .AMD64 = .REL32 },
+ );
+ },
+ }
+ coff.nodes.appendAssumeCapacity(.{ .import_thunk = gmi });
+ },
+ }
+
+ try si.flushMoved(coff);
+ return true;
+}
+
+fn flushSpecialSymbol(coff: *Coff, pending: SpecialSymbol) !SpecialSymbol {
+ const comp = coff.base.comp;
+
+ if (!coff.isImage()) return .none;
+ const gpa = comp.gpa;
+ const machine = coff.targetLoad(&coff.headerPtr().machine);
+ const target = &comp.root_mod.resolved_target.result;
+
+ return next: switch (pending) {
+ .entry => {
+ // TODO: Use explicitly specified entry if set, add err if not found
+ const entries: []const struct { ?[]const u8, []const u8 } = if (coff.isExe())
+ if (comp.config.link_libc) switch (coff.optionalHeaderField(.subsystem)) {
+ .WINDOWS_CUI => &.{
+ .{ "main", "mainCRTStartup" },
+ .{ "wmain", "wmainCRTStartup" },
+ },
+ .WINDOWS_GUI => &.{
+ .{ "WinMain", "WinMainCRTStartup" },
+ .{ "wWinMain", "wWinMainCRTStartup" },
+ },
+ else => unreachable,
+ } else &.{
+ .{ "wWinMainCRTStartup", "wWinMainCRTStartup" },
+ }
+ else
+ &.{.{ null, if (target.abi.isGnu()) "DllMainCRTStartup" else "_DllMainCRTStartup" }};
+
+ const entry_si = for (entries) |entry| {
+ if (entry[0]) |required_name|
+ if (coff.getDefinedGlobal(required_name) == .null) continue;
+
+ break try coff.globalSymbol(.{ .name = entry[1], .type = .code });
+ } else .null;
+
+ if (entry_si != .null) {
+ log.debug(
+ "entry({s}, {d})",
+ .{ entry_si.get(coff).gmi.name(coff).toSlice(coff), entry_si },
+ );
+
+ try coff.symbols.ensureUnusedCapacity(gpa, 1);
+ const optional_hdr_si = coff.addSymbolAssumeCapacity();
+ const optional_hdr_sym = optional_hdr_si.get(coff);
+ optional_hdr_sym.ni = Node.known.optional_header;
+ assert(optional_hdr_sym.loc_relocs == .none);
+ optional_hdr_sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
+
+ const optional_hdr = coff.optionalHeaderStandardPtr();
+ optional_hdr.address_of_entry_point = std.mem.nativeTo(
+ u32,
+ entry_si.get(coff).rva,
+ coff.targetEndian(),
+ );
+
+ try coff.addReloc(
+ optional_hdr_si,
+ @intFromPtr(&optional_hdr.address_of_entry_point) - @intFromPtr(optional_hdr),
+ entry_si,
+ .{ .known = 0 },
+ switch (machine) {
+ else => |tag| @panic(@tagName(tag)),
+ .AMD64 => .{ .AMD64 = .ADDR32NB },
+ .I386 => .{ .I386 = .DIR32NB },
+ },
+ );
+ }
+
+ // Referencing the startup functions may trigger loading the object containing them,
+ // we need to wait until that is done before looking for further symbols.
+ break :next .tls;
+ },
+ .tls => {
+ if (coff.getDefinedGlobal("_tls_used").unwrap()) |tls_used_si| {
+ log.debug("tlsDir({d})", .{tls_used_si});
+
+ const tls_directory = coff.dataDirectoryPtr(.TLS);
+ tls_directory.* = .{
+ .virtual_address = tls_used_si.get(coff).rva,
+ .size = switch (coff.targetLoad(&coff.optionalHeaderStandardPtr().magic)) {
+ _ => unreachable,
+ .PE32 => 24,
+ .@"PE32+" => 40,
+ },
+ };
+ if (coff.targetEndian() != native_endian)
+ std.mem.byteSwapAllFields(std.coff.ImageDataDirectory, tls_directory);
+
+ try coff.symbols.ensureUnusedCapacity(gpa, 1);
+ const data_dir_si = coff.addSymbolAssumeCapacity();
+ const data_dir_sym = data_dir_si.get(coff);
+ data_dir_sym.ni = Node.known.data_directories;
+ assert(data_dir_sym.loc_relocs == .none);
+ data_dir_sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
+
try coff.addReloc(
- si,
- init.len - 4,
- gop.value_ptr.import_address_table_si,
- @intCast(addr_size * import_symbol_index),
- .{ .AMD64 = .REL32 },
+ data_dir_si,
+ @intFromPtr(&tls_directory.virtual_address) - @intFromPtr(coff.dataDirectorySlice().ptr),
+ tls_used_si,
+ .{ .known = 0 },
+ switch (machine) {
+ else => |tag| @panic(@tagName(tag)),
+ .AMD64 => .{ .AMD64 = .ADDR32NB },
+ .I386 => .{ .I386 = .DIR32NB },
+ },
);
- },
- }
- coff.nodes.appendAssumeCapacity(.{ .global = gmi });
- sym.rva = coff.computeNodeRva(sym.ni);
- si.applyLocationRelocs(coff);
- }
+ }
+
+ break :next .none;
+ },
+ .none => unreachable,
+ };
}
fn flushLazy(coff: *Coff, pt: Zcu.PerThread, lmr: Node.LazyMapRef) !void {
@@ -2119,6 +6831,9 @@ fn flushLazy(coff: *Coff, pt: Zcu.PerThread, lmr: Node.LazyMapRef) !void {
}
assert(sym.loc_relocs == .none);
sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
+ if (!isImage(coff) and sym.target_relocs != .none)
+ try coff.pendingSymbolTableEntry(si);
+
break :ni sym.ni;
};
@@ -2138,42 +6853,107 @@ fn flushLazy(coff: *Coff, pt: Zcu.PerThread, lmr: Node.LazyMapRef) !void {
error.WriteFailed => return nw.err.?,
else => |e| return e,
};
- si.get(coff).size = @intCast(nw.interface.end);
- si.applyLocationRelocs(coff);
+ si.get(coff).extra.size = @intCast(nw.interface.end);
+ try si.applyLocationRelocs(coff);
}
fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void {
+ log.debug("flushMoved({s}, n{d})", .{ @tagName(coff.getNode(ni)), ni });
switch (coff.getNode(ni)) {
.file,
.header,
.signature,
+ => unreachable,
.coff_header,
.optional_header,
.data_directories,
.section_table,
- => unreachable,
- .image_section => |si| return coff.targetStore(
- &si.get(coff).section_number.header(coff).pointer_to_raw_data,
- @intCast(ni.fileLocation(&coff.mf, false).offset),
- ),
- .import_directory_table => coff.targetStore(
- &coff.dataDirectoryPtr(.IMPORT).virtual_address,
- coff.computeNodeRva(ni),
- ),
+ .placeholder,
+ => assert(!coff.isImage()),
+ .symbol_table,
+ .string_table,
+ => |_, tag| {
+ if (tag == .symbol_table)
+ coff.targetStore(
+ &coff.headerPtr().pointer_to_symbol_table,
+ @intCast(ni.location(&coff.mf).resolve(&coff.mf)[0]),
+ );
+
+ if (!coff.symbol_table.pending_shrink) {
+ const symbol_table_loc, const symbol_table_size = coff.symbol_table.ni.location(&coff.mf).resolve(&coff.mf);
+ const string_table_offset, _ = coff.symbol_table.strings_ni.location(&coff.mf).resolve(&coff.mf);
+ coff.symbol_table.pending_shrink = string_table_offset - (symbol_table_loc + symbol_table_size) > 0;
+ }
+ },
+ .relocation_table => |sn| {
+ coff.targetStore(
+ &sn.header(coff).pointer_to_relocations,
+ @intCast(ni.location(&coff.mf).resolve(&coff.mf)[0]),
+ );
+ },
+ .relocation_table_entry => {},
+ .archive_member_header => |mi| {
+ const member = mi.get(coff);
+ switch (member.kind) {
+ .first_linker, .second_linker, .longnames => {},
+ else => coff.targetStore(
+ &coff.secondLinkerMemberOffsetsSlice()[@intFromEnum(mi) - Member.Index.known_count],
+ @intCast(ni.fileLocation(&coff.mf, false).offset),
+ ),
+ }
+
+ if (member.kind == .coff)
+ try coff.pending_members.put(coff.base.comp.gpa, mi, {});
+ },
+ .archive_member,
+ => {},
+ .image_section => |si| {
+ const sym = si.get(coff);
+ const flags = coff.targetLoad(&sym.section_number.header(coff).flags);
+ if (!flags.CNT_UNINITIALIZED_DATA) {
+ const file_offset = if (isArchive(coff))
+ sym.ni.location(&coff.mf).resolve(&coff.mf)[0]
+ else
+ ni.fileLocation(&coff.mf, false).offset;
+
+ return coff.targetStore(
+ &sym.section_number.header(coff).pointer_to_raw_data,
+ @intCast(file_offset),
+ );
+ }
+ },
+ .input_section => |isi| {
+ try isi.symbol(coff).flushMoved(coff);
+ for (coff.input_symbols.items[@intFromEnum(isi.firstSymbol(coff))..]) |input_symbol| {
+ if (input_symbol.si.get(coff).ni != ni) break;
+ try input_symbol.si.flushMoved(coff);
+ }
+ },
+ .import_directory_table => {
+ _, const size = ni.location(&coff.mf).resolve(&coff.mf);
+ if (size > 0)
+ coff.targetStore(
+ &coff.dataDirectoryPtr(.IMPORT).virtual_address,
+ coff.computeNodeRva(ni),
+ );
+ },
.import_lookup_table => |import_index| coff.targetStore(
&coff.importDirectoryEntryPtr(import_index).import_lookup_table_rva,
coff.computeNodeRva(ni),
),
.import_address_table => |import_index| {
- const import_address_table_si = import_index.get(coff).import_address_table_si;
- import_address_table_si.flushMoved(coff);
+ const entry = import_index.get(coff);
+ const import_address_table_si = entry.import_address_table_si;
+ try import_address_table_si.flushMoved(coff);
coff.targetStore(
&coff.importDirectoryEntryPtr(import_index).import_address_table_rva,
import_address_table_si.get(coff).rva,
);
+
+ for (entry.import_address_table_symbols.items) |iat_ptr_si|
+ try iat_ptr_si.flushMoved(coff);
},
.import_hint_name_table => |import_index| {
- const target_endian = coff.targetEndian();
const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic);
const import_hint_name_rva = coff.computeNodeRva(ni);
coff.targetStore(
@@ -2186,78 +6966,173 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void {
import_entry.import_address_table_si.node(coff).slice(&coff.mf);
const import_hint_name_slice = ni.slice(&coff.mf);
const import_hint_name_align = ni.alignment(&coff.mf);
+
var import_hint_name_index: u32 = 0;
for (0..import_entry.len) |import_symbol_index| {
- import_hint_name_index = @intCast(import_hint_name_align.forward(
- std.mem.indexOfScalarPos(
- u8,
- import_hint_name_slice,
- import_hint_name_index,
- 0,
- ).? + 1,
- ));
switch (magic) {
_ => unreachable,
inline .PE32, .@"PE32+" => |ct_magic| {
- const Addr = switch (ct_magic) {
- _ => comptime unreachable,
- .PE32 => u32,
- .@"PE32+" => u64,
- };
- const import_lookup_table: []Addr = @ptrCast(@alignCast(import_lookup_slice));
- const import_address_table: []Addr = @ptrCast(@alignCast(import_address_slice));
- const rva = std.mem.nativeTo(
- Addr,
- import_hint_name_rva + import_hint_name_index,
- target_endian,
- );
- import_lookup_table[import_symbol_index] = rva;
- import_address_table[import_symbol_index] = rva;
+ const Entry = std.coff.ImportLookupTableEntry(ct_magic);
+ const import_lookup_table: []Entry = @ptrCast(@alignCast(import_lookup_slice));
+ const import_address_table: []Entry = @ptrCast(@alignCast(import_address_slice));
+
+ var entry = coff.targetLoad(&import_lookup_table[import_symbol_index]);
+ if (entry.is_ordinal)
+ continue;
+
+ import_hint_name_index = @intCast(import_hint_name_align.forward(
+ std.mem.indexOfScalarPos(
+ u8,
+ import_hint_name_slice,
+ import_hint_name_index,
+ 0,
+ ).? + 1,
+ ));
+
+ entry.payload.hint_name_rva = @intCast(import_hint_name_rva + import_hint_name_index);
+ import_hint_name_index += 2;
+
+ coff.targetStore(&import_lookup_table[import_symbol_index], entry);
+ coff.targetStore(&import_address_table[import_symbol_index], entry);
},
}
- import_hint_name_index += 2;
+ }
+ },
+ .export_directory_table => {
+ const rva = coff.computeNodeRva(ni);
+ coff.targetStore(&coff.dataDirectoryPtr(.EXPORT).virtual_address, rva);
+ coff.targetStore(&coff.exportDirectoryTable().name_rva, rva + @sizeOf(std.coff.ExportDirectoryTable));
+ },
+ .export_address_table => {
+ try coff.export_table.export_address_table_si.flushMoved(coff);
+
+ // These relocs are applied directly here instead of via the above flushMoved call as
+ // they are non-contiguous, and not tracked under export_address_table_si.
+ for (coff.export_table.entries.values()) |entry|
+ try entry.export_address_table_ri.get(coff).apply(coff);
+
+ coff.targetStore(
+ &coff.exportDirectoryTable().export_address_table_rva,
+ coff.computeNodeRva(ni),
+ );
+ },
+ .export_name_pointer_table => coff.targetStore(
+ &coff.exportDirectoryTable().name_pointer_table_rva,
+ coff.computeNodeRva(ni),
+ ),
+ .export_ordinal_table => coff.targetStore(
+ &coff.exportDirectoryTable().ordinal_table_rva,
+ coff.computeNodeRva(ni),
+ ),
+ .export_name_table => {
+ const name_table_rva = coff.computeNodeRva(coff.export_table.name_table_ni);
+ for (
+ coff.exportNamePointerTableSlice(),
+ coff.exportOrdinalTableSlice(),
+ ) |*np, target_ord| {
+ const ord: ExportTable.Ordinal = @enumFromInt(coff.targetLoad(&target_ord.unbiased_ordinal));
+ const entry = ord.get(coff);
+ coff.targetStore(
+ &np.name_rva,
+ @intCast(name_table_rva + entry.name_index),
+ );
}
},
inline .pseudo_section,
.object_section,
- .global,
+ .import_thunk,
.nav,
.uav,
.lazy_code,
.lazy_const_data,
- => |mi| mi.symbol(coff).flushMoved(coff),
+ => |mi| try mi.symbol(coff).flushMoved(coff),
+ .builtin => |si| try si.flushMoved(coff),
}
try ni.childrenMoved(coff.base.comp.gpa, &coff.mf);
}
fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void {
- _, const size = ni.location(&coff.mf).resolve(&coff.mf);
+ const offset, const size = ni.location(&coff.mf).resolve(&coff.mf);
+ log.debug("flushResized({s}, n{d}, 0x{x})", .{ @tagName(coff.getNode(ni)), ni, size });
+
switch (coff.getNode(ni)) {
- .file => {},
+ .file => {
+ if (coff.isArchive() and coff.members.items.len > 0) {
+ const last_member = coff.members.items[coff.members.items.len - 1];
+ // See .archive_member branch for reasoning
+ assert(Node.known.file.reverseChildren(&coff.mf).ni == last_member.content_ni);
+ try coff.flushResized(last_member.content_ni);
+ }
+ },
.header => {
- switch (coff.optionalHeaderPtr()) {
- inline else => |optional_header| coff.targetStore(
- &optional_header.size_of_headers,
- @intCast(size),
- ),
+ if (coff.isImage()) {
+ switch (coff.optionalHeaderPtr()) {
+ inline else => |optional_header| coff.targetStore(
+ &optional_header.size_of_headers,
+ @intCast(size),
+ ),
+ }
+
+ if (size > coff.section_table.values()[0].si.get(coff).rva) try coff.virtualSlide(
+ 0,
+ std.mem.alignForward(
+ u32,
+ @intCast(size * 4),
+ coff.optionalHeaderField(.section_alignment),
+ ),
+ );
}
- if (size > coff.image_section_table.items[0].get(coff).rva) try coff.virtualSlide(
- 0,
- std.mem.alignForward(
- u32,
- @intCast(size * 4),
- coff.optionalHeaderField(.section_alignment),
- ),
- );
},
- .signature, .coff_header, .optional_header, .data_directories => unreachable,
+ .signature,
+ .archive_member_header,
+ => unreachable,
+ .archive_member => |mi| {
+ const content_ni = mi.get(coff).content_ni;
+ const next_ni = content_ni.next(&coff.mf);
+ const content_offset, _ = content_ni.location(&coff.mf).resolve(&coff.mf);
+ const next_offset = switch (next_ni) {
+ .none => offset: {
+ assert(content_ni.parent(&coff.mf) == Node.known.file);
+ // This must take into account the final file size. If there are trailing
+ // bytes, they will be expected to contain another valid member header
+ break :offset coff.mf.memory_map.memory.len;
+ },
+ else => offset: {
+ assert(coff.getNode(next_ni) == .archive_member_header);
+ break :offset next_ni.location(&coff.mf).resolve(&coff.mf)[0];
+ },
+ };
+
+ // Not inserting IMAGE_ARCHIVE_PAD `\n` byte here, because we are expanding to full size
+ Member.storeHeaderDecimalStr(&mi.get(coff).headerPtr(coff).size, next_offset - content_offset);
+ },
+ .coff_header,
+ .optional_header,
+ .data_directories,
+ => unreachable,
.section_table => {},
+ .symbol_table => {
+ assert(!coff.isImage());
+ if (!coff.symbol_table.pending_shrink) {
+ const string_table_offset, _ = coff.symbol_table.strings_ni.location(&coff.mf).resolve(&coff.mf);
+ coff.symbol_table.pending_shrink =
+ size > coff.targetLoad(&coff.headerPtr().number_of_symbols) * std.coff.Symbol.sizeOf() or
+ string_table_offset - (offset + size) > 0;
+ }
+ },
+ .string_table => {
+ assert(!coff.isImage());
+ coff.targetStore(coff.symbolTableStringLenPtr(), @intCast(size));
+ },
+ .relocation_table,
+ .relocation_table_entry,
+ => assert(!coff.isImage()),
.image_section => |si| {
const sym = si.get(coff);
const section_index = sym.section_number.toIndex();
const section = &coff.sectionTableSlice()[section_index];
coff.targetStore(§ion.size_of_raw_data, @intCast(size));
- if (size > coff.targetLoad(§ion.virtual_size)) {
+ if (coff.isImage() and size > coff.targetLoad(§ion.virtual_size)) {
const virtual_size = std.mem.alignForward(
u32,
@intCast(size * 4),
@@ -2266,29 +7141,221 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void {
coff.targetStore(§ion.virtual_size, virtual_size);
try coff.virtualSlide(section_index + 1, sym.rva + virtual_size);
}
+
+ if (!coff.isImage()) {
+ if (coff.symbolTableSectionAuxEntryPtr(si.sti(coff))) |aux_ptr|
+ coff.targetStore(&aux_ptr.length, @intCast(size));
+ }
},
- .import_directory_table => coff.targetStore(
- &coff.dataDirectoryPtr(.IMPORT).size,
- @intCast(size),
- ),
- .import_lookup_table, .import_address_table, .import_hint_name_table => {},
+ .input_section => {},
+ .import_directory_table => {
+ const prev_size = coff.targetLoad(&coff.dataDirectoryPtr(.IMPORT).size);
+ coff.targetStore(
+ &coff.dataDirectoryPtr(.IMPORT).size,
+ @intCast(size),
+ );
+ if (prev_size == 0) try coff.flushMoved(ni);
+ },
+ .import_lookup_table,
+ .import_address_table,
+ .import_hint_name_table,
+ => {},
+ .export_directory_table => unreachable,
+ .export_address_table,
+ .export_name_pointer_table,
+ .export_ordinal_table,
+ .export_name_table,
+ => {},
inline .pseudo_section,
.object_section,
- => |smi| smi.symbol(coff).get(coff).size = @intCast(size),
- .global, .nav, .uav, .lazy_code, .lazy_const_data => {},
+ => |smi, tag| {
+ if (tag == .pseudo_section and smi.name(coff) == .@".edata") {
+ coff.targetStore(
+ &coff.dataDirectoryPtr(.EXPORT).size,
+ @intCast(size),
+ );
+ }
+
+ var sym = smi.symbol(coff).get(coff);
+ while (sym.flags.extra_tag == .next_alias_si)
+ sym = sym.extra.next_alias_si.get(coff);
+
+ sym.extra.size = @intCast(size);
+ },
+ .import_thunk,
+ .nav,
+ .uav,
+ .lazy_code,
+ .lazy_const_data,
+ .builtin,
+ => {},
+ .placeholder,
+ => unreachable,
+ }
+}
+
+fn flushMember(coff: *Coff, mi: Member.Index) !void {
+ const member = mi.get(coff);
+ switch (member.kind) {
+ .first_linker,
+ .longnames,
+ .import,
+ => unreachable,
+ .second_linker => {
+ const Context = struct {
+ coff: *Coff,
+ indices: []u16,
+ strings: []String,
+
+ pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool {
+ return std.mem.lessThan(
+ u8,
+ ctx.strings[lhs].toSlice(ctx.coff),
+ ctx.strings[rhs].toSlice(ctx.coff),
+ );
+ }
+
+ pub fn swap(ctx: @This(), lhs: usize, rhs: usize) void {
+ std.mem.swap(u16, &ctx.indices[lhs], &ctx.indices[rhs]);
+ std.mem.swap(String, &ctx.strings[lhs], &ctx.strings[rhs]);
+ }
+ };
+
+ // TODO: Does this sort need to also sort by linker input order (if names equal)?
+ std.sort.pdqContext(0, coff.lib_string_table.items.len, Context{
+ .coff = coff,
+ .indices = coff.secondLinkerMemberIndicesSlice(),
+ .strings = coff.lib_string_table.items,
+ });
+
+ var offset: usize = 0;
+ var string_table = coff.secondLinkerMemberStringsSlice();
+ for (coff.lib_string_table.items) |string| {
+ const str = string.toSlice(coff);
+ @memcpy(string_table[offset..][0..str.len], str);
+ string_table[offset + str.len] = 0;
+ offset += str.len + 1;
+ }
+ },
+ .coff => {
+ const file_offset: u32 = @intCast(member.header_ni.fileLocation(&coff.mf, false).offset);
+ const first_linker_offsets = coff.firstLinkerMemberOffsetsSlice();
+ for (member.first_linker_indices.values()) |mfli|
+ first_linker_offsets[@intFromEnum(mfli)] = std.mem.nativeTo(u32, file_offset, .big);
+ },
+ }
+}
+
+fn flushExportsSort(coff: *Coff) void {
+ const Context = struct {
+ coff: *Coff,
+ np: []std.coff.ExportNamePointerTableEntry,
+ ord: []std.coff.ExportOrdinalTableEntry,
+ entries: []ExportTable.Entry,
+ nt: []const u8,
+
+ pub fn lessThan(ctx: *const @This(), lhs: usize, rhs: usize) bool {
+ const lhs_entry = &ctx.entries[ctx.coff.targetLoad(&ctx.ord[lhs].unbiased_ordinal)];
+ const rhs_entry = &ctx.entries[ctx.coff.targetLoad(&ctx.ord[rhs].unbiased_ordinal)];
+ return std.mem.lessThan(
+ u8,
+ ctx.nt[lhs_entry.name_index..][0..lhs_entry.name_len],
+ ctx.nt[rhs_entry.name_index..][0..rhs_entry.name_len],
+ );
+ }
+
+ pub fn swap(ctx: @This(), lhs: usize, rhs: usize) void {
+ std.mem.swap(std.coff.ExportNamePointerTableEntry, &ctx.np[lhs], &ctx.np[rhs]);
+ std.mem.swap(std.coff.ExportOrdinalTableEntry, &ctx.ord[lhs], &ctx.ord[rhs]);
+ }
+ };
+
+ std.sort.pdqContext(0, coff.export_table.entries.count(), &Context{
+ .coff = coff,
+ .np = coff.exportNamePointerTableSlice(),
+ .ord = coff.exportOrdinalTableSlice(),
+ .entries = coff.export_table.entries.values(),
+ .nt = coff.export_table.name_table_ni.slice(&coff.mf),
+ });
+}
+
+fn flushSectionMerges(coff: *Coff) !void {
+ while (coff.section_merge_pending_index < coff.section_merges.count()) : (coff.section_merge_pending_index += 1)
+ try coff.flushSectionMerge(coff.section_merge_pending_index);
+}
+
+fn flushSectionMerge(coff: *Coff, index: u32) !void {
+ assert(coff.isImage());
+ const from = coff.section_merges.keys()[index];
+ const to = coff.section_merges.values()[index];
+ assert(from != to);
+
+ log.debug("flushSectionMerge({s}->{s})", .{ from.toSlice(coff), to.toSlice(coff) });
+
+ const opt_to_sec = coff.section_table.getPtr(to);
+ if (coff.section_table.getPtr(from)) |from_sec| {
+ const from_sym = from_sec.si.get(coff);
+ if (opt_to_sec) |to_sec| {
+ const to_sym = to_sec.si.get(coff);
+
+ // TODO: Create a pseudo-section named `from` in `to`, copy `from_sec` ni into that pseudo section
+ // TODO: Update .section_number for all contained syms
+ // TODO: Remove `from_sec` from section table (set size = 0 and can do it in flushResized?).
+ // This is non-trivial as we can't leave holes in the section table.
+ // TODO: Merge section flags
+ _ = to_sym;
+ return coff.base.comp.link_diags.fail("TODO implement section to section merge", .{});
+ } else if (coff.pseudo_section_table.get(to)) |to_ps_si| {
+ const to_sym = to_ps_si.get(coff);
+ if (from_sym.section_number == to_sym.section_number)
+ return;
+
+ // TODO: Same as above, except place `from` into a node in `to_psmi`'s parent
+ return coff.base.comp.link_diags.fail("TODO implement section to pseudosection merge", .{});
+ }
+
+ // If `to` doesn't exist, /MERGE is defined as renaming `from` to `to`.
+ // No other path will create image-level sections, so we can safely rename this now
+ const from_name = &from_sec.si.get(coff).section_number.header(coff).name;
+ const to_slice = to.toSlice(coff);
+ @memcpy(from_name[0..to_slice.len], to_slice);
+ @memset(from_name[to_slice.len..], 0);
+ } else if (coff.pseudo_section_table.getIndex(from)) |from_index| {
+ const from_psmi: Node.PseudoSectionMapIndex = @enumFromInt(from_index);
+ const from_sym = from_psmi.symbol(coff).get(coff);
+ if (opt_to_sec) |to_sec| {
+ const to_sym = to_sec.si.get(coff);
+ if (from_sym.section_number == to_sym.section_number)
+ return;
+
+ // TODO: Move from_psmi's node into to_sec
+ // TODO: Update .section_number for all contained syms
+ // TODO: Merge section flags
+ return coff.base.comp.link_diags.fail("TODO implement pseudosection to section merge", .{});
+ } else if (coff.pseudo_section_table.get(to)) |to_ps_si| {
+ const to_sym = to_ps_si.get(coff);
+ if (from_sym.section_number == to_sym.section_number)
+ return;
+
+ // TODO: Same as above, but move from_psmi's node after to_psmi's node in its parent
+ return coff.base.comp.link_diags.fail("TODO implement pseudosection to pseudosection merge", .{});
+ }
+
+ // Renaming pseudo-sections have no effect on the output, so this is a no-op.
}
}
+
fn virtualSlide(coff: *Coff, start_section_index: usize, start_rva: u32) !void {
var rva = start_rva;
for (
- coff.image_section_table.items[start_section_index..],
+ coff.section_table.values()[start_section_index..],
coff.sectionTableSlice()[start_section_index..],
- ) |section_si, *section| {
- const section_sym = section_si.get(coff);
+ ) |*section, *header| {
+ const section_sym = section.si.get(coff);
section_sym.rva = rva;
- coff.targetStore(§ion.virtual_address, rva);
+ coff.targetStore(&header.virtual_address, rva);
try section_sym.ni.childrenMoved(coff.base.comp.gpa, &coff.mf);
- rva += coff.targetLoad(§ion.virtual_size);
+ rva += coff.targetLoad(&header.virtual_size);
}
switch (coff.optionalHeaderPtr()) {
inline else => |optional_header| coff.targetStore(
@@ -2303,19 +7370,27 @@ pub fn updateExports(
pt: Zcu.PerThread,
exported: Zcu.Exported,
export_indices: []const Zcu.Export.Index,
+) link.Error!void {
+ const diags = &coff.base.comp.link_diags;
+ return coff.updateExportsInner(pt, exported, export_indices) catch |err| switch (err) {
+ error.MappedFileIo => return diags.fail(
+ "failed to write output file: {t}",
+ .{coff.mf.io_err.?},
+ ),
+ else => |e| return e,
+ };
+}
+fn updateExportsInner(
+ coff: *Coff,
+ pt: Zcu.PerThread,
+ exported: Zcu.Exported,
+ export_indices: []const Zcu.Export.Index,
) !void {
const zcu = pt.zcu;
const gpa = zcu.gpa;
const ip = &zcu.intern_pool;
- switch (exported) {
- .nav => |nav| log.debug("updateExports({f})", .{ip.getNav(nav).fqn.fmt(ip)}),
- .uav => |uav| log.debug("updateExports(@as({f}, {f}))", .{
- Type.fromInterned(ip.typeOf(uav)).fmt(pt),
- Value.fromInterned(uav).fmtValue(pt),
- }),
- }
- try coff.symbol_table.ensureUnusedCapacity(gpa, export_indices.len);
+ try coff.symbols.ensureUnusedCapacity(gpa, export_indices.len);
const exported_si: Symbol.Index = switch (exported) {
.nav => |nav| try coff.navSymbol(zcu, nav),
.uav => |uav| @enumFromInt(@intFromEnum(try coff.lowerUav(
@@ -2324,62 +7399,299 @@ pub fn updateExports(
Type.fromInterned(ip.typeOf(uav)).abiAlignment(zcu),
))),
};
+ switch (exported) {
+ .nav => |nav| log.debug("updateExports({f}) = {d}", .{ ip.getNav(nav).fqn.fmt(ip), exported_si }),
+ .uav => |uav| log.debug("updateExports(@as({f}, {f})) = {d}", .{
+ Type.fromInterned(ip.typeOf(uav)).fmt(pt),
+ Value.fromInterned(uav).fmtValue(pt),
+ exported_si,
+ }),
+ }
+ while (try coff.resolve(pt.tid)) {}
while (try coff.idle(pt.tid)) {}
+
+ const machine = coff.targetLoad(&coff.headerPtr().machine);
const exported_ni = exported_si.node(coff);
const exported_sym = exported_si.get(coff);
+ var prev_alias_si = exported_si;
+
for (export_indices) |export_index| {
const @"export" = export_index.ptr(zcu);
- const export_si = try coff.globalSymbol(@"export".opts.name.toSlice(ip), null);
+ const name = @"export".opts.name.toSlice(ip);
+
+ // TODO: add an errMsg if this conflicts with an existing symbol
+ const export_si = try coff.globalSymbol(.{ .name = name });
const export_sym = export_si.get(coff);
export_sym.ni = exported_ni;
export_sym.rva = exported_sym.rva;
- export_sym.size = exported_sym.size;
export_sym.section_number = exported_sym.section_number;
- export_si.applyTargetRelocs(coff);
- if (@"export".opts.name.eqlSlice("wWinMainCRTStartup", ip)) {
- coff.optionalHeaderStandardPtr().address_of_entry_point = exported_sym.rva;
- } else if (@"export".opts.name.eqlSlice("_tls_used", ip)) {
- const tls_directory = coff.dataDirectoryPtr(.TLS);
- tls_directory.* = .{ .virtual_address = exported_sym.rva, .size = exported_sym.size };
- if (coff.targetEndian() != native_endian)
- std.mem.byteSwapAllFields(std.coff.ImageDataDirectory, tls_directory);
+ if (@"export".opts.linkage == .weak and !coff.isImage()) {
+ // exported_si needs to be ahead of export_si in the symbol table,
+ // so that its sti is known when creating the weak external aux entry
+ try coff.pendingSymbolTableEntry(exported_si);
+ export_sym.flags.weak_external_strat = .alias;
+ export_sym.setValue(.{ .weak_alias_si = exported_si });
+ }
+ defer export_si.applyTargetRelocs(coff, .none) catch unreachable;
+
+ // The last symbol in the alias list holds the size
+ const prev_alias_sym = prev_alias_si.get(coff);
+ switch (prev_alias_sym.flags.extra_tag) {
+ .size => export_sym.setExtra(.{ .size = prev_alias_sym.extra.size }),
+ // This export should have been deleted
+ .next_alias_si => assert(prev_alias_sym.extra.next_alias_si == export_si),
+ else => unreachable,
+ }
+
+ prev_alias_sym.setExtra(.{ .next_alias_si = export_si });
+ prev_alias_si = export_si;
+
+ if (!coff.isImage()) continue;
+
+ const entries_ctx = ExportTable.Adapter{ .coff = coff };
+ const gop = try coff.export_table.entries.getOrPutAdapted(
+ gpa,
+ name,
+ entries_ctx,
+ );
+
+ if (!gop.found_existing) {
+ errdefer _ = coff.export_table.entries.pop();
+
+ const export_count = coff.export_table.entries.count();
+ if (export_count > std.math.maxInt(@FieldType(std.coff.ExportDirectoryTable, "number_of_entries")))
+ return coff.base.comp.link_diags.fail("exceeded maximum number of exports", .{});
+
+ const name_index: u32 = @intCast(coff.export_table.name_table_ni.location(&coff.mf).resolve(&coff.mf)[1]);
+ const new_name_table_size = name_index + name.len + 1;
+ if (new_name_table_size > std.math.maxInt(@FieldType(ExportTable.Entry, "name_index")))
+ return coff.base.comp.link_diags.fail("exports name table limit reached", .{});
+
+ try coff.export_table.name_table_ni.resize(&coff.mf, gpa, new_name_table_size);
+
+ const name_table_slice = coff.export_table.name_table_ni.slice(&coff.mf);
+ @memcpy(name_table_slice[name_index..][0 .. name.len + 1], name[0 .. name.len + 1]);
+
+ // If the new name sorts after the current tail of the sorted list, we don't need to re-sort
+ {
+ const ordinal_table_slice = coff.exportOrdinalTableSlice();
+ if (ordinal_table_slice.len > 0 and !coff.export_table.pending_sort) {
+ const tail_index: ExportTable.Ordinal =
+ @enumFromInt(ordinal_table_slice[ordinal_table_slice.len - 1].unbiased_ordinal);
+ const tail_entry = tail_index.get(coff);
+ const tail_name = name_table_slice[tail_entry.name_index..][0..tail_entry.name_len];
+ coff.export_table.pending_sort = std.mem.lessThan(u8, name, tail_name);
+ }
+ }
+
+ const edt = coff.exportDirectoryTable();
+ coff.targetStore(&edt.number_of_names, @intCast(export_count));
+ edt.number_of_entries = edt.number_of_names;
+
+ // TODO: These should all be resized ahead of time to fit all exports
+ // after https://github.com/ziglang/zig/issues/23616
+ try coff.export_table.export_address_table_si.node(coff).resize(
+ &coff.mf,
+ gpa,
+ export_count * @sizeOf(std.coff.ExportAddressTableEntry),
+ );
+
+ try coff.export_table.name_pointer_table_ni.resize(
+ &coff.mf,
+ gpa,
+ export_count * @sizeOf(std.coff.ExportNamePointerTableEntry),
+ );
+
+ try coff.export_table.ordinal_table_ni.resize(
+ &coff.mf,
+ gpa,
+ export_count * @sizeOf(std.coff.ExportOrdinalTableEntry),
+ );
+
+ coff.targetStore(
+ &coff.exportNamePointerTableSlice()[gop.index].name_rva,
+ @intCast(coff.computeNodeRva(coff.export_table.name_table_ni) + name_index),
+ );
+ coff.targetStore(
+ &coff.exportOrdinalTableSlice()[gop.index].unbiased_ordinal,
+ @intCast(gop.index),
+ );
+
+ gop.value_ptr.* = .{
+ .si = export_si,
+ .name_index = @intCast(name_index),
+ .name_len = @intCast(name.len),
+ .export_address_table_ri = @enumFromInt(coff.relocs.items.len),
+ };
+
+ try coff.addReloc(
+ coff.export_table.export_address_table_si,
+ @intCast(@sizeOf(std.coff.ExportAddressTableEntry) * gop.index),
+ export_si,
+ .{ .known = 0 },
+ switch (machine) {
+ else => |tag| @panic(@tagName(tag)),
+ .AMD64 => .{ .AMD64 = .ADDR32NB },
+ .I386 => .{ .I386 = .DIR32NB },
+ },
+ );
+ } else {
+ gop.value_ptr.si = export_si;
+ const reloc = gop.value_ptr.*.export_address_table_ri.get(coff);
+ reloc.target = export_si;
}
}
}
-pub fn deleteExport(coff: *Coff, exported: Zcu.Exported, name: InternPool.NullTerminatedString) void {
- _ = coff;
- _ = exported;
- _ = name;
+pub fn deleteExport(
+ coff: *Coff,
+ exported: Zcu.Exported,
+ name: InternPool.NullTerminatedString,
+) void {
+ const zcu = coff.base.comp.zcu.?;
+ const ip = &zcu.intern_pool;
+
+ const exported_si: Symbol.Index = switch (exported) {
+ .nav => |nav| coff.navs.get(nav).?,
+ .uav => |uav| coff.uavs.get(uav).?,
+ };
+
+ const name_slice = name.toSlice(ip);
+ log.debug("deleteExport({s}, {d})", .{ name_slice, exported_si });
+
+ // TODO: Delete from first / second linker member table
+ // TODO: Delete from symbol table inside section
}
-pub fn dump(coff: *Coff, tid: Zcu.PerThread.Id) Io.Cancelable!void {
+fn dumpStderr(coff: *Coff, tid: Zcu.PerThread.Id) !void {
const comp = coff.base.comp;
const io = comp.io;
var buffer: [512]u8 = undefined;
const stderr = try io.lockStderr(&buffer, null);
defer io.unlockStderr();
const w = &stderr.file_writer.interface;
- coff.printNode(tid, w, .root, 0) catch |err| switch (err) {
- error.WriteFailed => return stderr.err.?,
- };
+ _ = try coff.dump(w, tid);
}
-pub fn printNode(
+pub fn dump(coff: *Coff, w: *Io.Writer, tid: Zcu.PerThread.Id) !link.File.DumpResult {
+ if (coff.options.enable_link_snapshots) {
+ try coff.printNode(tid, w, .root, 0);
+ try w.writeAll("Section table:\n");
+ for (coff.section_table.keys(), coff.section_table.values()) |name, sec|
+ try coff.printSection(w, name, sec.si);
+ try w.writeAll("Symbol table:\n");
+ for (1..coff.symbols.items.len) |si|
+ try coff.printSymbol(w, tid, @enumFromInt(si));
+
+ return .enabled;
+ }
+ return .disabled;
+}
+
+fn printSection(coff: *Coff, w: *Io.Writer, name: String, si: Symbol.Index) !void {
+ const sym = si.get(coff);
+ try w.print("{d:0>6}@{d:0>2} {x:08} n{d:0>8} | {s}\n", .{
+ si,
+ sym.section_number,
+ if (sym.flags.extra_tag == .size) sym.extra.size else 0,
+ sym.ni,
+ name.toSlice(coff),
+ });
+}
+
+fn printSymbol(
coff: *Coff,
+ w: *Io.Writer,
tid: Zcu.PerThread.Id,
+ si: Symbol.Index,
+) !void {
+ const sym = si.get(coff);
+ const node = coff.getNode(sym.ni);
+ try w.print("{d:0>6}@{d:0>2} {x:08} {s} {s} {s} n{d:0>8}+{x:08}:{t: <26} | {x:08} ", .{
+ si,
+ sym.section_number,
+ if (sym.flags.extra_tag == .size)
+ @as(u64, sym.extra.size)
+ else if (sym.ni != .none)
+ sym.ni.location(&coff.mf).resolve(&coff.mf)[1]
+ else
+ 0,
+ switch (sym.flags.value_tag) {
+ .none => "xx",
+ .weak_alias_name => "an",
+ .weak_alias_si => "as",
+ .node_offset => "no",
+ },
+ switch (sym.flags.extra_tag) {
+ .size => "sz",
+ .isli => "li",
+ .next_alias_si => "na",
+ },
+ switch (sym.flags.type) {
+ .unknown => "u",
+ .code => "c",
+ .data => "d",
+ },
+ sym.ni,
+ if (sym.flags.value_tag == .node_offset) sym.value.node_offset else 0,
+ node,
+ sym.rva,
+ });
+
+ if (sym.gmi != .none) {
+ try w.print("G {f}\n", .{fmtGlobalName(coff, sym.gmi)});
+ } else {
+ try w.writeAll("| ");
+ try coff.printNodeName(w, tid, node);
+ if (sym.flags.extra_tag == .isli)
+ try w.print(" | {s}", .{sym.extra.isli.name(coff).toSlice(coff)});
+ try w.writeByte('\n');
+ }
+}
+
+const FmtGlobalName = struct { coff: *Coff, gmi: Node.GlobalMapIndex };
+
+fn fmtGlobalName(coff: *Coff, gmi: Node.GlobalMapIndex) std.fmt.Alt(FmtGlobalName, globalNameEscape) {
+ return .{ .data = .{ .coff = coff, .gmi = gmi } };
+}
+
+fn globalNameEscape(data: FmtGlobalName, w: *std.Io.Writer) std.Io.Writer.Error!void {
+ if (data.gmi == .none) return;
+ try w.writeAll(data.gmi.name(data.coff).toSlice(data.coff));
+ if (data.gmi.libName(data.coff).unwrap()) |lib_name|
+ try w.print("({s})", .{lib_name.toSlice(data.coff)});
+}
+
+fn printNodeName(
+ coff: *Coff,
w: *std.Io.Writer,
- ni: MappedFile.Node.Index,
- indent: usize,
+ tid: Zcu.PerThread.Id,
+ node: Node,
) !void {
- const node = coff.getNode(ni);
- try w.splatByteAll(' ', indent);
- try w.writeAll(@tagName(node));
switch (node) {
else => {},
.image_section => |si| try w.print("({s})", .{
std.mem.sliceTo(&si.get(coff).section_number.header(coff).name, 0),
}),
+ .input_section => |isi| {
+ const ioi = isi.input(coff);
+ const is = isi.inputSection(coff);
+ try w.print("({f}{f}, {s}", .{
+ ioi.path(coff).fmtEscapeString(),
+ fmtMemberNameString(ioi.memberName(coff)),
+ coff.getNode(is.si.node(coff).parent(&coff.mf)).object_section.name(coff).toSlice(coff),
+ });
+ if (is.comdat_si != .null) {
+ const comdat_sym = is.comdat_si.get(coff);
+ const comdat_name = if (comdat_sym.gmi != .none)
+ comdat_sym.gmi.name(coff).toSlice(coff)
+ else
+ coff.input_symbols.items[@intFromEnum(comdat_sym.extra.isli)].name.toSlice(coff);
+
+ try w.print("={s}", .{comdat_name});
+ }
+ try w.writeAll(")");
+ },
.import_lookup_table,
.import_address_table,
.import_hint_name_table,
@@ -2389,18 +7701,18 @@ pub fn printNode(
inline .pseudo_section, .object_section => |smi| try w.print("({s})", .{
smi.name(coff).toSlice(coff),
}),
- .global => |gmi| {
- const gn = gmi.globalName(coff);
+ .import_thunk,
+ => |gmi| {
try w.writeByte('(');
- if (gn.lib_name.toSlice(coff)) |lib_name| try w.print("{s}.dll, ", .{lib_name});
- try w.print("{s})", .{gn.name.toSlice(coff)});
+ if (gmi.libName(coff).toSlice(coff)) |lib_name| try w.print("{s}.dll, ", .{lib_name});
+ try w.print("{s})", .{gmi.name(coff).toSlice(coff)});
},
.nav => |nmi| {
const zcu = coff.base.comp.zcu.?;
const ip = &zcu.intern_pool;
const nav = ip.getNav(nmi.navIndex(coff));
try w.print("({f}, {f})", .{
- Type.fromInterned(nav.typeOf(ip)).fmt(.{ .zcu = zcu, .tid = tid }),
+ Type.fromInterned(ip.typeOf(nav.resolved.?.value)).fmt(.{ .zcu = zcu, .tid = tid }),
nav.fqn.fmt(ip),
});
},
@@ -2418,7 +7730,28 @@ pub fn printNode(
.tid = tid,
}),
}),
+ .builtin => |si| {
+ const sym = si.get(coff);
+ if (sym.gmi != .none) {
+ try w.writeByte('(');
+ if (sym.gmi.libName(coff).toSlice(coff)) |lib_name| try w.print("{s}.dll, ", .{lib_name});
+ try w.print("{s})", .{sym.gmi.name(coff).toSlice(coff)});
+ }
+ },
}
+}
+
+pub fn printNode(
+ coff: *Coff,
+ tid: Zcu.PerThread.Id,
+ w: *Io.Writer,
+ ni: MappedFile.Node.Index,
+ indent: usize,
+) !void {
+ const node = coff.getNode(ni);
+ try w.splatByteAll(' ', indent);
+ try w.writeAll(@tagName(node));
+ try coff.printNodeName(w, tid, node);
{
const mf_node = &coff.mf.nodes.items[@intFromEnum(ni)];
const off, const size = mf_node.location().resolve(&coff.mf);
@@ -2446,7 +7779,7 @@ pub fn printNode(
const line_len = 0x10;
var line_it = std.mem.window(
u8,
- coff.mf.contents[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)],
+ coff.mf.memory_map.memory[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)],
line_len,
line_len,
);
diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig
@@ -3785,7 +3785,7 @@ fn mapInputSection(elf: *Elf, opts: struct {
const new_alignment: std.mem.Alignment = .fromByteUnits(
std.math.ceilPowerOfTwoAssert(usize, @intCast(opts.addralign)),
);
- try existing_shndx.get(elf).ni.realign(&elf.mf, gpa, new_alignment);
+ try existing_shndx.get(elf).ni.realign(&elf.mf, gpa, new_alignment, .{ .set_alignment = true });
}
// ...and update the shdr as needed.
switch (elf.shdrPtr(existing_shndx)) {
@@ -3948,7 +3948,7 @@ fn uavMapIndex(
} else {
const node = uav_gop.value_ptr.lsi.index().ptr(elf).node;
if (resolved_align.toStdMem().order(node.alignment(&elf.mf)).compare(.gt)) {
- try node.realign(&elf.mf, gpa, resolved_align.toStdMem());
+ try node.realign(&elf.mf, gpa, resolved_align.toStdMem(), .{ .set_alignment = true });
}
}
return umi;
@@ -4677,7 +4677,7 @@ fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadPars
// We have a copy relocation for this global, but the amount of space we
// reserved for it could be too small or underaligned!
try copied_global.node.resize(&elf.mf, gpa, gop.value_ptr.size);
- try copied_global.node.realign(&elf.mf, gpa, gop.value_ptr.alignment);
+ try copied_global.node.realign(&elf.mf, gpa, gop.value_ptr.alignment, .{ .set_alignment = true });
const global_ptr = elf.globalByName(name).?;
switch (elf.symPtr(global_ptr.symtab_index)) {
inline else => |sym_ptr| elf.targetStore(&sym_ptr.size, @intCast(gop.value_ptr.size)),
@@ -6711,16 +6711,12 @@ pub fn deleteExport(elf: *Elf, exported: Zcu.Exported, name: InternPool.NullTerm
_ = name;
}
-pub fn dump(elf: *Elf, tid: Zcu.PerThread.Id) Io.Cancelable!void {
- const comp = elf.base.comp;
- const io = comp.io;
- var buffer: [512]u8 = undefined;
- const stderr = try io.lockStderr(&buffer, null);
- defer io.lockStderr();
- const w = &stderr.file_writer.interface;
- elf.printNode(tid, w, .root, 0) catch |err| switch (err) {
- error.WriteFailed => return stderr.err.?,
- };
+pub fn dump(elf: *Elf, w: *Io.Writer, tid: Zcu.PerThread.Id) !link.File.DumpResult {
+ if (elf.options.enable_link_snapshots) {
+ try elf.printNode(tid, w, .root, 0);
+ return .enabled;
+ }
+ return .disabled;
}
pub fn printNode(
@@ -6764,13 +6760,13 @@ pub fn printNode(
elf.getNode(isi.node(elf).parent(&elf.mf)).section.name(elf).slice(elf),
});
},
- .copied_global => |name| try w.print("(copy:{s})", .{name}),
+ .copied_global => |name| try w.print("(copy:{s})", .{name.slice(elf)}),
.nav => |nmi| {
const zcu = elf.base.comp.zcu.?;
const ip = &zcu.intern_pool;
const nav = ip.getNav(nmi.navIndex(elf));
try w.print("({f}, {f})", .{
- Type.fromInterned(nav.typeOf(ip)).fmt(.{ .zcu = zcu, .tid = tid }),
+ Type.fromInterned(ip.typeOf(nav.resolved.?.value)).fmt(.{ .zcu = zcu, .tid = tid }),
nav.fqn.fmt(ip),
});
},
diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig
@@ -26,6 +26,9 @@ updates: std.ArrayList(Node.Index),
update_prog_node: std.Progress.Node,
writers: std.SinglyLinkedList,
io_err: ?IoError,
+/// If locked, modifying the node layout is not allowed.
+/// Modifying node content is always allowed.
+nodes_lock: std.debug.SafetyLock = .{},
pub const growth_factor = 4;
@@ -188,6 +191,10 @@ pub const Node = extern struct {
return ni.get(mf).parent;
}
+ pub fn next(ni: Node.Index, mf: *const MappedFile) Node.Index {
+ return ni.get(mf).next;
+ }
+
pub fn ChildIterator(comptime direction: enum { prev, next }) type {
return struct {
mf: *const MappedFile,
@@ -330,6 +337,7 @@ pub const Node = extern struct {
}
pub fn resize(ni: Node.Index, mf: *MappedFile, gpa: std.mem.Allocator, size: u64) Error!void {
+ defer if (std.debug.runtime_safety) mf.verify();
mf.resizeNode(gpa, ni, size) catch |err| switch (err) {
error.OutOfMemory,
error.Canceled,
@@ -346,16 +354,23 @@ pub const Node = extern struct {
}
}
+ pub const RealignNodeOptions = struct {
+ /// Shift the node backwards if possible
+ try_backwards: bool = true,
+ /// If `set, persists `new_alignment` as the node's alignment for future operations.
+ set_alignment: bool = true,
+ };
+
/// Moves and expands a node such that its offset and size are aligned to `new_alignment`.
- ///
/// Asserts that `ni` is not `Node.Index.root`.
pub fn realign(
ni: Node.Index,
mf: *MappedFile,
gpa: std.mem.Allocator,
new_alignment: std.mem.Alignment,
+ opts: RealignNodeOptions,
) Error!void {
- mf.realignNode(gpa, ni, new_alignment) catch |err| switch (err) {
+ mf.realignNode(gpa, ni, new_alignment, opts) catch |err| switch (err) {
error.OutOfMemory,
error.Canceled,
=> |e| return e,
@@ -371,6 +386,26 @@ pub const Node = extern struct {
}
}
+ /// Shrink a node to `size`, exactly.
+ /// Asserts that the new size can contain all the children.
+ /// If `shift_next` is set, then the following node is shifted backwards into
+ /// the free space as much as alignment allows.
+ /// Asserts that `size` is >= the end of the last child node.
+ pub fn shrink(
+ ni: Node.Index,
+ mf: *MappedFile,
+ gpa: std.mem.Allocator,
+ size: u64,
+ shift_next: bool,
+ ) Error!void {
+ try mf.shrinkNode(gpa, ni, size, shift_next);
+ var writers_it = mf.writers.first;
+ while (writers_it) |writer_node| : (writers_it = writer_node.next) {
+ const w: *Node.Writer = @fieldParentPtr("writer_node", writer_node);
+ w.interface.buffer = w.ni.slice(mf);
+ }
+ }
+
pub fn writer(ni: Node.Index, mf: *MappedFile, gpa: std.mem.Allocator, w: *Writer) void {
w.* = .{
.gpa = gpa,
@@ -530,7 +565,26 @@ fn addNode(mf: *MappedFile, gpa: std.mem.Allocator, opts: struct {
add_node: AddNodeOptions,
}) Error!Node.Index {
if (opts.add_node.moved or opts.add_node.resized) try mf.updates.ensureUnusedCapacity(gpa, 1);
+ mf.nodes_lock.assertUnlocked();
const offset = opts.add_node.alignment.forward(@intCast(opts.offset));
+ if (opts.parent != .none) {
+ const new_end = offset + opts.add_node.size;
+ switch (opts.next) {
+ .none => {
+ _, const parent_size = opts.parent.location(mf).resolve(mf);
+ if (new_end > parent_size)
+ try opts.parent.resize(mf, gpa, new_end);
+ },
+ else => |next_ni| {
+ const next_offset, _ = next_ni.location(mf).resolve(mf);
+ if (new_end > next_offset)
+ try next_ni.realign(mf, gpa, opts.add_node.alignment, .{
+ .try_backwards = false,
+ .set_alignment = false,
+ });
+ },
+ }
+ }
const location_tag: Node.Location.Tag, const location_payload: Node.Location.Payload = location: {
if (std.math.cast(u32, offset)) |small_offset| break :location .{ .small, .{
.small = .{ .offset = small_offset, .size = 0 },
@@ -572,14 +626,12 @@ fn addNode(mf: *MappedFile, gpa: std.mem.Allocator, opts: struct {
},
.location_payload = location_payload,
};
+
{
- defer {
- free_node.flags.moved = false;
- free_node.flags.resized = false;
- }
- _, const parent_size = opts.parent.location(mf).resolve(mf);
- if (offset > parent_size) try opts.parent.resize(mf, gpa, offset);
try free_ni.resize(mf, gpa, opts.add_node.size);
+ if (opts.add_node.moved or opts.add_node.resized) try mf.updates.ensureUnusedCapacity(gpa, 1);
+ free_node.flags.moved = false;
+ free_node.flags.resized = false;
}
if (opts.add_node.moved) free_ni.movedAssumeCapacity(mf);
if (opts.add_node.resized) free_ni.resizedAssumeCapacity(mf);
@@ -666,11 +718,63 @@ pub fn addNodeAfter(
});
}
-fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested_size: u64) (Allocator.Error || Io.Cancelable || IoError)!void {
+fn shrinkNode(
+ mf: *MappedFile,
+ gpa: std.mem.Allocator,
+ ni: Node.Index,
+ size: u64,
+ shift_next: bool,
+) !void {
+ mf.nodes_lock.assertUnlocked();
+ const node = ni.get(mf);
+ const old_offset, _ = node.location().resolve(mf);
+
+ // This would require unmapping first
+ assert(ni != Node.Index.root);
+ defer if (std.debug.runtime_safety) mf.verify();
+
+ if (node.last != .none) {
+ const last = node.last.get(mf);
+ const last_offset, const last_size = last.location().resolve(mf);
+ assert(last_offset + last_size > size);
+ }
+
+ try mf.large.ensureUnusedCapacity(gpa, 4);
+ try mf.updates.ensureUnusedCapacity(gpa, 2);
+
+ ni.setLocationAssumeCapacity(mf, old_offset, size);
+ if (!shift_next or node.next == .none) return;
+
+ const next = node.next.get(mf);
+ const old_next_offset, const next_size = next.location().resolve(mf);
+ const padding = old_next_offset - (old_offset + size);
+ const new_next_offset = next.flags.alignment.forward(@intCast(old_next_offset - padding));
+
+ if (next.flags.has_content and new_next_offset < old_next_offset) {
+ const old_file_offset = node.next.fileLocation(mf, false).offset;
+ const new_file_offset = (old_file_offset - old_next_offset) + new_next_offset;
+ @memmove(
+ mf.memory_map.memory[@intCast(new_file_offset)..][0..@intCast(next_size)],
+ mf.memory_map.memory[@intCast(old_file_offset)..][0..@intCast(next_size)],
+ );
+ @memset(mf.memory_map.memory[@intCast(new_file_offset + next_size)..@intCast(old_file_offset + next_size)], 0);
+ }
+
+ node.next.setLocationAssumeCapacity(mf, new_next_offset, next_size);
+}
+
+fn resizeNode(
+ mf: *MappedFile,
+ gpa: std.mem.Allocator,
+ ni: Node.Index,
+ requested_size: u64,
+) (Allocator.Error || Io.Cancelable || IoError)!void {
+ mf.nodes_lock.assertUnlocked();
const io = mf.io;
const node = ni.get(mf);
const old_offset, const old_size = node.location().resolve(mf);
const new_size = node.flags.alignment.forward(@intCast(requested_size));
+
// Resize the entire file
if (ni == Node.Index.root) {
try mf.ensureCapacityForSetLocation(gpa);
@@ -703,6 +807,17 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested
if (is_linux and !mf.flags.fallocate_insert_range_unsupported and
node.flags.alignment.order(mf.flags.block_size).compare(.gte))
insert_range: {
+ const range_file_offset = ni.fileLocation(mf, false).offset + old_size;
+ const range_size = node.flags.alignment.forward(
+ @intCast(requested_size +| requested_size / growth_factor),
+ ) - old_size;
+
+ // If this node is being realigned, its current state might not
+ // meet the requirements for fallocate
+ if (!mf.flags.block_size.check(@intCast(range_file_offset)) or
+ !mf.flags.block_size.check(@intCast(range_size)))
+ break :insert_range;
+
mf.memory_map.write(io) catch |err| switch (err) {
error.WouldBlock => return error.Unexpected, // file was not opened as non-blocking
error.NotOpenForWriting => return error.Unexpected, // we definitely opened the file for writing
@@ -712,10 +827,6 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested
const last_offset, const last_size = parent.last.location(mf).resolve(mf);
const last_end = last_offset + last_size;
assert(last_end <= old_parent_size);
- const range_file_offset = ni.fileLocation(mf, false).offset + old_size;
- const range_size = node.flags.alignment.forward(
- @intCast(requested_size +| requested_size / growth_factor),
- ) - old_size;
_, const file_size = Node.Index.root.location(mf).resolve(mf);
while (true) switch (linux.errno(switch (std.math.order(range_file_offset, file_size)) {
.lt => linux.fallocate(
@@ -814,6 +925,7 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested
var last_fixed_ni = ni;
var first_floating_ni = node.next;
var shift = new_size - old_size;
+ var max_shift_align: std.mem.Alignment = .@"1";
var direction: enum { forward, reverse } = .forward;
while (true) {
assert(last_fixed_ni != .none);
@@ -830,10 +942,12 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested
if (new_last_fixed_offset + last_fixed_size <= old_first_floating_offset)
break :make_space;
assert(direction == .forward);
+ max_shift_align = max_shift_align.max(first_floating.flags.alignment.max(last_fixed.flags.alignment));
if (first_floating.flags.fixed) {
- shift = first_floating.flags.alignment.forward(@intCast(
+ shift = max_shift_align.forward(@intCast(
@max(shift, first_floating_size),
));
+
// Not enough space, try the next node
last_fixed_ni = first_floating_ni;
first_floating_ni = first_floating.next;
@@ -842,7 +956,7 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested
// Move the found floating node to make space for preceding fixed nodes
const last = parent.last.get(mf);
const last_offset, const last_size = last.location().resolve(mf);
- const new_first_floating_offset = first_floating.flags.alignment.forward(
+ const new_first_floating_offset = max_shift_align.forward(
@intCast(@max(new_last_fixed_offset + last_fixed_size, last_offset + last_size)),
);
const new_parent_size = new_first_floating_offset + first_floating_size;
@@ -903,7 +1017,7 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested
last_fixed_ni.setLocationAssumeCapacity(
mf,
old_last_fixed_offset,
- last_fixed_size + shift,
+ new_size,
);
return;
}
@@ -929,8 +1043,10 @@ fn realignNode(
gpa: std.mem.Allocator,
ni: Node.Index,
new_alignment: std.mem.Alignment,
+ opts: Node.Index.RealignNodeOptions,
) (Allocator.Error || Io.Cancelable || IoError)!void {
assert(ni != Node.Index.root); // currently unsupported
+ mf.nodes_lock.assertUnlocked();
const node = ni.get(mf);
const old_offset, const size = node.location().resolve(mf);
@@ -939,7 +1055,12 @@ fn realignNode(
defer if (std.debug.runtime_safety) mf.verify();
+ const prev_alignment = node.flags.alignment;
node.flags.alignment = new_alignment;
+ defer {
+ // alignment needs to be temporarily set for the resizes below
+ if (!opts.set_alignment) node.flags.alignment = prev_alignment;
+ }
const new_size = node.flags.alignment.forward(@intCast(size));
if (new_alignment.check(@intCast(old_offset))) {
@@ -956,6 +1077,37 @@ fn realignNode(
},
};
+ if (opts.try_backwards) {
+ const backward_offset = new_alignment.backward(@intCast(old_offset));
+ const prev_end = if (node.prev == .none) 0 else prev: {
+ const prev_offset, const prev_size = node.prev.location(mf).resolve(mf);
+ break :prev prev_offset + prev_size;
+ };
+
+ if (backward_offset >= prev_end) {
+ try mf.ensureCapacityForSetLocation(gpa);
+
+ if (node.flags.has_content) {
+ const old_file_offset = ni.fileLocation(mf, false).offset;
+ const new_file_offset = (old_file_offset - old_offset) + backward_offset;
+ @memmove(
+ mf.memory_map.memory[@intCast(new_file_offset)..][0..@intCast(size)],
+ mf.memory_map.memory[@intCast(old_file_offset)..][0..@intCast(size)],
+ );
+ @memset(mf.memory_map.memory[@intCast(new_file_offset + size)..@intCast(old_file_offset + size)], 0);
+ }
+
+ if (backward_offset + new_size <= trailing_end) {
+ ni.setLocationAssumeCapacity(mf, backward_offset, new_size);
+ } else {
+ ni.setLocationAssumeCapacity(mf, backward_offset, size);
+ try mf.resizeNode(gpa, ni, new_size);
+ }
+
+ return;
+ }
+ }
+
const forward_offset = new_alignment.forward(@intCast(old_offset));
if (forward_offset + new_size <= trailing_end) {
// Shift into the free space if possible
@@ -1135,6 +1287,12 @@ fn ensureTotalCapacityPreciseInner(mf: *MappedFile, new_capacity: usize) (Alloca
error.OperationUnsupported => {},
else => |e| return e,
}
+
+ mf.memory_map.write(io) catch |err| switch (err) {
+ error.WouldBlock => return error.Unexpected, // file was not opened as non-blocking
+ error.NotOpenForWriting => return error.Unexpected, // we definitely opened the file for writing
+ else => |e| return e,
+ };
unmap(mf);
}
@@ -1156,11 +1314,12 @@ pub fn unmap(mf: *MappedFile) void {
}
pub fn flush(mf: *MappedFile) (Io.Cancelable || error{MappedFileIo})!void {
- mf.memory_map.write(mf.io) catch |err| switch (err) {
+ mf.flushInner() catch |err| switch (err) {
error.Canceled => |e| return e,
error.WouldBlock, // file was not opened as non-blocking
error.NotOpenForWriting, // we definitely opened the file for writing
+ error.ReadOnlyFileSystem,
=> {
mf.io_err = error.Unexpected;
return error.MappedFileIo;
@@ -1173,6 +1332,11 @@ pub fn flush(mf: *MappedFile) (Io.Cancelable || error{MappedFileIo})!void {
};
}
+fn flushInner(mf: *MappedFile) (Io.File.WritePositionalError || Io.File.SetTimestampsError)!void {
+ try mf.memory_map.write(mf.io);
+ if (is_windows) try mf.memory_map.file.setTimestampsNow(mf.io);
+}
+
fn verify(mf: *MappedFile) void {
const root = Node.Index.root.get(mf);
assert(root.parent == .none);
@@ -1207,3 +1371,184 @@ fn verifyNode(mf: *MappedFile, parent_ni: Node.Index) void {
ni = node.next;
}
}
+
+const testing = std.testing;
+fn testVerifyContent(mf: *@This(), ni: Node.Index, value: u8, init_len: usize) !void {
+ // Not using std.mem.allEqual, so we can get useful output
+ const slice = ni.slice(mf);
+ var buf: [256]u8 = undefined;
+ @memset(buf[0..init_len], value);
+ @memset(buf[init_len..], 0);
+ try testing.expectEqualSlices(u8, buf[0..slice.len], slice);
+}
+
+test {
+ const gpa = testing.allocator;
+
+ var tmp_dir = testing.tmpDir(.{});
+ defer tmp_dir.cleanup();
+
+ var file = try tmp_dir.dir.createFile(testing.io, "test.mf", .{ .read = true });
+ defer file.close(testing.io);
+
+ var mf = try init(file, gpa, testing.io);
+ defer mf.deinit(gpa);
+
+ const a = try mf.addFirstChildNode(gpa, .root, .{ .fixed = true, .alignment = .@"4" });
+ const c = try mf.addLastChildNode(gpa, .root, .{ .fixed = true, .alignment = .@"4" });
+ const b = try mf.addNodeAfter(gpa, a, .{ .fixed = true, .alignment = .@"16" });
+ const d = try mf.addNodeAfter(gpa, b, .{ .alignment = .@"4" });
+
+ const a_init_size = 8;
+ const b_init_size = 16;
+ const c_init_size = 24;
+ const d_init_size = 28;
+
+ // Resize without content
+ {
+ // Verify size is aligned forward
+ try d.resize(&mf, gpa, d_init_size - 1);
+ try a.resize(&mf, gpa, a_init_size - 2);
+ try c.resize(&mf, gpa, c_init_size);
+ try b.resize(&mf, gpa, b_init_size);
+ mf.verify();
+
+ const a_loc, const a_size = a.location(&mf).resolve(&mf);
+ const b_loc, const b_size = b.location(&mf).resolve(&mf);
+ const c_loc, const c_size = c.location(&mf).resolve(&mf);
+ _, const d_size = d.location(&mf).resolve(&mf);
+ try testing.expect(a_size >= a_init_size);
+ try testing.expect(b_size >= b_init_size);
+ try testing.expect(c_size >= c_init_size);
+ try testing.expect(d_size >= d_init_size);
+ try testing.expect(b_loc >= a_loc + a_size);
+ try testing.expect(c_loc >= b_loc + b_size);
+ }
+
+ const a_exp_size = 24;
+ const b_exp_size = 28;
+ const c_exp_size = 48;
+ const d_exp_size = 32;
+
+ // Resize with content
+ {
+ @memset(a.slice(&mf)[0..a_init_size], 0xaa);
+ @memset(b.slice(&mf)[0..b_init_size], 0xbb);
+ @memset(c.slice(&mf)[0..c_init_size], 0xcc);
+ @memset(d.slice(&mf)[0..d_init_size], 0xdd);
+
+ try a.resize(&mf, gpa, a_exp_size);
+ try b.resize(&mf, gpa, b_exp_size);
+ try c.resize(&mf, gpa, c_exp_size);
+ try d.resize(&mf, gpa, d_exp_size);
+ mf.verify();
+
+ const a_loc, const a_size = a.location(&mf).resolve(&mf);
+ const b_loc, const b_size = b.location(&mf).resolve(&mf);
+ const c_loc, const c_size = c.location(&mf).resolve(&mf);
+ _, const d_size = d.location(&mf).resolve(&mf);
+ try testing.expect(a_size >= a_exp_size);
+ try testing.expect(b_size >= b_exp_size);
+ try testing.expect(c_size >= c_exp_size);
+ try testing.expect(d_size >= d_exp_size);
+ try testing.expect(b_loc >= a_loc + a_size);
+ try testing.expect(c_loc >= b_loc + b_size);
+
+ try testVerifyContent(&mf, a, 0xaa, a_init_size);
+ try testVerifyContent(&mf, b, 0xbb, b_init_size);
+ try testVerifyContent(&mf, c, 0xcc, c_init_size);
+ try testVerifyContent(&mf, d, 0xdd, d_init_size);
+ }
+
+ const child_init: []const struct { std.mem.Alignment, usize } = &.{
+ .{ .@"16", 16 },
+ .{ .@"1", 1 },
+ .{ .@"1", 19 },
+ .{ .@"1", 3 },
+ .{ .@"8", 30 },
+ .{ .@"2", 5 },
+ .{ .@"1", 60 },
+ .{ .@"2", 2 },
+ .{ .@"16", 32 },
+ };
+
+ var children: [child_init.len]Node.Index = undefined;
+
+ // Differently-aligned fixed sibling nodes
+ {
+ for (children[0 .. children.len - 1], child_init[0 .. children.len - 1], 0..) |*ni, opts, i| {
+ ni.* = try mf.addLastChildNode(gpa, b, .{
+ .alignment = opts.@"0",
+ .size = opts.@"1",
+ .fixed = true,
+ });
+
+ @memset(ni.slice(&mf)[0..opts.@"1"], @intCast(i + 1));
+ }
+ // Shift differently-aligned nodes by inserting a node
+ children[children.len - 1] = try mf.addNodeAfter(gpa, children[3], .{
+ .alignment = child_init[children.len - 1].@"0",
+ .size = child_init[children.len - 1].@"1",
+ .fixed = true,
+ });
+ @memset(children[children.len - 1].slice(&mf), @intCast(children.len));
+
+ mf.verify();
+ for (children, child_init, 0..) |ni, opts, i| {
+ try testVerifyContent(&mf, ni, @intCast(i + 1), opts.@"1");
+ }
+ }
+
+ // Shifting child nodes forward due via resize of parent.prev
+ {
+ try testing.expect(a.location(&mf).resolve(&mf)[1] < 64);
+ try a.resize(&mf, gpa, 64);
+
+ try testVerifyContent(&mf, a, 0xaa, a_init_size);
+ try testVerifyContent(&mf, c, 0xcc, c_init_size);
+ try testVerifyContent(&mf, d, 0xdd, d_init_size);
+ for (children, child_init, 0..) |ni, opts, i| {
+ try testVerifyContent(&mf, ni, @intCast(i + 1), opts.@"1");
+ }
+ }
+
+ // Re-align last node into trailing free space within parent
+ {
+ try b.resize(&mf, gpa, b.location(&mf).resolve(&mf)[1] + 64);
+
+ const last = children[children.len - 2];
+ try last.realign(&mf, gpa, .@"4", true);
+ mf.verify();
+
+ for (children, child_init, 0..) |ni, opts, i|
+ try testVerifyContent(&mf, ni, @intCast(i + 1), opts.@"1");
+ try testVerifyContent(&mf, c, 0xcc, c_init_size);
+ }
+
+ // Re-align, shifting sibling nodes
+ {
+ try children[1].realign(&mf, gpa, .@"8", true);
+ mf.verify();
+
+ for (children, child_init, 0..) |ni, opts, i|
+ try testVerifyContent(&mf, ni, @intCast(i + 1), opts.@"1");
+ try testVerifyContent(&mf, c, 0xcc, c_init_size);
+ }
+
+ // Shrink and shift start of trailing node into free space
+ {
+ try mf.shrinkNode(gpa, a, 16, true);
+ mf.verify();
+
+ const a_loc, const a_size = a.location(&mf).resolve(&mf);
+ const b_loc, _ = b.location(&mf).resolve(&mf);
+ try testing.expectEqual(b_loc, a_loc + a_size);
+
+ try testVerifyContent(&mf, a, 0xaa, a_init_size);
+ try testVerifyContent(&mf, c, 0xcc, c_init_size);
+ try testVerifyContent(&mf, d, 0xdd, d_init_size);
+ for (children, child_init, 0..) |ni, opts, i| {
+ try testVerifyContent(&mf, ni, @intCast(i + 1), opts.@"1");
+ }
+ }
+}
diff --git a/src/main.zig b/src/main.zig
@@ -1440,8 +1440,8 @@ fn buildOutputType(
dev.check(.stdio_listen);
listen = .stdio;
} else if (mem.eql(u8, arg, "--debug-link-snapshot")) {
- if (!build_options.enable_link_snapshots) {
- warn("Zig was compiled without linker snapshots enabled (-Dlink-snapshot). --debug-link-snapshot has no effect.", .{});
+ if (!build_options.enable_debug_extensions) {
+ warn("Zig was compiled without debug extensions. --debug-link-snapshot has no effect.", .{});
} else {
enable_link_snapshots = true;
}
diff --git a/test/link.zig b/test/link.zig
@@ -0,0 +1,341 @@
+pub fn addCases(ctx: *LinkContext) void {
+ if (ctx.target.result.isMinGW())
+ @import("link/mingw.zig").addCases(ctx);
+
+ if (ctx.includeTest("static-lib")) |case| {
+ const obj1 = case.addObject(.{
+ .name = "obj1",
+ .name_prefix = false,
+ .name_target = false,
+ .use_llvm = true,
+ .use_lld = true,
+ .c_source_bytes =
+ \\int foo1 = 1;
+ \\int foo2 = 2;
+ \\unsigned int fooBar() {
+ \\ return foo1 + foo2;
+ \\}
+ ,
+ });
+ const obj2 = case.addObject(.{
+ .name = "this_is_a_long_name",
+ .name_prefix = false,
+ .name_target = false,
+ .zig_source_bytes =
+ \\fn fooWeak() callconv(.c) usize {
+ \\ return 0xaabbccddaabbccdd;
+ \\}
+ \\export var foo_array: [2]u16 = .{ 0xffff, 0xabcd };
+ \\export var foo_strong: usize = 0x1122334411223344;
+ \\comptime {
+ \\ @export(&fooWeak, .{ .name = "fooWeak", .linkage = .weak });
+ \\ @export(&foo_strong, .{ .name = "foo_strong_alias", .linkage = .strong });
+ \\}
+ ,
+ });
+
+ const lib = case.addLibrary(.static, .{
+ .name = "lib",
+ .name_prefix = false,
+ .name_target = false,
+ });
+ lib.root_module.addObject(obj1);
+ lib.root_module.addObject(obj2);
+
+ case.verifyObjdump(lib.getEmittedBin(), &.{
+ "-s",
+ "--elements=file-type",
+ "--symbols",
+ "--only-symbol=foo",
+ }, .{ .use_llvm = true });
+
+ const exe = case.addExecutable(.{
+ .name = "test",
+ .zig_source_bytes =
+ \\extern fn fooBar() c_uint;
+ \\extern fn fooWeak() usize;
+ \\extern var foo_array: [2]u16;
+ \\extern var foo_strong: usize;
+ \\extern var foo_strong_alias: usize;
+ \\pub fn main() !u8 {
+ \\ return @intFromBool(0xcd003365cd00df35 != fooBar() +
+ \\ fooWeak() +
+ \\ foo_array[1] +
+ \\ foo_strong +
+ \\ foo_strong_alias);
+ \\}
+ ,
+ });
+ exe.root_module.linkLibrary(lib);
+
+ const run = case.addRunArtifact(exe);
+ run.addCheck(.{ .expect_term = .{ .exited = 0 } });
+ }
+
+ if (ctx.includeTest("tls")) |case| {
+ const obj = case.addObject(.{
+ .name = "obj",
+ .zig_source_bytes =
+ \\threadlocal var threadlocal_var: u32 = 1234;
+ \\threadlocal var threadlocal_arr: [4]u16 = .{ 0x1111, 0x2222, 0x3333, 0x4444, };
+ \\export fn threadlocal_read(a: *u32, b: *u16) void {
+ \\ a.* = threadlocal_var;
+ \\ b.* = threadlocal_arr[3];
+ \\}
+ \\export fn threadlocal_write(a: u32, b: u16) void {
+ \\ threadlocal_var = a;
+ \\ threadlocal_arr[3] = b;
+ \\}
+ ,
+ });
+
+ case.verifyObjdump(obj.getEmittedBin(), &.{
+ "-s",
+ "--symbols",
+ "--only-symbol=threadlocal",
+ "--only-symbol=tls",
+ }, .{ .use_llvm = true });
+
+ const exe = case.addExecutable(.{
+ .name = "test",
+ .zig_source_bytes =
+ \\extern fn threadlocal_read(a: *u32, b: *u16) void;
+ \\extern fn threadlocal_write(a: u32, b: u16) void;
+ \\threadlocal var threadlocal_foo: u64 = 0xcafecafecafecafe;
+ \\pub fn main() !u8 {
+ \\ var a: u32 = undefined;
+ \\ var b: u16 = undefined;
+ \\ threadlocal_read(&a, &b);
+ \\ if (a != 1234 or b != 0x4444) return 1;
+ \\ if (threadlocal_foo != 0xcafecafecafecafe) return 2;
+ \\ threadlocal_write(0xabcdabcd, 0x5555);
+ \\ threadlocal_foo = 1;
+ \\ threadlocal_read(&a, &b);
+ \\ if (a != 0xabcdabcd or b != 0x5555) return 3;
+ \\ if (threadlocal_foo != 1) return 4;
+ \\ return 0;
+ \\}
+ ,
+ });
+ exe.root_module.addObject(obj);
+
+ const run = case.addRunArtifact(exe);
+ run.addCheck(.{ .expect_term = .{ .exited = 0 } });
+ }
+
+ if (ctx.includeTest("dynamic-lib-code")) |case| {
+ const lib = case.addLibrary(.dynamic, .{
+ .name = "lib",
+ .name_target = false,
+ .zig_source_bytes =
+ \\export fn foo1() callconv(.c) u64 {
+ \\ return 0x1122334411223344;
+ \\}
+ \\export fn foo2() callconv(.c) u64 {
+ \\ return 0xaabbccddaabbccdd;
+ \\}
+ ,
+ });
+
+ case.verifyObjdump(lib.getEmittedBin(), &.{
+ "-s",
+ "--exports",
+ "--only-symbol=foo",
+ }, .{ .os = true });
+
+ if (ctx.target.result.os.tag == .windows) {
+ case.verifyObjdump(lib.getEmittedImplib(), &.{
+ "-s",
+ "--exports=sort",
+ "--only-symbol=foo",
+ }, .{ .sub_name = "implib", .os = true, .arch = true });
+ }
+
+ const exe = case.addExecutable(.{
+ .name = "test",
+ .zig_source_bytes =
+ \\extern fn foo1() u64;
+ \\pub fn main() !u8 {
+ \\ const foo2 = @extern(
+ \\ *const fn () callconv(.c) u64,
+ \\ .{ .name = "foo2", .is_dll_import = true },
+ \\ );
+ \\ return @intFromBool(0xbbde0021bbde0021 != foo1() + foo2());
+ \\}
+ ,
+ });
+ exe.root_module.linkLibrary(lib);
+
+ const run = case.addRunArtifact(exe);
+ run.addCheck(.{ .expect_term = .{ .exited = 0 } });
+ }
+
+ if (ctx.includeTest("dynamic-lib-data")) |case| {
+ const lib = case.addLibrary(.dynamic, .{
+ .name = "lib",
+ .name_target = false,
+ .zig_source_bytes =
+ \\export var foo_array: [2]u16 = .{ 0xffff, 0xabcd };
+ \\export var foo_strong: usize = 0x1122334411223344;
+ \\comptime {
+ \\ @export(&foo_strong, .{ .name = "foo_strong_alias", .linkage = .strong });
+ \\}
+ ,
+ });
+
+ case.verifyObjdump(lib.getEmittedBin(), &.{
+ "-s",
+ "--exports",
+ "--only-symbol=foo",
+ }, .{});
+
+ if (ctx.target.result.os.tag == .windows) {
+ case.verifyObjdump(lib.getEmittedImplib(), &.{
+ "-s",
+ "--exports=sort",
+ "--only-symbol=foo",
+ }, .{ .sub_name = "implib", .os = true, .arch = true });
+ }
+
+ const exe = case.addExecutable(.{
+ .name = "test",
+ .zig_source_bytes =
+ \\pub fn main() !u8 {
+ \\ const foo_array = @extern(*[2]u16, .{ .name = "foo_array", .is_dll_import = true });
+ \\ const foo_strong = @extern(*usize, .{ .name = "foo_strong", .is_dll_import = true });
+ \\ const foo_strong_alias = @extern(*usize, .{ .name = "foo_strong_alias", .is_dll_import = true });
+ \\ return @intFromBool(0x2244668822451255 !=
+ \\ foo_array[1] +
+ \\ foo_strong.* +
+ \\ foo_strong_alias.*);
+ \\}
+ ,
+ });
+ exe.root_module.linkLibrary(lib);
+
+ const run = case.addRunArtifact(exe);
+ run.addCheck(.{ .expect_term = .{ .exited = 0 } });
+ }
+
+ if (ctx.includeTest("abs-symbol")) |case| {
+ const abs = case.addObject(.{
+ .name = "abs",
+ .use_llvm = true, // TODO: .globl not supported on self-hosted
+ .use_lld = true,
+ .asm_source_bytes =
+ \\.globl foo
+ \\foo = 0xcafecafe
+ \\
+ ,
+ });
+
+ const abs_reloc = case.addObject(.{
+ .name = "abs_reloc",
+ .use_llvm = true, // TODO: .globl not supported on self-hosted
+ .use_lld = true,
+ .asm_source_bytes =
+ \\.data
+ \\.globl foo_copy
+ \\foo_copy:
+ \\.long foo
+ ,
+ });
+
+ case.verifyObjdump(abs_reloc.getEmittedBin(), &.{
+ "-s",
+ "--relocs",
+ }, .{ .arch = true });
+
+ const exe = case.addExecutable(.{
+ .name = "test",
+ .zig_source_bytes =
+ \\extern var foo_copy: u32;
+ \\pub fn main() !u8 {
+ \\ return @intFromBool(foo_copy != 0xcafecafe);
+ \\}
+ ,
+ });
+ exe.root_module.addObject(abs);
+ exe.root_module.addObject(abs_reloc);
+
+ const run = case.addRunArtifact(exe);
+ run.addCheck(.{ .expect_term = .{ .exited = 0 } });
+
+ if (!ctx.use_llvm) {
+ const exe_reloc_err = case.addExecutable(.{
+ .name = "test-reloc-err",
+ .zig_source_bytes =
+ \\extern const foo: u32;
+ \\pub fn main() !u8 {
+ \\ return @intFromBool(foo != 0xcafecafe);
+ \\}
+ ,
+ });
+ exe_reloc_err.root_module.addObject(abs);
+ case.expectLinkErrors(exe_reloc_err, .{
+ .contains = "error: absolute symbol 'foo' targeted by invalid relocation type: /?/",
+ });
+ }
+ }
+
+ if (ctx.includeTest("explicit-extern-lib-name")) |case| {
+ // TODO: Lld.zig does not look at explicit inputs to resolve explicit extern lib names
+ if (ctx.use_llvm) return;
+
+ const lib1 = case.addLibrary(.dynamic, .{
+ .name = "lib1",
+ .name_target = false,
+ .zig_source_bytes =
+ \\export fn foo() u8 {
+ \\ return 43;
+ \\}
+ ,
+ });
+
+ const lib2 = case.addLibrary(.dynamic, .{
+ .name = "lib2",
+ .name_target = false,
+ .zig_source_bytes =
+ \\export fn foo() u8 {
+ \\ return 42;
+ \\}
+ ,
+ });
+
+ const lib3 = case.addLibrary(.static, .{
+ .name = "lib3",
+ .zig_source_bytes =
+ \\extern fn foo() u8;
+ \\export fn callFoo() u8 {
+ \\ return foo();
+ \\}
+ ,
+ });
+
+ const exe = case.addExecutable(.{
+ .name = "test",
+ .zig_source_bytes =
+ \\extern "explicit-extern-lib-name-lib2" fn foo() u8;
+ \\extern fn callFoo() u8;
+ \\pub fn main() !u8 {
+ \\ return foo() + callFoo();
+ \\}
+ ,
+ });
+ exe.root_module.linkLibrary(lib1);
+ exe.root_module.linkLibrary(lib2);
+ // exe.root_module.addLibraryPath(.{
+ // .generated = .{
+ // .index = lib2.getEmittedBin().generated.index,
+ // .up = 1,
+ // },
+ // });
+ exe.root_module.linkLibrary(lib3);
+
+ const run = case.addRunArtifact(exe);
+ run.addCheck(.{ .expect_term = .{ .exited = 84 } });
+ }
+}
+
+const LinkContext = @import("tests.zig").LinkContext;
+const std = @import("std");
diff --git a/test/link/exports.zig b/test/link/exports.zig
@@ -0,0 +1,9 @@
+export fn foo_fn() void {}
+var foo_var: u32 = 1234;
+comptime {
+ @export(&foo_var, .{ .name = "foo_var", .linkage = .strong });
+}
+const foo_const: u64 = 5678;
+comptime {
+ @export(&foo_const, .{ .name = "foo_const", .linkage = .strong });
+}
diff --git a/test/link/mingw.zig b/test/link/mingw.zig
@@ -0,0 +1,48 @@
+pub fn addCases(ctx: *LinkContext) void {
+ if (ctx.includeTest("ctor-dtor")) |case| {
+ if (!ctx.link_libc) return;
+
+ const obj = case.addObject(.{
+ .name = "obj",
+ .use_llvm = true,
+ .use_lld = true,
+ .c_source_bytes =
+ \\#include <stdlib.h>
+ \\int foo;
+ \\__attribute__((constructor))
+ \\static void init_foo() {
+ \\ foo = 42;
+ \\}
+ \\__attribute__((destructor))
+ \\static void deinit_foo() {
+ \\ exit(42);
+ \\}
+ ,
+ });
+
+ const lib = case.addLibrary(.static, .{
+ .name = "lib",
+ .name_prefix = false,
+ .name_target = false,
+ });
+ lib.root_module.addObject(obj);
+
+ const exe = case.addExecutable(.{
+ .name = "test",
+ .zig_source_bytes =
+ \\extern var foo: u32;
+ \\pub fn main() !u8 {
+ \\ if (foo != 42) return 1;
+ \\ return 2;
+ \\}
+ ,
+ });
+ exe.root_module.addObject(obj);
+
+ const run = case.addRunArtifact(exe);
+ run.addCheck(.{ .expect_term = .{ .exited = 42 } });
+ }
+}
+
+const LinkContext = @import("../tests.zig").LinkContext;
+const std = @import("std");
diff --git a/test/link/snapshots/.gitattributes b/test/link/snapshots/.gitattributes
@@ -0,0 +1 @@
+*.dmp eol=lf
diff --git a/test/link/snapshots/abs-symbol.x86_64.dmp b/test/link/snapshots/abs-symbol.x86_64.dmp
@@ -0,0 +1 @@
+xxxxxxxx ADDR32 xxxxxxxx UNDEF | foo
diff --git a/test/link/snapshots/dynamic-lib-code.implib-x86_64-windows.dmp b/test/link/snapshots/dynamic-lib-code.implib-x86_64-windows.dmp
@@ -0,0 +1,32 @@
+ 0 date
+ 0 user_id
+ 0 group_id
+ 0 file_mode
+xxxxxxxxxxxxxxxx size
+ second_linker type
+ | x symbols
+ | x members
+xxxxxxxx __imp_foo1
+xxxxxxxx __imp_foo2
+xxxxxxxx foo1
+xxxxxxxx foo2
+ 0 version
+ 8664 machine (AMD64)
+ 0 time_date_stamp
+xxxxxxxxxxxxxxxx size_of_data
+xxxxxxxxxxxxxxxx hint
+ CODE import_type
+ NAME name_type
+ symbol name | foo1
+ import name | foo1
+ dll | dynamic-lib-code-lib.dll
+ 0 version
+ 8664 machine (AMD64)
+ 0 time_date_stamp
+xxxxxxxxxxxxxxxx size_of_data
+xxxxxxxxxxxxxxxx hint
+ CODE import_type
+ NAME name_type
+ symbol name | foo2
+ import name | foo2
+ dll | dynamic-lib-code-lib.dll
diff --git a/test/link/snapshots/dynamic-lib-code.windows.dmp b/test/link/snapshots/dynamic-lib-code.windows.dmp
@@ -0,0 +1,13 @@
+Export directory:
+ 0 flags
+ 0 time_date_stamp
+ 0.00 version
+xxxxxxxxxxxxxxxx name_rva
+ 1 ordinal_base
+xxxxxxxxxxxxxxxx number_of_entries
+xxxxxxxxxxxxxxxx number_of_names
+xxxxxxxxxxxxxxxx export_address_table_rva
+xxxxxxxxxxxxxxxx name_pointer_table_rva
+xxxxxxxxxxxxxxxx ordinal_table_rva
+xxxx xxxx xxxxxxxx | foo1
+xxxx xxxx xxxxxxxx | foo2
diff --git a/test/link/snapshots/dynamic-lib-data.dmp b/test/link/snapshots/dynamic-lib-data.dmp
@@ -0,0 +1,14 @@
+Export directory:
+ 0 flags
+ 0 time_date_stamp
+ 0.00 version
+xxxxxxxxxxxxxxxx name_rva
+ 1 ordinal_base
+xxxxxxxxxxxxxxxx number_of_entries
+xxxxxxxxxxxxxxxx number_of_names
+xxxxxxxxxxxxxxxx export_address_table_rva
+xxxxxxxxxxxxxxxx name_pointer_table_rva
+xxxxxxxxxxxxxxxx ordinal_table_rva
+xxxx xxxx xxxxxxxx | foo_array
+xxxx xxxx xxxxxxxx | foo_strong
+xxxx xxxx xxxxxxxx | foo_strong_alias
diff --git a/test/link/snapshots/dynamic-lib-data.implib-x86_64-windows.dmp b/test/link/snapshots/dynamic-lib-data.implib-x86_64-windows.dmp
@@ -0,0 +1,41 @@
+ 0 date
+ 0 user_id
+ 0 group_id
+ 0 file_mode
+xxxxxxxxxxxxxxxx size
+ second_linker type
+ | x symbols
+ | x members
+xxxxxxxx __imp_foo_array
+xxxxxxxx __imp_foo_strong
+xxxxxxxx __imp_foo_strong_alias
+ 0 version
+ 8664 machine (AMD64)
+ 0 time_date_stamp
+xxxxxxxxxxxxxxxx size_of_data
+xxxxxxxxxxxxxxxx hint
+ DATA import_type
+ NAME name_type
+ symbol name | foo_array
+ import name | foo_array
+ dll | dynamic-lib-data-lib.dll
+ 0 version
+ 8664 machine (AMD64)
+ 0 time_date_stamp
+xxxxxxxxxxxxxxxx size_of_data
+xxxxxxxxxxxxxxxx hint
+ DATA import_type
+ NAME name_type
+ symbol name | foo_strong
+ import name | foo_strong
+ dll | dynamic-lib-data-lib.dll
+ 0 version
+ 8664 machine (AMD64)
+ 0 time_date_stamp
+xxxxxxxxxxxxxxxx size_of_data
+xxxxxxxxxxxxxxxx hint
+ DATA import_type
+ NAME name_type
+ symbol name | foo_strong_alias
+ import name | foo_strong_alias
+ dll | dynamic-lib-data-lib.dll
diff --git a/test/link/snapshots/static-lib.llvm.dmp b/test/link/snapshots/static-lib.llvm.dmp
@@ -0,0 +1,15 @@
+lib.lib: COFF archive
+lib.lib(obj1.obj): COFF object
+xxxx 00000000 1 NULL() EXTERNAL | fooBar
+xxxx 00000000 2 NULL EXTERNAL | foo1
+xxxx 00000004 2 NULL EXTERNAL | foo2
+lib.lib(this_is_a_long_name.obj): COFF object
+xxxx 00000000 1 NULL() STATIC | this_is_a_long_name.fooWeak
+xxxx 00000000 2 NULL STATIC | this_is_a_long_name.foo_strong
+xxxx 00000008 2 NULL STATIC | this_is_a_long_name.foo_array
+xxxx 00000000 2 NULL EXTERNAL | foo_strong
+xxxx 00000000 2 NULL EXTERNAL | foo_strong_alias
+xxxx 00000008 2 NULL EXTERNAL | foo_array
+xxxx 00000000 UNDEF NULL WEAK_EXTERNAL | fooWeak
+ | Weak External [falls back to relative ordinal 000000+2 via SEARCH_ALIAS]
+xxxx 00000000 1 NULL() EXTERNAL | .weak.fooWeak.default.foo_strong
diff --git a/test/link/snapshots/static-lib.no-llvm.dmp b/test/link/snapshots/static-lib.no-llvm.dmp
@@ -0,0 +1,12 @@
+lib.lib: COFF archive
+lib.lib(obj1.obj): COFF object
+xxxx 00000000 1 NULL() EXTERNAL | fooBar
+xxxx 00000000 2 NULL EXTERNAL | foo1
+xxxx 00000004 2 NULL EXTERNAL | foo2
+lib.lib(this_is_a_long_name.obj): COFF object
+xxxx 00000000 4 NULL() EXTERNAL | this_is_a_long_name.fooWeak
+xxxx 00000000 2 NULL EXTERNAL | foo_strong
+xxxx 00000000 2 NULL EXTERNAL | foo_strong_alias
+xxxx 00000010 2 NULL EXTERNAL | foo_array
+xxxx 00000000 UNDEF NULL() WEAK_EXTERNAL | fooWeak
+ | Weak External [falls back to relative ordinal 000000-4 via SEARCH_ALIAS]
diff --git a/test/link/snapshots/tls.llvm.dmp b/test/link/snapshots/tls.llvm.dmp
@@ -0,0 +1,9 @@
+xxxx 00000000 6 NULL STATIC | .tls$
+ | Section [size xxxxxxxx chksum a194a569 relocs 0000 lines 0000]
+xxxx 00000000 1 NULL() STATIC | obj.threadlocal_write
+xxxx 00000000 UNDEF NULL EXTERNAL | _tls_index
+xxxx 00000000 6 NULL STATIC | obj.threadlocal_var
+xxxx 00000004 6 NULL STATIC | obj.threadlocal_arr
+xxxx 00000040 1 NULL() STATIC | obj.threadlocal_read
+xxxx 00000000 1 NULL() EXTERNAL | threadlocal_write
+xxxx 00000040 1 NULL() EXTERNAL | threadlocal_read
diff --git a/test/link/snapshots/tls.no-llvm.dmp b/test/link/snapshots/tls.no-llvm.dmp
@@ -0,0 +1,7 @@
+xxxx 00000000 5 NULL STATIC | .tls$
+ | Section [size xxxxxxxx chksum 00000000 relocs 0000 lines 0000]
+xxxx 00000000 5 NULL STATIC | obj.threadlocal_var
+xxxx 00000008 5 NULL STATIC | obj.threadlocal_arr
+xxxx 00000000 UNDEF NULL EXTERNAL | _tls_index
+xxxx 00000000 4 NULL() EXTERNAL | threadlocal_write
+xxxx 00000060 4 NULL() EXTERNAL | threadlocal_read
diff --git a/test/src/Link.zig b/test/src/Link.zig
@@ -0,0 +1,281 @@
+b: *Build,
+step: *Step,
+optimize: std.builtin.OptimizeMode,
+target: std.Build.ResolvedTarget,
+target_desc: []const u8,
+use_llvm: bool,
+use_lld: bool,
+link_libc: bool,
+test_filters: []const []const u8,
+update_step: ?*Step.UpdateSourceFiles,
+updated_snapshots: std.StringArrayHashMapUnmanaged(void),
+max_rss: usize,
+
+pub fn includeTest(self: *Link, prefix: []const u8) ?Case {
+ if (for (self.test_filters) |filter| {
+ if (std.mem.containsAtLeast(u8, prefix, 1, filter)) break false;
+ } else self.test_filters.len > 0) return null;
+
+ return .{
+ .ctx = self,
+ .prefix = prefix,
+ };
+}
+
+pub fn sourcePath(self: *const Link, sub_path: []const u8) std.Build.LazyPath {
+ return self.b.path(self.b.pathJoin(&.{ "test/link", sub_path }));
+}
+
+pub const Case = struct {
+ ctx: *Link,
+ prefix: []const u8,
+
+ fn resolveName(self: *const Case, overlay: *const OverlayOptions) []const u8 {
+ if (!overlay.name_prefix and !overlay.name_target)
+ return overlay.name;
+
+ if (overlay.name_prefix == overlay.name_target)
+ return self.ctx.b.fmt("{s}-{s}-{s}", .{ self.prefix, overlay.name, self.ctx.target_desc })
+ else if (overlay.name_prefix)
+ return self.ctx.b.fmt("{s}-{s}", .{ self.prefix, overlay.name })
+ else
+ return self.ctx.b.fmt("{s}-{s}", .{ overlay.name, self.ctx.target_desc });
+ }
+
+ pub fn addLibrary(
+ self: *const Case,
+ linkage: std.builtin.LinkMode,
+ overlay: OverlayOptions,
+ ) *Step.Compile {
+ return self.ctx.b.addLibrary(.{
+ .linkage = linkage,
+ .name = self.resolveName(&overlay),
+ .root_module = self.ctx.createModule(overlay),
+ .use_llvm = overlay.use_llvm orelse self.ctx.use_llvm,
+ .use_lld = overlay.use_lld orelse self.ctx.use_lld,
+ });
+ }
+
+ pub fn addExecutable(
+ self: *const Case,
+ overlay: OverlayOptions,
+ ) *Step.Compile {
+ return self.ctx.b.addExecutable(.{
+ .name = self.resolveName(&overlay),
+ .root_module = self.ctx.createModule(overlay),
+ .use_llvm = overlay.use_llvm orelse self.ctx.use_llvm,
+ .use_lld = overlay.use_lld orelse self.ctx.use_lld,
+ });
+ }
+
+ pub fn addRunArtifact(
+ self: *const Case,
+ exe: *Step.Compile,
+ ) *Step.Run {
+ const run_step = self.ctx.b.addRunArtifact(exe);
+ run_step.skip_foreign_checks = true;
+ self.ctx.step.dependOn(&run_step.step);
+ return run_step;
+ }
+
+ pub fn addObject(
+ self: *const Case,
+ overlay: OverlayOptions,
+ ) *Step.Compile {
+ return self.ctx.b.addObject(.{
+ .name = self.resolveName(&overlay),
+ .root_module = self.ctx.createModule(overlay),
+ .use_llvm = overlay.use_llvm orelse self.ctx.use_llvm,
+ .use_lld = overlay.use_lld orelse self.ctx.use_lld,
+ });
+ }
+
+ pub fn expectLinkErrors(
+ self: *const Case,
+ comp: *Step.Compile,
+ expected_errors: Step.Compile.ExpectedCompileErrors,
+ ) void {
+ comp.expect_errors = expected_errors;
+ const bin_file = comp.getEmittedBin();
+ bin_file.addStepDependencies(self.ctx.step);
+ }
+
+ const SnapshotScope = struct {
+ /// If a test case has multiple verifyObjdump calls, `opt_sub_name` should
+ /// be used to differentiate them.
+ sub_name: ?[]const u8 = null,
+ arch: bool = false,
+ os: bool = false,
+ abi: bool = false,
+ optimize: bool = false,
+ use_llvm: bool = false,
+ use_lld: bool = false,
+ link_libc: bool = false,
+ };
+
+ /// Verify the results of a `zig objdump` call against a snapshot, which
+ /// contains the expected output. Snapshots alias between all build
+ /// configurations by default, but by specifying fields in `scope`,
+ /// unique snapshot names are generated for each value of that field.
+ pub fn verifyObjdump(
+ self: *const Case,
+ file: Build.LazyPath,
+ args: []const []const u8,
+ scope: SnapshotScope,
+ ) void {
+ const ctx = self.ctx;
+ const snapshot_name = self.snapshotName(scope) catch @panic("OOM");
+ const snapshot_sub_path = ctx.b.pathJoin(&.{ "test/link/snapshots/", snapshot_name });
+
+ // Many tests may read the same snapshot, so only use the first one to update.
+ // If there are differences in output, they will show up on the next test run.
+ if (ctx.update_step != null) {
+ const gop = ctx.updated_snapshots.getOrPut(ctx.b.allocator, snapshot_sub_path) catch @panic("OOM");
+ if (gop.found_existing) return;
+ }
+
+ const run_step = Step.Run.create(ctx.b, ctx.b.fmt(
+ "objdump {s} {s}",
+ .{ snapshot_name, ctx.target_desc },
+ ));
+ run_step.addArgs(&.{ ctx.b.graph.zig_exe, "objdump" });
+ run_step.addFileArg(file);
+ run_step.addArgs(args);
+ run_step.addCheck(.{ .expect_term = .{ .exited = 0 } });
+
+ if (ctx.update_step) |update_step| {
+ // Workaround for the build system not realizing objdump itself has changed
+ run_step.has_side_effects = true;
+
+ const snapshot_update_path = run_step.captureStdOut(.{});
+ update_step.addCopyFileToSource(snapshot_update_path, snapshot_sub_path);
+ } else {
+ run_step.addCheck(.{ .expect_stdout_snapshot = ctx.b.path(snapshot_sub_path) });
+ }
+
+ ctx.step.dependOn(&run_step.step);
+ }
+
+ fn snapshotName(
+ self: *const Case,
+ scope: SnapshotScope,
+ ) ![]const u8 {
+ const ctx = self.ctx;
+ var snapshot_name: std.Io.Writer.Allocating = .init(ctx.b.allocator);
+ const w = &snapshot_name.writer;
+
+ try w.writeAll(self.prefix);
+ var sep: u8 = '.';
+
+ if (try snapshotNameInner(w, scope.sub_name != null, &sep))
+ try w.writeAll(scope.sub_name.?);
+ if (try snapshotNameInner(w, scope.arch, &sep))
+ try w.print("{t}", .{ctx.target.result.cpu.arch});
+ if (try snapshotNameInner(w, scope.os, &sep))
+ try w.print("{t}", .{ctx.target.result.os.tag});
+ if (try snapshotNameInner(w, scope.abi, &sep))
+ try w.print("{t}", .{ctx.target.result.abi});
+ if (try snapshotNameInner(w, scope.optimize, &sep))
+ try w.print("{t}", .{ctx.optimize});
+ if (try snapshotNameInner(w, scope.use_llvm, &sep))
+ try w.writeAll(if (ctx.use_llvm) "llvm" else "no-llvm");
+ if (try snapshotNameInner(w, scope.use_lld, &sep))
+ try w.writeAll(if (ctx.use_lld) "lld" else "no-lld");
+ if (try snapshotNameInner(w, scope.link_libc, &sep))
+ try w.writeAll(if (ctx.link_libc) "libc" else "no-libc");
+
+ if (sep == '-') sep = '.';
+ try w.writeByte(sep);
+ try w.writeAll("dmp");
+
+ return try snapshot_name.toOwnedSlice();
+ }
+
+ fn snapshotNameInner(w: *std.Io.Writer, cond: bool, sep: *u8) !bool {
+ if (cond) {
+ try w.writeByte(sep.*);
+ sep.* = '-';
+ }
+
+ return cond;
+ }
+};
+
+fn createModule(self: *const Link, overlay: OverlayOptions) *Build.Module {
+ const write_files = self.b.addWriteFiles();
+
+ const mod = self.b.createModule(.{
+ .target = self.target,
+ .optimize = self.optimize,
+ .root_source_file = overlay.zig_source_file orelse rsf: {
+ const bytes = overlay.zig_source_bytes orelse break :rsf null;
+ const name = self.b.fmt("{s}.zig", .{overlay.name});
+ break :rsf write_files.add(name, bytes);
+ },
+ .link_libc = self.link_libc, // TODO: Should this be in overlay instead?
+ .pic = overlay.pic,
+ .strip = 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;
+}
+
+const OverlayOptions = struct {
+ name: []const u8,
+ /// Prefix the name with the test case prefix.
+ /// Unset if names with specific lengths are needed.
+ name_prefix: bool = true,
+ /// Prefix the name with `target_desc`.
+ /// Can be unset when the snapshot needs to contain the name,
+ /// so that snapshots can alias between targets.
+ name_target: bool = true,
+ 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,
+ zig_source_file: ?std.Build.LazyPath = null,
+ pic: ?bool = null,
+ strip: ?bool = null,
+ use_llvm: ?bool = null,
+ use_lld: ?bool = null,
+};
+
+const std = @import("std");
+const Build = std.Build;
+const Step = Build.Step;
+
+const Link = @This();
diff --git a/test/standalone/shared_library/build.zig b/test/standalone/shared_library/build.zig
@@ -7,11 +7,47 @@ pub fn build(b: *std.Build) void {
const optimize: std.builtin.OptimizeMode = .Debug;
const target = b.standardTargetOptions(.{});
- const exe_names: []const []const u8 = &.{ "test", "test-dync" };
- const lib_names: []const []const u8 = &.{ "mathtest", "mathtest-dync" };
- const lib_link_libc: []const bool = &.{ false, true };
+ const exe_names: []const []const u8 = &.{
+ "test",
+ "test-dync",
+ "test-no-llvm",
+ "test-no-llvm-dync",
+ "test-exe-no-llvm",
+ "test-dync-exe-no-llvm",
+ "test-no-llvm-exe-no-llvm",
+ "test-no-llvm-dync-exe-no-llvm",
+ };
+ const lib_names: []const []const u8 = &.{
+ "mathtest",
+ "mathtest-dync",
+ "mathtest-no-llvm",
+ "mathtest-no-llvm-dync",
+ "mathtest-exe-no-llvm",
+ "mathtest-dync-exe-no-llvm",
+ "mathtest-no-llvm-exe-no-llvm",
+ "mathtest-no-llvm-dync-exe-no-llvm",
+ };
+ const lib_link_libc: []const bool = &.{ false, true, false, true, false, true, false, true };
+ const lib_use_llvm: []const bool = &.{ true, true, false, false, true, true, false, false };
+ const exe_use_llvm: []const bool = &.{ true, true, true, true, false, false, false, false };
+
+ for (
+ exe_names,
+ lib_names,
+ lib_link_libc,
+ lib_use_llvm,
+ exe_use_llvm,
+ ) |exe_name, lib_name, dyn_libc, lib_llvm, exe_llvm| {
+ const no_llvm = !lib_llvm or !exe_llvm;
+ if (no_llvm and target.result.os.tag == .macos) continue; // TODO
+ if (no_llvm and target.result.os.tag == .freebsd) continue; // TODO
+ if (no_llvm and target.result.os.tag == .netbsd) continue; // TODO
+ if (no_llvm and target.result.os.tag == .openbsd) continue; // TODO
+ if (no_llvm and target.result.cpu.arch == .aarch64) continue; // TODO
+ if (no_llvm and target.result.cpu.arch == .loongarch64) continue; // TODO
+ if (no_llvm and target.result.cpu.arch == .powerpc64le) continue; // TODO
+ if (no_llvm and target.result.cpu.arch == .s390x) continue; // TODO
- for (exe_names, lib_names, lib_link_libc) |exe_name, lib_name, dyn_libc| {
const lib = b.addLibrary(.{
.linkage = .dynamic,
.name = lib_name,
@@ -22,6 +58,7 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
.link_libc = dyn_libc,
}),
+ .use_llvm = lib_llvm,
});
const exe = b.addExecutable(.{
@@ -32,6 +69,7 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
.link_libc = true,
}),
+ .use_llvm = exe_llvm,
});
exe.root_module.addCSourceFile(.{
.file = b.path("test.c"),
diff --git a/test/standalone/shared_library/mathtest.zig b/test/standalone/shared_library/mathtest.zig
@@ -1,3 +1,5 @@
+export var exported_var: i32 = 9999;
+
export fn add(a: i32, b: i32) i32 {
return a + b;
}
diff --git a/test/standalone/shared_library/test.c b/test/standalone/shared_library/test.c
@@ -7,7 +7,16 @@
#include <stdint.h>
int32_t add(int32_t a, int32_t b);
+#if _WIN32
+#define IMPORT __declspec(dllimport)
+#else
+#define IMPORT
+#endif
+
+extern IMPORT int32_t exported_var;
+
int main(int argc, char **argv) {
assert(add(42, 1337) == 1379);
+ assert(exported_var == 9999);
return 0;
}
diff --git a/test/standalone/static_c_lib/build.zig b/test/standalone/static_c_lib/build.zig
@@ -5,26 +5,76 @@ pub fn build(b: *std.Build) void {
b.default_step = test_step;
const optimize: std.builtin.OptimizeMode = .Debug;
+ const target = b.standardTargetOptions(.{});
- const foo = b.addLibrary(.{
- .linkage = .static,
- .name = "foo",
- .root_module = b.createModule(.{
- .root_source_file = null,
- .optimize = optimize,
- .target = b.graph.host,
- }),
- });
- foo.root_module.addCSourceFile(.{ .file = b.path("foo.c"), .flags = &[_][]const u8{} });
- foo.root_module.addIncludePath(b.path("."));
+ const exe_names: []const []const u8 = &.{
+ "test",
+ "test-dync",
+ "test-no-llvm",
+ "test-no-llvm-dync",
+ "test-exe-no-llvm",
+ "test-dync-exe-no-llvm",
+ "test-no-llvm-exe-no-llvm",
+ "test-no-llvm-dync-exe-no-llvm",
+ };
+ const lib_names: []const []const u8 = &.{
+ "foo",
+ "foo-dync",
+ "foo-no-llvm",
+ "foo-no-llvm-dync",
+ "foo-exe-no-llvm",
+ "foo-dync-exe-no-llvm",
+ "foo-no-llvm-exe-no-llvm",
+ "foo-no-llvm-dync-exe-no-llvm",
+ };
+ const lib_link_libc: []const bool = &.{ false, true, false, true, false, true, false, true };
+ const lib_use_llvm: []const bool = &.{ true, true, false, false, true, true, false, false };
+ const exe_use_llvm: []const bool = &.{ true, true, true, true, false, false, false, false };
- const test_exe = b.addTest(.{ .root_module = b.createModule(.{
- .root_source_file = b.path("foo.zig"),
- .target = b.graph.host,
- .optimize = optimize,
- }) });
- test_exe.root_module.linkLibrary(foo);
- test_exe.root_module.addIncludePath(b.path("."));
+ for (
+ exe_names,
+ lib_names,
+ lib_link_libc,
+ lib_use_llvm,
+ exe_use_llvm,
+ ) |exe_name, lib_name, dyn_libc, lib_llvm, exe_llvm| {
+ const no_llvm = !lib_llvm or !exe_llvm;
+ if (no_llvm and target.result.os.tag == .macos) continue; // TODO
+ if (no_llvm and target.result.os.tag == .freebsd) continue; // TODO
+ if (no_llvm and target.result.os.tag == .netbsd) continue; // TODO
+ if (no_llvm and target.result.os.tag == .openbsd) continue; // TODO
+ if (no_llvm and target.result.cpu.arch == .aarch64) continue; // TODO
+ if (no_llvm and target.result.cpu.arch == .loongarch64) continue; // TODO
+ if (no_llvm and target.result.cpu.arch == .powerpc64le) continue; // TODO
+ if (no_llvm and target.result.cpu.arch == .s390x) continue; // TODO
- test_step.dependOn(&b.addRunArtifact(test_exe).step);
+ const foo = b.addLibrary(.{
+ .linkage = .static,
+ .name = lib_name,
+ .root_module = b.createModule(.{
+ .root_source_file = null,
+ .optimize = optimize,
+ .target = target,
+ .link_libc = dyn_libc,
+ }),
+ .use_llvm = lib_llvm,
+ });
+ foo.root_module.addCSourceFile(.{ .file = b.path("foo.c"), .flags = &[_][]const u8{} });
+ foo.root_module.addIncludePath(b.path("."));
+
+ const test_exe = b.addTest(.{
+ .name = exe_name,
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("foo.zig"),
+ .target = target,
+ .optimize = optimize,
+ .link_libc = dyn_libc,
+ }),
+ .use_llvm = exe_llvm,
+ });
+ test_exe.root_module.linkLibrary(foo);
+ test_exe.root_module.addIncludePath(b.path("."));
+
+ test_step.dependOn(&b.addRunArtifact(test_exe).step);
+ }
}
diff --git a/test/tests.zig b/test/tests.zig
@@ -10,6 +10,7 @@ const error_traces = @import("error_traces.zig");
const stack_traces = @import("stack_traces.zig");
const llvm_ir = @import("llvm_ir.zig");
const libc = @import("libc.zig");
+const link = @import("link.zig");
// Implementations
pub const ErrorTracesContext = @import("src/ErrorTrace.zig");
@@ -17,6 +18,7 @@ pub const StackTracesContext = @import("src/StackTrace.zig");
pub const DebuggerContext = @import("src/Debugger.zig");
pub const LlvmIrContext = @import("src/LlvmIr.zig");
pub const LibcContext = @import("src/Libc.zig");
+pub const LinkContext = @import("src/Link.zig");
const ModuleTestTarget = struct {
linkage: ?std.builtin.LinkMode = null,
@@ -2019,42 +2021,132 @@ const c_abi_targets = blk: {
},
},
- //.{
- // .target = .{
- // .cpu_arch = .x86_64,
- // .os_tag = .windows,
- // .abi = .gnu,
- // },
- // .use_llvm = false,
- // .c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"},
- //},
- //.{
- // .target = .{
- // .cpu_arch = .x86_64,
- // .cpu_model = .{ .explicit = &std.Target.x86.cpu.x86_64_v2 },
- // .os_tag = .windows,
- // .abi = .gnu,
- // },
- // .use_llvm = false,
- // .c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"},
- //},
- //.{
- // .target = .{
- // .cpu_arch = .x86_64,
- // .cpu_model = .{ .explicit = &std.Target.x86.cpu.x86_64_v3 },
- // .os_tag = .windows,
- // .abi = .gnu,
- // },
- // .use_llvm = false,
- // .c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"},
- //},
.{
.target = .{
.cpu_arch = .x86_64,
.os_tag = .windows,
.abi = .gnu,
},
+ .use_llvm = false,
+ .c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"},
+ },
+ .{
+ .target = .{
+ .cpu_arch = .x86_64,
+ .cpu_model = .{ .explicit = &std.Target.x86.cpu.x86_64_v2 },
+ .os_tag = .windows,
+ .abi = .gnu,
+ },
+ .use_llvm = false,
+ .c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"},
+ },
+ .{
+ .target = .{
+ .cpu_arch = .x86_64,
+ .cpu_model = .{ .explicit = &std.Target.x86.cpu.x86_64_v3 },
+ .os_tag = .windows,
+ .abi = .gnu,
+ },
+ .use_llvm = false,
+ .c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"},
+ },
+ .{
+ .target = .{
+ .cpu_arch = .x86_64,
+ .os_tag = .windows,
+ .abi = .gnu,
+ },
+ .use_llvm = true,
+ },
+ };
+};
+
+const LinkTarget = struct {
+ target: std.Target.Query = .{},
+ optimize_mode: std.builtin.OptimizeMode = .Debug,
+ link_libc: bool = false,
+ use_llvm: bool = false,
+ use_lld: bool = false,
+};
+
+const link_targets = blk: {
+ @setEvalBranchQuota(30000);
+ break :blk [_]LinkTarget{
+ // Native Targets
+
+ // .{
+ // .use_llvm = true,
+ // },
+
+ // Windows Targets
+
+ .{
+ .target = .{
+ .cpu_arch = .x86_64,
+ .os_tag = .windows,
+ .abi = .gnu,
+ },
+ },
+ .{
+ .target = .{
+ .cpu_arch = .x86_64,
+ .os_tag = .windows,
+ .abi = .gnu,
+ },
+ .link_libc = true,
+ },
+ .{
+ .target = .{
+ .cpu_arch = .x86_64,
+ .os_tag = .windows,
+ .abi = .gnu,
+ },
+ .use_llvm = true,
+ .use_lld = true,
+ },
+ .{
+ .target = .{
+ .cpu_arch = .x86_64,
+ .os_tag = .windows,
+ .abi = .gnu,
+ },
+ .link_libc = true,
+ .use_llvm = true,
+ .use_lld = true,
+ },
+ .{
+ .target = .{
+ .cpu_arch = .x86_64,
+ .os_tag = .windows,
+ .abi = .msvc,
+ },
+ },
+ .{
+ .target = .{
+ .cpu_arch = .x86_64,
+ .os_tag = .windows,
+ .abi = .msvc,
+ },
+ .link_libc = true,
+ },
+ .{
+ .target = .{
+ .cpu_arch = .x86_64,
+ .os_tag = .windows,
+ .abi = .msvc,
+ },
+ .use_llvm = true,
+ .use_lld = true,
+ },
+ .{
+ .target = .{
+ .cpu_arch = .x86_64,
+ .os_tag = .windows,
+ .abi = .msvc,
+ },
+ .link_libc = true,
.use_llvm = true,
+ .use_lld = true,
},
};
};
@@ -3083,6 +3175,77 @@ pub fn addCAbiTests(b: *std.Build, options: CAbiTestOptions) *Step {
return step;
}
+const LinkTestOptions = struct {
+ test_target_filters: []const []const u8,
+ test_filters: []const []const u8,
+ optimize_modes: []const OptimizeMode,
+ skip_non_native: bool,
+ skip_windows: bool,
+ skip_llvm: bool,
+ max_rss: usize,
+};
+
+pub fn addLinkTests(b: *std.Build, options: LinkTestOptions) *Step {
+ const step = b.step("test-link", "Run the linker tests");
+ const update_snapshots = b.option(
+ bool,
+ "link-snapshot-update",
+ "Update linker test snapshots in-place instead of testing against them",
+ ) orelse false;
+
+ for (link_targets) |link_target| {
+ if (options.skip_non_native and !link_target.target.isNative()) continue;
+ if (options.skip_windows and link_target.target.os_tag == .windows) continue;
+
+ const resolved_target = b.resolveTargetQuery(link_target.target);
+ const triple_txt = resolved_target.query.zigTriple(b.allocator) catch @panic("OOM");
+ const target = &resolved_target.result;
+
+ if (options.test_target_filters.len > 0) {
+ for (options.test_target_filters) |filter| {
+ if (std.mem.indexOf(u8, triple_txt, filter) != null) break;
+ } else continue;
+ }
+
+ for (options.optimize_modes) |optimize_mode| {
+ if (link_target.optimize_mode != optimize_mode) continue;
+ if (link_target.link_libc and target.abi == .msvc and b.graph.host.result.os.tag != .windows) continue;
+ const would_use_llvm = wouldUseLlvm(link_target.use_llvm, link_target.target, optimize_mode);
+ if (options.skip_llvm and would_use_llvm) continue;
+
+ const opt_update_step = if (update_snapshots) update: {
+ const update_step = Step.UpdateSourceFiles.create(b);
+ step.dependOn(&update_step.step);
+ break :update update_step;
+ } else null;
+
+ var context: LinkContext = .{
+ .b = b,
+ .step = step,
+ .optimize = optimize_mode,
+ .target = resolved_target,
+ .target_desc = std.fmt.allocPrint(b.allocator, "{s}-{t}{s}{s}{s}", .{
+ target.zigTriple(b.allocator) catch @panic("OOM"),
+ optimize_mode,
+ if (link_target.use_llvm) "-llvm" else "",
+ if (link_target.use_lld) "-lld" else "",
+ if (link_target.link_libc) "-libc" else "",
+ }) catch @panic("OOM"),
+ .use_llvm = link_target.use_llvm,
+ .use_lld = link_target.use_lld,
+ .link_libc = link_target.link_libc,
+ .test_filters = options.test_filters,
+ .update_step = opt_update_step,
+ .updated_snapshots = .empty,
+ .max_rss = options.max_rss,
+ };
+
+ link.addCases(&context);
+ }
+ }
+ return step;
+}
+
pub fn addCases(
b: *std.Build,
parent_step: *Step,