authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-06-25 19:20:51+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-06-25 19:20:51+02:00
logcd3288a3f6e48ee381196a6d6437309472c513fe
treef9a8cbf3b1fa6a6c72821abfa8afbc83c2c134bb
parent914b8f26b03944ac58900aefb3ab21fc988e6c7e
parent594b3faaa34f6f464a5c543489cf57fb89070999

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>

41 files changed, 9753 insertions(+), 845 deletions(-)

build.zig+9-3
......@@ -256,7 +256,6 @@ pub fn build(b: *std.Build) !void {
256256 const is_debug = optimize == .Debug;
257257 const enable_debug_extensions = b.option(bool, "debug-extensions", "Enable commands and options useful for debugging the compiler") orelse is_debug;
258258 const enable_logging = b.option(bool, "log", "Enable debug logging with --debug-log") orelse is_debug;
259 const enable_link_snapshots = b.option(bool, "link-snapshot", "Whether to enable linker state snapshots") orelse false;
260259
261260 const opt_version_string = b.option([]const u8, "version-string", "Override Zig version string. Default is to find out with git.");
262261 const version_slice = if (opt_version_string) |version| version else v: {
......@@ -372,7 +371,6 @@ pub fn build(b: *std.Build) !void {
372371
373372 exe_options.addOption(bool, "enable_debug_extensions", enable_debug_extensions);
374373 exe_options.addOption(bool, "enable_logging", enable_logging);
375 exe_options.addOption(bool, "enable_link_snapshots", enable_link_snapshots);
376374 exe_options.addOption(bool, "enable_tracy", tracy != null);
377375 exe_options.addOption(bool, "enable_tracy_callstack", tracy_callstack);
378376 exe_options.addOption(bool, "enable_tracy_allocation", tracy_allocation);
......@@ -629,6 +627,15 @@ pub fn build(b: *std.Build) !void {
629627 .skip_llvm = skip_llvm,
630628 .max_rss = 3_300_000_000,
631629 }));
630 test_step.dependOn(tests.addLinkTests(b, .{
631 .test_target_filters = test_target_filters,
632 .test_filters = test_filters,
633 .optimize_modes = optimize_modes,
634 .skip_non_native = skip_non_native,
635 .skip_windows = skip_windows,
636 .skip_llvm = skip_llvm,
637 .max_rss = 100_000_000,
638 }));
632639 test_step.dependOn(tests.addStackTraceTests(b, test_filters, skip_non_native));
633640 test_step.dependOn(tests.addErrorTraceTests(b, test_filters, optimize_modes, skip_non_native));
634641 test_step.dependOn(tests.addCliTests(b));
......@@ -724,7 +731,6 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
724731 exe_options.addOption(std.SemanticVersion, "semver", semver);
725732 exe_options.addOption(bool, "enable_debug_extensions", false);
726733 exe_options.addOption(bool, "enable_logging", false);
727 exe_options.addOption(bool, "enable_link_snapshots", false);
728734 exe_options.addOption(bool, "enable_tracy", false);
729735 exe_options.addOption(bool, "enable_tracy_callstack", false);
730736 exe_options.addOption(bool, "enable_tracy_allocation", false);
lib/compiler/Maker/Step/Run.zig+99-2
......@@ -2118,6 +2118,67 @@ fn runCommand(
21182118 });
21192119 }
21202120 }
2121 const snapshots: []const ?struct {
2122 path: Cache.Path,
2123 result: enum { stderr, stdout },
2124 } = &.{
2125 if (conf_run.expect_stderr_snapshot.value) |path| .{
2126 .path = try maker.resolveLazyPathIndex(arena, path, run_index),
2127 .result = .stderr,
2128 } else null,
2129 if (conf_run.expect_stdout_snapshot.value) |path| .{
2130 .path = try maker.resolveLazyPathIndex(arena, path, run_index),
2131 .result = .stdout,
2132 } else null,
2133 };
2134 for (snapshots) |opt_snapshot| {
2135 const snapshot = opt_snapshot orelse continue;
2136
2137 const file = snapshot.path.root_dir.handle.openFile(io, snapshot.path.sub_path, .{}) catch |err|
2138 return step.fail(maker, "unable to open snapshot file {f}: {t}", .{ snapshot.path, err });
2139 defer file.close(io);
2140
2141 var file_reader = file.reader(io, &.{});
2142 const snapshot_contents = file_reader.interface.allocRemaining(gpa, .unlimited) catch |err|
2143 return step.fail(maker, "unable to read snapshot file {f}: {t}", .{ snapshot.path, err });
2144 defer gpa.free(snapshot_contents);
2145
2146 const result = switch (snapshot.result) {
2147 .stderr => generic_result.stderr.?,
2148 .stdout => generic_result.stdout.?,
2149 };
2150 if (std.mem.findDiff(u8, snapshot_contents, result)) |diff_index| {
2151 var diff_line_number: usize = 1;
2152
2153 for (snapshot_contents[0..diff_index]) |value| {
2154 if (value == '\n') diff_line_number += 1;
2155 }
2156
2157 return step.fail(maker,
2158 \\
2159 \\========= snapshot file: =========
2160 \\{f}
2161 \\========= contained: =============
2162 \\{s}
2163 \\========= {t} output was: ========
2164 \\{s}
2165 \\==================================
2166 \\first difference on line {d}:
2167 \\expected:
2168 \\{f}
2169 \\found:
2170 \\{f}
2171 , .{
2172 snapshot.path,
2173 snapshot_contents,
2174 snapshot.result,
2175 result,
2176 diff_line_number,
2177 fmtSnapshotIndicatorLine(snapshot_contents, diff_index),
2178 fmtSnapshotIndicatorLine(result, diff_index),
2179 });
2180 }
2181 }
21212182 },
21222183 else => {
21232184 // On failure, report captured stderr like normal standard error output.
......@@ -2131,6 +2192,38 @@ fn runCommand(
21312192 }
21322193}
21332194
2195const FmtIndicatorLine = struct {
2196 buf: []const u8,
2197 index: usize,
2198};
2199
2200fn fmtSnapshotIndicatorLine(buf: []const u8, index: usize) std.fmt.Alt(
2201 FmtIndicatorLine,
2202 snapshotIndicatorLine,
2203) {
2204 return .{ .data = .{ .buf = buf, .index = index } };
2205}
2206
2207fn snapshotIndicatorLine(line: FmtIndicatorLine, w: *std.Io.Writer) std.Io.Writer.Error!void {
2208 const line_begin_index = if (std.mem.lastIndexOfScalar(u8, line.buf[0..line.index], '\n')) |line_begin|
2209 line_begin + 1
2210 else
2211 0;
2212 const line_end_index = if (std.mem.findScalar(u8, line.buf[line.index..], '\n')) |line_end|
2213 (line.index + line_end)
2214 else
2215 line.buf.len;
2216
2217 try w.writeAll(line.buf[line_begin_index..line_end_index]);
2218 try w.writeByte('\n');
2219 try w.splatByteAll(' ', line_end_index - line_begin_index);
2220 try w.writeByte('\n');
2221 if (line.index >= line.buf.len)
2222 try w.writeAll("^ (end of file)")
2223 else
2224 try w.print("^ ('\\x{x:0>2}')\n", .{line.buf[line.index]});
2225}
2226
21342227const EvalGenericResult = struct {
21352228 term: process.Child.Term,
21362229 stdout: ?[]const u8,
......@@ -2301,11 +2394,15 @@ fn setColorEnvironmentVariables(
23012394}
23022395
23032396fn checksContainStdout(conf_run: *const Configuration.Step.Run) bool {
2304 return conf_run.expect_stdout_exact.value != null or conf_run.expect_stdout_match.slice.len != 0;
2397 return conf_run.expect_stdout_exact.value != null or
2398 conf_run.expect_stdout_match.slice.len != 0 or
2399 conf_run.expect_stdout_snapshot.value != null;
23052400}
23062401
23072402fn checksContainStderr(conf_run: *const Configuration.Step.Run) bool {
2308 return conf_run.expect_stderr_exact.value != null or conf_run.expect_stderr_match.slice.len != 0;
2403 return conf_run.expect_stderr_exact.value != null or
2404 conf_run.expect_stderr_match.slice.len != 0 or
2405 conf_run.expect_stderr_snapshot.value != null;
23092406}
23102407
23112408/// If `path` is absolute, return it unchanged. If `make_absolute` is true, make it absolute.
lib/compiler/configurer.zig+8
......@@ -1011,6 +1011,8 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {
10111011 status: Configuration.Step.Run.ExpectTermStatus,
10121012 value: u32,
10131013 } = null;
1014 var expect_stderr_snapshot: ?Configuration.LazyPath.Index = null;
1015 var expect_stdout_snapshot: ?Configuration.LazyPath.Index = null;
10141016 switch (run.stdio) {
10151017 .check => |checks| for (checks.items) |check| switch (check) {
10161018 .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 {
10271029 .stopped => |x| .{ .status = .stopped, .value = @intFromEnum(x) },
10281030 .unknown => |x| .{ .status = .unknown, .value = x },
10291031 },
1032 .expect_stderr_snapshot => |path| expect_stderr_snapshot = try s.addLazyPath(path),
1033 .expect_stdout_snapshot => |path| expect_stdout_snapshot = try s.addLazyPath(path),
10301034 },
10311035 else => {},
10321036 }
......@@ -1066,6 +1070,8 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {
10661070 .expect_stdout_match = expect_stdout_match.items.len != 0,
10671071 .expect_term = expect_term != null,
10681072 .expect_term_status = if (expect_term) |t| t.status else .exited,
1073 .expect_stderr_snapshot = expect_stderr_snapshot != null,
1074 .expect_stdout_snapshot = expect_stdout_snapshot != null,
10691075 },
10701076 .file_inputs = .{ .slice = try s.initLazyPathList(run.file_inputs.items) },
10711077 .args = .{ .slice = try s.initArgsList(run.argv.items) },
......@@ -1088,6 +1094,8 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {
10881094 .expect_stdout_exact = .{ .value = if (expect_stdout_exact) |bytes| bytes else null },
10891095 .expect_stderr_match = .{ .slice = expect_stderr_match.items },
10901096 .expect_stdout_match = .{ .slice = expect_stdout_match.items },
1097 .expect_stderr_snapshot = .{ .value = expect_stderr_snapshot orelse null },
1098 .expect_stdout_snapshot = .{ .value = expect_stdout_snapshot orelse null },
10911099 .stdin = .{ .u = switch (run.stdin) {
10921100 .none => .none,
10931101 .bytes => |bytes| .{ .bytes = try wc.addBytes(bytes) },
lib/compiler/objdump.zig+1703-13
......@@ -4,19 +4,149 @@ const fatal = std.process.fatal;
44const mem = std.mem;
55const assert = std.debug.assert;
66
7const builtin = @import("builtin");
8const native_endian = builtin.cpu.arch.endian();
9
710var stdout_buffer: [4000]u8 = undefined;
811
12const Options = struct {
13 exports: bool,
14 exports_sort: bool,
15 file_headers: bool,
16 imports: bool,
17 input_path: []const u8,
18 member_filters: []const []const u8 = &.{},
19 member_headers: bool,
20 elements: std.enums.EnumArray(Element, bool),
21 redact: std.enums.EnumArray(FieldKind, bool),
22 relocs: bool,
23 section_filters: []const []const u8 = &.{},
24 section_headers: bool,
25 symbol_filters: []const []const u8 = &.{},
26 strings: bool,
27 symbols: bool,
28 tls: bool,
29
30 // Coff-specific
31 linker_member: ?std.coff.ArchiveMemberHeader.Kind,
32};
33
34const FieldKind = enum {
35 va,
36 rva,
37 ord,
38 size,
39};
40
41const Element = enum {
42 @"file-type",
43 @"header-name",
44 @"member-path",
45 newlines,
46 @"table-header",
47};
48
949pub fn main(init: std.process.Init) !void {
1050 const io = init.io;
1151 const args = try init.minimal.args.toSlice(init.arena.allocator());
52 const arena = init.arena.allocator();
1253
13 var opt_input_path: ?[]const u8 = null;
1454 var i: usize = 1;
55
56 var opt_exports: ?bool = null;
57 var opt_exports_sort: ?bool = null;
58 var opt_file_headers: ?bool = null;
59 var opt_imports: ?bool = null;
60 var opt_input_path: ?[]const u8 = null;
61 var opt_linker_member: ?std.coff.ArchiveMemberHeader.Kind = null;
62 var opt_member_headers: ?bool = null;
63 var any_elements = false;
64 var elements: ?@FieldType(Options, "elements") = null;
65 var redact: @FieldType(Options, "redact") = .initFill(false);
66 var opt_relocs: ?bool = null;
67 var opt_section_headers: ?bool = null;
68 var opt_strings: ?bool = null;
69 var opt_symbols: ?bool = null;
70 var opt_tls: ?bool = null;
71 var section_filters: std.ArrayList([]const u8) = .empty;
72 var symbol_filters: std.ArrayList([]const u8) = .empty;
73 var member_filters: std.ArrayList([]const u8) = .empty;
1574 while (i < args.len) : (i += 1) {
1675 const arg = args[i];
1776 if (mem.startsWith(u8, arg, "-")) {
1877 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
1978 return Io.File.stdout().writeStreamingAll(io, usage);
79 } else if (mem.eql(u8, arg, "--all-headers")) {
80 opt_file_headers = true;
81 opt_linker_member = .second_linker;
82 opt_member_headers = true;
83 opt_section_headers = true;
84 opt_symbols = true;
85 opt_relocs = true;
86 } else if (mem.startsWith(u8, arg, "--exports")) {
87 opt_exports = true;
88 opt_linker_member = .second_linker;
89 if (mem.eql(u8, arg["--exports".len..], "=sort"))
90 opt_exports_sort = true;
91 } else if (mem.eql(u8, arg, "--file-headers")) {
92 opt_file_headers = true;
93 } else if (mem.eql(u8, arg, "--imports")) {
94 opt_imports = true;
95 } else if (mem.startsWith(u8, arg, "--linker-member")) {
96 if (mem.eql(u8, arg["--linker-member".len..], "=1"))
97 opt_linker_member = .first_linker
98 else if (mem.eql(u8, arg["--linker-member".len..], "=longnames"))
99 opt_linker_member = .longnames
100 else
101 opt_linker_member = .second_linker;
102 } else if (mem.eql(u8, arg, "--member-headers")) {
103 opt_member_headers = true;
104 } else if (mem.startsWith(u8, arg, "--elements=")) {
105 any_elements = true;
106 var split = std.mem.splitScalar(u8, arg["--elements=".len..], ',');
107 while (split.next()) |element| {
108 const kind, const add = if (element.len > 0 and element[0] == '-')
109 .{ element[1..], false }
110 else
111 .{ element, true };
112
113 if (elements == null) elements = .initFill(false);
114 if (std.meta.stringToEnum(Element, kind)) |format_kind| {
115 elements.?.set(format_kind, add);
116 } else if (std.mem.eql(u8, kind, "all")) {
117 elements.? = .initFill(add);
118 } else {
119 fatal("unrecognized element: '{s}'", .{kind});
120 }
121 }
122 } else if (mem.startsWith(u8, arg, "--only-member=")) {
123 (try member_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-member=".len..]);
124 } else if (mem.startsWith(u8, arg, "--only-section=")) {
125 (try section_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-section=".len..]);
126 } else if (mem.startsWith(u8, arg, "--only-symbol=")) {
127 (try symbol_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-symbol=".len..]);
128 } else if (mem.startsWith(u8, arg, "--redact=")) {
129 const kind = arg["--redact=".len..];
130 if (std.meta.stringToEnum(FieldKind, kind)) |field_kind| {
131 redact.set(field_kind, true);
132 } else if (std.mem.eql(u8, kind, "all")) {
133 redact = .initFill(true);
134 } else {
135 fatal("unrecognized redaction kind: {s}", .{kind});
136 }
137 } else if (mem.eql(u8, arg, "--relocs")) {
138 opt_relocs = true;
139 } else if (mem.eql(u8, arg, "--section-headers")) {
140 opt_section_headers = true;
141 } else if (mem.eql(u8, arg, "-s") or mem.eql(u8, arg, "--snapshot")) {
142 elements = .initFill(false);
143 redact = .initFill(true);
144 } else if (mem.eql(u8, arg, "--strings")) {
145 opt_strings = true;
146 } else if (mem.eql(u8, arg, "--symbols")) {
147 opt_symbols = true;
148 } else if (mem.eql(u8, arg, "--tls")) {
149 opt_tls = true;
20150 } else {
21151 fatal("unrecognized argument: {s}", .{arg});
22152 }
......@@ -27,42 +157,134 @@ pub fn main(init: std.process.Init) !void {
27157 }
28158 }
29159
30 const input_path = opt_input_path orelse fatal("missing input file path positional argument", .{});
160 const opts: Options = .{
161 .input_path = opt_input_path orelse fatal("missing input file path positional argument", .{}),
162 .exports = opt_exports orelse false,
163 .exports_sort = opt_exports_sort orelse false,
164 .file_headers = opt_file_headers orelse false,
165 .imports = opt_imports orelse false,
166 .linker_member = opt_linker_member,
167 .member_filters = member_filters.items,
168 .member_headers = opt_member_headers orelse false,
169 .elements = elements orelse .initFill(true),
170 .redact = redact,
171 .relocs = opt_relocs orelse false,
172 .section_filters = section_filters.items,
173 .section_headers = opt_section_headers orelse false,
174 .strings = opt_strings orelse false,
175 .symbol_filters = symbol_filters.items,
176 .symbols = opt_symbols orelse false,
177 .tls = opt_tls orelse false,
178 };
31179
32 var file = std.Io.Dir.cwd().openFile(io, input_path, .{}) catch |err|
33 fatal("failed to open {s}: {t}", .{ input_path, err });
180 var file = std.Io.Dir.cwd().openFile(io, opts.input_path, .{}) catch |err|
181 fatal("failed to open {s}: {t}", .{ opts.input_path, err });
34182 defer file.close(io);
35183
36 var buffer: [4000]u8 = undefined;
184 var buffer: [4096]u8 = undefined;
37185 var file_reader = file.reader(io, &buffer);
38186 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &stdout_buffer);
39 dump(&file_reader.interface, &stdout_writer.interface) catch |err| switch (err) {
187
188 const ctx: DumpContext = .{
189 .gpa = init.gpa,
190 .opts = &opts,
191 .fr = &file_reader,
192 .w = &stdout_writer.interface,
193 };
194
195 dump(&ctx) catch |err| switch (err) {
40196 error.ReadFailed => return file_reader.err.?,
41197 error.WriteFailed => return stdout_writer.err.?,
42 error.UnknownFile => fatal("unrecognized file: {s}", .{input_path}),
198 error.UnknownFile => fatal("unrecognized file: {s}", .{opts.input_path}),
199 error.ParseFailure => {},
43200 else => |e| return e,
44201 };
45202 try stdout_writer.flush();
46203}
47204
48fn dump(r: *Io.Reader, w: *Io.Writer) !void {
205fn dump(d: *const DumpContext) !void {
206 const r = &d.fr.interface;
49207 try r.fill(4);
50208 elf: {
51209 if (!mem.eql(u8, r.buffered()[0..4], std.elf.MAGIC)) break :elf;
52 return elf.dump(r, w);
210 return elf.dump(r, d.w);
53211 }
54212 macho: {
55213 if (mem.readInt(u32, r.buffered()[0..4], .little) != std.macho.MH_MAGIC_64) break :macho;
56 return macho.dump(r, w);
214 return macho.dump(r, d.w);
57215 }
58216 wasm: {
59217 comptime assert(std.wasm.magic.len == 4);
60218 if (!mem.eql(u8, r.buffered()[0..4], &std.wasm.magic)) break :wasm;
61 return wasm.dump(r, w);
219 return wasm.dump(r, d.w);
220 }
221 coff: {
222 const ext = std.fs.path.extension(d.opts.input_path);
223 const basename = std.fs.path.basename(d.opts.input_path);
224 if (std.mem.eql(u8, ext, ".exe") or std.mem.eql(u8, ext, ".dll")) {
225 if (!mem.eql(u8, r.buffered()[0..2], "MZ")) break :coff;
226 try r.discardAll(std.coff.pe_pointer_offset);
227 const sig_offset = try r.takeInt(u32, .little);
228 try d.fr.seekTo(sig_offset);
229 const sig = try r.take(4);
230
231 if (!std.mem.eql(u8, sig, std.coff.pe_signature)) {
232 try d.w.print("invalid PE signature: {x}", .{sig});
233 return error.ParseFailure;
234 }
235
236 if (d.element(.@"file-type")) {
237 try d.w.print("{s}: PE/COFF image\n\n", .{basename});
238 if (d.element(.newlines)) try d.w.writeByte('\n');
239 }
240
241 return coff.dumpObject(d, true, basename);
242 } else if (std.mem.eql(u8, ext, ".lib")) {
243 r.fill(std.coff.archive_signature.len) catch break :coff;
244 if (!mem.eql(u8, r.buffered()[0..std.coff.archive_signature.len], std.coff.archive_signature)) break :coff;
245 if (d.element(.@"file-type")) {
246 try d.w.print("{s}: COFF archive\n", .{basename});
247 if (d.element(.newlines)) try d.w.writeByte('\n');
248 }
249
250 return coff.dumpArchive(d);
251 } else if (std.mem.eql(u8, ext, ".obj")) {
252 if (d.element(.@"file-type")) {
253 try d.w.print("{s}: COFF object\n", .{basename});
254 if (d.element(.newlines)) try d.w.writeByte('\n');
255 }
256
257 return coff.dumpObject(d, false, basename);
258 }
62259 }
63260 return error.UnknownFile;
64261}
65262
263const DumpContext = struct {
264 gpa: std.mem.Allocator,
265 opts: *const Options,
266 fr: *Io.File.Reader,
267 w: *Io.Writer,
268
269 fn element(self: *const DumpContext, e: Element) bool {
270 return self.opts.elements.get(e);
271 }
272
273 fn redacted(self: *const DumpContext, opt_kind: ?FieldKind) bool {
274 const kind = opt_kind orelse return false;
275 return self.opts.redact.get(kind);
276 }
277
278 fn failParse(
279 ctx: *const DumpContext,
280 comptime fmt: []const u8,
281 args: anytype,
282 ) noreturn {
283 std.log.err("error parsing '{s}'", .{std.fs.path.basename(ctx.opts.input_path)});
284 fatal(fmt, args);
285 }
286};
287
66288const elf = struct {
67289 fn dump(r: *Io.Reader, w: *Io.Writer) !void {
68290 _ = r;
......@@ -84,10 +306,1478 @@ const wasm = struct {
84306 }
85307};
86308
309const coff = struct {
310 const DIRECTORY_ENTRY = std.coff.IMAGE.DIRECTORY_ENTRY;
311
312 const Section = struct {
313 header: std.coff.SectionHeader,
314 name: []const u8,
315
316 fn rvaFileOffset(section: *const Section, rva: u32) !u32 {
317 if (rva < section.header.virtual_address or
318 rva >= section.header.virtual_address + section.header.size_of_raw_data)
319 return error.OutOfBounds;
320
321 return section.header.pointer_to_raw_data + (rva - section.header.virtual_address);
322 }
323 };
324
325 const ArchiveHeader = struct {
326 name: []const u8,
327 date: u40,
328 user_id: u20,
329 group_id: u20,
330 file_mode: u24,
331 size: u34,
332
333 pub fn fromRaw(d: *const DumpContext, raw_header: *const std.coff.ArchiveMemberHeader, opt_longnames: ?[]const u8) @This() {
334 const name = raw_header.parseName(opt_longnames) catch |err| switch (err) {
335 error.BadName => d.failParse("malformed member name: '{s}'", .{&raw_header.name}),
336 error.NoLongNames => d.failParse("member uses a long name, but there was no longnames member", .{}),
337 };
338
339 return .{
340 .name = name,
341 .date = raw_header.parseDate() catch |err|
342 d.failParse("unable to parse date '{s}' in member '{s}': {t}", .{ raw_header.date, name, err }),
343 .user_id = raw_header.parseUserId() catch |err|
344 d.failParse("unable to parse user_id '{s}' in member '{s}': {t}", .{ raw_header.user_id, name, err }),
345 .group_id = raw_header.parseGroupId() catch |err|
346 d.failParse("unable to parse group_id '{s}' in member '{s}': {t}", .{ raw_header.group_id, name, err }),
347 .file_mode = raw_header.parseFileMode() catch |err|
348 d.failParse("unable to parse file_mode '{s}' in member '{s}': {t}", .{ raw_header.file_mode, name, err }),
349 .size = raw_header.parseSize() catch |err|
350 d.failParse("unable to parse size '{s}' in member '{s}': {t}", .{ raw_header.size, name, err }),
351 };
352 }
353 };
354
355 fn dumpArchive(d: *const DumpContext) !void {
356 const gpa = d.gpa;
357 const fr = d.fr;
358 const w = d.w;
359
360 const r = &fr.interface;
361 r.toss(std.coff.archive_signature.len);
362
363 const Member = struct {
364 offset: u32,
365 order: ?u32,
366 };
367
368 var members: std.ArrayList(Member) = .empty;
369 defer members.deinit(gpa);
370 var symbol_member_indices: std.ArrayList(u32) = .empty;
371 defer symbol_member_indices.deinit(gpa);
372
373 var opt_expected_kind: ?std.coff.ArchiveMemberHeader.Kind = .first_linker;
374 var opt_longnames: ?[]const u8 = null;
375 defer if (opt_longnames) |l| gpa.free(l);
376
377 var pos = fr.logicalPos();
378 const size = try fr.getSize();
379 while (pos < size) : (pos = fr.logicalPos()) {
380 if ((pos & 1) != 0) try r.discardAll(1);
381 const raw_header = try r.takeStruct(std.coff.ArchiveMemberHeader, .little);
382 const header: ArchiveHeader = .fromRaw(d, &raw_header, opt_longnames);
383
384 if (!std.mem.eql(u8, &raw_header.end_of_header, std.coff.archive_end_of_header))
385 return d.failParse("malformed end-of-header field in member '{s}': {x}", .{ header.name, raw_header.end_of_header });
386
387 const dump_header =
388 (d.opts.member_headers and filterMatches(d.opts.member_filters, header.name)) or
389 (d.opts.linker_member == opt_expected_kind);
390
391 if (dump_header)
392 try dumpArchiveHeader(d, &header, @intCast(pos));
393
394 const member_end = fr.logicalPos() + header.size;
395 if (member_end > size)
396 return d.failParse("out-of-bounds length 0x{x} in member '{s}'", .{ header.size, header.name });
397
398 if (opt_expected_kind) |expected_kind| switch (expected_kind) {
399 .first_linker => {
400 if (!std.mem.eql(u8, header.name, "/"))
401 return d.failParse("expected first linker member, found '{s}'", .{header.name});
402
403 const num_symbols = try r.takeInt(u32, .big);
404 if (dump_header)
405 try w.print(
406 \\{t: >16} type
407 \\ | {d} symbols
408 \\
409 , .{ expected_kind, num_symbols });
410
411 if (d.opts.linker_member == .first_linker) {
412 if (d.element(.@"table-header"))
413 try w.writeAll(
414 \\
415 \\Archive symbols:
416 \\& Member Symbol
417 \\
418 );
419
420 const offsets = try r.readAlloc(gpa, num_symbols * 4);
421 defer gpa.free(offsets);
422
423 for (0..num_symbols) |symbol_i| {
424 const symbol = r.takeDelimiter(0) catch |err|
425 return d.failParse("unable to read first linker member string table: {t}", .{err});
426
427 if (!filterMatches(d.opts.symbol_filters, symbol.?))
428 continue;
429
430 const offset = std.mem.readInt(u32, offsets[symbol_i * 4 ..][0..4], .big);
431 try w.print("{f} {s}\n", .{
432 fmtIntField(d, offset, .{ .kind = .va }),
433 symbol.?,
434 });
435 }
436 }
437 if (dump_header and d.element(.newlines)) try w.writeByte('\n');
438
439 try fr.seekTo(member_end);
440 opt_expected_kind = .second_linker;
441 continue;
442 },
443 .second_linker => {
444 if (!std.mem.eql(u8, header.name, "/"))
445 return d.failParse("expected second linker member, found '{s}'", .{header.name});
446
447 const num_members = try r.takeInt(u32, .little);
448 pos = fr.logicalPos();
449 if (pos + num_members * @sizeOf(u32) > member_end)
450 return d.failParse("invalid member count 0x{x} in second linker member", .{num_members});
451
452 try members.ensureTotalCapacity(gpa, num_members);
453 for (0..num_members) |_|
454 members.addOneAssumeCapacity().* = .{
455 .offset = try r.takeInt(u32, .little),
456 .order = null,
457 };
458
459 const num_symbols = try r.takeInt(u32, .little);
460 pos = fr.logicalPos();
461 if (pos + num_symbols * @sizeOf(u16) > member_end)
462 return d.failParse("invalid symbol count 0x{x} in second linker member", .{num_symbols});
463
464 if (dump_header)
465 try w.print(
466 \\{t: >16} type
467 \\ | {f} symbols
468 \\ | {f} members
469 \\
470 , .{
471 expected_kind,
472 fmtIntField(d, num_symbols, .{ .kind = .size, .width = .auto }),
473 fmtIntField(d, num_members, .{ .kind = .size, .width = .auto }),
474 });
475
476 try symbol_member_indices.ensureTotalCapacity(gpa, num_symbols);
477 for (0..num_symbols) |order| {
478 const index = (try r.takeInt(u16, .little)) - 1;
479 if (index >= members.items.len)
480 return d.failParse("invalid member index 0x{x} in seconds linker member indices array", .{index});
481
482 symbol_member_indices.addOneAssumeCapacity().* = index;
483
484 if (members.items[index].order == null)
485 members.items[index].order = @intCast(order);
486 }
487
488 if (d.opts.exports and d.opts.exports_sort) {
489 std.sort.pdq(Member, members.items, {}, struct {
490 fn lessThan(ctx: void, lhs: Member, rhs: Member) bool {
491 _ = ctx;
492 if (lhs.order == null and rhs.order == null)
493 return lhs.offset < rhs.offset
494 else if (lhs.order) |lhs_order|
495 return if (rhs.order) |rhs_order| lhs_order < rhs_order else false
496 else if (rhs.order) |rhs_order|
497 return if (lhs.order) |lhs_order| lhs_order < rhs_order else true
498 else
499 unreachable;
500 }
501 }.lessThan);
502 }
503
504 if (d.opts.linker_member == .second_linker) {
505 if (d.element(.@"table-header"))
506 try w.writeAll(
507 \\
508 \\Archive Symbols:
509 \\& Member Symbol
510 \\
511 );
512
513 pos = fr.logicalPos();
514 var symbol_i: u32 = 0;
515 while (pos < member_end and symbol_i < num_symbols) : ({
516 pos = fr.logicalPos();
517 symbol_i += 1;
518 }) {
519 const symbol_name = if (r.takeDelimiter(0) catch |err| switch (err) {
520 error.StreamTooLong => null,
521 else => |e| return e,
522 }) |n| n else return d.failParse("unterminated string found in second linker member", .{});
523
524 if (!filterMatches(d.opts.symbol_filters, symbol_name))
525 continue;
526
527 try w.print("{f} {s}\n", .{
528 fmtIntField(
529 d,
530 members.items[symbol_member_indices.items[symbol_i]].offset,
531 .{ .kind = .va },
532 ),
533 symbol_name,
534 });
535 }
536
537 if (symbol_i != num_symbols)
538 return d.failParse(
539 " expected {d} entries in second linker member string table, but found {d}",
540 .{ num_symbols, symbol_i },
541 );
542 }
543
544 if (d.element(.newlines)) try w.writeByte('\n');
545 try fr.seekTo(member_end);
546 opt_expected_kind = .longnames;
547 continue;
548 },
549 .longnames => {
550 // This member is optional
551 if (std.mem.eql(u8, header.name, "//")) {
552 opt_longnames = try r.readAlloc(gpa, header.size);
553 if (dump_header)
554 try w.print("{t: >16} type\n", .{expected_kind});
555
556 if (d.opts.linker_member == .longnames) {
557 if (d.element(.@"table-header"))
558 try w.print(
559 \\
560 \\Longnames (0x{x} bytes):
561 \\
562 , .{opt_longnames.?.len});
563
564 var lr = Io.Reader.fixed(opt_longnames.?);
565 while (try lr.takeDelimiter(0)) |str| {
566 try w.writeAll(str);
567 try w.writeByte('\n');
568 }
569 }
570
571 if (d.element(.newlines)) try w.writeByte('\n');
572 }
573
574 opt_expected_kind = null;
575 break;
576 },
577 else => unreachable,
578 };
579 }
580
581 if (opt_expected_kind) |expected_kind| switch (expected_kind) {
582 .first_linker => d.failParse("missing first linker member", .{}),
583 .second_linker => d.failParse("missing second linker member", .{}),
584 else => {},
585 };
586
587 for (members.items, 0..) |member, member_i| {
588 fr.seekTo(member.offset) catch |err|
589 d.failParse("unable to read member {d} at offset 0x{x}: {t}", .{ member_i, member.offset, err });
590
591 const raw_header = try r.takeStruct(std.coff.ArchiveMemberHeader, .little);
592 const header: ArchiveHeader = .fromRaw(d, &raw_header, opt_longnames);
593 if (!filterMatches(d.opts.member_filters, header.name)) continue;
594
595 const member_sig = try r.peek(4);
596 const machine: std.coff.IMAGE.FILE.MACHINE =
597 @enumFromInt(std.mem.readInt(u16, member_sig[0..2], .little));
598 const sig = std.mem.readInt(u16, member_sig[2..4], .little);
599
600 const is_imp_lib = machine == std.coff.IMAGE.FILE.MACHINE.UNKNOWN and sig == 0xffff;
601 if (d.opts.member_headers)
602 try dumpArchiveHeader(d, &header, member.offset);
603
604 if (d.opts.member_headers or (d.opts.exports and is_imp_lib)) {
605 if (is_imp_lib) {
606 const imp_header = try r.takeStruct(std.coff.ImportHeader, .little);
607 const sym_name = (try r.takeDelimiter(0)).?;
608 const imp_dll = (try r.takeDelimiter(0)).?;
609
610 if (!filterMatches(d.opts.symbol_filters, sym_name))
611 continue;
612
613 if (d.element(.@"header-name"))
614 try w.writeAll("\nImport header:\n");
615
616 try dumpHeader(d, std.coff.ImportHeader, &imp_header, struct {
617 pub fn sig1(_: *const DumpContext, _: *const std.coff.ImportHeader) !void {}
618 pub fn sig2(_: *const DumpContext, _: *const std.coff.ImportHeader) !void {}
619 pub fn types(id: *const DumpContext, h: *const std.coff.ImportHeader) !void {
620 try id.w.print(
621 \\{t: >16} import_type
622 \\{t: >16} name_type
623 \\
624 , .{ h.types.type, h.types.name_type });
625 }
626 });
627
628 const imp_name = imp_name: switch (imp_header.types.name_type) {
629 .NAME_NOPREFIX,
630 .NAME_UNDECORATE,
631 => |tag| {
632 var imp_name = std.mem.trimStart(u8, sym_name, "?@_");
633 if (tag == .NAME_UNDECORATE)
634 imp_name = std.mem.sliceTo(imp_name, '@');
635 break :imp_name imp_name;
636 },
637 else => sym_name,
638 };
639
640 try w.print(
641 \\ symbol name | {s}
642 \\ import name | {s}
643 \\ dll | {s}
644 \\
645 , .{
646 sym_name,
647 imp_name,
648 imp_dll,
649 });
650 } else {
651 try w.writeAll(" COFF object type\n");
652 }
653 if (d.element(.newlines)) try w.writeByte('\n');
654 }
655
656 if (is_imp_lib) continue;
657 if (d.opts.section_headers or
658 d.opts.file_headers or
659 d.opts.relocs or
660 d.opts.strings or
661 d.opts.symbols)
662 {
663 const member_name = if (d.element(.@"member-path"))
664 header.name
665 else
666 std.fs.path.basename(header.name);
667
668 if (d.element(.@"file-type")) {
669 try w.print("{s}({s}): COFF object\n", .{
670 std.fs.path.basename(d.opts.input_path),
671 member_name,
672 });
673 if (d.element(.newlines)) try w.writeByte('\n');
674 }
675 try dumpObject(d, false, member_name);
676 }
677 }
678 }
679
680 fn dumpObject(
681 d: *const DumpContext,
682 is_image: bool,
683 obj_name: []const u8,
684 ) !void {
685 const gpa = d.gpa;
686 const fr = d.fr;
687 const w = d.w;
688
689 const file_location = fr.logicalPos();
690 const r = &fr.interface;
691 const header = r.takeStruct(std.coff.Header, .little) catch |err|
692 return d.failParse("unable to read COFF header: {t}", .{err});
693
694 if (d.opts.file_headers) {
695 if (d.element(.@"header-name")) try w.writeAll("COFF Header:\n");
696 try dumpHeader(d, std.coff.Header, &header, struct {});
697 if (d.element(.newlines)) try w.writeByte('\n');
698 }
699
700 switch (header.machine) {
701 _ => return d.failParse("unknown machine type: {x}", .{header.machine}),
702 else => {},
703 }
704
705 var known_dirs: [DIRECTORY_ENTRY.len]std.coff.ImageDataDirectory = undefined;
706 const needs_data_dirs =
707 d.opts.exports or
708 d.opts.imports or
709 d.opts.tls;
710
711 const ImageInfo = struct {
712 data_dirs: []const std.coff.ImageDataDirectory,
713 magic: std.coff.OptionalHeader.Magic,
714 image_base: u64,
715 };
716
717 const image_info: ?ImageInfo = if (header.size_of_optional_header > 0) image_info: {
718 if (!d.opts.file_headers and !needs_data_dirs) {
719 try fr.seekBy(header.size_of_optional_header);
720 break :image_info null;
721 }
722
723 if (d.opts.file_headers and d.element(.@"header-name"))
724 try w.writeAll("COFF Optional Header:\n");
725
726 const magic: std.coff.OptionalHeader.Magic = @enumFromInt(try r.peekInt(u16, .little));
727 const num_directory_entries, const image_base = switch (magic) {
728 inline .PE32, .@"PE32+" => |v| num_data_dirs: {
729 const OptionalHeader = if (v == .PE32)
730 std.coff.OptionalHeader.PE32
731 else
732 std.coff.OptionalHeader.@"PE32+";
733
734 const optional_header = r.takeStruct(OptionalHeader, .little) catch |err|
735 return d.failParse("unable to read optional header: {t}", .{err});
736
737 if (d.opts.file_headers) {
738 try dumpHeader(d, OptionalHeader, &optional_header, struct {
739 pub fn base_of_code(id: *const DumpContext, h: *const std.coff.OptionalHeader) !void {
740 const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base;
741 try dumpRvaField(id, @src().fn_name, h.base_of_code, base);
742 }
743
744 pub fn address_of_entry_point(id: *const DumpContext, h: *const std.coff.OptionalHeader) !void {
745 const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base;
746 try dumpRvaField(id, @src().fn_name, h.base_of_code, base);
747 }
748
749 pub fn major_linker_version(id: *const DumpContext, h: *const std.coff.OptionalHeader) !void {
750 try dumpVersionField(id.w, "linker_version", h.major_linker_version, h.minor_linker_version);
751 }
752 pub fn minor_linker_version(_: *const DumpContext, _: *const std.coff.OptionalHeader) !void {}
753
754 pub fn major_operating_system_version(id: *const DumpContext, h: *const OptionalHeader) !void {
755 try dumpVersionField(
756 id.w,
757 "operating_system_version",
758 h.major_operating_system_version,
759 h.minor_operating_system_version,
760 );
761 }
762 pub fn minor_operating_system_version(_: *const DumpContext, _: *const OptionalHeader) !void {}
763
764 pub fn major_image_version(id: *const DumpContext, h: *const OptionalHeader) !void {
765 try dumpVersionField(id.w, "image_version", h.major_image_version, h.minor_image_version);
766 }
767 pub fn minor_image_version(_: *const DumpContext, _: *const OptionalHeader) !void {}
768
769 pub fn major_subsystem_version(id: *const DumpContext, h: *const OptionalHeader) !void {
770 try dumpVersionField(id.w, "subsystem_version", h.major_subsystem_version, h.minor_subsystem_version);
771 }
772 pub fn minor_subsystem_version(_: *const DumpContext, _: *const OptionalHeader) !void {}
773 });
774 if (d.element(.newlines)) try w.writeByte('\n');
775 }
776
777 break :num_data_dirs .{
778 optional_header.number_of_rva_and_sizes,
779 optional_header.image_base,
780 };
781 },
782 else => return d.failParse("invalid optional header magic number: {x}", .{magic}),
783 };
784
785 if (d.opts.file_headers and d.element(.@"header-name"))
786 try w.writeAll("Data Directories:\n");
787
788 for (0..num_directory_entries) |dir_i| {
789 const dir = r.takeStruct(std.coff.ImageDataDirectory, .little) catch |err|
790 return d.failParse("unable to read data directory {x}: {t}", .{ dir_i, err });
791
792 if (dir_i < known_dirs.len)
793 known_dirs[dir_i] = dir;
794
795 if (d.opts.file_headers)
796 try w.print(
797 "{x: >16} {x: >8} {t}\n",
798 .{ dir.virtual_address, dir.size, @as(DIRECTORY_ENTRY, @enumFromInt(dir_i)) },
799 );
800 }
801 if (d.opts.file_headers and d.element(.newlines)) try w.writeByte('\n');
802
803 break :image_info .{
804 .data_dirs = known_dirs[0..@min(known_dirs.len, num_directory_entries)],
805 .magic = magic,
806 .image_base = image_base,
807 };
808 } else if (is_image) {
809 return d.failParse("image did not contain an optional header", .{});
810 } else null;
811
812 // Section names in images don't use the string table, as they must fit inline in the header
813 const load_string_table = (d.opts.strings or !is_image) and header.pointer_to_symbol_table > 0;
814 const string_table = if (load_string_table) string_table: {
815 const pos = fr.logicalPos();
816 fr.seekTo(file_location + header.pointer_to_symbol_table + header.number_of_symbols * std.coff.Symbol.sizeOf()) catch |err|
817 return d.failParse("unable to seek to string table: {t}", .{err});
818
819 const string_table_len = r.peekInt(u32, .little) catch |err|
820 return d.failParse("unable to read string table length: {t}", .{err});
821
822 const table = r.readAlloc(gpa, string_table_len) catch |err|
823 return d.failParse("unable to read string table: {t}", .{err});
824
825 try fr.seekTo(pos);
826 break :string_table table;
827 } else &.{};
828 defer gpa.free(string_table);
829
830 if (d.opts.strings) {
831 if (d.element(.@"table-header"))
832 try w.print(
833 \\String Table (0x{x} bytes):
834 \\
835 , .{string_table.len});
836
837 var sr = Io.Reader.fixed(string_table[@sizeOf(u32)..]);
838 while (try sr.takeDelimiter(0)) |str| {
839 try w.writeAll(str);
840 try w.writeByte('\n');
841 }
842
843 if (d.element(.newlines)) try w.writeByte('\n');
844 }
845
846 var sections: std.ArrayList(Section) = .empty;
847 defer sections.deinit(gpa);
848 var sections_with_data: u16 = 0;
849
850 const load_sections =
851 d.opts.section_headers or
852 d.opts.symbols or
853 d.opts.relocs or
854 needs_data_dirs;
855
856 if (load_sections) {
857 if (d.opts.section_headers and d.element(.@"table-header"))
858 try w.print(
859 \\Sections in '{s}':
860 \\Num Name RVA Virt Size Data Size & Data & Relocs & Lines # Relocs # Lines Flags
861 \\
862 , .{obj_name});
863
864 try sections.resize(gpa, header.number_of_sections);
865 for (sections.items, 0..) |*section, section_i| {
866 section.header = r.takeStruct(std.coff.SectionHeader, .little) catch |err|
867 return d.failParse("unable to read section header {x}: {t}", .{ section_i, err });
868 section.name = headerName(&section.header.name, string_table) catch |err| switch (err) {
869 error.Overflow,
870 error.InvalidCharacter,
871 => return d.failParse("unable to parse section name offset '{s}': {t}", .{
872 section.name,
873 err,
874 }),
875 error.OutOfBounds => return d.failParse("section name offset '{s}' was out of bounds (>= {x})", .{
876 section.name,
877 string_table.len,
878 }),
879 };
880
881 sections_with_data += @intFromBool(section.header.size_of_raw_data > 0);
882 if (d.opts.section_headers) {
883 if (!filterMatches(d.opts.section_filters, section.name)) continue;
884 const raw_name = std.mem.sliceTo(&section.header.name, 0);
885 try w.print(
886 "{x: >3} {s: <8} {f} {f} {f} {f} {f} {f} {f} {f} {x:0>8} |",
887 .{
888 section_i + 1,
889 raw_name,
890 fmtIntField(d, section.header.virtual_address, .{ .kind = .va }),
891 fmtIntField(d, section.header.virtual_size, .{ .kind = .size, .width = .{ .explicit = 9 } }),
892 fmtIntField(d, section.header.size_of_raw_data, .{ .kind = .size, .width = .{ .explicit = 9 } }),
893 fmtIntField(d, section.header.pointer_to_raw_data, .{ .kind = .va }),
894 fmtIntField(d, section.header.pointer_to_relocations, .{ .kind = .va }),
895 fmtIntField(d, section.header.pointer_to_linenumbers, .{ .kind = .va }),
896 fmtIntField(d, section.header.number_of_relocations, .{ .kind = .va }),
897 fmtIntField(d, section.header.number_of_linenumbers, .{ .kind = .va }),
898 @as(u32, @bitCast(section.header.flags)),
899 },
900 );
901
902 try dumpFlags(w, "{s}", std.coff.SectionHeader.Flags, &section.header.flags, 1);
903 if (section.name.len > 8)
904 try w.print("\n | {s}", .{section.name});
905
906 try w.writeByte('\n');
907 }
908 }
909
910 if (d.opts.section_headers and d.element(.newlines)) try w.writeByte('\n');
911 }
912
913 var symbols: std.ArrayList(struct {
914 name: []const u8,
915 section_number: std.coff.SectionNumber,
916 }) = .empty;
917 defer symbols.deinit(gpa);
918
919 var name_arena: std.heap.ArenaAllocator = .init(gpa);
920 defer name_arena.deinit();
921
922 if (d.opts.relocs)
923 try symbols.ensureUnusedCapacity(gpa, header.number_of_symbols);
924
925 if (d.opts.symbols or d.opts.relocs) {
926 if (header.pointer_to_symbol_table > 0) {
927 fr.seekTo(file_location + header.pointer_to_symbol_table) catch |err|
928 return d.failParse("unable to seek to symbol table: {t}", .{err});
929
930 if (d.opts.symbols and d.element(.@"table-header"))
931 try w.print(
932 \\Symbols in '{s}':
933 \\ Ord Value Sect Type Storage Name
934 \\
935 , .{obj_name});
936
937 const symbol_size = std.coff.Symbol.sizeOf();
938 var symbol_i: u32 = 0;
939 while (symbol_i < header.number_of_symbols) {
940 var symbol: std.coff.Symbol = undefined;
941 const symbol_bytes = r.take(symbol_size) catch |err|
942 return d.failParse("unable to read symbol {x}: {t}", .{ symbol_i, err });
943
944 @memcpy(std.mem.asBytes(&symbol)[0..symbol_size], symbol_bytes);
945 if (native_endian != .little)
946 std.mem.byteSwapAllFields(std.coff.Symbol, &symbol);
947
948 const aux_symbols = if (symbol.number_of_aux_symbols > 0)
949 try r.take(symbol_size * symbol.number_of_aux_symbols)
950 else
951 &.{};
952 defer symbol_i += symbol.number_of_aux_symbols + 1;
953
954 const name = if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: {
955 const index = std.mem.readInt(u32, symbol.name[4..], .little);
956 if (index >= string_table.len)
957 return d.failParse("invalid name offset for symbol {x} ({x} >= {x})", .{
958 symbol_i,
959 index,
960 string_table.len,
961 });
962 break :name std.mem.sliceTo(string_table[index..], 0);
963 } else try name_arena.allocator().dupe(u8, std.mem.sliceTo(&symbol.name, 0));
964
965 if (d.opts.relocs)
966 symbols.appendNTimesAssumeCapacity(.{
967 .name = name,
968 .section_number = symbol.section_number,
969 }, 1 + symbol.number_of_aux_symbols);
970
971 if (!d.opts.symbols or !filterMatches(d.opts.symbol_filters, name))
972 continue;
973
974 try w.print("{f} {x:0>8} ", .{
975 fmtIntField(d, @as(u16, @intCast(symbol_i)), .{ .kind = .ord }),
976 symbol.value,
977 });
978 try switch (symbol.section_number) {
979 .UNDEFINED => w.writeAll("UNDEF"),
980 .ABSOLUTE => w.writeAll(" ABS"),
981 .DEBUG => w.writeAll("DEBUG"),
982 else => |v| {
983 const backing = @intFromEnum(v);
984 const fmt = "{x: >5}";
985 if (backing >= 0)
986 try w.print(fmt, .{@as(u15, @intCast(backing))})
987 else
988 try w.print(fmt, .{backing});
989 },
990 };
991
992 try w.print("{t: >5}", .{symbol.type.base_type});
993 if (switch (symbol.type.complex_type) {
994 .NULL => " ",
995 .POINTER => "* ",
996 .FUNCTION => "()",
997 .ARRAY => "[]",
998 else => null,
999 }) |suffix| try w.writeAll(suffix) else try w.print("{x}", .{symbol.type.complex_type});
1000
1001 try w.print("{t: >16} | {s}\n", .{ symbol.storage_class, name });
1002
1003 for (0..symbol.number_of_aux_symbols) |aux_i| {
1004 _ = aux_i;
1005 try w.writeAll(" |");
1006
1007 if (symbol.storage_class == .EXTERNAL and
1008 symbol.type == std.coff.SymType{
1009 .complex_type = .FUNCTION,
1010 .base_type = .NULL,
1011 } and
1012 @intFromEnum(symbol.section_number) > 0)
1013 {
1014 try w.writeAll("TODO function aux symbol");
1015 } else if (symbol.type == std.coff.SymType{
1016 .complex_type = .FUNCTION,
1017 .base_type = .NULL,
1018 } and
1019 (std.mem.eql(u8, name, ".bf") or std.mem.eql(u8, name, ".ef")))
1020 {
1021 try w.writeAll("TODO bf / ef aux symbol");
1022 } else if (symbol.storage_class == .WEAK_EXTERNAL and symbol.section_number == .UNDEFINED) {
1023 if (symbol.value != 0)
1024 return d.failParse(
1025 "invalid value 0x{x} for weak external symbol 0x{x}",
1026 .{ symbol.value, symbol_i },
1027 );
1028
1029 var weak_external: std.coff.WeakExternalDefinition = undefined;
1030 @memcpy(std.mem.asBytes(&weak_external)[0..symbol_size], aux_symbols[0..symbol_size]);
1031 if (native_endian != .little)
1032 std.mem.byteSwapAllFields(std.coff.WeakExternalDefinition, &weak_external);
1033
1034 if (weak_external.tag_index >= header.number_of_symbols)
1035 return d.failParse(
1036 "invalid tag_index 0x{x} for weak external symbol 0x{x}",
1037 .{ weak_external.tag_index, symbol_i },
1038 );
1039
1040 if (d.redacted(.ord))
1041 try w.print(" Weak External [falls back to relative ordinal {x:0>8} via {t}]", .{
1042 @as(i64, weak_external.tag_index) - symbol_i,
1043 weak_external.flag,
1044 })
1045 else
1046 try w.print(" Weak External [falls back to ordinal {x:0>8} via {t}]", .{
1047 weak_external.tag_index,
1048 weak_external.flag,
1049 });
1050 } else if (symbol.storage_class == .FILE) {
1051 if (!std.mem.eql(u8, name, ".file")) {
1052 try w.print(" !! unexpected symbol name '{s}' for file symbol 0x{x}", .{ name, symbol_i });
1053 continue;
1054 }
1055
1056 const filename = std.mem.sliceTo(aux_symbols, 0);
1057 try w.print(" File '{s}'", .{filename});
1058 break;
1059 } else if (symbol.storage_class == .STATIC and
1060 symbol.type == std.coff.SymType{
1061 .complex_type = .NULL,
1062 .base_type = .NULL,
1063 } and
1064 symbol.value == 0 and
1065 switch (symbol.section_number) {
1066 .UNDEFINED, .DEBUG, .ABSOLUTE => false,
1067 else => |sn| @intFromEnum(sn) > 0,
1068 })
1069 {
1070 const section_i: u15 = @intCast(@intFromEnum(symbol.section_number) - 1);
1071 try w.writeAll(" Section ");
1072
1073 if (section_i >= sections.items.len) {
1074 try w.print(" !! invalid section number: {x}", .{section_i});
1075 continue;
1076 }
1077
1078 var section_def: std.coff.SectionDefinition = undefined;
1079 @memcpy(std.mem.asBytes(&section_def)[0..symbol_size], aux_symbols[0..symbol_size]);
1080 if (native_endian != .little)
1081 std.mem.byteSwapAllFields(std.coff.SectionDefinition, &section_def);
1082
1083 const section = &sections.items[section_i];
1084 if (section_def.number_of_relocations != section.header.number_of_relocations) {
1085 try w.print(
1086 " !! relocation count did not match section header: {d} vs {d}",
1087 .{ section_def.number_of_relocations, section.header.number_of_relocations },
1088 );
1089 continue;
1090 }
1091
1092 if (section_def.number_of_linenumbers != section.header.number_of_linenumbers) {
1093 try w.print(
1094 " !! line number count did not match section header: {d} vs {d}",
1095 .{ section_def.number_of_linenumbers, section.header.number_of_linenumbers },
1096 );
1097 continue;
1098 }
1099
1100 try w.print(" [size {f} chksum {x:0>8} relocs {x:0>4} lines {x:0>4}]", .{
1101 fmtIntField(d, section_def.length, .{ .kind = .size, .zero_fill = true }),
1102 section_def.checksum,
1103 section_def.number_of_relocations,
1104 section_def.number_of_linenumbers,
1105 });
1106
1107 switch (section_def.selection) {
1108 .NONE => {},
1109 else => |selection| {
1110 try w.print(" COMDAT({t}", .{selection});
1111 if (selection == .ASSOCIATIVE)
1112 try w.print("->{x}", .{section_def.number});
1113 try w.writeAll(")");
1114 },
1115 }
1116 }
1117
1118 try w.writeByte('\n');
1119 }
1120 }
1121
1122 if (d.opts.symbols and d.element(.newlines)) try w.writeByte('\n');
1123 } else if (d.opts.symbols) {
1124 try w.writeAll("No symbol table found\n");
1125 }
1126 }
1127
1128 if (d.opts.relocs) {
1129 const relocation_size = std.coff.Relocation.sizeOf();
1130
1131 for (sections.items, 0..) |section, section_i| {
1132 if (section.header.pointer_to_relocations == 0) continue;
1133
1134 if (d.element(.@"table-header"))
1135 try w.print(
1136 \\Relocs for section {x} '{s}' in {s}:
1137 \\ Offset Type Symbol -> Sect Name
1138 \\
1139 , .{ section_i + 1, section.name, obj_name });
1140
1141 fr.seekTo(file_location + section.header.pointer_to_relocations) catch |err|
1142 return d.failParse("unable to seek to section {x} relocation table: {t}", .{ section_i + 1, err });
1143
1144 for (0..section.header.number_of_relocations) |reloc_i| {
1145 var reloc: std.coff.Relocation = undefined;
1146 @memcpy(std.mem.asBytes(&reloc)[0..relocation_size], try r.take(relocation_size));
1147 if (native_endian != .little)
1148 std.mem.byteSwapAllFields(std.coff.Relocation, &reloc);
1149
1150 const sym = &symbols.items[reloc.symbol_table_index];
1151 if (!filterMatches(d.opts.symbol_filters, sym.name))
1152 continue;
1153
1154 try w.print("{f} ", .{
1155 fmtIntField(d, reloc.virtual_address, .{ .kind = .va, .zero_fill = true }),
1156 });
1157 switch (header.machine) {
1158 _ => unreachable,
1159 inline else => |m| switch (m.RelocationType()) {
1160 void => try w.writeAll("(unknown arch)"),
1161 else => |RelocationType| try w.print(
1162 "{t: <17} ",
1163 .{@as(RelocationType, @enumFromInt(reloc.type))},
1164 ),
1165 },
1166 }
1167
1168 if (reloc.symbol_table_index >= symbols.items.len)
1169 return d.failParse(
1170 "reloc {x} in section {x} has out-of-bounds symbol index {x}",
1171 .{ reloc_i, section_i + 1, reloc.symbol_table_index },
1172 );
1173
1174 try w.print("{f} {f} | {s}\n", .{
1175 fmtIntField(d, reloc.symbol_table_index, .{ .kind = .ord }),
1176 fmtSectionNumber(sym.section_number),
1177 sym.name,
1178 });
1179 }
1180 if (d.element(.newlines)) try w.writeByte('\n');
1181 }
1182 }
1183
1184 // Sections indices with raw data, sorted by RVA
1185 const rva_index = if (needs_data_dirs) rva_index: {
1186 const rva_index = try gpa.alloc(u16, sections_with_data);
1187 var indices_i: u16 = 0;
1188 for (sections.items, 0..) |*section, i| {
1189 if (section.header.size_of_raw_data == 0) continue;
1190 rva_index[indices_i] = @intCast(i);
1191 indices_i += 1;
1192 }
1193
1194 const Context = struct {
1195 indices: []u16,
1196 sections: []const Section,
1197
1198 pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool {
1199 return ctx.sections[ctx.indices[lhs]].header.virtual_address <
1200 ctx.sections[ctx.indices[rhs]].header.virtual_address;
1201 }
1202
1203 pub fn swap(ctx: @This(), lhs: usize, rhs: usize) void {
1204 std.mem.swap(u16, &ctx.indices[lhs], &ctx.indices[rhs]);
1205 }
1206 };
1207
1208 std.sort.pdqContext(0, rva_index.len, Context{
1209 .indices = rva_index,
1210 .sections = sections.items,
1211 });
1212
1213 break :rva_index rva_index;
1214 } else &.{};
1215 defer gpa.free(rva_index);
1216
1217 if (d.opts.exports) exports: {
1218 if (try seekToDataDirectory(
1219 d,
1220 rva_index,
1221 sections.items,
1222 (image_info orelse {
1223 try w.writeAll("COFF objects do not contain an export data directory");
1224 break :exports;
1225 }).data_dirs,
1226 .EXPORT,
1227 )) |section_index| {
1228 const export_dir = r.takeStruct(std.coff.ExportDirectoryTable, .little) catch |err|
1229 return d.failParse("unable to read export directory: {t}", .{err});
1230
1231 try w.print("Export directory:\n", .{});
1232 try dumpHeader(d, std.coff.ExportDirectoryTable, &export_dir, struct {
1233 pub fn major_version(id: *const DumpContext, h: *const std.coff.ExportDirectoryTable) !void {
1234 try dumpVersionField(id.w, "version", h.major_version, h.minor_version);
1235 }
1236 pub fn minor_version(_: *const DumpContext, _: *const std.coff.ExportDirectoryTable) !void {}
1237 });
1238
1239 const section = sections.items[section_index];
1240 const name_loc = section.rvaFileOffset(export_dir.name_rva) catch
1241 return d.failParse(
1242 "export name rva 0x{x} was not within the export section",
1243 .{export_dir.name_rva},
1244 );
1245
1246 const eat_loc = section.rvaFileOffset(export_dir.export_address_table_rva) catch
1247 return d.failParse(
1248 "export address table rva 0x{x} was not within the export section",
1249 .{export_dir.export_address_table_rva},
1250 );
1251
1252 const name_pointer_loc = section.rvaFileOffset(export_dir.name_pointer_table_rva) catch
1253 return d.failParse(
1254 "export name pointer table rva 0x{x} was not within the export section",
1255 .{export_dir.name_pointer_table_rva},
1256 );
1257
1258 const ord_loc = section.rvaFileOffset(export_dir.ordinal_table_rva) catch
1259 return d.failParse(
1260 "export ordinal table rva 0x{x} was not within the export section",
1261 .{export_dir.ordinal_table_rva},
1262 );
1263
1264 // All the variable length fields should be contained within this directory.
1265 // Read it entirely to avoid needing to seek per-name when iterating.
1266 const dir = image_info.?.data_dirs[@intFromEnum(DIRECTORY_ENTRY.EXPORT)];
1267 const dir_end_rva = dir.virtual_address + dir.size;
1268 const dir_loc = fr.logicalPos();
1269 const dir_slice = try r.readAlloc(gpa, dir.size);
1270 defer gpa.free(dir_slice);
1271
1272 const dll_name = std.mem.sliceTo(dir_slice[name_loc - dir_loc ..], 0);
1273 if (d.element(.@"table-header"))
1274 try w.print(
1275 \\
1276 \\Exports from {s}:
1277 \\ Ord Hint RVA Name
1278 \\
1279 , .{dll_name});
1280
1281 const name_pointers = dir_slice[name_pointer_loc - dir_loc ..][0 .. export_dir.number_of_names * @sizeOf(u32)];
1282 const ords = dir_slice[ord_loc - dir_loc ..][0 .. export_dir.number_of_names * @sizeOf(u16)];
1283 const addrs = dir_slice[eat_loc - dir_loc ..][0 .. export_dir.number_of_entries * @sizeOf(u32)];
1284 const name_rva_to_offset = dir.virtual_address + @sizeOf(std.coff.ExportDirectoryTable);
1285 for (0..export_dir.number_of_names) |name_i| {
1286 const name_rva = std.mem.readInt(u32, name_pointers[name_i * @sizeOf(u32) ..][0..@sizeOf(u32)], .little);
1287 const name = std.mem.sliceTo(dir_slice[name_rva - name_rva_to_offset ..], 0);
1288 if (!filterMatches(d.opts.symbol_filters, name))
1289 continue;
1290
1291 const ord = std.mem.readInt(u16, ords[name_i * @sizeOf(u16) ..][0..@sizeOf(u16)], .little);
1292 const addr = std.mem.readInt(u32, addrs[@as(u32, ord) * @sizeOf(u32) ..][0..@sizeOf(u32)], .little);
1293
1294 try w.print("{f} {f} ", .{
1295 fmtIntField(d, @as(u16, @intCast(export_dir.ordinal_base + ord)), .{ .kind = .ord }),
1296 fmtIntField(d, @as(u16, @intCast(name_i)), .{ .kind = .ord }),
1297 });
1298 const is_forwarder = addr >= dir.virtual_address and addr < dir_end_rva;
1299 if (is_forwarder) {
1300 try w.writeAll("forwards");
1301 } else {
1302 try w.print("{f}", .{fmtIntField(d, addr, .{ .kind = .rva })});
1303 }
1304
1305 try w.print(" | {s}", .{name});
1306 if (is_forwarder)
1307 try w.print(" -> {s}", .{std.mem.sliceTo(dir_slice[addr - name_rva_to_offset ..], 0)});
1308 try w.writeByte('\n');
1309 }
1310 }
1311 }
1312
1313 if (d.opts.imports) imports: {
1314 if (try seekToDataDirectory(
1315 d,
1316 rva_index,
1317 sections.items,
1318 (image_info orelse {
1319 try w.writeAll("COFF objects do not contain an import data directory");
1320 break :imports;
1321 }).data_dirs,
1322 .IMPORT,
1323 )) |_| {
1324 const Entry = std.coff.ImportDirectoryEntry;
1325 var directory_entries: std.ArrayList(Entry) = .empty;
1326 defer directory_entries.deinit(gpa);
1327 while (true) {
1328 const entry = r.takeStruct(Entry, .little) catch |err|
1329 return d.failParse(
1330 "unable to read import directory entry {x}: {t}",
1331 .{ directory_entries.items.len, err },
1332 );
1333
1334 if (std.mem.allEqual(u8, std.mem.asBytes(&entry), 0)) break;
1335 (try directory_entries.addOne(gpa)).* = entry;
1336 }
1337
1338 for (directory_entries.items) |entry| {
1339 const name_section = sectionContainingRva(
1340 rva_index,
1341 sections.items,
1342 entry.name_rva,
1343 ) orelse
1344 return d.failParse(
1345 "import directory entry name rva 0x{x} was not found in any section",
1346 .{entry.name_rva},
1347 );
1348
1349 const name_loc = sections.items[name_section].rvaFileOffset(
1350 entry.name_rva,
1351 ) catch unreachable;
1352 fr.seekTo(name_loc) catch |err|
1353 return d.failParse(
1354 "unable to seek to import directory entry name at 0x{x}: {t}",
1355 .{ name_loc, err },
1356 );
1357
1358 const dll_name = (try r.takeDelimiter(0)).?;
1359
1360 if (d.element(.@"header-name"))
1361 try w.print("Import table entry for {s}:\n", .{dll_name});
1362 try dumpHeader(d, Entry, &entry, struct {});
1363
1364 if (d.element(.@"table-header"))
1365 try w.print(
1366 \\
1367 \\ Ord Hint Name
1368 \\
1369 , .{});
1370
1371 const ilt_section = sectionContainingRva(
1372 rva_index,
1373 sections.items,
1374 entry.import_lookup_table_rva,
1375 ) orelse
1376 return d.failParse(
1377 "import directory entry ilt rva 0x{x} was not found in any section",
1378 .{entry.import_lookup_table_rva},
1379 );
1380
1381 const ilt_loc = sections.items[ilt_section].rvaFileOffset(
1382 entry.import_lookup_table_rva,
1383 ) catch unreachable;
1384 fr.seekTo(ilt_loc) catch |err|
1385 return d.failParse(
1386 "unable to seek to import directory ilt at 0x{x}: {t}",
1387 .{ ilt_loc, err },
1388 );
1389
1390 switch (image_info.?.magic) {
1391 _ => try w.writeAll("(unknown magic)"),
1392 inline else => |m| {
1393 const TableEntry = std.coff.ImportLookupTableEntry(m);
1394 const null_entry: TableEntry = @bitCast(@as(@typeInfo(TableEntry).@"struct".backing_integer.?, 0));
1395
1396 var ilt_entries: std.ArrayList(TableEntry) = .empty;
1397 defer ilt_entries.deinit(gpa);
1398 while (true) {
1399 const table_entry = r.takeStruct(TableEntry, .little) catch |err|
1400 return d.failParse(
1401 "unable to read ilt entry {s}:{x}: {t}",
1402 .{ dll_name, ilt_entries.items.len, err },
1403 );
1404 if (table_entry == null_entry) break;
1405 (try ilt_entries.addOne(gpa)).* = table_entry;
1406 }
1407
1408 for (ilt_entries.items, 0..) |ilt_entry, ilt_entry_i| {
1409 if (ilt_entry.is_ordinal) {
1410 try w.print("{x: >4}", .{ilt_entry.payload.ordinal.ordinal});
1411 } else {
1412 const hint_section = sectionContainingRva(
1413 rva_index,
1414 sections.items,
1415 ilt_entry.payload.hint_name_rva,
1416 ) orelse
1417 return d.failParse(
1418 "import directory ilt entry 0x{x}'s hint rva 0x{x} was not found in any section",
1419 .{ ilt_entry_i, ilt_entry.payload.hint_name_rva },
1420 );
1421
1422 const hint_loc = sections.items[hint_section].rvaFileOffset(
1423 ilt_entry.payload.hint_name_rva,
1424 ) catch unreachable;
1425 fr.seekTo(hint_loc) catch |err|
1426 return d.failParse(
1427 "unable to seek to ilt entry 0x{x}'s hint at 0x{x}: {t}",
1428 .{ ilt_entry_i, hint_loc, err },
1429 );
1430
1431 const hint = r.takeInt(u16, .little) catch |err|
1432 return d.failParse(
1433 "unable to read import directory ilt entry 0x{x}'s hint: {t}",
1434 .{ ilt_entry_i, err },
1435 );
1436
1437 const name = r.takeDelimiter(0) catch |err|
1438 return d.failParse(
1439 "unable to read import directory ilt entry 0x{x}'s name: {t}",
1440 .{ ilt_entry_i, err },
1441 );
1442
1443 try w.print(" {x: >4} | {s}\n", .{ hint, name.? });
1444 }
1445 }
1446 if (d.element(.newlines)) try w.writeByte('\n');
1447 },
1448 }
1449 }
1450 }
1451 }
1452
1453 if (d.opts.tls) tls: {
1454 if (try seekToDataDirectory(
1455 d,
1456 rva_index,
1457 sections.items,
1458 (image_info orelse {
1459 try w.writeAll("COFF objects do not contain a TLS data directory");
1460 break :tls;
1461 }).data_dirs,
1462 .TLS,
1463 )) |_| {
1464 switch (image_info.?.magic) {
1465 _ => try w.writeAll("(unknown magic)"),
1466 inline else => |m| {
1467 const TlsDirectoryEntry = std.coff.TlsDirectoryEntry(m);
1468 const tls_entry = r.takeStruct(TlsDirectoryEntry, .little) catch |err|
1469 return d.failParse("unable to read tls directory: {t}", .{err});
1470
1471 try w.writeAll("TLS Directory:\n");
1472 try dumpHeader(d, TlsDirectoryEntry, &tls_entry, struct {});
1473
1474 try w.writeAll(" | ");
1475 if (tls_entry.characteristics.alignment == .NONE) {
1476 try w.writeAll("Alignment not specified");
1477 } else {
1478 try w.print(
1479 "Alignment: {d}",
1480 .{tls_entry.characteristics.alignment.toByteUnits().?},
1481 );
1482 }
1483
1484 try w.writeAll(
1485 \\
1486 \\
1487 \\TLS Callbacks:
1488 \\ Address
1489 \\
1490 );
1491
1492 const callbacks_rva: u32 = @intCast(tls_entry.callbacks_va - image_info.?.image_base);
1493 const section_index = sectionContainingRva(
1494 rva_index,
1495 sections.items,
1496 callbacks_rva,
1497 ) orelse
1498 return d.failParse(
1499 "tls callbacks rva 0x{x} was not found in any section",
1500 .{callbacks_rva},
1501 );
1502
1503 const callbacks_loc = sections.items[section_index]
1504 .rvaFileOffset(callbacks_rva) catch unreachable;
1505
1506 fr.seekTo(callbacks_loc) catch |err|
1507 return d.failParse(
1508 "unable to seek to tls callbacks array at offset 0x{x}: {t}",
1509 .{ callbacks_loc, err },
1510 );
1511
1512 while (true) {
1513 const callback_va = r.takeInt(@FieldType(TlsDirectoryEntry, "callbacks_va"), .little) catch |err|
1514 return d.failParse(
1515 "unable to read tls callbacks array: {t}",
1516 .{err},
1517 );
1518
1519 try w.print("{f}\n", .{fmtIntField(d, callback_va, .{ .kind = .va })});
1520 if (callback_va == 0) break;
1521 }
1522 if (d.element(.newlines)) try w.writeByte('\n');
1523 },
1524 }
1525 }
1526 }
1527 }
1528
1529 fn seekToDataDirectory(
1530 d: *const DumpContext,
1531 rva_index: []const u16,
1532 sections: []const Section,
1533 data_dirs: []const std.coff.ImageDataDirectory,
1534 entry: DIRECTORY_ENTRY,
1535 ) !?u16 {
1536 if (@intFromEnum(entry) < data_dirs.len) blk: {
1537 const rva = data_dirs[@intFromEnum(entry)].virtual_address;
1538 if (rva == 0) break :blk;
1539
1540 const section_index = sectionContainingRva(rva_index, sections, rva) orelse
1541 return d.failParse(
1542 "{t} directory rva 0x{x} was not found in any section",
1543 .{ entry, rva },
1544 );
1545
1546 const file_offset = sections[section_index].rvaFileOffset(rva) catch unreachable;
1547 d.fr.seekTo(file_offset) catch |err|
1548 return d.failParse(
1549 "unable to seek to {t} directory at offset 0x{x}: {t}",
1550 .{ entry, file_offset, err },
1551 );
1552
1553 return section_index;
1554 }
1555
1556 try d.w.print("{t} directory was not present in optional header\n", .{entry});
1557 return null;
1558 }
1559
1560 fn sectionContainingRva(
1561 /// Indices into `sections` sorted by rva
1562 indices: []const u16,
1563 sections: []const Section,
1564 rva: u32,
1565 ) ?u16 {
1566 const Context = struct {
1567 rva: u32,
1568 sections: []const Section,
1569
1570 fn order(ctx: @This(), section_index: u16) std.math.Order {
1571 const h = &ctx.sections[section_index].header;
1572 if (ctx.rva < h.virtual_address) return .lt;
1573 const end = h.virtual_address + h.size_of_raw_data;
1574 if (ctx.rva >= end) return .gt;
1575 return .eq;
1576 }
1577 };
1578
1579 const indices_index = std.sort.binarySearch(u16, indices, Context{
1580 .rva = rva,
1581 .sections = sections,
1582 }, Context.order) orelse return null;
1583 return @intCast(indices[indices_index]);
1584 }
1585
1586 fn headerName(raw: *const [8]u8, string_table: []const u8) ![]const u8 {
1587 return if (raw[0] == '/') name: {
1588 const name_offset = try std.fmt.parseUnsigned(u24, std.mem.sliceTo(raw[1..], 0), 10);
1589 if (name_offset >= string_table.len)
1590 return error.OutOfBounds;
1591
1592 break :name std.mem.sliceTo(string_table[name_offset..], 0);
1593 } else std.mem.sliceTo(raw, 0);
1594 }
1595
1596 fn fmtSectionNumber(section_number: std.coff.SectionNumber) std.fmt.Alt(std.coff.SectionNumber, sectionNumberString) {
1597 return .{ .data = section_number };
1598 }
1599
1600 fn sectionNumberString(section_number: std.coff.SectionNumber, w: *std.Io.Writer) std.Io.Writer.Error!void {
1601 try switch (section_number) {
1602 .UNDEFINED => w.writeAll("UNDEF"),
1603 .ABSOLUTE => w.writeAll(" ABS"),
1604 .DEBUG => w.writeAll("DEBUG"),
1605 else => |v| {
1606 const backing = @intFromEnum(v);
1607 const fmt = "{x: >5}";
1608 if (backing >= 0)
1609 try w.print(fmt, .{@as(u15, @intCast(backing))})
1610 else
1611 try w.print(fmt, .{backing});
1612 },
1613 };
1614 }
1615
1616 const FormatIntField = struct {
1617 val: ?u64,
1618 width: ?usize,
1619 zero_fill: bool,
1620 };
1621
1622 fn fmtIntField(
1623 d: *const DumpContext,
1624 val: anytype,
1625 params: struct {
1626 kind: ?FieldKind = null,
1627 width: union(enum) {
1628 fit_max,
1629 auto,
1630 explicit: usize,
1631 } = .fit_max,
1632 zero_fill: bool = false,
1633 },
1634 ) std.fmt.Alt(FormatIntField, intFieldString) {
1635 return .{
1636 .data = .{
1637 .val = if (d.redacted(params.kind)) null else val,
1638 .width = switch (params.width) {
1639 .fit_max => @typeInfo(@TypeOf(val)).int.bits / 4,
1640 .auto => null,
1641 .explicit => |w| w,
1642 },
1643 .zero_fill = params.zero_fill,
1644 },
1645 };
1646 }
1647
1648 fn intFieldString(field: FormatIntField, w: *std.Io.Writer) std.Io.Writer.Error!void {
1649 if (field.val) |val| {
1650 try w.printInt(val, 16, .lower, .{
1651 .width = field.width,
1652 .alignment = .right,
1653 .fill = if (field.zero_fill) '0' else ' ',
1654 });
1655 } else try w.splatByteAll('x', field.width orelse 1);
1656 }
1657
1658 fn dumpFlags(w: *Io.Writer, comptime fmt: []const u8, comptime T: type, flags: *const T, cols: u32) !void {
1659 const s = @typeInfo(T).@"struct";
1660 inline for (s.field_names, s.field_types) |field_name, field_type| {
1661 if (field_type == bool and @field(flags, field_name)) {
1662 try w.splatByteAll(' ', cols);
1663 try w.print(fmt, .{field_name});
1664 }
1665 }
1666 }
1667
1668 fn dumpArchiveHeader(d: *const DumpContext, header: *const ArchiveHeader, pos: u32) !void {
1669 if (d.element(.@"header-name"))
1670 try d.w.print("Archive member at offset 0x{x}: '{s}'\n", .{ pos, header.name });
1671 try dumpHeader(d, ArchiveHeader, header, struct {
1672 pub fn name(_: *const DumpContext, _: *const ArchiveHeader) !void {}
1673 pub fn file_mode(id: *const DumpContext, h: *const ArchiveHeader) !void {
1674 try id.w.print("{o: >16} file_mode\n", .{h.file_mode});
1675 }
1676 });
1677 }
1678
1679 fn fieldKind(name: []const u8) ?FieldKind {
1680 if (std.mem.endsWith(u8, name, "_rva"))
1681 return .rva;
1682 if (std.mem.endsWith(u8, name, "_va") or
1683 std.mem.endsWith(u8, name, "_address") or
1684 std.mem.startsWith(u8, name, "pointer_"))
1685 return .va;
1686 if (std.mem.startsWith(u8, name, "number_") or
1687 std.mem.startsWith(u8, name, "size"))
1688 return .size;
1689 if (std.mem.startsWith(u8, name, "hint"))
1690 return .ord;
1691 return null;
1692 }
1693
1694 fn dumpHeader(
1695 d: *const DumpContext,
1696 comptime T: type,
1697 header: *const T,
1698 Custom: type,
1699 ) !void {
1700 const s = @typeInfo(T).@"struct";
1701 inline for (s.field_names, s.field_types) |field_name, field_type| {
1702 const val = &@field(header, field_name);
1703 if (@hasDecl(Custom, field_name)) {
1704 try @field(Custom, field_name)(d, header);
1705 } else {
1706 switch (@typeInfo(field_type)) {
1707 .int => try d.w.print("{f} {s}\n", .{ fmtIntField(d, val.*, .{
1708 .kind = comptime fieldKind(field_name),
1709 .width = .{ .explicit = 16 },
1710 }), field_name }),
1711 .@"enum" => try d.w.print("{x: >16} {s} ({t})\n", .{ val.*, field_name, val.* }),
1712 .@"struct" => |s_field| {
1713 switch (s_field.layout) {
1714 .auto,
1715 .@"extern",
1716 => try dumpHeader(d, field_type, val, Custom),
1717 .@"packed" => {
1718 try d.w.print("{x: >16} {s}\n", .{ @as(s_field.backing_integer.?, @bitCast(val.*)), field_name });
1719 try dumpFlags(d.w, "| {s}\n", field_type, val, 15);
1720 },
1721 }
1722 },
1723 else => unreachable,
1724 }
1725 }
1726 }
1727 }
1728
1729 fn dumpVersionField(w: *Io.Writer, name: []const u8, major: anytype, minor: anytype) !void {
1730 try w.print("{d: >13}.{x:0<2} {s}\n", .{ major, minor, name });
1731 }
1732
1733 fn dumpRvaField(d: *const DumpContext, name: []const u8, rva: u64, base: u64) !void {
1734 try d.w.print("{f} {s} ({f})\n", .{
1735 fmtIntField(d, rva, .{ .kind = .rva }),
1736 name,
1737 fmtIntField(d, base + rva, .{ .kind = .va }),
1738 });
1739 }
1740};
1741
1742fn filterMatches(filters: []const []const u8, val: []const u8) bool {
1743 return for (filters) |filter| {
1744 if (std.mem.containsAtLeast(u8, val, 1, filter)) break true;
1745 } else filters.len == 0;
1746}
1747
871748const usage =
881749 \\Usage: zig objdump [options] file
891750 \\
901751 \\Options:
91 \\ -h, --help Print this help and exit
92 \\
1752 \\ -h, --help Print this help and exit
1753 \\ --all-headers Alias for --file-headers --linker-member=2 --member-headers --section-headers --relocs --symbols
1754 \\ --exports[=sort] Display exported symbols.
1755 \\ In the case of COFF import libraries, displays the symbol list and import headers.
1756 \\ Specify =sort to optionally sort the import headers by symbol name.
1757 \\ --file-headers Display file-format specific headers
1758 \\ --imports Display imported symbols
1759 \\ --linker-member[=1|2|longnames] (Coff) Display contents of the specified archive linker member (default 2)
1760 \\ --member-headers Display archive member headers
1761 \\ --elements=[e1],[e2],-[e3],... Select which formatting elements are displayed. Intended for snapshot testing.
1762 \\ file-type File type summary
1763 \\ header-name Name that precedes a header block
1764 \\ member-path Display full member paths. If removed, only basenames will be used.
1765 \\ newlines Newlines between output sections
1766 \\ table-header Table headers with column names
1767 \\ all (default) All of the above
1768 \\ --only-member=[name] Only consider archive members names that contain [name]. Can be specified multiple times.
1769 \\ --only-section=[name] Only consider section names that contain [name]. Can be specified multiple times.
1770 \\ --only-symbol=[name] Only consider symbol names that contain [name]. Can be specified multiple times.
1771 \\ --redact=[kind] Redact the specified field kind. Intended for snapshot testing.
1772 \\ rva Relative virtual addresses
1773 \\ va Virtual addresses and file offsets
1774 \\ ord Symbol ordinals / hints
1775 \\ size Sizes and lengths
1776 \\ all All of the above
1777 \\ --relocs Display relocations
1778 \\ -s, --snapshot Alias for --redact=all --elements=-all
1779 \\ --section-headers Display section headers
1780 \\ --strings Display string tables
1781 \\ --symbols Display symbol tables
1782 \\ --tls Display TLS information
931783;
lib/compiler/resinator/cvtres.zig+1-1
......@@ -383,7 +383,7 @@ pub fn writeCoff(
383383fn writeSymbol(writer: *std.Io.Writer, symbol: std.coff.Symbol) !void {
384384 try writer.writeAll(&symbol.name);
385385 try writer.writeInt(u32, symbol.value, .little);
386 try writer.writeInt(u16, @intFromEnum(symbol.section_number), .little);
386 try writer.writeInt(i16, @intFromEnum(symbol.section_number), .little);
387387 try writer.writeInt(u8, @intFromEnum(symbol.type.base_type), .little);
388388 try writer.writeInt(u8, @intFromEnum(symbol.type.complex_type), .little);
389389 try writer.writeInt(u8, @intFromEnum(symbol.storage_class), .little);
lib/std/Build/Configuration.zig+5-1
......@@ -587,6 +587,8 @@ pub const Step = extern struct {
587587 expect_stderr_match: Storage.FlagLengthPrefixedList(.flags2, .expect_stderr_match, Bytes),
588588 expect_stdout_match: Storage.FlagLengthPrefixedList(.flags2, .expect_stdout_match, Bytes),
589589 expect_term_value: Storage.FlagOptional(.flags2, .expect_term, u32),
590 expect_stdout_snapshot: Storage.FlagOptional(.flags2, .expect_stdout_snapshot, LazyPath.Index),
591 expect_stderr_snapshot: Storage.FlagOptional(.flags2, .expect_stderr_snapshot, LazyPath.Index),
590592
591593 pub const CapturedStream = extern struct {
592594 generated_file: GeneratedFileIndex,
......@@ -686,7 +688,9 @@ pub const Step = extern struct {
686688 expect_stdout_match: bool,
687689 expect_term: bool,
688690 expect_term_status: ExpectTermStatus,
689 _: u25 = 0,
691 expect_stdout_snapshot: bool,
692 expect_stderr_snapshot: bool,
693 _: u23 = 0,
690694 };
691695 };
692696
lib/std/Build/Step/Run.zig+9
......@@ -133,6 +133,8 @@ pub const StdIo = union(enum) {
133133 expect_stdout_exact: []const u8,
134134 expect_stdout_match: []const u8,
135135 expect_term: process.Child.Term,
136 expect_stderr_snapshot: std.Build.LazyPath,
137 expect_stdout_snapshot: std.Build.LazyPath,
136138 };
137139};
138140
......@@ -682,6 +684,13 @@ pub fn addCheck(run: *Run, new_check: StdIo.Check) void {
682684 .check => |*checks| checks.append(b.allocator, new_check) catch @panic("OOM"),
683685 else => @panic("illegal call to addCheck: conflicting helper method calls. Suggest to directly set stdio field of Run instead"),
684686 }
687
688 switch (new_check) {
689 .expect_stderr_snapshot,
690 .expect_stdout_snapshot,
691 => |file| run.addFileInput(file),
692 else => {},
693 }
685694}
686695
687696pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath {
lib/std/coff.zig+246-62
......@@ -2,6 +2,12 @@ const std = @import("std.zig");
22const assert = std.debug.assert;
33const mem = std.mem;
44
5pub const archive_signature = "!<arch>\n";
6pub const archive_end_of_header = "`\n";
7
8pub const pe_signature = "PE\x00\x00";
9pub const pe_pointer_offset = 0x3C;
10
511pub const Header = extern struct {
612 /// The number that identifies the type of target machine.
713 machine: IMAGE.FILE.MACHINE,
......@@ -367,6 +373,36 @@ pub const DebugType = enum(u32) {
367373 _,
368374};
369375
376pub fn TlsDirectoryEntry(comptime magic: std.coff.OptionalHeader.Magic) type {
377 return switch (magic) {
378 _ => comptime unreachable,
379 .PE32 => extern struct {
380 raw_data_start_va: u32,
381 raw_data_end_va: u32,
382 tls_index_va: u32,
383 callbacks_va: u32,
384 size_of_zero_fill: u32,
385 characteristics: packed struct(u32) {
386 _reserved_0: u19,
387 alignment: SectionHeader.Flags.Align,
388 _reserved_1: u9,
389 },
390 },
391 .@"PE32+" => extern struct {
392 raw_data_start_va: u64,
393 raw_data_end_va: u64,
394 tls_index_va: u64,
395 callbacks_va: u64,
396 size_of_zero_fill: u32,
397 characteristics: packed struct(u32) {
398 _reserved_0: u19,
399 alignment: SectionHeader.Flags.Align,
400 _reserved_1: u9,
401 },
402 },
403 };
404}
405
370406pub const ImportDirectoryEntry = extern struct {
371407 /// The RVA of the import lookup table.
372408 /// This table contains a name or ordinal for each import.
......@@ -389,56 +425,28 @@ pub const ImportDirectoryEntry = extern struct {
389425 import_address_table_rva: u32,
390426};
391427
392pub const ImportLookupEntry32 = struct {
393 pub const ByName = packed struct(u32) {
394 name_table_rva: u31,
395 flag: u1 = 0,
428pub fn ImportLookupTableEntry(comptime magic: std.coff.OptionalHeader.Magic) type {
429 const Payload = packed union(u31) {
430 ordinal: packed struct(u31) {
431 ordinal: u16,
432 _: u15 = 0,
433 },
434 hint_name_rva: u31,
396435 };
397436
398 pub const ByOrdinal = packed struct(u32) {
399 ordinal_number: u16,
400 unused: u15 = 0,
401 flag: u1 = 1,
437 return switch (magic) {
438 _ => comptime unreachable,
439 .PE32 => packed struct(u32) {
440 payload: Payload,
441 is_ordinal: bool,
442 },
443 .@"PE32+" => packed struct(u64) {
444 payload: Payload,
445 _: u32 = 0,
446 is_ordinal: bool,
447 },
402448 };
403
404 const mask = 0x80000000;
405
406 pub fn getImportByName(raw: u32) ?ByName {
407 if (mask & raw != 0) return null;
408 return @as(ByName, @bitCast(raw));
409 }
410
411 pub fn getImportByOrdinal(raw: u32) ?ByOrdinal {
412 if (mask & raw == 0) return null;
413 return @as(ByOrdinal, @bitCast(raw));
414 }
415};
416
417pub const ImportLookupEntry64 = struct {
418 pub const ByName = packed struct(u64) {
419 name_table_rva: u31,
420 unused: u32 = 0,
421 flag: u1 = 0,
422 };
423
424 pub const ByOrdinal = packed struct(u64) {
425 ordinal_number: u16,
426 unused: u47 = 0,
427 flag: u1 = 1,
428 };
429
430 const mask = 0x8000000000000000;
431
432 pub fn getImportByName(raw: u64) ?ByName {
433 if (mask & raw != 0) return null;
434 return @as(ByName, @bitCast(raw));
435 }
436
437 pub fn getImportByOrdinal(raw: u64) ?ByOrdinal {
438 if (mask & raw == 0) return null;
439 return @as(ByOrdinal, @bitCast(raw));
440 }
441};
449}
442450
443451/// Every name ends with a NULL byte. IF the NULL byte does not fall on
444452/// 2byte boundary, the entry structure is padded to ensure 2byte alignment.
......@@ -452,6 +460,50 @@ pub const ImportHintNameEntry = extern struct {
452460 name: [1]u8,
453461};
454462
463pub const ExportDirectoryTable = extern struct {
464 /// Reserved
465 flags: u32,
466
467 /// Creation time of this table
468 time_date_stamp: u32,
469
470 major_version: u16,
471 minor_version: u16,
472
473 /// The address of an ASCII string that contains the name of the DLL.
474 /// This address is relative to the image base.
475 name_rva: u32,
476
477 /// The ordinal of the first export in this image
478 ordinal_base: u32,
479
480 /// Number of entries in the export address table
481 number_of_entries: u32,
482
483 /// Number of entries in the name pointer table and ordinal table
484 number_of_names: u32,
485
486 export_address_table_rva: u32,
487 name_pointer_table_rva: u32,
488 ordinal_table_rva: u32,
489};
490
491pub const ExportAddressTableEntry = extern struct {
492 /// If this address is within the export section, then this is the address of the export
493 /// Otherwise, this is the address of a string that specfies a symbol in another DLL:
494 /// <dll name>.<export name>
495 /// <dll name>.#<export ordinal>
496 export_or_forwarder_rva: u32,
497};
498
499pub const ExportNamePointerTableEntry = extern struct {
500 name_rva: u32,
501};
502
503pub const ExportOrdinalTableEntry = extern struct {
504 unbiased_ordinal: u16,
505};
506
455507pub const SectionHeader = extern struct {
456508 name: [8]u8,
457509 virtual_size: u32,
......@@ -610,11 +662,15 @@ pub const SectionHeader = extern struct {
610662 std.debug.assert(std.math.isPowerOfTwo(n));
611663 return @enumFromInt(@ctz(n) + 1);
612664 }
665
666 pub fn alignment(a: Align) ?std.mem.Alignment {
667 return .fromByteUnitsOptional(a.toByteUnits() orelse null);
668 }
613669 };
614670 };
615671};
616672
617pub const Symbol = struct {
673pub const Symbol = extern struct {
618674 name: [8]u8,
619675 value: u32,
620676 section_number: SectionNumber,
......@@ -622,7 +678,7 @@ pub const Symbol = struct {
622678 storage_class: StorageClass,
623679 number_of_aux_symbols: u8,
624680
625 pub fn sizeOf() usize {
681 pub fn sizeOf() comptime_int {
626682 return 18;
627683 }
628684
......@@ -639,18 +695,18 @@ pub const Symbol = struct {
639695 }
640696};
641697
642pub const SectionNumber = enum(u16) {
698pub const SectionNumber = enum(i16) {
643699 /// The symbol record is not yet assigned a section.
644700 /// A value of zero indicates that a reference to an external symbol is defined elsewhere.
645701 /// A value of non-zero is a common symbol with a size that is specified by the value.
646702 UNDEFINED = 0,
647703
648704 /// The symbol has an absolute (non-relocatable) value and is not an address.
649 ABSOLUTE = 0xffff,
705 ABSOLUTE = -1,
650706
651707 /// The symbol provides general type or debugging information but does not correspond to a section.
652708 /// Microsoft tools use this setting along with .file records (storage class FILE).
653 DEBUG = 0xfffe,
709 DEBUG = -2,
654710 _,
655711};
656712
......@@ -822,7 +878,7 @@ pub const StorageClass = enum(u8) {
822878 _,
823879};
824880
825pub const FunctionDefinition = struct {
881pub const FunctionDefinition = extern struct {
826882 /// The symbol-table index of the corresponding .bf (begin function) symbol record.
827883 tag_index: u32,
828884
......@@ -841,7 +897,7 @@ pub const FunctionDefinition = struct {
841897 unused: [2]u8,
842898};
843899
844pub const SectionDefinition = struct {
900pub const SectionDefinition = extern struct {
845901 /// The size of section data; the same as SizeOfRawData in the section header.
846902 length: u32,
847903
......@@ -863,7 +919,7 @@ pub const SectionDefinition = struct {
863919 unused: [3]u8,
864920};
865921
866pub const FileDefinition = struct {
922pub const FileDefinition = extern struct {
867923 /// An ANSI string that gives the name of the source file.
868924 /// This is padded with nulls if it is less than the maximum length.
869925 file_name: [18]u8,
......@@ -874,7 +930,7 @@ pub const FileDefinition = struct {
874930 }
875931};
876932
877pub const WeakExternalDefinition = struct {
933pub const WeakExternalDefinition = extern struct {
878934 /// The symbol-table index of sym2, the symbol to be linked if sym1 is not found.
879935 tag_index: u32,
880936
......@@ -885,7 +941,7 @@ pub const WeakExternalDefinition = struct {
885941
886942 unused: [10]u8,
887943
888 pub fn sizeOf() usize {
944 pub fn sizeOf() comptime_int {
889945 return 18;
890946 }
891947};
......@@ -933,7 +989,7 @@ pub const ComdatSelection = enum(u8) {
933989 _,
934990};
935991
936pub const DebugInfoDefinition = struct {
992pub const DebugInfoDefinition = extern struct {
937993 unused_1: [4]u8,
938994
939995 /// 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 {
9711027
9721028 // The lifetime of `data` must be longer than the lifetime of the returned Coff
9731029 pub fn init(data: []const u8, is_loaded: bool) error{ EndOfStream, MissingPEHeader }!Coff {
974 const pe_pointer_offset = 0x3C;
975 const pe_magic = "PE\x00\x00";
976
9771030 if (data.len < pe_pointer_offset + 4) return error.EndOfStream;
9781031 const header_offset = mem.readInt(u32, data[pe_pointer_offset..][0..4], .little);
9791032 if (data.len < header_offset + 4) return error.EndOfStream;
980 const is_image = mem.eql(u8, data[header_offset..][0..4], pe_magic);
1033 const is_image = mem.eql(u8, data[header_offset..][0..4], pe_signature);
9811034
9821035 const coff: Coff = .{
9831036 .data = data,
......@@ -1348,6 +1401,10 @@ pub const Relocation = extern struct {
13481401 virtual_address: u32,
13491402 symbol_table_index: u32,
13501403 type: u16,
1404
1405 pub fn sizeOf() comptime_int {
1406 return 10;
1407 }
13511408};
13521409
13531410pub const IMAGE = struct {
......@@ -1465,6 +1522,36 @@ pub const IMAGE = struct {
14651522 _,
14661523 /// AXP 64 (Same as Alpha 64)
14671524 pub const AXP64: IMAGE.FILE.MACHINE = .ALPHA64;
1525
1526 pub fn RelocationType(comptime machine: IMAGE.FILE.MACHINE) type {
1527 return switch (machine) {
1528 .AMD64,
1529 => REL.AMD64,
1530 .ARM,
1531 .ARMNT,
1532 => REL.ARM,
1533 .ARM64,
1534 .ARM64EC,
1535 .ARM64X,
1536 => REL.ARM64,
1537 .I386 => REL.I386,
1538 .IA64 => REL.IA64,
1539 .M32R => REL.M32R,
1540 .MIPS16,
1541 .MIPSFPU,
1542 .MIPSFPU16,
1543 => REL.MIPS,
1544 .POWERPC,
1545 .POWERPCFP,
1546 => REL.PPC,
1547 .SH3,
1548 .SH3DSP,
1549 .SH4,
1550 .SH5,
1551 => REL.SH,
1552 else => void,
1553 };
1554 }
14681555 };
14691556 };
14701557
......@@ -1919,3 +2006,100 @@ pub const IMAGE = struct {
19192006 };
19202007 };
19212008};
2009
2010pub const ArchiveMemberHeader = extern struct {
2011 /// Left-justified '/' terminated member name
2012 name: [16]u8,
2013 /// Left-justified ASCII decimal: seconds since January 1st, 1970
2014 date: [12]u8,
2015 /// Left-justified ASCII decimal: user id
2016 user_id: [6]u8,
2017 /// Left-justified ASCII decimal: group id
2018 group_id: [6]u8,
2019 /// Left-justified ASCII octal: file mode
2020 file_mode: [8]u8,
2021 /// Left-justified ASCII decimal: size of the member following this header,
2022 /// not including the size of this header.
2023 size: [10]u8,
2024 /// The literal string '`\n'
2025 end_of_header: [2]u8,
2026
2027 /// Extracts the name of the member by either reading it directly from
2028 /// the header, or by finding it inside the longnames member, if provided.
2029 pub fn parseName(
2030 self: *const ArchiveMemberHeader,
2031 opt_longnames: ?[]const u8,
2032 ) ![]const u8 {
2033 const trim = std.mem.trimEnd(u8, &self.name, &.{' '});
2034
2035 if (trim.len == 0) return error.BadName;
2036 return if (trim[0] == '/') name: {
2037 if (trim.len == 1 or
2038 trim.len == 2 and trim[1] == '/')
2039 break :name trim;
2040
2041 const offset = std.fmt.parseUnsigned(u50, trim[1..], 10) catch
2042 return error.BadName;
2043
2044 if (opt_longnames) |longnames| {
2045 if (offset >= longnames.len) return error.BadName;
2046 break :name std.mem.sliceTo(longnames[@intCast(offset)..], 0);
2047 } else return error.NoLongNames;
2048 } else if (trim[trim.len - 1] == '/')
2049 trim[0 .. trim.len - 1]
2050 else
2051 return error.BadName;
2052 }
2053
2054 fn parseField(field: []const u8, T: type, base: u8) !T {
2055 if (std.mem.allEqual(u8, field, ' ')) return 0;
2056 if (field[0] == '-')
2057 return @bitCast(try std.fmt.parseInt(
2058 @Int(.signed, @typeInfo(T).int.bits),
2059 std.mem.trimEnd(u8, field, &.{' '}),
2060 base,
2061 ));
2062
2063 return std.fmt.parseUnsigned(T, std.mem.trimEnd(u8, field, &.{' '}), base);
2064 }
2065
2066 pub fn parseDate(self: *const ArchiveMemberHeader) !u40 {
2067 return parseField(&self.date, u40, 10);
2068 }
2069
2070 pub fn parseUserId(self: *const ArchiveMemberHeader) !u20 {
2071 return parseField(&self.user_id, u20, 10);
2072 }
2073
2074 pub fn parseGroupId(self: *const ArchiveMemberHeader) !u20 {
2075 return parseField(&self.group_id, u20, 10);
2076 }
2077
2078 pub fn parseFileMode(self: *const ArchiveMemberHeader) !u20 {
2079 return parseField(&self.group_id, u20, 8);
2080 }
2081
2082 pub fn parseSize(self: *const ArchiveMemberHeader) !u34 {
2083 return parseField(&self.size, u34, 10);
2084 }
2085
2086 pub const Kind = enum {
2087 first_linker,
2088 second_linker,
2089 longnames,
2090 coff,
2091 import,
2092 };
2093};
2094
2095pub const LineNumber = extern struct {
2096 type: extern union {
2097 symbol_table_index: u32,
2098 virtual_address: u32,
2099 },
2100 line_number: u16,
2101
2102 pub fn sizeOf() comptime_int {
2103 return 6;
2104 }
2105};
lib/std/meta.zig+9
......@@ -499,6 +499,15 @@ test DeclEnum {
499499 try expectEqualEnum(enum {}, DeclEnum(D));
500500}
501501
502pub fn BareUnion(comptime T: type) type {
503 const u = switch (@typeInfo(T)) {
504 .@"union" => |u| u,
505 else => @compileError("expected union type, found '" ++ @typeName(T) ++ "'"),
506 };
507
508 return @Union(u.layout, null, u.field_names, u.field_types[0..], u.field_attrs[0..]);
509}
510
502511pub fn Tag(comptime T: type) type {
503512 return switch (@typeInfo(T)) {
504513 .@"enum" => |info| info.tag_type,
lib/std/multi_array_list.zig+1-1
......@@ -44,7 +44,7 @@ pub fn MultiArrayList(comptime T: type) type {
4444 const Elem = switch (@typeInfo(T)) {
4545 .@"struct" => T,
4646 .@"union" => |u| struct {
47 pub const Bare = @Union(u.layout, null, u.field_names, u.field_types[0..], u.field_attrs[0..]);
47 pub const Bare = std.meta.BareUnion(T);
4848 pub const Tag =
4949 u.tag_type orelse @compileError("MultiArrayList does not support untagged unions");
5050 tags: Tag,
lib/std/start.zig+1-1
......@@ -93,7 +93,7 @@ fn DllMainCRTStartup(
9393 fdwReason: std.os.windows.DWORD,
9494 lpReserved: std.os.windows.LPVOID,
9595) callconv(.winapi) std.os.windows.BOOL {
96 if (!builtin.single_threaded and !builtin.link_libc) {
96 if (!builtin.single_threaded) {
9797 _ = @import("os/windows/tls.zig");
9898 }
9999
lib/std/zig.zig-4
......@@ -996,14 +996,11 @@ pub const EmitArtifact = enum {
996996 docs,
997997 pdb,
998998 h,
999 compiler_rt_dyn_lib,
1000999
10011000 /// If using `Server` to communicate with the compiler, it will place requested artifacts in
10021001 /// paths under the output directory, where those paths are named according to this function.
10031002 /// Returned string is allocated with `gpa` and owned by the caller.
10041003 pub fn cacheName(ea: EmitArtifact, gpa: Allocator, opts: BinNameOptions) Allocator.Error![]const u8 {
1005 // hack for stage2_x86_64 + coff. See Coff.flush.
1006 if (ea == .compiler_rt_dyn_lib) return "compiler_rt.dll";
10071004 const suffix: []const u8 = switch (ea) {
10081005 .bin => return binNameAlloc(gpa, opts),
10091006 .@"asm" => ".s",
......@@ -1013,7 +1010,6 @@ pub const EmitArtifact = enum {
10131010 .docs => "-docs",
10141011 .pdb => ".pdb",
10151012 .h => ".h",
1016 .compiler_rt_dyn_lib => unreachable,
10171013 };
10181014 return std.fmt.allocPrint(gpa, "{s}{s}", .{ opts.root_name, suffix });
10191015 }
src/Compilation.zig+52-50
......@@ -223,8 +223,6 @@ compiler_rt_lib: ?CrtFile = null,
223223/// Populated when we build the compiler_rt_obj object. A Job to build this is indicated
224224/// by setting `queued_jobs.compiler_rt_obj` and resolved before calling linker.flush().
225225compiler_rt_obj: ?CrtFile = null,
226/// hack for stage2_x86_64 + coff
227compiler_rt_dyn_lib: ?CrtFile = null,
228226/// Populated when we build the libfuzzer static library. A Job to build this
229227/// is indicated by setting `queued_jobs.fuzzer_lib` and resolved before
230228/// calling linker.flush().
......@@ -287,8 +285,6 @@ emit_llvm_bc: ?[]const u8,
287285emit_docs: ?[]const u8,
288286
289287const QueuedJobs = struct {
290 /// hack for stage2_x86_64 + coff
291 compiler_rt_dyn_lib: bool = false,
292288 compiler_rt_lib: bool = false,
293289 compiler_rt_obj: bool = false,
294290 ubsan_rt_lib: bool = false,
......@@ -1781,7 +1777,7 @@ fn addModuleTableToCacheHash(
17811777 }
17821778}
17831779
1784const RtStrat = enum { none, lib, obj, zcu, dyn_lib };
1780const RtStrat = enum { none, lib, obj, zcu };
17851781
17861782pub const CreateDiagnostic = union(enum) {
17871783 export_table_import_table_conflict,
......@@ -1902,12 +1898,6 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
19021898 };
19031899 if (have_zcu and (!need_llvm or use_llvm)) {
19041900 if (output_mode == .Obj) break :s .zcu;
1905 switch (target_util.zigBackend(target, use_llvm)) {
1906 else => {},
1907 .stage2_aarch64, .stage2_x86_64 => if (target.ofmt == .coff) {
1908 break :s if (is_exe_or_dyn_lib and build_options.have_llvm) .dyn_lib else .zcu;
1909 },
1910 }
19111901 }
19121902 if (need_llvm and !build_options.have_llvm) break :s .none; // impossible to build without llvm
19131903 if (is_exe_or_dyn_lib) break :s .lib;
......@@ -2628,11 +2618,6 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
26282618 log.debug("queuing a job to build compiler_rt_obj", .{});
26292619 comp.queued_jobs.compiler_rt_obj = true;
26302620 },
2631 .dyn_lib => {
2632 // hack for stage2_x86_64 + coff
2633 log.debug("queuing a job to build compiler_rt_dyn_lib", .{});
2634 comp.queued_jobs.compiler_rt_dyn_lib = true;
2635 },
26362621 }
26372622
26382623 switch (comp.ubsan_rt_strat) {
......@@ -2645,7 +2630,6 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
26452630 log.debug("queuing a job to build ubsan_rt_obj", .{});
26462631 comp.queued_jobs.ubsan_rt_obj = true;
26472632 },
2648 .dyn_lib => unreachable, // hack for compiler_rt only
26492633 }
26502634
26512635 switch (comp.zigc_strat) {
......@@ -2654,7 +2638,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic,
26542638 log.debug("queuing a job to build libzigc", .{});
26552639 comp.queued_jobs.zigc_lib = true;
26562640 },
2657 .obj, .dyn_lib => unreachable, // only available as a static library or inside an existing ZCU
2641 .obj => unreachable, // only available as a static library or inside an existing ZCU
26582642 }
26592643
26602644 if (is_exe_or_dyn_lib and comp.config.any_fuzz) {
......@@ -2713,7 +2697,6 @@ pub fn destroy(comp: *Compilation) void {
27132697 if (comp.zigc_static_lib) |*crt_file| crt_file.deinit(gpa, io);
27142698 if (comp.compiler_rt_lib) |*crt_file| crt_file.deinit(gpa, io);
27152699 if (comp.compiler_rt_obj) |*crt_file| crt_file.deinit(gpa, io);
2716 if (comp.compiler_rt_dyn_lib) |*crt_file| crt_file.deinit(gpa, io);
27172700 if (comp.fuzzer_lib) |*crt_file| crt_file.deinit(gpa, io);
27182701
27192702 if (comp.glibc_so_files) |*glibc_file| {
......@@ -4492,22 +4475,16 @@ fn performAllTheWork(
44924475
44934476 comp.link_queue.finishZcuQueue(comp);
44944477
4495 // This has to happen after the main semantic analysis loop because it is possible for Sema to
4478 // Main thread work is all done, now just wait for all async work.
4479 try misc_group.await(io);
4480
4481 // This has to happen again after the main semantic analysis loop because it is possible for Sema to
44964482 // call `addLinkLib` and hence add more items to `comp.windows_libs`.
4497 for (comp.windows_libs.keys()[comp.windows_libs_num_done..]) |link_lib| {
4498 mingw.buildImportLib(comp, link_lib) catch |err| {
4499 // TODO Surface more error details.
4500 comp.lockAndSetMiscFailure(
4501 .windows_import_lib,
4502 "unable to generate DLL import .lib file for {s}: {t}",
4503 .{ link_lib, err },
4504 );
4505 };
4506 }
4483 for (comp.windows_libs.keys()[comp.windows_libs_num_done..]) |lib_name|
4484 misc_group.async(io, buildMingwImportLib, .{ comp, lib_name, false, main_progress_node });
45074485 comp.windows_libs_num_done = @intCast(comp.windows_libs.count());
4508
4509 // Main thread work is all done, now just wait for all async work.
45104486 try misc_group.await(io);
4487
45114488 comp.link_queue.wait(io);
45124489}
45134490
......@@ -4566,24 +4543,6 @@ fn dispatchPrelinkWork(comp: *Compilation, main_progress_node: std.Progress.Node
45664543 });
45674544 }
45684545
4569 // hack for stage2_x86_64 + coff
4570 if (comp.queued_jobs.compiler_rt_dyn_lib and comp.compiler_rt_dyn_lib == null) {
4571 prelink_group.async(io, buildRt, .{
4572 comp,
4573 "compiler_rt.zig",
4574 "compiler_rt",
4575 .Lib,
4576 .dynamic,
4577 .compiler_rt,
4578 main_progress_node,
4579 RtOptions{
4580 .checks_valgrind = true,
4581 .allow_lto = false,
4582 },
4583 &comp.compiler_rt_dyn_lib,
4584 });
4585 }
4586
45874546 if (comp.queued_jobs.fuzzer_lib and comp.fuzzer_lib == null) {
45884547 prelink_group.async(io, buildRt, .{
45894548 comp,
......@@ -4727,6 +4686,16 @@ fn dispatchPrelinkWork(comp: *Compilation, main_progress_node: std.Progress.Node
47274686 });
47284687 }
47294688
4689 while (comp.windows_libs_num_done < comp.windows_libs.count()) {
4690 prelink_group.async(io, buildMingwImportLib, .{
4691 comp,
4692 comp.windows_libs.keys()[comp.windows_libs_num_done],
4693 true,
4694 main_progress_node,
4695 });
4696 comp.windows_libs_num_done += 1;
4697 }
4698
47304699 prelink_group.await(io) catch |err| switch (err) {
47314700 error.Canceled => unreachable, // see swapCancelProtection above
47324701 };
......@@ -5412,6 +5381,39 @@ fn buildMingwCrtFile(comp: *Compilation, crt_file: mingw.CrtFile, prog_node: std
54125381 }
54135382}
54145383
5384fn buildMingwImportLib(comp: *Compilation, lib_name: []const u8, is_prelink: bool, prog_node: std.Progress.Node) void {
5385 const crt_file_path = mingw.buildImportLib(comp, lib_name, prog_node) catch |err| switch (err) {
5386 // TODO: This isn't actually true for self-hosted
5387 // In the non-prelink case we will end up putting foo.lib onto the linker line and letting the linker
5388 // use its library paths to look for libraries and report any problems.
5389 error.DefNotFound => return if (is_prelink) {
5390 comp.lockAndSetMiscFailure(
5391 .windows_import_lib,
5392 "definition not found for required mingw DLL import .lib {s}",
5393 .{lib_name},
5394 );
5395 },
5396 // TODO Surface more error details.
5397 else => |e| return comp.lockAndSetMiscFailure(
5398 .windows_import_lib,
5399 "unable to generate mingw DLL import .lib file for {s}: {t}",
5400 .{ lib_name, e },
5401 ),
5402 };
5403
5404 if (is_prelink)
5405 comp.queuePrelinkTasks(&.{.{
5406 .load_archive = .{
5407 .path = crt_file_path,
5408 .must_link = false,
5409 },
5410 }}) catch |err| comp.lockAndSetMiscFailure(
5411 .windows_import_lib,
5412 "unable to queue prelink task for mingw import lib {f}: {t}",
5413 .{ crt_file_path, err },
5414 );
5415}
5416
54155417fn buildWasiLibcCrtFile(comp: *Compilation, crt_file: wasi_libc.CrtFile, prog_node: std.Progress.Node) void {
54165418 if (wasi_libc.buildCrtFile(comp, crt_file, prog_node)) |_| {
54175419 comp.queued_jobs.wasi_libc_crt_file[@intFromEnum(crt_file)] = false;
src/codegen/x86_64/Emit.zig+37-18
......@@ -113,6 +113,7 @@ pub fn emitMir(emit: *Emit) Error!void {
113113 .default => true,
114114 .hidden, .protected => false,
115115 },
116 .is_dll_import = @"extern".is_dll_import,
116117 .force_pcrel_direct = switch (@"extern".relocation) {
117118 .any => false,
118119 .pcrel => true,
......@@ -154,20 +155,13 @@ pub fn emitMir(emit: *Emit) Error!void {
154155 @enumFromInt(try elf_file.getGlobalSymbol(extern_func.toSlice(&emit.lower.mir).?, null))
155156 else if (emit.bin_file.cast(.elf2)) |elf| try elf.externSymbol(.{
156157 .name = extern_func.toSlice(&emit.lower.mir).?,
157 .lib_name = switch (comp.compiler_rt_strat) {
158 .none, .lib, .obj, .zcu => null,
159 .dyn_lib => "compiler_rt",
160 },
158 .lib_name = null,
161159 .type = .FUNC,
162160 }) else if (emit.bin_file.cast(.macho)) |macho_file|
163161 @enumFromInt(try macho_file.getGlobalSymbol(extern_func.toSlice(&emit.lower.mir).?, null))
164 else if (emit.bin_file.cast(.coff2)) |coff| @enumFromInt(@intFromEnum(try coff.globalSymbol(
165 extern_func.toSlice(&emit.lower.mir).?,
166 switch (comp.compiler_rt_strat) {
167 .none, .lib, .obj, .zcu => null,
168 .dyn_lib => "compiler_rt",
169 },
170 ))) else return emit.fail("external symbol unimplemented for {s}", .{@tagName(emit.bin_file.tag)}),
162 else if (emit.bin_file.cast(.coff2)) |coff| @enumFromInt(@intFromEnum(try coff.globalSymbol(.{
163 .name = extern_func.toSlice(&emit.lower.mir).?,
164 }))) else return emit.fail("external symbol unimplemented for {s}", .{@tagName(emit.bin_file.tag)}),
171165 .is_extern = true,
172166 } },
173167 },
......@@ -179,7 +173,13 @@ pub fn emitMir(emit: *Emit) Error!void {
179173 switch (lowered_inst.encoding.mnemonic) {
180174 .call => {
181175 reloc.target = .{ .branch = target };
182 try emit.encodeInst(lowered_inst, reloc_info);
176 if (target.is_dll_import and emit.bin_file.cast(.coff2) != null) {
177 try emit.encodeInst(try .new(.none, .call, &.{
178 .{ .mem = .initRip(.ptr, 0) },
179 }, emit.lower.target), reloc_info);
180 } else {
181 try emit.encodeInst(lowered_inst, reloc_info);
182 }
183183 continue :lowered_inst;
184184 },
185185 else => {},
......@@ -255,7 +255,25 @@ pub fn emitMir(emit: *Emit) Error!void {
255255 else => unreachable,
256256 }
257257 } else if (emit.bin_file.cast(.coff2)) |_| {
258 switch (lowered_inst.encoding.mnemonic) {
258 if (target.is_dll_import) switch (lowered_inst.encoding.mnemonic) {
259 .lea => try emit.encodeInst(try .new(.none, .mov, &.{
260 lowered_inst.ops[0],
261 .{ .mem = .initRip(.ptr, 0) },
262 }, emit.lower.target), reloc_info),
263 .mov => {
264 try emit.encodeInst(try .new(.none, .mov, &.{
265 lowered_inst.ops[0],
266 .{ .mem = .initRip(.ptr, 0) },
267 }, emit.lower.target), reloc_info);
268 try emit.encodeInst(try .new(.none, .mov, &.{
269 lowered_inst.ops[0],
270 .{ .mem = .initSib(lowered_inst.ops[reloc.op_index].mem.sib.ptr_size, .{ .base = .{
271 .reg = lowered_inst.ops[0].reg.to64(),
272 } }) },
273 }, emit.lower.target), &.{});
274 },
275 else => unreachable,
276 } else switch (lowered_inst.encoding.mnemonic) {
259277 .lea => try emit.encodeInst(try .new(.none, .lea, &.{
260278 lowered_inst.ops[0],
261279 .{ .mem = .initRip(.none, 0) },
......@@ -374,7 +392,7 @@ pub fn emitMir(emit: *Emit) Error!void {
374392 .op_index = 1,
375393 .target = .{ .symbol = .{
376394 .symbol = @enumFromInt(@intFromEnum(
377 try coff.globalSymbol("__tls_index", null),
395 try coff.globalSymbol(.{ .name = "__tls_index" }),
378396 )),
379397 .is_extern = false,
380398 } },
......@@ -409,7 +427,7 @@ pub fn emitMir(emit: *Emit) Error!void {
409427 .op_index = 1,
410428 .target = .{ .symbol = .{
411429 .symbol = @enumFromInt(@intFromEnum(
412 try coff.globalSymbol("_tls_index", null),
430 try coff.globalSymbol(.{ .name = "_tls_index" }),
413431 )),
414432 .is_extern = false,
415433 } },
......@@ -725,6 +743,7 @@ const RelocInfo = struct {
725743 const Symbol = struct {
726744 symbol: link.File.SymbolId,
727745 is_extern: bool,
746 is_dll_import: bool = false,
728747 force_pcrel_direct: bool = false,
729748 };
730749 };
......@@ -816,7 +835,7 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI
816835 @enumFromInt(@intFromEnum(emit.atom_id)),
817836 end_offset - 4,
818837 @enumFromInt(@intFromEnum(target.symbol)),
819 reloc.off,
838 .{ .known = reloc.off },
820839 .{ .AMD64 = .REL32 },
821840 ) else unreachable,
822841 .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
854873 @enumFromInt(@intFromEnum(emit.atom_id)),
855874 end_offset - 4,
856875 @enumFromInt(@intFromEnum(target.symbol)),
857 reloc.off,
876 .{ .known = reloc.off },
858877 .{ .AMD64 = .REL32 },
859878 ) else return emit.fail("TODO implement {s} reloc for {s}", .{
860879 @tagName(reloc.target), @tagName(emit.bin_file.tag),
......@@ -912,7 +931,7 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI
912931 @enumFromInt(@intFromEnum(emit.atom_id)),
913932 end_offset - 4,
914933 @enumFromInt(@intFromEnum(target.symbol)),
915 reloc.off,
934 .{ .known = reloc.off },
916935 .{ .AMD64 = .SECREL },
917936 ) else return emit.fail("TODO implement {s} reloc for {s}", .{
918937 @tagName(reloc.target), @tagName(emit.bin_file.tag),
src/crash_report.zig+29
......@@ -84,6 +84,25 @@ pub const CodegenFunc = if (enabled) struct {
8484 pub fn stop(_: InternPool.Index) void {}
8585};
8686
87pub const LinkerOp = if (enabled) struct {
88 lf: *link.File,
89 tid: Zcu.PerThread.Id,
90 threadlocal var current: ?LinkerOp = null;
91 pub fn start(lf: *link.File, tid: Zcu.PerThread.Id) void {
92 std.debug.assert(current == null);
93 current = .{ .lf = lf, .tid = tid };
94 }
95 pub fn stop(lf: *link.File, tid: Zcu.PerThread.Id) void {
96 std.debug.assert(current.?.lf == lf and current.?.tid == tid);
97 current = null;
98 }
99} else struct {
100 const current: ?noreturn = null;
101 // Dummy implementation
102 pub fn start(_: *link.File, _: Zcu.PerThread.Id) void {}
103 pub fn stop(_: *link.File, _: Zcu.PerThread.Id) void {}
104};
105
87106fn dumpCrashContext() Io.Writer.Error!void {
88107 const S = struct {
89108 /// 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 {
111130 try w.print("Generating function '{f}'\n\n", .{func_fqn.fmt(&cg.zcu.intern_pool)});
112131 } else if (AnalyzeBody.current) |anal| {
113132 try dumpCrashContextSema(anal, w, &S.crash_heap);
133 } else if (LinkerOp.current) |linker_op| {
134 try w.writeAll("Linker snapshot:\n");
135 switch (try linker_op.lf.dump(w, linker_op.tid)) {
136 .unimplemented => try w.writeAll("(backend does not support link snapshots)"),
137 .needs_extensions => try w.writeAll("(build with -Ddebug-extensions to dump linker state)"),
138 .disabled => try w.writeAll("(run with --debug-link-snapshot to dump linker state)"),
139 .enabled => {},
140 }
141 try w.writeAll("\n\n");
114142 } else {
115143 try w.writeAll("(no context)\n\n");
116144 }
......@@ -185,6 +213,7 @@ const Zir = std.zig.Zir;
185213
186214const Sema = @import("Sema.zig");
187215const Zcu = @import("Zcu.zig");
216const link = @import("link.zig");
188217const InternPool = @import("InternPool.zig");
189218const dev = @import("dev.zig");
190219const print_zir = @import("print_zir.zig");
src/libs/mingw.zig+23-16
......@@ -207,9 +207,14 @@ fn addCrtCcArgs(
207207 });
208208}
209209
210pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
210pub fn buildImportLib(comp: *Compilation, lib_name: []const u8, prog_node: std.Progress.Node) !Cache.Path {
211211 dev.check(.build_import_lib);
212212
213 log.debug("buildImportLib({s})", .{lib_name});
214
215 const sub_node = prog_node.start(lib_name, 0);
216 defer sub_node.end();
217
213218 const gpa = comp.gpa;
214219 const io = comp.io;
215220
......@@ -218,12 +223,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
218223 const arena = arena_allocator.allocator();
219224
220225 const def_file_path = findDef(arena, io, comp.getTarget(), comp.dirs.zig_lib, lib_name) catch |err| switch (err) {
221 error.FileNotFound => {
222 log.debug("no {s}.def file available to make a DLL import {s}.lib", .{ lib_name, lib_name });
223 // In this case we will end up putting foo.lib onto the linker line and letting the linker
224 // use its library paths to look for libraries and report any problems.
225 return;
226 },
226 error.FileNotFound => return error.DefNotFound,
227227 else => |e| return e,
228228 };
229229 // Only .def.in files need preprocessing
......@@ -263,14 +263,16 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
263263 comp.mutex.lockUncancelable(io);
264264 defer comp.mutex.unlock(io);
265265 try comp.crt_files.ensureUnusedCapacity(gpa, 1);
266
267 const crt_file_path: Cache.Path = .{
268 .root_dir = comp.dirs.global_cache,
269 .sub_path = sub_path,
270 };
266271 comp.crt_files.putAssumeCapacityNoClobber(final_lib_basename, .{
267 .full_object_path = .{
268 .root_dir = comp.dirs.global_cache,
269 .sub_path = sub_path,
270 },
272 .full_object_path = crt_file_path,
271273 .lock = man.toOwnedLock(),
272274 });
273 return;
275 return crt_file_path;
274276 }
275277
276278 const digest = man.final();
......@@ -294,6 +296,9 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
294296 }
295297
296298 const members = members: {
299 const members_node = sub_node.start("Members", 0);
300 defer members_node.end();
301
297302 const input = switch (def_needs_preprocessing) {
298303 true => pp: {
299304 var aw: Io.Writer.Allocating = .init(gpa);
......@@ -357,13 +362,15 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
357362
358363 comp.mutex.lockUncancelable(io);
359364 defer comp.mutex.unlock(io);
365 const crt_file_path: Cache.Path = .{
366 .root_dir = comp.dirs.global_cache,
367 .sub_path = lib_final_path,
368 };
360369 try comp.crt_files.putNoClobber(gpa, final_lib_basename, .{
361 .full_object_path = .{
362 .root_dir = comp.dirs.global_cache,
363 .sub_path = lib_final_path,
364 },
370 .full_object_path = crt_file_path,
365371 .lock = man.toOwnedLock(),
366372 });
373 return crt_file_path;
367374}
368375
369376pub fn libExists(
src/libs/mingw/implib.zig+1-1
......@@ -1012,7 +1012,7 @@ fn getShortImport(
10121012fn writeSymbol(writer: *std.Io.Writer, symbol: std.coff.Symbol) !void {
10131013 try writer.writeAll(&symbol.name);
10141014 try writer.writeInt(u32, symbol.value, .little);
1015 try writer.writeInt(u16, @intFromEnum(symbol.section_number), .little);
1015 try writer.writeInt(i16, @intFromEnum(symbol.section_number), .little);
10161016 try writer.writeInt(u8, @intFromEnum(symbol.type.base_type), .little);
10171017 try writer.writeInt(u8, @intFromEnum(symbol.type.complex_type), .little);
10181018 try writer.writeInt(u8, @intFromEnum(symbol.storage_class), .little);
src/link.zig+91-3
......@@ -25,6 +25,7 @@ const Package = @import("Package.zig");
2525const dev = @import("dev.zig");
2626const target_util = @import("target.zig");
2727const codegen = @import("codegen.zig");
28const crash_report = @import("crash_report.zig");
2829
2930pub const aarch64 = @import("link/aarch64.zig");
3031pub const LdScript = @import("link/LdScript.zig");
......@@ -481,7 +482,7 @@ pub const File = struct {
481482 rpath_list: []const []const u8,
482483
483484 /// Zig compiler development linker flags.
484 /// Enable dumping of linker's state as JSON.
485 /// Enable dumping of linker's state.
485486 enable_link_snapshots: bool,
486487
487488 /// Darwin-specific linker flags:
......@@ -790,6 +791,7 @@ pub const File = struct {
790791 assert(base.comp.zcu.?.llvm_object == null);
791792 const nav = pt.zcu.intern_pool.getNav(nav_index);
792793 assert(nav.resolved.?.value != .none);
794
793795 switch (base.tag) {
794796 .lld => unreachable,
795797 .plan9 => unreachable,
......@@ -924,6 +926,9 @@ pub const File = struct {
924926 /// Commit pending changes and write headers. Takes into account final output mode.
925927 /// `arena` has the lifetime of the call to `Compilation.update`.
926928 pub fn flush(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) Error!void {
929 crash_report.LinkerOp.start(base, tid);
930 defer crash_report.LinkerOp.stop(base, tid);
931
927932 const comp = base.comp;
928933 const io = comp.io;
929934 if (comp.clang_preprocessor_mode == .yes or comp.clang_preprocessor_mode == .pch) {
......@@ -975,6 +980,10 @@ pub const File = struct {
975980 export_indices: []const Zcu.Export.Index,
976981 ) Error!void {
977982 assert(base.comp.zcu.?.llvm_object == null);
983
984 crash_report.LinkerOp.start(base, pt.tid);
985 defer crash_report.LinkerOp.stop(base, pt.tid);
986
978987 switch (base.tag) {
979988 .lld => unreachable,
980989 .plan9 => unreachable,
......@@ -1006,6 +1015,7 @@ pub const File = struct {
10061015 /// Never called when LLVM is codegenning the ZCU.
10071016 pub fn getNavVAddr(base: *File, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, reloc_info: RelocInfo) Error!u64 {
10081017 assert(base.comp.zcu.?.llvm_object == null);
1018
10091019 switch (base.tag) {
10101020 .lld => unreachable,
10111021 .c => unreachable,
......@@ -1027,6 +1037,7 @@ pub const File = struct {
10271037 decl_align: InternPool.Alignment,
10281038 ) Error!SymbolId {
10291039 assert(base.comp.zcu.?.llvm_object == null);
1040
10301041 switch (base.tag) {
10311042 .lld => unreachable,
10321043 .c => unreachable,
......@@ -1043,6 +1054,7 @@ pub const File = struct {
10431054 /// Never called when LLVM is codegenning the ZCU.
10441055 pub fn getUavVAddr(base: *File, decl_val: InternPool.Index, reloc_info: RelocInfo) Error!u64 {
10451056 assert(base.comp.zcu.?.llvm_object == null);
1057
10461058 switch (base.tag) {
10471059 .lld => unreachable,
10481060 .c => unreachable,
......@@ -1063,6 +1075,7 @@ pub const File = struct {
10631075 name: InternPool.NullTerminatedString,
10641076 ) void {
10651077 assert(base.comp.zcu.?.llvm_object == null);
1078
10661079 switch (base.tag) {
10671080 .lld => unreachable,
10681081 .plan9 => unreachable,
......@@ -1077,6 +1090,31 @@ pub const File = struct {
10771090 }
10781091 }
10791092
1093 pub const DumpResult = enum {
1094 unimplemented,
1095 needs_extensions,
1096 disabled,
1097 enabled,
1098 };
1099
1100 pub fn dump(base: *File, w: *Io.Writer, tid: Zcu.PerThread.Id) !DumpResult {
1101 if (!build_options.enable_debug_extensions) return .not_built;
1102 switch (base.tag) {
1103 .elf,
1104 .macho,
1105 .c,
1106 .wasm,
1107 .spirv,
1108 .plan9,
1109 .lld,
1110 => return .unimplemented,
1111 inline else => |tag| {
1112 dev.check(tag.devFeature());
1113 return @as(*tag.Type(), @fieldParentPtr("base", base)).dump(w, tid);
1114 },
1115 }
1116 }
1117
10801118 /// Opens a path as an object file and parses it into the linker.
10811119 fn openLoadObject(base: *File, path: Path) anyerror!void {
10821120 if (base.tag == .lld) return;
......@@ -1178,8 +1216,9 @@ pub const File = struct {
11781216 pub fn loadInput(base: *File, input: Input) anyerror!void {
11791217 if (base.tag == .lld) return;
11801218 assert(!base.post_prelink);
1219
11811220 switch (base.tag) {
1182 inline .elf, .elf2, .wasm, .spirv => |tag| {
1221 inline .coff2, .elf, .elf2, .wasm, .spirv => |tag| {
11831222 dev.check(tag.devFeature());
11841223 return @as(*tag.Type(), @fieldParentPtr("base", base)).loadInput(input);
11851224 },
......@@ -1441,7 +1480,8 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {
14411480
14421481 const target = &comp.root_mod.resolved_target.result;
14431482 const flags = target_util.libcFullLinkFlags(target);
1444 const crt_dir = comp.libc_installation.?.crt_dir.?;
1483 const libc_installation = comp.libc_installation.?;
1484 const crt_dir = libc_installation.crt_dir.?;
14451485 const sep = std.fs.path.sep_str;
14461486 for (flags) |flag| {
14471487 assert(mem.startsWith(u8, flag, "-l"));
......@@ -1493,6 +1533,54 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {
14931533 },
14941534 }
14951535 }
1536
1537 if (target.os.tag == .windows and target.abi == .msvc) {
1538 const inputs: []const struct {
1539 dir: enum { crt, msvc_lib, kernel32_lib },
1540 name: []const u8,
1541 } = switch (comp.config.link_mode) {
1542 .dynamic => &.{
1543 .{ .dir = .msvc_lib, .name = "msvcrt.lib" },
1544 .{ .dir = .msvc_lib, .name = "vcruntime.lib" },
1545 .{ .dir = .msvc_lib, .name = "legacy_stdio_definitions.lib" },
1546 .{ .dir = .crt, .name = "ucrt.lib" },
1547 .{ .dir = .kernel32_lib, .name = "kernel32.lib" },
1548 .{ .dir = .kernel32_lib, .name = "ntdll.lib" },
1549 },
1550 .static => &.{
1551 .{ .dir = .msvc_lib, .name = "libcmt.lib" },
1552 .{ .dir = .msvc_lib, .name = "libvcruntime.lib" },
1553 .{ .dir = .msvc_lib, .name = "legacy_stdio_definitions.lib" },
1554 .{ .dir = .crt, .name = "libucrt.lib" },
1555 .{ .dir = .kernel32_lib, .name = "kernel32.lib" },
1556 .{ .dir = .kernel32_lib, .name = "ntdll.lib" },
1557 },
1558 };
1559
1560 for (inputs) |lib| {
1561 const path = Path.initCwd(
1562 std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}", .{
1563 switch (lib.dir) {
1564 .crt => crt_dir,
1565 .msvc_lib => libc_installation.msvc_lib_dir.?,
1566 .kernel32_lib => libc_installation.kernel32_lib_dir.?,
1567 },
1568 lib.name,
1569 }) catch return diags.setAllocFailure(),
1570 );
1571 if (std.mem.endsWith(u8, lib.name, "lib")) {
1572 base.openLoadArchive(path, false) catch |err| switch (err) {
1573 error.LinkFailure => return, // error reported via diags
1574 else => |e| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}),
1575 };
1576 } else {
1577 base.openLoadObject(path) catch |err| switch (err) {
1578 error.LinkFailure => return, // error reported via diags
1579 else => |e| diags.addParseError(path, "failed to parse object: {s}", .{@errorName(e)}),
1580 };
1581 }
1582 }
1583 }
14961584 },
14971585 .load_object => |path| {
14981586 const prog_node = comp.link_prog_node.start("Parse Object", 0);
src/link/Coff.zig+5916-583
......@@ -17,11 +17,40 @@ const target_util = @import("../target.zig");
1717const Type = @import("../Type.zig");
1818const Value = @import("../Value.zig");
1919const Zcu = @import("../Zcu.zig");
20const ModuleDefinition = @import("../libs/mingw/def.zig").ModuleDefinition;
21const implib = @import("../libs/mingw/implib.zig");
22const Path = std.Build.Cache.Path;
2023
2124base: link.File,
25options: link.File.OpenOptions,
2226mf: MappedFile,
2327nodes: std.MultiArrayList(Node),
28members: std.ArrayList(Member),
29pending_members: std.array_hash_map.Auto(Member.Index, void),
30lib_string_table: std.ArrayList(String),
31lib_string_len: u32,
32long_names_table: LongNamesTable,
2433import_table: ImportTable,
34export_table: ExportTable,
35symbol_table: SymbolTable,
36inputs: std.array_hash_map.Custom(std.Build.Cache.Path, void, std.Build.Cache.Path.TableAdapter, false),
37input_archives: std.ArrayList(InputArchive),
38input_archive_members: std.ArrayList(InputArchive.Member),
39input_archive_symbols: std.ArrayList(InputArchive.Member.Symbol),
40input_archive_symbol_indices: std.array_hash_map.Auto(String, InputArchive.SearchList),
41pending_input: ?InputArchive.Member.Index,
42pending_default_libs: std.ArrayList(struct {
43 path: []const u8,
44 ioi: InputObject.Index,
45}),
46alternate_names: std.array_hash_map.Auto(String, String),
47input_objects: std.ArrayList(InputObject),
48input_symbols: std.ArrayList(struct { si: Symbol.Index, name: String }),
49input_sections: std.ArrayList(Node.InputSection),
50input_section_pending_index: u32,
51inputs_complete: bool,
52exports_complete: bool,
53pending_special_symbol: SpecialSymbol,
2554strings: std.HashMapUnmanaged(
2655 u32,
2756 void,
......@@ -29,11 +58,13 @@ strings: std.HashMapUnmanaged(
2958 std.hash_map.default_max_load_percentage,
3059),
3160string_bytes: std.ArrayList(u8),
32image_section_table: std.ArrayList(Symbol.Index),
61section_table: std.array_hash_map.Auto(String, Section),
3362pseudo_section_table: std.array_hash_map.Auto(String, Symbol.Index),
3463object_section_table: std.array_hash_map.Auto(String, Symbol.Index),
35symbol_table: std.ArrayList(Symbol),
36globals: std.array_hash_map.Auto(GlobalName, Symbol.Index),
64section_merges: std.array_hash_map.Auto(String, String),
65section_merge_pending_index: u32,
66symbols: std.ArrayList(Symbol),
67globals: std.array_hash_map.Auto(String, Global),
3768global_pending_index: u32,
3869navs: std.array_hash_map.Auto(InternPool.Nav.Index, Symbol.Index),
3970uavs: std.array_hash_map.Auto(InternPool.Index, Symbol.Index),
......@@ -45,8 +76,13 @@ pending_uavs: std.array_hash_map.Auto(Node.UavMapIndex, struct {
4576 alignment: InternPool.Alignment,
4677}),
4778relocs: std.ArrayList(Reloc),
79first_free_reloc: Reloc.Index,
80last_free_reloc: Reloc.Index,
4881const_prog_node: std.Progress.Node,
4982synth_prog_node: std.Progress.Node,
83symbol_prog_node: std.Progress.Node,
84member_prog_node: std.Progress.Node,
85input_prog_node: std.Progress.Node,
5086
5187pub const default_file_alignment: u16 = 0x200;
5288pub const default_size_of_stack_reserve: u32 = 0x1000000;
......@@ -54,6 +90,17 @@ pub const default_size_of_stack_commit: u32 = 0x1000;
5490pub const default_size_of_heap_reserve: u32 = 0x100000;
5591pub const default_size_of_heap_commit: u32 = 0x1000;
5692
93pub const imp_prefix = "__imp_";
94
95const header_name_max_len = @typeInfo(@FieldType(std.coff.SectionHeader, "name")).array.len;
96
97const Error = link.Error || error{MappedFileIo};
98const LoadInputError = Error ||
99 Io.File.SeekError ||
100 Io.File.Reader.SizeError ||
101 Io.Reader.Error ||
102 MappedFile.Error;
103
57104/// This is the start of a Portable Executable (PE) file.
58105/// It starts with a MS-DOS header followed by a MS-DOS stub program.
59106/// This data does not change so we include it as follows in all binaries.
......@@ -134,25 +181,53 @@ pub const msdos_stub: [120]u8 = .{
134181pub const Node = union(enum) {
135182 file,
136183 header,
184 /// Images and archives only.
137185 signature,
186 /// Archives only.
187 archive_member_header: Member.Index,
188 archive_member: Member.Index,
189
138190 coff_header,
191
192 /// Image only
139193 optional_header,
140194 data_directories,
195
141196 section_table,
197
198 /// Archives and objects only
199 symbol_table,
200 string_table,
201 relocation_table: Symbol.SectionNumber,
202 relocation_table_entry: Reloc.Index,
203
142204 image_section: Symbol.Index,
143205
206 /// Images only
144207 import_directory_table,
145208 import_lookup_table: ImportTable.Index,
146209 import_address_table: ImportTable.Index,
147210 import_hint_name_table: ImportTable.Index,
148211
212 /// Images only
213 export_directory_table,
214 export_address_table,
215 export_name_pointer_table,
216 export_ordinal_table,
217 export_name_table,
218
149219 pseudo_section: PseudoSectionMapIndex,
150220 object_section: ObjectSectionMapIndex,
151 global: GlobalMapIndex,
221 input_section: InputSection.Index,
222 import_thunk: GlobalMapIndex,
152223 nav: NavMapIndex,
153224 uav: UavMapIndex,
154225 lazy_code: LazyMapRef.Index(.code),
155226 lazy_const_data: LazyMapRef.Index(.const_data),
227 builtin: Symbol.Index,
228
229 /// Takes the place of a known node index when that node is not present in the output
230 placeholder,
156231
157232 pub const PseudoSectionMapIndex = enum(u32) {
158233 _,
......@@ -179,14 +254,30 @@ pub const Node = union(enum) {
179254 };
180255
181256 pub const GlobalMapIndex = enum(u32) {
257 none,
182258 _,
183259
184 pub fn globalName(gmi: GlobalMapIndex, coff: *const Coff) GlobalName {
185 return coff.globals.keys()[@intFromEnum(gmi)];
260 pub fn wrap(i: ?u32) GlobalMapIndex {
261 return @enumFromInt((i orelse return .none) + 1);
262 }
263
264 pub fn unwrap(gmi: GlobalMapIndex) ?u32 {
265 return switch (gmi) {
266 .none => null,
267 _ => @intFromEnum(gmi) - 1,
268 };
269 }
270
271 pub fn name(gmi: GlobalMapIndex, coff: *const Coff) String {
272 return coff.globals.keys()[gmi.unwrap().?];
186273 }
187274
188275 pub fn symbol(gmi: GlobalMapIndex, coff: *const Coff) Symbol.Index {
189 return coff.globals.values()[@intFromEnum(gmi)];
276 return coff.globals.values()[gmi.unwrap().?].si;
277 }
278
279 pub fn libName(gmi: GlobalMapIndex, coff: *const Coff) String.Optional {
280 return coff.globals.values()[gmi.unwrap().?].lib_name;
190281 }
191282 };
192283
......@@ -214,6 +305,47 @@ pub const Node = union(enum) {
214305 }
215306 };
216307
308 const InputSection = struct {
309 ioi: InputObject.Index,
310 si: Symbol.Index,
311 comdat_si: Symbol.Index,
312 file_location: MappedFile.Node.FileLocation,
313 first_li: Node.InputSection.LocalIndex,
314 crc: u32,
315
316 pub const Index = enum(u32) {
317 _,
318
319 pub fn inputSection(isi: Index, coff: *const Coff) *InputSection {
320 return &coff.input_sections.items[@intFromEnum(isi)];
321 }
322
323 pub fn input(isi: Index, coff: *const Coff) InputObject.Index {
324 return coff.input_sections.items[@intFromEnum(isi)].ioi;
325 }
326
327 pub fn fileLocation(isi: Index, coff: *const Coff) MappedFile.Node.FileLocation {
328 return coff.input_sections.items[@intFromEnum(isi)].file_location;
329 }
330
331 pub fn symbol(isi: Index, coff: *const Coff) Symbol.Index {
332 return coff.input_sections.items[@intFromEnum(isi)].si;
333 }
334
335 pub fn firstSymbol(isi: Index, coff: *const Coff) LocalIndex {
336 return coff.input_sections.items[@intFromEnum(isi)].first_li;
337 }
338 };
339
340 const LocalIndex = enum(u32) {
341 _,
342
343 pub fn name(isli: LocalIndex, coff: *const Coff) String {
344 return coff.input_symbols.items[@intFromEnum(isli)].name;
345 }
346 };
347 };
348
217349 pub const LazyMapRef = struct {
218350 kind: link.File.LazySymbol.Kind,
219351 index: u32,
......@@ -253,6 +385,14 @@ pub const Node = union(enum) {
253385 file,
254386 header,
255387 signature,
388 first_linker_member_header,
389 first_linker_member,
390 second_linker_member_header,
391 second_linker_member,
392 longnames_member_header,
393 longnames_member,
394 zcu_member_header,
395 zcu_member,
256396 coff_header,
257397 optional_header,
258398 data_directories,
......@@ -270,14 +410,338 @@ pub const Node = union(enum) {
270410 }
271411};
272412
413pub const InputArchive = struct {
414 path: std.Build.Cache.Path,
415
416 const Index = enum(u32) {
417 _,
418
419 pub fn path(iai: InputArchive.Index, coff: *Coff) std.Build.Cache.Path {
420 return coff.input_archives.items[@intFromEnum(iai)].path;
421 }
422 };
423
424 pub const Member = struct {
425 iai: InputArchive.Index,
426 name: String,
427 content: union(enum) {
428 // This range includes the member header
429 object: MappedFile.Node.FileLocation,
430 import: struct {
431 symbol_name: String,
432 lib_name: String,
433 // Either ordinal or hint, depending on value of name_type
434 import_ordinal_hint: u16,
435 type: std.coff.ImportType,
436 name_type: std.coff.ImportNameType,
437 },
438 },
439 flags: packed struct {
440 // Set if an attempt was made to load this member
441 is_loaded: bool,
442 },
443
444 const Index = enum(u32) {
445 _,
446
447 pub fn member(iami: InputArchive.Member.Index, coff: *Coff) *InputArchive.Member {
448 return &coff.input_archive_members.items[@intFromEnum(iami)];
449 }
450 };
451
452 pub const Symbol = struct {
453 iami: InputArchive.Member.Index,
454 // Set to its own index to indicate its the last in the list
455 next: InputArchive.Member.Symbol.Index,
456
457 const Index = enum(u32) {
458 _,
459 };
460 };
461 };
462
463 pub const SearchList = struct {
464 first: InputArchive.Member.Symbol.Index,
465 last: InputArchive.Member.Symbol.Index,
466 };
467};
468
469pub const InputObject = struct {
470 path: std.Build.Cache.Path,
471 member_name: ?[]const u8,
472 source_name: String.Optional,
473
474 pub const Index = enum(u32) {
475 _,
476
477 pub fn path(ioi: Index, coff: *const Coff) std.Build.Cache.Path {
478 return coff.input_objects.items[@intFromEnum(ioi)].path;
479 }
480
481 pub fn memberName(ioi: Index, coff: *const Coff) ?[]const u8 {
482 return coff.input_objects.items[@intFromEnum(ioi)].member_name;
483 }
484 };
485};
486
487pub const Member = struct {
488 kind: std.coff.ArchiveMemberHeader.Kind,
489 header_ni: MappedFile.Node.Index,
490 content_ni: MappedFile.Node.Index,
491 first_linker_indices: std.array_hash_map.Auto(struct {
492 mi: Member.Index,
493 name: String,
494 }, FirstLinkerIndex),
495
496 pub const Index = enum(u16) {
497 first,
498 second,
499 longnames,
500 _,
501
502 const known_count = @typeInfo(Index).@"enum".field_names.len;
503
504 pub fn get(member_index: Member.Index, coff: *Coff) *Member {
505 return &coff.members.items[@intFromEnum(member_index)];
506 }
507 };
508
509 pub const FirstLinkerIndex = enum(u32) {
510 _,
511 };
512
513 pub fn headerPtr(member: *Member, coff: *Coff) *std.coff.ArchiveMemberHeader {
514 return @ptrCast(@alignCast(member.header_ni.slice(&coff.mf)));
515 }
516
517 /// Sets `name` as the name field of this member's header, either directly (if it's short enough),
518 /// or by creating an entry in the longnames member and storing a reference to that entry.
519 pub fn initHeader(member: *Member, coff: *Coff, name: []const u8, timestamp: u32) !void {
520 const max_name_len = @typeInfo(@FieldType(std.coff.ArchiveMemberHeader, "name")).array.len;
521 const opt_name_offset = if (name.len >= max_name_len) offset: {
522 const gpa = coff.base.comp.gpa;
523 const entries_ctx = LongNamesTable.Adapter{ .coff = coff };
524 const gop = try coff.long_names_table.entries.getOrPutAdapted(
525 gpa,
526 name,
527 entries_ctx,
528 );
529
530 if (!gop.found_existing) {
531 errdefer _ = coff.export_table.entries.pop();
532
533 _, const old_size = Node.known.longnames_member.location(&coff.mf).resolve(&coff.mf);
534 const new_size = old_size + name.len + 1;
535 assert(new_size < comptime try std.math.powi(u64, 10, max_name_len - 1));
536
537 try Node.known.longnames_member.resize(&coff.mf, gpa, new_size);
538 const name_table_slice = Node.known.longnames_member.slice(&coff.mf);
539 const name_slice = name_table_slice[@intCast(old_size)..][0 .. name.len + 1];
540 @memcpy(name_slice[0..name.len], name);
541 name_slice[name.len] = 0;
542
543 gop.value_ptr.* = .{
544 .offset = old_size,
545 .len = name.len,
546 };
547 }
548
549 break :offset gop.value_ptr.offset;
550 } else null;
551
552 const header = member.headerPtr(coff);
553 if (opt_name_offset) |name_offset| {
554 header.name[0] = '/';
555 storeHeaderDecimalStr(header.name[1..], name_offset);
556 } else {
557 @memcpy(header.name[0..name.len], name);
558 header.name[name.len] = '/';
559 const padding = max_name_len - name.len - 1;
560 @memset(header.name[max_name_len - padding ..], ' ');
561 }
562
563 storeHeaderDecimalStr(&header.date, timestamp);
564
565 // Matching the Microsoft behaviour of emitting blanks for these fields
566 header.user_id = @splat(' ');
567 header.group_id = @splat(' ');
568
569 // file_mode is actually octal, but we only ever write 0 to it
570 storeHeaderDecimalStr(&header.file_mode, 0);
571 if (!member.content_ni.hasResized(&coff.mf))
572 storeHeaderDecimalStr(
573 &header.size,
574 member.content_ni.location(&coff.mf).resolve(&coff.mf)[1],
575 );
576
577 @memcpy(&header.end_of_header, std.coff.archive_end_of_header);
578 }
579
580 pub fn storeHeaderDecimalStr(field_ptr: anytype, value: u64) void {
581 const array_info = @typeInfo(@typeInfo(@TypeOf(field_ptr)).pointer.child).array;
582 assert(array_info.child == u8);
583 assert(value < comptime try std.math.powi(u64, 10, array_info.len));
584 _ = std.fmt.printInt(field_ptr, value, 10, .lower, .{
585 .width = array_info.len,
586 .alignment = .left,
587 .fill = ' ',
588 });
589 }
590
591 pub fn loadHeaderDecimalStr(field_ptr: anytype, value: u64) void {
592 const array_info = @typeInfo(@typeInfo(@TypeOf(field_ptr)).pointer.child).array;
593 assert(array_info.child == u8);
594 assert(value < comptime try std.math.powi(u64, 10, array_info.len));
595 _ = std.fmt.printInt(field_ptr, value, 10, .lower, .{
596 .width = array_info.len,
597 .alignment = .left,
598 .fill = ' ',
599 });
600 }
601};
602
603pub const LongNamesTable = struct {
604 ni: MappedFile.Node.Index = .none,
605 entries: std.array_hash_map.Auto(void, Entry),
606
607 pub const Entry = struct {
608 offset: u64,
609 len: u64,
610 };
611
612 const Adapter = struct {
613 coff: *Coff,
614
615 pub fn eql(adapter: Adapter, lhs_key: []const u8, _: void, rhs_index: usize) bool {
616 assert(adapter.coff.isArchive());
617 const longnames_slice = Node.known.longnames_member.slice(&adapter.coff.mf);
618 const rhs = adapter.coff.long_names_table.entries.values()[rhs_index];
619 return std.mem.eql(u8, longnames_slice[@intCast(rhs.offset)..][0..@intCast(rhs.len)], lhs_key);
620 }
621
622 pub fn hash(_: Adapter, key: []const u8) u32 {
623 assert(std.mem.indexOfScalar(u8, key, 0) == null);
624 return std.array_hash_map.hashString(key);
625 }
626 };
627};
628
629pub const SymbolTable = struct {
630 ni: MappedFile.Node.Index,
631 strings_ni: MappedFile.Node.Index,
632 strings: std.array_hash_map.Auto(String, StringIndex),
633 symbols: std.array_hash_map.Auto(Symbol.Index, SymbolTable.Index),
634 pending_symbol_index: u32,
635
636 // Resizing the symbol table node has the result of accumulating padding
637 // between the last symbol in the symbol table node and the start of the
638 // string table node, due to the shifting method when resizing the parent in MappedFile.
639 // The spec requires the string table begin immediately after the last symbol,
640 // so we compact the symbol table node and move the string table back if needed.
641 pending_shrink: bool,
642
643 pub const StringIndex = enum(u32) {
644 _,
645 };
646
647 pub const SymbolName = union(enum) {
648 short: []const u8,
649 long: StringIndex,
650
651 pub fn store(name: SymbolName, coff: *const Coff, field: *[8]u8) void {
652 switch (name) {
653 .short => |s| {
654 @memcpy(field[0..s.len], s);
655 @memset(field[s.len..], 0);
656 },
657 .long => |l| {
658 @memset(field[0..4], 0);
659 std.mem.writePackedInt(u32, field[4..], 0, @intFromEnum(l), coff.targetEndian());
660 },
661 }
662 }
663 };
664
665 // Symbol.Index does not map 1:1 with SymbolTable.Index:
666 // - Not all symbols need a symbol table entry
667 // - A variable number of auxiliary entries may trail each symbol
668 pub const Index = enum(u32) {
669 none,
670 _,
671
672 pub fn wrap(i: u32) Index {
673 return @enumFromInt(i + 1);
674 }
675
676 pub fn unwrap(sti: Index) ?u32 {
677 return switch (sti) {
678 .none => null,
679 _ => @intFromEnum(sti) - 1,
680 };
681 }
682 };
683};
684
685pub const ExportTable = struct {
686 ni: MappedFile.Node.Index,
687 export_directory_table_ni: MappedFile.Node.Index,
688 export_address_table_si: Symbol.Index,
689 name_pointer_table_ni: MappedFile.Node.Index,
690 ordinal_table_ni: MappedFile.Node.Index,
691 name_table_ni: MappedFile.Node.Index,
692 entries: std.array_hash_map.Auto(void, Entry),
693 pending_sort: bool = false,
694
695 pub const Entry = struct {
696 si: Symbol.Index,
697 name_index: u32,
698 name_len: u32,
699 export_address_table_ri: Reloc.Index,
700 };
701
702 const Adapter = struct {
703 coff: *Coff,
704
705 pub fn eql(adapter: Adapter, lhs_key: []const u8, _: void, rhs_index: usize) bool {
706 const coff = adapter.coff;
707 const name_table_slice = coff.export_table.name_table_ni.slice(&coff.mf);
708 const rhs = coff.export_table.entries.values()[rhs_index];
709 return std.mem.eql(u8, name_table_slice[rhs.name_index..][0..rhs.name_len], lhs_key);
710 }
711
712 pub fn hash(_: Adapter, key: []const u8) u32 {
713 assert(std.mem.indexOfScalar(u8, key, 0) == null);
714 return std.array_hash_map.hashString(key);
715 }
716 };
717
718 pub const Ordinal = enum(u16) {
719 _,
720
721 pub fn get(export_index: ExportTable.Ordinal, coff: *Coff) *Entry {
722 return &coff.export_table.entries.values()[@intFromEnum(export_index)];
723 }
724 };
725};
726
273727pub const ImportTable = struct {
274728 ni: MappedFile.Node.Index,
275729 entries: std.array_hash_map.Auto(void, Entry),
730 iat_symbol_indices: std.array_hash_map.Auto(struct {
731 iti: ImportTable.Index,
732 name: String.Optional,
733 // If name == .none this is the ordinal, otherwise the hint
734 ordinal_hint: u16,
735 }, u32),
276736
277737 pub const Entry = struct {
278738 import_lookup_table_ni: MappedFile.Node.Index,
279739 import_address_table_si: Symbol.Index,
280740 import_hint_name_table_ni: MappedFile.Node.Index,
741 // All .iat_ptr globals that reference this table.
742 // This is separate from `iat_symbol_indices` because multiple symbols
743 // can reference to the same iat entry, after name demangling.
744 import_address_table_symbols: std.ArrayList(Symbol.Index),
281745 len: u32,
282746 hint_name_len: u32,
283747 };
......@@ -314,13 +778,32 @@ pub const String = enum(u32) {
314778 @".rdata" = 13,
315779 @".text" = 20,
316780 @".tls$" = 26,
781 @".edata" = 32,
782 @".ctors" = 39,
783 @".ctors$ZZZ" = 46,
784 @".dtors" = 57,
785 @".dtors$ZZZ" = 64,
786 @".bss" = 75,
787 @".fptable" = 80,
788 @".tls" = 89,
789 @".thunks" = 94,
317790 _,
318791
319792 pub const Optional = enum(u32) {
320793 @".data" = @intFromEnum(String.@".data"),
794 @".idata" = @intFromEnum(String.@".idata"),
321795 @".rdata" = @intFromEnum(String.@".rdata"),
322796 @".text" = @intFromEnum(String.@".text"),
323797 @".tls$" = @intFromEnum(String.@".tls$"),
798 @".edata" = @intFromEnum(String.@".edata"),
799 @".ctors" = @intFromEnum(String.@".ctors"),
800 @".ctors$ZZZ" = @intFromEnum(String.@".ctors$ZZZ"),
801 @".dtors" = @intFromEnum(String.@".dtors"),
802 @".dtors$ZZZ" = @intFromEnum(String.@".dtors$ZZZ"),
803 @".bss" = @intFromEnum(String.@".bss"),
804 @".fptable" = @intFromEnum(String.@".fptable"),
805 @".tls" = @intFromEnum(String.@".tls"),
806 @".thunks" = @intFromEnum(String.@".thunks"),
324807 none = std.math.maxInt(u32),
325808 _,
326809
......@@ -346,20 +829,176 @@ pub const String = enum(u32) {
346829 }
347830};
348831
349pub const GlobalName = struct { name: String, lib_name: String.Optional };
832pub const Section = struct {
833 si: Symbol.Index,
834 relocation_table_ni: MappedFile.Node.Index,
835
836 pub const RelocationIndex = enum(u16) {
837 none,
838 _,
839
840 pub fn wrap(i: ?u16) RelocationIndex {
841 return @enumFromInt((i orelse return .none) + 1);
842 }
843
844 pub fn unwrap(sri: RelocationIndex) ?u16 {
845 return switch (sri) {
846 .none => null,
847 _ => @intFromEnum(sri) - 1,
848 };
849 }
850
851 pub fn entry(
852 sri: RelocationIndex,
853 coff: *Coff,
854 sn: Symbol.SectionNumber,
855 ) ?*align(2) std.coff.Relocation {
856 if (sri == .none) return null;
857 const table_slice = sn.section(coff).relocation_table_ni.slice(&coff.mf);
858 return @ptrCast(@alignCast(&table_slice[@as(u32, sri.unwrap().?) * std.coff.Relocation.sizeOf()]));
859 }
860 };
861};
862
863pub const Global = struct {
864 si: Symbol.Index,
865 lib_name: String.Optional,
866};
867
868pub const WeakExternalStrat = enum(u3) {
869 none,
870 no_library,
871 library,
872 alias,
873 anti_dependency,
874
875 pub fn fromFlag(flag: std.coff.WeakExternalFlag) WeakExternalStrat {
876 return switch (flag) {
877 .SEARCH_NOLIBRARY => .no_library,
878 .SEARCH_LIBRARY => .library,
879 .SEARCH_ALIAS => .alias,
880 .ANTI_DEPENDENCY => .anti_dependency,
881 _ => unreachable,
882 };
883 }
884};
885
886const SpecialSymbol = enum {
887 entry,
888 tls,
889 none,
890};
350891
351892pub const Symbol = struct {
352893 ni: MappedFile.Node.Index,
353894 rva: u32,
354 size: u32,
895 value: std.meta.BareUnion(Symbol.Value),
896 extra: std.meta.BareUnion(Symbol.Extra),
897 flags: packed struct(u16) {
898 value_tag: ValueTag,
899 extra_tag: ExtraTag,
900 type: Symbol.Type,
901 dll_storage_class: DllStorageClass,
902 weak_external_strat: WeakExternalStrat,
903 _: u5 = 0,
904 },
355905 /// Relocations contained within this symbol
356906 loc_relocs: Reloc.Index,
357907 /// Relocations targeting this symbol
358908 target_relocs: Reloc.Index,
359909 section_number: SectionNumber,
360 unused0: u32 = 0,
361 unused1: u32 = 0,
362 unused2: u16 = 0,
910 gmi: Node.GlobalMapIndex,
911
912 pub const DllStorageClass = enum(u2) {
913 default,
914 dllimport,
915 dllexport,
916 };
917
918 pub const Type = enum(u2) {
919 unknown,
920 code,
921 data,
922 };
923
924 const ValueTag = enum(u2) {
925 none,
926 node_offset,
927 weak_alias_si,
928 weak_alias_name,
929 };
930
931 pub const Value = union(ValueTag) {
932 none,
933 /// The offset of the symbol within its node. Used with symbols that
934 /// don't create their own nodes: .input_section, .import_address_table
935 /// Images only.
936 node_offset: u32,
937 /// Images: the weak alias that should replace this symbol if it is not resolved.
938 /// Objects: he target of a weak external that hasn't been assigned an sti yet.
939 /// Globals only.
940 weak_alias_si: Symbol.Index,
941 /// For weak externals that have an alias that is also an undef
942 /// external, this is the name of the alias global that should
943 /// be generated and resolved if this symbol is not resolved.
944 /// Globals only, images only.
945 weak_alias_name: String,
946 };
947
948 const ExtraTag = enum(u2) {
949 size,
950 isli,
951 next_alias_si,
952 };
953
954 pub const Extra = union(ExtraTag) {
955 // The size of the symbol
956 size: u32,
957 /// Only valid when .ni == .input_section and .value_tag == .node_offset
958 isli: Node.InputSection.LocalIndex,
959 /// The next symbol in the list of aliases of this symbol.
960 next_alias_si: Symbol.Index,
961 };
962
963 pub fn setValue(sym: *Symbol, value: Symbol.Value) void {
964 sym.flags.value_tag = std.meta.activeTag(value);
965 sym.value = switch (sym.flags.value_tag) {
966 inline else => |t| @unionInit(
967 @FieldType(Symbol, "value"),
968 @tagName(t),
969 @field(value, @tagName(t)),
970 ),
971 };
972 }
973
974 pub fn setExtra(sym: *Symbol, extra: Symbol.Extra) void {
975 sym.flags.extra_tag = std.meta.activeTag(extra);
976 sym.extra = switch (sym.flags.extra_tag) {
977 inline else => |t| @unionInit(
978 @FieldType(Symbol, "extra"),
979 @tagName(t),
980 @field(extra, @tagName(t)),
981 ),
982 };
983 }
984
985 pub fn nodeOffset(sym: *const Symbol, coff: *Coff) u32 {
986 return switch (sym.flags.value_tag) {
987 .node_offset => offset: {
988 assert(switch (coff.getNode(sym.ni)) {
989 // Separate nodes are not created for these entries per-symbol
990 .input_section, .import_address_table => true,
991 else => false,
992 });
993 break :offset sym.value.node_offset;
994 },
995 else => 0,
996 };
997 }
998
999 pub fn size(sym: *const Symbol) u32 {
1000 return if (sym.flags.extra_tag == .size) sym.extra.size else 0;
1001 }
3631002
3641003 pub const SectionNumber = enum(i16) {
3651004 UNDEFINED = 0,
......@@ -371,8 +1010,20 @@ pub const Symbol = struct {
3711010 return @intCast(@intFromEnum(sn) - 1);
3721011 }
3731012
1013 fn hasIndex(sn: SectionNumber) bool {
1014 return @intFromEnum(sn) > 0;
1015 }
1016
3741017 pub fn symbol(sn: SectionNumber, coff: *const Coff) Symbol.Index {
375 return coff.image_section_table.items[sn.toIndex()];
1018 return sn.section(coff).si;
1019 }
1020
1021 pub fn name(sn: SectionNumber, coff: *const Coff) String {
1022 return coff.section_table.keys()[sn.toIndex()];
1023 }
1024
1025 pub fn section(sn: SectionNumber, coff: *const Coff) *Section {
1026 return &coff.section_table.values()[sn.toIndex()];
3761027 }
3771028
3781029 pub fn header(sn: SectionNumber, coff: *Coff) *std.coff.SectionHeader {
......@@ -382,6 +1033,7 @@ pub const Symbol = struct {
3821033
3831034 pub const Index = enum(u32) {
3841035 null,
1036 bss,
3851037 data,
3861038 rdata,
3871039 text,
......@@ -390,7 +1042,12 @@ pub const Symbol = struct {
3901042 const known_count = @typeInfo(Index).@"enum".field_names.len;
3911043
3921044 pub fn get(si: Symbol.Index, coff: *Coff) *Symbol {
393 return &coff.symbol_table.items[@intFromEnum(si)];
1045 return &coff.symbols.items[@intFromEnum(si)];
1046 }
1047
1048 pub fn unwrap(si: Symbol.Index) ?Symbol.Index {
1049 if (si == .null) return null;
1050 return si;
3941051 }
3951052
3961053 pub fn node(si: Symbol.Index, coff: *Coff) MappedFile.Node.Index {
......@@ -399,37 +1056,92 @@ pub const Symbol = struct {
3991056 return ni;
4001057 }
4011058
402 pub fn flushMoved(si: Symbol.Index, coff: *Coff) void {
403 const sym = si.get(coff);
404 sym.rva = coff.computeNodeRva(sym.ni);
405 si.applyLocationRelocs(coff);
406 si.applyTargetRelocs(coff);
1059 pub fn sti(si: Symbol.Index, coff: *Coff) SymbolTable.Index {
1060 assert(!coff.isImage());
1061 return coff.symbol_table.symbols.get(si) orelse .none;
1062 }
1063
1064 pub fn next(si: Symbol.Index) Symbol.Index {
1065 return @enumFromInt(@intFromEnum(si) + 1);
1066 }
1067
1068 pub fn knownString(si: Symbol.Index) String.Optional {
1069 return switch (si) {
1070 .null, _ => .none,
1071 inline else => |tag| @field(String.Optional, "." ++ @tagName(tag)),
1072 };
4071073 }
4081074
409 pub fn applyLocationRelocs(si: Symbol.Index, coff: *Coff) void {
410 for (coff.relocs.items[@intFromEnum(si.get(coff).loc_relocs)..]) |*reloc| {
411 if (reloc.loc != si) break;
412 reloc.apply(coff);
1075 pub fn flushMoved(si: Symbol.Index, coff: *Coff) !void {
1076 const sym = si.get(coff);
1077 sym.rva = coff.computeNodeRva(sym.ni) + sym.nodeOffset(coff);
1078 try si.applyLocationRelocs(coff);
1079 try si.applyTargetRelocs(coff, .none);
1080
1081 var alias_sym = sym;
1082 while (alias_sym.flags.extra_tag == .next_alias_si) {
1083 const alias_si = alias_sym.extra.next_alias_si;
1084 alias_sym = alias_si.get(coff);
1085 assert(alias_sym.ni == sym.ni);
1086 alias_sym.rva = sym.rva;
1087 try alias_si.applyTargetRelocs(coff, .none);
4131088 }
4141089 }
4151090
416 pub fn applyTargetRelocs(si: Symbol.Index, coff: *Coff) void {
417 var ri = si.get(coff).target_relocs;
1091 pub fn flushSymbolTableIndex(si: Symbol.Index, coff: *Coff) void {
1092 const sym = si.get(coff);
1093 const index = si.sti(coff).unwrap().?;
1094 var ri = sym.target_relocs;
4181095 while (ri != .none) {
4191096 const reloc = ri.get(coff);
4201097 assert(reloc.target == si);
421 reloc.apply(coff);
1098 if (reloc.sri.entry(coff, reloc.loc.get(coff).section_number)) |entry|
1099 coff.targetStore(&entry.symbol_table_index, index);
1100 ri = reloc.next;
1101 }
1102 }
1103
1104 pub fn applyLocationRelocs(si: Symbol.Index, coff: *Coff) !void {
1105 const sym = si.get(coff);
1106 switch (sym.loc_relocs) {
1107 .none => {},
1108 else => |loc_relocs| {
1109 for (coff.relocs.items[@intFromEnum(loc_relocs)..]) |*reloc| {
1110 if (reloc.loc != si) break;
1111 if (reloc.sri.entry(coff, sym.section_number)) |entry| coff.targetStore(
1112 &entry.virtual_address,
1113 @intCast(coff.computeSymbolSectionOffset(sym, .image) + reloc.offset),
1114 );
1115 try reloc.apply(coff);
1116 }
1117 },
1118 }
1119 }
1120
1121 pub fn applyTargetRelocs(si: Symbol.Index, coff: *Coff, end: Reloc.Index) !void {
1122 const sym = si.get(coff);
1123
1124 var ri = sym.target_relocs;
1125 while (ri != end) {
1126 const reloc = ri.get(coff);
1127 assert(reloc.target == si);
1128 try reloc.apply(coff);
4221129 ri = reloc.next;
4231130 }
4241131 }
4251132
4261133 pub fn deleteLocationRelocs(si: Symbol.Index, coff: *Coff) void {
4271134 const sym = si.get(coff);
428 for (coff.relocs.items[@intFromEnum(sym.loc_relocs)..]) |*reloc| {
429 if (reloc.loc != si) break;
430 reloc.delete(coff);
1135 switch (sym.loc_relocs) {
1136 .none => {},
1137 else => |loc_relocs| {
1138 for (coff.relocs.items[@intFromEnum(loc_relocs)..]) |*reloc| {
1139 if (reloc.loc != si) break;
1140 reloc.delete(coff);
1141 }
1142 sym.loc_relocs = .none;
1143 },
4311144 }
432 sym.loc_relocs = .none;
4331145 }
4341146 };
4351147
......@@ -439,14 +1151,24 @@ pub const Symbol = struct {
4391151};
4401152
4411153pub const Reloc = extern struct {
442 type: Reloc.Type,
1154 offset: u64,
1155 addend: i64,
1156 type: Reloc.Type,
1157 sri: Section.RelocationIndex,
4431158 prev: Reloc.Index,
4441159 next: Reloc.Index,
4451160 loc: Symbol.Index,
4461161 target: Symbol.Index,
447 unused: u32,
448 offset: u64,
449 addend: i64,
1162 flags: packed struct(u8) {
1163 /// Indicates the addend is not known and should be recovered from the location itself.
1164 /// COFF relocation tables don't encode the addend, only the location.
1165 recover_addend: bool,
1166 /// Set if this reloc is in the free list.
1167 /// When set, `prev` / `next` refer to other relocs in the free list.
1168 /// All other fields are undefined.
1169 free: bool,
1170 _: u6 = 0,
1171 },
4501172
4511173 pub const Type = extern union {
4521174 AMD64: std.coff.IMAGE.REL.AMD64,
......@@ -464,135 +1186,319 @@ pub const Reloc = extern struct {
4641186 none = std.math.maxInt(u32),
4651187 _,
4661188
467 pub fn get(si: Reloc.Index, coff: *Coff) *Reloc {
468 return &coff.relocs.items[@intFromEnum(si)];
1189 pub fn wrap(i: ?u32) Reloc.Index {
1190 return @enumFromInt((i orelse return .none) + 1);
1191 }
1192
1193 pub fn get(ri: Reloc.Index, coff: *Coff) *Reloc {
1194 return &coff.relocs.items[@intFromEnum(ri)];
4691195 }
4701196 };
4711197
472 pub fn apply(reloc: *const Reloc, coff: *Coff) void {
1198 pub fn apply(reloc: *Reloc, coff: *Coff) !void {
4731199 const loc_sym = reloc.loc.get(coff);
4741200 switch (loc_sym.ni) {
4751201 .none => return,
4761202 else => |ni| if (ni.hasMoved(&coff.mf)) return,
4771203 }
478 const target_sym = reloc.target.get(coff);
479 switch (target_sym.ni) {
480 .none => return,
481 else => |ni| if (ni.hasMoved(&coff.mf)) return,
482 }
1204
4831205 const loc_slice = loc_sym.ni.slice(&coff.mf)[@intCast(reloc.offset)..];
484 const target_rva = target_sym.rva +% @as(u64, @bitCast(reloc.addend));
4851206 const target_endian = coff.targetEndian();
486 switch (coff.targetLoad(&coff.headerPtr().machine)) {
487 else => |machine| @panic(@tagName(machine)),
488 .AMD64 => switch (reloc.type.AMD64) {
489 else => |kind| @panic(@tagName(kind)),
490 .ABSOLUTE => {},
491 .ADDR64 => std.mem.writeInt(
492 u64,
493 loc_slice[0..8],
494 coff.optionalHeaderField(.image_base) + target_rva,
495 target_endian,
496 ),
497 .ADDR32 => std.mem.writeInt(
498 u32,
499 loc_slice[0..4],
500 @intCast(coff.optionalHeaderField(.image_base) + target_rva),
501 target_endian,
502 ),
503 .ADDR32NB => std.mem.writeInt(
504 u32,
505 loc_slice[0..4],
506 @intCast(target_rva),
507 target_endian,
508 ),
509 .REL32 => std.mem.writeInt(
510 i32,
511 loc_slice[0..4],
512 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 4)))),
513 target_endian,
514 ),
515 .REL32_1 => std.mem.writeInt(
516 i32,
517 loc_slice[0..4],
518 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 5)))),
519 target_endian,
520 ),
521 .REL32_2 => std.mem.writeInt(
522 i32,
523 loc_slice[0..4],
524 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 6)))),
525 target_endian,
526 ),
527 .REL32_3 => std.mem.writeInt(
528 i32,
529 loc_slice[0..4],
530 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 7)))),
531 target_endian,
532 ),
533 .REL32_4 => std.mem.writeInt(
534 i32,
535 loc_slice[0..4],
536 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 8)))),
537 target_endian,
538 ),
539 .REL32_5 => std.mem.writeInt(
540 i32,
541 loc_slice[0..4],
542 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 9)))),
543 target_endian,
544 ),
545 .SECREL => std.mem.writeInt(
546 u32,
547 loc_slice[0..4],
548 coff.computeNodeSectionOffset(target_sym.ni),
549 target_endian,
550 ),
551 },
552 .I386 => switch (reloc.type.I386) {
553 else => |kind| @panic(@tagName(kind)),
554 .ABSOLUTE => {},
555 .DIR16 => std.mem.writeInt(
556 u16,
557 loc_slice[0..2],
558 @intCast(coff.optionalHeaderField(.image_base) + target_rva),
559 target_endian,
560 ),
561 .REL16 => std.mem.writeInt(
562 i16,
563 loc_slice[0..2],
564 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 2)))),
565 target_endian,
566 ),
567 .DIR32 => std.mem.writeInt(
568 u32,
569 loc_slice[0..4],
570 @intCast(coff.optionalHeaderField(.image_base) + target_rva),
571 target_endian,
572 ),
573 .DIR32NB => std.mem.writeInt(
574 u32,
575 loc_slice[0..4],
576 @intCast(target_rva),
577 target_endian,
578 ),
579 .REL32 => std.mem.writeInt(
580 i32,
581 loc_slice[0..4],
582 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 4)))),
583 target_endian,
584 ),
585 .SECREL => std.mem.writeInt(
586 u32,
587 loc_slice[0..4],
588 coff.computeNodeSectionOffset(target_sym.ni),
589 target_endian,
590 ),
591 },
1207 const target_machine = coff.targetLoad(&coff.headerPtr().machine);
1208
1209 if (!coff.isImage()) {
1210 assert(!reloc.flags.recover_addend);
1211 switch (target_machine) {
1212 else => |machine| @panic(@tagName(machine)),
1213 .AMD64 => switch (reloc.type.AMD64) {
1214 else => |kind| @panic(@tagName(kind)),
1215 .ABSOLUTE => {},
1216 .ADDR64 => std.mem.writeInt(
1217 u64,
1218 loc_slice[0..8],
1219 @intCast(reloc.addend),
1220 target_endian,
1221 ),
1222 .ADDR32,
1223 .ADDR32NB,
1224 .SECREL,
1225 => std.mem.writeInt(
1226 u32,
1227 loc_slice[0..4],
1228 @intCast(reloc.addend),
1229 target_endian,
1230 ),
1231 .REL32,
1232 .REL32_1,
1233 .REL32_2,
1234 .REL32_3,
1235 .REL32_4,
1236 .REL32_5,
1237 => std.mem.writeInt(
1238 i32,
1239 loc_slice[0..4],
1240 @intCast(reloc.addend),
1241 target_endian,
1242 ),
1243 },
1244 .I386 => switch (reloc.type.I386) {
1245 else => |kind| @panic(@tagName(kind)),
1246 .ABSOLUTE => {},
1247 .DIR16,
1248 => std.mem.writeInt(
1249 u16,
1250 loc_slice[0..2],
1251 @intCast(reloc.addend),
1252 target_endian,
1253 ),
1254 .REL16,
1255 => std.mem.writeInt(
1256 i16,
1257 loc_slice[0..2],
1258 @intCast(reloc.addend),
1259 target_endian,
1260 ),
1261 .DIR32,
1262 .DIR32NB,
1263 .SECREL,
1264 => std.mem.writeInt(
1265 u32,
1266 loc_slice[0..4],
1267 @intCast(reloc.addend),
1268 target_endian,
1269 ),
1270 .REL32,
1271 => std.mem.writeInt(
1272 i32,
1273 loc_slice[0..4],
1274 @intCast(reloc.addend),
1275 target_endian,
1276 ),
1277 },
1278 }
1279
1280 return;
1281 } else if (reloc.flags.recover_addend) {
1282 reloc.flags.recover_addend = false;
1283 reloc.addend = switch (target_machine) {
1284 else => |machine| @panic(@tagName(machine)),
1285 .AMD64 => switch (reloc.type.AMD64) {
1286 else => |kind| @panic(@tagName(kind)),
1287 .ABSOLUTE => 0,
1288 .ADDR64 => @bitCast(std.mem.readInt(
1289 u64,
1290 loc_slice[0..8],
1291 target_endian,
1292 )),
1293 .ADDR32,
1294 .ADDR32NB,
1295 .SECREL,
1296 .REL32,
1297 .REL32_1,
1298 .REL32_2,
1299 .REL32_3,
1300 .REL32_4,
1301 .REL32_5,
1302 => std.mem.readInt(
1303 i32,
1304 loc_slice[0..4],
1305 target_endian,
1306 ),
1307 },
1308 .I386 => switch (reloc.type.I386) {
1309 else => |kind| @panic(@tagName(kind)),
1310 .ABSOLUTE => 0,
1311 .DIR16,
1312 .REL16,
1313 => std.mem.readInt(
1314 i16,
1315 loc_slice[0..2],
1316 target_endian,
1317 ),
1318 .DIR32,
1319 .DIR32NB,
1320 .SECREL,
1321 .REL32,
1322 => std.mem.readInt(
1323 i32,
1324 loc_slice[0..4],
1325 target_endian,
1326 ),
1327 },
1328 };
1329 }
1330
1331 const target_sym = reloc.target.get(coff);
1332 const is_abs = switch (target_sym.ni) {
1333 .none => if (target_sym.section_number == .ABSOLUTE) true else return,
1334 else => |ni| if (ni.hasMoved(&coff.mf)) return else false,
1335 };
1336
1337 const target_rva = target_sym.rva +% @as(u64, @bitCast(reloc.addend));
1338 if (is_abs) {
1339 switch (target_machine) {
1340 else => |machine| @panic(@tagName(machine)),
1341 .AMD64 => switch (reloc.type.AMD64) {
1342 // TODO: Could wait to report these later, in reportUndefs -> reportRelocErrs,
1343 // so that this function doesn't return an err
1344 else => |kind| return coff.base.comp.link_diags.fail(
1345 "absolute symbol '{s}' targeted by invalid relocation type: {t}",
1346 .{ target_sym.gmi.name(coff).toSlice(coff), kind },
1347 ),
1348 .ABSOLUTE => {},
1349 .ADDR64 => std.mem.writeInt(
1350 u64,
1351 loc_slice[0..8],
1352 target_rva,
1353 target_endian,
1354 ),
1355 .ADDR32 => std.mem.writeInt(
1356 u32,
1357 loc_slice[0..4],
1358 @intCast(target_rva),
1359 target_endian,
1360 ),
1361 },
1362 .I386 => switch (reloc.type.I386) {
1363 else => |kind| return coff.base.comp.link_diags.fail(
1364 "absolute symbol '{s}' targeted by invalid relocation type: {t}",
1365 .{ target_sym.gmi.name(coff).toSlice(coff), kind },
1366 ),
1367 .ABSOLUTE => {},
1368 .DIR16 => std.mem.writeInt(
1369 u16,
1370 loc_slice[0..2],
1371 @intCast(target_rva),
1372 target_endian,
1373 ),
1374 .DIR32 => std.mem.writeInt(
1375 u32,
1376 loc_slice[0..4],
1377 @intCast(target_rva),
1378 target_endian,
1379 ),
1380 },
1381 }
1382 } else {
1383 switch (target_machine) {
1384 else => |machine| @panic(@tagName(machine)),
1385 .AMD64 => switch (reloc.type.AMD64) {
1386 else => |kind| @panic(@tagName(kind)),
1387 .ABSOLUTE => {},
1388 .ADDR64 => std.mem.writeInt(
1389 u64,
1390 loc_slice[0..8],
1391 coff.optionalHeaderField(.image_base) + target_rva,
1392 target_endian,
1393 ),
1394 .ADDR32 => std.mem.writeInt(
1395 u32,
1396 loc_slice[0..4],
1397 @intCast(coff.optionalHeaderField(.image_base) + target_rva),
1398 target_endian,
1399 ),
1400 .ADDR32NB => std.mem.writeInt(
1401 u32,
1402 loc_slice[0..4],
1403 @intCast(target_rva),
1404 target_endian,
1405 ),
1406 .REL32 => std.mem.writeInt(
1407 i32,
1408 loc_slice[0..4],
1409 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 4)))),
1410 target_endian,
1411 ),
1412 .REL32_1 => std.mem.writeInt(
1413 i32,
1414 loc_slice[0..4],
1415 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 5)))),
1416 target_endian,
1417 ),
1418 .REL32_2 => std.mem.writeInt(
1419 i32,
1420 loc_slice[0..4],
1421 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 6)))),
1422 target_endian,
1423 ),
1424 .REL32_3 => std.mem.writeInt(
1425 i32,
1426 loc_slice[0..4],
1427 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 7)))),
1428 target_endian,
1429 ),
1430 .REL32_4 => std.mem.writeInt(
1431 i32,
1432 loc_slice[0..4],
1433 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 8)))),
1434 target_endian,
1435 ),
1436 .REL32_5 => std.mem.writeInt(
1437 i32,
1438 loc_slice[0..4],
1439 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 9)))),
1440 target_endian,
1441 ),
1442 .SECREL => std.mem.writeInt(
1443 u32,
1444 loc_slice[0..4],
1445 @intCast(coff.computeSymbolSectionOffset(target_sym, .pseudo) + reloc.addend),
1446 target_endian,
1447 ),
1448 },
1449 .I386 => switch (reloc.type.I386) {
1450 else => |kind| @panic(@tagName(kind)),
1451 .ABSOLUTE => {},
1452 .DIR16 => std.mem.writeInt(
1453 u16,
1454 loc_slice[0..2],
1455 @intCast(coff.optionalHeaderField(.image_base) + target_rva),
1456 target_endian,
1457 ),
1458 .REL16 => std.mem.writeInt(
1459 i16,
1460 loc_slice[0..2],
1461 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 2)))),
1462 target_endian,
1463 ),
1464 .DIR32 => std.mem.writeInt(
1465 u32,
1466 loc_slice[0..4],
1467 @intCast(coff.optionalHeaderField(.image_base) + target_rva),
1468 target_endian,
1469 ),
1470 .DIR32NB => std.mem.writeInt(
1471 u32,
1472 loc_slice[0..4],
1473 @intCast(target_rva),
1474 target_endian,
1475 ),
1476 .REL32 => std.mem.writeInt(
1477 i32,
1478 loc_slice[0..4],
1479 @intCast(@as(i64, @bitCast(target_rva -% (loc_sym.rva + reloc.offset + 4)))),
1480 target_endian,
1481 ),
1482 .SECREL => std.mem.writeInt(
1483 u32,
1484 loc_slice[0..4],
1485 @intCast(coff.computeSymbolSectionOffset(target_sym, .pseudo) + reloc.addend),
1486 target_endian,
1487 ),
1488 },
1489 }
5921490 }
5931491 }
5941492
5951493 pub fn delete(reloc: *Reloc, coff: *Coff) void {
1494 if (reloc.sri != .none) {
1495 // TODO: Need to remove this from the COFF relocation table (maybe removeswap?)
1496 // TODO: If this was the last reloc causing something to be in the symbol table, we should remove
1497 // the symbol table entry (and unset sti). That will require flushSymbolTableIndex on the
1498 // swapped symbol if we exchange indices
1499 @panic("TODO implement symbol table reloc deletions");
1500 }
1501
5961502 switch (reloc.prev) {
5971503 .none => {
5981504 const target = reloc.target.get(coff);
......@@ -605,7 +1511,24 @@ pub const Reloc = extern struct {
6051511 .none => {},
6061512 else => |next| next.get(coff).prev = reloc.prev,
6071513 }
1514
6081515 reloc.* = undefined;
1516 reloc.flags = .{
1517 .recover_addend = false,
1518 .free = true,
1519 };
1520
1521 const ri: Reloc.Index = .wrap(@intCast(reloc - coff.relocs.items.ptr));
1522 if (coff.last_free_reloc == .none) {
1523 assert(coff.first_free_reloc == .none);
1524 coff.first_free_reloc = ri;
1525 coff.last_free_reloc = ri;
1526 } else {
1527 coff.last_free_reloc.get(coff).next = ri;
1528 reloc.prev = coff.last_free_reloc;
1529 reloc.next = .none;
1530 coff.last_free_reloc = ri;
1531 }
6091532 }
6101533
6111534 comptime {
......@@ -639,14 +1562,6 @@ fn create(
6391562 assert(target.ofmt == .coff);
6401563 if (target.cpu.arch.endian() != comptime targetEndian(undefined))
6411564 return error.UnsupportedCOFFArchitecture;
642 const is_image = switch (comp.config.output_mode) {
643 .Exe => true,
644 .Lib => switch (comp.config.link_mode) {
645 .static => false,
646 .dynamic => true,
647 },
648 .Obj => false,
649 };
6501565 const machine = target.toCoffMachine();
6511566 const timestamp: u32 = 0;
6521567 const major_subsystem_version = options.major_subsystem_version orelse 6;
......@@ -689,18 +1604,61 @@ fn create(
6891604 .allow_shlib_undefined = false,
6901605 .stack_size = 0,
6911606 },
1607 .options = options,
6921608 .mf = try .init(file, comp.gpa, io),
6931609 .nodes = .empty,
1610 .members = .empty,
1611 .pending_members = .empty,
1612 .lib_string_table = .empty,
1613 .lib_string_len = 0,
1614 .long_names_table = .{
1615 .entries = .empty,
1616 },
6941617 .import_table = .{
6951618 .ni = .none,
6961619 .entries = .empty,
1620 .iat_symbol_indices = .empty,
1621 },
1622 .export_table = .{
1623 .ni = .none,
1624 .export_directory_table_ni = .none,
1625 .export_address_table_si = .null,
1626 .name_pointer_table_ni = .none,
1627 .ordinal_table_ni = .none,
1628 .name_table_ni = .none,
1629 .entries = .empty,
1630 },
1631 .symbol_table = .{
1632 .ni = .none,
1633 .strings_ni = .none,
1634 .strings = .empty,
1635 .symbols = .empty,
1636 .pending_symbol_index = 0,
1637 .pending_shrink = false,
6971638 },
1639 .inputs = .empty,
1640 .input_archives = .empty,
1641 .input_archive_members = .empty,
1642 .input_archive_symbols = .empty,
1643 .input_archive_symbol_indices = .empty,
1644 .pending_input = null,
1645 .pending_default_libs = .empty,
1646 .alternate_names = .empty,
1647 .input_objects = .empty,
1648 .input_symbols = .empty,
1649 .input_sections = .empty,
1650 .input_section_pending_index = 0,
1651 .inputs_complete = false,
1652 .exports_complete = false,
1653 .pending_special_symbol = .entry,
6981654 .strings = .empty,
6991655 .string_bytes = .empty,
700 .image_section_table = .empty,
1656 .section_table = .empty,
7011657 .pseudo_section_table = .empty,
7021658 .object_section_table = .empty,
703 .symbol_table = .empty,
1659 .section_merges = .empty,
1660 .section_merge_pending_index = 0,
1661 .symbols = .empty,
7041662 .globals = .empty,
7051663 .global_pending_index = 0,
7061664 .navs = .empty,
......@@ -711,8 +1669,13 @@ fn create(
7111669 }),
7121670 .pending_uavs = .empty,
7131671 .relocs = .empty,
1672 .first_free_reloc = .none,
1673 .last_free_reloc = .none,
7141674 .const_prog_node = .none,
7151675 .synth_prog_node = .none,
1676 .symbol_prog_node = .none,
1677 .member_prog_node = .none,
1678 .input_prog_node = .none,
7161679 };
7171680 errdefer coff.deinit();
7181681
......@@ -725,14 +1688,20 @@ fn create(
7251688 }
7261689
7271690 try coff.initHeaders(
728 is_image,
7291691 machine,
7301692 timestamp,
7311693 major_subsystem_version,
7321694 minor_subsystem_version,
7331695 magic,
1696 if (options.subsystem) |s| switch (s) {
1697 .console => .WINDOWS_CUI,
1698 .windows => .WINDOWS_GUI,
1699 else => return error.UnsupportedCOFFSubsystem,
1700 } else .WINDOWS_CUI,
7341701 section_align,
1702 std.fs.path.basename(path.sub_path),
7351703 );
1704 try coff.initBuiltins();
7361705 return coff;
7371706}
7381707
......@@ -740,13 +1709,31 @@ pub fn deinit(coff: *Coff) void {
7401709 const gpa = coff.base.comp.gpa;
7411710 coff.mf.deinit(gpa);
7421711 coff.nodes.deinit(gpa);
1712 coff.pending_members.deinit(gpa);
1713 coff.lib_string_table.deinit(gpa);
1714 coff.long_names_table.entries.deinit(gpa);
7431715 coff.import_table.entries.deinit(gpa);
1716 coff.import_table.iat_symbol_indices.deinit(gpa);
1717 coff.export_table.entries.deinit(gpa);
1718 coff.symbol_table.strings.deinit(gpa);
1719 coff.symbol_table.symbols.deinit(gpa);
1720 coff.inputs.deinit(gpa);
1721 coff.input_archives.deinit(gpa);
1722 coff.input_archive_members.deinit(gpa);
1723 coff.input_archive_symbols.deinit(gpa);
1724 coff.input_archive_symbol_indices.deinit(gpa);
1725 for (coff.pending_default_libs.items) |l| gpa.free(l.path);
1726 coff.pending_default_libs.deinit(gpa);
1727 coff.alternate_names.deinit(gpa);
1728 coff.input_objects.deinit(gpa);
1729 coff.input_symbols.deinit(gpa);
1730 coff.input_sections.deinit(gpa);
7441731 coff.strings.deinit(gpa);
7451732 coff.string_bytes.deinit(gpa);
746 coff.image_section_table.deinit(gpa);
1733 coff.section_table.deinit(gpa);
7471734 coff.pseudo_section_table.deinit(gpa);
7481735 coff.object_section_table.deinit(gpa);
749 coff.symbol_table.deinit(gpa);
1736 coff.symbols.deinit(gpa);
7501737 coff.globals.deinit(gpa);
7511738 coff.navs.deinit(gpa);
7521739 coff.uavs.deinit(gpa);
......@@ -756,21 +1743,65 @@ pub fn deinit(coff: *Coff) void {
7561743 coff.* = undefined;
7571744}
7581745
1746fn isImage(coff: *const Coff) bool {
1747 const comp = coff.base.comp;
1748 return switch (comp.config.output_mode) {
1749 .Exe => true,
1750 .Lib => switch (comp.config.link_mode) {
1751 .static => false,
1752 .dynamic => true,
1753 },
1754 .Obj => false,
1755 };
1756}
1757
1758fn isArchive(coff: *const Coff) bool {
1759 const comp = coff.base.comp;
1760 return switch (comp.config.output_mode) {
1761 .Exe => false,
1762 .Lib => switch (comp.config.link_mode) {
1763 .static => true,
1764 .dynamic => false,
1765 },
1766 .Obj => false,
1767 };
1768}
1769
1770fn isExe(coff: *const Coff) bool {
1771 return coff.base.comp.config.output_mode == .Exe;
1772}
1773
1774fn isObj(coff: *const Coff) bool {
1775 return coff.base.comp.config.output_mode == .Obj;
1776}
1777
1778fn hasCoffHeader(coff: *const Coff) bool {
1779 return coff.base.comp.zcu != null or !coff.isArchive();
1780}
1781
1782fn sectionParent(coff: *Coff) MappedFile.Node.Index {
1783 assert(coff.hasCoffHeader());
1784 return if (coff.isArchive()) Node.known.zcu_member else Node.known.file;
1785}
1786
7591787fn initHeaders(
7601788 coff: *Coff,
761 is_image: bool,
7621789 machine: std.coff.IMAGE.FILE.MACHINE,
7631790 timestamp: u32,
7641791 major_subsystem_version: u16,
7651792 minor_subsystem_version: u16,
7661793 magic: std.coff.OptionalHeader.Magic,
1794 subsystem: std.coff.Subsystem,
7671795 section_align: std.mem.Alignment,
1796 file_name: []const u8,
7681797) !void {
7691798 const comp = coff.base.comp;
7701799 const gpa = comp.gpa;
7711800 const target_endian = coff.targetEndian();
7721801 const file_align: std.mem.Alignment = comptime .fromByteUnits(default_file_alignment);
773
1802 const is_image = coff.isImage();
1803 const is_archive = coff.isArchive();
1804 const target = &comp.root_mod.resolved_target.result;
7741805 const optional_header_size: u16 = if (is_image) switch (magic) {
7751806 _ => unreachable,
7761807 inline else => |ct_magic| @sizeOf(@field(std.coff.OptionalHeader, @tagName(ct_magic))),
......@@ -780,33 +1811,120 @@ fn initHeaders(
7801811 else
7811812 0;
7821813
783 const expected_nodes_len = Node.known_count + 6 +
784 @as(usize, @intFromBool(comp.config.any_non_single_threaded)) * 2;
1814 var expected_nodes_len: usize = Node.known_count;
1815 if (coff.hasCoffHeader()) {
1816 // Sections
1817 expected_nodes_len += 4;
1818
1819 if (is_image) {
1820 // Pseudo-sections and import / export table
1821 expected_nodes_len += 9;
1822 if (comp.config.link_libc and target.abi == .msvc)
1823 expected_nodes_len += 1;
1824 } else
1825 // Symbol table
1826 expected_nodes_len += 2;
1827
1828 // TLS section
1829 if (comp.config.any_non_single_threaded) {
1830 if (!is_image) expected_nodes_len += 1;
1831 expected_nodes_len += 1;
1832 }
1833 }
1834 defer assert(coff.nodes.len == expected_nodes_len);
1835
7851836 try coff.nodes.ensureTotalCapacity(gpa, expected_nodes_len);
7861837 coff.nodes.appendAssumeCapacity(.file);
7871838
7881839 const header_ni = Node.known.header;
789 assert(header_ni == try coff.mf.addOnlyChildNode(gpa, .root, .{
1840 assert(header_ni == try coff.mf.addOnlyChildNode(gpa, Node.known.file, .{
7901841 .alignment = coff.mf.flags.block_size,
7911842 .fixed = true,
7921843 }));
7931844 coff.nodes.appendAssumeCapacity(.header);
7941845
7951846 const signature_ni = Node.known.signature;
796 assert(signature_ni == try coff.mf.addOnlyChildNode(gpa, header_ni, .{
797 .size = (if (is_image) msdos_stub.len else 0) + "PE\x00\x00".len,
1847 assert(signature_ni == try coff.mf.addLastChildNode(gpa, if (is_image or !is_archive) header_ni else Node.known.file, .{
1848 .size = if (is_image)
1849 msdos_stub.len + std.coff.pe_signature.len
1850 else if (is_archive)
1851 std.coff.archive_signature.len
1852 else
1853 0,
7981854 .alignment = .@"4",
7991855 .fixed = true,
8001856 }));
8011857 coff.nodes.appendAssumeCapacity(.signature);
802 {
803 const signature_slice = signature_ni.slice(&coff.mf);
804 if (is_image) @memcpy(signature_slice[0..msdos_stub.len], &msdos_stub);
805 @memcpy(signature_slice[signature_slice.len - 4 ..], "PE\x00\x00");
1858
1859 const signature_slice = signature_ni.slice(&coff.mf);
1860 if (is_image) {
1861 @memcpy(signature_slice[0..msdos_stub.len], &msdos_stub);
1862 @memcpy(signature_slice[signature_slice.len - std.coff.pe_signature.len ..], std.coff.pe_signature);
1863 } else if (is_archive) {
1864 @memcpy(signature_slice, std.coff.archive_signature);
8061865 }
8071866
1867 const opt_coff_parent_ni = if (is_archive) parent: {
1868 const initial_member_count = Member.Index.known_count + @intFromBool(comp.zcu != null);
1869 try coff.members.ensureTotalCapacity(gpa, initial_member_count);
1870
1871 assert(Member.Index.first == try coff.addMemberAssumeCapacity(.first_linker, @sizeOf(u32)));
1872 coff.targetStore(coff.firstLinkerMemberNumSymbolsPtr(), 0);
1873
1874 assert(Member.Index.second == try coff.addMemberAssumeCapacity(.second_linker, 2 * @sizeOf(u32)));
1875 coff.targetStore(coff.secondLinkerMemberNumMembersPtr(), 0);
1876 coff.targetStore(coff.secondLinkerMemberNumSymbolsPtr(), 0);
1877
1878 assert(Member.Index.longnames == try coff.addMemberAssumeCapacity(.longnames, 0));
1879
1880 const first_linker_member = Member.Index.first.get(coff);
1881 const second_linker_member = Member.Index.second.get(coff);
1882 const longnames_member = Member.Index.longnames.get(coff);
1883
1884 try first_linker_member.initHeader(coff, "", timestamp);
1885 try second_linker_member.initHeader(coff, "", timestamp);
1886 try longnames_member.initHeader(coff, "/", timestamp);
1887
1888 if (comp.zcu) |zcu| {
1889 const zcu_mi = try coff.addMemberAssumeCapacity(.coff, @sizeOf(std.coff.Header));
1890 const zcu_member = zcu_mi.get(coff);
1891 try zcu_member.initHeader(coff, zcu.main_mod.fully_qualified_name, timestamp);
1892
1893 break :parent zcu_member.content_ni;
1894 }
1895
1896 // These placeholder nodes are placed before the first member - if there are
1897 // no other members then the last linker member (longnames) needs to expand
1898 // to fill the padding at the end of the file.
1899 assert(Node.known.zcu_member_header == try coff.mf.addNodeAfter(gpa, Node.known.header, .{}));
1900 assert(Node.known.zcu_member == try coff.mf.addNodeAfter(gpa, Node.known.header, .{}));
1901 coff.nodes.appendAssumeCapacity(.placeholder);
1902 coff.nodes.appendAssumeCapacity(.placeholder);
1903
1904 break :parent null;
1905 } else parent: {
1906 // TODO: Not ideal to have this many placeholder nodes - use two distinct `Node.known` types?
1907 while (true) {
1908 const placeholder_ni = try coff.mf.addLastChildNode(gpa, Node.known.file, .{});
1909 coff.nodes.appendAssumeCapacity(.placeholder);
1910 if (placeholder_ni == Node.known.zcu_member) break;
1911 }
1912
1913 break :parent Node.known.header;
1914 };
1915
1916 const coff_parent_ni = opt_coff_parent_ni orelse {
1917 // If we're not generating any code, no more known nodes are used
1918 while (coff.nodes.len < Node.known_count) {
1919 _ = try coff.mf.addNodeAfter(gpa, Node.known.header, .{});
1920 coff.nodes.appendAssumeCapacity(.placeholder);
1921 }
1922
1923 return;
1924 };
1925
8081926 const coff_header_ni = Node.known.coff_header;
809 assert(coff_header_ni == try coff.mf.addLastChildNode(gpa, header_ni, .{
1927 assert(coff_header_ni == try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{
8101928 .size = @sizeOf(std.coff.Header),
8111929 .alignment = .@"4",
8121930 .fixed = true,
......@@ -834,7 +1952,7 @@ fn initHeaders(
8341952 }
8351953
8361954 const optional_header_ni = Node.known.optional_header;
837 assert(optional_header_ni == try coff.mf.addLastChildNode(gpa, header_ni, .{
1955 assert(optional_header_ni == try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{
8381956 .size = optional_header_size,
8391957 .alignment = .@"4",
8401958 .fixed = true,
......@@ -876,7 +1994,7 @@ fn initHeaders(
8761994 .size_of_image = 0,
8771995 .size_of_headers = 0,
8781996 .checksum = 0,
879 .subsystem = .WINDOWS_CUI,
1997 .subsystem = subsystem,
8801998 .dll_flags = .{
8811999 .HIGH_ENTROPY_VA = true,
8822000 .DYNAMIC_BASE = true,
......@@ -925,7 +2043,7 @@ fn initHeaders(
9252043 .size_of_image = 0,
9262044 .size_of_headers = 0,
9272045 .checksum = 0,
928 .subsystem = .WINDOWS_CUI,
2046 .subsystem = subsystem,
9292047 .dll_flags = .{
9302048 .HIGH_ENTROPY_VA = true,
9312049 .DYNAMIC_BASE = true,
......@@ -946,13 +2064,13 @@ fn initHeaders(
9462064 }
9472065
9482066 const data_directories_ni = Node.known.data_directories;
949 assert(data_directories_ni == try coff.mf.addLastChildNode(gpa, header_ni, .{
2067 assert(data_directories_ni == try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{
9502068 .size = data_directories_size,
9512069 .alignment = .@"4",
9522070 .fixed = true,
9532071 }));
9542072 coff.nodes.appendAssumeCapacity(.data_directories);
955 {
2073 if (is_image) {
9562074 const data_directories = coff.dataDirectorySlice();
9572075 @memset(data_directories, .{ .virtual_address = 0, .size = 0 });
9582076 if (target_endian != native_endian) std.mem.byteSwapAllFields(
......@@ -962,7 +2080,7 @@ fn initHeaders(
9622080 }
9632081
9642082 const section_table_ni = Node.known.section_table;
965 assert(section_table_ni == try coff.mf.addLastChildNode(gpa, header_ni, .{
2083 assert(section_table_ni == try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{
9662084 .alignment = .@"4",
9672085 .fixed = true,
9682086 }));
......@@ -970,65 +2088,292 @@ fn initHeaders(
9702088
9712089 assert(coff.nodes.len == Node.known_count);
9722090
973 try coff.symbol_table.ensureTotalCapacity(gpa, Symbol.Index.known_count);
974 coff.symbol_table.addOneAssumeCapacity().* = .{
975 .ni = .none,
976 .rva = 0,
977 .size = 0,
978 .loc_relocs = .none,
979 .target_relocs = .none,
980 .section_number = .UNDEFINED,
981 };
982 assert(try coff.addSection(".data", .{
2091 if (!is_image) {
2092 // TODO: These two nodes could be inside one movable node?
2093 coff.symbol_table.ni = try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{
2094 .alignment = .@"2",
2095 .fixed = true,
2096 .moved = true,
2097 });
2098 coff.nodes.appendAssumeCapacity(.symbol_table);
2099
2100 coff.symbol_table.strings_ni = try coff.mf.addLastChildNode(gpa, coff_parent_ni, .{
2101 .size = @sizeOf(u32),
2102 .fixed = true,
2103 .resized = true,
2104 });
2105 coff.nodes.appendAssumeCapacity(.string_table);
2106 coff.targetStore(coff.symbolTableStringLenPtr(), @sizeOf(u32));
2107 }
2108
2109 try coff.symbols.ensureTotalCapacity(gpa, Symbol.Index.known_count);
2110 assert(coff.addSymbolAssumeCapacity() == .null);
2111
2112 // TODO: How do we tell MappedFile not to allocate physical space for .bss?
2113 // TODO: Could have a node flag 'virtual' that can never have slice* or fileLocation called on it
2114 // TODO: Instead of it's own section, place .bss as a pseudo-section at the end of .text in the extra space
2115 assert(try coff.addSection(.@".bss", .{
2116 .CNT_UNINITIALIZED_DATA = true,
2117 .MEM_READ = true,
2118 .MEM_WRITE = true,
2119 }) == .bss);
2120 assert(try coff.addSection(.@".data", .{
9832121 .CNT_INITIALIZED_DATA = true,
9842122 .MEM_READ = true,
9852123 .MEM_WRITE = true,
9862124 }) == .data);
987 assert(try coff.addSection(".rdata", .{
2125 assert(try coff.addSection(.@".rdata", .{
9882126 .CNT_INITIALIZED_DATA = true,
9892127 .MEM_READ = true,
9902128 }) == .rdata);
991 assert(try coff.addSection(".text", .{
2129 assert(try coff.addSection(.@".text", .{
9922130 .CNT_CODE = true,
9932131 .MEM_EXECUTE = true,
9942132 .MEM_READ = true,
9952133 }) == .text);
9962134
997 coff.import_table.ni = try coff.mf.addLastChildNode(
998 gpa,
999 (try coff.objectSectionMapIndex(
1000 .@".idata",
2135 if (is_image) {
2136 if (comp.config.link_libc and target.abi == .msvc) {
2137 // This section contains a function pointer table used by control flow guard:
2138 // https://learn.microsoft.com/en-us/windows/win32/secbp/control-flow-guard
2139 // The page containing it is set to PAGE_READONLY during startup, so this can't
2140 // be merged into .data this protection would overlap writable memory.
2141 _ = try coff.addSection(.@".fptable", .{
2142 .CNT_INITIALIZED_DATA = true,
2143 .MEM_READ = true,
2144 .MEM_WRITE = true,
2145 });
2146 }
2147
2148 // TODO: Lazily initialize this instead, avoid the extra logic for this in flushMoved / flushResized
2149 coff.import_table.ni = try coff.mf.addLastChildNode(
2150 gpa,
2151 (try coff.objectSectionMapIndex(
2152 .@".idata",
2153 coff.mf.flags.block_size,
2154 .{ .read = true, .initialized = true },
2155 )).symbol(coff).node(coff),
2156 .{ .alignment = .@"4" },
2157 );
2158 coff.nodes.appendAssumeCapacity(.import_directory_table);
2159
2160 coff.export_table.ni = (try coff.pseudoSectionMapIndex(
2161 .@".edata",
2162 .of(std.coff.ExportDirectoryTable),
2163 .{ .read = true, .initialized = true },
2164 )).symbol(coff).node(coff);
2165
2166 coff.export_table.export_directory_table_ni = try coff.mf.addLastChildNode(
2167 gpa,
2168 coff.export_table.ni,
2169 .{
2170 .size = @sizeOf(std.coff.ExportDirectoryTable) + file_name.len + 1,
2171 .moved = true,
2172 .fixed = true,
2173 },
2174 );
2175 coff.nodes.appendAssumeCapacity(.export_directory_table);
2176
2177 const name_index = @sizeOf(std.coff.ExportDirectoryTable);
2178 const table_slice = coff.export_table.export_directory_table_ni.slice(&coff.mf);
2179 @memcpy(table_slice[name_index..][0..file_name.len], file_name[0..file_name.len]);
2180 @memset(table_slice[name_index + file_name.len ..], 0);
2181
2182 const export_address_table_ni = try coff.mf.addLastChildNode(gpa, coff.export_table.ni, .{
2183 .alignment = .of(std.coff.ExportAddressTableEntry),
2184 .moved = true,
2185 });
2186 coff.nodes.appendAssumeCapacity(.export_address_table);
2187
2188 try coff.symbols.ensureUnusedCapacity(gpa, 1);
2189 coff.export_table.export_address_table_si = coff.addSymbolAssumeCapacity();
2190
2191 const export_address_table_sym = coff.export_table.export_address_table_si.get(coff);
2192 export_address_table_sym.ni = export_address_table_ni;
2193 assert(export_address_table_sym.loc_relocs == .none);
2194 export_address_table_sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
2195 export_address_table_sym.section_number =
2196 coff.getNode(coff.export_table.ni).pseudo_section.symbol(coff).get(coff).section_number;
2197
2198 coff.export_table.name_pointer_table_ni = try coff.mf.addLastChildNode(gpa, coff.export_table.ni, .{
2199 .alignment = .of(std.coff.ExportNamePointerTableEntry),
2200 .moved = true,
2201 });
2202 coff.nodes.appendAssumeCapacity(.export_name_pointer_table);
2203
2204 coff.export_table.ordinal_table_ni = try coff.mf.addLastChildNode(gpa, coff.export_table.ni, .{
2205 .alignment = .of(std.coff.ExportOrdinalTableEntry),
2206 .moved = true,
2207 });
2208 coff.nodes.appendAssumeCapacity(.export_ordinal_table);
2209
2210 coff.export_table.name_table_ni = try coff.mf.addLastChildNode(gpa, coff.export_table.ni, .{
2211 .alignment = .of(u8),
2212 .moved = true,
2213 });
2214 coff.nodes.appendAssumeCapacity(.export_name_table);
2215
2216 const export_directory_table = coff.exportDirectoryTable();
2217 export_directory_table.* = .{
2218 .flags = 0,
2219 .time_date_stamp = timestamp,
2220 .major_version = 0,
2221 .minor_version = 0,
2222 .name_rva = 0,
2223 .ordinal_base = 1,
2224 .number_of_entries = 0,
2225 .number_of_names = 0,
2226 .export_address_table_rva = 0,
2227 .name_pointer_table_rva = 0,
2228 .ordinal_table_rva = 0,
2229 };
2230 if (target_endian != native_endian)
2231 std.mem.byteSwapAllFields(std.coff.ExportDirectoryTable, export_directory_table);
2232 }
2233
2234 if (comp.config.any_non_single_threaded) {
2235 if (!is_image)
2236 _ = try coff.addSection(.@".tls$", .{
2237 .CNT_INITIALIZED_DATA = true,
2238 .MEM_READ = true,
2239 .MEM_WRITE = true,
2240 });
2241
2242 // While tls variables allocated at runtime are writable, the template itself is not.
2243 // In images, the template is in a .tls pseudo section in .rdata.
2244 // In objects / archives, this section is part of the above .tls$ section. The suffix
2245 // is maintained so merging can occur with other input tls symbols when linked later.
2246 _ = try coff.pseudoSectionMapIndex(
2247 if (is_image) .@".tls" else .@".tls$",
10012248 coff.mf.flags.block_size,
1002 .{ .read = true },
1003 )).symbol(coff).node(coff),
1004 .{ .alignment = .@"4", .moved = true },
1005 );
1006 coff.nodes.appendAssumeCapacity(.import_directory_table);
2249 .{ .read = true, .write = !is_image, .initialized = true },
2250 );
2251 }
2252}
10072253
1008 // While tls variables allocated at runtime are writable, the template itself is not
1009 if (comp.config.any_non_single_threaded) _ = try coff.objectSectionMapIndex(
1010 .@".tls$",
1011 coff.mf.flags.block_size,
1012 .{ .read = true },
1013 );
2254pub fn initBuiltins(coff: *Coff) !void {
2255 const comp = coff.base.comp;
2256 const gpa = comp.gpa;
2257 const target = &comp.root_mod.resolved_target.result;
2258 if (coff.isImage()) {
2259 const si = try coff.globalSymbol(.{ .name = "__ImageBase", .type = .data });
2260 const sym = si.get(coff);
2261 sym.ni = Node.known.header;
2262 }
2263
2264 defer coff.flushSectionMerges() catch unreachable;
2265 if (coff.isImage() and target.isMinGW() and comp.config.link_libc) {
2266 try coff.symbols.ensureUnusedCapacity(gpa, 8);
2267 try coff.globals.ensureUnusedCapacity(gpa, 2);
2268 try coff.nodes.ensureUnusedCapacity(gpa, 8);
2269 try coff.section_merges.ensureUnusedCapacity(gpa, 2);
2270
2271 const lists: []const struct { global: []const u8, start: String, end: String } = &.{
2272 .{ .global = "__CTOR_LIST__", .start = .@".ctors", .end = .@".ctors$ZZZ" },
2273 .{ .global = "__DTOR_LIST__", .start = .@".dtors", .end = .@".dtors$ZZZ" },
2274 };
2275
2276 // We need to explicitly merge these into .rdata as in objects they can be marked
2277 // as MEM_WRITE, and would have mismatced section flags.
2278 try coff.section_merges.put(gpa, .@".ctors", .@".rdata");
2279 try coff.section_merges.put(gpa, .@".dtors", .@".rdata");
2280
2281 for (lists) |list| {
2282 const addr_info = coff.targetAddrInfo();
2283
2284 // Any .(c|d)tor$(.*) input sections will merge in between these sections
2285 const start_osmi = try coff.objectSectionMapIndex(
2286 list.start,
2287 addr_info.alignment,
2288 .{ .read = true, .initialized = true },
2289 );
2290 const end_osmi = try coff.objectSectionMapIndex(
2291 list.end,
2292 addr_info.alignment,
2293 .{ .read = true, .initialized = true },
2294 );
2295
2296 // Additional nodes are used here, instead of just adding the sentinel
2297 // directly to the section data, since once input sections are added
2298 // as children, they would overwrite that data.
2299 const start_sym = start_osmi.symbol(coff).get(coff);
2300 const list_len_si = try coff.globalSymbol(.{ .name = list.global, .type = .data });
2301 const list_len_sym = list_len_si.get(coff);
2302 list_len_sym.setExtra(.{ .size = addr_info.size });
2303 list_len_sym.ni = try coff.mf.addFirstChildNode(gpa, start_sym.ni, .{
2304 .size = addr_info.size,
2305 .fixed = true,
2306 });
2307 coff.nodes.appendAssumeCapacity(.{ .builtin = list_len_si });
2308 list_len_sym.section_number = start_sym.section_number;
2309
2310 const start_slice = list_len_sym.ni.slice(&coff.mf);
2311 switch (addr_info.magic) {
2312 _ => unreachable,
2313 inline .PE32, .@"PE32+" => |t| {
2314 const addr: *TargetAddr(t) = @ptrCast(@alignCast(start_slice));
2315 // For __CTOR_LIST__ -1 indicates that the list is null terminated.
2316 // For __DTOR_LIST__, this value is ignored, the list is always null terminated
2317 coff.targetStore(addr, std.math.maxInt(TargetAddr(t)));
2318 },
2319 }
2320
2321 const end_sym = end_osmi.symbol(coff).get(coff);
2322 const list_end_si = coff.addSymbolAssumeCapacity();
2323 const list_end_sym = list_end_si.get(coff);
2324 list_end_sym.setExtra(.{ .size = addr_info.size });
2325 list_end_sym.ni = try coff.mf.addFirstChildNode(gpa, end_sym.ni, .{
2326 .size = addr_info.size,
2327 .fixed = true,
2328 });
2329 coff.nodes.appendAssumeCapacity(.{ .builtin = list_end_si });
2330 list_end_sym.section_number = start_sym.section_number;
10142331
1015 assert(coff.nodes.len == expected_nodes_len);
2332 @memset(list_end_sym.ni.slice(&coff.mf), 0);
2333
2334 try list_len_si.flushMoved(coff);
2335 try list_end_si.flushMoved(coff);
2336 }
2337 }
10162338}
10172339
10182340pub fn startProgress(coff: *Coff, prog_node: std.Progress.Node) void {
10192341 prog_node.increaseEstimatedTotalItems(3);
10202342 coff.const_prog_node = prog_node.start("Constants", coff.pending_uavs.count());
10212343 coff.synth_prog_node = prog_node.start("Synthetics", count: {
1022 var count = coff.globals.count() - coff.global_pending_index;
2344 var count =
2345 coff.globals.count() - coff.global_pending_index +
2346 coff.section_merges.count() - coff.section_merge_pending_index;
2347
10232348 for (&coff.lazy.values) |*lazy| count += lazy.map.count() - lazy.pending_index;
10242349 break :count count;
10252350 });
2351 if (!isImage(coff)) {
2352 prog_node.increaseEstimatedTotalItems(2);
2353 coff.symbol_prog_node = prog_node.start(
2354 "Symbols",
2355 coff.symbol_table.symbols.count() - coff.symbol_table.pending_symbol_index,
2356 );
2357 coff.member_prog_node = prog_node.start("Members", coff.pending_members.count());
2358 }
2359 coff.input_prog_node = prog_node.start(
2360 "Inputs",
2361 coff.input_sections.items.len - coff.input_section_pending_index,
2362 );
10262363 coff.mf.update_prog_node = prog_node.start("Relocations", coff.mf.updates.items.len);
10272364}
10282365
10292366pub fn endProgress(coff: *Coff) void {
10302367 coff.mf.update_prog_node.end();
10312368 coff.mf.update_prog_node = .none;
2369 coff.input_prog_node.end();
2370 coff.input_prog_node = .none;
2371 if (!coff.isImage()) {
2372 coff.member_prog_node.end();
2373 coff.member_prog_node = .none;
2374 coff.symbol_prog_node.end();
2375 coff.symbol_prog_node = .none;
2376 }
10322377 coff.synth_prog_node.end();
10332378 coff.synth_prog_node = .none;
10342379 coff.const_prog_node.end();
......@@ -1044,10 +2389,20 @@ fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 {
10442389 .file,
10452390 .header,
10462391 .signature,
2392 .archive_member_header,
2393 .archive_member,
10472394 .coff_header,
10482395 .optional_header,
10492396 .data_directories,
10502397 .section_table,
2398 .export_name_table,
2399 .placeholder,
2400 .symbol_table,
2401 .string_table,
2402 .relocation_table,
2403 .relocation_table_entry,
2404 .input_section,
2405 .builtin,
10512406 => unreachable,
10522407 .image_section => |si| si,
10532408 .import_directory_table => break :parent_rva coff.targetLoad(
......@@ -1062,9 +2417,21 @@ fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 {
10622417 .import_hint_name_table => |import_index| break :parent_rva coff.targetLoad(
10632418 &coff.importDirectoryEntryPtr(import_index).name_rva,
10642419 ),
2420 .export_directory_table => break :parent_rva coff.targetLoad(
2421 &coff.dataDirectoryPtr(.EXPORT).virtual_address,
2422 ),
2423 .export_address_table => break :parent_rva coff.targetLoad(
2424 &coff.exportDirectoryTable().export_address_table_rva,
2425 ),
2426 .export_name_pointer_table => break :parent_rva coff.targetLoad(
2427 &coff.exportDirectoryTable().name_pointer_table_rva,
2428 ),
2429 .export_ordinal_table => break :parent_rva coff.targetLoad(
2430 &coff.exportDirectoryTable().ordinal_table_rva,
2431 ),
10652432 inline .pseudo_section,
10662433 .object_section,
1067 .global,
2434 .import_thunk,
10682435 .nav,
10692436 .uav,
10702437 .lazy_code,
......@@ -1076,24 +2443,55 @@ fn computeNodeRva(coff: *Coff, ni: MappedFile.Node.Index) u32 {
10762443 const offset, _ = ni.location(&coff.mf).resolve(&coff.mf);
10772444 return @intCast(parent_rva + offset);
10782445}
1079fn computeNodeSectionOffset(coff: *Coff, ni: MappedFile.Node.Index) u32 {
1080 var section_offset: u32 = 0;
1081 var parent_ni = ni;
2446
2447fn computeSymbolSectionOffset(
2448 coff: *Coff,
2449 sym: *const Symbol,
2450 relative_to: enum { image, pseudo },
2451) u32 {
2452 var section_offset: u32 = sym.nodeOffset(coff);
2453 var parent_ni = sym.ni;
10822454 while (true) {
10832455 const offset, _ = parent_ni.location(&coff.mf).resolve(&coff.mf);
10842456 section_offset += @intCast(offset);
10852457 parent_ni = parent_ni.parent(&coff.mf);
10862458 switch (coff.getNode(parent_ni)) {
10872459 else => unreachable,
1088 .image_section, .pseudo_section => return section_offset,
1089 .object_section => {},
2460 .image_section => break,
2461 .pseudo_section => if (relative_to == .pseudo) break,
2462 .object_section,
2463 => {},
10902464 }
10912465 }
2466
2467 return section_offset;
10922468}
10932469
10942470pub inline fn targetEndian(_: *const Coff) std.lang.Endian {
10952471 return .little;
10962472}
2473
2474fn targetAddrInfo(coff: *Coff) struct {
2475 size: u8,
2476 alignment: std.mem.Alignment,
2477 magic: std.coff.OptionalHeader.Magic,
2478} {
2479 const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic);
2480 switch (magic) {
2481 _ => unreachable,
2482 .PE32 => return .{ .size = 4, .alignment = .@"4", .magic = magic },
2483 .@"PE32+" => return .{ .size = 8, .alignment = .@"8", .magic = magic },
2484 }
2485}
2486
2487fn TargetAddr(comptime magic: std.coff.OptionalHeader.Magic) type {
2488 return switch (magic) {
2489 _ => comptime unreachable,
2490 .PE32 => u32,
2491 .@"PE32+" => u64,
2492 };
2493}
2494
10972495fn targetLoad(coff: *const Coff, ptr: anytype) @typeInfo(@TypeOf(ptr)).pointer.child {
10982496 const Child = @typeInfo(@TypeOf(ptr)).pointer.child;
10992497 return switch (@typeInfo(Child)) {
......@@ -1122,9 +2520,55 @@ fn targetStore(coff: *const Coff, ptr: anytype, val: @typeInfo(@TypeOf(ptr)).poi
11222520}
11232521
11242522pub fn headerPtr(coff: *Coff) *std.coff.Header {
2523 assert(coff.hasCoffHeader());
11252524 return @ptrCast(@alignCast(Node.known.coff_header.slice(&coff.mf)));
11262525}
11272526
2527pub fn firstLinkerMemberNumSymbolsPtr(coff: *Coff) *u32 {
2528 assert(coff.isArchive());
2529 return @ptrCast(@alignCast(Node.known.first_linker_member.slice(&coff.mf)));
2530}
2531
2532pub fn firstLinkerMemberOffsetsSlice(coff: *Coff) []u32 {
2533 const len = std.mem.toNative(u32, coff.firstLinkerMemberNumSymbolsPtr().*, .big);
2534 return @ptrCast(@alignCast(Node.known.first_linker_member.slice(&coff.mf)[@sizeOf(u32)..][0 .. len * @sizeOf(u32)]));
2535}
2536
2537pub fn secondLinkerMemberNumMembersPtr(coff: *Coff) *align(2) u32 {
2538 assert(coff.isArchive());
2539 return @ptrCast(@alignCast(Node.known.second_linker_member.slice(&coff.mf)));
2540}
2541
2542pub fn secondLinkerMemberOffsetsSlice(coff: *Coff) []align(2) u32 {
2543 const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr());
2544 return @ptrCast(@alignCast(
2545 Node.known.second_linker_member.slice(&coff.mf)[@sizeOf(u32)..][0 .. num_members * @sizeOf(u32)],
2546 ));
2547}
2548
2549pub fn secondLinkerMemberNumSymbolsPtr(coff: *Coff) *align(2) u32 {
2550 const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr());
2551 return @ptrCast(@alignCast(
2552 Node.known.second_linker_member.slice(&coff.mf)[(1 + num_members) * @sizeOf(u32) ..],
2553 ));
2554}
2555
2556pub fn secondLinkerMemberIndicesSlice(coff: *Coff) []u16 {
2557 const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr());
2558 const num_symbols = coff.targetLoad(coff.secondLinkerMemberNumSymbolsPtr());
2559 return @ptrCast(@alignCast(
2560 Node.known.second_linker_member.slice(&coff.mf)[(2 + num_members) * @sizeOf(u32) ..][0 .. num_symbols * @sizeOf(u16)],
2561 ));
2562}
2563
2564pub fn secondLinkerMemberStringsSlice(coff: *Coff) []u8 {
2565 const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr());
2566 const num_symbols = coff.targetLoad(coff.secondLinkerMemberNumSymbolsPtr());
2567 return @ptrCast(@alignCast(
2568 Node.known.second_linker_member.slice(&coff.mf)[(2 + num_members) * @sizeOf(u32) + num_symbols * @sizeOf(u16) ..],
2569 ));
2570}
2571
11282572pub fn optionalHeaderStandardPtr(coff: *Coff) *std.coff.OptionalHeader {
11292573 return @ptrCast(@alignCast(
11302574 Node.known.optional_header.slice(&coff.mf)[0..@sizeOf(std.coff.OptionalHeader)],
......@@ -1136,6 +2580,7 @@ pub const OptionalHeaderPtr = union(std.coff.OptionalHeader.Magic) {
11362580 @"PE32+": *std.coff.OptionalHeader.@"PE32+",
11372581};
11382582pub fn optionalHeaderPtr(coff: *Coff) OptionalHeaderPtr {
2583 assert(coff.isImage());
11392584 const slice = Node.known.optional_header.slice(&coff.mf);
11402585 return switch (coff.targetLoad(&coff.optionalHeaderStandardPtr().magic)) {
11412586 _ => unreachable,
......@@ -1150,6 +2595,7 @@ pub fn optionalHeaderField(
11502595 coff: *Coff,
11512596 comptime field: std.meta.FieldEnum(std.coff.OptionalHeader.@"PE32+"),
11522597) @FieldType(std.coff.OptionalHeader.@"PE32+", @tagName(field)) {
2598 assert(coff.isImage());
11532599 return switch (coff.optionalHeaderPtr()) {
11542600 inline else => |optional_header| coff.targetLoad(&@field(optional_header, @tagName(field))),
11552601 };
......@@ -1158,6 +2604,7 @@ pub fn optionalHeaderField(
11582604pub fn dataDirectorySlice(
11592605 coff: *Coff,
11602606) *[std.coff.IMAGE.DIRECTORY_ENTRY.len]std.coff.ImageDataDirectory {
2607 assert(coff.isImage());
11612608 return @ptrCast(@alignCast(Node.known.data_directories.slice(&coff.mf)));
11622609}
11632610pub fn dataDirectoryPtr(
......@@ -1168,10 +2615,48 @@ pub fn dataDirectoryPtr(
11682615}
11692616
11702617pub fn sectionTableSlice(coff: *Coff) []std.coff.SectionHeader {
1171 return @ptrCast(@alignCast(Node.known.section_table.slice(&coff.mf)));
2618 return @ptrCast(@alignCast(
2619 Node.known.section_table.slice(&coff.mf)[0 .. coff.section_table.count() * @sizeOf(std.coff.SectionHeader)],
2620 ));
2621}
2622
2623pub fn symbolTableEntryStoragePtr(coff: *Coff, index: u32) *[std.coff.Symbol.sizeOf()]u8 {
2624 assert(!coff.isImage());
2625 const offset = index * std.coff.Symbol.sizeOf();
2626 return @ptrCast(@alignCast(coff.symbol_table.ni.slice(&coff.mf)[offset..][0..std.coff.Symbol.sizeOf()]));
2627}
2628
2629pub fn symbolTableEntryPtr(coff: *Coff, sti: SymbolTable.Index) ?*align(2) std.coff.Symbol {
2630 if (sti.unwrap()) |index|
2631 return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, index)))
2632 else
2633 return null;
2634}
2635
2636pub fn symbolTableSectionAuxEntryPtr(coff: *Coff, sti: SymbolTable.Index) ?*align(2) std.coff.SectionDefinition {
2637 if (symbolTableEntryPtr(coff, sti)) |entry| {
2638 assert(entry.storage_class == .STATIC and entry.number_of_aux_symbols == 1);
2639 return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, sti.unwrap().? + 1)));
2640 } else {
2641 return null;
2642 }
2643}
2644
2645pub fn symbolTableWeakExternalAuxEntryPtr(coff: *Coff, sti: SymbolTable.Index) ?*align(2) std.coff.WeakExternalDefinition {
2646 if (symbolTableEntryPtr(coff, sti)) |entry| {
2647 assert(entry.storage_class == .WEAK_EXTERNAL and entry.number_of_aux_symbols == 1);
2648 return @ptrCast(@alignCast(symbolTableEntryStoragePtr(coff, sti.unwrap().? + 1)));
2649 } else {
2650 return null;
2651 }
2652}
2653
2654pub fn symbolTableStringLenPtr(coff: *Coff) *align(1) u32 {
2655 return @ptrCast(@alignCast(coff.symbol_table.strings_ni.slice(&coff.mf)[0..@sizeOf(u32)]));
11722656}
11732657
11742658pub fn importDirectoryTableSlice(coff: *Coff) []std.coff.ImportDirectoryEntry {
2659 assert(coff.isImage());
11752660 return @ptrCast(@alignCast(coff.import_table.ni.slice(&coff.mf)));
11762661}
11772662pub fn importDirectoryEntryPtr(
......@@ -1181,16 +2666,40 @@ pub fn importDirectoryEntryPtr(
11812666 return &coff.importDirectoryTableSlice()[@intFromEnum(import_index)];
11822667}
11832668
2669pub fn exportDirectoryTable(coff: *Coff) *std.coff.ExportDirectoryTable {
2670 return @ptrCast(@alignCast(coff.export_table.export_directory_table_ni.slice(&coff.mf)));
2671}
2672
2673pub fn exportNamePointerTableSlice(coff: *Coff) []std.coff.ExportNamePointerTableEntry {
2674 const debug = coff.export_table.name_pointer_table_ni.slice(&coff.mf);
2675 _ = debug;
2676
2677 return @ptrCast(@alignCast(coff.export_table.name_pointer_table_ni.slice(&coff.mf)));
2678}
2679
2680pub fn exportOrdinalTableSlice(coff: *Coff) []std.coff.ExportOrdinalTableEntry {
2681 return @ptrCast(@alignCast(coff.export_table.ordinal_table_ni.slice(&coff.mf)));
2682}
2683
11842684fn addSymbolAssumeCapacity(coff: *Coff) Symbol.Index {
1185 defer coff.symbol_table.addOneAssumeCapacity().* = .{
2685 defer coff.symbols.addOneAssumeCapacity().* = .{
11862686 .ni = .none,
11872687 .rva = 0,
1188 .size = 0,
2688 .value = .{ .none = {} },
2689 .extra = .{ .size = 0 },
2690 .flags = .{
2691 .value_tag = .none,
2692 .extra_tag = .size,
2693 .type = .unknown,
2694 .dll_storage_class = .default,
2695 .weak_external_strat = .none,
2696 },
11892697 .loc_relocs = .none,
11902698 .target_relocs = .none,
11912699 .section_number = .UNDEFINED,
2700 .gmi = .none,
11922701 };
1193 return @enumFromInt(coff.symbol_table.items.len);
2702 return @enumFromInt(coff.symbols.items.len);
11942703}
11952704
11962705fn initSymbolAssumeCapacity(coff: *Coff) !Symbol.Index {
......@@ -1205,12 +2714,55 @@ fn getOrPutString(coff: *Coff, string: []const u8) !String {
12052714fn getOrPutOptionalString(coff: *Coff, string: ?[]const u8) !String.Optional {
12062715 return (try coff.getOrPutString(string orelse return .none)).toOptional();
12072716}
2717fn getString(coff: *Coff, string: []const u8) String.Optional {
2718 if (coff.strings.getKeyAdapted(
2719 string,
2720 std.hash_map.StringIndexAdapter{ .bytes = &coff.string_bytes },
2721 )) |key|
2722 return @as(String, @enumFromInt(key)).toOptional()
2723 else
2724 return .none;
2725}
2726
2727/// If the name does not fit in the symbol header, adds it to the symbol table string table.
2728/// If the caller knows this name already has a String associated with it, they can avoid
2729/// a redundant call to `getOrPutString` by specifying `opt_string`.
2730/// The lifetime of the return value matches that of `name`.
2731fn getOrPutSymbolName(coff: *Coff, name: []const u8, opt_string: ?String) !SymbolTable.SymbolName {
2732 assert(!coff.isImage());
2733 const gpa = coff.base.comp.gpa;
2734
2735 return if (name.len > header_name_max_len) name: {
2736 const string = opt_string orelse try coff.getOrPutString(name);
2737 const string_gop = try coff.symbol_table.strings.getOrPut(gpa, string);
2738 if (!string_gop.found_existing) {
2739 const string_index = coff.symbol_table.strings_ni.location(&coff.mf).resolve(&coff.mf)[1];
2740 string_gop.value_ptr.* = @enumFromInt(string_index);
2741
2742 try coff.symbol_table.strings_ni.resize(&coff.mf, gpa, string_index + name.len + 1);
2743 const slice = coff.symbol_table.strings_ni.slice(&coff.mf);
2744 @memcpy(slice[@intCast(string_index)..][0..name.len], name);
2745 slice[@intCast(string_index + name.len)] = 0;
2746 }
2747
2748 break :name .{ .long = string_gop.value_ptr.* };
2749 } else .{ .short = name };
2750}
12082751
2752/// `len` does not include null terminators
12092753fn ensureUnusedStringCapacity(coff: *Coff, len: usize) !void {
12102754 const gpa = coff.base.comp.gpa;
12112755 try coff.strings.ensureUnusedCapacityContext(gpa, 1, .{ .bytes = &coff.string_bytes });
12122756 try coff.string_bytes.ensureUnusedCapacity(gpa, len + 1);
12132757}
2758
2759/// `total_len` includes null terminators
2760fn ensureManyUnusedStringCapacity(coff: *Coff, num_strings: u32, total_len: usize) !void {
2761 const gpa = coff.base.comp.gpa;
2762 try coff.strings.ensureUnusedCapacityContext(gpa, num_strings, .{ .bytes = &coff.string_bytes });
2763 try coff.string_bytes.ensureUnusedCapacity(gpa, total_len + num_strings);
2764}
2765
12142766fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String {
12152767 const gop = coff.strings.getOrPutAssumeCapacityAdapted(
12162768 string,
......@@ -1225,18 +2777,78 @@ fn getOrPutStringAssumeCapacity(coff: *Coff, string: []const u8) String {
12252777 return @enumFromInt(gop.key_ptr.*);
12262778}
12272779
1228pub fn globalSymbol(coff: *Coff, name: []const u8, lib_name: ?[]const u8) !Symbol.Index {
1229 const gpa = coff.base.comp.gpa;
1230 try coff.symbol_table.ensureUnusedCapacity(gpa, 1);
1231 const sym_gop = try coff.globals.getOrPut(gpa, .{
1232 .name = try coff.getOrPutString(name),
1233 .lib_name = try coff.getOrPutOptionalString(lib_name),
1234 });
2780const GlobalOptions = struct {
2781 name: []const u8,
2782 lib_name: ?[]const u8 = null,
2783 type: Symbol.Type = .unknown,
2784 dll_storage_class: Symbol.DllStorageClass = .default,
2785};
2786
2787fn getOrPutGlobalSymbol(
2788 coff: *Coff,
2789 opts: GlobalOptions,
2790) !std.array_hash_map.Auto(String, Global).GetOrPutResult {
2791 const comp = coff.base.comp;
2792 const gpa = comp.gpa;
2793 try coff.symbols.ensureUnusedCapacity(gpa, 1);
2794
2795 const lib_name: String.Optional = if (opts.lib_name) |lib_name| lib_name: {
2796 const is_libc = std.zig.target.isLibCLibName(&comp.root_mod.resolved_target.result, lib_name);
2797 if (is_libc) {
2798 // This is guaranteed by Sema.handleExternLibName
2799 if (!comp.config.link_libc) unreachable;
2800
2801 // TODO: The user has requested this symbol come from libc, but this logic allows
2802 // it to come from anywhere. We need to know what inputs are libc inputs,
2803 // and set a flag to only search them for this symbol.
2804 break :lib_name .none;
2805 }
2806
2807 break :lib_name (try coff.getOrPutString(lib_name)).toOptional();
2808 } else .none;
2809
2810 const sym_gop = try coff.globals.getOrPut(gpa, try coff.getOrPutString(opts.name));
12352811 if (!sym_gop.found_existing) {
1236 sym_gop.value_ptr.* = coff.addSymbolAssumeCapacity();
2812 const si = coff.addSymbolAssumeCapacity();
2813 const sym = si.get(coff);
2814 sym.gmi = .wrap(@intCast(sym_gop.index));
2815 sym.flags.type = opts.type;
2816 sym.flags.dll_storage_class = opts.dll_storage_class;
2817 sym_gop.value_ptr.* = .{
2818 .si = si,
2819 .lib_name = lib_name,
2820 };
12372821 coff.synth_prog_node.increaseEstimatedTotalItems(1);
2822
2823 log.debug("globalSymbol({s}, {?s}) = {d}", .{ opts.name, opts.lib_name, si });
2824 }
2825
2826 return sym_gop;
2827}
2828
2829fn getDefinedGlobal(coff: *Coff, name: []const u8) Symbol.Index {
2830 if (coff.globals.get(
2831 coff.getString(name).unwrap() orelse return .null,
2832 )) |global| if (global.si.get(coff).ni != .none) return global.si;
2833 return .null;
2834}
2835
2836pub fn globalSymbol(coff: *Coff, opts: GlobalOptions) !Symbol.Index {
2837 const gop = try coff.getOrPutGlobalSymbol(opts);
2838 return gop.value_ptr.si;
2839}
2840
2841pub fn pendingSymbolTableEntry(coff: *Coff, si: Symbol.Index) !void {
2842 assert(!coff.isImage());
2843 const sym = si.get(coff);
2844
2845 assert(sym.ni != .none or sym.gmi != .none);
2846 const gpa = coff.base.comp.gpa;
2847 const gop = try coff.symbol_table.symbols.getOrPut(gpa, si);
2848 if (!gop.found_existing) {
2849 coff.symbol_prog_node.increaseEstimatedTotalItems(1);
2850 gop.value_ptr.* = .none;
12382851 }
1239 return sym_gop.value_ptr.*;
12402852}
12412853
12422854fn navSection(
......@@ -1247,13 +2859,13 @@ fn navSection(
12472859 const ip = &zcu.intern_pool;
12482860 const default: String, const attributes: ObjectSectionAttributes =
12492861 if (nav_resolved.@"threadlocal" and coff.base.comp.config.any_non_single_threaded) .{
1250 .@".tls$", .{ .read = true, .write = true },
2862 .@".tls$", .{ .read = true, .write = true, .initialized = true },
12512863 } else if (ip.isFunctionType(nav_resolved.type)) .{
12522864 .@".text", .{ .read = true, .execute = true },
12532865 } else if (nav_resolved.@"const") .{
1254 .@".rdata", .{ .read = true },
2866 .@".rdata", .{ .read = true, .initialized = true },
12552867 } else .{
1256 .@".data", .{ .read = true, .write = true },
2868 .@".data", .{ .read = true, .write = true, .initialized = true },
12572869 };
12582870
12592871 return (try coff.objectSectionMapIndex(
......@@ -1270,7 +2882,7 @@ fn navSection(
12702882}
12712883fn navMapIndex(coff: *Coff, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Node.NavMapIndex {
12722884 const gpa = zcu.gpa;
1273 try coff.symbol_table.ensureUnusedCapacity(gpa, 1);
2885 try coff.symbols.ensureUnusedCapacity(gpa, 1);
12742886 const sym_gop = try coff.navs.getOrPut(gpa, nav_index);
12752887 if (!sym_gop.found_existing) sym_gop.value_ptr.* = coff.addSymbolAssumeCapacity();
12762888 return @enumFromInt(sym_gop.index);
......@@ -1278,17 +2890,20 @@ fn navMapIndex(coff: *Coff, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Node.Na
12782890pub fn navSymbol(coff: *Coff, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Symbol.Index {
12792891 const ip = &zcu.intern_pool;
12802892 const nav = ip.getNav(nav_index);
1281 if (nav.getExtern(ip)) |@"extern"| return coff.globalSymbol(
1282 @"extern".name.toSlice(ip),
1283 @"extern".lib_name.toSlice(ip),
1284 );
2893 if (nav.getExtern(ip)) |@"extern"| return coff.globalSymbol(.{
2894 .name = @"extern".name.toSlice(ip),
2895 .lib_name = @"extern".lib_name.toSlice(ip),
2896 // TODO: Threadlocal as well?
2897 .type = if (ip.isFunctionType(nav.resolved.?.type)) .code else .data,
2898 .dll_storage_class = if (@"extern".is_dll_import) .dllimport else .default,
2899 });
12852900 const nmi = try coff.navMapIndex(zcu, nav_index);
12862901 return nmi.symbol(coff);
12872902}
12882903
12892904fn uavMapIndex(coff: *Coff, uav_val: InternPool.Index) !Node.UavMapIndex {
12902905 const gpa = coff.base.comp.gpa;
1291 try coff.symbol_table.ensureUnusedCapacity(gpa, 1);
2906 try coff.symbols.ensureUnusedCapacity(gpa, 1);
12922907 const sym_gop = try coff.uavs.getOrPut(gpa, uav_val);
12932908 if (!sym_gop.found_existing) sym_gop.value_ptr.* = coff.addSymbolAssumeCapacity();
12942909 return @enumFromInt(sym_gop.index);
......@@ -1300,7 +2915,7 @@ pub fn uavSymbol(coff: *Coff, uav_val: InternPool.Index) !Symbol.Index {
13002915
13012916pub fn lazySymbol(coff: *Coff, lazy: link.File.LazySymbol) !Symbol.Index {
13022917 const gpa = coff.base.comp.gpa;
1303 try coff.symbol_table.ensureUnusedCapacity(gpa, 1);
2918 try coff.symbols.ensureUnusedCapacity(gpa, 1);
13042919 const sym_gop = try coff.lazy.getPtr(lazy.kind).map.getOrPut(gpa, lazy.ty);
13052920 if (!sym_gop.found_existing) {
13062921 sym_gop.value_ptr.* = try coff.initSymbolAssumeCapacity();
......@@ -1314,7 +2929,7 @@ pub fn getNavVAddr(
13142929 pt: Zcu.PerThread,
13152930 nav: InternPool.Nav.Index,
13162931 reloc_info: link.File.RelocInfo,
1317) !u64 {
2932) link.Error!u64 {
13182933 return coff.getVAddr(reloc_info, try coff.navSymbol(pt.zcu, nav));
13192934}
13202935
......@@ -1322,30 +2937,426 @@ pub fn getUavVAddr(
13222937 coff: *Coff,
13232938 uav: InternPool.Index,
13242939 reloc_info: link.File.RelocInfo,
1325) !u64 {
2940) link.Error!u64 {
13262941 return coff.getVAddr(reloc_info, try coff.uavSymbol(uav));
13272942}
13282943
1329pub fn getVAddr(coff: *Coff, reloc_info: link.File.RelocInfo, target_si: Symbol.Index) !u64 {
2944pub fn getVAddr(coff: *Coff, reloc_info: link.File.RelocInfo, target_si: Symbol.Index) link.Error!u64 {
13302945 try coff.addReloc(
13312946 @enumFromInt(@intFromEnum(reloc_info.parent.atom_index)),
13322947 reloc_info.offset,
13332948 target_si,
1334 reloc_info.addend,
2949 .{ .known = reloc_info.addend },
13352950 switch (coff.targetLoad(&coff.headerPtr().machine)) {
13362951 else => unreachable,
13372952 .AMD64 => .{ .AMD64 = .ADDR64 },
13382953 .I386 => .{ .I386 = .DIR32 },
13392954 },
13402955 );
1341 return coff.optionalHeaderField(.image_base) + target_si.get(coff).rva;
2956
2957 var vaddr: u64 = target_si.get(coff).rva;
2958 if (coff.isImage()) vaddr += coff.optionalHeaderField(.image_base);
2959 return vaddr;
2960}
2961
2962/// Caller guarantees there is capacity for one member and two nodes
2963fn addMemberAssumeCapacity(coff: *Coff, kind: std.coff.ArchiveMemberHeader.Kind, size: u64) !Member.Index {
2964 const comp = coff.base.comp;
2965 const gpa = comp.gpa;
2966
2967 // TODO: These two nodes could to be inside a movable node if kind == .coff|.import
2968 const header_ni = try coff.mf.addLastChildNode(gpa, Node.known.file, .{
2969 .size = @sizeOf(std.coff.ArchiveMemberHeader),
2970 .alignment = .@"2",
2971 .fixed = true,
2972 .moved = true,
2973 });
2974
2975 const content_ni = try coff.mf.addLastChildNode(gpa, Node.known.file, .{
2976 // The actual alignment required by the spec is 2, but to allow aligned access to
2977 // the various COFF data structures in-place during linking we overalign
2978 .alignment = switch (kind) {
2979 .coff => .@"4",
2980 else => .@"2",
2981 },
2982 .size = size,
2983 .resized = size > 0,
2984 .fixed = true,
2985 });
2986
2987 const mi: Member.Index = @enumFromInt(coff.members.items.len);
2988 coff.members.appendAssumeCapacity(.{
2989 .kind = kind,
2990 .header_ni = header_ni,
2991 .content_ni = content_ni,
2992 .first_linker_indices = .empty,
2993 });
2994
2995 coff.nodes.appendAssumeCapacity(.{ .archive_member_header = mi });
2996 coff.nodes.appendAssumeCapacity(.{ .archive_member = mi });
2997
2998 switch (kind) {
2999 .first_linker, .second_linker, .longnames => {},
3000 else => {
3001 const new_num_members = coff.members.items.len - Member.Index.known_count;
3002 coff.targetStore(
3003 coff.secondLinkerMemberNumMembersPtr(),
3004 @intCast(new_num_members),
3005 );
3006
3007 const old_size = Node.known.second_linker_member.location(&coff.mf).resolve(&coff.mf)[1];
3008 const old_header_size = new_num_members * @sizeOf(u32);
3009 const trailing_size: usize = @intCast(old_size - old_header_size);
3010 try Node.known.second_linker_member.resize(&coff.mf, gpa, old_size + @sizeOf(u32));
3011
3012 const slice = Node.known.second_linker_member.slice(&coff.mf);
3013 @memmove(
3014 slice[old_header_size + @sizeOf(u32) ..][0..trailing_size],
3015 slice[old_header_size..][0..trailing_size],
3016 );
3017
3018 // Offset will be written by flushMoved on header_ni
3019 },
3020 }
3021
3022 switch (kind) {
3023 .first_linker,
3024 .longnames,
3025 .import,
3026 => {},
3027 .second_linker,
3028 .coff,
3029 => {
3030 try coff.pending_members.ensureTotalCapacity(
3031 gpa,
3032 coff.pending_members.capacity() + 1,
3033 );
3034 coff.member_prog_node.increaseEstimatedTotalItems(1);
3035 },
3036 }
3037
3038 return mi;
3039}
3040
3041fn appendMemberSymbolString(
3042 coff: *Coff,
3043 strings_ni: MappedFile.Node.Index,
3044 new_size: u64,
3045 name: []const u8,
3046 offset: u64,
3047) !void {
3048 try strings_ni.resize(&coff.mf, coff.base.comp.gpa, new_size);
3049 const name_slice = strings_ni.slice(&coff.mf)[offset..][0 .. name.len + 1];
3050 @memcpy(name_slice[0..name.len], name);
3051 name_slice[name.len] = 0;
3052}
3053
3054fn ensureMemberSymbol(coff: *Coff, mi: Member.Index, name: String) !void {
3055 const gpa = coff.base.comp.gpa;
3056 const member = mi.get(coff);
3057 assert(member.kind == .coff);
3058
3059 const gop = try member.first_linker_indices.getOrPut(gpa, .{ .mi = mi, .name = name });
3060 if (gop.found_existing) return;
3061
3062 const mfli: Member.FirstLinkerIndex = blk: {
3063 const num_symbols_ptr = coff.firstLinkerMemberNumSymbolsPtr();
3064 const num_symbols = std.mem.toNative(u32, num_symbols_ptr.*, .big);
3065 num_symbols_ptr.* = std.mem.nativeTo(u32, num_symbols + 1, .big);
3066 break :blk @enumFromInt(num_symbols);
3067 };
3068
3069 gop.value_ptr.* = mfli;
3070
3071 // Linker member fields are not modeled as nodes because MappedFile
3072 // can't guarantee that they will be tightly packed after resizing
3073
3074 const name_slice = name.toSlice(coff);
3075 const new_string_table_size: u32 = @intCast(coff.lib_string_len + name_slice.len + 1);
3076 defer coff.lib_string_len = new_string_table_size;
3077
3078 {
3079 const old_header_size: usize = @intCast(@sizeOf(u32) + @intFromEnum(mfli) * @sizeOf(u32));
3080 const new_header_size: usize = @intCast(old_header_size + @sizeOf(u32));
3081 try Node.known.first_linker_member.resize(&coff.mf, gpa, new_header_size + new_string_table_size);
3082
3083 const slice = Node.known.first_linker_member.slice(&coff.mf);
3084 @memmove(slice[new_header_size..][0..coff.lib_string_len], slice[old_header_size..][0..coff.lib_string_len]);
3085 @memcpy(slice[new_header_size + coff.lib_string_len ..][0..name_slice.len], name_slice[0..name_slice.len]);
3086 slice[new_header_size + coff.lib_string_len + name_slice.len] = 0;
3087
3088 // New offset entry is written in flushMember
3089 }
3090
3091 {
3092 const num_members = coff.targetLoad(coff.secondLinkerMemberNumMembersPtr());
3093 const old_header_size = 2 * @sizeOf(u32) + num_members * @sizeOf(u32) + @intFromEnum(mfli) * @sizeOf(u16);
3094 const new_header_size = old_header_size + @sizeOf(u16);
3095 try Node.known.second_linker_member.resize(&coff.mf, gpa, new_header_size + new_string_table_size);
3096
3097 const old_needs_sort = coff.pending_members.get(Member.Index.second) != null;
3098 const needs_sort = old_needs_sort or (if (coff.lib_string_table.items.len > 0)
3099 std.mem.lessThan(
3100 u8,
3101 name_slice,
3102 coff.lib_string_table.items[coff.lib_string_table.items.len - 1].toSlice(coff),
3103 )
3104 else
3105 false);
3106
3107 try coff.lib_string_table.append(gpa, name);
3108
3109 const slice = Node.known.second_linker_member.slice(&coff.mf);
3110 coff.targetStore(coff.secondLinkerMemberNumSymbolsPtr(), @intFromEnum(mfli) + 1);
3111 if (!needs_sort) {
3112 @memmove(slice[new_header_size..][0..coff.lib_string_len], slice[old_header_size..][0..coff.lib_string_len]);
3113 @memcpy(slice[new_header_size + coff.lib_string_len ..][0..name_slice.len], name_slice[0..name_slice.len]);
3114 slice[new_header_size + coff.lib_string_len + name_slice.len] = 0;
3115 } else if (!old_needs_sort) {
3116 // The entire string table is rebuilt in flushMember after sorting
3117 coff.pending_members.putAssumeCapacity(Member.Index.second, {});
3118 }
3119
3120 // Indices in this table are 1-based
3121 const index_ptr: *u16 = @ptrCast(@alignCast(slice[old_header_size..]));
3122 coff.targetStore(index_ptr, @intCast(@intFromEnum(mi) - Member.Index.known_count + 1));
3123 }
3124
3125 coff.pending_members.putAssumeCapacity(mi, {});
3126 coff.member_prog_node.increaseEstimatedTotalItems(1);
3127}
3128
3129fn flushSymbolTableEntry(coff: *Coff, index: u32, pt: Zcu.PerThread) !void {
3130 assert(!coff.isImage());
3131 const gpa = coff.base.comp.gpa;
3132
3133 const si = coff.symbol_table.symbols.keys()[index];
3134 const sti = &coff.symbol_table.symbols.values()[index];
3135
3136 const sym = si.get(coff);
3137 assert(sym.ni != .none or sym.gmi != .none);
3138
3139 const entry = coff.symbolTableEntryPtr(sti.*) orelse entry: {
3140 var buf: [15]u8 = undefined;
3141 const symbol_name, const num_aux_symbols: u8, const complex_type: std.coff.ComplexType =
3142 if (sym.gmi != .none) blk: {
3143 const name = sym.gmi.name(coff);
3144 break :blk .{
3145 try coff.getOrPutSymbolName(name.toSlice(coff), name),
3146 @intFromBool(sym.flags.weak_external_strat != .none),
3147 if (Symbol.Index.text.get(coff).section_number == sym.section_number)
3148 .FUNCTION
3149 else
3150 .NULL,
3151 };
3152 } else blk: switch (coff.getNode(sym.ni)) {
3153 .image_section => .{
3154 try coff.getOrPutSymbolName(&sym.section_number.header(coff).name, null),
3155 1,
3156 .NULL,
3157 },
3158 .nav => |nmi| {
3159 const zcu = coff.base.comp.zcu.?;
3160 const ip = &zcu.intern_pool;
3161 const nav = ip.getNav(nmi.navIndex(coff));
3162 break :blk .{
3163 try coff.getOrPutSymbolName(nav.fqn.toSlice(ip), null),
3164 0,
3165 if (ip.isFunctionType(nav.resolved.?.type)) .FUNCTION else .NULL,
3166 };
3167 },
3168 .uav => |umi| {
3169 var w = Io.Writer.fixed(&buf);
3170 w.print("__anon_{x}", .{umi.uavValue(coff)}) catch unreachable;
3171 break :blk .{
3172 try coff.getOrPutSymbolName(w.buffered(), null),
3173 0,
3174 .NULL,
3175 };
3176 },
3177 inline .lazy_code, .lazy_const_data => |mi, tag| {
3178 const lazy_sym = mi.lazySymbol(coff);
3179 const name = try std.fmt.allocPrint(gpa, "__lazy_{s}_{f}", .{
3180 @tagName(lazy_sym.kind),
3181 Type.fromInterned(lazy_sym.ty).fmt(pt),
3182 });
3183 defer gpa.free(name);
3184
3185 const string = try coff.getOrPutString(name);
3186 break :blk .{
3187 try coff.getOrPutSymbolName(string.toSlice(coff), string),
3188 0,
3189 if (tag == .lazy_code) .FUNCTION else .NULL,
3190 };
3191 },
3192 else => {
3193 log.err("TODO implement symbol table init for {s} ({d})", .{ @tagName(coff.getNode(sym.ni)), si });
3194 unreachable;
3195 },
3196 };
3197
3198 const old_num_symbols = coff.targetLoad(&coff.headerPtr().number_of_symbols);
3199 const new_num_symbols = old_num_symbols + 1 + num_aux_symbols;
3200 coff.targetStore(&coff.headerPtr().number_of_symbols, new_num_symbols);
3201
3202 try coff.symbol_table.ni.resize(&coff.mf, gpa, new_num_symbols * std.coff.Symbol.sizeOf());
3203
3204 sti.* = .wrap(old_num_symbols);
3205 si.flushSymbolTableIndex(coff);
3206
3207 const entry = coff.symbolTableEntryPtr(sti.*).?;
3208 symbol_name.store(coff, &entry.name);
3209
3210 entry.section_number = @enumFromInt(@intFromEnum(sym.section_number));
3211 entry.type = .{
3212 .complex_type = complex_type,
3213 .base_type = .NULL,
3214 };
3215
3216 entry.storage_class = if (sym.gmi != .none)
3217 .EXTERNAL
3218 else if (sym.flags.extra_tag == .next_alias_si) storage: {
3219 var alias_sym = sym;
3220 const weak_external = while (alias_sym.flags.extra_tag == .next_alias_si) {
3221 const alias_si = alias_sym.extra.next_alias_si;
3222 alias_sym = alias_si.get(coff);
3223 assert(alias_sym.ni == sym.ni);
3224 if (alias_sym.flags.weak_external_strat != .none)
3225 break true;
3226 } else false;
3227 break :storage if (weak_external) .EXTERNAL else .STATIC;
3228 } else .STATIC;
3229
3230 entry.number_of_aux_symbols = num_aux_symbols;
3231 if (coff.targetEndian() != native_endian)
3232 std.mem.byteSwapAllFieldsAligned(std.coff.Symbol, .@"2", entry);
3233
3234 if (num_aux_symbols > 0) aux_init: {
3235 if (sym.gmi != .none) {
3236 entry.section_number = .UNDEFINED;
3237 entry.storage_class = .WEAK_EXTERNAL;
3238
3239 const tag_index = sym.value.weak_alias_si.sti(coff).unwrap().?;
3240 const aux_ptr = coff.symbolTableWeakExternalAuxEntryPtr(sti.*).?;
3241 aux_ptr.* = .{
3242 .tag_index = tag_index,
3243 .flag = switch (sym.flags.weak_external_strat) {
3244 .none => unreachable,
3245 .no_library => .SEARCH_NOLIBRARY,
3246 .library => .SEARCH_LIBRARY,
3247 .alias => .SEARCH_ALIAS,
3248 .anti_dependency => .ANTI_DEPENDENCY,
3249 },
3250 .unused = @splat(0),
3251 };
3252 if (coff.targetEndian() != native_endian)
3253 std.mem.byteSwapAllFieldsAligned(std.coff.WeakExternalDefinition, .@"2", aux_ptr);
3254
3255 break :aux_init;
3256 } else switch (coff.getNode(sym.ni)) {
3257 .image_section => |sec_si| {
3258 assert(si == sec_si);
3259 const header = sym.section_number.header(coff);
3260 const aux_ptr = coff.symbolTableSectionAuxEntryPtr(sti.*).?;
3261 aux_ptr.* = .{
3262 .length = @intCast(sym.ni.location(&coff.mf).resolve(&coff.mf)[1]),
3263 .number_of_relocations = header.number_of_relocations,
3264 .number_of_linenumbers = header.number_of_linenumbers,
3265 .checksum = 0,
3266 .number = 0,
3267 .selection = .NONE,
3268 .unused = @splat(0),
3269 };
3270 if (coff.targetEndian() != native_endian)
3271 std.mem.byteSwapAllFieldsAligned(std.coff.SectionDefinition, .@"2", aux_ptr);
3272
3273 break :aux_init;
3274 },
3275 else => {},
3276 }
3277
3278 unreachable;
3279 }
3280
3281 break :entry entry;
3282 };
3283
3284 coff.targetStore(&entry.value, switch (sym.section_number) {
3285 .UNDEFINED => if (entry.storage_class == .WEAK_EXTERNAL) 0 else sym.size(),
3286 .ABSOLUTE,
3287 .DEBUG,
3288 => unreachable,
3289 else => switch (coff.getNode(sym.ni)) {
3290 .image_section => 0,
3291 else => coff.computeSymbolSectionOffset(sym, .image),
3292 },
3293 });
3294
3295 log.debug("flushSymbolTableEntry({d}) = {d}", .{ si, sti.* });
3296}
3297
3298fn flushInputMember(coff: *Coff, iami: InputArchive.Member.Index) !void {
3299 const member = iami.member(coff);
3300 assert(!member.flags.is_loaded);
3301 defer member.flags.is_loaded = true;
3302 switch (member.content) {
3303 .import => unreachable,
3304 .object => |file_location| {
3305 if (file_location.size == 0) return;
3306 const comp = coff.base.comp;
3307 const io = comp.io;
3308 const path = member.iai.path(coff);
3309 const file = try path.root_dir.handle.openFile(io, path.sub_path, .{});
3310 defer file.close(io);
3311 var buffer: [4096]u8 = undefined;
3312 var fr = file.reader(io, &buffer);
3313 const offset = file_location.offset + @sizeOf(std.coff.ArchiveMemberHeader);
3314 try fr.seekTo(offset);
3315 log.debug("flushInputMember({f}({s}))", .{ path, member.name.toSlice(coff) });
3316 try coff.loadObject(path, member.name.toSlice(coff), &fr, .{
3317 .offset = offset,
3318 .size = file_location.size,
3319 });
3320 },
3321 }
3322}
3323
3324fn flushInputSection(coff: *Coff, isi: Node.InputSection.Index) !void {
3325 const file_loc = isi.fileLocation(coff);
3326 if (file_loc.size == 0) return;
3327 const comp = coff.base.comp;
3328 const io = comp.io;
3329 const gpa = comp.gpa;
3330 const ioi = isi.input(coff);
3331 const path = ioi.path(coff);
3332 const file = try path.root_dir.handle.openFile(io, path.sub_path, .{});
3333 defer file.close(io);
3334 var fr = file.reader(io, &.{});
3335 try fr.seekTo(file_loc.offset);
3336 var nw: MappedFile.Node.Writer = undefined;
3337 const si = isi.symbol(coff);
3338 si.node(coff).writer(&coff.mf, gpa, &nw);
3339 defer nw.deinit();
3340 log.debug("flushInputSection({f}{f}, {s}, {d}, n{d})", .{
3341 path,
3342 fmtMemberNameString(ioi.memberName(coff)),
3343 si.get(coff).section_number.name(coff).toSlice(coff),
3344 si,
3345 si.node(coff),
3346 });
3347 if (try nw.interface.sendFileAll(&fr, .limited(@intCast(file_loc.size))) != file_loc.size)
3348 return error.EndOfStream;
3349 try si.applyLocationRelocs(coff);
13423350}
13433351
1344fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags) !Symbol.Index {
3352fn addSection(coff: *Coff, name: String, flags: std.coff.SectionHeader.Flags) !Symbol.Index {
3353 assert(coff.hasCoffHeader());
3354
13453355 const gpa = coff.base.comp.gpa;
13463356 try coff.nodes.ensureUnusedCapacity(gpa, 1);
1347 try coff.image_section_table.ensureUnusedCapacity(gpa, 1);
1348 try coff.symbol_table.ensureUnusedCapacity(gpa, 1);
3357 try coff.section_table.ensureUnusedCapacity(gpa, 1);
3358 try coff.symbols.ensureUnusedCapacity(gpa, 1);
3359 if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1);
13493360
13503361 const coff_header = coff.headerPtr();
13513362 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
13563367 gpa,
13573368 @sizeOf(std.coff.SectionHeader) * section_table_len,
13583369 );
1359 const ni = try coff.mf.addLastChildNode(gpa, .root, .{
3370
3371 const ni = try coff.mf.addLastChildNode(gpa, coff.sectionParent(), .{
13603372 .alignment = coff.mf.flags.block_size,
13613373 .moved = true,
13623374 .bubbles_moved = false,
13633375 });
3376
13643377 const si = coff.addSymbolAssumeCapacity();
1365 coff.image_section_table.appendAssumeCapacity(si);
3378 coff.section_table.putAssumeCapacity(name, .{
3379 .si = si,
3380 .relocation_table_ni = .none,
3381 });
13663382 coff.nodes.appendAssumeCapacity(.{ .image_section = si });
13673383 const section_table = coff.sectionTableSlice();
1368 const virtual_size = coff.optionalHeaderField(.section_alignment);
1369 const rva: u32 = switch (section_index) {
1370 0 => @intCast(Node.known.header.location(&coff.mf).resolve(&coff.mf)[1]),
1371 else => coff.image_section_table.items[section_index - 1].get(coff).rva +
1372 coff.targetLoad(&section_table[section_index - 1].virtual_size),
1373 };
3384
3385 const virtual_size, const rva = if (coff.isImage()) block: {
3386 const virtual_size = coff.optionalHeaderField(.section_alignment);
3387 const rva: u32 = switch (section_index) {
3388 0 => @intCast(Node.known.header.location(&coff.mf).resolve(&coff.mf)[1]),
3389 else => coff.section_table.values()[section_index - 1].si.get(coff).rva +
3390 coff.targetLoad(&section_table[section_index - 1].virtual_size),
3391 };
3392
3393 break :block .{ virtual_size, rva };
3394 } else .{ 0, 0 };
3395
13743396 {
13753397 const sym = si.get(coff);
13763398 sym.ni = ni;
......@@ -1390,16 +3412,24 @@ fn addSection(coff: *Coff, name: []const u8, flags: std.coff.SectionHeader.Flags
13903412 .number_of_linenumbers = 0,
13913413 .flags = flags,
13923414 };
1393 @memcpy(section.name[0..name.len], name);
1394 @memset(section.name[name.len..], 0);
13953415 if (coff.targetEndian() != native_endian)
13963416 std.mem.byteSwapAllFields(std.coff.SectionHeader, section);
1397 switch (coff.optionalHeaderPtr()) {
1398 inline else => |optional_header| coff.targetStore(
1399 &optional_header.size_of_image,
1400 @intCast(rva + virtual_size),
1401 ),
3417
3418 const name_slice = name.toSlice(coff);
3419 if (coff.isImage()) {
3420 @memcpy(section.name[0..name_slice.len], name_slice);
3421 @memset(section.name[name_slice.len..], 0);
3422 switch (coff.optionalHeaderPtr()) {
3423 inline else => |optional_header| coff.targetStore(
3424 &optional_header.size_of_image,
3425 @intCast(rva + virtual_size),
3426 ),
3427 }
3428 } else {
3429 (try coff.getOrPutSymbolName(name_slice, name)).store(coff, &section.name);
3430 try coff.pendingSymbolTableEntry(si);
14023431 }
3432
14033433 return si;
14043434}
14053435
......@@ -1412,7 +3442,40 @@ const ObjectSectionAttributes = packed struct {
14123442 nocache: bool = false,
14133443 discard: bool = false,
14143444 remove: bool = false,
3445 initialized: bool = false,
3446 uninitialized: bool = false,
3447
3448 pub fn fromFlags(flags: std.coff.SectionHeader.Flags) ObjectSectionAttributes {
3449 return .{
3450 .read = flags.MEM_READ,
3451 .write = flags.MEM_WRITE,
3452 .execute = flags.MEM_EXECUTE,
3453 .shared = flags.MEM_SHARED,
3454 .nopage = flags.MEM_NOT_PAGED,
3455 .nocache = flags.MEM_NOT_CACHED,
3456 .discard = flags.MEM_DISCARDABLE,
3457 .remove = flags.LNK_REMOVE,
3458 .initialized = flags.CNT_INITIALIZED_DATA,
3459 .uninitialized = flags.CNT_UNINITIALIZED_DATA,
3460 };
3461 }
3462
3463 pub fn asFlags(attr: ObjectSectionAttributes) std.coff.SectionHeader.Flags {
3464 return .{
3465 .MEM_READ = attr.read,
3466 .MEM_WRITE = attr.write,
3467 .MEM_EXECUTE = attr.execute,
3468 .MEM_SHARED = attr.shared,
3469 .MEM_NOT_PAGED = attr.nopage,
3470 .MEM_NOT_CACHED = attr.nocache,
3471 .MEM_DISCARDABLE = attr.discard,
3472 .LNK_REMOVE = attr.remove,
3473 .CNT_INITIALIZED_DATA = attr.uninitialized,
3474 .CNT_UNINITIALIZED_DATA = attr.uninitialized,
3475 };
3476 }
14153477};
3478
14163479fn pseudoSectionMapIndex(
14173480 coff: *Coff,
14183481 name: String,
......@@ -1422,15 +3485,25 @@ fn pseudoSectionMapIndex(
14223485 const gpa = coff.base.comp.gpa;
14233486 const pseudo_section_gop = try coff.pseudo_section_table.getOrPut(gpa, name);
14243487 const psmi: Node.PseudoSectionMapIndex = @enumFromInt(pseudo_section_gop.index);
1425 if (!pseudo_section_gop.found_existing) {
1426 const parent: Symbol.Index = if (attributes.execute)
1427 .text
1428 else if (attributes.write)
1429 .data
1430 else
1431 .rdata;
3488 const parent_sn = if (!pseudo_section_gop.found_existing) sn: {
3489 const effective_name = coff.section_merges.get(name) orelse name;
3490 const parent = if (coff.section_table.get(effective_name)) |existing_sec|
3491 existing_sec.si
3492 else if (coff.isImage()) parent: {
3493 const parent: Symbol.Index = if (attributes.uninitialized)
3494 .bss
3495 else if (attributes.execute)
3496 .text
3497 else if (attributes.write)
3498 .data
3499 else
3500 .rdata;
3501
3502 break :parent parent;
3503 } else try coff.addSection(effective_name, attributes.asFlags());
3504
14323505 try coff.nodes.ensureUnusedCapacity(gpa, 1);
1433 try coff.symbol_table.ensureUnusedCapacity(gpa, 1);
3506 try coff.symbols.ensureUnusedCapacity(gpa, 1);
14343507 const ni = try coff.mf.addLastChildNode(gpa, parent.node(coff), .{ .alignment = alignment });
14353508 const si = coff.addSymbolAssumeCapacity();
14363509 pseudo_section_gop.value_ptr.* = si;
......@@ -1441,9 +3514,30 @@ fn pseudoSectionMapIndex(
14413514 assert(sym.loc_relocs == .none);
14423515 sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
14433516 coff.nodes.appendAssumeCapacity(.{ .pseudo_section = psmi });
1444 }
3517 break :sn sym.section_number;
3518 } else pseudo_section_gop.value_ptr.get(coff).section_number;
3519
3520 try coff.verifyParentSectionAttributes(
3521 parent_sn,
3522 name,
3523 .pseudo,
3524 .fromFlags(parent_sn.header(coff).flags),
3525 attributes,
3526 );
3527
14453528 return psmi;
14463529}
3530
3531fn objectSectionParentName(coff: *Coff, name: []const u8) []const u8 {
3532 // In images we want to sort object sections into the final root section name.
3533 // Otherwise, we want to keep the full name so that this sort can occur correctly when
3534 // the object is finally linked into an image.
3535 return if (coff.isImage())
3536 name[0 .. std.mem.indexOfScalar(u8, name, '$') orelse name.len]
3537 else
3538 name;
3539}
3540
14473541fn objectSectionMapIndex(
14483542 coff: *Coff,
14493543 name: String,
......@@ -1451,16 +3545,23 @@ fn objectSectionMapIndex(
14513545 attributes: ObjectSectionAttributes,
14523546) !Node.ObjectSectionMapIndex {
14533547 const gpa = coff.base.comp.gpa;
3548 const name_slice = name.toSlice(coff);
3549 // TODO: Should this be a section merge instead?
3550 const effective_attributes = if (coff.isImage() and std.mem.startsWith(u8, name_slice, ".tls")) attr: {
3551 // In images, the .tls section is a read-only template
3552 var attr = attributes;
3553 attr.write = false;
3554 break :attr attr;
3555 } else attributes;
3556
14543557 const object_section_gop = try coff.object_section_table.getOrPut(gpa, name);
14553558 const osmi: Node.ObjectSectionMapIndex = @enumFromInt(object_section_gop.index);
1456 if (!object_section_gop.found_existing) {
1457 try coff.ensureUnusedStringCapacity(name.toSlice(coff).len);
1458 const name_slice = name.toSlice(coff);
1459 const parent = (try coff.pseudoSectionMapIndex(coff.getOrPutStringAssumeCapacity(
1460 name_slice[0 .. std.mem.indexOfScalar(u8, name_slice, '$') orelse name_slice.len],
1461 ), alignment, attributes)).symbol(coff);
3559 const sym = if (!object_section_gop.found_existing) sym: {
3560 try coff.ensureUnusedStringCapacity(name_slice.len);
3561 const parent_name = coff.getOrPutStringAssumeCapacity(coff.objectSectionParentName(name_slice));
3562 const parent = (try coff.pseudoSectionMapIndex(parent_name, alignment, effective_attributes)).symbol(coff);
14623563 try coff.nodes.ensureUnusedCapacity(gpa, 1);
1463 try coff.symbol_table.ensureUnusedCapacity(gpa, 1);
3564 try coff.symbols.ensureUnusedCapacity(gpa, 1);
14643565 const parent_ni = parent.node(coff);
14653566 var prev_ni: MappedFile.Node.Index = .none;
14663567 var next_it = parent_ni.children(&coff.mf);
......@@ -1492,30 +3593,219 @@ fn objectSectionMapIndex(
14923593 assert(sym.loc_relocs == .none);
14933594 sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
14943595 coff.nodes.appendAssumeCapacity(.{ .object_section = osmi });
3596 break :sym sym;
3597 } else object_section_gop.value_ptr.get(coff);
3598
3599 const parent_ni = sym.ni.parent(&coff.mf);
3600 const parent_alignment = parent_ni.alignment(&coff.mf);
3601 if (alignment.compare(.gt, parent_alignment)) {
3602 log.debug("realignParent({s}, {d}) {d}->{d}", .{ name.toSlice(coff), parent_ni, parent_alignment, alignment });
3603 try parent_ni.realign(&coff.mf, gpa, alignment, .{ .set_alignment = true });
3604 }
3605
3606 const old_alignment = sym.ni.alignment(&coff.mf);
3607 if (alignment.compare(.gt, old_alignment)) {
3608 log.debug("realignObject({s}) {d}->{d}", .{ name.toSlice(coff), old_alignment, alignment });
3609 try sym.ni.realign(&coff.mf, gpa, alignment, .{ .set_alignment = true });
14953610 }
3611
3612 try coff.verifyParentSectionAttributes(
3613 sym.section_number,
3614 name,
3615 .object,
3616 .fromFlags(sym.section_number.header(coff).flags),
3617 effective_attributes,
3618 );
3619
14963620 return osmi;
14973621}
14983622
3623fn verifyParentSectionAttributes(
3624 coff: *Coff,
3625 parent: Symbol.SectionNumber,
3626 child_name: String,
3627 child_kind: enum { pseudo, object },
3628 parent_attrs: ObjectSectionAttributes,
3629 child_attrs: ObjectSectionAttributes,
3630) !void {
3631 if (parent_attrs == child_attrs) return;
3632
3633 const was_merged = switch (child_kind) {
3634 .pseudo => coff.section_merges.contains(child_name),
3635 .object => if (coff.getString(
3636 coff.objectSectionParentName(child_name.toSlice(coff)),
3637 ).unwrap()) |pseudo_name|
3638 coff.section_merges.contains(pseudo_name)
3639 else
3640 false,
3641 };
3642
3643 // The section was intentionally merged by the user or builtin rule
3644 if (was_merged) return;
3645
3646 const BackingT = @typeInfo(ObjectSectionAttributes).@"struct".backing_integer.?;
3647 const num_notes = @popCount(@as(BackingT, @bitCast(parent_attrs)) ^ @as(BackingT, @bitCast(child_attrs)));
3648 var err = try coff.base.comp.link_diags.addErrorWithNotes(num_notes);
3649 try err.addMsg("{t} section '{s}' was placed in parent section '{s}' with mismatched flags", .{
3650 child_kind,
3651 child_name.toSlice(coff),
3652 parent.name(coff).toSlice(coff),
3653 });
3654
3655 inline for (comptime std.meta.fieldNames(ObjectSectionAttributes)) |field| {
3656 if (@field(child_attrs, field) != @field(parent_attrs, field)) {
3657 err.addNote("flags.{s} was {d} in {s}, but {d} in {s}", .{
3658 field,
3659 @intFromBool(@field(child_attrs, field)),
3660 child_name.toSlice(coff),
3661 @intFromBool(@field(parent_attrs, field)),
3662 parent.name(coff).toSlice(coff),
3663 });
3664 }
3665 }
3666
3667 return error.AlreadyReported;
3668}
3669
3670const RelocAddend = union(enum) {
3671 known: i64,
3672 /// Relocs tables in input objects don't include the addend.
3673 /// The value needs to be recovered from the reloc location.
3674 pending: void,
3675};
3676
3677// TODO: There should be an API where the caller can indicate how many contiguous relocs they need
3678// and it should attempt to allocate these from from the free list if available. We can cache
3679// the run length of each segment on Reloc when `free` is set.
14993680pub fn addReloc(
15003681 coff: *Coff,
15013682 loc_si: Symbol.Index,
15023683 offset: u64,
15033684 target_si: Symbol.Index,
1504 addend: i64,
3685 addend: RelocAddend,
3686 @"type": Reloc.Type,
3687) link.Error!void {
3688 const diags = &coff.base.comp.link_diags;
3689 try coff.ensureUnusedRelocCapacity(loc_si, 1);
3690 coff.addRelocAssumeCapacity(loc_si, offset, target_si, addend, @"type") catch |err| switch (err) {
3691 error.MappedFileIo => return diags.fail(
3692 "failed to write output file: {t}",
3693 .{coff.mf.io_err.?},
3694 ),
3695 else => |e| return e,
3696 };
3697}
3698
3699fn ensureUnusedRelocCapacity(coff: *Coff, loc_si: Symbol.Index, len: usize) !void {
3700 const gpa = coff.base.comp.gpa;
3701 try coff.relocs.ensureUnusedCapacity(gpa, len);
3702 if (isImage(coff)) return;
3703 switch (loc_si.get(coff).section_number) {
3704 .UNDEFINED, .ABSOLUTE, .DEBUG => {},
3705 else => |loc_sn| {
3706 const section = loc_sn.section(coff);
3707 if (section.relocation_table_ni == .none)
3708 try coff.nodes.ensureUnusedCapacity(gpa, 1);
3709 },
3710 }
3711}
3712
3713fn addRelocAssumeCapacity(
3714 coff: *Coff,
3715 loc_si: Symbol.Index,
3716 offset: u64,
3717 target_si: Symbol.Index,
3718 addend: RelocAddend,
15053719 @"type": Reloc.Type,
15063720) !void {
15073721 const gpa = coff.base.comp.gpa;
15083722 const target = target_si.get(coff);
3723
15093724 const ri: Reloc.Index = @enumFromInt(coff.relocs.items.len);
1510 (try coff.relocs.addOne(gpa)).* = .{
3725 log.debug("addReloc({d}@{d}+0x{x} -> {d}@{d}+0x{x}{s}) = {d}", .{
3726 loc_si,
3727 loc_si.get(coff).section_number,
3728 offset,
3729 target_si,
3730 target_si.get(coff).section_number,
3731 if (addend == .pending) 0 else addend.known,
3732 if (addend == .pending) "p" else "k",
3733 ri,
3734 });
3735
3736 const sri: Section.RelocationIndex = if (isImage(coff))
3737 .none
3738 else switch (loc_si.get(coff).section_number) {
3739 .UNDEFINED,
3740 .ABSOLUTE,
3741 .DEBUG,
3742 => .none,
3743 else => |loc_sn| sri: {
3744 // The target may not have a node yet, or it could be an extern that will never
3745 // have a node. In that case, flushGlobal will create the symbol table entry.
3746 const existing_sti = target_si.sti(coff);
3747 const sti: SymbolTable.Index = if (existing_sti != .none)
3748 existing_sti
3749 else if (target.ni != .none) sti: {
3750 try coff.pendingSymbolTableEntry(target_si);
3751 break :sti .none;
3752 } else .none;
3753
3754 const sri: Section.RelocationIndex = blk: {
3755 const section = loc_sn.section(coff);
3756 const header = loc_sn.header(coff);
3757 const old_num_relocations = coff.targetLoad(&header.number_of_relocations);
3758 const new_num_relocations = old_num_relocations + 1;
3759 const new_size = @as(u32, new_num_relocations) * std.coff.Relocation.sizeOf();
3760
3761 coff.targetStore(&header.number_of_relocations, new_num_relocations);
3762 if (coff.symbolTableSectionAuxEntryPtr(loc_sn.symbol(coff).sti(coff))) |aux_ptr|
3763 coff.targetStore(&aux_ptr.number_of_relocations, new_num_relocations);
3764
3765 if (section.relocation_table_ni == .none) {
3766 section.relocation_table_ni = try coff.mf.addLastChildNode(
3767 gpa,
3768 coff.sectionParent(),
3769 .{
3770 .size = new_size,
3771 .alignment = .@"2",
3772 .moved = true,
3773 .resized = true,
3774 },
3775 );
3776 coff.nodes.appendAssumeCapacity(.{ .relocation_table = loc_sn });
3777 } else {
3778 try section.relocation_table_ni.resize(&coff.mf, gpa, new_size);
3779 }
3780
3781 // TODO: These need to allocate from a free list, once deleting relocs from the table is supported
3782 break :blk .wrap(old_num_relocations);
3783 };
3784
3785 const entry = sri.entry(coff, loc_sn).?;
3786 if (sti.unwrap()) |index| coff.targetStore(&entry.symbol_table_index, index);
3787
3788 // applyLocationRelocs updates `virtual_address`
3789 // flushSymbolTableIndex updates `symbol_table_index`
3790 coff.targetStore(&entry.type, @bitCast(@"type"));
3791
3792 break :sri sri;
3793 },
3794 };
3795
3796 coff.relocs.addOneAssumeCapacity().* = .{
15113797 .type = @"type",
15123798 .prev = .none,
15133799 .next = target.target_relocs,
15143800 .loc = loc_si,
15153801 .target = target_si,
1516 .unused = 0,
3802 .sri = sri,
15173803 .offset = offset,
1518 .addend = addend,
3804 .addend = if (addend == .pending) 0 else addend.known,
3805 .flags = .{
3806 .recover_addend = addend == .pending,
3807 .free = false,
3808 },
15193809 };
15203810 switch (target.target_relocs) {
15213811 .none => {},
......@@ -1524,15 +3814,1641 @@ pub fn addReloc(
15243814 target.target_relocs = ri;
15253815}
15263816
3817fn failLoadInput(
3818 coff: *Coff,
3819 err: LoadInputError,
3820 fr: *Io.File.Reader,
3821 path: std.Build.Cache.Path,
3822) link.Error {
3823 const diags = &coff.base.comp.link_diags;
3824 switch (err) {
3825 else => |e| return e,
3826 error.MappedFileIo => return diags.fail(
3827 "failed to write output file: {t}",
3828 .{coff.mf.io_err.?},
3829 ),
3830 error.EndOfStream => return diags.failParse(
3831 path,
3832 "unexpected eof",
3833 .{},
3834 ),
3835 error.AccessDenied,
3836 error.Unexpected,
3837 error.Unseekable,
3838 => |e| return diags.fail(
3839 "failed to read \"{f}\": {t}",
3840 .{ path.fmtEscapeString(), e },
3841 ),
3842 error.PermissionDenied,
3843 error.SystemResources,
3844 error.Streaming,
3845 => |e| return diags.fail(
3846 "failed to stat \"{f}\": {t}",
3847 .{ path.fmtEscapeString(), e },
3848 ),
3849 error.ReadFailed => switch (fr.err.?) {
3850 error.Canceled => |e| return e,
3851 else => |e| return diags.fail(
3852 "failed to read \"{f}\": {t}",
3853 .{ path.fmtEscapeString(), e },
3854 ),
3855 },
3856 }
3857}
3858
3859pub fn loadInput(coff: *Coff, input: link.Input) link.Error!void {
3860 const comp = coff.base.comp;
3861 const io = comp.io;
3862
3863 const path = input.path() orelse unreachable;
3864 const gop = try coff.inputs.getOrPut(comp.gpa, path);
3865 if (gop.found_existing) return;
3866 errdefer _ = coff.inputs.swapRemove(path);
3867
3868 var buf: [4096]u8 = undefined;
3869 switch (input) {
3870 .object => |object| {
3871 var fr = object.file.reader(io, &buf);
3872 coff.loadObject(object.path, null, &fr, .{
3873 .offset = fr.logicalPos(),
3874 .size = fr.getSize() catch |err|
3875 return coff.failLoadInput(err, &fr, object.path),
3876 }) catch |err| return coff.failLoadInput(err, &fr, object.path);
3877 },
3878 .archive => |archive| {
3879 var fr = archive.file.reader(io, &buf);
3880 coff.loadArchive(archive.path, &fr) catch |err|
3881 return coff.failLoadInput(err, &fr, archive.path);
3882 },
3883 .res => |res| {
3884 var fr = res.file.reader(io, &buf);
3885 coff.loadRes(res.path, &fr) catch |err|
3886 return coff.failLoadInput(err, &fr, res.path);
3887 },
3888 .dso => |dso| {
3889 var fr = dso.file.reader(io, &buf);
3890 coff.loadDll(dso.path, &fr) catch |err|
3891 return coff.failLoadInput(err, &fr, dso.path);
3892 },
3893 .dso_exact => unreachable,
3894 }
3895}
3896
3897fn fmtMemberNameString(memberName: ?[]const u8) std.fmt.Alt(?[]const u8, memberNameStringEscape) {
3898 return .{ .data = memberName };
3899}
3900
3901fn memberNameStringEscape(memberName: ?[]const u8, w: *std.Io.Writer) std.Io.Writer.Error!void {
3902 try w.print("({f})", .{std.zig.fmtString(memberName orelse return)});
3903}
3904
3905fn inputSectionHeaderNameSlice(
3906 coff: *Coff,
3907 header: *const std.coff.SectionHeader,
3908 string_table: []const u8,
3909 path: std.Build.Cache.Path,
3910 section_i: usize,
3911) ![]const u8 {
3912 const diags = &coff.base.comp.link_diags;
3913 return if (header.name[0] == '/') name: {
3914 const offset_str = std.mem.sliceTo(header.name[1..], 0);
3915 const name_offset = std.fmt.parseUnsigned(u24, offset_str, 10) catch
3916 return diags.failParse(path, "ill-formed section name in section {d}: '{s}'", .{
3917 section_i,
3918 header.name[0 .. offset_str.len + 1],
3919 });
3920
3921 if (name_offset > string_table.len)
3922 return diags.failParse(path, "out-of-bounds section name offset in section {d}: {d}", .{ section_i, name_offset });
3923
3924 break :name std.mem.sliceTo(string_table[name_offset..], 0);
3925 } else std.mem.sliceTo(&header.name, 0);
3926}
3927
3928fn loadObject(
3929 coff: *Coff,
3930 path: std.Build.Cache.Path,
3931 member_name: ?[]const u8,
3932 fr: *Io.File.Reader,
3933 fl: MappedFile.Node.FileLocation,
3934) LoadInputError!void {
3935 const comp = coff.base.comp;
3936 const gpa = comp.gpa;
3937 const diags = &comp.link_diags;
3938 const r = &fr.interface;
3939 const target = &comp.root_mod.resolved_target.result;
3940 const target_endian = coff.targetEndian();
3941 const is_archive = coff.isArchive();
3942 assert(!coff.isObj());
3943 // We want to evaluate new merges as we see them in .drectve sections to avoid redundant work
3944 assert(coff.section_merge_pending_index == coff.section_merges.count());
3945
3946 log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtMemberNameString(member_name) });
3947
3948 const header = try r.peekStruct(std.coff.Header, .little);
3949 if (header.machine != target.toCoffMachine())
3950 return diags.failParse(path, "machine mismatch: expected {t}, found {t}", .{
3951 target.toCoffMachine(),
3952 header.machine,
3953 });
3954 if (header.number_of_sections == 0) return;
3955 if (@sizeOf(std.coff.Header) + @as(usize, header.number_of_sections) * @sizeOf(std.coff.SectionHeader) > fl.size)
3956 return diags.failParse(path, "invalid section table", .{});
3957 const unexpected_header_flags: []const std.meta.FieldEnum(std.coff.Header.Flags) = &.{
3958 .RELOCS_STRIPPED,
3959 .EXECUTABLE_IMAGE,
3960 .AGGRESSIVE_WS_TRIM,
3961 .RESERVED,
3962 .BYTES_REVERSED_LO,
3963 .DLL,
3964 .BYTES_REVERSED_HI,
3965 };
3966 inline for (unexpected_header_flags) |flag|
3967 if (@field(header.flags, @tagName(flag)))
3968 return diags.failParse(path, "unexpected flag set: {t}", .{flag});
3969
3970 if (header.size_of_optional_header != 0)
3971 return diags.failParse(path, "unexpected optional header", .{});
3972
3973 const symbol_table_len = header.number_of_symbols * std.coff.Symbol.sizeOf();
3974 const symbol_table_end = header.pointer_to_symbol_table + symbol_table_len;
3975 // String table length (which includes the length field) immediately trails the symbol table
3976 if (symbol_table_end + @sizeOf(u32) > fl.size)
3977 return diags.failParse(path, "bad symbol table location", .{});
3978
3979 try fr.seekTo(fl.offset + symbol_table_end);
3980 const string_table_len = try r.peekInt(u32, target_endian);
3981 if (string_table_len < @sizeOf(u32) or
3982 symbol_table_end + string_table_len > fl.size)
3983 return diags.failParse(path, "bad string table length: 0x{x}", .{string_table_len});
3984
3985 const ioi: InputObject.Index = @enumFromInt(coff.input_objects.items.len);
3986 try coff.input_objects.ensureUnusedCapacity(gpa, 1);
3987 const input = coff.input_objects.addOneAssumeCapacity();
3988 input.* = .{
3989 .path = path,
3990 .member_name = if (member_name) |m| try gpa.dupe(u8, m) else null,
3991 .source_name = .none,
3992 };
3993
3994 const string_table = string_table: {
3995 const string_table = try gpa.alloc(u8, string_table_len);
3996 errdefer gpa.free(string_table);
3997 try r.readSliceAll(string_table);
3998 break :string_table string_table;
3999 };
4000 defer gpa.free(string_table);
4001
4002 try coff.ensureManyUnusedStringCapacity(
4003 header.number_of_sections + header.number_of_symbols,
4004 header.number_of_sections * 9 +
4005 header.number_of_symbols * 9 +
4006 string_table_len - @sizeOf(u32),
4007 );
4008
4009 const PendingSymbolIndex = enum(u32) {
4010 none,
4011 _,
4012
4013 pub fn wrap(i: ?u32) @This() {
4014 return @enumFromInt((i orelse return .none) + 1);
4015 }
4016
4017 pub fn unwrap(i: @This()) ?u32 {
4018 return switch (i) {
4019 .none => null,
4020 _ => @intFromEnum(i) - 1,
4021 };
4022 }
4023 };
4024
4025 const PendingInputSection = struct {
4026 header: std.coff.SectionHeader,
4027 name: String,
4028 si: Symbol.Index,
4029 parent_si: Symbol.Index,
4030 psi: PendingSymbolIndex,
4031 num_symbols: u32,
4032 comdat: std.coff.ComdatSelection,
4033 comdat_psi: PendingSymbolIndex,
4034 comdat_crc: u32,
4035 comdat_association: Symbol.SectionNumber,
4036 comdat_result: union(enum) {
4037 pending,
4038 // Root of the association chain
4039 pending_association: Symbol.SectionNumber,
4040 include,
4041 skip,
4042 },
4043 };
4044
4045 const sections: []PendingInputSection = if (coff.isImage()) sections: {
4046 const sections = try gpa.alloc(PendingInputSection, header.number_of_sections);
4047 errdefer gpa.free(sections);
4048
4049 try fr.seekTo(fl.offset + @sizeOf(std.coff.Header));
4050 for (sections, 0..) |*section, section_i| {
4051 section.* = .{
4052 .header = try r.takeStruct(std.coff.SectionHeader, target_endian),
4053 .name = undefined,
4054 .si = .null,
4055 .parent_si = .null,
4056 .psi = .none,
4057 .num_symbols = 0,
4058 .comdat = .NONE,
4059 .comdat_psi = .none,
4060 .comdat_crc = 0,
4061 .comdat_association = .UNDEFINED,
4062 .comdat_result = .pending,
4063 };
4064
4065 const section_name_slice = if (section.header.name[0] == '/') name: {
4066 const offset_str = std.mem.sliceTo(section.header.name[1..], 0);
4067 const name_offset = std.fmt.parseUnsigned(u24, offset_str, 10) catch
4068 return diags.failParse(path, "ill-formed section name offset in section {d}: '{s}'", .{
4069 section_i,
4070 section.header.name[0 .. offset_str.len + 1],
4071 });
4072
4073 if (name_offset > string_table.len)
4074 return diags.failParse(
4075 path,
4076 "out-of-bounds section name offset in section {d}: {d}",
4077 .{ section_i, name_offset },
4078 );
4079
4080 break :name std.mem.sliceTo(string_table[name_offset..], 0);
4081 } else std.mem.sliceTo(&section.header.name, 0);
4082 section.name = coff.getOrPutStringAssumeCapacity(section_name_slice);
4083
4084 if (section.header.pointer_to_linenumbers +
4085 @as(u32, section.header.number_of_linenumbers) * std.coff.LineNumber.sizeOf() > fl.size)
4086 return diags.failParse(path, "bad line numbers location in section {d} `{s}`", .{
4087 section_i,
4088 section_name_slice,
4089 });
4090
4091 if (section.header.pointer_to_relocations +
4092 @as(u32, section.header.number_of_relocations) * std.coff.Relocation.sizeOf() > fl.size)
4093 return diags.failParse(path, "bad relocations location in section {d} `{s}`", .{
4094 section_i,
4095 section_name_slice,
4096 });
4097
4098 if (section.header.pointer_to_raw_data + section.header.size_of_raw_data > fl.size)
4099 return diags.failParse(path, "bad raw data location in section {d} `{s}`", .{
4100 section_i,
4101 section_name_slice,
4102 });
4103 }
4104
4105 break :sections sections;
4106 } else &.{};
4107 defer gpa.free(sections);
4108
4109 const mi = if (is_archive) mi: {
4110 try coff.nodes.ensureUnusedCapacity(gpa, 2);
4111 try coff.members.ensureUnusedCapacity(gpa, 1);
4112 const path_str = try path.toString(gpa);
4113 defer gpa.free(path_str);
4114
4115 const mi = try coff.addMemberAssumeCapacity(.coff, fl.size);
4116 const member = mi.get(coff);
4117 try member.initHeader(coff, path_str, header.time_date_stamp);
4118
4119 {
4120 // TODO: This should be deferred to an idle task (but resize it here!)
4121 var nw: MappedFile.Node.Writer = undefined;
4122 member.content_ni.writer(&coff.mf, gpa, &nw);
4123 defer nw.deinit();
4124
4125 try fr.seekTo(fl.offset);
4126 const written = nw.interface.sendFileAll(fr, .limited64(fl.size)) catch |err| switch (err) {
4127 error.WriteFailed => return nw.err.?,
4128 else => |e| return e,
4129 };
4130
4131 if (written != fl.size) return error.EndOfStream;
4132 }
4133
4134 break :mi mi;
4135 } else undefined;
4136
4137 try fr.seekTo(fl.offset + header.pointer_to_symbol_table);
4138 const symbol_size = std.coff.Symbol.sizeOf();
4139
4140 const PendingSymbol = struct {
4141 name: String,
4142 value: union(enum) {
4143 // Size of the section
4144 section: u32,
4145 // If section is absolute, the symbol value.
4146 // Otherwise, offset within the section.
4147 static: u32,
4148 // If section is undefined, the symbol size.
4149 // If section is absolute, the symbol value.
4150 // Otherwise offset within the section.
4151 external: u32,
4152 // The index of the target symbol of this weak external
4153 weak_external: u32,
4154 // Trails .weak_external
4155 weak_external_aux: WeakExternalStrat,
4156 },
4157 section_number: Symbol.SectionNumber,
4158 si: Symbol.Index,
4159 // If a weak external targets this symbol, the index of the weak external
4160 weak_external_psi: PendingSymbolIndex,
4161 };
4162
4163 var num_global_symbols: u32 = 0;
4164 var pending_symbols: std.array_hash_map.Auto(u32, PendingSymbol) = .empty;
4165 defer pending_symbols.deinit(gpa);
4166 if (!is_archive)
4167 try pending_symbols.ensureUnusedCapacity(gpa, header.number_of_symbols);
4168
4169 var section_merges: std.ArrayList(struct {
4170 from: String,
4171 to: String,
4172 }) = .empty;
4173 defer section_merges.deinit(gpa);
4174
4175 // Discover symbol names and COMDAT symbol mappings
4176 var symbol_i: u32 = 0;
4177 var num_included_symbols: u32 = 0;
4178 while (symbol_i < header.number_of_symbols) {
4179 var symbol: std.coff.Symbol = undefined;
4180 @memcpy(std.mem.asBytes(&symbol)[0..symbol_size], try r.take(symbol_size));
4181 if (target_endian != native_endian)
4182 std.mem.byteSwapAllFields(std.coff.Symbol, &symbol);
4183
4184 const aux_symbols = if (symbol.number_of_aux_symbols > 0)
4185 try r.take(symbol_size * symbol.number_of_aux_symbols)
4186 else
4187 &.{};
4188 defer symbol_i += symbol.number_of_aux_symbols + 1;
4189
4190 const name = std.mem.sliceTo(if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: {
4191 const index = std.mem.readInt(u32, symbol.name[4..], target_endian);
4192 if (index >= string_table.len)
4193 return diags.failParse(path, "bad string offset for symbol 0x{x}", .{symbol_i});
4194 break :name string_table[index..];
4195 } else &symbol.name, 0);
4196
4197 if (is_archive) {
4198 if (switch (symbol.storage_class) {
4199 .WEAK_EXTERNAL => true,
4200 .EXTERNAL => symbol.section_number != .UNDEFINED,
4201 else => false,
4202 }) try coff.ensureMemberSymbol(mi, coff.getOrPutStringAssumeCapacity(name));
4203
4204 continue;
4205 }
4206
4207 switch (symbol.section_number) {
4208 .UNDEFINED, .DEBUG, .ABSOLUTE => {},
4209 else => |sn| if (@intFromEnum(sn) > sections.len)
4210 return diags.failParse(path, "out-of-bounds section number {d} in symbol 0x{x}", .{ sn, symbol_i }),
4211 }
4212
4213 const psi: PendingSymbolIndex = .wrap(@intCast(pending_symbols.count()));
4214 const section_number: Symbol.SectionNumber = @enumFromInt(@intFromEnum(symbol.section_number));
4215
4216 const values: []const @FieldType(PendingSymbol, "value") = pending_symbols: switch (symbol.storage_class) {
4217 .STATIC, .LABEL => |storage_class| switch (section_number) {
4218 // TODO: Do we need to do anything with @feat.00?
4219 // https://llvm.org/doxygen/namespacellvm_1_1COFF.html#aeffa16735e18df727a173beaf748c392
4220 .UNDEFINED,
4221 .DEBUG,
4222 => &.{},
4223 .ABSOLUTE => &.{.{ .static = symbol.value }},
4224 else => |sn| {
4225 const section = &sections[sn.toIndex()];
4226
4227 // Section symbol
4228 const is_section = storage_class == .STATIC and
4229 symbol.value == 0 and
4230 symbol.type == std.coff.SymType{
4231 .complex_type = .NULL,
4232 .base_type = .NULL,
4233 } and
4234 symbol.number_of_aux_symbols > 0;
4235
4236 if (is_section) {
4237 if (symbol.number_of_aux_symbols > 1)
4238 return diags.failParse(path, "invalid number of aux symbols for section symbol 0x{x}: {d}", .{
4239 symbol_i,
4240 symbol.number_of_aux_symbols,
4241 });
4242
4243 var section_def: std.coff.SectionDefinition = undefined;
4244 @memcpy(std.mem.asBytes(&section_def)[0..symbol_size], aux_symbols[0..symbol_size]);
4245 if (target_endian != native_endian)
4246 std.mem.byteSwapAllFields(std.coff.SectionDefinition, &section_def);
4247
4248 if (section_def.number_of_relocations != section.header.number_of_relocations)
4249 return diags.failParse(
4250 path,
4251 "section aux symbol 0x{x} for '{s}' relocation count did not match section header: {d} vs {d}",
4252 .{ symbol_i + 1, name, section_def.number_of_relocations, section.header.number_of_relocations },
4253 );
4254
4255 if (section_def.number_of_linenumbers != section.header.number_of_linenumbers)
4256 return diags.failParse(
4257 path,
4258 "section aux symbol 0x{x} for '{s}' line number count did not match section header: {d} vs {d}",
4259 .{ symbol_i + 1, name, section_def.number_of_linenumbers, section.header.number_of_linenumbers },
4260 );
4261
4262 if (section.header.flags.LNK_COMDAT) {
4263 if (section_def.selection == .ASSOCIATIVE) {
4264 if (section_def.number == 0 or section_def.number > sections.len)
4265 return diags.failParse(
4266 path,
4267 "section aux symbol 0x{x} for '{s}' contained an invalid associated section number: 0x{x}",
4268 .{ symbol_i + 1, name, section_def.number },
4269 );
4270
4271 section.comdat_association = @enumFromInt(section_def.number);
4272 }
4273
4274 section.comdat = section_def.selection;
4275 section.comdat_crc = section_def.checksum;
4276 }
4277
4278 section.psi = psi;
4279 }
4280
4281 break :pending_symbols &.{if (is_section)
4282 .{ .section = section.header.size_of_raw_data }
4283 else
4284 .{ .static = symbol.value }};
4285 },
4286 },
4287 .WEAK_EXTERNAL => switch (symbol.section_number) {
4288 .UNDEFINED => {
4289 if (symbol.value != 0)
4290 return diags.failParse(
4291 path,
4292 "invalid value {d} for weak external symbol 0x{x}",
4293 .{ symbol.value, symbol_i },
4294 );
4295
4296 var weak_external: std.coff.WeakExternalDefinition = undefined;
4297 @memcpy(std.mem.asBytes(&weak_external)[0..symbol_size], aux_symbols[0..symbol_size]);
4298 if (target_endian != native_endian)
4299 std.mem.byteSwapAllFields(std.coff.WeakExternalDefinition, &weak_external);
4300
4301 if (weak_external.tag_index >= header.number_of_symbols)
4302 return diags.failParse(
4303 path,
4304 "invalid tag_index 0x{x} for weak external symbol 0x{x}",
4305 .{ weak_external.tag_index, symbol_i },
4306 );
4307
4308 break :pending_symbols switch (weak_external.flag) {
4309 else => |flag| &.{
4310 .{ .weak_external = weak_external.tag_index },
4311 .{ .weak_external_aux = WeakExternalStrat.fromFlag(flag) },
4312 },
4313 _ => return diags.failParse(
4314 path,
4315 "encountered unknown weak external characteristic 0x{x} for symbol 0x{x}",
4316 .{ weak_external.flag, symbol_i },
4317 ),
4318 };
4319 },
4320 else => |sn| return diags.failParse(
4321 path,
4322 "invalid section number {d} for weak external symbol 0x{x}",
4323 .{ sn, symbol_i },
4324 ),
4325 },
4326 .EXTERNAL => switch (section_number) {
4327 .UNDEFINED,
4328 .ABSOLUTE,
4329 => &.{.{ .external = symbol.value }},
4330 .DEBUG => return diags.failParse(
4331 path,
4332 "unexpected external symbol 0x{x} in DEBUG section: '{s}'",
4333 .{ symbol_i, name },
4334 ),
4335 else => &.{.{ .external = symbol.value }},
4336 },
4337 .FILE => {
4338 if (!std.mem.eql(u8, name, ".file"))
4339 return diags.failParse(
4340 path,
4341 "unexpected symbol name '{s}' for file symbol 0x{x}",
4342 .{ name, symbol_i },
4343 );
4344
4345 var file: std.coff.FileDefinition = undefined;
4346 @memcpy(std.mem.asBytes(&file)[0..symbol_size], aux_symbols[0..symbol_size]);
4347
4348 input.source_name = (try coff.getOrPutString(file.getFileName())).toOptional();
4349 break :pending_symbols &.{};
4350 },
4351 else => |storage_class| return diags.failParse(
4352 path,
4353 "TODO handle storage class {t} for symbol 0x{x}",
4354 .{ storage_class, symbol_i },
4355 ),
4356 };
4357
4358 for (values, 0..) |value, i| {
4359 if (section_number == .ABSOLUTE)
4360 num_included_symbols += 1;
4361
4362 switch (value) {
4363 .section => {},
4364 .static,
4365 .external,
4366 .weak_external,
4367 => {
4368 num_global_symbols += 1;
4369 if (section_number.hasIndex()) {
4370 const section = &sections[section_number.toIndex()];
4371 section.num_symbols += 1;
4372 if (section.header.flags.LNK_COMDAT and section.comdat_psi == .none)
4373 section.comdat_psi = psi;
4374 }
4375 },
4376 .weak_external_aux => {},
4377 }
4378
4379 const symbol_name = coff.getOrPutStringAssumeCapacity(name);
4380 pending_symbols.putAssumeCapacity(symbol_i + @as(u32, @intCast(i)), .{
4381 .name = symbol_name,
4382 .value = value,
4383 .section_number = section_number,
4384 .si = .null,
4385 .weak_external_psi = .none,
4386 });
4387 }
4388 }
4389
4390 try coff.globals.ensureUnusedCapacity(gpa, num_global_symbols);
4391 for (sections) |*section| {
4392 if (section.header.flags.LNK_INFO) {
4393 if (std.mem.eql(u8, &section.header.name, ".drectve")) {
4394 try fr.seekTo(fl.offset + section.header.pointer_to_raw_data);
4395 // TODO: Don't really want an additional buffer here, but want to limit to size_of_raw_data
4396 var buf: [128]u8 = undefined;
4397 var section_r = r.limited(.limited(section.header.size_of_raw_data), &buf);
4398 while (section_r.interface.takeDelimiter(' ') catch |err| switch (err) {
4399 error.StreamTooLong => return diags.failParse(path, "unexpectedly long .drectve argument", .{}),
4400 else => |e| return e,
4401 }) |arg| {
4402 // Microsoft tools emit 3 space characters into this section even with /Zl
4403 if (arg.len == 0) continue;
4404
4405 if (std.ascii.startsWithIgnoreCase(arg, "-exclude-symbols:")) {
4406 // TODO: When implementing mingw auto-exports (if at all?), track this to not export this symbol
4407 } else if (std.ascii.startsWithIgnoreCase(arg, "/include:")) {
4408 _ = try coff.globalSymbol(.{ .name = arg["/include:".len..] });
4409 } else if (std.ascii.startsWithIgnoreCase(arg, "/alternatename:")) {
4410 var split = std.mem.splitScalar(u8, arg["/alternatename:".len..], '=');
4411 const orig = split.first();
4412 const alt = split.next() orelse
4413 return diags.failParse(path, "malformed .drectve argument: '{s}'", .{arg});
4414
4415 try coff.ensureManyUnusedStringCapacity(2, orig.len + alt.len + 2);
4416 const orig_str = coff.getOrPutStringAssumeCapacity(orig);
4417 const alt_str = coff.getOrPutStringAssumeCapacity(alt);
4418 const gop = try coff.alternate_names.getOrPut(gpa, orig_str);
4419 if (!gop.found_existing) {
4420 log.debug("alternateName({s}={s})", .{ orig, alt });
4421 gop.value_ptr.* = alt_str;
4422 } else if (gop.value_ptr.* != alt_str)
4423 return diags.failParse(
4424 path,
4425 "conflicting /alternatename .drectve arguments: first seen as {s}={s}, now seen as {s}={s}",
4426 .{ orig, gop.value_ptr.toSlice(coff), orig, alt },
4427 );
4428 } else if (std.ascii.startsWithIgnoreCase(arg, "/guardsym:")) {
4429 // TODO: https://learn.microsoft.com/en-us/windows/win32/secbp/pe-metadata
4430 } else if (std.ascii.startsWithIgnoreCase(arg, "/merge:")) merge: {
4431 var split = std.mem.splitScalar(u8, arg["/merge:".len..], '=');
4432 const from = split.first();
4433 const to = split.next() orelse
4434 return diags.failParse(path, "malformed .drectve argument: '{s}'", .{arg});
4435 if (to.len > header_name_max_len)
4436 return diags.failParse(
4437 path,
4438 "/merge .drectve target exceeds max length of {d}: '{s}'",
4439 .{ header_name_max_len, arg },
4440 );
4441 if (std.mem.eql(u8, from, to)) break :merge;
4442
4443 try coff.ensureManyUnusedStringCapacity(2, from.len + to.len + 2);
4444 const from_str = coff.getOrPutStringAssumeCapacity(from);
4445 const to_str = coff.getOrPutStringAssumeCapacity(to);
4446
4447 {
4448 var iter = to_str;
4449 while (coff.section_merges.get(iter)) |next_to| {
4450 if (next_to == from_str)
4451 return diags.failParse(
4452 path,
4453 "/merge .drectve argument would create a cycle: {s}={s} leads to {s}={s}",
4454 .{ from, to, iter.toSlice(coff), to },
4455 );
4456
4457 iter = next_to;
4458 }
4459 }
4460
4461 try coff.section_merges.ensureUnusedCapacity(gpa, 1);
4462 const gop = coff.section_merges.getOrPutAssumeCapacity(from_str);
4463 if (!gop.found_existing) {
4464 coff.synth_prog_node.increaseEstimatedTotalItems(1);
4465 gop.value_ptr.* = to_str;
4466 } else if (gop.value_ptr.* != to_str)
4467 return diags.failParse(
4468 path,
4469 "conflicting /merge .drectve arguments: first seen as {s}={s}, now seen as {s}={s}",
4470 .{ from, gop.value_ptr.toSlice(coff), from, to },
4471 );
4472 } else if (std.ascii.startsWithIgnoreCase(arg, "/disallowlib:")) {
4473 const lib_name = arg["/disallowlib:".len..];
4474 // TODO: Track these and issue error in prelink if any match
4475 _ = lib_name;
4476 } else if (std.ascii.startsWithIgnoreCase(arg, "/defaultlib:")) {
4477 const lib_path = arg["/defaultlib:".len..];
4478 const trim = std.mem.trim(u8, lib_path, "\"");
4479 if (lib_path.len == trim.len or lib_path.len - 2 == trim.len) {
4480 if (!comp.config.link_libc or comp.libc_installation == null)
4481 return diags.failParse(path, "encountered /DEFAULTLIB .drectve argument when libc was not available: {s}", .{arg});
4482
4483 (try coff.pending_default_libs.addOne(gpa)).* = .{
4484 .path = try gpa.dupe(u8, lib_path),
4485 .ioi = ioi,
4486 };
4487 } else return diags.failParse(
4488 path,
4489 "malformed /DEFAULTLIB .drectve argument: `{s}`",
4490 .{arg},
4491 );
4492 } else return diags.failParse(path, "unsupported argument in .drectve section: `{s}`", .{arg});
4493 }
4494 }
4495
4496 section.comdat_result = .skip;
4497 continue;
4498 }
4499
4500 if (section.header.flags.LNK_REMOVE or
4501 section.header.flags.MEM_DISCARDABLE)
4502 {
4503 // TODO: Convert .debug$* sections into PDB
4504 section.comdat_result = .skip;
4505 continue;
4506 }
4507
4508 section.comdat_result = comdat: switch (section.comdat) {
4509 .NONE => .include,
4510 .ASSOCIATIVE => {
4511 // Associative COMDAT sections have no COMDAT symbol.
4512 // They are linked if the assocated section is linked.
4513 var iter = section;
4514 var iter_sn = iter.comdat_association;
4515 while (iter.comdat == .ASSOCIATIVE) {
4516 iter = &sections[iter_sn.toIndex()];
4517 iter_sn = iter.comdat_association;
4518 if (iter == section)
4519 return diags.failParse(
4520 path,
4521 "circular COMDAT association loop detected, starting at symbol 0x{x}",
4522 .{pending_symbols.keys()[section.psi.unwrap().?]},
4523 );
4524 }
4525
4526 assert(iter != section);
4527 break :comdat switch (iter.comdat_result) {
4528 .pending => .{ .pending_association = iter_sn },
4529 else => |iter_result| iter_result,
4530 };
4531 },
4532 else => |comdat| {
4533 const psi = section.comdat_psi.unwrap() orelse section.psi.unwrap().?;
4534 const symbol = &pending_symbols.values()[psi];
4535 const si = existing: switch (symbol.value) {
4536 .weak_external => unreachable,
4537 .weak_external_aux => unreachable,
4538 .static => break :comdat .include,
4539 .section => {
4540 assert(section.comdat_psi == .none);
4541 if (coff.object_section_table.get(section.name)) |si|
4542 break :existing si
4543 else if (coff.pseudo_section_table.get(section.name)) |si|
4544 break :existing si
4545 else if (coff.section_table.get(section.name)) |s|
4546 break :existing s.si
4547 else
4548 break :comdat .include;
4549 },
4550 .external => {
4551 const global_gop = try coff.getOrPutGlobalSymbol(.{
4552 .name = symbol.name.toSlice(coff),
4553 });
4554
4555 // TODO: What if the same symbol is incorrectly defined twice in this obj?
4556 // Would need to mark this global as pending, or notice it later when .ni != none
4557 if (!global_gop.found_existing or global_gop.value_ptr.si.get(coff).ni == .none) {
4558 symbol.si = global_gop.value_ptr.si;
4559 break :comdat .include;
4560 }
4561
4562 break :existing global_gop.value_ptr.si;
4563 },
4564 };
4565
4566 const index = pending_symbols.keys()[psi];
4567 switch (comdat) {
4568 .NODUPLICATES => return coff.failMultipleDefinitions(
4569 path,
4570 member_name,
4571 symbol.name,
4572 index,
4573 si,
4574 .duplicate,
4575 ),
4576 .ANY => {
4577 symbol.si = si;
4578 break :comdat .skip;
4579 },
4580 .SAME_SIZE => {
4581 // TODO: Verify that this node isn't resized after creation
4582 _, const size = si.get(coff).ni.location(&coff.mf).resolve(&coff.mf);
4583 if (size == section.header.size_of_raw_data) {
4584 symbol.si = si;
4585 break :comdat .skip;
4586 }
4587
4588 return coff.failMultipleDefinitions(
4589 path,
4590 member_name,
4591 symbol.name,
4592 index,
4593 si,
4594 .{ .size = .{ .a = size, .b = section.header.size_of_raw_data } },
4595 );
4596 },
4597 .EXACT_MATCH => {
4598 const sym = si.get(coff);
4599 const existing_crc = switch (coff.getNode(sym.ni)) {
4600 .input_section => |isi| isi.inputSection(coff).crc,
4601 else => std.hash.crc.Crc32Jamcrc.hash(sym.ni.sliceConst(&coff.mf)),
4602 };
4603
4604 if (existing_crc == section.comdat_crc) {
4605 symbol.si = si;
4606 break :comdat .skip;
4607 }
4608
4609 return coff.failMultipleDefinitions(
4610 path,
4611 member_name,
4612 symbol.name,
4613 index,
4614 si,
4615 .{ .crc = .{ .a = existing_crc, .b = section.comdat_crc } },
4616 );
4617 },
4618 .LARGEST => {
4619 // TODO: Resize existing .ni and replace with this section's contents
4620 // TODO: This will be tricky, what to do about existing InputSection?
4621 unreachable;
4622 },
4623 .NONE, .ASSOCIATIVE, _ => unreachable,
4624 }
4625 },
4626 };
4627 }
4628
4629 try coff.flushSectionMerges();
4630
4631 // Resolve pending associations, create parent sections
4632 var num_included_sections: u16 = 0;
4633 var num_included_relocs: u32 = 0;
4634 for (sections) |*section| {
4635 comdat: switch (section.comdat_result) {
4636 .pending_association => |root_assoc_sn| {
4637 const root_result = sections[root_assoc_sn.toIndex()].comdat_result;
4638 assert(root_result != .pending_association);
4639 section.comdat_result = root_result;
4640 continue :comdat root_result;
4641 },
4642 .include => {},
4643 .skip => {
4644 assert(switch (section.comdat) {
4645 .NONE, .ASSOCIATIVE => true,
4646 else => if (section.comdat_psi.unwrap()) |psi|
4647 pending_symbols.values()[psi].si != .null
4648 else
4649 pending_symbols.values()[section.psi.unwrap().?].si != .null,
4650 });
4651 continue;
4652 },
4653 .pending => unreachable,
4654 }
4655
4656 // Until we support sorting .pdata, we shouldn't merge these in, the result would be invalid
4657 const section_name = section.name.toSlice(coff);
4658 if (std.mem.startsWith(u8, section_name, ".pdata"))
4659 continue;
4660
4661 num_included_sections += 1;
4662 num_included_symbols += section.num_symbols;
4663 num_included_relocs += section.header.number_of_relocations;
4664
4665 section.parent_si = (try coff.objectSectionMapIndex(
4666 section.name,
4667 section.header.flags.ALIGN.alignment() orelse .@"1",
4668 .fromFlags(section.header.flags),
4669 )).symbol(coff);
4670 }
4671
4672 try coff.nodes.ensureUnusedCapacity(gpa, num_included_sections);
4673 try coff.relocs.ensureUnusedCapacity(gpa, num_included_relocs);
4674 try coff.symbols.ensureUnusedCapacity(gpa, num_included_symbols + num_included_sections);
4675 try coff.input_sections.ensureUnusedCapacity(gpa, num_included_sections);
4676
4677 for (sections) |*section| {
4678 if (section.parent_si == .null) continue;
4679
4680 const ni = try coff.mf.addLastChildNode(gpa, section.parent_si.node(coff), .{
4681 .size = section.header.size_of_raw_data,
4682 .alignment = section.header.flags.ALIGN.alignment() orelse .@"1",
4683 .moved = true,
4684 });
4685 coff.nodes.appendAssumeCapacity(.{ .input_section = @enumFromInt(coff.input_sections.items.len) });
4686
4687 section.si = coff.addSymbolAssumeCapacity();
4688 if (section.psi.unwrap()) |psi|
4689 pending_symbols.values()[psi].si = section.si;
4690
4691 const sym = section.si.get(coff);
4692 sym.ni = ni;
4693 sym.section_number = section.parent_si.get(coff).section_number;
4694
4695 coff.input_sections.addOneAssumeCapacity().* = .{
4696 .ioi = ioi,
4697 .si = section.si,
4698 .file_location = .{
4699 .offset = fl.offset + section.header.pointer_to_raw_data,
4700 .size = section.header.size_of_raw_data,
4701 },
4702 .first_li = @enumFromInt(coff.input_symbols.items.len),
4703 .crc = section.comdat_crc,
4704 .comdat_si = if (section.comdat_psi.unwrap()) |psi|
4705 pending_symbols.values()[psi].si
4706 else
4707 .null,
4708 };
4709
4710 log.debug(
4711 "addInputSection({s}, 0x{x}) = {d}@{d}",
4712 .{ section.name.toSlice(coff), section.comdat_crc, section.si, sym.section_number },
4713 );
4714 coff.synth_prog_node.increaseEstimatedTotalItems(1);
4715 }
4716
4717 for (pending_symbols.values(), pending_symbols.keys(), 0..) |*symbol, index, i| {
4718 switch (symbol.value) {
4719 .weak_external_aux => continue,
4720 else => {},
4721 }
4722
4723 defer log.debug("addInputSymbol({s}, 0x{x}@{d}, {t}=0x{x}) = n{d} {d}@{d}", .{
4724 symbol.name.toSlice(coff),
4725 index,
4726 symbol.section_number,
4727 symbol.value,
4728 switch (symbol.value) {
4729 .weak_external_aux => unreachable,
4730 inline else => |v| v,
4731 },
4732 symbol.si.get(coff).ni,
4733 symbol.si,
4734 symbol.si.get(coff).section_number,
4735 });
4736
4737 const section = switch (symbol.section_number) {
4738 .UNDEFINED => switch (symbol.value) {
4739 .section,
4740 .static,
4741 .weak_external_aux,
4742 => unreachable,
4743 .external => {
4744 if (symbol.weak_external_psi.unwrap()) |weak_external_i| {
4745 // If the alias itself is an undef external, we need to wait until flushing the weak
4746 // external global before creating a global for the alias, as another input could
4747 // still provide the weak external.
4748 const weak_sym = pending_symbols.values()[weak_external_i].si.get(coff);
4749 weak_sym.setValue(.{ .weak_alias_name = symbol.name });
4750 weak_sym.flags.weak_external_strat = pending_symbols.values()[weak_external_i + 1].value.weak_external_aux;
4751 }
4752
4753 // Deferred until referenced by a reloc in this object.
4754 // vcruntime.lib defines symbols like this (ie. memcpy_$fo$) that are not referenced
4755 continue;
4756 },
4757 .weak_external => |alias_index| {
4758 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) });
4759 symbol.si = global_gop.value_ptr.si;
4760 if (!global_gop.found_existing or symbol.si.get(coff).ni == .none) {
4761 const sym = symbol.si.get(coff);
4762 const alias = pending_symbols.getPtr(alias_index) orelse
4763 return diags.failParse(
4764 path,
4765 "weak external 0x{x} {s}{f} targets unknown symbol index 0x{x}",
4766 .{
4767 index,
4768 symbol.name.toSlice(coff),
4769 fmtMemberNameString(member_name),
4770 alias_index,
4771 },
4772 );
4773
4774 if (alias.si == .null and alias_index > index) {
4775 // Resolve this once we see alias
4776 alias.weak_external_psi = .wrap(@intCast(i));
4777 } else {
4778 sym.setValue(if (alias.si.unwrap()) |alias_si| .{
4779 .weak_alias_si = alias_si,
4780 } else .{
4781 .weak_alias_name = alias.name,
4782 });
4783 sym.flags.weak_external_strat = pending_symbols.values()[i + 1].value.weak_external_aux;
4784 }
4785 }
4786
4787 continue;
4788 },
4789 },
4790 .ABSOLUTE => {
4791 const value = sym: switch (symbol.value) {
4792 .static => |value| {
4793 symbol.si = coff.addSymbolAssumeCapacity();
4794 break :sym value;
4795 },
4796 .external => |value| {
4797 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) });
4798 symbol.si = global_gop.value_ptr.si;
4799 if (global_gop.found_existing)
4800 return coff.failMultipleDefinitions(
4801 path,
4802 member_name,
4803 symbol.name,
4804 index,
4805 global_gop.value_ptr.si,
4806 .none,
4807 );
4808 break :sym value;
4809 },
4810 else => unreachable,
4811 };
4812
4813 const sym = symbol.si.get(coff);
4814 sym.rva = value;
4815 sym.section_number = .ABSOLUTE;
4816 continue;
4817 },
4818 .DEBUG => continue,
4819 else => |sn| &sections[sn.toIndex()],
4820 };
4821
4822 if (section.si == .null)
4823 continue;
4824
4825 if (symbol.si == .null) {
4826 switch (symbol.value) {
4827 .section => unreachable,
4828 .static => {
4829 symbol.si = coff.addSymbolAssumeCapacity();
4830 },
4831 .external => {
4832 assert(index != section.comdat_psi.unwrap());
4833 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) });
4834 symbol.si = global_gop.value_ptr.si;
4835
4836 const sym = symbol.si.get(coff);
4837 if (global_gop.found_existing and sym.ni != .none)
4838 return coff.failMultipleDefinitions(
4839 path,
4840 member_name,
4841 symbol.name,
4842 index,
4843 global_gop.value_ptr.si,
4844 .none,
4845 );
4846 },
4847 .weak_external,
4848 .weak_external_aux,
4849 => unreachable,
4850 }
4851
4852 if (section.comdat_psi.unwrap() == @as(u32, @intCast(i)))
4853 coff.getNode(section.si.get(coff).ni).input_section.inputSection(coff).comdat_si = symbol.si;
4854 }
4855
4856 if (symbol.weak_external_psi.unwrap()) |weak_external_i| {
4857 assert(symbol.si != .null);
4858 const weak_sym = pending_symbols.values()[weak_external_i].si.get(coff);
4859 weak_sym.setValue(.{ .weak_alias_si = symbol.si });
4860 weak_sym.flags.weak_external_strat = pending_symbols.values()[weak_external_i + 1].value.weak_external_aux;
4861 }
4862
4863 if (section.si != symbol.si) {
4864 const sym = symbol.si.get(coff);
4865 assert(sym.ni == .none);
4866 sym.ni = section.si.get(coff).ni;
4867 switch (symbol.value) {
4868 .section => |v| sym.setExtra(.{ .size = v }),
4869 .static => |v| sym.setValue(.{ .node_offset = v }),
4870 .external => |v| switch (symbol.section_number) {
4871 .UNDEFINED, .ABSOLUTE, .DEBUG => unreachable,
4872 else => sym.setValue(.{ .node_offset = v }),
4873 },
4874 .weak_external,
4875 .weak_external_aux,
4876 => unreachable,
4877 }
4878
4879 sym.section_number = section.si.get(coff).section_number;
4880 }
4881 }
4882
4883 const relocation_size = std.coff.Relocation.sizeOf();
4884 for (sections) |section| {
4885 if (section.si == .null) continue;
4886
4887 const loc_sym = section.si.get(coff);
4888 assert(loc_sym.loc_relocs == .none);
4889 loc_sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
4890
4891 if (section.header.number_of_relocations == 0) continue;
4892
4893 try fr.seekTo(fl.offset + section.header.pointer_to_relocations);
4894 for (0..section.header.number_of_relocations) |reloc_i| {
4895 var reloc: std.coff.Relocation = undefined;
4896 @memcpy(std.mem.asBytes(&reloc)[0..relocation_size], try r.take(relocation_size));
4897 if (target_endian != native_endian)
4898 std.mem.byteSwapAllFields(std.coff.Relocation, &reloc);
4899
4900 const symbol = pending_symbols.getPtr(reloc.symbol_table_index) orelse
4901 return diags.failParse(
4902 path,
4903 "relocation 0x{x} in section '{s}' of {f}{f} targets invalid symbol index 0x{x}",
4904 .{
4905 reloc_i,
4906 section.name.toSlice(coff),
4907 path.fmtEscapeString(),
4908 fmtMemberNameString(member_name),
4909 reloc.symbol_table_index,
4910 },
4911 );
4912
4913 if (symbol.si == .null) {
4914 assert(symbol.section_number == .UNDEFINED);
4915 switch (symbol.value) {
4916 .external => |size| {
4917 const global_gop = try coff.getOrPutGlobalSymbol(.{ .name = symbol.name.toSlice(coff) });
4918 symbol.si = global_gop.value_ptr.si;
4919 if (!global_gop.found_existing or symbol.si.get(coff).ni == .none) {
4920 const sym = symbol.si.get(coff);
4921 sym.setExtra(.{ .size = @max(sym.size(), size) });
4922 }
4923 },
4924 else => unreachable,
4925 }
4926 }
4927
4928 assert(symbol.si != .null);
4929 try coff.addReloc(
4930 section.si,
4931 reloc.virtual_address - section.header.virtual_address,
4932 symbol.si,
4933 .pending,
4934 @bitCast(reloc.type),
4935 );
4936 }
4937 }
4938
4939 // Set up contiguous symbol ranges in `input_symbols` for both symbols we just created,
4940 // and symbols that were previously created as undefined, but we just defined.
4941 const SortContext = struct {
4942 v: []const PendingSymbol,
4943
4944 pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
4945 const lhs = &ctx.v[a_index];
4946 const rhs = &ctx.v[b_index];
4947 if (lhs.section_number == rhs.section_number)
4948 return @intFromEnum(lhs.si) < @intFromEnum(rhs.si);
4949 return @intFromEnum(lhs.section_number) < @intFromEnum(rhs.section_number);
4950 }
4951 };
4952
4953 pending_symbols.sortUnstable(SortContext{ .v = pending_symbols.values() });
4954
4955 try coff.input_symbols.ensureUnusedCapacity(gpa, num_included_symbols + num_included_sections);
4956 var prev_sn: Symbol.SectionNumber = .DEBUG;
4957 var include_section = false;
4958 for (pending_symbols.values()) |symbol| {
4959 // The symbol may have not been included, or it's an undefined external / aux
4960 if (symbol.si == .null or symbol.si.get(coff).ni == .none) continue;
4961
4962 if (prev_sn != symbol.section_number) {
4963 prev_sn = symbol.section_number;
4964 if (symbol.section_number.hasIndex()) {
4965 const section = &sections[symbol.section_number.toIndex()];
4966 include_section = section.comdat_result == .include;
4967 if (include_section) {
4968 const isi = coff.getNode(section.si.get(coff).ni).input_section;
4969 isi.inputSection(coff).first_li = @enumFromInt(coff.input_symbols.items.len);
4970 }
4971 }
4972 }
4973
4974 if (include_section) {
4975 assert(coff.getNode(symbol.si.get(coff).ni) == .input_section);
4976 symbol.si.get(coff).setExtra(.{ .isli = @enumFromInt(coff.input_symbols.items.len) });
4977 coff.input_symbols.addOneAssumeCapacity().* = .{
4978 .si = symbol.si,
4979 .name = symbol.name,
4980 };
4981 }
4982 }
4983}
4984
4985fn failMultipleDefinitions(
4986 coff: *Coff,
4987 path: std.Build.Cache.Path,
4988 member_name: ?[]const u8,
4989 name: String,
4990 index: u32,
4991 existing_si: Symbol.Index,
4992 comdat_reason: union(enum) {
4993 none: void,
4994 duplicate: void,
4995 size: struct { a: u64, b: u64 },
4996 crc: struct { a: u32, b: u32 },
4997 },
4998) error{ AlreadyReported, OutOfMemory } {
4999 const num_notes: usize = 2 + @as(usize, @intFromBool(comdat_reason != .none));
5000 var err = try coff.base.comp.link_diags.addErrorWithNotes(num_notes);
5001 try err.addMsg("multiple definitions of '{s}'", .{name.toSlice(coff)});
5002
5003 switch (coff.getNode(existing_si.get(coff).ni)) {
5004 .input_section => |isi| {
5005 const other_ioi = isi.input(coff);
5006 err.addNote("first seen in input '{f}{f}'", .{
5007 other_ioi.path(coff).fmtEscapeString(),
5008 fmtMemberNameString(other_ioi.memberName(coff)),
5009 });
5010 },
5011 .nav, .uav => err.addNote("first seen in module '{s}'", .{
5012 coff.base.comp.zcu.?.root_mod.fully_qualified_name,
5013 }),
5014 else => unreachable,
5015 }
5016
5017 err.addNote("defined again in input '{f}{f}' (0x{x}))", .{ path, fmtMemberNameString(member_name), index });
5018 switch (comdat_reason) {
5019 .none => {},
5020 .duplicate => err.addNote("COMDAT rule requires no duplicates", .{}),
5021 .size => |s| err.addNote(
5022 "COMDAT rule require duplicates to have the same size ({d} vs {d})",
5023 .{ s.a, s.b },
5024 ),
5025 .crc => |s| err.addNote(
5026 "COMDAT rule require duplicates to have the same CRC (0x{x} vs 0x{x})",
5027 .{ s.a, s.b },
5028 ),
5029 }
5030
5031 return error.AlreadyReported;
5032}
5033
5034const ArchiveMemberHeader = struct {
5035 name: []const u8,
5036 size: u34,
5037};
5038
5039/// Return value lifetime is that of `header`
5040fn parseArchiveMemberHeader(
5041 diags: *link.Diags,
5042 path: std.Build.Cache.Path,
5043 header: *const std.coff.ArchiveMemberHeader,
5044 opt_longnames: ?[]const u8,
5045) !ArchiveMemberHeader {
5046 return parseArchiveMemberHeaderInner(header, opt_longnames) catch |err| switch (err) {
5047 error.BadName => return diags.failParse(path, "malformed member name: '{s}'", .{&header.name}),
5048 error.BadSize => return diags.failParse(path, "malformed member size: '{s}'", .{&header.size}),
5049 error.BadEndOfHeader => return diags.failParse(path, "end of header was invalid", .{}),
5050 error.NoLongNames => return diags.failParse(path, "long name used without longnames member", .{}),
5051 };
5052}
5053
5054fn parseArchiveMemberHeaderInner(
5055 header: *const std.coff.ArchiveMemberHeader,
5056 opt_longnames: ?[]const u8,
5057) !ArchiveMemberHeader {
5058 const name = try header.parseName(opt_longnames);
5059 const size = header.parseSize() catch return error.BadSize;
5060
5061 if (!std.mem.eql(u8, &header.end_of_header, std.coff.archive_end_of_header))
5062 return error.BadEndOfHeader;
5063
5064 return .{
5065 .name = name,
5066 .size = size,
5067 };
5068}
5069
5070fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) LoadInputError!void {
5071 const comp = coff.base.comp;
5072 const gpa = comp.gpa;
5073 const diags = &comp.link_diags;
5074 const r = &fr.interface;
5075 const target_endian = coff.targetEndian();
5076
5077 log.debug("loadArchive({f})", .{path.fmtEscapeString()});
5078
5079 const signature = try r.take(std.coff.archive_signature.len);
5080 if (!std.mem.eql(u8, signature, std.coff.archive_signature))
5081 return diags.failParse(path, "bad signature", .{});
5082
5083 var opt_expected_kind: ?std.coff.ArchiveMemberHeader.Kind = .first_linker;
5084 var opt_longnames: ?[]const u8 = null;
5085 defer if (opt_longnames) |l| gpa.free(l);
5086
5087 var members: std.ArrayList(struct {
5088 offset: u32,
5089 iami: ?InputArchive.Member.Index,
5090 }) = .empty;
5091 var symbol_member_indices: std.ArrayList(u32) = .empty;
5092
5093 const iai: InputArchive.Index = @enumFromInt(coff.input_archives.items.len);
5094 (try coff.input_archives.addOne(gpa)).* = .{
5095 .path = path,
5096 };
5097
5098 const first_iami = coff.input_archive_members.items.len;
5099 const first_iamsi = coff.input_archive_symbols.items.len;
5100 const first_symbol_indices_index = coff.input_archive_symbol_indices.count();
5101
5102 errdefer {
5103 for (coff.input_archive_symbol_indices.values()) |*v| {
5104 if (@intFromEnum(v.last) < first_iamsi) continue;
5105 if (@intFromEnum(v.first) >= first_iamsi) continue;
5106
5107 var iter = v.first;
5108 v.last = while (iter != v.last) {
5109 const sym = &coff.input_archive_symbols.items[@intFromEnum(iter)];
5110 if (@intFromEnum(sym.next) >= first_iamsi) {
5111 sym.next = iter;
5112 break iter;
5113 }
5114
5115 iter = sym.next;
5116 } else unreachable;
5117 }
5118
5119 // New entries in this map will only have pointed to iamsi we also just added
5120 coff.input_archive_symbol_indices.shrinkRetainingCapacity(first_symbol_indices_index);
5121 coff.input_archive_symbols.shrinkRetainingCapacity(first_iamsi);
5122 coff.input_archive_members.shrinkRetainingCapacity(first_iami);
5123 _ = coff.input_archives.pop();
5124 }
5125
5126 var pos = fr.logicalPos();
5127 const size = try fr.getSize();
5128 while (pos < size) : (pos = fr.logicalPos()) {
5129 if ((pos & 1) != 0) try r.discardAll(1);
5130 const header = try r.takeStruct(std.coff.ArchiveMemberHeader, target_endian);
5131 const res = try parseArchiveMemberHeader(diags, path, &header, opt_longnames);
5132
5133 const member_end = fr.logicalPos() + res.size;
5134 if (member_end > size)
5135 return diags.failParse(path, "out-of-bounds length 0x{x} in member '{s}'", .{ res.size, res.name });
5136
5137 log.debug("loadArchiveMember({s})", .{res.name});
5138
5139 if (opt_expected_kind) |expected_kind| switch (expected_kind) {
5140 .first_linker => {
5141 if (!std.mem.eql(u8, res.name, "/"))
5142 return diags.failParse(path, "expected first linker member, found '{s}'", .{res.name});
5143
5144 try fr.seekTo(fr.logicalPos() + res.size);
5145 opt_expected_kind = .second_linker;
5146 continue;
5147 },
5148 .second_linker => {
5149 if (!std.mem.eql(u8, res.name, "/"))
5150 return diags.failParse(path, "expected second linker member, found '{s}'", .{res.name});
5151
5152 const num_members = try r.takeInt(u32, target_endian);
5153 pos = fr.logicalPos();
5154 if (pos + num_members * @sizeOf(u32) > member_end)
5155 return diags.failParse(path, "invalid member count 0x{x} in second linker member", .{num_members});
5156
5157 try members.ensureTotalCapacity(gpa, num_members);
5158 for (0..num_members) |_|
5159 members.addOneAssumeCapacity().* = .{
5160 .offset = try r.takeInt(u32, target_endian),
5161 .iami = null,
5162 };
5163
5164 const num_symbols = try r.takeInt(u32, target_endian);
5165 pos = fr.logicalPos();
5166 if (pos + num_symbols * @sizeOf(u16) > member_end)
5167 return diags.failParse(path, "invalid symbol count 0x{x} in second linker member", .{num_symbols});
5168
5169 try symbol_member_indices.ensureTotalCapacity(gpa, num_symbols);
5170 for (0..num_symbols) |_|
5171 symbol_member_indices.addOneAssumeCapacity().* = (try r.takeInt(u16, target_endian)) - 1;
5172
5173 pos = fr.logicalPos();
5174 try coff.ensureManyUnusedStringCapacity(num_symbols, @intCast(member_end - pos));
5175 try coff.input_archive_members.ensureUnusedCapacity(gpa, num_members);
5176 try coff.input_archive_symbols.ensureUnusedCapacity(gpa, num_symbols);
5177 try coff.input_archive_symbol_indices.ensureUnusedCapacity(gpa, num_symbols);
5178
5179 var symbol_i: u32 = 0;
5180 while (pos < member_end and symbol_i < num_symbols) : ({
5181 pos = fr.logicalPos();
5182 symbol_i += 1;
5183 }) {
5184 const name = if (r.takeDelimiter(0) catch |err| switch (err) {
5185 error.StreamTooLong => null,
5186 else => |e| return e,
5187 }) |n| n else return diags.failParse(path, "unterminated string found in second linker member", .{});
5188
5189 const string = coff.getOrPutStringAssumeCapacity(name);
5190 const iamsi: InputArchive.Member.Symbol.Index = @enumFromInt(coff.input_archive_symbols.items.len);
5191 const symbol_gop = coff.input_archive_symbol_indices.getOrPutAssumeCapacity(string);
5192 if (!symbol_gop.found_existing) {
5193 symbol_gop.value_ptr.* = .{
5194 .first = iamsi,
5195 .last = iamsi,
5196 };
5197 } else {
5198 coff.input_archive_symbols.items[@intFromEnum(symbol_gop.value_ptr.last)].next = iamsi;
5199 symbol_gop.value_ptr.last = iamsi;
5200 }
5201
5202 const iami = members.items[symbol_member_indices.items[symbol_i]].iami orelse iami: {
5203 const iami: InputArchive.Member.Index = @enumFromInt(coff.input_archive_members.items.len);
5204 const member_offset = members.items[symbol_member_indices.items[symbol_i]].offset;
5205 coff.input_archive_members.addOneAssumeCapacity().* = .{
5206 .iai = iai,
5207 .name = undefined,
5208 .content = .{
5209 .object = .{
5210 .offset = member_offset,
5211 .size = undefined,
5212 },
5213 },
5214 .flags = .{
5215 .is_loaded = false,
5216 },
5217 };
5218
5219 members.items[symbol_member_indices.items[symbol_i]].iami = iami;
5220 break :iami iami;
5221 };
5222
5223 log.debug("loadArchiveMemberSymbol({s}) = ({d}, {d}, {d})", .{ name, iai, iami, iamsi });
5224
5225 coff.input_archive_symbols.addOneAssumeCapacity().* = .{
5226 .iami = iami,
5227 .next = iamsi,
5228 };
5229 }
5230
5231 if (symbol_i != num_symbols)
5232 return diags.failParse(
5233 path,
5234 " expected {d} entries in second linker member string table, but found {d}",
5235 .{ num_symbols, symbol_i },
5236 );
5237
5238 try fr.seekTo(member_end);
5239 opt_expected_kind = .longnames;
5240 continue;
5241 },
5242 .longnames => {
5243 // This member is optional
5244 if (std.mem.eql(u8, res.name, "//"))
5245 opt_longnames = try r.readAlloc(gpa, @intCast(res.size));
5246
5247 opt_expected_kind = null;
5248 break;
5249 },
5250 else => unreachable,
5251 };
5252 }
5253
5254 if (opt_expected_kind) |expected_kind| switch (expected_kind) {
5255 .first_linker => return diags.failParse(path, "missing first linker member", .{}),
5256 .second_linker => return diags.failParse(path, "missing second linker member", .{}),
5257 else => {},
5258 };
5259
5260 // Validate / read names and sizes of all the referenced members, enumerate imports
5261 for (coff.input_archive_members.items[first_iami..]) |*member| {
5262 try fr.seekTo(member.content.object.offset);
5263
5264 const header = try r.takeStruct(std.coff.ArchiveMemberHeader, target_endian);
5265 const res = try parseArchiveMemberHeader(diags, path, &header, opt_longnames);
5266
5267 try coff.ensureUnusedStringCapacity(res.name.len);
5268 member.name = coff.getOrPutStringAssumeCapacity(res.name);
5269
5270 const member_sig = try r.peek(4);
5271 const machine: std.coff.IMAGE.FILE.MACHINE =
5272 @enumFromInt(std.mem.readInt(u16, member_sig[0..2], target_endian));
5273 const sig = std.mem.readInt(u16, member_sig[2..4], target_endian);
5274
5275 log.debug("verifyArchiveMember({s}) = 0x{x}+{x}", .{
5276 res.name,
5277 member.content.object.offset,
5278 res.size,
5279 });
5280
5281 const expected_machine = comp.root_mod.resolved_target.result.toCoffMachine();
5282 if (machine == std.coff.IMAGE.FILE.MACHINE.UNKNOWN and sig == 0xffff) {
5283 const import_header = try r.takeStruct(std.coff.ImportHeader, target_endian);
5284 const strings = r.take(import_header.size_of_data) catch |err| switch (err) {
5285 error.EndOfStream => return diags.failParse(path, "invalid data size in import header '{s}'", .{res.name}),
5286 else => |e| return e,
5287 };
5288
5289 var split = std.mem.splitScalar(u8, strings, 0);
5290 const symbol_name = split.next() orelse
5291 return diags.failParse(path, "invalid symbol name string in import header '{s}'", .{res.name});
5292 var lib_name = split.next() orelse
5293 return diags.failParse(path, "invalid dll name string in import header '{s}' ('{s}')", .{ res.name, symbol_name });
5294
5295 if (import_header.machine != expected_machine)
5296 return diags.failParse(path, "machine mismatch in import header '{s}' ('{s}'): expected {t}, found {t}", .{
5297 res.name,
5298 symbol_name,
5299 expected_machine,
5300 machine,
5301 });
5302
5303 const ext = ".dll";
5304 if (!std.mem.endsWith(u8, lib_name, ext))
5305 return diags.failParse(
5306 path,
5307 "unexpected extension for import '{s} ('{s}'): '{s}'",
5308 .{ res.name, symbol_name, lib_name },
5309 );
5310
5311 lib_name = lib_name[0 .. lib_name.len - ext.len];
5312 log.debug("verifyArchiveImportHeader({s}, {s}, {s}) = {t} ({t})", .{
5313 res.name,
5314 symbol_name,
5315 lib_name,
5316 import_header.types.type,
5317 import_header.types.name_type,
5318 });
5319
5320 try coff.ensureManyUnusedStringCapacity(2, strings.len - ext.len);
5321 member.content = .{
5322 .import = .{
5323 .symbol_name = coff.getOrPutStringAssumeCapacity(symbol_name),
5324 .lib_name = coff.getOrPutStringAssumeCapacity(lib_name),
5325 .import_ordinal_hint = import_header.hint,
5326 .type = import_header.types.type,
5327 .name_type = import_header.types.name_type,
5328 },
5329 };
5330 } else {
5331 member.content.object.size = res.size;
5332 // Microsoft's CRT contains members that set .UNKNOWN but do have undef symbols
5333 if (machine != expected_machine and machine != .UNKNOWN) {
5334 return diags.failParse(path, "machine mismatch in member header '{s}': expected {t}, found {t}", .{
5335 res.name,
5336 expected_machine,
5337 machine,
5338 });
5339 }
5340 }
5341 }
5342}
5343
5344fn loadRes(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) LoadInputError!void {
5345 const comp = coff.base.comp;
5346 const gpa = comp.gpa;
5347 const diags = &comp.link_diags;
5348 const r = &fr.interface;
5349
5350 log.debug("loadRes({f})", .{path.fmtEscapeString()});
5351
5352 _ = gpa;
5353 _ = diags;
5354 _ = r;
5355}
5356
5357fn loadDll(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) LoadInputError!void {
5358 const comp = coff.base.comp;
5359 const gpa = comp.gpa;
5360 const diags = &comp.link_diags;
5361 const r = &fr.interface;
5362
5363 log.debug("loadDll({f})", .{path.fmtEscapeString()});
5364
5365 _ = gpa;
5366 _ = diags;
5367 _ = r;
5368}
5369
15275370pub fn prelink(coff: *Coff, prog_node: std.Progress.Node) link.Error!void {
1528 _ = coff;
15295371 _ = prog_node;
5372 const base = coff.base;
5373 const comp = base.comp;
5374
5375 log.debug("prelink()", .{});
5376
5377 if (coff.pending_default_libs.items.len > 0) {
5378 // Libs provided by /DEFAULTLIB arguments in objects are searched after all other inputs
5379 const gpa = comp.gpa;
5380 const arena = comp.arena;
5381 const target = &comp.root_mod.resolved_target.result;
5382
5383 defer {
5384 for (coff.pending_default_libs.items) |l| gpa.free(l.path);
5385 coff.pending_default_libs.clearAndFree(gpa);
5386 }
5387
5388 assert(comp.config.link_libc);
5389 const libc_installation = comp.libc_installation.?;
5390 const all_paths: [3]?[]const u8 = .{
5391 libc_installation.crt_dir,
5392 libc_installation.msvc_lib_dir,
5393 libc_installation.kernel32_lib_dir,
5394 };
5395 const search_paths = all_paths[0..if (target.abi == .msvc or target.abi == .itanium) 3 else 1];
5396 lib: for (coff.pending_default_libs.items) |lib| {
5397 if (!std.mem.eql(u8, std.fs.path.extension(lib.path), ".lib"))
5398 return comp.link_diags.failParse(
5399 lib.ioi.path(coff),
5400 "/DEFAULTLIB library '{s}' had unexpected extension",
5401 .{lib.path},
5402 );
5403
5404 log.debug("loadDefaultLib({s}, {f})", .{ lib.path, lib.ioi.path(coff) });
5405 for (search_paths) |opt_path| if (opt_path) |search_path| {
5406 const lib_path = try Path.initCwd(search_path).join(arena, lib.path);
5407 const archive = link.openObject(comp.io, lib_path, false, false) catch |err| switch (err) {
5408 error.FileNotFound => {
5409 arena.free(lib_path.sub_path);
5410 continue;
5411 },
5412 else => |e| return comp.link_diags.failParse(
5413 lib.ioi.path(coff),
5414 "error opening /DEFAULTLIB library '{s}': {t}",
5415 .{ lib.path, e },
5416 ),
5417 };
5418 errdefer archive.file.close(comp.io);
5419
5420 coff.loadInput(.{ .archive = archive }) catch |err| switch (err) {
5421 else => |e| return comp.link_diags.failParse(
5422 lib.ioi.path(coff),
5423 "error loading /DEFAULTLIB library '{s}': {t}",
5424 .{ lib.path, e },
5425 ),
5426 };
5427
5428 break :lib;
5429 };
5430
5431 return comp.link_diags.failParse(
5432 lib.ioi.path(coff),
5433 "/DEFAULTLIB library '{s}' was not found",
5434 .{lib.path},
5435 );
5436 }
5437 }
5438
5439 coff.inputs_complete = true;
5440 if (comp.zcu == null)
5441 coff.exports_complete = true;
15305442}
15315443
1532pub fn updateNav(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void {
5444pub fn updateNav(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) link.Error!void {
15335445 coff.updateNavInner(pt, nav_index) catch |err| switch (err) {
5446 error.MappedFileIo => return coff.base.cgFail(
5447 nav_index,
5448 "linker failed to update variable: {t}",
5449 .{coff.mf.io_err.?},
5450 ),
15345451 else => |e| return e,
1535 error.MappedFileIo => return coff.base.cgFail(nav_index, "linker failed to update variable: {t}", .{coff.mf.io_err.?}),
15365452 };
15375453}
15385454fn 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
15465462
15475463 const nmi = try coff.navMapIndex(zcu, nav_index);
15485464 const si = nmi.symbol(coff);
5465 log.debug("updateNav({f}) = {d}", .{ nav.fqn.fmt(ip), si });
15495466 const ni = ni: {
15505467 switch (si.get(coff).ni) {
15515468 .none => {
15525469 const sec_si = try coff.navSection(zcu, nav.resolved.?);
15535470 try coff.nodes.ensureUnusedCapacity(gpa, 1);
5471 if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1);
15545472 const ni = try coff.mf.addLastChildNode(gpa, sec_si.node(coff), .{
15555473 .alignment = zcu.navAlignment(nav_index).toStdMem(),
15565474 .moved = true,
......@@ -1565,6 +5483,9 @@ fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde
15655483 const sym = si.get(coff);
15665484 assert(sym.loc_relocs == .none);
15675485 sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
5486 if (!isImage(coff) and sym.target_relocs != .none)
5487 try coff.pendingSymbolTableEntry(si);
5488
15685489 break :ni sym.ni;
15695490 };
15705491
......@@ -1582,12 +5503,12 @@ fn updateNavInner(coff: *Coff, pt: Zcu.PerThread, nav_index: InternPool.Nav.Inde
15825503 error.WriteFailed => return nw.err.?,
15835504 else => |e| return e,
15845505 };
1585 si.get(coff).size = @intCast(nw.interface.end);
1586 si.applyLocationRelocs(coff);
5506 si.get(coff).extra.size = @intCast(nw.interface.end);
5507 try si.applyLocationRelocs(coff);
15875508 }
15885509
15895510 if (nav.resolved.?.@"linksection".unwrap()) |_| {
1590 try ni.resize(&coff.mf, gpa, si.get(coff).size);
5511 try ni.resize(&coff.mf, gpa, si.get(coff).extra.size);
15915512 var parent_ni = ni;
15925513 while (true) {
15935514 parent_ni = parent_ni.parent(&coff.mf);
......@@ -1610,7 +5531,7 @@ pub fn lowerUav(
16105531 pt: Zcu.PerThread,
16115532 uav_val: InternPool.Index,
16125533 uav_align: InternPool.Alignment,
1613) !link.File.SymbolId {
5534) link.Error!link.File.SymbolId {
16145535 const zcu = pt.zcu;
16155536 const gpa = zcu.gpa;
16165537
......@@ -1639,7 +5560,7 @@ pub fn updateFunc(
16395560 pt: Zcu.PerThread,
16405561 func_index: InternPool.Index,
16415562 mir: *const codegen.AnyMir,
1642) !void {
5563) link.Error!void {
16435564 coff.updateFuncInner(pt, func_index, mir) catch |err| switch (err) {
16445565 else => |e| return e,
16455566 error.MappedFileIo => return coff.base.cgFail(
......@@ -1669,6 +5590,7 @@ fn updateFuncInner(
16695590 .none => {
16705591 const sec_si = try coff.navSection(zcu, nav.resolved.?);
16715592 try coff.nodes.ensureUnusedCapacity(gpa, 1);
5593 if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1);
16725594 const mod = zcu.navFileScope(func.owner_nav).mod.?;
16735595 const target = &mod.resolved_target.result;
16745596 const ni = try coff.mf.addLastChildNode(gpa, sec_si.node(coff), .{
......@@ -1694,6 +5616,8 @@ fn updateFuncInner(
16945616 const sym = si.get(coff);
16955617 assert(sym.loc_relocs == .none);
16965618 sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
5619 if (!isImage(coff) and sym.target_relocs != .none)
5620 try coff.pendingSymbolTableEntry(si);
16975621 break :ni sym.ni;
16985622 };
16995623
......@@ -1712,21 +5636,245 @@ fn updateFuncInner(
17125636 error.WriteFailed => return nw.err.?,
17135637 else => |e| return e,
17145638 };
1715 si.get(coff).size = @intCast(nw.interface.end);
1716 si.applyLocationRelocs(coff);
5639 si.get(coff).extra.size = @intCast(nw.interface.end);
5640 try si.applyLocationRelocs(coff);
5641}
5642
5643pub fn updateErrorData(coff: *Coff, pt: Zcu.PerThread) !void {
5644 coff.flushLazy(pt, .{
5645 .kind = .const_data,
5646 .index = @intCast(coff.lazy.getPtr(.const_data).map.getIndex(.anyerror_type) orelse return),
5647 }) catch |err| switch (err) {
5648 else => |e| return e,
5649 error.MappedFileIo => return coff.base.comp.link_diags.fail(
5650 "updateErrorData failed: {t}",
5651 .{coff.mf.io_err.?},
5652 ),
5653 };
5654}
5655
5656fn flushImplib(
5657 coff: *Coff,
5658 implib_file: []const u8,
5659) !void {
5660 // Emitting implibs is only valid for images
5661 assert(coff.export_table.ni != .none);
5662
5663 const comp = coff.base.comp;
5664 const gpa = comp.gpa;
5665 const io = comp.io;
5666
5667 const image_name = std.mem.sliceTo(
5668 coff.export_table.ni.slice(&coff.mf)[@sizeOf(std.coff.ExportDirectoryTable)..],
5669 0,
5670 );
5671 const machine_type = coff.targetLoad(&coff.headerPtr().machine);
5672 const members = members: {
5673 const def_arena: std.heap.ArenaAllocator = .init(gpa);
5674 var def: ModuleDefinition = .{
5675 .name = image_name,
5676 .arena = def_arena,
5677 .type = .mingw,
5678 };
5679 defer def.deinit();
5680
5681 try def.exports.ensureUnusedCapacity(
5682 def.arena.allocator(),
5683 coff.export_table.entries.count(),
5684 );
5685
5686 const name_table_slice = coff.export_table.name_table_ni.slice(&coff.mf);
5687 for (coff.export_table.entries.values(), 0..) |entry, ord| {
5688 const name = name_table_slice[entry.name_index..][0..entry.name_len];
5689 const section_number = entry.si.get(coff).section_number;
5690 const import_type: std.coff.ImportType = switch (section_number.symbol(coff)) {
5691 .data, .rdata => .DATA,
5692 .text => .CODE,
5693 else => return comp.link_diags.fail(
5694 "unsupported section for export '{s}': {s}",
5695 .{ name, &section_number.header(coff).name },
5696 ),
5697 };
5698
5699 def.exports.appendAssumeCapacity(.{
5700 .name = name,
5701 .mangled_symbol_name = null,
5702 .ext_name = null,
5703 .import_name = null,
5704 .export_as = null,
5705 .no_name = false,
5706 .ordinal = @intCast(ord),
5707 .type = import_type,
5708 .private = false,
5709 });
5710 }
5711
5712 def.fixupForImportLibraryGeneration(machine_type);
5713 break :members try implib.getMembers(gpa, def, machine_type);
5714 };
5715 defer members.deinit();
5716
5717 const lib_sub_path = try std.fs.path.join(gpa, &.{
5718 std.fs.path.dirname(coff.base.emit.sub_path) orelse "",
5719 implib_file,
5720 });
5721 defer gpa.free(lib_sub_path);
5722
5723 const lib_final_file = try coff.base.emit.root_dir.handle.createFile(io, lib_sub_path, .{ .truncate = true });
5724 defer lib_final_file.close(io);
5725 var buffer: [1024]u8 = undefined;
5726 var file_writer = lib_final_file.writer(io, &buffer);
5727 try implib.writeCoffArchive(gpa, &file_writer.interface, members);
5728 try file_writer.interface.flush();
17175729}
17185730
1719pub fn updateErrorData(coff: *Coff, pt: Zcu.PerThread) !void {
1720 coff.flushLazy(pt, .{
1721 .kind = .const_data,
1722 .index = @intCast(coff.lazy.getPtr(.const_data).map.getIndex(.anyerror_type) orelse return),
1723 }) catch |err| switch (err) {
1724 else => |e| return e,
1725 error.MappedFileIo => return coff.base.comp.link_diags.fail(
1726 "updateErrorData failed: {t}",
1727 .{coff.mf.io_err.?},
1728 ),
1729 };
5731fn reportUndefs(coff: *Coff, tid: Zcu.PerThread.Id) !void {
5732 const comp = coff.base.comp;
5733 const gpa = comp.gpa;
5734 const max_notes = 4;
5735
5736 var undef_indices: std.ArrayListUnmanaged(u32) = .empty;
5737 for (coff.relocs.items, 0..) |reloc, reloc_i| {
5738 if (reloc.flags.free) continue;
5739 const target_sym = reloc.target.get(coff);
5740 switch (target_sym.ni) {
5741 .none => {
5742 assert(target_sym.gmi != .none);
5743 if (target_sym.section_number == .ABSOLUTE) continue;
5744 (try undef_indices.addOne(gpa)).* = @intCast(reloc_i);
5745 },
5746 else => continue,
5747 }
5748 }
5749
5750 if (undef_indices.items.len == 0) return;
5751
5752 const undefLessThan = struct {
5753 fn lessThan(ctx: *const Coff, lhs: u32, rhs: u32) bool {
5754 const reloc_l = &ctx.relocs.items[lhs];
5755 const reloc_r = &ctx.relocs.items[rhs];
5756 if (reloc_l.target == reloc_r.target)
5757 return @intFromEnum(reloc_l.loc) < @intFromEnum(reloc_r.loc)
5758 else
5759 return @intFromEnum(reloc_l.target) < @intFromEnum(reloc_r.target);
5760 }
5761 }.lessThan;
5762
5763 std.mem.sortUnstable(u32, undef_indices.items, coff, undefLessThan);
5764
5765 var start_i: usize = 0;
5766 var num_unique_references: usize = 1;
5767 for (0..undef_indices.items.len) |i| {
5768 const target = coff.relocs.items[undef_indices.items[start_i]].target;
5769 if (i == undef_indices.items.len - 1 or target != coff.relocs.items[undef_indices.items[i + 1]].target) {
5770 defer {
5771 start_i = i + 1;
5772 num_unique_references = 1;
5773 }
5774
5775 const num_full_notes = @min(max_notes, num_unique_references);
5776 var err = try comp.link_diags.addErrorWithNotes(
5777 num_full_notes + @intFromBool(num_unique_references > max_notes),
5778 );
5779 const target_sym = target.get(coff);
5780 try err.addMsg("undefined symbol: {s}", .{target_sym.gmi.name(coff).toSlice(coff)});
5781
5782 // TODO: If lib_name is set, show the user
5783
5784 var prev_loc_si: Symbol.Index = .null;
5785 for (undef_indices.items[start_i .. i + 1]) |reference_i| {
5786 if (err.note_slot == num_full_notes) break;
5787
5788 const reloc = &coff.relocs.items[reference_i];
5789 const loc_si = reloc.loc;
5790 if (loc_si == prev_loc_si) continue;
5791 defer prev_loc_si = loc_si;
5792
5793 const loc_sym = loc_si.get(coff);
5794
5795 // TODO: Make this a helper for anything that needs to report "referenced by" notes
5796 switch (coff.getNode(loc_sym.ni)) {
5797 .data_directories => {
5798 const dir: std.coff.IMAGE.DIRECTORY_ENTRY =
5799 @enumFromInt(reloc.offset / @sizeOf(std.coff.ImageDataDirectory));
5800 err.addNote("referenced by data directory entry: {t}", .{dir});
5801 },
5802 .optional_header => err.addNote("referenced by optional header field", .{}),
5803 .input_section => |isi| {
5804 const other_ioi = isi.input(coff);
5805 if (loc_sym.gmi == .none) {
5806 const section = isi.inputSection(coff);
5807 const section_name = coff.getNode(loc_sym.ni.parent(&coff.mf))
5808 .object_section.name(coff).toSlice(coff);
5809
5810 if (section.comdat_si != .null) {
5811 const comdat_sym = section.comdat_si.get(coff);
5812 const comdat_name = if (comdat_sym.gmi != .none)
5813 comdat_sym.gmi.name(coff).toSlice(coff)
5814 else
5815 comdat_sym.extra.isli.name(coff).toSlice(coff);
5816
5817 err.addNote("referenced by input COMDAT section '{s}={s}' '{f}{f}'", .{
5818 section_name,
5819 comdat_name,
5820 other_ioi.path(coff).fmtEscapeString(),
5821 fmtMemberNameString(other_ioi.memberName(coff)),
5822 });
5823 } else {
5824 err.addNote("referenced by input section '{s}' '{f}{f}'", .{
5825 section_name,
5826 other_ioi.path(coff).fmtEscapeString(),
5827 fmtMemberNameString(other_ioi.memberName(coff)),
5828 });
5829 }
5830 } else {
5831 err.addNote("referenced by input symbol '{s}' from '{f}{f}'", .{
5832 loc_sym.gmi.name(coff).toSlice(coff),
5833 other_ioi.path(coff).fmtEscapeString(),
5834 fmtMemberNameString(other_ioi.memberName(coff)),
5835 });
5836 }
5837 },
5838 .import_thunk => |gmi| err.addNote("referenced by import thunk for '{s}'", .{
5839 gmi.name(coff).toSlice(coff),
5840 }),
5841 inline .nav,
5842 .uav,
5843 .lazy_code,
5844 .lazy_const_data,
5845 => |val, tag| {
5846 err.addNote("referenced by '{f}'", .{
5847 format: switch (tag) {
5848 .nav => {
5849 const ip = &comp.zcu.?.intern_pool;
5850 break :format ip.getNav(val.navIndex(coff)).fqn.fmt(ip);
5851 },
5852 .uav => Value.fromInterned(val.uavValue(coff)).fmtValue(.{
5853 .zcu = coff.base.comp.zcu.?,
5854 .tid = tid,
5855 }),
5856 inline .lazy_code, .lazy_const_data => Type.fromInterned(val.lazySymbol(coff).ty).fmt(.{
5857 .zcu = coff.base.comp.zcu.?,
5858 .tid = tid,
5859 }),
5860 else => unreachable,
5861 },
5862 });
5863 },
5864 else => unreachable,
5865 }
5866 }
5867
5868 if (num_unique_references > max_notes)
5869 err.addNote("referenced {d} more times", .{num_unique_references - max_notes});
5870 } else if (i != start_i and
5871 coff.relocs.items[undef_indices.items[i - 1]].loc != coff.relocs.items[undef_indices.items[i]].loc)
5872 {
5873 num_unique_references += 1;
5874 }
5875 }
5876
5877 return error.AlreadyReported;
17305878}
17315879
17325880pub fn flush(
......@@ -1734,35 +5882,79 @@ pub fn flush(
17345882 arena: std.mem.Allocator,
17355883 tid: Zcu.PerThread.Id,
17365884 prog_node: std.Progress.Node,
1737) !void {
5885) link.Error!void {
17385886 _ = arena;
17395887 _ = prog_node;
5888 const comp = coff.base.comp;
5889
5890 // TODO: When https://github.com/ziglang/zig/issues/23617 is in,
5891 // this should be set after updateExports instead
5892 coff.exports_complete = true;
5893
5894 while (try coff.resolve(tid)) {}
17405895 while (try coff.idle(tid)) {}
17415896
1742 // hack for stage2_x86_64 + coff
1743 const comp = coff.base.comp;
1744 if (comp.compiler_rt_dyn_lib) |crt_file| {
1745 const gpa = comp.gpa;
1746 const io = comp.io;
1747 const compiler_rt_sub_path = try std.fs.path.join(gpa, &.{
1748 std.fs.path.dirname(coff.base.emit.sub_path) orelse "",
1749 std.fs.path.basename(crt_file.full_object_path.sub_path),
1750 });
1751 defer gpa.free(compiler_rt_sub_path);
1752 std.Io.Dir.copyFile(
1753 crt_file.full_object_path.root_dir.handle,
1754 crt_file.full_object_path.sub_path,
1755 coff.base.emit.root_dir.handle,
1756 compiler_rt_sub_path,
1757 io,
1758 .{},
1759 ) catch |err| return comp.link_diags.fail("copy '{s}' failed: {t}", .{ compiler_rt_sub_path, err });
5897 // This has to occur after all other flushMoved / flushResized have resolved,
5898 // but it will also generate one more set of resizes and moves.
5899 if (coff.symbol_table.pending_shrink) {
5900 coff.symbol_table.pending_shrink = false;
5901
5902 const number_of_symbols = coff.targetLoad(&coff.headerPtr().number_of_symbols);
5903 coff.symbol_table.ni.shrink(
5904 &coff.mf,
5905 comp.gpa,
5906 number_of_symbols * std.coff.Symbol.sizeOf(),
5907 true,
5908 ) catch |err| return comp.link_diags.fail(
5909 "linker failed to compact symbol table: {t}",
5910 .{err},
5911 );
17605912 }
5913 while (try coff.idle(tid)) {}
5914
5915 if (coff.isImage())
5916 try coff.reportUndefs(tid);
5917
5918 if (comp.emit_implib) |implib_file|
5919 coff.flushImplib(implib_file) catch |err|
5920 return comp.link_diags.fail("flushing implib '{s}' failed: {t}", .{ implib_file, err });
5921
5922 coff.mf.flush() catch |err| switch (err) {
5923 error.Canceled => |e| return e,
5924 else => |e| return comp.link_diags.fail("flush write failed: {t}", .{e}),
5925 };
5926
5927 if (coff.options.enable_link_snapshots)
5928 coff.dumpStderr(tid) catch |err|
5929 return comp.link_diags.fail("dumping link snapshot failed: {t}", .{err});
17615930}
17625931
1763pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
5932/// Runs a single "resolution" task.
5933/// These are tasks that need to modify the node structure in some way.
5934/// They must run in a defined order with respect to linker tasks.
5935fn resolve(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
17645936 const comp = coff.base.comp;
17655937 task: {
5938 while (coff.section_merge_pending_index < coff.section_merges.count()) {
5939 defer coff.section_merge_pending_index += 1;
5940 const sub_prog_node = coff.synth_prog_node.start(
5941 coff.section_merges.keys()[coff.section_merge_pending_index].toSlice(coff),
5942 0,
5943 );
5944 defer sub_prog_node.end();
5945 coff.flushSectionMerge(coff.section_merge_pending_index) catch |err| switch (err) {
5946 //error.OutOfMemory => |e| return e,
5947 else => |e| return comp.link_diags.fail(
5948 "linker failed to merge section {s} into {s}: {t}",
5949 .{
5950 coff.section_merges.keys()[coff.section_merge_pending_index].toSlice(coff),
5951 coff.section_merges.values()[coff.section_merge_pending_index].toSlice(coff),
5952 e,
5953 },
5954 ),
5955 };
5956 break :task;
5957 }
17665958 while (coff.pending_uavs.pop()) |pending_uav| {
17675959 const sub_prog_node = coff.idleProgNode(tid, coff.const_prog_node, .{ .uav = pending_uav.key });
17685960 defer sub_prog_node.end();
......@@ -1779,22 +5971,52 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
17795971 };
17805972 break :task;
17815973 }
1782 if (coff.global_pending_index < coff.globals.count()) {
1783 const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = tid };
1784 const gmi: Node.GlobalMapIndex = @enumFromInt(coff.global_pending_index);
1785 coff.global_pending_index += 1;
5974 if (coff.pending_input) |pending_iami| {
5975 const name_slice = pending_iami.member(coff).name.toSlice(coff);
5976 const sub_prog_node = coff.input_prog_node.start(
5977 name_slice,
5978 0,
5979 );
5980 defer sub_prog_node.end();
5981 coff.pending_input = null;
5982 coff.flushInputMember(pending_iami) catch |err| switch (err) {
5983 error.OutOfMemory => return error.OutOfMemory,
5984 else => |e| return comp.link_diags.fail(
5985 "linker failed to load archive member '{f}{f}': {t}",
5986 .{
5987 pending_iami.member(coff).iai.path(coff),
5988 fmtMemberNameString(name_slice),
5989 e,
5990 },
5991 ),
5992 };
5993 break :task;
5994 }
5995 if (coff.exports_complete and coff.global_pending_index < coff.globals.count()) {
5996 const gmi: Node.GlobalMapIndex = .wrap(coff.global_pending_index);
17865997 const sub_prog_node = coff.synth_prog_node.start(
1787 gmi.globalName(coff).name.toSlice(coff),
5998 gmi.name(coff).toSlice(coff),
17885999 0,
17896000 );
17906001 defer sub_prog_node.end();
1791 coff.flushGlobal(pt, gmi) catch |err| switch (err) {
6002 if (coff.flushGlobal(gmi) catch |err| switch (err) {
17926003 else => |e| return e,
17936004 error.MappedFileIo => return comp.link_diags.fail(
17946005 "linker failed to lower constant: {t}",
17956006 .{coff.mf.io_err.?},
17966007 ),
1797 };
6008 }) coff.global_pending_index += 1;
6009 break :task;
6010 }
6011 if (coff.exports_complete and coff.pending_special_symbol != .none) {
6012 coff.pending_special_symbol = coff.flushSpecialSymbol(coff.pending_special_symbol) catch |err|
6013 switch (err) {
6014 error.OutOfMemory => |e| return e,
6015 else => |e| return comp.link_diags.fail(
6016 "linker failed to flush special symbols: {t}",
6017 .{e},
6018 ),
6019 };
17986020 break :task;
17996021 }
18006022 var lazy_it = coff.lazy.iterator();
......@@ -1824,6 +6046,73 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
18246046 };
18256047 break :task;
18266048 };
6049 if (coff.symbol_table.pending_symbol_index < coff.symbol_table.symbols.count()) {
6050 defer coff.symbol_table.pending_symbol_index += 1;
6051 const si = coff.symbol_table.symbols.keys()[coff.symbol_table.pending_symbol_index];
6052 const sym = si.get(coff);
6053 const sub_prog_node = coff.idleProgNode(
6054 tid,
6055 coff.symbol_prog_node,
6056 if (sym.ni != .none)
6057 coff.getNode(sym.ni)
6058 else
6059 .{ .import_thunk = sym.gmi },
6060 );
6061 defer sub_prog_node.end();
6062 coff.flushSymbolTableEntry(
6063 coff.symbol_table.pending_symbol_index,
6064 .{ .zcu = comp.zcu.?, .tid = tid },
6065 ) catch |err| switch (err) {
6066 error.OutOfMemory => return error.OutOfMemory,
6067 else => |e| return comp.link_diags.fail(
6068 "linker failed to flush symbol table entry: {t}",
6069 .{e},
6070 ),
6071 };
6072 break :task;
6073 }
6074 }
6075
6076 if (coff.section_merge_pending_index < coff.section_merges.count()) return true;
6077 if (coff.pending_uavs.count() > 0) return true;
6078 if (coff.pending_input != null) return true;
6079 if (coff.exports_complete and coff.globals.count() > coff.global_pending_index) return true;
6080 assert(!coff.exports_complete or coff.inputs_complete);
6081 if (coff.exports_complete and coff.pending_special_symbol != .none) return true;
6082 for (&coff.lazy.values) |lazy| if (lazy.map.count() > lazy.pending_index) return true;
6083 if (coff.symbol_table.pending_symbol_index < coff.symbol_table.symbols.count()) return true;
6084 return false;
6085}
6086
6087pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
6088 // Idle tasks should not modify create / modify nodes, otherwise the output is not reproducible.
6089 coff.mf.nodes_lock.lock();
6090 defer coff.mf.nodes_lock.unlock();
6091
6092 const comp = coff.base.comp;
6093 task: {
6094 // TODO: Idle task for flushing obj into lib
6095 if (coff.input_section_pending_index < coff.input_sections.items.len) {
6096 const isi: Node.InputSection.Index = @enumFromInt(coff.input_section_pending_index);
6097 coff.input_section_pending_index += 1;
6098 const sub_prog_node = coff.idleProgNode(tid, coff.input_prog_node, coff.getNode(isi.symbol(coff).node(coff)));
6099 defer sub_prog_node.end();
6100 coff.flushInputSection(isi) catch |err| switch (err) {
6101 else => |e| {
6102 const ioi = isi.input(coff);
6103 return comp.link_diags.fail(
6104 "linker failed to read input section '{s}' from \"{f}{f}\": {t}",
6105 .{
6106 isi.symbol(coff).get(coff).section_number.name(coff).toSlice(coff),
6107 ioi.path(coff).fmtEscapeString(),
6108 fmtMemberNameString(ioi.memberName(coff)),
6109 e,
6110 },
6111 );
6112 },
6113 };
6114 break :task;
6115 }
18276116 while (coff.mf.updates.pop()) |ni| {
18286117 const clean_moved = ni.cleanMoved(&coff.mf);
18296118 const clean_resized = ni.cleanResized(&coff.mf);
......@@ -1836,11 +6125,33 @@ pub fn idle(coff: *Coff, tid: Zcu.PerThread.Id) !bool {
18366125 break :task;
18376126 } else coff.mf.update_prog_node.completeOne();
18386127 }
6128 while (coff.pending_members.pop()) |pending_mi| {
6129 const sub_prog_node = coff.idleProgNode(
6130 tid,
6131 coff.symbol_prog_node,
6132 coff.getNode(pending_mi.key.get(coff).content_ni),
6133 );
6134 defer sub_prog_node.end();
6135 try coff.flushMember(pending_mi.key);
6136 break :task;
6137 }
6138 if (coff.exports_complete and coff.export_table.pending_sort) {
6139 defer coff.export_table.pending_sort = false;
6140 const sub_prog_node = coff.idleProgNode(
6141 tid,
6142 coff.synth_prog_node,
6143 coff.getNode(coff.export_table.ni),
6144 );
6145 defer sub_prog_node.end();
6146
6147 coff.flushExportsSort();
6148 break :task;
6149 }
18396150 }
1840 if (coff.pending_uavs.count() > 0) return true;
1841 if (coff.globals.count() > coff.global_pending_index) return true;
1842 for (&coff.lazy.values) |lazy| if (lazy.map.count() > lazy.pending_index) return true;
6151 if (coff.input_sections.items.len > coff.input_section_pending_index) return true;
18436152 if (coff.mf.updates.items.len > 0) return true;
6153 if (coff.pending_members.count() > 0) return true;
6154 if (coff.exports_complete and coff.export_table.pending_sort) return true;
18446155 return false;
18456156}
18466157
......@@ -1855,7 +6166,15 @@ fn idleProgNode(
18556166 else => |tag| @tagName(tag),
18566167 .image_section => |si| std.mem.sliceTo(&si.get(coff).section_number.header(coff).name, 0),
18576168 inline .pseudo_section, .object_section => |smi| smi.name(coff).toSlice(coff),
1858 .global => |gmi| gmi.globalName(coff).name.toSlice(coff),
6169 .input_section => |isi| {
6170 const ioi = isi.input(coff);
6171 break :name std.fmt.bufPrint(&name, "{f}{f} {s}", .{
6172 ioi.path(coff).fmtEscapeString(),
6173 fmtMemberNameString(ioi.memberName(coff)),
6174 coff.getNode(isi.symbol(coff).node(coff).parent(&coff.mf)).object_section.name(coff).toSlice(coff),
6175 }) catch &name;
6176 },
6177 .import_thunk => |gmi| gmi.name(coff).toSlice(coff),
18596178 .nav => |nmi| {
18606179 const ip = &coff.base.comp.zcu.?.intern_pool;
18616180 break :name ip.getNav(nmi.navIndex(coff)).fqn.toSlice(ip);
......@@ -1866,6 +6185,7 @@ fn idleProgNode(
18666185 .tid = tid,
18676186 }),
18686187 }) catch &name,
6188 .archive_member => |mi| &mi.get(coff).headerPtr(coff).name,
18696189 }, 0);
18706190}
18716191
......@@ -1886,9 +6206,10 @@ fn flushUav(
18866206 const sec_si = (try coff.objectSectionMapIndex(
18876207 .@".rdata",
18886208 coff.mf.flags.block_size,
1889 .{ .read = true },
6209 .{ .read = true, .initialized = true },
18906210 )).symbol(coff);
18916211 try coff.nodes.ensureUnusedCapacity(gpa, 1);
6212 if (!isImage(coff)) try coff.symbol_table.symbols.ensureUnusedCapacity(gpa, 1);
18926213 const sym = si.get(coff);
18936214 const ni = try coff.mf.addLastChildNode(gpa, sec_si.node(coff), .{
18946215 .alignment = uav_align.toStdMem(),
......@@ -1907,6 +6228,9 @@ fn flushUav(
19076228 const sym = si.get(coff);
19086229 assert(sym.loc_relocs == .none);
19096230 sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
6231 if (!isImage(coff) and sym.target_relocs != .none)
6232 try coff.pendingSymbolTableEntry(si);
6233
19106234 break :ni sym.ni;
19116235 };
19126236
......@@ -1923,173 +6247,561 @@ fn flushUav(
19236247 error.WriteFailed => return nw.err.?,
19246248 else => |e| return e,
19256249 };
1926 si.get(coff).size = @intCast(nw.interface.end);
1927 si.applyLocationRelocs(coff);
6250 si.get(coff).extra.size = @intCast(nw.interface.end);
6251 try si.applyLocationRelocs(coff);
19286252}
19296253
1930fn flushGlobal(coff: *Coff, pt: Zcu.PerThread, gmi: Node.GlobalMapIndex) !void {
1931 const zcu = pt.zcu;
1932 const comp = zcu.comp;
1933 const gpa = zcu.gpa;
1934 const gn = gmi.globalName(coff);
1935 if (gn.lib_name.toSlice(coff)) |lib_name| {
1936 const name = gn.name.toSlice(coff);
1937 try coff.nodes.ensureUnusedCapacity(gpa, 4);
1938 try coff.symbol_table.ensureUnusedCapacity(gpa, 1);
6254fn aliasGlobal(coff: *Coff, gmi: Node.GlobalMapIndex, alias_si: Symbol.Index) !void {
6255 const si = gmi.symbol(coff);
6256 const sym = si.get(coff);
6257 const alias_sym = alias_si.get(coff);
6258 assert(sym.section_number == .UNDEFINED);
6259 assert(sym.loc_relocs == .none);
6260
6261 log.debug("aliasGlobal({s}, {?s}) {d}->{d} ({?s})", .{
6262 gmi.name(coff).toSlice(coff),
6263 gmi.libName(coff).toSlice(coff),
6264 si,
6265 alias_si,
6266 if (alias_sym.gmi != .none) alias_sym.gmi.name(coff).toSlice(coff) else null,
6267 });
19396268
1940 const target_endian = coff.targetEndian();
1941 const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic);
1942 const addr_size: u64, const addr_align: std.mem.Alignment = switch (magic) {
1943 _ => unreachable,
1944 .PE32 => .{ 4, .@"4" },
1945 .@"PE32+" => .{ 8, .@"8" },
1946 };
6269 var ri = sym.target_relocs;
6270 while (ri != .none) {
6271 const reloc = ri.get(coff);
6272 assert(reloc.target == si);
6273 reloc.target = alias_si;
6274 if (reloc.next == .none) {
6275 reloc.next = alias_sym.target_relocs;
6276 if (alias_sym.target_relocs != .none)
6277 alias_sym.target_relocs.get(coff).prev = ri;
6278 break;
6279 }
6280 ri = reloc.next;
6281 }
19476282
1948 const gop = try coff.import_table.entries.getOrPutAdapted(
1949 gpa,
1950 lib_name,
1951 ImportTable.Adapter{ .coff = coff },
1952 );
1953 const import_hint_name_align: std.mem.Alignment = .@"2";
1954 if (!gop.found_existing) {
1955 errdefer _ = coff.import_table.entries.pop();
1956 try coff.import_table.ni.resize(
1957 &coff.mf,
1958 gpa,
1959 @sizeOf(std.coff.ImportDirectoryEntry) * (gop.index + 2),
6283 const prev_target_relocs = alias_sym.target_relocs;
6284 if (sym.target_relocs != .none)
6285 alias_sym.target_relocs = sym.target_relocs;
6286 sym.target_relocs = .none;
6287 sym.gmi = alias_sym.gmi;
6288 coff.globals.values()[gmi.unwrap().?].si = alias_si;
6289 // Only apply the new relocs
6290 try alias_si.applyTargetRelocs(coff, prev_target_relocs);
6291}
6292
6293fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
6294 const comp = coff.base.comp;
6295 const gpa = comp.gpa;
6296 const name = gmi.name(coff);
6297 const si = gmi.symbol(coff);
6298
6299 log.debug(
6300 "flushGlobal({s}, {?s}) = n{d} {d}@{d}",
6301 .{
6302 name.toSlice(coff),
6303 gmi.libName(coff).toSlice(coff),
6304 si.get(coff).ni,
6305 si,
6306 si.get(coff).section_number,
6307 },
6308 );
6309
6310 if (!coff.isImage()) {
6311 try coff.pendingSymbolTableEntry(si);
6312 if (coff.isArchive() and si.get(coff).ni != .none)
6313 try coff.ensureMemberSymbol(
6314 coff.getNode(Node.known.zcu_member).archive_member,
6315 name,
19606316 );
1961 const import_hint_name_table_len =
1962 import_hint_name_align.forward(lib_name.len + ".dll".len + 1);
1963 const idata_section_ni = coff.import_table.ni.parent(&coff.mf);
1964 const import_lookup_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{
1965 .size = addr_size * 2,
1966 .alignment = addr_align,
1967 .moved = true,
1968 });
1969 const import_address_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{
1970 .size = addr_size * 2,
1971 .alignment = addr_align,
1972 .moved = true,
1973 });
1974 const import_address_table_si = coff.addSymbolAssumeCapacity();
1975 {
1976 const import_address_table_sym = import_address_table_si.get(coff);
1977 import_address_table_sym.ni = import_address_table_ni;
1978 assert(import_address_table_sym.loc_relocs == .none);
1979 import_address_table_sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
1980 import_address_table_sym.section_number =
1981 coff.getNode(idata_section_ni).object_section.symbol(coff).get(coff).section_number;
6317
6318 return true;
6319 }
6320
6321 if (si.get(coff).ni != .none)
6322 return true;
6323
6324 const Import = struct {
6325 lib_name: String,
6326 name: String.Optional,
6327 ordinal_hint: u16,
6328 kind: enum {
6329 iat_ptr,
6330 thunk,
6331 },
6332 };
6333
6334 const import: Import = import: {
6335 const sym = si.get(coff);
6336 const name_slice = name.toSlice(coff);
6337 const imp_match = std.mem.startsWith(u8, name_slice, imp_prefix);
6338
6339 // Globals may have the __imp_ prefix already if they are undef externals from another input.
6340 assert(sym.flags.dll_storage_class != .dllexport);
6341 const search_name, const is_imp = if (imp_match or sym.flags.dll_storage_class != .dllimport)
6342 .{ name, imp_match }
6343 else name: {
6344 try coff.ensureUnusedStringCapacity(imp_prefix.len + name_slice.len);
6345 const imp_name = try std.fmt.allocPrint(gpa, imp_prefix ++ "{s}", .{name_slice});
6346 defer gpa.free(imp_name);
6347 break :name .{ coff.getOrPutStringAssumeCapacity(imp_name), true };
6348 };
6349
6350 const opt_alt_search_name = coff.alternate_names.get(search_name);
6351 const search_libs = switch (sym.flags.value_tag) {
6352 .weak_alias_si, .weak_alias_name => switch (sym.flags.weak_external_strat) {
6353 .none => unreachable,
6354 .no_library => false,
6355 .library,
6356 .alias,
6357 => true,
6358 .anti_dependency => return comp.link_diags.fail(
6359 // TODO: Figure out what the purpose of this is
6360 "TODO support anti_dependency weak external: {s}",
6361 .{name.toSlice(coff)},
6362 ),
6363 },
6364 else => true,
6365 };
6366
6367 const opt_indices_lists: []const ?InputArchive.SearchList = if (search_libs) &.{
6368 coff.input_archive_symbol_indices.get(search_name),
6369 if (opt_alt_search_name) |alt| coff.input_archive_symbol_indices.get(alt) else null,
6370 } else &.{};
6371
6372 for (opt_indices_lists) |opt_indices_list| {
6373 const indices_list = opt_indices_list orelse continue;
6374 var iter: InputArchive.Member.Symbol.Index = indices_list.first;
6375 while (true) {
6376 const archive_sym = &coff.input_archive_symbols.items[@intFromEnum(iter)];
6377 const member = &coff.input_archive_members.items[@intFromEnum(archive_sym.iami)];
6378 member: switch (member.content) {
6379 .object => if (!member.flags.is_loaded) {
6380 if (gmi.libName(coff).unwrap()) |lib_name|
6381 if (!std.ascii.eqlIgnoreCase(
6382 lib_name.toSlice(coff),
6383 member.iai.path(coff).stem(),
6384 )) break :member;
6385
6386 // Try loading the input member and then retry.
6387 // This could still be a member containing imports
6388 // that use the older non-IMPORT_HEADER method.
6389 coff.pending_input = archive_sym.iami;
6390 return false;
6391 },
6392 .import => |import| {
6393 if (gmi.libName(coff).unwrap()) |lib_name|
6394 if (!std.ascii.eqlIgnoreCase(
6395 import.lib_name.toSlice(coff),
6396 lib_name.toSlice(coff),
6397 )) break :member;
6398
6399 const imp_name: String.Optional = name: switch (import.name_type) {
6400 .NAME,
6401 .NAME_NOPREFIX,
6402 .NAME_UNDECORATE,
6403 => |tag| {
6404 const symbol_name: []const u8 = import.symbol_name.toSlice(coff);
6405 const end_match = std.mem.endsWith(u8, name_slice, symbol_name);
6406 const len_delta = name_slice.len -% symbol_name.len;
6407 if (!end_match or
6408 (!imp_match and len_delta != 0) or
6409 (imp_match and len_delta != imp_prefix.len))
6410 return comp.link_diags.fail(
6411 "global '{s}' has mismatched symbol name in import header: '{s}'",
6412 .{
6413 name.toSlice(coff),
6414 import.symbol_name.toSlice(coff),
6415 },
6416 );
6417
6418 const imp_name = if (tag == .NAME) import.symbol_name else undecorated: {
6419 var imp_name = std.mem.trimStart(u8, symbol_name, "?@_");
6420 if (tag == .NAME_UNDECORATE)
6421 imp_name = std.mem.sliceTo(imp_name, '@');
6422
6423 try coff.ensureUnusedStringCapacity(imp_name.len);
6424 break :undecorated coff.getOrPutStringAssumeCapacity(imp_name);
6425 };
6426
6427 break :name imp_name.toOptional();
6428 },
6429 .ORDINAL => break :name .none,
6430 else => |t| return comp.link_diags.fail("TODO handle name_type {t}", .{t}),
6431 };
6432
6433 break :import .{
6434 .lib_name = import.lib_name,
6435 .name = imp_name,
6436 .ordinal_hint = import.import_ordinal_hint,
6437 .kind = if (import.type == .CODE and !is_imp) .thunk else .iat_ptr,
6438 };
6439 },
6440 }
6441
6442 if (archive_sym.next == iter) break;
6443 iter = archive_sym.next;
19826444 }
1983 const import_hint_name_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{
1984 .size = import_hint_name_table_len,
1985 .alignment = import_hint_name_align,
1986 .moved = true,
1987 });
1988 gop.value_ptr.* = .{
1989 .import_lookup_table_ni = import_lookup_table_ni,
1990 .import_address_table_si = import_address_table_si,
1991 .import_hint_name_table_ni = import_hint_name_table_ni,
1992 .len = 0,
1993 .hint_name_len = @intCast(import_hint_name_table_len),
6445 }
6446
6447 switch (sym.flags.value_tag) {
6448 .weak_alias_si => {
6449 try coff.aliasGlobal(gmi, sym.value.weak_alias_si);
6450 return true;
6451 },
6452 .weak_alias_name => {
6453 // Convert an unresolved weak external that itself refers to an undef external
6454 // into a (possibly new) global, so it can be resolved separately.
6455 const alias_gop = try coff.getOrPutGlobalSymbol(.{
6456 .name = sym.value.weak_alias_name.toSlice(coff),
6457 });
6458 try coff.aliasGlobal(gmi, alias_gop.value_ptr.si);
6459 return true;
6460 },
6461 else => {},
6462 }
6463
6464 // If there was an object that had the alternate name, we've attempted to load it
6465 if (opt_alt_search_name) |alt_search_name| {
6466 if (coff.globals.get(alt_search_name)) |alias_global| {
6467 try coff.aliasGlobal(gmi, alias_global.si);
6468 return true;
6469 }
6470 }
6471
6472 // Allow importing symbols with no implib entry, if a lib_name was specified.
6473 // This is necessary for certain ntdll symbols, such as LdrRegisterDllNotification,
6474 // which are not in the implib.
6475 if (sym.flags.type != .unknown) {
6476 if (gmi.libName(coff).unwrap()) |lib_name| break :import .{
6477 .lib_name = lib_name,
6478 .name = name.toOptional(),
6479 .ordinal_hint = 0,
6480 .kind = if (sym.flags.type == .code) .thunk else .iat_ptr,
19946481 };
1995 const import_hint_name_slice = import_hint_name_table_ni.slice(&coff.mf);
1996 @memcpy(import_hint_name_slice[0..lib_name.len], lib_name);
1997 @memcpy(import_hint_name_slice[lib_name.len..][0..".dll".len], ".dll");
1998 @memset(import_hint_name_slice[lib_name.len + ".dll".len ..], 0);
1999 coff.nodes.appendAssumeCapacity(.{ .import_lookup_table = @enumFromInt(gop.index) });
2000 coff.nodes.appendAssumeCapacity(.{ .import_address_table = @enumFromInt(gop.index) });
2001 coff.nodes.appendAssumeCapacity(.{ .import_hint_name_table = @enumFromInt(gop.index) });
2002
2003 const import_directory_entries = coff.importDirectoryTableSlice()[gop.index..][0..2];
2004 import_directory_entries.* = .{ .{
2005 .import_lookup_table_rva = coff.computeNodeRva(import_lookup_table_ni),
2006 .time_date_stamp = 0,
2007 .forwarder_chain = 0,
2008 .name_rva = coff.computeNodeRva(import_hint_name_table_ni),
2009 .import_address_table_rva = coff.computeNodeRva(import_address_table_ni),
2010 }, .{
2011 .import_lookup_table_rva = 0,
2012 .time_date_stamp = 0,
2013 .forwarder_chain = 0,
2014 .name_rva = 0,
2015 .import_address_table_rva = 0,
2016 } };
2017 if (target_endian != native_endian)
2018 std.mem.byteSwapAllFields([2]std.coff.ImportDirectoryEntry, import_directory_entries);
20196482 }
6483
6484 return true;
6485 };
6486
6487 try coff.nodes.ensureUnusedCapacity(gpa, 4);
6488 try coff.symbols.ensureUnusedCapacity(gpa, 2);
6489
6490 const target_endian = coff.targetEndian();
6491 const addr_info = coff.targetAddrInfo();
6492 const lib_name = import.lib_name.toSlice(coff);
6493 const gop = try coff.import_table.entries.getOrPutAdapted(
6494 gpa,
6495 lib_name,
6496 ImportTable.Adapter{ .coff = coff },
6497 );
6498 const import_hint_name_align: std.mem.Alignment = .@"2";
6499 if (!gop.found_existing) {
6500 errdefer _ = coff.import_table.entries.pop();
6501 try coff.import_table.ni.resize(
6502 &coff.mf,
6503 gpa,
6504 @sizeOf(std.coff.ImportDirectoryEntry) * (gop.index + 2),
6505 );
6506 const import_hint_name_table_len =
6507 import_hint_name_align.forward(lib_name.len + ".dll".len + 1);
6508 const idata_section_ni = coff.import_table.ni.parent(&coff.mf);
6509 const import_lookup_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{
6510 .size = addr_info.size * 2,
6511 .alignment = addr_info.alignment,
6512 .moved = true,
6513 });
6514 const import_address_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{
6515 .size = addr_info.size * 2,
6516 .alignment = addr_info.alignment,
6517 .moved = true,
6518 });
6519 const import_address_table_si = coff.addSymbolAssumeCapacity();
6520 {
6521 const import_address_table_sym = import_address_table_si.get(coff);
6522 import_address_table_sym.ni = import_address_table_ni;
6523 assert(import_address_table_sym.loc_relocs == .none);
6524 import_address_table_sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
6525 import_address_table_sym.section_number =
6526 coff.getNode(idata_section_ni).object_section.symbol(coff).get(coff).section_number;
6527 }
6528 const import_hint_name_table_ni = try coff.mf.addLastChildNode(gpa, idata_section_ni, .{
6529 .size = import_hint_name_table_len,
6530 .alignment = import_hint_name_align,
6531 .moved = true,
6532 });
6533 gop.value_ptr.* = .{
6534 .import_lookup_table_ni = import_lookup_table_ni,
6535 .import_address_table_si = import_address_table_si,
6536 .import_hint_name_table_ni = import_hint_name_table_ni,
6537 .import_address_table_symbols = .empty,
6538 .len = 0,
6539 .hint_name_len = @intCast(import_hint_name_table_len),
6540 };
6541 const import_hint_name_slice = import_hint_name_table_ni.slice(&coff.mf);
6542 @memcpy(import_hint_name_slice[0..lib_name.len], lib_name);
6543 @memcpy(import_hint_name_slice[lib_name.len..][0..".dll".len], ".dll");
6544 @memset(import_hint_name_slice[lib_name.len + ".dll".len ..], 0);
6545 coff.nodes.appendAssumeCapacity(.{ .import_lookup_table = @enumFromInt(gop.index) });
6546 coff.nodes.appendAssumeCapacity(.{ .import_address_table = @enumFromInt(gop.index) });
6547 coff.nodes.appendAssumeCapacity(.{ .import_hint_name_table = @enumFromInt(gop.index) });
6548
6549 const import_directory_entries = coff.importDirectoryTableSlice()[gop.index..][0..2];
6550 import_directory_entries.* = .{ .{
6551 .import_lookup_table_rva = coff.computeNodeRva(import_lookup_table_ni),
6552 .time_date_stamp = 0,
6553 .forwarder_chain = 0,
6554 .name_rva = coff.computeNodeRva(import_hint_name_table_ni),
6555 .import_address_table_rva = coff.computeNodeRva(import_address_table_ni),
6556 }, .{
6557 .import_lookup_table_rva = 0,
6558 .time_date_stamp = 0,
6559 .forwarder_chain = 0,
6560 .name_rva = 0,
6561 .import_address_table_rva = 0,
6562 } };
6563 if (target_endian != native_endian)
6564 std.mem.byteSwapAllFields([2]std.coff.ImportDirectoryEntry, import_directory_entries);
6565 }
6566
6567 log.debug(
6568 "flushGlobalImport({s}, {?s}, {d}, {s})",
6569 .{ name.toSlice(coff), import.name.toSlice(coff), import.ordinal_hint, lib_name },
6570 );
6571
6572 const iat_symbol_gop = try coff.import_table.iat_symbol_indices.getOrPut(gpa, .{
6573 .iti = @enumFromInt(gop.index),
6574 .name = import.name,
6575 .ordinal_hint = import.ordinal_hint,
6576 });
6577 if (!iat_symbol_gop.found_existing) {
20206578 const import_symbol_index = gop.value_ptr.len;
6579 iat_symbol_gop.value_ptr.* = import_symbol_index;
6580
20216581 gop.value_ptr.len = import_symbol_index + 1;
2022 const new_symbol_table_size = addr_size * (import_symbol_index + 2);
2023 const import_hint_name_index = gop.value_ptr.hint_name_len;
2024 gop.value_ptr.hint_name_len = @intCast(
2025 import_hint_name_align.forward(import_hint_name_index + 2 + name.len + 1),
2026 );
6582 const new_symbol_table_size = addr_info.size * (import_symbol_index + 2);
6583
20276584 try gop.value_ptr.import_lookup_table_ni.resize(&coff.mf, gpa, new_symbol_table_size);
20286585 const import_address_table_ni = gop.value_ptr.import_address_table_si.node(coff);
20296586 try import_address_table_ni.resize(&coff.mf, gpa, new_symbol_table_size);
2030 try gop.value_ptr.import_hint_name_table_ni.resize(&coff.mf, gpa, gop.value_ptr.hint_name_len);
6587
6588 const opt_imp_name = import.name.toSlice(coff);
6589 const opt_import_hint_name_index = if (opt_imp_name) |imp_name| blk: {
6590 const import_hint_name_index = gop.value_ptr.hint_name_len;
6591 gop.value_ptr.hint_name_len = @intCast(
6592 import_hint_name_align.forward(import_hint_name_index + 2 + imp_name.len + 1),
6593 );
6594 try gop.value_ptr.import_hint_name_table_ni.resize(&coff.mf, gpa, gop.value_ptr.hint_name_len);
6595 break :blk import_hint_name_index;
6596 } else null;
6597
6598 const import_hint_name_rva = if (opt_import_hint_name_index) |import_hint_name_index| blk: {
6599 const import_hint_name_slice = gop.value_ptr.import_hint_name_table_ni.slice(&coff.mf);
6600 const ordinal_hint: *u16 = @ptrCast(@alignCast(import_hint_name_slice[import_hint_name_index..][0..2]));
6601 ordinal_hint.* = std.mem.nativeTo(u16, import.ordinal_hint, target_endian);
6602 @memcpy(import_hint_name_slice[import_hint_name_index + 2 ..][0..opt_imp_name.?.len], opt_imp_name.?);
6603 @memset(import_hint_name_slice[import_hint_name_index + 2 + opt_imp_name.?.len ..], 0);
6604 break :blk coff.computeNodeRva(gop.value_ptr.import_hint_name_table_ni) + import_hint_name_index;
6605 } else 0;
6606
20316607 const import_lookup_slice = gop.value_ptr.import_lookup_table_ni.slice(&coff.mf);
20326608 const import_address_slice = import_address_table_ni.slice(&coff.mf);
2033 const import_hint_name_slice = gop.value_ptr.import_hint_name_table_ni.slice(&coff.mf);
2034 @memset(import_hint_name_slice[import_hint_name_index..][0..2], 0);
2035 @memcpy(import_hint_name_slice[import_hint_name_index + 2 ..][0..name.len], name);
2036 @memset(import_hint_name_slice[import_hint_name_index + 2 + name.len ..], 0);
2037 const import_hint_name_rva =
2038 coff.computeNodeRva(gop.value_ptr.import_hint_name_table_ni) + import_hint_name_index;
2039 switch (magic) {
6609 switch (addr_info.magic) {
20406610 _ => unreachable,
20416611 inline .PE32, .@"PE32+" => |ct_magic| {
2042 const Addr = switch (ct_magic) {
2043 _ => comptime unreachable,
2044 .PE32 => u32,
2045 .@"PE32+" => u64,
2046 };
2047 const import_lookup_table: []Addr = @ptrCast(@alignCast(import_lookup_slice));
2048 const import_address_table: []Addr = @ptrCast(@alignCast(import_address_slice));
2049 const import_hint_name_rvas: [2]Addr = .{
2050 std.mem.nativeTo(Addr, @intCast(import_hint_name_rva), target_endian),
2051 std.mem.nativeTo(Addr, 0, target_endian),
6612 const Entry = std.coff.ImportLookupTableEntry(ct_magic);
6613 const import_lookup_table: []Entry = @ptrCast(@alignCast(import_lookup_slice));
6614 const import_address_table: []Entry = @ptrCast(@alignCast(import_address_slice));
6615 var import_hint_name_rvas: [2]Entry = .{
6616 .{
6617 .payload = if (import.name == .none)
6618 .{ .ordinal = .{ .ordinal = import.ordinal_hint } }
6619 else
6620 .{ .hint_name_rva = @intCast(import_hint_name_rva) },
6621 .is_ordinal = import.name == .none,
6622 },
6623 @bitCast(@as(@typeInfo(Entry).@"struct".backing_integer.?, 0)),
20526624 };
6625 if (native_endian != target_endian)
6626 for (&import_hint_name_rvas) |*v| std.mem.byteSwapAllFields(Entry, v);
6627
20536628 import_lookup_table[import_symbol_index..][0..2].* = import_hint_name_rvas;
20546629 import_address_table[import_symbol_index..][0..2].* = import_hint_name_rvas;
20556630 },
20566631 }
2057 const si = gmi.symbol(coff);
2058 const sym = si.get(coff);
2059 sym.section_number = Symbol.Index.text.get(coff).section_number;
2060 assert(sym.loc_relocs == .none);
2061 sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
2062 switch (coff.targetLoad(&coff.headerPtr().machine)) {
2063 else => |tag| @panic(@tagName(tag)),
2064 .AMD64 => {
2065 const init = [_]u8{ 0xff, 0x25, 0x00, 0x00, 0x00, 0x00 };
2066 const target = &comp.root_mod.resolved_target.result;
2067 const ni = try coff.mf.addLastChildNode(gpa, Symbol.Index.text.node(coff), .{
2068 .alignment = switch (comp.root_mod.optimize_mode) {
2069 .Debug,
2070 .ReleaseSafe,
2071 .ReleaseFast,
2072 => target_util.defaultFunctionAlignment(target),
2073 .ReleaseSmall => target_util.minFunctionAlignment(target),
2074 }.toStdMem(),
2075 .size = init.len,
2076 });
2077 @memcpy(ni.slice(&coff.mf)[0..init.len], &init);
2078 sym.ni = ni;
2079 sym.size = init.len;
6632 }
6633
6634 const sym = si.get(coff);
6635 assert(sym.loc_relocs == .none);
6636 const iat_offset: u32 = @intCast(addr_info.size * iat_symbol_gop.value_ptr.*);
6637 switch (import.kind) {
6638 .iat_ptr => {
6639 const iat_sym = gop.value_ptr.import_address_table_si.get(coff);
6640 sym.section_number = iat_sym.section_number;
6641 sym.ni = iat_sym.ni;
6642 sym.setValue(.{ .node_offset = iat_offset });
6643 (try gop.value_ptr.import_address_table_symbols.addOne(gpa)).* = si;
6644 },
6645 .thunk => {
6646 sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
6647
6648 const target = &comp.root_mod.resolved_target.result;
6649 const alignment = switch (comp.root_mod.optimize_mode) {
6650 .Debug,
6651 .ReleaseSafe,
6652 .ReleaseFast,
6653 => target_util.defaultFunctionAlignment(target),
6654 .ReleaseSmall => target_util.minFunctionAlignment(target),
6655 }.toStdMem();
6656 const parent_si = (try coff.pseudoSectionMapIndex(
6657 .@".thunks",
6658 alignment,
6659 .{ .execute = true, .read = true },
6660 )).symbol(coff);
6661
6662 const parent_sym = parent_si.get(coff);
6663 sym.section_number = parent_sym.section_number;
6664
6665 switch (coff.targetLoad(&coff.headerPtr().machine)) {
6666 else => |tag| @panic(@tagName(tag)),
6667 .AMD64 => {
6668 const init = [_]u8{ 0xff, 0x25, 0x00, 0x00, 0x00, 0x00 };
6669 const ni = try coff.mf.addLastChildNode(gpa, parent_sym.ni, .{
6670 .alignment = alignment,
6671 .size = init.len,
6672 });
6673 @memcpy(ni.slice(&coff.mf)[0..init.len], &init);
6674 sym.ni = ni;
6675 sym.extra.size = init.len;
6676 try coff.addReloc(
6677 si,
6678 init.len - 4,
6679 gop.value_ptr.import_address_table_si,
6680 .{ .known = iat_offset },
6681 .{ .AMD64 = .REL32 },
6682 );
6683 },
6684 }
6685 coff.nodes.appendAssumeCapacity(.{ .import_thunk = gmi });
6686 },
6687 }
6688
6689 try si.flushMoved(coff);
6690 return true;
6691}
6692
6693fn flushSpecialSymbol(coff: *Coff, pending: SpecialSymbol) !SpecialSymbol {
6694 const comp = coff.base.comp;
6695
6696 if (!coff.isImage()) return .none;
6697 const gpa = comp.gpa;
6698 const machine = coff.targetLoad(&coff.headerPtr().machine);
6699 const target = &comp.root_mod.resolved_target.result;
6700
6701 return next: switch (pending) {
6702 .entry => {
6703 // TODO: Use explicitly specified entry if set, add err if not found
6704 const entries: []const struct { ?[]const u8, []const u8 } = if (coff.isExe())
6705 if (comp.config.link_libc) switch (coff.optionalHeaderField(.subsystem)) {
6706 .WINDOWS_CUI => &.{
6707 .{ "main", "mainCRTStartup" },
6708 .{ "wmain", "wmainCRTStartup" },
6709 },
6710 .WINDOWS_GUI => &.{
6711 .{ "WinMain", "WinMainCRTStartup" },
6712 .{ "wWinMain", "wWinMainCRTStartup" },
6713 },
6714 else => unreachable,
6715 } else &.{
6716 .{ "wWinMainCRTStartup", "wWinMainCRTStartup" },
6717 }
6718 else
6719 &.{.{ null, if (target.abi.isGnu()) "DllMainCRTStartup" else "_DllMainCRTStartup" }};
6720
6721 const entry_si = for (entries) |entry| {
6722 if (entry[0]) |required_name|
6723 if (coff.getDefinedGlobal(required_name) == .null) continue;
6724
6725 break try coff.globalSymbol(.{ .name = entry[1], .type = .code });
6726 } else .null;
6727
6728 if (entry_si != .null) {
6729 log.debug(
6730 "entry({s}, {d})",
6731 .{ entry_si.get(coff).gmi.name(coff).toSlice(coff), entry_si },
6732 );
6733
6734 try coff.symbols.ensureUnusedCapacity(gpa, 1);
6735 const optional_hdr_si = coff.addSymbolAssumeCapacity();
6736 const optional_hdr_sym = optional_hdr_si.get(coff);
6737 optional_hdr_sym.ni = Node.known.optional_header;
6738 assert(optional_hdr_sym.loc_relocs == .none);
6739 optional_hdr_sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
6740
6741 const optional_hdr = coff.optionalHeaderStandardPtr();
6742 optional_hdr.address_of_entry_point = std.mem.nativeTo(
6743 u32,
6744 entry_si.get(coff).rva,
6745 coff.targetEndian(),
6746 );
6747
6748 try coff.addReloc(
6749 optional_hdr_si,
6750 @intFromPtr(&optional_hdr.address_of_entry_point) - @intFromPtr(optional_hdr),
6751 entry_si,
6752 .{ .known = 0 },
6753 switch (machine) {
6754 else => |tag| @panic(@tagName(tag)),
6755 .AMD64 => .{ .AMD64 = .ADDR32NB },
6756 .I386 => .{ .I386 = .DIR32NB },
6757 },
6758 );
6759 }
6760
6761 // Referencing the startup functions may trigger loading the object containing them,
6762 // we need to wait until that is done before looking for further symbols.
6763 break :next .tls;
6764 },
6765 .tls => {
6766 if (coff.getDefinedGlobal("_tls_used").unwrap()) |tls_used_si| {
6767 log.debug("tlsDir({d})", .{tls_used_si});
6768
6769 const tls_directory = coff.dataDirectoryPtr(.TLS);
6770 tls_directory.* = .{
6771 .virtual_address = tls_used_si.get(coff).rva,
6772 .size = switch (coff.targetLoad(&coff.optionalHeaderStandardPtr().magic)) {
6773 _ => unreachable,
6774 .PE32 => 24,
6775 .@"PE32+" => 40,
6776 },
6777 };
6778 if (coff.targetEndian() != native_endian)
6779 std.mem.byteSwapAllFields(std.coff.ImageDataDirectory, tls_directory);
6780
6781 try coff.symbols.ensureUnusedCapacity(gpa, 1);
6782 const data_dir_si = coff.addSymbolAssumeCapacity();
6783 const data_dir_sym = data_dir_si.get(coff);
6784 data_dir_sym.ni = Node.known.data_directories;
6785 assert(data_dir_sym.loc_relocs == .none);
6786 data_dir_sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
6787
20806788 try coff.addReloc(
2081 si,
2082 init.len - 4,
2083 gop.value_ptr.import_address_table_si,
2084 @intCast(addr_size * import_symbol_index),
2085 .{ .AMD64 = .REL32 },
6789 data_dir_si,
6790 @intFromPtr(&tls_directory.virtual_address) - @intFromPtr(coff.dataDirectorySlice().ptr),
6791 tls_used_si,
6792 .{ .known = 0 },
6793 switch (machine) {
6794 else => |tag| @panic(@tagName(tag)),
6795 .AMD64 => .{ .AMD64 = .ADDR32NB },
6796 .I386 => .{ .I386 = .DIR32NB },
6797 },
20866798 );
2087 },
2088 }
2089 coff.nodes.appendAssumeCapacity(.{ .global = gmi });
2090 sym.rva = coff.computeNodeRva(sym.ni);
2091 si.applyLocationRelocs(coff);
2092 }
6799 }
6800
6801 break :next .none;
6802 },
6803 .none => unreachable,
6804 };
20936805}
20946806
20956807fn 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 {
21196831 }
21206832 assert(sym.loc_relocs == .none);
21216833 sym.loc_relocs = @enumFromInt(coff.relocs.items.len);
6834 if (!isImage(coff) and sym.target_relocs != .none)
6835 try coff.pendingSymbolTableEntry(si);
6836
21226837 break :ni sym.ni;
21236838 };
21246839
......@@ -2138,42 +6853,107 @@ fn flushLazy(coff: *Coff, pt: Zcu.PerThread, lmr: Node.LazyMapRef) !void {
21386853 error.WriteFailed => return nw.err.?,
21396854 else => |e| return e,
21406855 };
2141 si.get(coff).size = @intCast(nw.interface.end);
2142 si.applyLocationRelocs(coff);
6856 si.get(coff).extra.size = @intCast(nw.interface.end);
6857 try si.applyLocationRelocs(coff);
21436858}
21446859
21456860fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void {
6861 log.debug("flushMoved({s}, n{d})", .{ @tagName(coff.getNode(ni)), ni });
21466862 switch (coff.getNode(ni)) {
21476863 .file,
21486864 .header,
21496865 .signature,
6866 => unreachable,
21506867 .coff_header,
21516868 .optional_header,
21526869 .data_directories,
21536870 .section_table,
2154 => unreachable,
2155 .image_section => |si| return coff.targetStore(
2156 &si.get(coff).section_number.header(coff).pointer_to_raw_data,
2157 @intCast(ni.fileLocation(&coff.mf, false).offset),
2158 ),
2159 .import_directory_table => coff.targetStore(
2160 &coff.dataDirectoryPtr(.IMPORT).virtual_address,
2161 coff.computeNodeRva(ni),
2162 ),
6871 .placeholder,
6872 => assert(!coff.isImage()),
6873 .symbol_table,
6874 .string_table,
6875 => |_, tag| {
6876 if (tag == .symbol_table)
6877 coff.targetStore(
6878 &coff.headerPtr().pointer_to_symbol_table,
6879 @intCast(ni.location(&coff.mf).resolve(&coff.mf)[0]),
6880 );
6881
6882 if (!coff.symbol_table.pending_shrink) {
6883 const symbol_table_loc, const symbol_table_size = coff.symbol_table.ni.location(&coff.mf).resolve(&coff.mf);
6884 const string_table_offset, _ = coff.symbol_table.strings_ni.location(&coff.mf).resolve(&coff.mf);
6885 coff.symbol_table.pending_shrink = string_table_offset - (symbol_table_loc + symbol_table_size) > 0;
6886 }
6887 },
6888 .relocation_table => |sn| {
6889 coff.targetStore(
6890 &sn.header(coff).pointer_to_relocations,
6891 @intCast(ni.location(&coff.mf).resolve(&coff.mf)[0]),
6892 );
6893 },
6894 .relocation_table_entry => {},
6895 .archive_member_header => |mi| {
6896 const member = mi.get(coff);
6897 switch (member.kind) {
6898 .first_linker, .second_linker, .longnames => {},
6899 else => coff.targetStore(
6900 &coff.secondLinkerMemberOffsetsSlice()[@intFromEnum(mi) - Member.Index.known_count],
6901 @intCast(ni.fileLocation(&coff.mf, false).offset),
6902 ),
6903 }
6904
6905 if (member.kind == .coff)
6906 try coff.pending_members.put(coff.base.comp.gpa, mi, {});
6907 },
6908 .archive_member,
6909 => {},
6910 .image_section => |si| {
6911 const sym = si.get(coff);
6912 const flags = coff.targetLoad(&sym.section_number.header(coff).flags);
6913 if (!flags.CNT_UNINITIALIZED_DATA) {
6914 const file_offset = if (isArchive(coff))
6915 sym.ni.location(&coff.mf).resolve(&coff.mf)[0]
6916 else
6917 ni.fileLocation(&coff.mf, false).offset;
6918
6919 return coff.targetStore(
6920 &sym.section_number.header(coff).pointer_to_raw_data,
6921 @intCast(file_offset),
6922 );
6923 }
6924 },
6925 .input_section => |isi| {
6926 try isi.symbol(coff).flushMoved(coff);
6927 for (coff.input_symbols.items[@intFromEnum(isi.firstSymbol(coff))..]) |input_symbol| {
6928 if (input_symbol.si.get(coff).ni != ni) break;
6929 try input_symbol.si.flushMoved(coff);
6930 }
6931 },
6932 .import_directory_table => {
6933 _, const size = ni.location(&coff.mf).resolve(&coff.mf);
6934 if (size > 0)
6935 coff.targetStore(
6936 &coff.dataDirectoryPtr(.IMPORT).virtual_address,
6937 coff.computeNodeRva(ni),
6938 );
6939 },
21636940 .import_lookup_table => |import_index| coff.targetStore(
21646941 &coff.importDirectoryEntryPtr(import_index).import_lookup_table_rva,
21656942 coff.computeNodeRva(ni),
21666943 ),
21676944 .import_address_table => |import_index| {
2168 const import_address_table_si = import_index.get(coff).import_address_table_si;
2169 import_address_table_si.flushMoved(coff);
6945 const entry = import_index.get(coff);
6946 const import_address_table_si = entry.import_address_table_si;
6947 try import_address_table_si.flushMoved(coff);
21706948 coff.targetStore(
21716949 &coff.importDirectoryEntryPtr(import_index).import_address_table_rva,
21726950 import_address_table_si.get(coff).rva,
21736951 );
6952
6953 for (entry.import_address_table_symbols.items) |iat_ptr_si|
6954 try iat_ptr_si.flushMoved(coff);
21746955 },
21756956 .import_hint_name_table => |import_index| {
2176 const target_endian = coff.targetEndian();
21776957 const magic = coff.targetLoad(&coff.optionalHeaderStandardPtr().magic);
21786958 const import_hint_name_rva = coff.computeNodeRva(ni);
21796959 coff.targetStore(
......@@ -2186,78 +6966,173 @@ fn flushMoved(coff: *Coff, ni: MappedFile.Node.Index) !void {
21866966 import_entry.import_address_table_si.node(coff).slice(&coff.mf);
21876967 const import_hint_name_slice = ni.slice(&coff.mf);
21886968 const import_hint_name_align = ni.alignment(&coff.mf);
6969
21896970 var import_hint_name_index: u32 = 0;
21906971 for (0..import_entry.len) |import_symbol_index| {
2191 import_hint_name_index = @intCast(import_hint_name_align.forward(
2192 std.mem.indexOfScalarPos(
2193 u8,
2194 import_hint_name_slice,
2195 import_hint_name_index,
2196 0,
2197 ).? + 1,
2198 ));
21996972 switch (magic) {
22006973 _ => unreachable,
22016974 inline .PE32, .@"PE32+" => |ct_magic| {
2202 const Addr = switch (ct_magic) {
2203 _ => comptime unreachable,
2204 .PE32 => u32,
2205 .@"PE32+" => u64,
2206 };
2207 const import_lookup_table: []Addr = @ptrCast(@alignCast(import_lookup_slice));
2208 const import_address_table: []Addr = @ptrCast(@alignCast(import_address_slice));
2209 const rva = std.mem.nativeTo(
2210 Addr,
2211 import_hint_name_rva + import_hint_name_index,
2212 target_endian,
2213 );
2214 import_lookup_table[import_symbol_index] = rva;
2215 import_address_table[import_symbol_index] = rva;
6975 const Entry = std.coff.ImportLookupTableEntry(ct_magic);
6976 const import_lookup_table: []Entry = @ptrCast(@alignCast(import_lookup_slice));
6977 const import_address_table: []Entry = @ptrCast(@alignCast(import_address_slice));
6978
6979 var entry = coff.targetLoad(&import_lookup_table[import_symbol_index]);
6980 if (entry.is_ordinal)
6981 continue;
6982
6983 import_hint_name_index = @intCast(import_hint_name_align.forward(
6984 std.mem.indexOfScalarPos(
6985 u8,
6986 import_hint_name_slice,
6987 import_hint_name_index,
6988 0,
6989 ).? + 1,
6990 ));
6991
6992 entry.payload.hint_name_rva = @intCast(import_hint_name_rva + import_hint_name_index);
6993 import_hint_name_index += 2;
6994
6995 coff.targetStore(&import_lookup_table[import_symbol_index], entry);
6996 coff.targetStore(&import_address_table[import_symbol_index], entry);
22166997 },
22176998 }
2218 import_hint_name_index += 2;
6999 }
7000 },
7001 .export_directory_table => {
7002 const rva = coff.computeNodeRva(ni);
7003 coff.targetStore(&coff.dataDirectoryPtr(.EXPORT).virtual_address, rva);
7004 coff.targetStore(&coff.exportDirectoryTable().name_rva, rva + @sizeOf(std.coff.ExportDirectoryTable));
7005 },
7006 .export_address_table => {
7007 try coff.export_table.export_address_table_si.flushMoved(coff);
7008
7009 // These relocs are applied directly here instead of via the above flushMoved call as
7010 // they are non-contiguous, and not tracked under export_address_table_si.
7011 for (coff.export_table.entries.values()) |entry|
7012 try entry.export_address_table_ri.get(coff).apply(coff);
7013
7014 coff.targetStore(
7015 &coff.exportDirectoryTable().export_address_table_rva,
7016 coff.computeNodeRva(ni),
7017 );
7018 },
7019 .export_name_pointer_table => coff.targetStore(
7020 &coff.exportDirectoryTable().name_pointer_table_rva,
7021 coff.computeNodeRva(ni),
7022 ),
7023 .export_ordinal_table => coff.targetStore(
7024 &coff.exportDirectoryTable().ordinal_table_rva,
7025 coff.computeNodeRva(ni),
7026 ),
7027 .export_name_table => {
7028 const name_table_rva = coff.computeNodeRva(coff.export_table.name_table_ni);
7029 for (
7030 coff.exportNamePointerTableSlice(),
7031 coff.exportOrdinalTableSlice(),
7032 ) |*np, target_ord| {
7033 const ord: ExportTable.Ordinal = @enumFromInt(coff.targetLoad(&target_ord.unbiased_ordinal));
7034 const entry = ord.get(coff);
7035 coff.targetStore(
7036 &np.name_rva,
7037 @intCast(name_table_rva + entry.name_index),
7038 );
22197039 }
22207040 },
22217041 inline .pseudo_section,
22227042 .object_section,
2223 .global,
7043 .import_thunk,
22247044 .nav,
22257045 .uav,
22267046 .lazy_code,
22277047 .lazy_const_data,
2228 => |mi| mi.symbol(coff).flushMoved(coff),
7048 => |mi| try mi.symbol(coff).flushMoved(coff),
7049 .builtin => |si| try si.flushMoved(coff),
22297050 }
22307051 try ni.childrenMoved(coff.base.comp.gpa, &coff.mf);
22317052}
22327053
22337054fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void {
2234 _, const size = ni.location(&coff.mf).resolve(&coff.mf);
7055 const offset, const size = ni.location(&coff.mf).resolve(&coff.mf);
7056 log.debug("flushResized({s}, n{d}, 0x{x})", .{ @tagName(coff.getNode(ni)), ni, size });
7057
22357058 switch (coff.getNode(ni)) {
2236 .file => {},
7059 .file => {
7060 if (coff.isArchive() and coff.members.items.len > 0) {
7061 const last_member = coff.members.items[coff.members.items.len - 1];
7062 // See .archive_member branch for reasoning
7063 assert(Node.known.file.reverseChildren(&coff.mf).ni == last_member.content_ni);
7064 try coff.flushResized(last_member.content_ni);
7065 }
7066 },
22377067 .header => {
2238 switch (coff.optionalHeaderPtr()) {
2239 inline else => |optional_header| coff.targetStore(
2240 &optional_header.size_of_headers,
2241 @intCast(size),
2242 ),
7068 if (coff.isImage()) {
7069 switch (coff.optionalHeaderPtr()) {
7070 inline else => |optional_header| coff.targetStore(
7071 &optional_header.size_of_headers,
7072 @intCast(size),
7073 ),
7074 }
7075
7076 if (size > coff.section_table.values()[0].si.get(coff).rva) try coff.virtualSlide(
7077 0,
7078 std.mem.alignForward(
7079 u32,
7080 @intCast(size * 4),
7081 coff.optionalHeaderField(.section_alignment),
7082 ),
7083 );
22437084 }
2244 if (size > coff.image_section_table.items[0].get(coff).rva) try coff.virtualSlide(
2245 0,
2246 std.mem.alignForward(
2247 u32,
2248 @intCast(size * 4),
2249 coff.optionalHeaderField(.section_alignment),
2250 ),
2251 );
22527085 },
2253 .signature, .coff_header, .optional_header, .data_directories => unreachable,
7086 .signature,
7087 .archive_member_header,
7088 => unreachable,
7089 .archive_member => |mi| {
7090 const content_ni = mi.get(coff).content_ni;
7091 const next_ni = content_ni.next(&coff.mf);
7092 const content_offset, _ = content_ni.location(&coff.mf).resolve(&coff.mf);
7093 const next_offset = switch (next_ni) {
7094 .none => offset: {
7095 assert(content_ni.parent(&coff.mf) == Node.known.file);
7096 // This must take into account the final file size. If there are trailing
7097 // bytes, they will be expected to contain another valid member header
7098 break :offset coff.mf.memory_map.memory.len;
7099 },
7100 else => offset: {
7101 assert(coff.getNode(next_ni) == .archive_member_header);
7102 break :offset next_ni.location(&coff.mf).resolve(&coff.mf)[0];
7103 },
7104 };
7105
7106 // Not inserting IMAGE_ARCHIVE_PAD `\n` byte here, because we are expanding to full size
7107 Member.storeHeaderDecimalStr(&mi.get(coff).headerPtr(coff).size, next_offset - content_offset);
7108 },
7109 .coff_header,
7110 .optional_header,
7111 .data_directories,
7112 => unreachable,
22547113 .section_table => {},
7114 .symbol_table => {
7115 assert(!coff.isImage());
7116 if (!coff.symbol_table.pending_shrink) {
7117 const string_table_offset, _ = coff.symbol_table.strings_ni.location(&coff.mf).resolve(&coff.mf);
7118 coff.symbol_table.pending_shrink =
7119 size > coff.targetLoad(&coff.headerPtr().number_of_symbols) * std.coff.Symbol.sizeOf() or
7120 string_table_offset - (offset + size) > 0;
7121 }
7122 },
7123 .string_table => {
7124 assert(!coff.isImage());
7125 coff.targetStore(coff.symbolTableStringLenPtr(), @intCast(size));
7126 },
7127 .relocation_table,
7128 .relocation_table_entry,
7129 => assert(!coff.isImage()),
22557130 .image_section => |si| {
22567131 const sym = si.get(coff);
22577132 const section_index = sym.section_number.toIndex();
22587133 const section = &coff.sectionTableSlice()[section_index];
22597134 coff.targetStore(&section.size_of_raw_data, @intCast(size));
2260 if (size > coff.targetLoad(&section.virtual_size)) {
7135 if (coff.isImage() and size > coff.targetLoad(&section.virtual_size)) {
22617136 const virtual_size = std.mem.alignForward(
22627137 u32,
22637138 @intCast(size * 4),
......@@ -2266,29 +7141,221 @@ fn flushResized(coff: *Coff, ni: MappedFile.Node.Index) !void {
22667141 coff.targetStore(&section.virtual_size, virtual_size);
22677142 try coff.virtualSlide(section_index + 1, sym.rva + virtual_size);
22687143 }
7144
7145 if (!coff.isImage()) {
7146 if (coff.symbolTableSectionAuxEntryPtr(si.sti(coff))) |aux_ptr|
7147 coff.targetStore(&aux_ptr.length, @intCast(size));
7148 }
22697149 },
2270 .import_directory_table => coff.targetStore(
2271 &coff.dataDirectoryPtr(.IMPORT).size,
2272 @intCast(size),
2273 ),
2274 .import_lookup_table, .import_address_table, .import_hint_name_table => {},
7150 .input_section => {},
7151 .import_directory_table => {
7152 const prev_size = coff.targetLoad(&coff.dataDirectoryPtr(.IMPORT).size);
7153 coff.targetStore(
7154 &coff.dataDirectoryPtr(.IMPORT).size,
7155 @intCast(size),
7156 );
7157 if (prev_size == 0) try coff.flushMoved(ni);
7158 },
7159 .import_lookup_table,
7160 .import_address_table,
7161 .import_hint_name_table,
7162 => {},
7163 .export_directory_table => unreachable,
7164 .export_address_table,
7165 .export_name_pointer_table,
7166 .export_ordinal_table,
7167 .export_name_table,
7168 => {},
22757169 inline .pseudo_section,
22767170 .object_section,
2277 => |smi| smi.symbol(coff).get(coff).size = @intCast(size),
2278 .global, .nav, .uav, .lazy_code, .lazy_const_data => {},
7171 => |smi, tag| {
7172 if (tag == .pseudo_section and smi.name(coff) == .@".edata") {
7173 coff.targetStore(
7174 &coff.dataDirectoryPtr(.EXPORT).size,
7175 @intCast(size),
7176 );
7177 }
7178
7179 var sym = smi.symbol(coff).get(coff);
7180 while (sym.flags.extra_tag == .next_alias_si)
7181 sym = sym.extra.next_alias_si.get(coff);
7182
7183 sym.extra.size = @intCast(size);
7184 },
7185 .import_thunk,
7186 .nav,
7187 .uav,
7188 .lazy_code,
7189 .lazy_const_data,
7190 .builtin,
7191 => {},
7192 .placeholder,
7193 => unreachable,
7194 }
7195}
7196
7197fn flushMember(coff: *Coff, mi: Member.Index) !void {
7198 const member = mi.get(coff);
7199 switch (member.kind) {
7200 .first_linker,
7201 .longnames,
7202 .import,
7203 => unreachable,
7204 .second_linker => {
7205 const Context = struct {
7206 coff: *Coff,
7207 indices: []u16,
7208 strings: []String,
7209
7210 pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool {
7211 return std.mem.lessThan(
7212 u8,
7213 ctx.strings[lhs].toSlice(ctx.coff),
7214 ctx.strings[rhs].toSlice(ctx.coff),
7215 );
7216 }
7217
7218 pub fn swap(ctx: @This(), lhs: usize, rhs: usize) void {
7219 std.mem.swap(u16, &ctx.indices[lhs], &ctx.indices[rhs]);
7220 std.mem.swap(String, &ctx.strings[lhs], &ctx.strings[rhs]);
7221 }
7222 };
7223
7224 // TODO: Does this sort need to also sort by linker input order (if names equal)?
7225 std.sort.pdqContext(0, coff.lib_string_table.items.len, Context{
7226 .coff = coff,
7227 .indices = coff.secondLinkerMemberIndicesSlice(),
7228 .strings = coff.lib_string_table.items,
7229 });
7230
7231 var offset: usize = 0;
7232 var string_table = coff.secondLinkerMemberStringsSlice();
7233 for (coff.lib_string_table.items) |string| {
7234 const str = string.toSlice(coff);
7235 @memcpy(string_table[offset..][0..str.len], str);
7236 string_table[offset + str.len] = 0;
7237 offset += str.len + 1;
7238 }
7239 },
7240 .coff => {
7241 const file_offset: u32 = @intCast(member.header_ni.fileLocation(&coff.mf, false).offset);
7242 const first_linker_offsets = coff.firstLinkerMemberOffsetsSlice();
7243 for (member.first_linker_indices.values()) |mfli|
7244 first_linker_offsets[@intFromEnum(mfli)] = std.mem.nativeTo(u32, file_offset, .big);
7245 },
7246 }
7247}
7248
7249fn flushExportsSort(coff: *Coff) void {
7250 const Context = struct {
7251 coff: *Coff,
7252 np: []std.coff.ExportNamePointerTableEntry,
7253 ord: []std.coff.ExportOrdinalTableEntry,
7254 entries: []ExportTable.Entry,
7255 nt: []const u8,
7256
7257 pub fn lessThan(ctx: *const @This(), lhs: usize, rhs: usize) bool {
7258 const lhs_entry = &ctx.entries[ctx.coff.targetLoad(&ctx.ord[lhs].unbiased_ordinal)];
7259 const rhs_entry = &ctx.entries[ctx.coff.targetLoad(&ctx.ord[rhs].unbiased_ordinal)];
7260 return std.mem.lessThan(
7261 u8,
7262 ctx.nt[lhs_entry.name_index..][0..lhs_entry.name_len],
7263 ctx.nt[rhs_entry.name_index..][0..rhs_entry.name_len],
7264 );
7265 }
7266
7267 pub fn swap(ctx: @This(), lhs: usize, rhs: usize) void {
7268 std.mem.swap(std.coff.ExportNamePointerTableEntry, &ctx.np[lhs], &ctx.np[rhs]);
7269 std.mem.swap(std.coff.ExportOrdinalTableEntry, &ctx.ord[lhs], &ctx.ord[rhs]);
7270 }
7271 };
7272
7273 std.sort.pdqContext(0, coff.export_table.entries.count(), &Context{
7274 .coff = coff,
7275 .np = coff.exportNamePointerTableSlice(),
7276 .ord = coff.exportOrdinalTableSlice(),
7277 .entries = coff.export_table.entries.values(),
7278 .nt = coff.export_table.name_table_ni.slice(&coff.mf),
7279 });
7280}
7281
7282fn flushSectionMerges(coff: *Coff) !void {
7283 while (coff.section_merge_pending_index < coff.section_merges.count()) : (coff.section_merge_pending_index += 1)
7284 try coff.flushSectionMerge(coff.section_merge_pending_index);
7285}
7286
7287fn flushSectionMerge(coff: *Coff, index: u32) !void {
7288 assert(coff.isImage());
7289 const from = coff.section_merges.keys()[index];
7290 const to = coff.section_merges.values()[index];
7291 assert(from != to);
7292
7293 log.debug("flushSectionMerge({s}->{s})", .{ from.toSlice(coff), to.toSlice(coff) });
7294
7295 const opt_to_sec = coff.section_table.getPtr(to);
7296 if (coff.section_table.getPtr(from)) |from_sec| {
7297 const from_sym = from_sec.si.get(coff);
7298 if (opt_to_sec) |to_sec| {
7299 const to_sym = to_sec.si.get(coff);
7300
7301 // TODO: Create a pseudo-section named `from` in `to`, copy `from_sec` ni into that pseudo section
7302 // TODO: Update .section_number for all contained syms
7303 // TODO: Remove `from_sec` from section table (set size = 0 and can do it in flushResized?).
7304 // This is non-trivial as we can't leave holes in the section table.
7305 // TODO: Merge section flags
7306 _ = to_sym;
7307 return coff.base.comp.link_diags.fail("TODO implement section to section merge", .{});
7308 } else if (coff.pseudo_section_table.get(to)) |to_ps_si| {
7309 const to_sym = to_ps_si.get(coff);
7310 if (from_sym.section_number == to_sym.section_number)
7311 return;
7312
7313 // TODO: Same as above, except place `from` into a node in `to_psmi`'s parent
7314 return coff.base.comp.link_diags.fail("TODO implement section to pseudosection merge", .{});
7315 }
7316
7317 // If `to` doesn't exist, /MERGE is defined as renaming `from` to `to`.
7318 // No other path will create image-level sections, so we can safely rename this now
7319 const from_name = &from_sec.si.get(coff).section_number.header(coff).name;
7320 const to_slice = to.toSlice(coff);
7321 @memcpy(from_name[0..to_slice.len], to_slice);
7322 @memset(from_name[to_slice.len..], 0);
7323 } else if (coff.pseudo_section_table.getIndex(from)) |from_index| {
7324 const from_psmi: Node.PseudoSectionMapIndex = @enumFromInt(from_index);
7325 const from_sym = from_psmi.symbol(coff).get(coff);
7326 if (opt_to_sec) |to_sec| {
7327 const to_sym = to_sec.si.get(coff);
7328 if (from_sym.section_number == to_sym.section_number)
7329 return;
7330
7331 // TODO: Move from_psmi's node into to_sec
7332 // TODO: Update .section_number for all contained syms
7333 // TODO: Merge section flags
7334 return coff.base.comp.link_diags.fail("TODO implement pseudosection to section merge", .{});
7335 } else if (coff.pseudo_section_table.get(to)) |to_ps_si| {
7336 const to_sym = to_ps_si.get(coff);
7337 if (from_sym.section_number == to_sym.section_number)
7338 return;
7339
7340 // TODO: Same as above, but move from_psmi's node after to_psmi's node in its parent
7341 return coff.base.comp.link_diags.fail("TODO implement pseudosection to pseudosection merge", .{});
7342 }
7343
7344 // Renaming pseudo-sections have no effect on the output, so this is a no-op.
22797345 }
22807346}
7347
22817348fn virtualSlide(coff: *Coff, start_section_index: usize, start_rva: u32) !void {
22827349 var rva = start_rva;
22837350 for (
2284 coff.image_section_table.items[start_section_index..],
7351 coff.section_table.values()[start_section_index..],
22857352 coff.sectionTableSlice()[start_section_index..],
2286 ) |section_si, *section| {
2287 const section_sym = section_si.get(coff);
7353 ) |*section, *header| {
7354 const section_sym = section.si.get(coff);
22887355 section_sym.rva = rva;
2289 coff.targetStore(&section.virtual_address, rva);
7356 coff.targetStore(&header.virtual_address, rva);
22907357 try section_sym.ni.childrenMoved(coff.base.comp.gpa, &coff.mf);
2291 rva += coff.targetLoad(&section.virtual_size);
7358 rva += coff.targetLoad(&header.virtual_size);
22927359 }
22937360 switch (coff.optionalHeaderPtr()) {
22947361 inline else => |optional_header| coff.targetStore(
......@@ -2303,19 +7370,27 @@ pub fn updateExports(
23037370 pt: Zcu.PerThread,
23047371 exported: Zcu.Exported,
23057372 export_indices: []const Zcu.Export.Index,
7373) link.Error!void {
7374 const diags = &coff.base.comp.link_diags;
7375 return coff.updateExportsInner(pt, exported, export_indices) catch |err| switch (err) {
7376 error.MappedFileIo => return diags.fail(
7377 "failed to write output file: {t}",
7378 .{coff.mf.io_err.?},
7379 ),
7380 else => |e| return e,
7381 };
7382}
7383fn updateExportsInner(
7384 coff: *Coff,
7385 pt: Zcu.PerThread,
7386 exported: Zcu.Exported,
7387 export_indices: []const Zcu.Export.Index,
23067388) !void {
23077389 const zcu = pt.zcu;
23087390 const gpa = zcu.gpa;
23097391 const ip = &zcu.intern_pool;
23107392
2311 switch (exported) {
2312 .nav => |nav| log.debug("updateExports({f})", .{ip.getNav(nav).fqn.fmt(ip)}),
2313 .uav => |uav| log.debug("updateExports(@as({f}, {f}))", .{
2314 Type.fromInterned(ip.typeOf(uav)).fmt(pt),
2315 Value.fromInterned(uav).fmtValue(pt),
2316 }),
2317 }
2318 try coff.symbol_table.ensureUnusedCapacity(gpa, export_indices.len);
7393 try coff.symbols.ensureUnusedCapacity(gpa, export_indices.len);
23197394 const exported_si: Symbol.Index = switch (exported) {
23207395 .nav => |nav| try coff.navSymbol(zcu, nav),
23217396 .uav => |uav| @enumFromInt(@intFromEnum(try coff.lowerUav(
......@@ -2324,62 +7399,299 @@ pub fn updateExports(
23247399 Type.fromInterned(ip.typeOf(uav)).abiAlignment(zcu),
23257400 ))),
23267401 };
7402 switch (exported) {
7403 .nav => |nav| log.debug("updateExports({f}) = {d}", .{ ip.getNav(nav).fqn.fmt(ip), exported_si }),
7404 .uav => |uav| log.debug("updateExports(@as({f}, {f})) = {d}", .{
7405 Type.fromInterned(ip.typeOf(uav)).fmt(pt),
7406 Value.fromInterned(uav).fmtValue(pt),
7407 exported_si,
7408 }),
7409 }
7410 while (try coff.resolve(pt.tid)) {}
23277411 while (try coff.idle(pt.tid)) {}
7412
7413 const machine = coff.targetLoad(&coff.headerPtr().machine);
23287414 const exported_ni = exported_si.node(coff);
23297415 const exported_sym = exported_si.get(coff);
7416 var prev_alias_si = exported_si;
7417
23307418 for (export_indices) |export_index| {
23317419 const @"export" = export_index.ptr(zcu);
2332 const export_si = try coff.globalSymbol(@"export".opts.name.toSlice(ip), null);
7420 const name = @"export".opts.name.toSlice(ip);
7421
7422 // TODO: add an errMsg if this conflicts with an existing symbol
7423 const export_si = try coff.globalSymbol(.{ .name = name });
23337424 const export_sym = export_si.get(coff);
23347425 export_sym.ni = exported_ni;
23357426 export_sym.rva = exported_sym.rva;
2336 export_sym.size = exported_sym.size;
23377427 export_sym.section_number = exported_sym.section_number;
2338 export_si.applyTargetRelocs(coff);
2339 if (@"export".opts.name.eqlSlice("wWinMainCRTStartup", ip)) {
2340 coff.optionalHeaderStandardPtr().address_of_entry_point = exported_sym.rva;
2341 } else if (@"export".opts.name.eqlSlice("_tls_used", ip)) {
2342 const tls_directory = coff.dataDirectoryPtr(.TLS);
2343 tls_directory.* = .{ .virtual_address = exported_sym.rva, .size = exported_sym.size };
2344 if (coff.targetEndian() != native_endian)
2345 std.mem.byteSwapAllFields(std.coff.ImageDataDirectory, tls_directory);
7428 if (@"export".opts.linkage == .weak and !coff.isImage()) {
7429 // exported_si needs to be ahead of export_si in the symbol table,
7430 // so that its sti is known when creating the weak external aux entry
7431 try coff.pendingSymbolTableEntry(exported_si);
7432 export_sym.flags.weak_external_strat = .alias;
7433 export_sym.setValue(.{ .weak_alias_si = exported_si });
7434 }
7435 defer export_si.applyTargetRelocs(coff, .none) catch unreachable;
7436
7437 // The last symbol in the alias list holds the size
7438 const prev_alias_sym = prev_alias_si.get(coff);
7439 switch (prev_alias_sym.flags.extra_tag) {
7440 .size => export_sym.setExtra(.{ .size = prev_alias_sym.extra.size }),
7441 // This export should have been deleted
7442 .next_alias_si => assert(prev_alias_sym.extra.next_alias_si == export_si),
7443 else => unreachable,
7444 }
7445
7446 prev_alias_sym.setExtra(.{ .next_alias_si = export_si });
7447 prev_alias_si = export_si;
7448
7449 if (!coff.isImage()) continue;
7450
7451 const entries_ctx = ExportTable.Adapter{ .coff = coff };
7452 const gop = try coff.export_table.entries.getOrPutAdapted(
7453 gpa,
7454 name,
7455 entries_ctx,
7456 );
7457
7458 if (!gop.found_existing) {
7459 errdefer _ = coff.export_table.entries.pop();
7460
7461 const export_count = coff.export_table.entries.count();
7462 if (export_count > std.math.maxInt(@FieldType(std.coff.ExportDirectoryTable, "number_of_entries")))
7463 return coff.base.comp.link_diags.fail("exceeded maximum number of exports", .{});
7464
7465 const name_index: u32 = @intCast(coff.export_table.name_table_ni.location(&coff.mf).resolve(&coff.mf)[1]);
7466 const new_name_table_size = name_index + name.len + 1;
7467 if (new_name_table_size > std.math.maxInt(@FieldType(ExportTable.Entry, "name_index")))
7468 return coff.base.comp.link_diags.fail("exports name table limit reached", .{});
7469
7470 try coff.export_table.name_table_ni.resize(&coff.mf, gpa, new_name_table_size);
7471
7472 const name_table_slice = coff.export_table.name_table_ni.slice(&coff.mf);
7473 @memcpy(name_table_slice[name_index..][0 .. name.len + 1], name[0 .. name.len + 1]);
7474
7475 // If the new name sorts after the current tail of the sorted list, we don't need to re-sort
7476 {
7477 const ordinal_table_slice = coff.exportOrdinalTableSlice();
7478 if (ordinal_table_slice.len > 0 and !coff.export_table.pending_sort) {
7479 const tail_index: ExportTable.Ordinal =
7480 @enumFromInt(ordinal_table_slice[ordinal_table_slice.len - 1].unbiased_ordinal);
7481 const tail_entry = tail_index.get(coff);
7482 const tail_name = name_table_slice[tail_entry.name_index..][0..tail_entry.name_len];
7483 coff.export_table.pending_sort = std.mem.lessThan(u8, name, tail_name);
7484 }
7485 }
7486
7487 const edt = coff.exportDirectoryTable();
7488 coff.targetStore(&edt.number_of_names, @intCast(export_count));
7489 edt.number_of_entries = edt.number_of_names;
7490
7491 // TODO: These should all be resized ahead of time to fit all exports
7492 // after https://github.com/ziglang/zig/issues/23616
7493 try coff.export_table.export_address_table_si.node(coff).resize(
7494 &coff.mf,
7495 gpa,
7496 export_count * @sizeOf(std.coff.ExportAddressTableEntry),
7497 );
7498
7499 try coff.export_table.name_pointer_table_ni.resize(
7500 &coff.mf,
7501 gpa,
7502 export_count * @sizeOf(std.coff.ExportNamePointerTableEntry),
7503 );
7504
7505 try coff.export_table.ordinal_table_ni.resize(
7506 &coff.mf,
7507 gpa,
7508 export_count * @sizeOf(std.coff.ExportOrdinalTableEntry),
7509 );
7510
7511 coff.targetStore(
7512 &coff.exportNamePointerTableSlice()[gop.index].name_rva,
7513 @intCast(coff.computeNodeRva(coff.export_table.name_table_ni) + name_index),
7514 );
7515 coff.targetStore(
7516 &coff.exportOrdinalTableSlice()[gop.index].unbiased_ordinal,
7517 @intCast(gop.index),
7518 );
7519
7520 gop.value_ptr.* = .{
7521 .si = export_si,
7522 .name_index = @intCast(name_index),
7523 .name_len = @intCast(name.len),
7524 .export_address_table_ri = @enumFromInt(coff.relocs.items.len),
7525 };
7526
7527 try coff.addReloc(
7528 coff.export_table.export_address_table_si,
7529 @intCast(@sizeOf(std.coff.ExportAddressTableEntry) * gop.index),
7530 export_si,
7531 .{ .known = 0 },
7532 switch (machine) {
7533 else => |tag| @panic(@tagName(tag)),
7534 .AMD64 => .{ .AMD64 = .ADDR32NB },
7535 .I386 => .{ .I386 = .DIR32NB },
7536 },
7537 );
7538 } else {
7539 gop.value_ptr.si = export_si;
7540 const reloc = gop.value_ptr.*.export_address_table_ri.get(coff);
7541 reloc.target = export_si;
23467542 }
23477543 }
23487544}
23497545
2350pub fn deleteExport(coff: *Coff, exported: Zcu.Exported, name: InternPool.NullTerminatedString) void {
2351 _ = coff;
2352 _ = exported;
2353 _ = name;
7546pub fn deleteExport(
7547 coff: *Coff,
7548 exported: Zcu.Exported,
7549 name: InternPool.NullTerminatedString,
7550) void {
7551 const zcu = coff.base.comp.zcu.?;
7552 const ip = &zcu.intern_pool;
7553
7554 const exported_si: Symbol.Index = switch (exported) {
7555 .nav => |nav| coff.navs.get(nav).?,
7556 .uav => |uav| coff.uavs.get(uav).?,
7557 };
7558
7559 const name_slice = name.toSlice(ip);
7560 log.debug("deleteExport({s}, {d})", .{ name_slice, exported_si });
7561
7562 // TODO: Delete from first / second linker member table
7563 // TODO: Delete from symbol table inside section
23547564}
23557565
2356pub fn dump(coff: *Coff, tid: Zcu.PerThread.Id) Io.Cancelable!void {
7566fn dumpStderr(coff: *Coff, tid: Zcu.PerThread.Id) !void {
23577567 const comp = coff.base.comp;
23587568 const io = comp.io;
23597569 var buffer: [512]u8 = undefined;
23607570 const stderr = try io.lockStderr(&buffer, null);
23617571 defer io.unlockStderr();
23627572 const w = &stderr.file_writer.interface;
2363 coff.printNode(tid, w, .root, 0) catch |err| switch (err) {
2364 error.WriteFailed => return stderr.err.?,
2365 };
7573 _ = try coff.dump(w, tid);
23667574}
23677575
2368pub fn printNode(
7576pub fn dump(coff: *Coff, w: *Io.Writer, tid: Zcu.PerThread.Id) !link.File.DumpResult {
7577 if (coff.options.enable_link_snapshots) {
7578 try coff.printNode(tid, w, .root, 0);
7579 try w.writeAll("Section table:\n");
7580 for (coff.section_table.keys(), coff.section_table.values()) |name, sec|
7581 try coff.printSection(w, name, sec.si);
7582 try w.writeAll("Symbol table:\n");
7583 for (1..coff.symbols.items.len) |si|
7584 try coff.printSymbol(w, tid, @enumFromInt(si));
7585
7586 return .enabled;
7587 }
7588 return .disabled;
7589}
7590
7591fn printSection(coff: *Coff, w: *Io.Writer, name: String, si: Symbol.Index) !void {
7592 const sym = si.get(coff);
7593 try w.print("{d:0>6}@{d:0>2} {x:08} n{d:0>8} | {s}\n", .{
7594 si,
7595 sym.section_number,
7596 if (sym.flags.extra_tag == .size) sym.extra.size else 0,
7597 sym.ni,
7598 name.toSlice(coff),
7599 });
7600}
7601
7602fn printSymbol(
23697603 coff: *Coff,
7604 w: *Io.Writer,
23707605 tid: Zcu.PerThread.Id,
7606 si: Symbol.Index,
7607) !void {
7608 const sym = si.get(coff);
7609 const node = coff.getNode(sym.ni);
7610 try w.print("{d:0>6}@{d:0>2} {x:08} {s} {s} {s} n{d:0>8}+{x:08}:{t: <26} | {x:08} ", .{
7611 si,
7612 sym.section_number,
7613 if (sym.flags.extra_tag == .size)
7614 @as(u64, sym.extra.size)
7615 else if (sym.ni != .none)
7616 sym.ni.location(&coff.mf).resolve(&coff.mf)[1]
7617 else
7618 0,
7619 switch (sym.flags.value_tag) {
7620 .none => "xx",
7621 .weak_alias_name => "an",
7622 .weak_alias_si => "as",
7623 .node_offset => "no",
7624 },
7625 switch (sym.flags.extra_tag) {
7626 .size => "sz",
7627 .isli => "li",
7628 .next_alias_si => "na",
7629 },
7630 switch (sym.flags.type) {
7631 .unknown => "u",
7632 .code => "c",
7633 .data => "d",
7634 },
7635 sym.ni,
7636 if (sym.flags.value_tag == .node_offset) sym.value.node_offset else 0,
7637 node,
7638 sym.rva,
7639 });
7640
7641 if (sym.gmi != .none) {
7642 try w.print("G {f}\n", .{fmtGlobalName(coff, sym.gmi)});
7643 } else {
7644 try w.writeAll("| ");
7645 try coff.printNodeName(w, tid, node);
7646 if (sym.flags.extra_tag == .isli)
7647 try w.print(" | {s}", .{sym.extra.isli.name(coff).toSlice(coff)});
7648 try w.writeByte('\n');
7649 }
7650}
7651
7652const FmtGlobalName = struct { coff: *Coff, gmi: Node.GlobalMapIndex };
7653
7654fn fmtGlobalName(coff: *Coff, gmi: Node.GlobalMapIndex) std.fmt.Alt(FmtGlobalName, globalNameEscape) {
7655 return .{ .data = .{ .coff = coff, .gmi = gmi } };
7656}
7657
7658fn globalNameEscape(data: FmtGlobalName, w: *std.Io.Writer) std.Io.Writer.Error!void {
7659 if (data.gmi == .none) return;
7660 try w.writeAll(data.gmi.name(data.coff).toSlice(data.coff));
7661 if (data.gmi.libName(data.coff).unwrap()) |lib_name|
7662 try w.print("({s})", .{lib_name.toSlice(data.coff)});
7663}
7664
7665fn printNodeName(
7666 coff: *Coff,
23717667 w: *std.Io.Writer,
2372 ni: MappedFile.Node.Index,
2373 indent: usize,
7668 tid: Zcu.PerThread.Id,
7669 node: Node,
23747670) !void {
2375 const node = coff.getNode(ni);
2376 try w.splatByteAll(' ', indent);
2377 try w.writeAll(@tagName(node));
23787671 switch (node) {
23797672 else => {},
23807673 .image_section => |si| try w.print("({s})", .{
23817674 std.mem.sliceTo(&si.get(coff).section_number.header(coff).name, 0),
23827675 }),
7676 .input_section => |isi| {
7677 const ioi = isi.input(coff);
7678 const is = isi.inputSection(coff);
7679 try w.print("({f}{f}, {s}", .{
7680 ioi.path(coff).fmtEscapeString(),
7681 fmtMemberNameString(ioi.memberName(coff)),
7682 coff.getNode(is.si.node(coff).parent(&coff.mf)).object_section.name(coff).toSlice(coff),
7683 });
7684 if (is.comdat_si != .null) {
7685 const comdat_sym = is.comdat_si.get(coff);
7686 const comdat_name = if (comdat_sym.gmi != .none)
7687 comdat_sym.gmi.name(coff).toSlice(coff)
7688 else
7689 coff.input_symbols.items[@intFromEnum(comdat_sym.extra.isli)].name.toSlice(coff);
7690
7691 try w.print("={s}", .{comdat_name});
7692 }
7693 try w.writeAll(")");
7694 },
23837695 .import_lookup_table,
23847696 .import_address_table,
23857697 .import_hint_name_table,
......@@ -2389,18 +7701,18 @@ pub fn printNode(
23897701 inline .pseudo_section, .object_section => |smi| try w.print("({s})", .{
23907702 smi.name(coff).toSlice(coff),
23917703 }),
2392 .global => |gmi| {
2393 const gn = gmi.globalName(coff);
7704 .import_thunk,
7705 => |gmi| {
23947706 try w.writeByte('(');
2395 if (gn.lib_name.toSlice(coff)) |lib_name| try w.print("{s}.dll, ", .{lib_name});
2396 try w.print("{s})", .{gn.name.toSlice(coff)});
7707 if (gmi.libName(coff).toSlice(coff)) |lib_name| try w.print("{s}.dll, ", .{lib_name});
7708 try w.print("{s})", .{gmi.name(coff).toSlice(coff)});
23977709 },
23987710 .nav => |nmi| {
23997711 const zcu = coff.base.comp.zcu.?;
24007712 const ip = &zcu.intern_pool;
24017713 const nav = ip.getNav(nmi.navIndex(coff));
24027714 try w.print("({f}, {f})", .{
2403 Type.fromInterned(nav.typeOf(ip)).fmt(.{ .zcu = zcu, .tid = tid }),
7715 Type.fromInterned(ip.typeOf(nav.resolved.?.value)).fmt(.{ .zcu = zcu, .tid = tid }),
24047716 nav.fqn.fmt(ip),
24057717 });
24067718 },
......@@ -2418,7 +7730,28 @@ pub fn printNode(
24187730 .tid = tid,
24197731 }),
24207732 }),
7733 .builtin => |si| {
7734 const sym = si.get(coff);
7735 if (sym.gmi != .none) {
7736 try w.writeByte('(');
7737 if (sym.gmi.libName(coff).toSlice(coff)) |lib_name| try w.print("{s}.dll, ", .{lib_name});
7738 try w.print("{s})", .{sym.gmi.name(coff).toSlice(coff)});
7739 }
7740 },
24217741 }
7742}
7743
7744pub fn printNode(
7745 coff: *Coff,
7746 tid: Zcu.PerThread.Id,
7747 w: *Io.Writer,
7748 ni: MappedFile.Node.Index,
7749 indent: usize,
7750) !void {
7751 const node = coff.getNode(ni);
7752 try w.splatByteAll(' ', indent);
7753 try w.writeAll(@tagName(node));
7754 try coff.printNodeName(w, tid, node);
24227755 {
24237756 const mf_node = &coff.mf.nodes.items[@intFromEnum(ni)];
24247757 const off, const size = mf_node.location().resolve(&coff.mf);
......@@ -2446,7 +7779,7 @@ pub fn printNode(
24467779 const line_len = 0x10;
24477780 var line_it = std.mem.window(
24487781 u8,
2449 coff.mf.contents[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)],
7782 coff.mf.memory_map.memory[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)],
24507783 line_len,
24517784 line_len,
24527785 );
src/link/Elf2.zig+11-15
......@@ -3785,7 +3785,7 @@ fn mapInputSection(elf: *Elf, opts: struct {
37853785 const new_alignment: std.mem.Alignment = .fromByteUnits(
37863786 std.math.ceilPowerOfTwoAssert(usize, @intCast(opts.addralign)),
37873787 );
3788 try existing_shndx.get(elf).ni.realign(&elf.mf, gpa, new_alignment);
3788 try existing_shndx.get(elf).ni.realign(&elf.mf, gpa, new_alignment, .{ .set_alignment = true });
37893789 }
37903790 // ...and update the shdr as needed.
37913791 switch (elf.shdrPtr(existing_shndx)) {
......@@ -3948,7 +3948,7 @@ fn uavMapIndex(
39483948 } else {
39493949 const node = uav_gop.value_ptr.lsi.index().ptr(elf).node;
39503950 if (resolved_align.toStdMem().order(node.alignment(&elf.mf)).compare(.gt)) {
3951 try node.realign(&elf.mf, gpa, resolved_align.toStdMem());
3951 try node.realign(&elf.mf, gpa, resolved_align.toStdMem(), .{ .set_alignment = true });
39523952 }
39533953 }
39543954 return umi;
......@@ -4677,7 +4677,7 @@ fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadPars
46774677 // We have a copy relocation for this global, but the amount of space we
46784678 // reserved for it could be too small or underaligned!
46794679 try copied_global.node.resize(&elf.mf, gpa, gop.value_ptr.size);
4680 try copied_global.node.realign(&elf.mf, gpa, gop.value_ptr.alignment);
4680 try copied_global.node.realign(&elf.mf, gpa, gop.value_ptr.alignment, .{ .set_alignment = true });
46814681 const global_ptr = elf.globalByName(name).?;
46824682 switch (elf.symPtr(global_ptr.symtab_index)) {
46834683 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
67116711 _ = name;
67126712}
67136713
6714pub fn dump(elf: *Elf, tid: Zcu.PerThread.Id) Io.Cancelable!void {
6715 const comp = elf.base.comp;
6716 const io = comp.io;
6717 var buffer: [512]u8 = undefined;
6718 const stderr = try io.lockStderr(&buffer, null);
6719 defer io.lockStderr();
6720 const w = &stderr.file_writer.interface;
6721 elf.printNode(tid, w, .root, 0) catch |err| switch (err) {
6722 error.WriteFailed => return stderr.err.?,
6723 };
6714pub fn dump(elf: *Elf, w: *Io.Writer, tid: Zcu.PerThread.Id) !link.File.DumpResult {
6715 if (elf.options.enable_link_snapshots) {
6716 try elf.printNode(tid, w, .root, 0);
6717 return .enabled;
6718 }
6719 return .disabled;
67246720}
67256721
67266722pub fn printNode(
......@@ -6764,13 +6760,13 @@ pub fn printNode(
67646760 elf.getNode(isi.node(elf).parent(&elf.mf)).section.name(elf).slice(elf),
67656761 });
67666762 },
6767 .copied_global => |name| try w.print("(copy:{s})", .{name}),
6763 .copied_global => |name| try w.print("(copy:{s})", .{name.slice(elf)}),
67686764 .nav => |nmi| {
67696765 const zcu = elf.base.comp.zcu.?;
67706766 const ip = &zcu.intern_pool;
67716767 const nav = ip.getNav(nmi.navIndex(elf));
67726768 try w.print("({f}, {f})", .{
6773 Type.fromInterned(nav.typeOf(ip)).fmt(.{ .zcu = zcu, .tid = tid }),
6769 Type.fromInterned(ip.typeOf(nav.resolved.?.value)).fmt(.{ .zcu = zcu, .tid = tid }),
67746770 nav.fqn.fmt(ip),
67756771 });
67766772 },
src/link/MappedFile.zig+362-17
......@@ -26,6 +26,9 @@ updates: std.ArrayList(Node.Index),
2626update_prog_node: std.Progress.Node,
2727writers: std.SinglyLinkedList,
2828io_err: ?IoError,
29/// If locked, modifying the node layout is not allowed.
30/// Modifying node content is always allowed.
31nodes_lock: std.debug.SafetyLock = .{},
2932
3033pub const growth_factor = 4;
3134
......@@ -188,6 +191,10 @@ pub const Node = extern struct {
188191 return ni.get(mf).parent;
189192 }
190193
194 pub fn next(ni: Node.Index, mf: *const MappedFile) Node.Index {
195 return ni.get(mf).next;
196 }
197
191198 pub fn ChildIterator(comptime direction: enum { prev, next }) type {
192199 return struct {
193200 mf: *const MappedFile,
......@@ -330,6 +337,7 @@ pub const Node = extern struct {
330337 }
331338
332339 pub fn resize(ni: Node.Index, mf: *MappedFile, gpa: std.mem.Allocator, size: u64) Error!void {
340 defer if (std.debug.runtime_safety) mf.verify();
333341 mf.resizeNode(gpa, ni, size) catch |err| switch (err) {
334342 error.OutOfMemory,
335343 error.Canceled,
......@@ -346,16 +354,23 @@ pub const Node = extern struct {
346354 }
347355 }
348356
357 pub const RealignNodeOptions = struct {
358 /// Shift the node backwards if possible
359 try_backwards: bool = true,
360 /// If `set, persists `new_alignment` as the node's alignment for future operations.
361 set_alignment: bool = true,
362 };
363
349364 /// Moves and expands a node such that its offset and size are aligned to `new_alignment`.
350 ///
351365 /// Asserts that `ni` is not `Node.Index.root`.
352366 pub fn realign(
353367 ni: Node.Index,
354368 mf: *MappedFile,
355369 gpa: std.mem.Allocator,
356370 new_alignment: std.mem.Alignment,
371 opts: RealignNodeOptions,
357372 ) Error!void {
358 mf.realignNode(gpa, ni, new_alignment) catch |err| switch (err) {
373 mf.realignNode(gpa, ni, new_alignment, opts) catch |err| switch (err) {
359374 error.OutOfMemory,
360375 error.Canceled,
361376 => |e| return e,
......@@ -371,6 +386,26 @@ pub const Node = extern struct {
371386 }
372387 }
373388
389 /// Shrink a node to `size`, exactly.
390 /// Asserts that the new size can contain all the children.
391 /// If `shift_next` is set, then the following node is shifted backwards into
392 /// the free space as much as alignment allows.
393 /// Asserts that `size` is >= the end of the last child node.
394 pub fn shrink(
395 ni: Node.Index,
396 mf: *MappedFile,
397 gpa: std.mem.Allocator,
398 size: u64,
399 shift_next: bool,
400 ) Error!void {
401 try mf.shrinkNode(gpa, ni, size, shift_next);
402 var writers_it = mf.writers.first;
403 while (writers_it) |writer_node| : (writers_it = writer_node.next) {
404 const w: *Node.Writer = @fieldParentPtr("writer_node", writer_node);
405 w.interface.buffer = w.ni.slice(mf);
406 }
407 }
408
374409 pub fn writer(ni: Node.Index, mf: *MappedFile, gpa: std.mem.Allocator, w: *Writer) void {
375410 w.* = .{
376411 .gpa = gpa,
......@@ -530,7 +565,26 @@ fn addNode(mf: *MappedFile, gpa: std.mem.Allocator, opts: struct {
530565 add_node: AddNodeOptions,
531566}) Error!Node.Index {
532567 if (opts.add_node.moved or opts.add_node.resized) try mf.updates.ensureUnusedCapacity(gpa, 1);
568 mf.nodes_lock.assertUnlocked();
533569 const offset = opts.add_node.alignment.forward(@intCast(opts.offset));
570 if (opts.parent != .none) {
571 const new_end = offset + opts.add_node.size;
572 switch (opts.next) {
573 .none => {
574 _, const parent_size = opts.parent.location(mf).resolve(mf);
575 if (new_end > parent_size)
576 try opts.parent.resize(mf, gpa, new_end);
577 },
578 else => |next_ni| {
579 const next_offset, _ = next_ni.location(mf).resolve(mf);
580 if (new_end > next_offset)
581 try next_ni.realign(mf, gpa, opts.add_node.alignment, .{
582 .try_backwards = false,
583 .set_alignment = false,
584 });
585 },
586 }
587 }
534588 const location_tag: Node.Location.Tag, const location_payload: Node.Location.Payload = location: {
535589 if (std.math.cast(u32, offset)) |small_offset| break :location .{ .small, .{
536590 .small = .{ .offset = small_offset, .size = 0 },
......@@ -572,14 +626,12 @@ fn addNode(mf: *MappedFile, gpa: std.mem.Allocator, opts: struct {
572626 },
573627 .location_payload = location_payload,
574628 };
629
575630 {
576 defer {
577 free_node.flags.moved = false;
578 free_node.flags.resized = false;
579 }
580 _, const parent_size = opts.parent.location(mf).resolve(mf);
581 if (offset > parent_size) try opts.parent.resize(mf, gpa, offset);
582631 try free_ni.resize(mf, gpa, opts.add_node.size);
632 if (opts.add_node.moved or opts.add_node.resized) try mf.updates.ensureUnusedCapacity(gpa, 1);
633 free_node.flags.moved = false;
634 free_node.flags.resized = false;
583635 }
584636 if (opts.add_node.moved) free_ni.movedAssumeCapacity(mf);
585637 if (opts.add_node.resized) free_ni.resizedAssumeCapacity(mf);
......@@ -666,11 +718,63 @@ pub fn addNodeAfter(
666718 });
667719}
668720
669fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested_size: u64) (Allocator.Error || Io.Cancelable || IoError)!void {
721fn shrinkNode(
722 mf: *MappedFile,
723 gpa: std.mem.Allocator,
724 ni: Node.Index,
725 size: u64,
726 shift_next: bool,
727) !void {
728 mf.nodes_lock.assertUnlocked();
729 const node = ni.get(mf);
730 const old_offset, _ = node.location().resolve(mf);
731
732 // This would require unmapping first
733 assert(ni != Node.Index.root);
734 defer if (std.debug.runtime_safety) mf.verify();
735
736 if (node.last != .none) {
737 const last = node.last.get(mf);
738 const last_offset, const last_size = last.location().resolve(mf);
739 assert(last_offset + last_size > size);
740 }
741
742 try mf.large.ensureUnusedCapacity(gpa, 4);
743 try mf.updates.ensureUnusedCapacity(gpa, 2);
744
745 ni.setLocationAssumeCapacity(mf, old_offset, size);
746 if (!shift_next or node.next == .none) return;
747
748 const next = node.next.get(mf);
749 const old_next_offset, const next_size = next.location().resolve(mf);
750 const padding = old_next_offset - (old_offset + size);
751 const new_next_offset = next.flags.alignment.forward(@intCast(old_next_offset - padding));
752
753 if (next.flags.has_content and new_next_offset < old_next_offset) {
754 const old_file_offset = node.next.fileLocation(mf, false).offset;
755 const new_file_offset = (old_file_offset - old_next_offset) + new_next_offset;
756 @memmove(
757 mf.memory_map.memory[@intCast(new_file_offset)..][0..@intCast(next_size)],
758 mf.memory_map.memory[@intCast(old_file_offset)..][0..@intCast(next_size)],
759 );
760 @memset(mf.memory_map.memory[@intCast(new_file_offset + next_size)..@intCast(old_file_offset + next_size)], 0);
761 }
762
763 node.next.setLocationAssumeCapacity(mf, new_next_offset, next_size);
764}
765
766fn resizeNode(
767 mf: *MappedFile,
768 gpa: std.mem.Allocator,
769 ni: Node.Index,
770 requested_size: u64,
771) (Allocator.Error || Io.Cancelable || IoError)!void {
772 mf.nodes_lock.assertUnlocked();
670773 const io = mf.io;
671774 const node = ni.get(mf);
672775 const old_offset, const old_size = node.location().resolve(mf);
673776 const new_size = node.flags.alignment.forward(@intCast(requested_size));
777
674778 // Resize the entire file
675779 if (ni == Node.Index.root) {
676780 try mf.ensureCapacityForSetLocation(gpa);
......@@ -703,6 +807,17 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested
703807 if (is_linux and !mf.flags.fallocate_insert_range_unsupported and
704808 node.flags.alignment.order(mf.flags.block_size).compare(.gte))
705809 insert_range: {
810 const range_file_offset = ni.fileLocation(mf, false).offset + old_size;
811 const range_size = node.flags.alignment.forward(
812 @intCast(requested_size +| requested_size / growth_factor),
813 ) - old_size;
814
815 // If this node is being realigned, its current state might not
816 // meet the requirements for fallocate
817 if (!mf.flags.block_size.check(@intCast(range_file_offset)) or
818 !mf.flags.block_size.check(@intCast(range_size)))
819 break :insert_range;
820
706821 mf.memory_map.write(io) catch |err| switch (err) {
707822 error.WouldBlock => return error.Unexpected, // file was not opened as non-blocking
708823 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
712827 const last_offset, const last_size = parent.last.location(mf).resolve(mf);
713828 const last_end = last_offset + last_size;
714829 assert(last_end <= old_parent_size);
715 const range_file_offset = ni.fileLocation(mf, false).offset + old_size;
716 const range_size = node.flags.alignment.forward(
717 @intCast(requested_size +| requested_size / growth_factor),
718 ) - old_size;
719830 _, const file_size = Node.Index.root.location(mf).resolve(mf);
720831 while (true) switch (linux.errno(switch (std.math.order(range_file_offset, file_size)) {
721832 .lt => linux.fallocate(
......@@ -814,6 +925,7 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested
814925 var last_fixed_ni = ni;
815926 var first_floating_ni = node.next;
816927 var shift = new_size - old_size;
928 var max_shift_align: std.mem.Alignment = .@"1";
817929 var direction: enum { forward, reverse } = .forward;
818930 while (true) {
819931 assert(last_fixed_ni != .none);
......@@ -830,10 +942,12 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested
830942 if (new_last_fixed_offset + last_fixed_size <= old_first_floating_offset)
831943 break :make_space;
832944 assert(direction == .forward);
945 max_shift_align = max_shift_align.max(first_floating.flags.alignment.max(last_fixed.flags.alignment));
833946 if (first_floating.flags.fixed) {
834 shift = first_floating.flags.alignment.forward(@intCast(
947 shift = max_shift_align.forward(@intCast(
835948 @max(shift, first_floating_size),
836949 ));
950
837951 // Not enough space, try the next node
838952 last_fixed_ni = first_floating_ni;
839953 first_floating_ni = first_floating.next;
......@@ -842,7 +956,7 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested
842956 // Move the found floating node to make space for preceding fixed nodes
843957 const last = parent.last.get(mf);
844958 const last_offset, const last_size = last.location().resolve(mf);
845 const new_first_floating_offset = first_floating.flags.alignment.forward(
959 const new_first_floating_offset = max_shift_align.forward(
846960 @intCast(@max(new_last_fixed_offset + last_fixed_size, last_offset + last_size)),
847961 );
848962 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
9031017 last_fixed_ni.setLocationAssumeCapacity(
9041018 mf,
9051019 old_last_fixed_offset,
906 last_fixed_size + shift,
1020 new_size,
9071021 );
9081022 return;
9091023 }
......@@ -929,8 +1043,10 @@ fn realignNode(
9291043 gpa: std.mem.Allocator,
9301044 ni: Node.Index,
9311045 new_alignment: std.mem.Alignment,
1046 opts: Node.Index.RealignNodeOptions,
9321047) (Allocator.Error || Io.Cancelable || IoError)!void {
9331048 assert(ni != Node.Index.root); // currently unsupported
1049 mf.nodes_lock.assertUnlocked();
9341050
9351051 const node = ni.get(mf);
9361052 const old_offset, const size = node.location().resolve(mf);
......@@ -939,7 +1055,12 @@ fn realignNode(
9391055
9401056 defer if (std.debug.runtime_safety) mf.verify();
9411057
1058 const prev_alignment = node.flags.alignment;
9421059 node.flags.alignment = new_alignment;
1060 defer {
1061 // alignment needs to be temporarily set for the resizes below
1062 if (!opts.set_alignment) node.flags.alignment = prev_alignment;
1063 }
9431064
9441065 const new_size = node.flags.alignment.forward(@intCast(size));
9451066 if (new_alignment.check(@intCast(old_offset))) {
......@@ -956,6 +1077,37 @@ fn realignNode(
9561077 },
9571078 };
9581079
1080 if (opts.try_backwards) {
1081 const backward_offset = new_alignment.backward(@intCast(old_offset));
1082 const prev_end = if (node.prev == .none) 0 else prev: {
1083 const prev_offset, const prev_size = node.prev.location(mf).resolve(mf);
1084 break :prev prev_offset + prev_size;
1085 };
1086
1087 if (backward_offset >= prev_end) {
1088 try mf.ensureCapacityForSetLocation(gpa);
1089
1090 if (node.flags.has_content) {
1091 const old_file_offset = ni.fileLocation(mf, false).offset;
1092 const new_file_offset = (old_file_offset - old_offset) + backward_offset;
1093 @memmove(
1094 mf.memory_map.memory[@intCast(new_file_offset)..][0..@intCast(size)],
1095 mf.memory_map.memory[@intCast(old_file_offset)..][0..@intCast(size)],
1096 );
1097 @memset(mf.memory_map.memory[@intCast(new_file_offset + size)..@intCast(old_file_offset + size)], 0);
1098 }
1099
1100 if (backward_offset + new_size <= trailing_end) {
1101 ni.setLocationAssumeCapacity(mf, backward_offset, new_size);
1102 } else {
1103 ni.setLocationAssumeCapacity(mf, backward_offset, size);
1104 try mf.resizeNode(gpa, ni, new_size);
1105 }
1106
1107 return;
1108 }
1109 }
1110
9591111 const forward_offset = new_alignment.forward(@intCast(old_offset));
9601112 if (forward_offset + new_size <= trailing_end) {
9611113 // Shift into the free space if possible
......@@ -1135,6 +1287,12 @@ fn ensureTotalCapacityPreciseInner(mf: *MappedFile, new_capacity: usize) (Alloca
11351287 error.OperationUnsupported => {},
11361288 else => |e| return e,
11371289 }
1290
1291 mf.memory_map.write(io) catch |err| switch (err) {
1292 error.WouldBlock => return error.Unexpected, // file was not opened as non-blocking
1293 error.NotOpenForWriting => return error.Unexpected, // we definitely opened the file for writing
1294 else => |e| return e,
1295 };
11381296 unmap(mf);
11391297 }
11401298
......@@ -1156,11 +1314,12 @@ pub fn unmap(mf: *MappedFile) void {
11561314}
11571315
11581316pub fn flush(mf: *MappedFile) (Io.Cancelable || error{MappedFileIo})!void {
1159 mf.memory_map.write(mf.io) catch |err| switch (err) {
1317 mf.flushInner() catch |err| switch (err) {
11601318 error.Canceled => |e| return e,
11611319
11621320 error.WouldBlock, // file was not opened as non-blocking
11631321 error.NotOpenForWriting, // we definitely opened the file for writing
1322 error.ReadOnlyFileSystem,
11641323 => {
11651324 mf.io_err = error.Unexpected;
11661325 return error.MappedFileIo;
......@@ -1173,6 +1332,11 @@ pub fn flush(mf: *MappedFile) (Io.Cancelable || error{MappedFileIo})!void {
11731332 };
11741333}
11751334
1335fn flushInner(mf: *MappedFile) (Io.File.WritePositionalError || Io.File.SetTimestampsError)!void {
1336 try mf.memory_map.write(mf.io);
1337 if (is_windows) try mf.memory_map.file.setTimestampsNow(mf.io);
1338}
1339
11761340fn verify(mf: *MappedFile) void {
11771341 const root = Node.Index.root.get(mf);
11781342 assert(root.parent == .none);
......@@ -1207,3 +1371,184 @@ fn verifyNode(mf: *MappedFile, parent_ni: Node.Index) void {
12071371 ni = node.next;
12081372 }
12091373}
1374
1375const testing = std.testing;
1376fn testVerifyContent(mf: *@This(), ni: Node.Index, value: u8, init_len: usize) !void {
1377 // Not using std.mem.allEqual, so we can get useful output
1378 const slice = ni.slice(mf);
1379 var buf: [256]u8 = undefined;
1380 @memset(buf[0..init_len], value);
1381 @memset(buf[init_len..], 0);
1382 try testing.expectEqualSlices(u8, buf[0..slice.len], slice);
1383}
1384
1385test {
1386 const gpa = testing.allocator;
1387
1388 var tmp_dir = testing.tmpDir(.{});
1389 defer tmp_dir.cleanup();
1390
1391 var file = try tmp_dir.dir.createFile(testing.io, "test.mf", .{ .read = true });
1392 defer file.close(testing.io);
1393
1394 var mf = try init(file, gpa, testing.io);
1395 defer mf.deinit(gpa);
1396
1397 const a = try mf.addFirstChildNode(gpa, .root, .{ .fixed = true, .alignment = .@"4" });
1398 const c = try mf.addLastChildNode(gpa, .root, .{ .fixed = true, .alignment = .@"4" });
1399 const b = try mf.addNodeAfter(gpa, a, .{ .fixed = true, .alignment = .@"16" });
1400 const d = try mf.addNodeAfter(gpa, b, .{ .alignment = .@"4" });
1401
1402 const a_init_size = 8;
1403 const b_init_size = 16;
1404 const c_init_size = 24;
1405 const d_init_size = 28;
1406
1407 // Resize without content
1408 {
1409 // Verify size is aligned forward
1410 try d.resize(&mf, gpa, d_init_size - 1);
1411 try a.resize(&mf, gpa, a_init_size - 2);
1412 try c.resize(&mf, gpa, c_init_size);
1413 try b.resize(&mf, gpa, b_init_size);
1414 mf.verify();
1415
1416 const a_loc, const a_size = a.location(&mf).resolve(&mf);
1417 const b_loc, const b_size = b.location(&mf).resolve(&mf);
1418 const c_loc, const c_size = c.location(&mf).resolve(&mf);
1419 _, const d_size = d.location(&mf).resolve(&mf);
1420 try testing.expect(a_size >= a_init_size);
1421 try testing.expect(b_size >= b_init_size);
1422 try testing.expect(c_size >= c_init_size);
1423 try testing.expect(d_size >= d_init_size);
1424 try testing.expect(b_loc >= a_loc + a_size);
1425 try testing.expect(c_loc >= b_loc + b_size);
1426 }
1427
1428 const a_exp_size = 24;
1429 const b_exp_size = 28;
1430 const c_exp_size = 48;
1431 const d_exp_size = 32;
1432
1433 // Resize with content
1434 {
1435 @memset(a.slice(&mf)[0..a_init_size], 0xaa);
1436 @memset(b.slice(&mf)[0..b_init_size], 0xbb);
1437 @memset(c.slice(&mf)[0..c_init_size], 0xcc);
1438 @memset(d.slice(&mf)[0..d_init_size], 0xdd);
1439
1440 try a.resize(&mf, gpa, a_exp_size);
1441 try b.resize(&mf, gpa, b_exp_size);
1442 try c.resize(&mf, gpa, c_exp_size);
1443 try d.resize(&mf, gpa, d_exp_size);
1444 mf.verify();
1445
1446 const a_loc, const a_size = a.location(&mf).resolve(&mf);
1447 const b_loc, const b_size = b.location(&mf).resolve(&mf);
1448 const c_loc, const c_size = c.location(&mf).resolve(&mf);
1449 _, const d_size = d.location(&mf).resolve(&mf);
1450 try testing.expect(a_size >= a_exp_size);
1451 try testing.expect(b_size >= b_exp_size);
1452 try testing.expect(c_size >= c_exp_size);
1453 try testing.expect(d_size >= d_exp_size);
1454 try testing.expect(b_loc >= a_loc + a_size);
1455 try testing.expect(c_loc >= b_loc + b_size);
1456
1457 try testVerifyContent(&mf, a, 0xaa, a_init_size);
1458 try testVerifyContent(&mf, b, 0xbb, b_init_size);
1459 try testVerifyContent(&mf, c, 0xcc, c_init_size);
1460 try testVerifyContent(&mf, d, 0xdd, d_init_size);
1461 }
1462
1463 const child_init: []const struct { std.mem.Alignment, usize } = &.{
1464 .{ .@"16", 16 },
1465 .{ .@"1", 1 },
1466 .{ .@"1", 19 },
1467 .{ .@"1", 3 },
1468 .{ .@"8", 30 },
1469 .{ .@"2", 5 },
1470 .{ .@"1", 60 },
1471 .{ .@"2", 2 },
1472 .{ .@"16", 32 },
1473 };
1474
1475 var children: [child_init.len]Node.Index = undefined;
1476
1477 // Differently-aligned fixed sibling nodes
1478 {
1479 for (children[0 .. children.len - 1], child_init[0 .. children.len - 1], 0..) |*ni, opts, i| {
1480 ni.* = try mf.addLastChildNode(gpa, b, .{
1481 .alignment = opts.@"0",
1482 .size = opts.@"1",
1483 .fixed = true,
1484 });
1485
1486 @memset(ni.slice(&mf)[0..opts.@"1"], @intCast(i + 1));
1487 }
1488 // Shift differently-aligned nodes by inserting a node
1489 children[children.len - 1] = try mf.addNodeAfter(gpa, children[3], .{
1490 .alignment = child_init[children.len - 1].@"0",
1491 .size = child_init[children.len - 1].@"1",
1492 .fixed = true,
1493 });
1494 @memset(children[children.len - 1].slice(&mf), @intCast(children.len));
1495
1496 mf.verify();
1497 for (children, child_init, 0..) |ni, opts, i| {
1498 try testVerifyContent(&mf, ni, @intCast(i + 1), opts.@"1");
1499 }
1500 }
1501
1502 // Shifting child nodes forward due via resize of parent.prev
1503 {
1504 try testing.expect(a.location(&mf).resolve(&mf)[1] < 64);
1505 try a.resize(&mf, gpa, 64);
1506
1507 try testVerifyContent(&mf, a, 0xaa, a_init_size);
1508 try testVerifyContent(&mf, c, 0xcc, c_init_size);
1509 try testVerifyContent(&mf, d, 0xdd, d_init_size);
1510 for (children, child_init, 0..) |ni, opts, i| {
1511 try testVerifyContent(&mf, ni, @intCast(i + 1), opts.@"1");
1512 }
1513 }
1514
1515 // Re-align last node into trailing free space within parent
1516 {
1517 try b.resize(&mf, gpa, b.location(&mf).resolve(&mf)[1] + 64);
1518
1519 const last = children[children.len - 2];
1520 try last.realign(&mf, gpa, .@"4", true);
1521 mf.verify();
1522
1523 for (children, child_init, 0..) |ni, opts, i|
1524 try testVerifyContent(&mf, ni, @intCast(i + 1), opts.@"1");
1525 try testVerifyContent(&mf, c, 0xcc, c_init_size);
1526 }
1527
1528 // Re-align, shifting sibling nodes
1529 {
1530 try children[1].realign(&mf, gpa, .@"8", true);
1531 mf.verify();
1532
1533 for (children, child_init, 0..) |ni, opts, i|
1534 try testVerifyContent(&mf, ni, @intCast(i + 1), opts.@"1");
1535 try testVerifyContent(&mf, c, 0xcc, c_init_size);
1536 }
1537
1538 // Shrink and shift start of trailing node into free space
1539 {
1540 try mf.shrinkNode(gpa, a, 16, true);
1541 mf.verify();
1542
1543 const a_loc, const a_size = a.location(&mf).resolve(&mf);
1544 const b_loc, _ = b.location(&mf).resolve(&mf);
1545 try testing.expectEqual(b_loc, a_loc + a_size);
1546
1547 try testVerifyContent(&mf, a, 0xaa, a_init_size);
1548 try testVerifyContent(&mf, c, 0xcc, c_init_size);
1549 try testVerifyContent(&mf, d, 0xdd, d_init_size);
1550 for (children, child_init, 0..) |ni, opts, i| {
1551 try testVerifyContent(&mf, ni, @intCast(i + 1), opts.@"1");
1552 }
1553 }
1554}
src/main.zig+2-2
......@@ -1440,8 +1440,8 @@ fn buildOutputType(
14401440 dev.check(.stdio_listen);
14411441 listen = .stdio;
14421442 } else if (mem.eql(u8, arg, "--debug-link-snapshot")) {
1443 if (!build_options.enable_link_snapshots) {
1444 warn("Zig was compiled without linker snapshots enabled (-Dlink-snapshot). --debug-link-snapshot has no effect.", .{});
1443 if (!build_options.enable_debug_extensions) {
1444 warn("Zig was compiled without debug extensions. --debug-link-snapshot has no effect.", .{});
14451445 } else {
14461446 enable_link_snapshots = true;
14471447 }
test/link.zig created+341
......@@ -0,0 +1,341 @@
1pub fn addCases(ctx: *LinkContext) void {
2 if (ctx.target.result.isMinGW())
3 @import("link/mingw.zig").addCases(ctx);
4
5 if (ctx.includeTest("static-lib")) |case| {
6 const obj1 = case.addObject(.{
7 .name = "obj1",
8 .name_prefix = false,
9 .name_target = false,
10 .use_llvm = true,
11 .use_lld = true,
12 .c_source_bytes =
13 \\int foo1 = 1;
14 \\int foo2 = 2;
15 \\unsigned int fooBar() {
16 \\ return foo1 + foo2;
17 \\}
18 ,
19 });
20 const obj2 = case.addObject(.{
21 .name = "this_is_a_long_name",
22 .name_prefix = false,
23 .name_target = false,
24 .zig_source_bytes =
25 \\fn fooWeak() callconv(.c) usize {
26 \\ return 0xaabbccddaabbccdd;
27 \\}
28 \\export var foo_array: [2]u16 = .{ 0xffff, 0xabcd };
29 \\export var foo_strong: usize = 0x1122334411223344;
30 \\comptime {
31 \\ @export(&fooWeak, .{ .name = "fooWeak", .linkage = .weak });
32 \\ @export(&foo_strong, .{ .name = "foo_strong_alias", .linkage = .strong });
33 \\}
34 ,
35 });
36
37 const lib = case.addLibrary(.static, .{
38 .name = "lib",
39 .name_prefix = false,
40 .name_target = false,
41 });
42 lib.root_module.addObject(obj1);
43 lib.root_module.addObject(obj2);
44
45 case.verifyObjdump(lib.getEmittedBin(), &.{
46 "-s",
47 "--elements=file-type",
48 "--symbols",
49 "--only-symbol=foo",
50 }, .{ .use_llvm = true });
51
52 const exe = case.addExecutable(.{
53 .name = "test",
54 .zig_source_bytes =
55 \\extern fn fooBar() c_uint;
56 \\extern fn fooWeak() usize;
57 \\extern var foo_array: [2]u16;
58 \\extern var foo_strong: usize;
59 \\extern var foo_strong_alias: usize;
60 \\pub fn main() !u8 {
61 \\ return @intFromBool(0xcd003365cd00df35 != fooBar() +
62 \\ fooWeak() +
63 \\ foo_array[1] +
64 \\ foo_strong +
65 \\ foo_strong_alias);
66 \\}
67 ,
68 });
69 exe.root_module.linkLibrary(lib);
70
71 const run = case.addRunArtifact(exe);
72 run.addCheck(.{ .expect_term = .{ .exited = 0 } });
73 }
74
75 if (ctx.includeTest("tls")) |case| {
76 const obj = case.addObject(.{
77 .name = "obj",
78 .zig_source_bytes =
79 \\threadlocal var threadlocal_var: u32 = 1234;
80 \\threadlocal var threadlocal_arr: [4]u16 = .{ 0x1111, 0x2222, 0x3333, 0x4444, };
81 \\export fn threadlocal_read(a: *u32, b: *u16) void {
82 \\ a.* = threadlocal_var;
83 \\ b.* = threadlocal_arr[3];
84 \\}
85 \\export fn threadlocal_write(a: u32, b: u16) void {
86 \\ threadlocal_var = a;
87 \\ threadlocal_arr[3] = b;
88 \\}
89 ,
90 });
91
92 case.verifyObjdump(obj.getEmittedBin(), &.{
93 "-s",
94 "--symbols",
95 "--only-symbol=threadlocal",
96 "--only-symbol=tls",
97 }, .{ .use_llvm = true });
98
99 const exe = case.addExecutable(.{
100 .name = "test",
101 .zig_source_bytes =
102 \\extern fn threadlocal_read(a: *u32, b: *u16) void;
103 \\extern fn threadlocal_write(a: u32, b: u16) void;
104 \\threadlocal var threadlocal_foo: u64 = 0xcafecafecafecafe;
105 \\pub fn main() !u8 {
106 \\ var a: u32 = undefined;
107 \\ var b: u16 = undefined;
108 \\ threadlocal_read(&a, &b);
109 \\ if (a != 1234 or b != 0x4444) return 1;
110 \\ if (threadlocal_foo != 0xcafecafecafecafe) return 2;
111 \\ threadlocal_write(0xabcdabcd, 0x5555);
112 \\ threadlocal_foo = 1;
113 \\ threadlocal_read(&a, &b);
114 \\ if (a != 0xabcdabcd or b != 0x5555) return 3;
115 \\ if (threadlocal_foo != 1) return 4;
116 \\ return 0;
117 \\}
118 ,
119 });
120 exe.root_module.addObject(obj);
121
122 const run = case.addRunArtifact(exe);
123 run.addCheck(.{ .expect_term = .{ .exited = 0 } });
124 }
125
126 if (ctx.includeTest("dynamic-lib-code")) |case| {
127 const lib = case.addLibrary(.dynamic, .{
128 .name = "lib",
129 .name_target = false,
130 .zig_source_bytes =
131 \\export fn foo1() callconv(.c) u64 {
132 \\ return 0x1122334411223344;
133 \\}
134 \\export fn foo2() callconv(.c) u64 {
135 \\ return 0xaabbccddaabbccdd;
136 \\}
137 ,
138 });
139
140 case.verifyObjdump(lib.getEmittedBin(), &.{
141 "-s",
142 "--exports",
143 "--only-symbol=foo",
144 }, .{ .os = true });
145
146 if (ctx.target.result.os.tag == .windows) {
147 case.verifyObjdump(lib.getEmittedImplib(), &.{
148 "-s",
149 "--exports=sort",
150 "--only-symbol=foo",
151 }, .{ .sub_name = "implib", .os = true, .arch = true });
152 }
153
154 const exe = case.addExecutable(.{
155 .name = "test",
156 .zig_source_bytes =
157 \\extern fn foo1() u64;
158 \\pub fn main() !u8 {
159 \\ const foo2 = @extern(
160 \\ *const fn () callconv(.c) u64,
161 \\ .{ .name = "foo2", .is_dll_import = true },
162 \\ );
163 \\ return @intFromBool(0xbbde0021bbde0021 != foo1() + foo2());
164 \\}
165 ,
166 });
167 exe.root_module.linkLibrary(lib);
168
169 const run = case.addRunArtifact(exe);
170 run.addCheck(.{ .expect_term = .{ .exited = 0 } });
171 }
172
173 if (ctx.includeTest("dynamic-lib-data")) |case| {
174 const lib = case.addLibrary(.dynamic, .{
175 .name = "lib",
176 .name_target = false,
177 .zig_source_bytes =
178 \\export var foo_array: [2]u16 = .{ 0xffff, 0xabcd };
179 \\export var foo_strong: usize = 0x1122334411223344;
180 \\comptime {
181 \\ @export(&foo_strong, .{ .name = "foo_strong_alias", .linkage = .strong });
182 \\}
183 ,
184 });
185
186 case.verifyObjdump(lib.getEmittedBin(), &.{
187 "-s",
188 "--exports",
189 "--only-symbol=foo",
190 }, .{});
191
192 if (ctx.target.result.os.tag == .windows) {
193 case.verifyObjdump(lib.getEmittedImplib(), &.{
194 "-s",
195 "--exports=sort",
196 "--only-symbol=foo",
197 }, .{ .sub_name = "implib", .os = true, .arch = true });
198 }
199
200 const exe = case.addExecutable(.{
201 .name = "test",
202 .zig_source_bytes =
203 \\pub fn main() !u8 {
204 \\ const foo_array = @extern(*[2]u16, .{ .name = "foo_array", .is_dll_import = true });
205 \\ const foo_strong = @extern(*usize, .{ .name = "foo_strong", .is_dll_import = true });
206 \\ const foo_strong_alias = @extern(*usize, .{ .name = "foo_strong_alias", .is_dll_import = true });
207 \\ return @intFromBool(0x2244668822451255 !=
208 \\ foo_array[1] +
209 \\ foo_strong.* +
210 \\ foo_strong_alias.*);
211 \\}
212 ,
213 });
214 exe.root_module.linkLibrary(lib);
215
216 const run = case.addRunArtifact(exe);
217 run.addCheck(.{ .expect_term = .{ .exited = 0 } });
218 }
219
220 if (ctx.includeTest("abs-symbol")) |case| {
221 const abs = case.addObject(.{
222 .name = "abs",
223 .use_llvm = true, // TODO: .globl not supported on self-hosted
224 .use_lld = true,
225 .asm_source_bytes =
226 \\.globl foo
227 \\foo = 0xcafecafe
228 \\
229 ,
230 });
231
232 const abs_reloc = case.addObject(.{
233 .name = "abs_reloc",
234 .use_llvm = true, // TODO: .globl not supported on self-hosted
235 .use_lld = true,
236 .asm_source_bytes =
237 \\.data
238 \\.globl foo_copy
239 \\foo_copy:
240 \\.long foo
241 ,
242 });
243
244 case.verifyObjdump(abs_reloc.getEmittedBin(), &.{
245 "-s",
246 "--relocs",
247 }, .{ .arch = true });
248
249 const exe = case.addExecutable(.{
250 .name = "test",
251 .zig_source_bytes =
252 \\extern var foo_copy: u32;
253 \\pub fn main() !u8 {
254 \\ return @intFromBool(foo_copy != 0xcafecafe);
255 \\}
256 ,
257 });
258 exe.root_module.addObject(abs);
259 exe.root_module.addObject(abs_reloc);
260
261 const run = case.addRunArtifact(exe);
262 run.addCheck(.{ .expect_term = .{ .exited = 0 } });
263
264 if (!ctx.use_llvm) {
265 const exe_reloc_err = case.addExecutable(.{
266 .name = "test-reloc-err",
267 .zig_source_bytes =
268 \\extern const foo: u32;
269 \\pub fn main() !u8 {
270 \\ return @intFromBool(foo != 0xcafecafe);
271 \\}
272 ,
273 });
274 exe_reloc_err.root_module.addObject(abs);
275 case.expectLinkErrors(exe_reloc_err, .{
276 .contains = "error: absolute symbol 'foo' targeted by invalid relocation type: /?/",
277 });
278 }
279 }
280
281 if (ctx.includeTest("explicit-extern-lib-name")) |case| {
282 // TODO: Lld.zig does not look at explicit inputs to resolve explicit extern lib names
283 if (ctx.use_llvm) return;
284
285 const lib1 = case.addLibrary(.dynamic, .{
286 .name = "lib1",
287 .name_target = false,
288 .zig_source_bytes =
289 \\export fn foo() u8 {
290 \\ return 43;
291 \\}
292 ,
293 });
294
295 const lib2 = case.addLibrary(.dynamic, .{
296 .name = "lib2",
297 .name_target = false,
298 .zig_source_bytes =
299 \\export fn foo() u8 {
300 \\ return 42;
301 \\}
302 ,
303 });
304
305 const lib3 = case.addLibrary(.static, .{
306 .name = "lib3",
307 .zig_source_bytes =
308 \\extern fn foo() u8;
309 \\export fn callFoo() u8 {
310 \\ return foo();
311 \\}
312 ,
313 });
314
315 const exe = case.addExecutable(.{
316 .name = "test",
317 .zig_source_bytes =
318 \\extern "explicit-extern-lib-name-lib2" fn foo() u8;
319 \\extern fn callFoo() u8;
320 \\pub fn main() !u8 {
321 \\ return foo() + callFoo();
322 \\}
323 ,
324 });
325 exe.root_module.linkLibrary(lib1);
326 exe.root_module.linkLibrary(lib2);
327 // exe.root_module.addLibraryPath(.{
328 // .generated = .{
329 // .index = lib2.getEmittedBin().generated.index,
330 // .up = 1,
331 // },
332 // });
333 exe.root_module.linkLibrary(lib3);
334
335 const run = case.addRunArtifact(exe);
336 run.addCheck(.{ .expect_term = .{ .exited = 84 } });
337 }
338}
339
340const LinkContext = @import("tests.zig").LinkContext;
341const std = @import("std");
test/link/exports.zig created+9
......@@ -0,0 +1,9 @@
1export fn foo_fn() void {}
2var foo_var: u32 = 1234;
3comptime {
4 @export(&foo_var, .{ .name = "foo_var", .linkage = .strong });
5}
6const foo_const: u64 = 5678;
7comptime {
8 @export(&foo_const, .{ .name = "foo_const", .linkage = .strong });
9}
test/link/mingw.zig created+48
......@@ -0,0 +1,48 @@
1pub fn addCases(ctx: *LinkContext) void {
2 if (ctx.includeTest("ctor-dtor")) |case| {
3 if (!ctx.link_libc) return;
4
5 const obj = case.addObject(.{
6 .name = "obj",
7 .use_llvm = true,
8 .use_lld = true,
9 .c_source_bytes =
10 \\#include <stdlib.h>
11 \\int foo;
12 \\__attribute__((constructor))
13 \\static void init_foo() {
14 \\ foo = 42;
15 \\}
16 \\__attribute__((destructor))
17 \\static void deinit_foo() {
18 \\ exit(42);
19 \\}
20 ,
21 });
22
23 const lib = case.addLibrary(.static, .{
24 .name = "lib",
25 .name_prefix = false,
26 .name_target = false,
27 });
28 lib.root_module.addObject(obj);
29
30 const exe = case.addExecutable(.{
31 .name = "test",
32 .zig_source_bytes =
33 \\extern var foo: u32;
34 \\pub fn main() !u8 {
35 \\ if (foo != 42) return 1;
36 \\ return 2;
37 \\}
38 ,
39 });
40 exe.root_module.addObject(obj);
41
42 const run = case.addRunArtifact(exe);
43 run.addCheck(.{ .expect_term = .{ .exited = 42 } });
44 }
45}
46
47const LinkContext = @import("../tests.zig").LinkContext;
48const std = @import("std");
test/link/snapshots/.gitattributes created+1
......@@ -0,0 +1 @@
1*.dmp eol=lf
test/link/snapshots/abs-symbol.x86_64.dmp created+1
......@@ -0,0 +1 @@
1xxxxxxxx ADDR32 xxxxxxxx UNDEF | foo
test/link/snapshots/dynamic-lib-code.implib-x86_64-windows.dmp created+32
......@@ -0,0 +1,32 @@
1 0 date
2 0 user_id
3 0 group_id
4 0 file_mode
5xxxxxxxxxxxxxxxx size
6 second_linker type
7 | x symbols
8 | x members
9xxxxxxxx __imp_foo1
10xxxxxxxx __imp_foo2
11xxxxxxxx foo1
12xxxxxxxx foo2
13 0 version
14 8664 machine (AMD64)
15 0 time_date_stamp
16xxxxxxxxxxxxxxxx size_of_data
17xxxxxxxxxxxxxxxx hint
18 CODE import_type
19 NAME name_type
20 symbol name | foo1
21 import name | foo1
22 dll | dynamic-lib-code-lib.dll
23 0 version
24 8664 machine (AMD64)
25 0 time_date_stamp
26xxxxxxxxxxxxxxxx size_of_data
27xxxxxxxxxxxxxxxx hint
28 CODE import_type
29 NAME name_type
30 symbol name | foo2
31 import name | foo2
32 dll | dynamic-lib-code-lib.dll
test/link/snapshots/dynamic-lib-code.windows.dmp created+13
......@@ -0,0 +1,13 @@
1Export directory:
2 0 flags
3 0 time_date_stamp
4 0.00 version
5xxxxxxxxxxxxxxxx name_rva
6 1 ordinal_base
7xxxxxxxxxxxxxxxx number_of_entries
8xxxxxxxxxxxxxxxx number_of_names
9xxxxxxxxxxxxxxxx export_address_table_rva
10xxxxxxxxxxxxxxxx name_pointer_table_rva
11xxxxxxxxxxxxxxxx ordinal_table_rva
12xxxx xxxx xxxxxxxx | foo1
13xxxx xxxx xxxxxxxx | foo2
test/link/snapshots/dynamic-lib-data.dmp created+14
......@@ -0,0 +1,14 @@
1Export directory:
2 0 flags
3 0 time_date_stamp
4 0.00 version
5xxxxxxxxxxxxxxxx name_rva
6 1 ordinal_base
7xxxxxxxxxxxxxxxx number_of_entries
8xxxxxxxxxxxxxxxx number_of_names
9xxxxxxxxxxxxxxxx export_address_table_rva
10xxxxxxxxxxxxxxxx name_pointer_table_rva
11xxxxxxxxxxxxxxxx ordinal_table_rva
12xxxx xxxx xxxxxxxx | foo_array
13xxxx xxxx xxxxxxxx | foo_strong
14xxxx xxxx xxxxxxxx | foo_strong_alias
test/link/snapshots/dynamic-lib-data.implib-x86_64-windows.dmp created+41
......@@ -0,0 +1,41 @@
1 0 date
2 0 user_id
3 0 group_id
4 0 file_mode
5xxxxxxxxxxxxxxxx size
6 second_linker type
7 | x symbols
8 | x members
9xxxxxxxx __imp_foo_array
10xxxxxxxx __imp_foo_strong
11xxxxxxxx __imp_foo_strong_alias
12 0 version
13 8664 machine (AMD64)
14 0 time_date_stamp
15xxxxxxxxxxxxxxxx size_of_data
16xxxxxxxxxxxxxxxx hint
17 DATA import_type
18 NAME name_type
19 symbol name | foo_array
20 import name | foo_array
21 dll | dynamic-lib-data-lib.dll
22 0 version
23 8664 machine (AMD64)
24 0 time_date_stamp
25xxxxxxxxxxxxxxxx size_of_data
26xxxxxxxxxxxxxxxx hint
27 DATA import_type
28 NAME name_type
29 symbol name | foo_strong
30 import name | foo_strong
31 dll | dynamic-lib-data-lib.dll
32 0 version
33 8664 machine (AMD64)
34 0 time_date_stamp
35xxxxxxxxxxxxxxxx size_of_data
36xxxxxxxxxxxxxxxx hint
37 DATA import_type
38 NAME name_type
39 symbol name | foo_strong_alias
40 import name | foo_strong_alias
41 dll | dynamic-lib-data-lib.dll
test/link/snapshots/static-lib.llvm.dmp created+15
......@@ -0,0 +1,15 @@
1lib.lib: COFF archive
2lib.lib(obj1.obj): COFF object
3xxxx 00000000 1 NULL() EXTERNAL | fooBar
4xxxx 00000000 2 NULL EXTERNAL | foo1
5xxxx 00000004 2 NULL EXTERNAL | foo2
6lib.lib(this_is_a_long_name.obj): COFF object
7xxxx 00000000 1 NULL() STATIC | this_is_a_long_name.fooWeak
8xxxx 00000000 2 NULL STATIC | this_is_a_long_name.foo_strong
9xxxx 00000008 2 NULL STATIC | this_is_a_long_name.foo_array
10xxxx 00000000 2 NULL EXTERNAL | foo_strong
11xxxx 00000000 2 NULL EXTERNAL | foo_strong_alias
12xxxx 00000008 2 NULL EXTERNAL | foo_array
13xxxx 00000000 UNDEF NULL WEAK_EXTERNAL | fooWeak
14 | Weak External [falls back to relative ordinal 000000+2 via SEARCH_ALIAS]
15xxxx 00000000 1 NULL() EXTERNAL | .weak.fooWeak.default.foo_strong
test/link/snapshots/static-lib.no-llvm.dmp created+12
......@@ -0,0 +1,12 @@
1lib.lib: COFF archive
2lib.lib(obj1.obj): COFF object
3xxxx 00000000 1 NULL() EXTERNAL | fooBar
4xxxx 00000000 2 NULL EXTERNAL | foo1
5xxxx 00000004 2 NULL EXTERNAL | foo2
6lib.lib(this_is_a_long_name.obj): COFF object
7xxxx 00000000 4 NULL() EXTERNAL | this_is_a_long_name.fooWeak
8xxxx 00000000 2 NULL EXTERNAL | foo_strong
9xxxx 00000000 2 NULL EXTERNAL | foo_strong_alias
10xxxx 00000010 2 NULL EXTERNAL | foo_array
11xxxx 00000000 UNDEF NULL() WEAK_EXTERNAL | fooWeak
12 | Weak External [falls back to relative ordinal 000000-4 via SEARCH_ALIAS]
test/link/snapshots/tls.llvm.dmp created+9
......@@ -0,0 +1,9 @@
1xxxx 00000000 6 NULL STATIC | .tls$
2 | Section [size xxxxxxxx chksum a194a569 relocs 0000 lines 0000]
3xxxx 00000000 1 NULL() STATIC | obj.threadlocal_write
4xxxx 00000000 UNDEF NULL EXTERNAL | _tls_index
5xxxx 00000000 6 NULL STATIC | obj.threadlocal_var
6xxxx 00000004 6 NULL STATIC | obj.threadlocal_arr
7xxxx 00000040 1 NULL() STATIC | obj.threadlocal_read
8xxxx 00000000 1 NULL() EXTERNAL | threadlocal_write
9xxxx 00000040 1 NULL() EXTERNAL | threadlocal_read
test/link/snapshots/tls.no-llvm.dmp created+7
......@@ -0,0 +1,7 @@
1xxxx 00000000 5 NULL STATIC | .tls$
2 | Section [size xxxxxxxx chksum 00000000 relocs 0000 lines 0000]
3xxxx 00000000 5 NULL STATIC | obj.threadlocal_var
4xxxx 00000008 5 NULL STATIC | obj.threadlocal_arr
5xxxx 00000000 UNDEF NULL EXTERNAL | _tls_index
6xxxx 00000000 4 NULL() EXTERNAL | threadlocal_write
7xxxx 00000060 4 NULL() EXTERNAL | threadlocal_read
test/src/Link.zig created+281
......@@ -0,0 +1,281 @@
1b: *Build,
2step: *Step,
3optimize: std.builtin.OptimizeMode,
4target: std.Build.ResolvedTarget,
5target_desc: []const u8,
6use_llvm: bool,
7use_lld: bool,
8link_libc: bool,
9test_filters: []const []const u8,
10update_step: ?*Step.UpdateSourceFiles,
11updated_snapshots: std.StringArrayHashMapUnmanaged(void),
12max_rss: usize,
13
14pub fn includeTest(self: *Link, prefix: []const u8) ?Case {
15 if (for (self.test_filters) |filter| {
16 if (std.mem.containsAtLeast(u8, prefix, 1, filter)) break false;
17 } else self.test_filters.len > 0) return null;
18
19 return .{
20 .ctx = self,
21 .prefix = prefix,
22 };
23}
24
25pub fn sourcePath(self: *const Link, sub_path: []const u8) std.Build.LazyPath {
26 return self.b.path(self.b.pathJoin(&.{ "test/link", sub_path }));
27}
28
29pub const Case = struct {
30 ctx: *Link,
31 prefix: []const u8,
32
33 fn resolveName(self: *const Case, overlay: *const OverlayOptions) []const u8 {
34 if (!overlay.name_prefix and !overlay.name_target)
35 return overlay.name;
36
37 if (overlay.name_prefix == overlay.name_target)
38 return self.ctx.b.fmt("{s}-{s}-{s}", .{ self.prefix, overlay.name, self.ctx.target_desc })
39 else if (overlay.name_prefix)
40 return self.ctx.b.fmt("{s}-{s}", .{ self.prefix, overlay.name })
41 else
42 return self.ctx.b.fmt("{s}-{s}", .{ overlay.name, self.ctx.target_desc });
43 }
44
45 pub fn addLibrary(
46 self: *const Case,
47 linkage: std.builtin.LinkMode,
48 overlay: OverlayOptions,
49 ) *Step.Compile {
50 return self.ctx.b.addLibrary(.{
51 .linkage = linkage,
52 .name = self.resolveName(&overlay),
53 .root_module = self.ctx.createModule(overlay),
54 .use_llvm = overlay.use_llvm orelse self.ctx.use_llvm,
55 .use_lld = overlay.use_lld orelse self.ctx.use_lld,
56 });
57 }
58
59 pub fn addExecutable(
60 self: *const Case,
61 overlay: OverlayOptions,
62 ) *Step.Compile {
63 return self.ctx.b.addExecutable(.{
64 .name = self.resolveName(&overlay),
65 .root_module = self.ctx.createModule(overlay),
66 .use_llvm = overlay.use_llvm orelse self.ctx.use_llvm,
67 .use_lld = overlay.use_lld orelse self.ctx.use_lld,
68 });
69 }
70
71 pub fn addRunArtifact(
72 self: *const Case,
73 exe: *Step.Compile,
74 ) *Step.Run {
75 const run_step = self.ctx.b.addRunArtifact(exe);
76 run_step.skip_foreign_checks = true;
77 self.ctx.step.dependOn(&run_step.step);
78 return run_step;
79 }
80
81 pub fn addObject(
82 self: *const Case,
83 overlay: OverlayOptions,
84 ) *Step.Compile {
85 return self.ctx.b.addObject(.{
86 .name = self.resolveName(&overlay),
87 .root_module = self.ctx.createModule(overlay),
88 .use_llvm = overlay.use_llvm orelse self.ctx.use_llvm,
89 .use_lld = overlay.use_lld orelse self.ctx.use_lld,
90 });
91 }
92
93 pub fn expectLinkErrors(
94 self: *const Case,
95 comp: *Step.Compile,
96 expected_errors: Step.Compile.ExpectedCompileErrors,
97 ) void {
98 comp.expect_errors = expected_errors;
99 const bin_file = comp.getEmittedBin();
100 bin_file.addStepDependencies(self.ctx.step);
101 }
102
103 const SnapshotScope = struct {
104 /// If a test case has multiple verifyObjdump calls, `opt_sub_name` should
105 /// be used to differentiate them.
106 sub_name: ?[]const u8 = null,
107 arch: bool = false,
108 os: bool = false,
109 abi: bool = false,
110 optimize: bool = false,
111 use_llvm: bool = false,
112 use_lld: bool = false,
113 link_libc: bool = false,
114 };
115
116 /// Verify the results of a `zig objdump` call against a snapshot, which
117 /// contains the expected output. Snapshots alias between all build
118 /// configurations by default, but by specifying fields in `scope`,
119 /// unique snapshot names are generated for each value of that field.
120 pub fn verifyObjdump(
121 self: *const Case,
122 file: Build.LazyPath,
123 args: []const []const u8,
124 scope: SnapshotScope,
125 ) void {
126 const ctx = self.ctx;
127 const snapshot_name = self.snapshotName(scope) catch @panic("OOM");
128 const snapshot_sub_path = ctx.b.pathJoin(&.{ "test/link/snapshots/", snapshot_name });
129
130 // Many tests may read the same snapshot, so only use the first one to update.
131 // If there are differences in output, they will show up on the next test run.
132 if (ctx.update_step != null) {
133 const gop = ctx.updated_snapshots.getOrPut(ctx.b.allocator, snapshot_sub_path) catch @panic("OOM");
134 if (gop.found_existing) return;
135 }
136
137 const run_step = Step.Run.create(ctx.b, ctx.b.fmt(
138 "objdump {s} {s}",
139 .{ snapshot_name, ctx.target_desc },
140 ));
141 run_step.addArgs(&.{ ctx.b.graph.zig_exe, "objdump" });
142 run_step.addFileArg(file);
143 run_step.addArgs(args);
144 run_step.addCheck(.{ .expect_term = .{ .exited = 0 } });
145
146 if (ctx.update_step) |update_step| {
147 // Workaround for the build system not realizing objdump itself has changed
148 run_step.has_side_effects = true;
149
150 const snapshot_update_path = run_step.captureStdOut(.{});
151 update_step.addCopyFileToSource(snapshot_update_path, snapshot_sub_path);
152 } else {
153 run_step.addCheck(.{ .expect_stdout_snapshot = ctx.b.path(snapshot_sub_path) });
154 }
155
156 ctx.step.dependOn(&run_step.step);
157 }
158
159 fn snapshotName(
160 self: *const Case,
161 scope: SnapshotScope,
162 ) ![]const u8 {
163 const ctx = self.ctx;
164 var snapshot_name: std.Io.Writer.Allocating = .init(ctx.b.allocator);
165 const w = &snapshot_name.writer;
166
167 try w.writeAll(self.prefix);
168 var sep: u8 = '.';
169
170 if (try snapshotNameInner(w, scope.sub_name != null, &sep))
171 try w.writeAll(scope.sub_name.?);
172 if (try snapshotNameInner(w, scope.arch, &sep))
173 try w.print("{t}", .{ctx.target.result.cpu.arch});
174 if (try snapshotNameInner(w, scope.os, &sep))
175 try w.print("{t}", .{ctx.target.result.os.tag});
176 if (try snapshotNameInner(w, scope.abi, &sep))
177 try w.print("{t}", .{ctx.target.result.abi});
178 if (try snapshotNameInner(w, scope.optimize, &sep))
179 try w.print("{t}", .{ctx.optimize});
180 if (try snapshotNameInner(w, scope.use_llvm, &sep))
181 try w.writeAll(if (ctx.use_llvm) "llvm" else "no-llvm");
182 if (try snapshotNameInner(w, scope.use_lld, &sep))
183 try w.writeAll(if (ctx.use_lld) "lld" else "no-lld");
184 if (try snapshotNameInner(w, scope.link_libc, &sep))
185 try w.writeAll(if (ctx.link_libc) "libc" else "no-libc");
186
187 if (sep == '-') sep = '.';
188 try w.writeByte(sep);
189 try w.writeAll("dmp");
190
191 return try snapshot_name.toOwnedSlice();
192 }
193
194 fn snapshotNameInner(w: *std.Io.Writer, cond: bool, sep: *u8) !bool {
195 if (cond) {
196 try w.writeByte(sep.*);
197 sep.* = '-';
198 }
199
200 return cond;
201 }
202};
203
204fn createModule(self: *const Link, overlay: OverlayOptions) *Build.Module {
205 const write_files = self.b.addWriteFiles();
206
207 const mod = self.b.createModule(.{
208 .target = self.target,
209 .optimize = self.optimize,
210 .root_source_file = overlay.zig_source_file orelse rsf: {
211 const bytes = overlay.zig_source_bytes orelse break :rsf null;
212 const name = self.b.fmt("{s}.zig", .{overlay.name});
213 break :rsf write_files.add(name, bytes);
214 },
215 .link_libc = self.link_libc, // TODO: Should this be in overlay instead?
216 .pic = overlay.pic,
217 .strip = overlay.strip,
218 });
219
220 if (overlay.objcpp_source_bytes) |bytes| {
221 mod.addCSourceFile(.{
222 .file = write_files.add("a.mm", bytes),
223 .flags = overlay.objcpp_source_flags,
224 });
225 }
226 if (overlay.objc_source_bytes) |bytes| {
227 mod.addCSourceFile(.{
228 .file = write_files.add("a.m", bytes),
229 .flags = overlay.objc_source_flags,
230 });
231 }
232 if (overlay.cpp_source_bytes) |bytes| {
233 mod.addCSourceFile(.{
234 .file = write_files.add("a.cpp", bytes),
235 .flags = overlay.cpp_source_flags,
236 });
237 }
238 if (overlay.c_source_bytes) |bytes| {
239 mod.addCSourceFile(.{
240 .file = write_files.add("a.c", bytes),
241 .flags = overlay.c_source_flags,
242 });
243 }
244 if (overlay.asm_source_bytes) |bytes| {
245 mod.addAssemblyFile(write_files.add("a.s", bytes));
246 }
247
248 return mod;
249}
250
251const OverlayOptions = struct {
252 name: []const u8,
253 /// Prefix the name with the test case prefix.
254 /// Unset if names with specific lengths are needed.
255 name_prefix: bool = true,
256 /// Prefix the name with `target_desc`.
257 /// Can be unset when the snapshot needs to contain the name,
258 /// so that snapshots can alias between targets.
259 name_target: bool = true,
260 asm_source_bytes: ?[]const u8 = null,
261 c_source_bytes: ?[]const u8 = null,
262 c_source_flags: []const []const u8 = &.{},
263 cpp_source_bytes: ?[]const u8 = null,
264 cpp_source_flags: []const []const u8 = &.{},
265 objc_source_bytes: ?[]const u8 = null,
266 objc_source_flags: []const []const u8 = &.{},
267 objcpp_source_bytes: ?[]const u8 = null,
268 objcpp_source_flags: []const []const u8 = &.{},
269 zig_source_bytes: ?[]const u8 = null,
270 zig_source_file: ?std.Build.LazyPath = null,
271 pic: ?bool = null,
272 strip: ?bool = null,
273 use_llvm: ?bool = null,
274 use_lld: ?bool = null,
275};
276
277const std = @import("std");
278const Build = std.Build;
279const Step = Build.Step;
280
281const Link = @This();
test/standalone/shared_library/build.zig+42-4
......@@ -7,11 +7,47 @@ pub fn build(b: *std.Build) void {
77 const optimize: std.builtin.OptimizeMode = .Debug;
88 const target = b.standardTargetOptions(.{});
99
10 const exe_names: []const []const u8 = &.{ "test", "test-dync" };
11 const lib_names: []const []const u8 = &.{ "mathtest", "mathtest-dync" };
12 const lib_link_libc: []const bool = &.{ false, true };
10 const exe_names: []const []const u8 = &.{
11 "test",
12 "test-dync",
13 "test-no-llvm",
14 "test-no-llvm-dync",
15 "test-exe-no-llvm",
16 "test-dync-exe-no-llvm",
17 "test-no-llvm-exe-no-llvm",
18 "test-no-llvm-dync-exe-no-llvm",
19 };
20 const lib_names: []const []const u8 = &.{
21 "mathtest",
22 "mathtest-dync",
23 "mathtest-no-llvm",
24 "mathtest-no-llvm-dync",
25 "mathtest-exe-no-llvm",
26 "mathtest-dync-exe-no-llvm",
27 "mathtest-no-llvm-exe-no-llvm",
28 "mathtest-no-llvm-dync-exe-no-llvm",
29 };
30 const lib_link_libc: []const bool = &.{ false, true, false, true, false, true, false, true };
31 const lib_use_llvm: []const bool = &.{ true, true, false, false, true, true, false, false };
32 const exe_use_llvm: []const bool = &.{ true, true, true, true, false, false, false, false };
33
34 for (
35 exe_names,
36 lib_names,
37 lib_link_libc,
38 lib_use_llvm,
39 exe_use_llvm,
40 ) |exe_name, lib_name, dyn_libc, lib_llvm, exe_llvm| {
41 const no_llvm = !lib_llvm or !exe_llvm;
42 if (no_llvm and target.result.os.tag == .macos) continue; // TODO
43 if (no_llvm and target.result.os.tag == .freebsd) continue; // TODO
44 if (no_llvm and target.result.os.tag == .netbsd) continue; // TODO
45 if (no_llvm and target.result.os.tag == .openbsd) continue; // TODO
46 if (no_llvm and target.result.cpu.arch == .aarch64) continue; // TODO
47 if (no_llvm and target.result.cpu.arch == .loongarch64) continue; // TODO
48 if (no_llvm and target.result.cpu.arch == .powerpc64le) continue; // TODO
49 if (no_llvm and target.result.cpu.arch == .s390x) continue; // TODO
1350
14 for (exe_names, lib_names, lib_link_libc) |exe_name, lib_name, dyn_libc| {
1551 const lib = b.addLibrary(.{
1652 .linkage = .dynamic,
1753 .name = lib_name,
......@@ -22,6 +58,7 @@ pub fn build(b: *std.Build) void {
2258 .optimize = optimize,
2359 .link_libc = dyn_libc,
2460 }),
61 .use_llvm = lib_llvm,
2562 });
2663
2764 const exe = b.addExecutable(.{
......@@ -32,6 +69,7 @@ pub fn build(b: *std.Build) void {
3269 .optimize = optimize,
3370 .link_libc = true,
3471 }),
72 .use_llvm = exe_llvm,
3573 });
3674 exe.root_module.addCSourceFile(.{
3775 .file = b.path("test.c"),
test/standalone/shared_library/mathtest.zig+2
......@@ -1,3 +1,5 @@
1export var exported_var: i32 = 9999;
2
13export fn add(a: i32, b: i32) i32 {
24 return a + b;
35}
test/standalone/shared_library/test.c+9
......@@ -7,7 +7,16 @@
77#include <stdint.h>
88int32_t add(int32_t a, int32_t b);
99
10#if _WIN32
11#define IMPORT __declspec(dllimport)
12#else
13#define IMPORT
14#endif
15
16extern IMPORT int32_t exported_var;
17
1018int main(int argc, char **argv) {
1119 assert(add(42, 1337) == 1379);
20 assert(exported_var == 9999);
1221 return 0;
1322}
test/standalone/static_c_lib/build.zig+69-19
......@@ -5,26 +5,76 @@ pub fn build(b: *std.Build) void {
55 b.default_step = test_step;
66
77 const optimize: std.builtin.OptimizeMode = .Debug;
8 const target = b.standardTargetOptions(.{});
89
9 const foo = b.addLibrary(.{
10 .linkage = .static,
11 .name = "foo",
12 .root_module = b.createModule(.{
13 .root_source_file = null,
14 .optimize = optimize,
15 .target = b.graph.host,
16 }),
17 });
18 foo.root_module.addCSourceFile(.{ .file = b.path("foo.c"), .flags = &[_][]const u8{} });
19 foo.root_module.addIncludePath(b.path("."));
10 const exe_names: []const []const u8 = &.{
11 "test",
12 "test-dync",
13 "test-no-llvm",
14 "test-no-llvm-dync",
15 "test-exe-no-llvm",
16 "test-dync-exe-no-llvm",
17 "test-no-llvm-exe-no-llvm",
18 "test-no-llvm-dync-exe-no-llvm",
19 };
20 const lib_names: []const []const u8 = &.{
21 "foo",
22 "foo-dync",
23 "foo-no-llvm",
24 "foo-no-llvm-dync",
25 "foo-exe-no-llvm",
26 "foo-dync-exe-no-llvm",
27 "foo-no-llvm-exe-no-llvm",
28 "foo-no-llvm-dync-exe-no-llvm",
29 };
30 const lib_link_libc: []const bool = &.{ false, true, false, true, false, true, false, true };
31 const lib_use_llvm: []const bool = &.{ true, true, false, false, true, true, false, false };
32 const exe_use_llvm: []const bool = &.{ true, true, true, true, false, false, false, false };
2033
21 const test_exe = b.addTest(.{ .root_module = b.createModule(.{
22 .root_source_file = b.path("foo.zig"),
23 .target = b.graph.host,
24 .optimize = optimize,
25 }) });
26 test_exe.root_module.linkLibrary(foo);
27 test_exe.root_module.addIncludePath(b.path("."));
34 for (
35 exe_names,
36 lib_names,
37 lib_link_libc,
38 lib_use_llvm,
39 exe_use_llvm,
40 ) |exe_name, lib_name, dyn_libc, lib_llvm, exe_llvm| {
41 const no_llvm = !lib_llvm or !exe_llvm;
42 if (no_llvm and target.result.os.tag == .macos) continue; // TODO
43 if (no_llvm and target.result.os.tag == .freebsd) continue; // TODO
44 if (no_llvm and target.result.os.tag == .netbsd) continue; // TODO
45 if (no_llvm and target.result.os.tag == .openbsd) continue; // TODO
46 if (no_llvm and target.result.cpu.arch == .aarch64) continue; // TODO
47 if (no_llvm and target.result.cpu.arch == .loongarch64) continue; // TODO
48 if (no_llvm and target.result.cpu.arch == .powerpc64le) continue; // TODO
49 if (no_llvm and target.result.cpu.arch == .s390x) continue; // TODO
2850
29 test_step.dependOn(&b.addRunArtifact(test_exe).step);
51 const foo = b.addLibrary(.{
52 .linkage = .static,
53 .name = lib_name,
54 .root_module = b.createModule(.{
55 .root_source_file = null,
56 .optimize = optimize,
57 .target = target,
58 .link_libc = dyn_libc,
59 }),
60 .use_llvm = lib_llvm,
61 });
62 foo.root_module.addCSourceFile(.{ .file = b.path("foo.c"), .flags = &[_][]const u8{} });
63 foo.root_module.addIncludePath(b.path("."));
64
65 const test_exe = b.addTest(.{
66 .name = exe_name,
67 .root_module = b.createModule(.{
68 .root_source_file = b.path("foo.zig"),
69 .target = target,
70 .optimize = optimize,
71 .link_libc = dyn_libc,
72 }),
73 .use_llvm = exe_llvm,
74 });
75 test_exe.root_module.linkLibrary(foo);
76 test_exe.root_module.addIncludePath(b.path("."));
77
78 test_step.dependOn(&b.addRunArtifact(test_exe).step);
79 }
3080}
test/tests.zig+192-29
......@@ -10,6 +10,7 @@ const error_traces = @import("error_traces.zig");
1010const stack_traces = @import("stack_traces.zig");
1111const llvm_ir = @import("llvm_ir.zig");
1212const libc = @import("libc.zig");
13const link = @import("link.zig");
1314
1415// Implementations
1516pub const ErrorTracesContext = @import("src/ErrorTrace.zig");
......@@ -17,6 +18,7 @@ pub const StackTracesContext = @import("src/StackTrace.zig");
1718pub const DebuggerContext = @import("src/Debugger.zig");
1819pub const LlvmIrContext = @import("src/LlvmIr.zig");
1920pub const LibcContext = @import("src/Libc.zig");
21pub const LinkContext = @import("src/Link.zig");
2022
2123const ModuleTestTarget = struct {
2224 linkage: ?std.builtin.LinkMode = null,
......@@ -2019,42 +2021,132 @@ const c_abi_targets = blk: {
20192021 },
20202022 },
20212023
2022 //.{
2023 // .target = .{
2024 // .cpu_arch = .x86_64,
2025 // .os_tag = .windows,
2026 // .abi = .gnu,
2027 // },
2028 // .use_llvm = false,
2029 // .c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"},
2030 //},
2031 //.{
2032 // .target = .{
2033 // .cpu_arch = .x86_64,
2034 // .cpu_model = .{ .explicit = &std.Target.x86.cpu.x86_64_v2 },
2035 // .os_tag = .windows,
2036 // .abi = .gnu,
2037 // },
2038 // .use_llvm = false,
2039 // .c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"},
2040 //},
2041 //.{
2042 // .target = .{
2043 // .cpu_arch = .x86_64,
2044 // .cpu_model = .{ .explicit = &std.Target.x86.cpu.x86_64_v3 },
2045 // .os_tag = .windows,
2046 // .abi = .gnu,
2047 // },
2048 // .use_llvm = false,
2049 // .c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"},
2050 //},
20512024 .{
20522025 .target = .{
20532026 .cpu_arch = .x86_64,
20542027 .os_tag = .windows,
20552028 .abi = .gnu,
20562029 },
2030 .use_llvm = false,
2031 .c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"},
2032 },
2033 .{
2034 .target = .{
2035 .cpu_arch = .x86_64,
2036 .cpu_model = .{ .explicit = &std.Target.x86.cpu.x86_64_v2 },
2037 .os_tag = .windows,
2038 .abi = .gnu,
2039 },
2040 .use_llvm = false,
2041 .c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"},
2042 },
2043 .{
2044 .target = .{
2045 .cpu_arch = .x86_64,
2046 .cpu_model = .{ .explicit = &std.Target.x86.cpu.x86_64_v3 },
2047 .os_tag = .windows,
2048 .abi = .gnu,
2049 },
2050 .use_llvm = false,
2051 .c_defines = &.{"ZIG_BACKEND_STAGE2_X86_64"},
2052 },
2053 .{
2054 .target = .{
2055 .cpu_arch = .x86_64,
2056 .os_tag = .windows,
2057 .abi = .gnu,
2058 },
2059 .use_llvm = true,
2060 },
2061 };
2062};
2063
2064const LinkTarget = struct {
2065 target: std.Target.Query = .{},
2066 optimize_mode: std.builtin.OptimizeMode = .Debug,
2067 link_libc: bool = false,
2068 use_llvm: bool = false,
2069 use_lld: bool = false,
2070};
2071
2072const link_targets = blk: {
2073 @setEvalBranchQuota(30000);
2074 break :blk [_]LinkTarget{
2075 // Native Targets
2076
2077 // .{
2078 // .use_llvm = true,
2079 // },
2080
2081 // Windows Targets
2082
2083 .{
2084 .target = .{
2085 .cpu_arch = .x86_64,
2086 .os_tag = .windows,
2087 .abi = .gnu,
2088 },
2089 },
2090 .{
2091 .target = .{
2092 .cpu_arch = .x86_64,
2093 .os_tag = .windows,
2094 .abi = .gnu,
2095 },
2096 .link_libc = true,
2097 },
2098 .{
2099 .target = .{
2100 .cpu_arch = .x86_64,
2101 .os_tag = .windows,
2102 .abi = .gnu,
2103 },
2104 .use_llvm = true,
2105 .use_lld = true,
2106 },
2107 .{
2108 .target = .{
2109 .cpu_arch = .x86_64,
2110 .os_tag = .windows,
2111 .abi = .gnu,
2112 },
2113 .link_libc = true,
2114 .use_llvm = true,
2115 .use_lld = true,
2116 },
2117 .{
2118 .target = .{
2119 .cpu_arch = .x86_64,
2120 .os_tag = .windows,
2121 .abi = .msvc,
2122 },
2123 },
2124 .{
2125 .target = .{
2126 .cpu_arch = .x86_64,
2127 .os_tag = .windows,
2128 .abi = .msvc,
2129 },
2130 .link_libc = true,
2131 },
2132 .{
2133 .target = .{
2134 .cpu_arch = .x86_64,
2135 .os_tag = .windows,
2136 .abi = .msvc,
2137 },
2138 .use_llvm = true,
2139 .use_lld = true,
2140 },
2141 .{
2142 .target = .{
2143 .cpu_arch = .x86_64,
2144 .os_tag = .windows,
2145 .abi = .msvc,
2146 },
2147 .link_libc = true,
20572148 .use_llvm = true,
2149 .use_lld = true,
20582150 },
20592151 };
20602152};
......@@ -3083,6 +3175,77 @@ pub fn addCAbiTests(b: *std.Build, options: CAbiTestOptions) *Step {
30833175 return step;
30843176}
30853177
3178const LinkTestOptions = struct {
3179 test_target_filters: []const []const u8,
3180 test_filters: []const []const u8,
3181 optimize_modes: []const OptimizeMode,
3182 skip_non_native: bool,
3183 skip_windows: bool,
3184 skip_llvm: bool,
3185 max_rss: usize,
3186};
3187
3188pub fn addLinkTests(b: *std.Build, options: LinkTestOptions) *Step {
3189 const step = b.step("test-link", "Run the linker tests");
3190 const update_snapshots = b.option(
3191 bool,
3192 "link-snapshot-update",
3193 "Update linker test snapshots in-place instead of testing against them",
3194 ) orelse false;
3195
3196 for (link_targets) |link_target| {
3197 if (options.skip_non_native and !link_target.target.isNative()) continue;
3198 if (options.skip_windows and link_target.target.os_tag == .windows) continue;
3199
3200 const resolved_target = b.resolveTargetQuery(link_target.target);
3201 const triple_txt = resolved_target.query.zigTriple(b.allocator) catch @panic("OOM");
3202 const target = &resolved_target.result;
3203
3204 if (options.test_target_filters.len > 0) {
3205 for (options.test_target_filters) |filter| {
3206 if (std.mem.indexOf(u8, triple_txt, filter) != null) break;
3207 } else continue;
3208 }
3209
3210 for (options.optimize_modes) |optimize_mode| {
3211 if (link_target.optimize_mode != optimize_mode) continue;
3212 if (link_target.link_libc and target.abi == .msvc and b.graph.host.result.os.tag != .windows) continue;
3213 const would_use_llvm = wouldUseLlvm(link_target.use_llvm, link_target.target, optimize_mode);
3214 if (options.skip_llvm and would_use_llvm) continue;
3215
3216 const opt_update_step = if (update_snapshots) update: {
3217 const update_step = Step.UpdateSourceFiles.create(b);
3218 step.dependOn(&update_step.step);
3219 break :update update_step;
3220 } else null;
3221
3222 var context: LinkContext = .{
3223 .b = b,
3224 .step = step,
3225 .optimize = optimize_mode,
3226 .target = resolved_target,
3227 .target_desc = std.fmt.allocPrint(b.allocator, "{s}-{t}{s}{s}{s}", .{
3228 target.zigTriple(b.allocator) catch @panic("OOM"),
3229 optimize_mode,
3230 if (link_target.use_llvm) "-llvm" else "",
3231 if (link_target.use_lld) "-lld" else "",
3232 if (link_target.link_libc) "-libc" else "",
3233 }) catch @panic("OOM"),
3234 .use_llvm = link_target.use_llvm,
3235 .use_lld = link_target.use_lld,
3236 .link_libc = link_target.link_libc,
3237 .test_filters = options.test_filters,
3238 .update_step = opt_update_step,
3239 .updated_snapshots = .empty,
3240 .max_rss = options.max_rss,
3241 };
3242
3243 link.addCases(&context);
3244 }
3245 }
3246 return step;
3247}
3248
30863249pub fn addCases(
30873250 b: *std.Build,
30883251 parent_step: *Step,