authorgravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2026-06-05 01:55:36-04:00
committergravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2026-06-23 00:27:14-04:00
log1c6d19b0ff6c816cf9f73507e7e4ff35b19a1e1d
tree00f0bd41894a6d55f7552960516b94a64c9c2f18
parentab46b8235450cd1d8cb1a483859c2265588a1863

tests: more work on the linker snapshot testing framework

build: add the ability to test the output of a Run step against a snapshot objdump: add --redact, --omit-element, --snapshot, and --only-symbol

11 files changed, 634 insertions(+), 344 deletions(-)

lib/compiler/Maker/Step/Run.zig+47-2
...@@ -2100,6 +2100,47 @@ fn runCommand(...@@ -2100,6 +2100,47 @@ fn runCommand(
2100 });2100 });
2101 }2101 }
2102 }2102 }
2103 const snapshots: []const ?struct {
2104 path: Cache.Path,
2105 result: enum { stderr, stdout },
2106 } = &.{
2107 if (conf_run.expect_stderr_snapshot.value) |path| .{
2108 .path = try maker.resolveLazyPathIndex(arena, path, run_index),
2109 .result = .stderr,
2110 } else null,
2111 if (conf_run.expect_stdout_snapshot.value) |path| .{
2112 .path = try maker.resolveLazyPathIndex(arena, path, run_index),
2113 .result = .stdout,
2114 } else null,
2115 };
2116 for (snapshots) |opt_snapshot| {
2117 const snapshot = opt_snapshot orelse continue;
2118
2119 const file = snapshot.path.root_dir.handle.openFile(io, snapshot.path.sub_path, .{}) catch |err|
2120 return step.fail(maker, "unable to open snapshot file {f}: {t}", .{ snapshot.path, err });
2121 defer file.close(io);
2122
2123 var file_reader = file.reader(io, &.{});
2124 const snapshot_contents = file_reader.interface.allocRemaining(gpa, .unlimited) catch |err|
2125 return step.fail(maker, "unable to read snapshot file {f}: {t}", .{ snapshot.path, err });
2126 defer gpa.free(snapshot_contents);
2127
2128 const result = switch (snapshot.result) {
2129 .stdout => generic_result.stdout.?,
2130 .stderr => generic_result.stderr.?,
2131 };
2132 if (!mem.eql(u8, snapshot_contents, result)) {
2133 return step.fail(maker,
2134 \\
2135 \\========= snapshot file: =========
2136 \\{f}
2137 \\========= contained: =============
2138 \\{s}
2139 \\========= {t} output was: ========
2140 \\{s}
2141 , .{ snapshot.path, snapshot_contents, snapshot.result, result });
2142 }
2143 }
2103 },2144 },
2104 else => {2145 else => {
2105 // On failure, report captured stderr like normal standard error output.2146 // On failure, report captured stderr like normal standard error output.
...@@ -2283,11 +2324,15 @@ fn setColorEnvironmentVariables(...@@ -2283,11 +2324,15 @@ fn setColorEnvironmentVariables(
2283}2324}
22842325
2285fn checksContainStdout(conf_run: *const Configuration.Step.Run) bool {2326fn checksContainStdout(conf_run: *const Configuration.Step.Run) bool {
2286 return conf_run.expect_stdout_exact.value != null or conf_run.expect_stdout_match.slice.len != 0;2327 return conf_run.expect_stdout_exact.value != null or
2328 conf_run.expect_stdout_match.slice.len != 0 or
2329 conf_run.expect_stdout_snapshot.value != null;
2287}2330}
22882331
2289fn checksContainStderr(conf_run: *const Configuration.Step.Run) bool {2332fn checksContainStderr(conf_run: *const Configuration.Step.Run) bool {
2290 return conf_run.expect_stderr_exact.value != null or conf_run.expect_stderr_match.slice.len != 0;2333 return conf_run.expect_stderr_exact.value != null or
2334 conf_run.expect_stderr_match.slice.len != 0 or
2335 conf_run.expect_stderr_snapshot.value != null;
2291}2336}
22922337
2293/// If `path` is cwd-relative, make it relative to the cwd of the child instead.2338/// If `path` is cwd-relative, make it relative to the cwd of the child instead.
lib/compiler/configurer.zig+8
...@@ -1006,6 +1006,8 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {...@@ -1006,6 +1006,8 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {
1006 status: Configuration.Step.Run.ExpectTermStatus,1006 status: Configuration.Step.Run.ExpectTermStatus,
1007 value: u32,1007 value: u32,
1008 } = null;1008 } = null;
1009 var expect_stderr_snapshot: ?Configuration.LazyPath.Index = null;
1010 var expect_stdout_snapshot: ?Configuration.LazyPath.Index = null;
1009 switch (run.stdio) {1011 switch (run.stdio) {
1010 .check => |checks| for (checks.items) |check| switch (check) {1012 .check => |checks| for (checks.items) |check| switch (check) {
1011 .expect_stderr_exact => |bytes| expect_stderr_exact = try wc.addBytes(bytes),1013 .expect_stderr_exact => |bytes| expect_stderr_exact = try wc.addBytes(bytes),
...@@ -1022,6 +1024,8 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {...@@ -1022,6 +1024,8 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {
1022 .stopped => |x| .{ .status = .stopped, .value = @intFromEnum(x) },1024 .stopped => |x| .{ .status = .stopped, .value = @intFromEnum(x) },
1023 .unknown => |x| .{ .status = .unknown, .value = x },1025 .unknown => |x| .{ .status = .unknown, .value = x },
1024 },1026 },
1027 .expect_stderr_snapshot => |path| expect_stderr_snapshot = try s.addLazyPath(path),
1028 .expect_stdout_snapshot => |path| expect_stdout_snapshot = try s.addLazyPath(path),
1025 },1029 },
1026 else => {},1030 else => {},
1027 }1031 }
...@@ -1061,6 +1065,8 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {...@@ -1061,6 +1065,8 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {
1061 .expect_stdout_match = expect_stdout_match.items.len != 0,1065 .expect_stdout_match = expect_stdout_match.items.len != 0,
1062 .expect_term = expect_term != null,1066 .expect_term = expect_term != null,
1063 .expect_term_status = if (expect_term) |t| t.status else .exited,1067 .expect_term_status = if (expect_term) |t| t.status else .exited,
1068 .expect_stderr_snapshot = expect_stderr_snapshot != null,
1069 .expect_stdout_snapshot = expect_stdout_snapshot != null,
1064 },1070 },
1065 .file_inputs = .{ .slice = try s.initLazyPathList(run.file_inputs.items) },1071 .file_inputs = .{ .slice = try s.initLazyPathList(run.file_inputs.items) },
1066 .args = .{ .slice = try s.initArgsList(run.argv.items) },1072 .args = .{ .slice = try s.initArgsList(run.argv.items) },
...@@ -1081,6 +1087,8 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {...@@ -1081,6 +1087,8 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void {
1081 .expect_stdout_exact = .{ .value = if (expect_stdout_exact) |bytes| bytes else null },1087 .expect_stdout_exact = .{ .value = if (expect_stdout_exact) |bytes| bytes else null },
1082 .expect_stderr_match = .{ .slice = expect_stderr_match.items },1088 .expect_stderr_match = .{ .slice = expect_stderr_match.items },
1083 .expect_stdout_match = .{ .slice = expect_stdout_match.items },1089 .expect_stdout_match = .{ .slice = expect_stdout_match.items },
1090 .expect_stderr_snapshot = .{ .value = expect_stderr_snapshot orelse null },
1091 .expect_stdout_snapshot = .{ .value = expect_stdout_snapshot orelse null },
1084 .stdin = .{ .u = switch (run.stdin) {1092 .stdin = .{ .u = switch (run.stdin) {
1085 .none => .none,1093 .none => .none,
1086 .bytes => |bytes| .{ .bytes = try wc.addBytes(bytes) },1094 .bytes => |bytes| .{ .bytes = try wc.addBytes(bytes) },
lib/compiler/objdump.zig+448-297
...@@ -16,9 +16,12 @@ const Options = struct {...@@ -16,9 +16,12 @@ const Options = struct {
16 input_path: []const u8,16 input_path: []const u8,
17 member_filters: []const []const u8 = &.{},17 member_filters: []const []const u8 = &.{},
18 member_headers: bool,18 member_headers: bool,
19 omit_elements: std.enums.EnumArray(Element, bool),
20 redact: std.enums.EnumArray(FieldKind, bool),
19 relocs: bool,21 relocs: bool,
20 section_filters: []const []const u8 = &.{},22 section_filters: []const []const u8 = &.{},
21 section_headers: bool,23 section_headers: bool,
24 symbol_filters: []const []const u8 = &.{},
22 strings: bool,25 strings: bool,
23 symbols: bool,26 symbols: bool,
24 tls: bool,27 tls: bool,
...@@ -27,6 +30,20 @@ const Options = struct {...@@ -27,6 +30,20 @@ const Options = struct {
27 linker_member: ?std.coff.ArchiveMemberHeader.Kind,30 linker_member: ?std.coff.ArchiveMemberHeader.Kind,
28};31};
2932
33const FieldKind = enum {
34 va,
35 rva,
36 ord,
37 size,
38};
39
40const Element = enum {
41 @"file-type",
42 @"table-header",
43 @"header-names",
44 newlines,
45};
46
30pub fn main(init: std.process.Init) !void {47pub fn main(init: std.process.Init) !void {
31 const io = init.io;48 const io = init.io;
32 const args = try init.minimal.args.toSlice(init.arena.allocator());49 const args = try init.minimal.args.toSlice(init.arena.allocator());
...@@ -40,12 +57,15 @@ pub fn main(init: std.process.Init) !void {...@@ -40,12 +57,15 @@ pub fn main(init: std.process.Init) !void {
40 var opt_input_path: ?[]const u8 = null;57 var opt_input_path: ?[]const u8 = null;
41 var opt_linker_member: ?std.coff.ArchiveMemberHeader.Kind = null;58 var opt_linker_member: ?std.coff.ArchiveMemberHeader.Kind = null;
42 var opt_member_headers: ?bool = null;59 var opt_member_headers: ?bool = null;
60 var omit_elements: @FieldType(Options, "omit_elements") = .initFill(false);
61 var redact: @FieldType(Options, "redact") = .initFill(false);
43 var opt_relocs: ?bool = null;62 var opt_relocs: ?bool = null;
44 var opt_section_headers: ?bool = null;63 var opt_section_headers: ?bool = null;
45 var opt_strings: ?bool = null;64 var opt_strings: ?bool = null;
46 var opt_symbols: ?bool = null;65 var opt_symbols: ?bool = null;
47 var opt_tls: ?bool = null;66 var opt_tls: ?bool = null;
48 var section_filters: std.ArrayList([]const u8) = .empty;67 var section_filters: std.ArrayList([]const u8) = .empty;
68 var symbol_filters: std.ArrayList([]const u8) = .empty;
49 var member_filters: std.ArrayList([]const u8) = .empty;69 var member_filters: std.ArrayList([]const u8) = .empty;
50 while (i < args.len) : (i += 1) {70 while (i < args.len) : (i += 1) {
51 const arg = args[i];71 const arg = args[i];
...@@ -73,14 +93,37 @@ pub fn main(init: std.process.Init) !void {...@@ -73,14 +93,37 @@ pub fn main(init: std.process.Init) !void {
73 opt_linker_member = .second_linker;93 opt_linker_member = .second_linker;
74 } else if (mem.eql(u8, arg, "--member-headers")) {94 } else if (mem.eql(u8, arg, "--member-headers")) {
75 opt_member_headers = true;95 opt_member_headers = true;
76 } else if (mem.startsWith(u8, arg, "--only-section=")) {96 } else if (mem.startsWith(u8, arg, "--omit-element=")) {
77 (try section_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-section=".len..]);97 const kind = arg["--omit-element=".len..];
98 if (std.meta.stringToEnum(Element, kind)) |format_kind| {
99 omit_elements.set(format_kind, true);
100 } else if (std.mem.eql(u8, kind, "all")) {
101 omit_elements = .initFill(true);
102 } else {
103 fatal("unrecognized element: {s}", .{kind});
104 }
78 } else if (mem.startsWith(u8, arg, "--only-member=")) {105 } else if (mem.startsWith(u8, arg, "--only-member=")) {
79 (try member_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-member=".len..]);106 (try member_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-member=".len..]);
107 } else if (mem.startsWith(u8, arg, "--only-section=")) {
108 (try section_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-section=".len..]);
109 } else if (mem.startsWith(u8, arg, "--only-symbol=")) {
110 (try symbol_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-symbol=".len..]);
111 } else if (mem.startsWith(u8, arg, "--redact=")) {
112 const kind = arg["--redact=".len..];
113 if (std.meta.stringToEnum(FieldKind, kind)) |field_kind| {
114 redact.set(field_kind, true);
115 } else if (std.mem.eql(u8, kind, "all")) {
116 redact = .initFill(true);
117 } else {
118 fatal("unrecognized redaction kind: {s}", .{kind});
119 }
80 } else if (mem.eql(u8, arg, "--relocs")) {120 } else if (mem.eql(u8, arg, "--relocs")) {
81 opt_relocs = true;121 opt_relocs = true;
82 } else if (mem.eql(u8, arg, "--section-headers")) {122 } else if (mem.eql(u8, arg, "--section-headers")) {
83 opt_section_headers = true;123 opt_section_headers = true;
124 } else if (mem.eql(u8, arg, "-s") or mem.eql(u8, arg, "--snapshot")) {
125 omit_elements = .initFill(true);
126 redact = .initFill(true);
84 } else if (mem.eql(u8, arg, "--strings")) {127 } else if (mem.eql(u8, arg, "--strings")) {
85 opt_strings = true;128 opt_strings = true;
86 } else if (mem.eql(u8, arg, "--symbols")) {129 } else if (mem.eql(u8, arg, "--symbols")) {
...@@ -105,10 +148,13 @@ pub fn main(init: std.process.Init) !void {...@@ -105,10 +148,13 @@ pub fn main(init: std.process.Init) !void {
105 .linker_member = opt_linker_member,148 .linker_member = opt_linker_member,
106 .member_filters = member_filters.items,149 .member_filters = member_filters.items,
107 .member_headers = opt_member_headers orelse false,150 .member_headers = opt_member_headers orelse false,
151 .omit_elements = omit_elements,
152 .redact = redact,
153 .relocs = opt_relocs orelse false,
108 .section_filters = section_filters.items,154 .section_filters = section_filters.items,
109 .section_headers = opt_section_headers orelse false,155 .section_headers = opt_section_headers orelse false,
110 .relocs = opt_relocs orelse false,
111 .strings = opt_strings orelse false,156 .strings = opt_strings orelse false,
157 .symbol_filters = symbol_filters.items,
112 .symbols = opt_symbols orelse false,158 .symbols = opt_symbols orelse false,
113 .tls = opt_tls orelse false,159 .tls = opt_tls orelse false,
114 };160 };
...@@ -120,7 +166,15 @@ pub fn main(init: std.process.Init) !void {...@@ -120,7 +166,15 @@ pub fn main(init: std.process.Init) !void {
120 var buffer: [4096]u8 = undefined;166 var buffer: [4096]u8 = undefined;
121 var file_reader = file.reader(io, &buffer);167 var file_reader = file.reader(io, &buffer);
122 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &stdout_buffer);168 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &stdout_buffer);
123 dump(init.gpa, &opts, &file_reader, &stdout_writer.interface) catch |err| switch (err) {169
170 const ctx: DumpContext = .{
171 .gpa = init.gpa,
172 .opts = &opts,
173 .fr = &file_reader,
174 .w = &stdout_writer.interface,
175 };
176
177 dump(&ctx) catch |err| switch (err) {
124 error.ReadFailed => return file_reader.err.?,178 error.ReadFailed => return file_reader.err.?,
125 error.WriteFailed => return stdout_writer.err.?,179 error.WriteFailed => return stdout_writer.err.?,
126 error.UnknownFile => fatal("unrecognized file: {s}", .{opts.input_path}),180 error.UnknownFile => fatal("unrecognized file: {s}", .{opts.input_path}),
...@@ -130,60 +184,82 @@ pub fn main(init: std.process.Init) !void {...@@ -130,60 +184,82 @@ pub fn main(init: std.process.Init) !void {
130 try stdout_writer.flush();184 try stdout_writer.flush();
131}185}
132186
133fn dump(gpa: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void {187fn dump(d: *const DumpContext) !void {
134 const r = &fr.interface;188 const r = &d.fr.interface;
135 try r.fill(4);189 try r.fill(4);
136 elf: {190 elf: {
137 if (!mem.eql(u8, r.buffered()[0..4], std.elf.MAGIC)) break :elf;191 if (!mem.eql(u8, r.buffered()[0..4], std.elf.MAGIC)) break :elf;
138 return elf.dump(r, w);192 return elf.dump(r, d.w);
139 }193 }
140 macho: {194 macho: {
141 if (mem.readInt(u32, r.buffered()[0..4], .little) != std.macho.MH_MAGIC_64) break :macho;195 if (mem.readInt(u32, r.buffered()[0..4], .little) != std.macho.MH_MAGIC_64) break :macho;
142 return macho.dump(r, w);196 return macho.dump(r, d.w);
143 }197 }
144 wasm: {198 wasm: {
145 comptime assert(std.wasm.magic.len == 4);199 comptime assert(std.wasm.magic.len == 4);
146 if (!mem.eql(u8, r.buffered()[0..4], &std.wasm.magic)) break :wasm;200 if (!mem.eql(u8, r.buffered()[0..4], &std.wasm.magic)) break :wasm;
147 return wasm.dump(r, w);201 return wasm.dump(r, d.w);
148 }202 }
149 coff: {203 coff: {
150 const ext = std.fs.path.extension(opts.input_path);204 const ext = std.fs.path.extension(d.opts.input_path);
151 const basename = std.fs.path.basename(opts.input_path);205 const basename = std.fs.path.basename(d.opts.input_path);
152 if (std.mem.eql(u8, ext, ".exe") or std.mem.eql(u8, ext, ".dll")) {206 if (std.mem.eql(u8, ext, ".exe") or std.mem.eql(u8, ext, ".dll")) {
153 if (!mem.eql(u8, r.buffered()[0..2], "MZ")) break :coff;207 if (!mem.eql(u8, r.buffered()[0..2], "MZ")) break :coff;
154 try r.discardAll(std.coff.pe_pointer_offset);208 try r.discardAll(std.coff.pe_pointer_offset);
155 const sig_offset = try r.takeInt(u32, .little);209 const sig_offset = try r.takeInt(u32, .little);
156 try fr.seekTo(sig_offset);210 try d.fr.seekTo(sig_offset);
157 const sig = try r.take(4);211 const sig = try r.take(4);
158212
159 if (!std.mem.eql(u8, sig, std.coff.pe_signature)) {213 if (!std.mem.eql(u8, sig, std.coff.pe_signature)) {
160 try w.print("invalid PE signature: {x}", .{sig});214 try d.w.print("invalid PE signature: {x}", .{sig});
161 return error.ParseFailure;215 return error.ParseFailure;
162 }216 }
163217
164 try w.print("{s}: PE/COFF image\n\n", .{basename});218 if (d.element(.@"file-type"))
165 return coff.dumpObject(gpa, opts, true, basename, fr, w);219 try d.w.print("{s}: PE/COFF image\n\n", .{basename});
220
221 return coff.dumpObject(d, true, basename);
166 } else if (std.mem.eql(u8, ext, ".lib")) {222 } else if (std.mem.eql(u8, ext, ".lib")) {
167 r.fill(std.coff.archive_signature.len) catch break :coff;223 r.fill(std.coff.archive_signature.len) catch break :coff;
168 if (!mem.eql(u8, r.buffered()[0..std.coff.archive_signature.len], std.coff.archive_signature)) break :coff;224 if (!mem.eql(u8, r.buffered()[0..std.coff.archive_signature.len], std.coff.archive_signature)) break :coff;
169 try w.print("{s}: COFF archive\n\n", .{basename});225 if (d.element(.@"file-type"))
170 return coff.dumpArchive(gpa, opts, fr, w);226 try d.w.print("{s}: COFF archive\n\n", .{basename});
227
228 return coff.dumpArchive(d);
171 } else if (std.mem.eql(u8, ext, ".obj")) {229 } else if (std.mem.eql(u8, ext, ".obj")) {
172 try w.print("{s}: COFF object\n\n", .{basename});230 if (d.element(.@"file-type"))
173 return coff.dumpObject(gpa, opts, false, basename, fr, w);231 try d.w.print("{s}: COFF object\n\n", .{basename});
232
233 return coff.dumpObject(d, false, basename);
174 }234 }
175 }235 }
176 return error.UnknownFile;236 return error.UnknownFile;
177}237}
178238
179fn failParse(239const DumpContext = struct {
240 gpa: std.mem.Allocator,
180 opts: *const Options,241 opts: *const Options,
181 comptime fmt: []const u8,242 fr: *Io.File.Reader,
182 args: anytype,243 w: *Io.Writer,
183) noreturn {244
184 std.log.err("error parsing '{s}'", .{std.fs.path.basename(opts.input_path)});245 fn element(self: *const DumpContext, e: Element) bool {
185 fatal(fmt, args);246 return !self.opts.omit_elements.get(e);
186}247 }
248
249 fn redacted(self: *const DumpContext, opt_kind: ?FieldKind) bool {
250 const kind = opt_kind orelse return false;
251 return self.opts.redact.get(kind);
252 }
253
254 fn failParse(
255 ctx: *const DumpContext,
256 comptime fmt: []const u8,
257 args: anytype,
258 ) noreturn {
259 std.log.err("error parsing '{s}'", .{std.fs.path.basename(ctx.opts.input_path)});
260 fatal(fmt, args);
261 }
262};
187263
188const elf = struct {264const elf = struct {
189 fn dump(r: *Io.Reader, w: *Io.Writer) !void {265 fn dump(r: *Io.Reader, w: *Io.Writer) !void {
...@@ -230,29 +306,33 @@ const coff = struct {...@@ -230,29 +306,33 @@ const coff = struct {
230 file_mode: u24,306 file_mode: u24,
231 size: u34,307 size: u34,
232308
233 pub fn fromRaw(opts: *const Options, raw_header: *const std.coff.ArchiveMemberHeader, opt_longnames: ?[]const u8) @This() {309 pub fn fromRaw(d: *const DumpContext, raw_header: *const std.coff.ArchiveMemberHeader, opt_longnames: ?[]const u8) @This() {
234 const name = raw_header.parseName(opt_longnames) catch |err| switch (err) {310 const name = raw_header.parseName(opt_longnames) catch |err| switch (err) {
235 error.BadName => failParse(opts, "malformed member name: '{s}'", .{&raw_header.name}),311 error.BadName => d.failParse("malformed member name: '{s}'", .{&raw_header.name}),
236 error.NoLongNames => failParse(opts, "member uses a long name, but there was no longnames member", .{}),312 error.NoLongNames => d.failParse("member uses a long name, but there was no longnames member", .{}),
237 };313 };
238314
239 return .{315 return .{
240 .name = name,316 .name = name,
241 .date = raw_header.parseDate() catch |err|317 .date = raw_header.parseDate() catch |err|
242 failParse(opts, "unable to parse date '{s}' in member '{s}': {t}", .{ raw_header.date, name, err }),318 d.failParse("unable to parse date '{s}' in member '{s}': {t}", .{ raw_header.date, name, err }),
243 .user_id = raw_header.parseUserId() catch |err|319 .user_id = raw_header.parseUserId() catch |err|
244 failParse(opts, "unable to parse user_id '{s}' in member '{s}': {t}", .{ raw_header.user_id, name, err }),320 d.failParse("unable to parse user_id '{s}' in member '{s}': {t}", .{ raw_header.user_id, name, err }),
245 .group_id = raw_header.parseGroupId() catch |err|321 .group_id = raw_header.parseGroupId() catch |err|
246 failParse(opts, "unable to parse group_id '{s}' in member '{s}': {t}", .{ raw_header.group_id, name, err }),322 d.failParse("unable to parse group_id '{s}' in member '{s}': {t}", .{ raw_header.group_id, name, err }),
247 .file_mode = raw_header.parseFileMode() catch |err|323 .file_mode = raw_header.parseFileMode() catch |err|
248 failParse(opts, "unable to parse file_mode '{s}' in member '{s}': {t}", .{ raw_header.file_mode, name, err }),324 d.failParse("unable to parse file_mode '{s}' in member '{s}': {t}", .{ raw_header.file_mode, name, err }),
249 .size = raw_header.parseSize() catch |err|325 .size = raw_header.parseSize() catch |err|
250 failParse(opts, "unable to parse size '{s}' in member '{s}': {t}", .{ raw_header.size, name, err }),326 d.failParse("unable to parse size '{s}' in member '{s}': {t}", .{ raw_header.size, name, err }),
251 };327 };
252 }328 }
253 };329 };
254330
255 fn dumpArchive(gpa: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void {331 fn dumpArchive(d: *const DumpContext) !void {
332 const gpa = d.gpa;
333 const fr = d.fr;
334 const w = d.w;
335
256 const r = &fr.interface;336 const r = &fr.interface;
257 r.toss(std.coff.archive_signature.len);337 r.toss(std.coff.archive_signature.len);
258338
...@@ -272,26 +352,26 @@ const coff = struct {...@@ -272,26 +352,26 @@ const coff = struct {
272 while (pos < size) : (pos = fr.logicalPos()) {352 while (pos < size) : (pos = fr.logicalPos()) {
273 if ((pos & 1) != 0) try r.discardAll(1);353 if ((pos & 1) != 0) try r.discardAll(1);
274 const raw_header = try r.takeStruct(std.coff.ArchiveMemberHeader, .little);354 const raw_header = try r.takeStruct(std.coff.ArchiveMemberHeader, .little);
275 const header: ArchiveHeader = .fromRaw(opts, &raw_header, opt_longnames);355 const header: ArchiveHeader = .fromRaw(d, &raw_header, opt_longnames);
276356
277 if (!std.mem.eql(u8, &raw_header.end_of_header, std.coff.archive_end_of_header))357 if (!std.mem.eql(u8, &raw_header.end_of_header, std.coff.archive_end_of_header))
278 return failParse(opts, "malformed end-of-header field in member '{s}': {x}", .{ header.name, raw_header.end_of_header });358 return d.failParse("malformed end-of-header field in member '{s}': {x}", .{ header.name, raw_header.end_of_header });
279359
280 const dump_header =360 const dump_header =
281 (opts.member_headers and filterMatches(opts.member_filters, header.name)) or361 (d.opts.member_headers and filterMatches(d.opts.member_filters, header.name)) or
282 (opts.linker_member == opt_expected_kind);362 (d.opts.linker_member == opt_expected_kind);
283363
284 if (dump_header)364 if (dump_header)
285 try dumpArchiveHeader(w, &header, @intCast(pos));365 try dumpArchiveHeader(d, &header, @intCast(pos));
286366
287 const member_end = fr.logicalPos() + header.size;367 const member_end = fr.logicalPos() + header.size;
288 if (member_end > size)368 if (member_end > size)
289 return failParse(opts, "out-of-bounds length 0x{x} in member '{s}'", .{ header.size, header.name });369 return d.failParse("out-of-bounds length 0x{x} in member '{s}'", .{ header.size, header.name });
290370
291 if (opt_expected_kind) |expected_kind| switch (expected_kind) {371 if (opt_expected_kind) |expected_kind| switch (expected_kind) {
292 .first_linker => {372 .first_linker => {
293 if (!std.mem.eql(u8, header.name, "/"))373 if (!std.mem.eql(u8, header.name, "/"))
294 return failParse(opts, "expected first linker member, found '{s}'", .{header.name});374 return d.failParse("expected first linker member, found '{s}'", .{header.name});
295375
296 const num_symbols = try r.takeInt(u32, .big);376 const num_symbols = try r.takeInt(u32, .big);
297 if (dump_header)377 if (dump_header)
...@@ -301,7 +381,7 @@ const coff = struct {...@@ -301,7 +381,7 @@ const coff = struct {
301 \\381 \\
302 , .{ expected_kind, num_symbols });382 , .{ expected_kind, num_symbols });
303383
304 if (opts.linker_member == .first_linker) {384 if (d.opts.linker_member == .first_linker) {
305 try w.writeAll(385 try w.writeAll(
306 \\386 \\
307 \\Archive symbols:387 \\Archive symbols:
...@@ -314,11 +394,15 @@ const coff = struct {...@@ -314,11 +394,15 @@ const coff = struct {
314394
315 for (0..num_symbols) |symbol_i| {395 for (0..num_symbols) |symbol_i| {
316 const symbol = r.takeDelimiter(0) catch |err|396 const symbol = r.takeDelimiter(0) catch |err|
317 return failParse(opts, "unable to read first linker member string table: {t}", .{err});397 return d.failParse("unable to read first linker member string table: {t}", .{err});
318 try w.print("{x: >8} {s}\n", .{ std.mem.readInt(u32, offsets[symbol_i * 4 ..][0..4], .big), symbol.? });398 const offset = std.mem.readInt(u32, offsets[symbol_i * 4 ..][0..4], .big);
399 try w.print("{f} {s}\n", .{
400 fmtIntField(d, offset, .{ .kind = .va }),
401 symbol.?,
402 });
319 }403 }
320 }404 }
321 if (dump_header) try w.writeByte('\n');405 if (dump_header and d.element(.newlines)) try w.writeByte('\n');
322406
323 try fr.seekTo(member_end);407 try fr.seekTo(member_end);
324 opt_expected_kind = .second_linker;408 opt_expected_kind = .second_linker;
...@@ -326,12 +410,12 @@ const coff = struct {...@@ -326,12 +410,12 @@ const coff = struct {
326 },410 },
327 .second_linker => {411 .second_linker => {
328 if (!std.mem.eql(u8, header.name, "/"))412 if (!std.mem.eql(u8, header.name, "/"))
329 return failParse(opts, "expected second linker member, found '{s}'", .{header.name});413 return d.failParse("expected second linker member, found '{s}'", .{header.name});
330414
331 const num_members = try r.takeInt(u32, .little);415 const num_members = try r.takeInt(u32, .little);
332 pos = fr.logicalPos();416 pos = fr.logicalPos();
333 if (pos + num_members * @sizeOf(u32) > member_end)417 if (pos + num_members * @sizeOf(u32) > member_end)
334 return failParse(opts, "invalid member count 0x{x} in second linker member", .{num_members});418 return d.failParse("invalid member count 0x{x} in second linker member", .{num_members});
335419
336 try members.ensureTotalCapacity(gpa, num_members);420 try members.ensureTotalCapacity(gpa, num_members);
337 for (0..num_members) |_|421 for (0..num_members) |_|
...@@ -342,7 +426,7 @@ const coff = struct {...@@ -342,7 +426,7 @@ const coff = struct {
342 const num_symbols = try r.takeInt(u32, .little);426 const num_symbols = try r.takeInt(u32, .little);
343 pos = fr.logicalPos();427 pos = fr.logicalPos();
344 if (pos + num_symbols * @sizeOf(u16) > member_end)428 if (pos + num_symbols * @sizeOf(u16) > member_end)
345 return failParse(opts, "invalid symbol count 0x{x} in second linker member", .{num_symbols});429 return d.failParse("invalid symbol count 0x{x} in second linker member", .{num_symbols});
346430
347 if (dump_header)431 if (dump_header)
348 try w.print(432 try w.print(
...@@ -356,13 +440,14 @@ const coff = struct {...@@ -356,13 +440,14 @@ const coff = struct {
356 for (0..num_symbols) |_|440 for (0..num_symbols) |_|
357 symbol_member_indices.addOneAssumeCapacity().* = (try r.takeInt(u16, .little)) - 1;441 symbol_member_indices.addOneAssumeCapacity().* = (try r.takeInt(u16, .little)) - 1;
358442
359 if (opts.linker_member == .second_linker) {443 if (d.opts.linker_member == .second_linker) {
360 try w.writeAll(444 if (d.element(.@"table-header"))
361 \\445 try w.writeAll(
362 \\Archive Symbols:446 \\
363 \\& Member Symbol447 \\Archive Symbols:
364 \\448 \\& Member Symbol
365 );449 \\
450 );
366451
367 pos = fr.logicalPos();452 pos = fr.logicalPos();
368 var symbol_i: u32 = 0;453 var symbol_i: u32 = 0;
...@@ -373,23 +458,26 @@ const coff = struct {...@@ -373,23 +458,26 @@ const coff = struct {
373 const symbol_name = if (r.takeDelimiter(0) catch |err| switch (err) {458 const symbol_name = if (r.takeDelimiter(0) catch |err| switch (err) {
374 error.StreamTooLong => null,459 error.StreamTooLong => null,
375 else => |e| return e,460 else => |e| return e,
376 }) |n| n else return failParse(opts, "unterminated string found in second linker member", .{});461 }) |n| n else return d.failParse("unterminated string found in second linker member", .{});
377462
378 try w.print("{x: >8} {s}\n", .{463 try w.print("{f} {s}\n", .{
379 members.items[symbol_member_indices.items[symbol_i]].offset,464 fmtIntField(
465 d,
466 members.items[symbol_member_indices.items[symbol_i]].offset,
467 .{ .kind = .va },
468 ),
380 symbol_name,469 symbol_name,
381 });470 });
382 }471 }
383472
384 if (symbol_i != num_symbols)473 if (symbol_i != num_symbols)
385 return failParse(474 return d.failParse(
386 opts,
387 " expected {d} entries in second linker member string table, but found {d}",475 " expected {d} entries in second linker member string table, but found {d}",
388 .{ num_symbols, symbol_i },476 .{ num_symbols, symbol_i },
389 );477 );
390 }478 }
391479
392 try w.writeByte('\n');480 if (d.element(.newlines)) try w.writeByte('\n');
393 try fr.seekTo(member_end);481 try fr.seekTo(member_end);
394 opt_expected_kind = .longnames;482 opt_expected_kind = .longnames;
395 continue;483 continue;
...@@ -401,12 +489,13 @@ const coff = struct {...@@ -401,12 +489,13 @@ const coff = struct {
401 if (dump_header)489 if (dump_header)
402 try w.print("{t: >16} type\n", .{expected_kind});490 try w.print("{t: >16} type\n", .{expected_kind});
403491
404 if (opts.linker_member == .longnames) {492 if (d.opts.linker_member == .longnames) {
405 try w.print(493 if (d.element(.@"table-header"))
406 \\494 try w.print(
407 \\Longnames (0x{x} bytes):495 \\
408 \\496 \\Longnames (0x{x} bytes):
409 , .{opt_longnames.?.len});497 \\
498 , .{opt_longnames.?.len});
410499
411 var lr = Io.Reader.fixed(opt_longnames.?);500 var lr = Io.Reader.fixed(opt_longnames.?);
412 while (try lr.takeDelimiter(0)) |str| {501 while (try lr.takeDelimiter(0)) |str| {
...@@ -415,7 +504,7 @@ const coff = struct {...@@ -415,7 +504,7 @@ const coff = struct {
415 }504 }
416 }505 }
417506
418 try w.writeByte('\n');507 if (d.element(.newlines)) try w.writeByte('\n');
419 }508 }
420509
421 opt_expected_kind = null;510 opt_expected_kind = null;
...@@ -426,18 +515,18 @@ const coff = struct {...@@ -426,18 +515,18 @@ const coff = struct {
426 }515 }
427516
428 if (opt_expected_kind) |expected_kind| switch (expected_kind) {517 if (opt_expected_kind) |expected_kind| switch (expected_kind) {
429 .first_linker => failParse(opts, "missing first linker member", .{}),518 .first_linker => d.failParse("missing first linker member", .{}),
430 .second_linker => failParse(opts, "missing second linker member", .{}),519 .second_linker => d.failParse("missing second linker member", .{}),
431 else => {},520 else => {},
432 };521 };
433522
434 for (members.items, 0..) |member, member_i| {523 for (members.items, 0..) |member, member_i| {
435 fr.seekTo(member.offset) catch |err|524 fr.seekTo(member.offset) catch |err|
436 failParse(opts, "unable to read member {d} at offset 0x{x}: {t}", .{ member_i, member.offset, err });525 d.failParse("unable to read member {d} at offset 0x{x}: {t}", .{ member_i, member.offset, err });
437526
438 const raw_header = try r.takeStruct(std.coff.ArchiveMemberHeader, .little);527 const raw_header = try r.takeStruct(std.coff.ArchiveMemberHeader, .little);
439 const header: ArchiveHeader = .fromRaw(opts, &raw_header, opt_longnames);528 const header: ArchiveHeader = .fromRaw(d, &raw_header, opt_longnames);
440 if (!filterMatches(opts.member_filters, header.name)) continue;529 if (!filterMatches(d.opts.member_filters, header.name)) continue;
441530
442 const member_sig = try r.peek(4);531 const member_sig = try r.peek(4);
443 const machine: std.coff.IMAGE.FILE.MACHINE =532 const machine: std.coff.IMAGE.FILE.MACHINE =
...@@ -445,17 +534,17 @@ const coff = struct {...@@ -445,17 +534,17 @@ const coff = struct {
445 const sig = std.mem.readInt(u16, member_sig[2..4], .little);534 const sig = std.mem.readInt(u16, member_sig[2..4], .little);
446535
447 const is_imp_lib = machine == std.coff.IMAGE.FILE.MACHINE.UNKNOWN and sig == 0xffff;536 const is_imp_lib = machine == std.coff.IMAGE.FILE.MACHINE.UNKNOWN and sig == 0xffff;
448 if (opts.member_headers or (opts.exports and is_imp_lib)) {537 if (d.opts.member_headers or (d.opts.exports and is_imp_lib)) {
449 try dumpArchiveHeader(w, &header, member.offset);538 try dumpArchiveHeader(d, &header, member.offset);
450 if (is_imp_lib) {539 if (is_imp_lib) {
451 try w.writeAll("\nImport header:\n");540 try w.writeAll("\nImport header:\n");
452541
453 const imp_header = try r.takeStruct(std.coff.ImportHeader, .little);542 const imp_header = try r.takeStruct(std.coff.ImportHeader, .little);
454 try dumpHeader(w, std.coff.ImportHeader, &imp_header, struct {543 try dumpHeader(d, std.coff.ImportHeader, &imp_header, struct {
455 pub fn sig1(_: *const std.coff.ImportHeader, _: *Io.Writer) !void {}544 pub fn sig1(_: *const DumpContext, _: *const std.coff.ImportHeader) !void {}
456 pub fn sig2(_: *const std.coff.ImportHeader, _: *Io.Writer) !void {}545 pub fn sig2(_: *const DumpContext, _: *const std.coff.ImportHeader) !void {}
457 pub fn types(h: *const std.coff.ImportHeader, cw: *Io.Writer) !void {546 pub fn types(id: *const DumpContext, h: *const std.coff.ImportHeader) !void {
458 try cw.print(547 try id.w.print(
459 \\{t: >16} import_type 548 \\{t: >16} import_type
460 \\{t: >16} name_type 549 \\{t: >16} name_type
461 \\550 \\
...@@ -490,51 +579,53 @@ const coff = struct {...@@ -490,51 +579,53 @@ const coff = struct {
490 } else {579 } else {
491 try w.writeAll(" COFF object type\n");580 try w.writeAll(" COFF object type\n");
492 }581 }
493 try w.writeByte('\n');582 if (d.element(.newlines)) try w.writeByte('\n');
494 }583 }
495584
496 if (is_imp_lib) continue;585 if (is_imp_lib) continue;
497 if (opts.section_headers or586 if (d.opts.section_headers or
498 opts.file_headers or587 d.opts.file_headers or
499 opts.relocs or588 d.opts.relocs or
500 opts.strings or589 d.opts.strings or
501 opts.symbols)590 d.opts.symbols)
502 {591 {
503 try w.print("{s}({s}): COFF object\n\n", .{ std.fs.path.basename(opts.input_path), header.name });592 if (d.element(.@"file-type"))
504 try dumpObject(gpa, opts, false, header.name, fr, w);593 try w.print("{s}({s}): COFF object\n\n", .{ std.fs.path.basename(d.opts.input_path), header.name });
594 try dumpObject(d, false, header.name);
505 }595 }
506 }596 }
507 }597 }
508598
509 fn dumpObject(599 fn dumpObject(
510 gpa: std.mem.Allocator,600 d: *const DumpContext,
511 opts: *const Options,
512 is_image: bool,601 is_image: bool,
513 obj_name: []const u8,602 obj_name: []const u8,
514 fr: *Io.File.Reader,
515 w: *Io.Writer,
516 ) !void {603 ) !void {
604 const gpa = d.gpa;
605 const fr = d.fr;
606 const w = d.w;
607
517 const file_location = fr.logicalPos();608 const file_location = fr.logicalPos();
518 const r = &fr.interface;609 const r = &fr.interface;
519 const header = r.takeStruct(std.coff.Header, .little) catch |err|610 const header = r.takeStruct(std.coff.Header, .little) catch |err|
520 return failParse(opts, "unable to read COFF header: {t}", .{err});611 return d.failParse("unable to read COFF header: {t}", .{err});
521612
522 if (opts.file_headers) {613 if (d.opts.file_headers) {
523 try w.writeAll("COFF Header:\n");614 if (d.element(.@"header-names")) try w.writeAll("COFF Header:\n");
524 try dumpHeader(w, std.coff.Header, &header, struct {});615 try dumpHeader(d, std.coff.Header, &header, struct {});
525 try w.writeByte('\n');616 if (d.element(.newlines)) try w.writeByte('\n');
526 }617 }
527618
528 switch (header.machine) {619 switch (header.machine) {
529 _ => return failParse(opts, "unknown machine type: {x}", .{header.machine}),620 _ => return d.failParse("unknown machine type: {x}", .{header.machine}),
530 else => {},621 else => {},
531 }622 }
532623
533 var known_dirs: [DIRECTORY_ENTRY.len]std.coff.ImageDataDirectory = undefined;624 var known_dirs: [DIRECTORY_ENTRY.len]std.coff.ImageDataDirectory = undefined;
534 const needs_data_dirs =625 const needs_data_dirs =
535 opts.exports or626 d.opts.exports or
536 opts.imports or627 d.opts.imports or
537 opts.tls;628 d.opts.tls;
538629
539 const ImageInfo = struct {630 const ImageInfo = struct {
540 data_dirs: []const std.coff.ImageDataDirectory,631 data_dirs: []const std.coff.ImageDataDirectory,
...@@ -543,12 +634,14 @@ const coff = struct {...@@ -543,12 +634,14 @@ const coff = struct {
543 };634 };
544635
545 const image_info: ?ImageInfo = if (header.size_of_optional_header > 0) image_info: {636 const image_info: ?ImageInfo = if (header.size_of_optional_header > 0) image_info: {
546 if (!opts.file_headers and !needs_data_dirs) {637 if (!d.opts.file_headers and !needs_data_dirs) {
547 try fr.seekBy(header.size_of_optional_header);638 try fr.seekBy(header.size_of_optional_header);
548 break :image_info null;639 break :image_info null;
549 }640 }
550641
551 if (opts.file_headers) try w.writeAll("COFF Optional Header:\n");642 if (d.opts.file_headers and d.element(.@"header-names"))
643 try w.writeAll("COFF Optional Header:\n");
644
552 const magic: std.coff.OptionalHeader.Magic = @enumFromInt(try r.peekInt(u16, .little));645 const magic: std.coff.OptionalHeader.Magic = @enumFromInt(try r.peekInt(u16, .little));
553 const num_directory_entries, const image_base = switch (magic) {646 const num_directory_entries, const image_base = switch (magic) {
554 inline .PE32, .@"PE32+" => |v| num_data_dirs: {647 inline .PE32, .@"PE32+" => |v| num_data_dirs: {
...@@ -558,46 +651,46 @@ const coff = struct {...@@ -558,46 +651,46 @@ const coff = struct {
558 std.coff.OptionalHeader.@"PE32+";651 std.coff.OptionalHeader.@"PE32+";
559652
560 const optional_header = r.takeStruct(OptionalHeader, .little) catch |err|653 const optional_header = r.takeStruct(OptionalHeader, .little) catch |err|
561 return failParse(opts, "unable to read optional header: {t}", .{err});654 return d.failParse("unable to read optional header: {t}", .{err});
562655
563 if (opts.file_headers) {656 if (d.opts.file_headers) {
564 try dumpHeader(w, OptionalHeader, &optional_header, struct {657 try dumpHeader(d, OptionalHeader, &optional_header, struct {
565 pub fn base_of_code(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void {658 pub fn base_of_code(id: *const DumpContext, h: *const std.coff.OptionalHeader) !void {
566 const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base;659 const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base;
567 try dumpRvaField(cw, @src().fn_name, h.base_of_code, base);660 try dumpRvaField(id, @src().fn_name, h.base_of_code, base);
568 }661 }
569662
570 pub fn address_of_entry_point(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void {663 pub fn address_of_entry_point(id: *const DumpContext, h: *const std.coff.OptionalHeader) !void {
571 const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base;664 const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base;
572 try dumpRvaField(cw, @src().fn_name, h.base_of_code, base);665 try dumpRvaField(id, @src().fn_name, h.base_of_code, base);
573 }666 }
574667
575 pub fn major_linker_version(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void {668 pub fn major_linker_version(id: *const DumpContext, h: *const std.coff.OptionalHeader) !void {
576 try dumpVersionField(cw, "linker_version", h.major_linker_version, h.minor_linker_version);669 try dumpVersionField(id.w, "linker_version", h.major_linker_version, h.minor_linker_version);
577 }670 }
578 pub fn minor_linker_version(_: *const std.coff.OptionalHeader, _: *Io.Writer) !void {}671 pub fn minor_linker_version(_: *const DumpContext, _: *const std.coff.OptionalHeader) !void {}
579672
580 pub fn major_operating_system_version(h: *const OptionalHeader, cw: *Io.Writer) !void {673 pub fn major_operating_system_version(id: *const DumpContext, h: *const OptionalHeader) !void {
581 try dumpVersionField(674 try dumpVersionField(
582 cw,675 id.w,
583 "operating_system_version",676 "operating_system_version",
584 h.major_operating_system_version,677 h.major_operating_system_version,
585 h.minor_operating_system_version,678 h.minor_operating_system_version,
586 );679 );
587 }680 }
588 pub fn minor_operating_system_version(_: *const OptionalHeader, _: *Io.Writer) !void {}681 pub fn minor_operating_system_version(_: *const DumpContext, _: *const OptionalHeader) !void {}
589682
590 pub fn major_image_version(h: *const OptionalHeader, cw: *Io.Writer) !void {683 pub fn major_image_version(id: *const DumpContext, h: *const OptionalHeader) !void {
591 try dumpVersionField(cw, "image_version", h.major_image_version, h.minor_image_version);684 try dumpVersionField(id.w, "image_version", h.major_image_version, h.minor_image_version);
592 }685 }
593 pub fn minor_image_version(_: *const OptionalHeader, _: *Io.Writer) !void {}686 pub fn minor_image_version(_: *const DumpContext, _: *const OptionalHeader) !void {}
594687
595 pub fn major_subsystem_version(h: *const OptionalHeader, cw: *Io.Writer) !void {688 pub fn major_subsystem_version(id: *const DumpContext, h: *const OptionalHeader) !void {
596 try dumpVersionField(cw, "subsystem_version", h.major_subsystem_version, h.minor_subsystem_version);689 try dumpVersionField(id.w, "subsystem_version", h.major_subsystem_version, h.minor_subsystem_version);
597 }690 }
598 pub fn minor_subsystem_version(_: *const OptionalHeader, _: *Io.Writer) !void {}691 pub fn minor_subsystem_version(_: *const DumpContext, _: *const OptionalHeader) !void {}
599 });692 });
600 try w.writeByte('\n');693 if (d.element(.newlines)) try w.writeByte('\n');
601 }694 }
602695
603 break :num_data_dirs .{696 break :num_data_dirs .{
...@@ -605,24 +698,26 @@ const coff = struct {...@@ -605,24 +698,26 @@ const coff = struct {
605 optional_header.image_base,698 optional_header.image_base,
606 };699 };
607 },700 },
608 else => return failParse(opts, "invalid optional header magic number: {x}", .{magic}),701 else => return d.failParse("invalid optional header magic number: {x}", .{magic}),
609 };702 };
610703
611 if (opts.file_headers) try w.writeAll("Data Directories:\n");704 if (d.opts.file_headers and d.element(.@"header-names"))
705 try w.writeAll("Data Directories:\n");
706
612 for (0..num_directory_entries) |dir_i| {707 for (0..num_directory_entries) |dir_i| {
613 const dir = r.takeStruct(std.coff.ImageDataDirectory, .little) catch |err|708 const dir = r.takeStruct(std.coff.ImageDataDirectory, .little) catch |err|
614 return failParse(opts, "unable to read data directory {x}: {t}", .{ dir_i, err });709 return d.failParse("unable to read data directory {x}: {t}", .{ dir_i, err });
615710
616 if (dir_i < known_dirs.len)711 if (dir_i < known_dirs.len)
617 known_dirs[dir_i] = dir;712 known_dirs[dir_i] = dir;
618713
619 if (opts.file_headers)714 if (d.opts.file_headers)
620 try w.print(715 try w.print(
621 "{x: >16} {x: >8} {t}\n",716 "{x: >16} {x: >8} {t}\n",
622 .{ dir.virtual_address, dir.size, @as(DIRECTORY_ENTRY, @enumFromInt(dir_i)) },717 .{ dir.virtual_address, dir.size, @as(DIRECTORY_ENTRY, @enumFromInt(dir_i)) },
623 );718 );
624 }719 }
625 if (opts.file_headers) try w.writeByte('\n');720 if (d.opts.file_headers and d.element(.newlines)) try w.writeByte('\n');
626721
627 break :image_info .{722 break :image_info .{
628 .data_dirs = known_dirs[0..@min(known_dirs.len, num_directory_entries)],723 .data_dirs = known_dirs[0..@min(known_dirs.len, num_directory_entries)],
...@@ -630,32 +725,33 @@ const coff = struct {...@@ -630,32 +725,33 @@ const coff = struct {
630 .image_base = image_base,725 .image_base = image_base,
631 };726 };
632 } else if (is_image) {727 } else if (is_image) {
633 return failParse(opts, "image did not contain an optional header", .{});728 return d.failParse("image did not contain an optional header", .{});
634 } else null;729 } else null;
635730
636 // Section names in images don't use the string table, as they must fit inline in the header731 // Section names in images don't use the string table, as they must fit inline in the header
637 const load_string_table = (opts.strings or !is_image) and header.pointer_to_symbol_table > 0;732 const load_string_table = (d.opts.strings or !is_image) and header.pointer_to_symbol_table > 0;
638 const string_table = if (load_string_table) string_table: {733 const string_table = if (load_string_table) string_table: {
639 const pos = fr.logicalPos();734 const pos = fr.logicalPos();
640 fr.seekTo(file_location + header.pointer_to_symbol_table + header.number_of_symbols * std.coff.Symbol.sizeOf()) catch |err|735 fr.seekTo(file_location + header.pointer_to_symbol_table + header.number_of_symbols * std.coff.Symbol.sizeOf()) catch |err|
641 return failParse(opts, "unable to seek to string table: {t}", .{err});736 return d.failParse("unable to seek to string table: {t}", .{err});
642737
643 const string_table_len = r.peekInt(u32, .little) catch |err|738 const string_table_len = r.peekInt(u32, .little) catch |err|
644 return failParse(opts, "unable to read string table length: {t}", .{err});739 return d.failParse("unable to read string table length: {t}", .{err});
645740
646 const table = r.readAlloc(gpa, string_table_len) catch |err|741 const table = r.readAlloc(gpa, string_table_len) catch |err|
647 return failParse(opts, "unable to read string table: {t}", .{err});742 return d.failParse("unable to read string table: {t}", .{err});
648743
649 try fr.seekTo(pos);744 try fr.seekTo(pos);
650 break :string_table table;745 break :string_table table;
651 } else &.{};746 } else &.{};
652 defer gpa.free(string_table);747 defer gpa.free(string_table);
653748
654 if (opts.strings) {749 if (d.opts.strings) {
655 try w.print(750 if (d.element(.@"table-header"))
656 \\String Table (0x{x} bytes):751 try w.print(
657 \\752 \\String Table (0x{x} bytes):
658 , .{string_table.len});753 \\
754 , .{string_table.len});
659755
660 var sr = Io.Reader.fixed(string_table[4..]);756 var sr = Io.Reader.fixed(string_table[4..]);
661 while (try sr.takeDelimiter(0)) |str| {757 while (try sr.takeDelimiter(0)) |str| {
...@@ -663,7 +759,7 @@ const coff = struct {...@@ -663,7 +759,7 @@ const coff = struct {
663 try w.writeByte('\n');759 try w.writeByte('\n');
664 }760 }
665761
666 try w.writeByte('\n');762 if (d.element(.newlines)) try w.writeByte('\n');
667 }763 }
668764
669 var sections: std.ArrayList(Section) = .empty;765 var sections: std.ArrayList(Section) = .empty;
...@@ -671,13 +767,13 @@ const coff = struct {...@@ -671,13 +767,13 @@ const coff = struct {
671 var sections_with_data: u16 = 0;767 var sections_with_data: u16 = 0;
672768
673 const load_sections =769 const load_sections =
674 opts.section_headers or770 d.opts.section_headers or
675 opts.symbols or771 d.opts.symbols or
676 opts.relocs or772 d.opts.relocs or
677 needs_data_dirs;773 needs_data_dirs;
678774
679 if (load_sections) {775 if (load_sections) {
680 if (opts.section_headers)776 if (d.opts.section_headers and d.element(.@"table-header"))
681 try w.print(777 try w.print(
682 \\Sections in '{s}':778 \\Sections in '{s}':
683 \\Num Name RVA Virt Size Data Size & Data & Relocs & Lines # Relocs # Lines Flags779 \\Num Name RVA Virt Size Data Size & Data & Relocs & Lines # Relocs # Lines Flags
...@@ -687,37 +783,37 @@ const coff = struct {...@@ -687,37 +783,37 @@ const coff = struct {
687 try sections.resize(gpa, header.number_of_sections);783 try sections.resize(gpa, header.number_of_sections);
688 for (sections.items, 0..) |*section, section_i| {784 for (sections.items, 0..) |*section, section_i| {
689 section.header = r.takeStruct(std.coff.SectionHeader, .little) catch |err|785 section.header = r.takeStruct(std.coff.SectionHeader, .little) catch |err|
690 return failParse(opts, "unable to read section header {x}: {t}", .{ section_i, err });786 return d.failParse("unable to read section header {x}: {t}", .{ section_i, err });
691 section.name = headerName(&section.header.name, string_table) catch |err| switch (err) {787 section.name = headerName(&section.header.name, string_table) catch |err| switch (err) {
692 error.Overflow,788 error.Overflow,
693 error.InvalidCharacter,789 error.InvalidCharacter,
694 => return failParse(opts, "unable to parse section name offset '{s}': {t}", .{790 => return d.failParse("unable to parse section name offset '{s}': {t}", .{
695 section.name,791 section.name,
696 err,792 err,
697 }),793 }),
698 error.OutOfBounds => return failParse(opts, "section name offset '{s}' was out of bounds (>= {x})", .{794 error.OutOfBounds => return d.failParse("section name offset '{s}' was out of bounds (>= {x})", .{
699 section.name,795 section.name,
700 string_table.len,796 string_table.len,
701 }),797 }),
702 };798 };
703799
704 sections_with_data += @intFromBool(section.header.size_of_raw_data > 0);800 sections_with_data += @intFromBool(section.header.size_of_raw_data > 0);
705 if (opts.section_headers) {801 if (d.opts.section_headers) {
706 if (!filterMatches(opts.section_filters, section.name)) continue;802 if (!filterMatches(d.opts.section_filters, section.name)) continue;
707 const raw_name = std.mem.sliceTo(&section.header.name, 0);803 const raw_name = std.mem.sliceTo(&section.header.name, 0);
708 try w.print(804 try w.print(
709 "{x: >3} {s: <8} {x: >8} {x: >9} {x: >9} {x: >8} {x: >8} {x: >8} {x: >8} {x: >8} {x:0>8} |",805 "{x: >3} {s: <8} {f} {f} {f} {f} {f} {f} {f} {f} {x:0>8} |",
710 .{806 .{
711 section_i + 1,807 section_i + 1,
712 raw_name,808 raw_name,
713 section.header.virtual_address,809 fmtIntField(d, section.header.virtual_address, .{ .kind = .va }),
714 section.header.virtual_size,810 fmtIntField(d, section.header.virtual_size, .{ .kind = .size, .width = 9 }),
715 section.header.size_of_raw_data,811 fmtIntField(d, section.header.size_of_raw_data, .{ .kind = .size, .width = 9 }),
716 section.header.pointer_to_raw_data,812 fmtIntField(d, section.header.pointer_to_raw_data, .{ .kind = .va }),
717 section.header.pointer_to_relocations,813 fmtIntField(d, section.header.pointer_to_relocations, .{ .kind = .va }),
718 section.header.pointer_to_linenumbers,814 fmtIntField(d, section.header.pointer_to_linenumbers, .{ .kind = .va }),
719 section.header.number_of_relocations,815 fmtIntField(d, section.header.number_of_relocations, .{ .kind = .va }),
720 section.header.number_of_linenumbers,816 fmtIntField(d, section.header.number_of_linenumbers, .{ .kind = .va }),
721 @as(u32, @bitCast(section.header.flags)),817 @as(u32, @bitCast(section.header.flags)),
722 },818 },
723 );819 );
...@@ -730,7 +826,7 @@ const coff = struct {...@@ -730,7 +826,7 @@ const coff = struct {
730 }826 }
731 }827 }
732828
733 if (opts.section_headers) try w.writeByte('\n');829 if (d.opts.section_headers and d.element(.newlines)) try w.writeByte('\n');
734 }830 }
735831
736 var symbols: std.ArrayList(struct {832 var symbols: std.ArrayList(struct {
...@@ -738,15 +834,15 @@ const coff = struct {...@@ -738,15 +834,15 @@ const coff = struct {
738 section_number: std.coff.SectionNumber,834 section_number: std.coff.SectionNumber,
739 }) = .empty;835 }) = .empty;
740 defer symbols.deinit(gpa);836 defer symbols.deinit(gpa);
741 if (opts.relocs)837 if (d.opts.relocs)
742 try symbols.ensureUnusedCapacity(gpa, header.number_of_symbols);838 try symbols.ensureUnusedCapacity(gpa, header.number_of_symbols);
743839
744 if (opts.symbols or opts.relocs) {840 if (d.opts.symbols or d.opts.relocs) {
745 if (header.pointer_to_symbol_table > 0) {841 if (header.pointer_to_symbol_table > 0) {
746 fr.seekTo(file_location + header.pointer_to_symbol_table) catch |err|842 fr.seekTo(file_location + header.pointer_to_symbol_table) catch |err|
747 return failParse(opts, "unable to seek to symbol table: {t}", .{err});843 return d.failParse("unable to seek to symbol table: {t}", .{err});
748844
749 if (opts.symbols)845 if (d.opts.symbols and d.element(.@"table-header"))
750 try w.print(846 try w.print(
751 \\Symbols in '{s}':847 \\Symbols in '{s}':
752 \\ Ord Value Sect Type Storage Name848 \\ Ord Value Sect Type Storage Name
...@@ -758,7 +854,7 @@ const coff = struct {...@@ -758,7 +854,7 @@ const coff = struct {
758 while (symbol_i < header.number_of_symbols) {854 while (symbol_i < header.number_of_symbols) {
759 var symbol: std.coff.Symbol = undefined;855 var symbol: std.coff.Symbol = undefined;
760 const symbol_bytes = r.take(symbol_size) catch |err|856 const symbol_bytes = r.take(symbol_size) catch |err|
761 return failParse(opts, "unable to read symbol {x}: {t}", .{ symbol_i, err });857 return d.failParse("unable to read symbol {x}: {t}", .{ symbol_i, err });
762858
763 @memcpy(std.mem.asBytes(&symbol)[0..symbol_size], symbol_bytes);859 @memcpy(std.mem.asBytes(&symbol)[0..symbol_size], symbol_bytes);
764 if (native_endian != .little)860 if (native_endian != .little)
...@@ -773,7 +869,7 @@ const coff = struct {...@@ -773,7 +869,7 @@ const coff = struct {
773 const name = std.mem.sliceTo(if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: {869 const name = std.mem.sliceTo(if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: {
774 const index = std.mem.readInt(u32, symbol.name[4..], .little);870 const index = std.mem.readInt(u32, symbol.name[4..], .little);
775 if (index >= string_table.len)871 if (index >= string_table.len)
776 return failParse(opts, "invalid name offset for symbol {x} ({x} >= {x})", .{872 return d.failParse("invalid name offset for symbol {x} ({x} >= {x})", .{
777 symbol_i,873 symbol_i,
778 index,874 index,
779 string_table.len,875 string_table.len,
...@@ -781,16 +877,19 @@ const coff = struct {...@@ -781,16 +877,19 @@ const coff = struct {
781 break :name string_table[index..];877 break :name string_table[index..];
782 } else &symbol.name, 0);878 } else &symbol.name, 0);
783879
784 if (opts.relocs)880 if (d.opts.relocs)
785 symbols.appendNTimesAssumeCapacity(.{881 symbols.appendNTimesAssumeCapacity(.{
786 .name = name,882 .name = name,
787 .section_number = symbol.section_number,883 .section_number = symbol.section_number,
788 }, 1 + symbol.number_of_aux_symbols);884 }, 1 + symbol.number_of_aux_symbols);
789885
790 if (!opts.symbols)886 if (!d.opts.symbols or !filterMatches(d.opts.symbol_filters, name))
791 continue;887 continue;
792888
793 try w.print("{x: >4} {x:0>8} ", .{ symbol_i, symbol.value });889 try w.print("{f} {x:0>8} ", .{
890 fmtIntField(d, @as(u16, @intCast(symbol_i)), .{ .kind = .ord }),
891 symbol.value,
892 });
794 try switch (symbol.section_number) {893 try switch (symbol.section_number) {
795 .UNDEFINED => w.writeAll("UNDEF"),894 .UNDEFINED => w.writeAll("UNDEF"),
796 .ABSOLUTE => w.writeAll(" ABS"),895 .ABSOLUTE => w.writeAll(" ABS"),
...@@ -814,8 +913,7 @@ const coff = struct {...@@ -814,8 +913,7 @@ const coff = struct {
814 else => null,913 else => null,
815 }) |suffix| try w.writeAll(suffix) else try w.print("{x}", .{symbol.type.complex_type});914 }) |suffix| try w.writeAll(suffix) else try w.print("{x}", .{symbol.type.complex_type});
816915
817 try w.print("{t: >16} | {s}", .{ symbol.storage_class, name });916 try w.print("{t: >16} | {s}\n", .{ symbol.storage_class, name });
818 try w.writeByte('\n');
819917
820 for (0..symbol.number_of_aux_symbols) |aux_i| {918 for (0..symbol.number_of_aux_symbols) |aux_i| {
821 _ = aux_i;919 _ = aux_i;
...@@ -838,8 +936,7 @@ const coff = struct {...@@ -838,8 +936,7 @@ const coff = struct {
838 try w.writeAll("TODO bf / ef aux symbol");936 try w.writeAll("TODO bf / ef aux symbol");
839 } else if (symbol.storage_class == .WEAK_EXTERNAL and symbol.section_number == .UNDEFINED) {937 } else if (symbol.storage_class == .WEAK_EXTERNAL and symbol.section_number == .UNDEFINED) {
840 if (symbol.value != 0)938 if (symbol.value != 0)
841 return failParse(939 return d.failParse(
842 opts,
843 "invalid value 0x{x} for weak external symbol 0x{x}",940 "invalid value 0x{x} for weak external symbol 0x{x}",
844 .{ symbol.value, symbol_i },941 .{ symbol.value, symbol_i },
845 );942 );
...@@ -850,14 +947,11 @@ const coff = struct {...@@ -850,14 +947,11 @@ const coff = struct {
850 std.mem.byteSwapAllFields(std.coff.SectionDefinition, &weak_external);947 std.mem.byteSwapAllFields(std.coff.SectionDefinition, &weak_external);
851948
852 if (weak_external.tag_index >= header.number_of_symbols)949 if (weak_external.tag_index >= header.number_of_symbols)
853 return failParse(950 return d.failParse(
854 opts,
855 "invalid tag_index 0x{x} for weak external symbol 0x{x}",951 "invalid tag_index 0x{x} for weak external symbol 0x{x}",
856 .{ weak_external.tag_index, symbol_i },952 .{ weak_external.tag_index, symbol_i },
857 );953 );
858954
859 // TODO
860
861 try w.print(" Weak External [falls back to {x:0>8} via {t}]", .{955 try w.print(" Weak External [falls back to {x:0>8} via {t}]", .{
862 weak_external.tag_index,956 weak_external.tag_index,
863 weak_external.flag,957 weak_external.flag,
...@@ -914,8 +1008,8 @@ const coff = struct {...@@ -914,8 +1008,8 @@ const coff = struct {
914 continue;1008 continue;
915 }1009 }
9161010
917 try w.print(" [size {x:0>8} chksum {x:0>8} relocs {x:0>4} lines {x:0>4}]", .{1011 try w.print(" [size {f} chksum {x:0>8} relocs {x:0>4} lines {x:0>4}]", .{
918 section_def.length,1012 fmtIntField(d, section_def.length, .{ .kind = .size, .zero_fill = true }),
919 section_def.checksum,1013 section_def.checksum,
920 section_def.number_of_relocations,1014 section_def.number_of_relocations,
921 section_def.number_of_linenumbers,1015 section_def.number_of_linenumbers,
...@@ -930,19 +1024,19 @@ const coff = struct {...@@ -930,19 +1024,19 @@ const coff = struct {
930 try w.writeAll(")");1024 try w.writeAll(")");
931 },1025 },
932 }1026 }
933 } else {}1027 }
9341028
935 try w.writeByte('\n');1029 try w.writeByte('\n');
936 }1030 }
937 }1031 }
9381032
939 if (opts.symbols) try w.writeByte('\n');1033 if (d.opts.symbols and d.element(.newlines)) try w.writeByte('\n');
940 } else if (opts.symbols) {1034 } else if (d.opts.symbols) {
941 try w.writeAll("No symbol table found\n");1035 try w.writeAll("No symbol table found\n");
942 }1036 }
943 }1037 }
9441038
945 if (opts.relocs) {1039 if (d.opts.relocs) {
946 const relocation_size = std.coff.Relocation.sizeOf();1040 const relocation_size = std.coff.Relocation.sizeOf();
9471041
948 for (sections.items, 0..) |section, section_i| {1042 for (sections.items, 0..) |section, section_i| {
...@@ -955,7 +1049,7 @@ const coff = struct {...@@ -955,7 +1049,7 @@ const coff = struct {
955 , .{ section_i + 1, section.name, obj_name });1049 , .{ section_i + 1, section.name, obj_name });
9561050
957 fr.seekTo(file_location + section.header.pointer_to_relocations) catch |err|1051 fr.seekTo(file_location + section.header.pointer_to_relocations) catch |err|
958 return failParse(opts, "unable to seek to section {x} relocation table: {t}", .{ section_i + 1, err });1052 return d.failParse("unable to seek to section {x} relocation table: {t}", .{ section_i + 1, err });
9591053
960 for (0..section.header.number_of_relocations) |reloc_i| {1054 for (0..section.header.number_of_relocations) |reloc_i| {
961 var reloc: std.coff.Relocation = undefined;1055 var reloc: std.coff.Relocation = undefined;
...@@ -963,7 +1057,13 @@ const coff = struct {...@@ -963,7 +1057,13 @@ const coff = struct {
963 if (native_endian != .little)1057 if (native_endian != .little)
964 std.mem.byteSwapAllFields(std.coff.Relocation, &reloc);1058 std.mem.byteSwapAllFields(std.coff.Relocation, &reloc);
9651059
966 try w.print("{x:0>8} ", .{reloc.virtual_address});1060 const sym = &symbols.items[reloc.symbol_table_index];
1061 if (!filterMatches(d.opts.symbol_filters, sym.name))
1062 continue;
1063
1064 try w.print("{f} ", .{
1065 fmtIntField(d, reloc.virtual_address, .{ .kind = .va, .zero_fill = true }),
1066 });
967 switch (header.machine) {1067 switch (header.machine) {
968 _ => unreachable,1068 _ => unreachable,
969 inline else => |m| switch (m.RelocationType()) {1069 inline else => |m| switch (m.RelocationType()) {
...@@ -976,20 +1076,18 @@ const coff = struct {...@@ -976,20 +1076,18 @@ const coff = struct {
976 }1076 }
9771077
978 if (reloc.symbol_table_index >= symbols.items.len)1078 if (reloc.symbol_table_index >= symbols.items.len)
979 return failParse(1079 return d.failParse(
980 opts,
981 "reloc {x} in section {x} has out-of-bounds symbol index {x}",1080 "reloc {x} in section {x} has out-of-bounds symbol index {x}",
982 .{ reloc_i, section_i + 1, reloc.symbol_table_index },1081 .{ reloc_i, section_i + 1, reloc.symbol_table_index },
983 );1082 );
9841083
985 const sym = &symbols.items[reloc.symbol_table_index];1084 try w.print("{f} {f} | {s}\n", .{
986 try w.print("{x: >8} {f} | {s}\n", .{1085 fmtIntField(d, reloc.symbol_table_index, .{ .kind = .ord }),
987 reloc.symbol_table_index,
988 fmtSectionNumber(sym.section_number),1086 fmtSectionNumber(sym.section_number),
989 sym.name,1087 sym.name,
990 });1088 });
991 }1089 }
992 try w.writeByte('\n');1090 if (d.element(.newlines)) try w.writeByte('\n');
993 }1091 }
994 }1092 }
9951093
...@@ -1026,44 +1124,40 @@ const coff = struct {...@@ -1026,44 +1124,40 @@ const coff = struct {
1026 } else &.{};1124 } else &.{};
1027 defer gpa.free(rva_index);1125 defer gpa.free(rva_index);
10281126
1029 if (opts.exports) {1127 if (d.opts.exports) {
1030 if (try seekToDataDirectory(opts, fr, w, rva_index, sections.items, image_info.?.data_dirs, .EXPORT)) |section_index| {1128 if (try seekToDataDirectory(d, rva_index, sections.items, image_info.?.data_dirs, .EXPORT)) |section_index| {
1031 const export_dir = r.takeStruct(std.coff.ExportDirectoryTable, .little) catch |err|1129 const export_dir = r.takeStruct(std.coff.ExportDirectoryTable, .little) catch |err|
1032 return failParse(opts, "unable to read export directory: {t}", .{err});1130 return d.failParse("unable to read export directory: {t}", .{err});
10331131
1034 try w.print("Export directory:\n", .{});1132 try w.print("Export directory:\n", .{});
1035 try dumpHeader(w, std.coff.ExportDirectoryTable, &export_dir, struct {1133 try dumpHeader(d, std.coff.ExportDirectoryTable, &export_dir, struct {
1036 pub fn major_version(h: *const std.coff.ExportDirectoryTable, cw: *Io.Writer) !void {1134 pub fn major_version(id: *const DumpContext, h: *const std.coff.ExportDirectoryTable) !void {
1037 try dumpVersionField(cw, "version", h.major_version, h.minor_version);1135 try dumpVersionField(id.w, "version", h.major_version, h.minor_version);
1038 }1136 }
1039 pub fn minor_version(_: *const std.coff.ExportDirectoryTable, _: *Io.Writer) !void {}1137 pub fn minor_version(_: *const DumpContext, _: *const std.coff.ExportDirectoryTable) !void {}
1040 });1138 });
10411139
1042 const section = sections.items[section_index];1140 const section = sections.items[section_index];
1043 const name_loc = section.rvaFileOffset(export_dir.name_rva) catch1141 const name_loc = section.rvaFileOffset(export_dir.name_rva) catch
1044 return failParse(1142 return d.failParse(
1045 opts,
1046 "export name rva 0x{x} was not within the export section",1143 "export name rva 0x{x} was not within the export section",
1047 .{export_dir.name_rva},1144 .{export_dir.name_rva},
1048 );1145 );
10491146
1050 const eat_loc = section.rvaFileOffset(export_dir.export_address_table_rva) catch1147 const eat_loc = section.rvaFileOffset(export_dir.export_address_table_rva) catch
1051 return failParse(1148 return d.failParse(
1052 opts,
1053 "export address table rva 0x{x} was not within the export section",1149 "export address table rva 0x{x} was not within the export section",
1054 .{export_dir.export_address_table_rva},1150 .{export_dir.export_address_table_rva},
1055 );1151 );
10561152
1057 const name_pointer_loc = section.rvaFileOffset(export_dir.name_pointer_table_rva) catch1153 const name_pointer_loc = section.rvaFileOffset(export_dir.name_pointer_table_rva) catch
1058 return failParse(1154 return d.failParse(
1059 opts,
1060 "export name pointer table rva 0x{x} was not within the export section",1155 "export name pointer table rva 0x{x} was not within the export section",
1061 .{export_dir.name_pointer_table_rva},1156 .{export_dir.name_pointer_table_rva},
1062 );1157 );
10631158
1064 const ord_loc = section.rvaFileOffset(export_dir.ordinal_table_rva) catch1159 const ord_loc = section.rvaFileOffset(export_dir.ordinal_table_rva) catch
1065 return failParse(1160 return d.failParse(
1066 opts,
1067 "export ordinal table rva 0x{x} was not within the export section",1161 "export ordinal table rva 0x{x} was not within the export section",
1068 .{export_dir.ordinal_table_rva},1162 .{export_dir.ordinal_table_rva},
1069 );1163 );
...@@ -1077,12 +1171,13 @@ const coff = struct {...@@ -1077,12 +1171,13 @@ const coff = struct {
1077 defer gpa.free(dir_slice);1171 defer gpa.free(dir_slice);
10781172
1079 const dll_name = std.mem.sliceTo(dir_slice[name_loc - dir_loc ..], 0);1173 const dll_name = std.mem.sliceTo(dir_slice[name_loc - dir_loc ..], 0);
1080 try w.print(1174 if (d.element(.@"table-header"))
1081 \\1175 try w.print(
1082 \\Exports from {s}:1176 \\
1083 \\ Ord Hint RVA Name1177 \\Exports from {s}:
1084 \\1178 \\ Ord Hint RVA Name
1085 , .{dll_name});1179 \\
1180 , .{dll_name});
10861181
1087 const name_pointers = dir_slice[name_pointer_loc - dir_loc ..][0 .. export_dir.number_of_names * @sizeOf(u32)];1182 const name_pointers = dir_slice[name_pointer_loc - dir_loc ..][0 .. export_dir.number_of_names * @sizeOf(u32)];
1088 const ords = dir_slice[ord_loc - dir_loc ..][0 .. export_dir.number_of_names * @sizeOf(u16)];1183 const ords = dir_slice[ord_loc - dir_loc ..][0 .. export_dir.number_of_names * @sizeOf(u16)];
...@@ -1090,18 +1185,24 @@ const coff = struct {...@@ -1090,18 +1185,24 @@ const coff = struct {
1090 const name_rva_to_offset = dir.virtual_address + @sizeOf(std.coff.ExportDirectoryTable);1185 const name_rva_to_offset = dir.virtual_address + @sizeOf(std.coff.ExportDirectoryTable);
1091 for (0..export_dir.number_of_names) |name_i| {1186 for (0..export_dir.number_of_names) |name_i| {
1092 const name_rva = std.mem.readInt(u32, name_pointers[name_i * @sizeOf(u32) ..][0..@sizeOf(u32)], .little);1187 const name_rva = std.mem.readInt(u32, name_pointers[name_i * @sizeOf(u32) ..][0..@sizeOf(u32)], .little);
1188 const name = std.mem.sliceTo(dir_slice[name_rva - name_rva_to_offset ..], 0);
1189 if (!filterMatches(d.opts.symbol_filters, name))
1190 continue;
1191
1093 const ord = std.mem.readInt(u16, ords[name_i * @sizeOf(u16) ..][0..@sizeOf(u16)], .little);1192 const ord = std.mem.readInt(u16, ords[name_i * @sizeOf(u16) ..][0..@sizeOf(u16)], .little);
1094 const addr = std.mem.readInt(u32, addrs[@as(u32, ord) * @sizeOf(u32) ..][0..@sizeOf(u32)], .little);1193 const addr = std.mem.readInt(u32, addrs[@as(u32, ord) * @sizeOf(u32) ..][0..@sizeOf(u32)], .little);
10951194
1096 try w.print("{x: >4} {x: >4} ", .{ export_dir.ordinal_base + ord, name_i });1195 try w.print("{f} {f} ", .{
1196 fmtIntField(d, @as(u16, @intCast(export_dir.ordinal_base + ord)), .{ .kind = .ord }),
1197 fmtIntField(d, @as(u16, @intCast(name_i)), .{ .kind = .ord }),
1198 });
1097 const is_forwarder = addr >= dir.virtual_address and addr < dir_end_rva;1199 const is_forwarder = addr >= dir.virtual_address and addr < dir_end_rva;
1098 if (is_forwarder) {1200 if (is_forwarder) {
1099 try w.writeAll("forwards");1201 try w.writeAll("forwards");
1100 } else {1202 } else {
1101 try w.print("{x: >8}", .{addr});1203 try w.print("{f}", .{fmtIntField(d, addr, .{ .kind = .rva })});
1102 }1204 }
11031205
1104 const name = std.mem.sliceTo(dir_slice[name_rva - name_rva_to_offset ..], 0);
1105 try w.print(" | {s}", .{name});1206 try w.print(" | {s}", .{name});
1106 if (is_forwarder)1207 if (is_forwarder)
1107 try w.print(" -> {s}", .{std.mem.sliceTo(dir_slice[addr - name_rva_to_offset ..], 0)});1208 try w.print(" -> {s}", .{std.mem.sliceTo(dir_slice[addr - name_rva_to_offset ..], 0)});
...@@ -1110,11 +1211,9 @@ const coff = struct {...@@ -1110,11 +1211,9 @@ const coff = struct {
1110 }1211 }
1111 }1212 }
11121213
1113 if (opts.imports) {1214 if (d.opts.imports) {
1114 if (try seekToDataDirectory(1215 if (try seekToDataDirectory(
1115 opts,1216 d,
1116 fr,
1117 w,
1118 rva_index,1217 rva_index,
1119 sections.items,1218 sections.items,
1120 image_info.?.data_dirs,1219 image_info.?.data_dirs,
...@@ -1125,8 +1224,7 @@ const coff = struct {...@@ -1125,8 +1224,7 @@ const coff = struct {
1125 defer directory_entries.deinit(gpa);1224 defer directory_entries.deinit(gpa);
1126 while (true) {1225 while (true) {
1127 const entry = r.takeStruct(Entry, .little) catch |err|1226 const entry = r.takeStruct(Entry, .little) catch |err|
1128 return failParse(1227 return d.failParse(
1129 opts,
1130 "unable to read import directory entry {x}: {t}",1228 "unable to read import directory entry {x}: {t}",
1131 .{ directory_entries.items.len, err },1229 .{ directory_entries.items.len, err },
1132 );1230 );
...@@ -1141,8 +1239,7 @@ const coff = struct {...@@ -1141,8 +1239,7 @@ const coff = struct {
1141 sections.items,1239 sections.items,
1142 entry.name_rva,1240 entry.name_rva,
1143 ) orelse1241 ) orelse
1144 return failParse(1242 return d.failParse(
1145 opts,
1146 "import directory entry name rva 0x{x} was not found in any section",1243 "import directory entry name rva 0x{x} was not found in any section",
1147 .{entry.name_rva},1244 .{entry.name_rva},
1148 );1245 );
...@@ -1151,8 +1248,7 @@ const coff = struct {...@@ -1151,8 +1248,7 @@ const coff = struct {
1151 entry.name_rva,1248 entry.name_rva,
1152 ) catch unreachable;1249 ) catch unreachable;
1153 fr.seekTo(name_loc) catch |err|1250 fr.seekTo(name_loc) catch |err|
1154 return failParse(1251 return d.failParse(
1155 opts,
1156 "unable to seek to import directory entry name at 0x{x}: {t}",1252 "unable to seek to import directory entry name at 0x{x}: {t}",
1157 .{ name_loc, err },1253 .{ name_loc, err },
1158 );1254 );
...@@ -1160,7 +1256,7 @@ const coff = struct {...@@ -1160,7 +1256,7 @@ const coff = struct {
1160 const dll_name = (try r.takeDelimiter(0)).?;1256 const dll_name = (try r.takeDelimiter(0)).?;
11611257
1162 try w.print("Import table entry for {s}:\n", .{dll_name});1258 try w.print("Import table entry for {s}:\n", .{dll_name});
1163 try dumpHeader(w, Entry, &entry, struct {});1259 try dumpHeader(d, Entry, &entry, struct {});
11641260
1165 try w.print(1261 try w.print(
1166 \\1262 \\
...@@ -1173,8 +1269,7 @@ const coff = struct {...@@ -1173,8 +1269,7 @@ const coff = struct {
1173 sections.items,1269 sections.items,
1174 entry.import_lookup_table_rva,1270 entry.import_lookup_table_rva,
1175 ) orelse1271 ) orelse
1176 return failParse(1272 return d.failParse(
1177 opts,
1178 "import directory entry ilt rva 0x{x} was not found in any section",1273 "import directory entry ilt rva 0x{x} was not found in any section",
1179 .{entry.import_lookup_table_rva},1274 .{entry.import_lookup_table_rva},
1180 );1275 );
...@@ -1183,8 +1278,7 @@ const coff = struct {...@@ -1183,8 +1278,7 @@ const coff = struct {
1183 entry.import_lookup_table_rva,1278 entry.import_lookup_table_rva,
1184 ) catch unreachable;1279 ) catch unreachable;
1185 fr.seekTo(ilt_loc) catch |err|1280 fr.seekTo(ilt_loc) catch |err|
1186 return failParse(1281 return d.failParse(
1187 opts,
1188 "unable to seek to import directory ilt at 0x{x}: {t}",1282 "unable to seek to import directory ilt at 0x{x}: {t}",
1189 .{ ilt_loc, err },1283 .{ ilt_loc, err },
1190 );1284 );
...@@ -1199,8 +1293,7 @@ const coff = struct {...@@ -1199,8 +1293,7 @@ const coff = struct {
1199 defer ilt_entries.deinit(gpa);1293 defer ilt_entries.deinit(gpa);
1200 while (true) {1294 while (true) {
1201 const table_entry = r.takeStruct(TableEntry, .little) catch |err|1295 const table_entry = r.takeStruct(TableEntry, .little) catch |err|
1202 return failParse(1296 return d.failParse(
1203 opts,
1204 "unable to read ilt entry {s}:{x}: {t}",1297 "unable to read ilt entry {s}:{x}: {t}",
1205 .{ dll_name, ilt_entries.items.len, err },1298 .{ dll_name, ilt_entries.items.len, err },
1206 );1299 );
...@@ -1217,8 +1310,7 @@ const coff = struct {...@@ -1217,8 +1310,7 @@ const coff = struct {
1217 sections.items,1310 sections.items,
1218 ilt_entry.payload.hint_name_rva,1311 ilt_entry.payload.hint_name_rva,
1219 ) orelse1312 ) orelse
1220 return failParse(1313 return d.failParse(
1221 opts,
1222 "import directory ilt entry 0x{x}'s hint rva 0x{x} was not found in any section",1314 "import directory ilt entry 0x{x}'s hint rva 0x{x} was not found in any section",
1223 .{ ilt_entry_i, ilt_entry.payload.hint_name_rva },1315 .{ ilt_entry_i, ilt_entry.payload.hint_name_rva },
1224 );1316 );
...@@ -1227,22 +1319,19 @@ const coff = struct {...@@ -1227,22 +1319,19 @@ const coff = struct {
1227 ilt_entry.payload.hint_name_rva,1319 ilt_entry.payload.hint_name_rva,
1228 ) catch unreachable;1320 ) catch unreachable;
1229 fr.seekTo(hint_loc) catch |err|1321 fr.seekTo(hint_loc) catch |err|
1230 return failParse(1322 return d.failParse(
1231 opts,
1232 "unable to seek to ilt entry 0x{x}'s hint at 0x{x}: {t}",1323 "unable to seek to ilt entry 0x{x}'s hint at 0x{x}: {t}",
1233 .{ ilt_entry_i, hint_loc, err },1324 .{ ilt_entry_i, hint_loc, err },
1234 );1325 );
12351326
1236 const hint = r.takeInt(u16, .little) catch |err|1327 const hint = r.takeInt(u16, .little) catch |err|
1237 return failParse(1328 return d.failParse(
1238 opts,
1239 "unable to read import directory ilt entry 0x{x}'s hint: {t}",1329 "unable to read import directory ilt entry 0x{x}'s hint: {t}",
1240 .{ ilt_entry_i, err },1330 .{ ilt_entry_i, err },
1241 );1331 );
12421332
1243 const name = r.takeDelimiter(0) catch |err|1333 const name = r.takeDelimiter(0) catch |err|
1244 return failParse(1334 return d.failParse(
1245 opts,
1246 "unable to read import directory ilt entry 0x{x}'s name: {t}",1335 "unable to read import directory ilt entry 0x{x}'s name: {t}",
1247 .{ ilt_entry_i, err },1336 .{ ilt_entry_i, err },
1248 );1337 );
...@@ -1250,18 +1339,16 @@ const coff = struct {...@@ -1250,18 +1339,16 @@ const coff = struct {
1250 try w.print(" {x: >4} | {s}\n", .{ hint, name.? });1339 try w.print(" {x: >4} | {s}\n", .{ hint, name.? });
1251 }1340 }
1252 }1341 }
1253 try w.writeByte('\n');1342 if (d.element(.newlines)) try w.writeByte('\n');
1254 },1343 },
1255 }1344 }
1256 }1345 }
1257 }1346 }
1258 }1347 }
12591348
1260 if (opts.tls) {1349 if (d.opts.tls) {
1261 if (try seekToDataDirectory(1350 if (try seekToDataDirectory(
1262 opts,1351 d,
1263 fr,
1264 w,
1265 rva_index,1352 rva_index,
1266 sections.items,1353 sections.items,
1267 image_info.?.data_dirs,1354 image_info.?.data_dirs,
...@@ -1272,10 +1359,10 @@ const coff = struct {...@@ -1272,10 +1359,10 @@ const coff = struct {
1272 inline else => |m| {1359 inline else => |m| {
1273 const TlsDirectoryEntry = std.coff.TlsDirectoryEntry(m);1360 const TlsDirectoryEntry = std.coff.TlsDirectoryEntry(m);
1274 const tls_entry = r.takeStruct(TlsDirectoryEntry, .little) catch |err|1361 const tls_entry = r.takeStruct(TlsDirectoryEntry, .little) catch |err|
1275 return failParse(opts, "unable to read tls directory: {t}", .{err});1362 return d.failParse("unable to read tls directory: {t}", .{err});
12761363
1277 try w.writeAll("TLS Directory:\n");1364 try w.writeAll("TLS Directory:\n");
1278 try dumpHeader(w, TlsDirectoryEntry, &tls_entry, struct {});1365 try dumpHeader(d, TlsDirectoryEntry, &tls_entry, struct {});
12791366
1280 try w.writeAll(" | ");1367 try w.writeAll(" | ");
1281 if (tls_entry.characteristics.alignment == .NONE) {1368 if (tls_entry.characteristics.alignment == .NONE) {
...@@ -1301,8 +1388,7 @@ const coff = struct {...@@ -1301,8 +1388,7 @@ const coff = struct {
1301 sections.items,1388 sections.items,
1302 callbacks_rva,1389 callbacks_rva,
1303 ) orelse1390 ) orelse
1304 return failParse(1391 return d.failParse(
1305 opts,
1306 "tls callbacks rva 0x{x} was not found in any section",1392 "tls callbacks rva 0x{x} was not found in any section",
1307 .{callbacks_rva},1393 .{callbacks_rva},
1308 );1394 );
...@@ -1311,24 +1397,22 @@ const coff = struct {...@@ -1311,24 +1397,22 @@ const coff = struct {
1311 .rvaFileOffset(callbacks_rva) catch unreachable;1397 .rvaFileOffset(callbacks_rva) catch unreachable;
13121398
1313 fr.seekTo(callbacks_loc) catch |err|1399 fr.seekTo(callbacks_loc) catch |err|
1314 return failParse(1400 return d.failParse(
1315 opts,
1316 "unable to seek to tls callbacks array at offset 0x{x}: {t}",1401 "unable to seek to tls callbacks array at offset 0x{x}: {t}",
1317 .{ callbacks_loc, err },1402 .{ callbacks_loc, err },
1318 );1403 );
13191404
1320 while (true) {1405 while (true) {
1321 const callback_va = r.takeInt(@FieldType(TlsDirectoryEntry, "callbacks_va"), .little) catch |err|1406 const callback_va = r.takeInt(@FieldType(TlsDirectoryEntry, "callbacks_va"), .little) catch |err|
1322 return failParse(1407 return d.failParse(
1323 opts,
1324 "unable to read tls callbacks array: {t}",1408 "unable to read tls callbacks array: {t}",
1325 .{err},1409 .{err},
1326 );1410 );
13271411
1328 try w.print("{x: >16} \n", .{callback_va});1412 try w.print("{f}\n", .{fmtIntField(d, callback_va, .{ .kind = .va })});
1329 if (callback_va == 0) break;1413 if (callback_va == 0) break;
1330 }1414 }
1331 try w.writeByte('\n');1415 if (d.element(.newlines)) try w.writeByte('\n');
1332 },1416 },
1333 }1417 }
1334 }1418 }
...@@ -1336,9 +1420,7 @@ const coff = struct {...@@ -1336,9 +1420,7 @@ const coff = struct {
1336 }1420 }
13371421
1338 fn seekToDataDirectory(1422 fn seekToDataDirectory(
1339 opts: *const Options,1423 d: *const DumpContext,
1340 fr: *Io.File.Reader,
1341 w: *Io.Writer,
1342 rva_index: []const u16,1424 rva_index: []const u16,
1343 sections: []const Section,1425 sections: []const Section,
1344 data_dirs: []const std.coff.ImageDataDirectory,1426 data_dirs: []const std.coff.ImageDataDirectory,
...@@ -1349,16 +1431,14 @@ const coff = struct {...@@ -1349,16 +1431,14 @@ const coff = struct {
1349 if (rva == 0) break :blk;1431 if (rva == 0) break :blk;
13501432
1351 const section_index = sectionContainingRva(rva_index, sections, rva) orelse1433 const section_index = sectionContainingRva(rva_index, sections, rva) orelse
1352 return failParse(1434 return d.failParse(
1353 opts,
1354 "{t} directory rva 0x{x} was not found in any section",1435 "{t} directory rva 0x{x} was not found in any section",
1355 .{ entry, rva },1436 .{ entry, rva },
1356 );1437 );
13571438
1358 const file_offset = sections[section_index].rvaFileOffset(rva) catch unreachable;1439 const file_offset = sections[section_index].rvaFileOffset(rva) catch unreachable;
1359 fr.seekTo(file_offset) catch |err|1440 d.fr.seekTo(file_offset) catch |err|
1360 return failParse(1441 return d.failParse(
1361 opts,
1362 "unable to seek to {t} directory at offset 0x{x}: {t}",1442 "unable to seek to {t} directory at offset 0x{x}: {t}",
1363 .{ entry, file_offset, err },1443 .{ entry, file_offset, err },
1364 );1444 );
...@@ -1366,7 +1446,7 @@ const coff = struct {...@@ -1366,7 +1446,7 @@ const coff = struct {
1366 return section_index;1446 return section_index;
1367 }1447 }
13681448
1369 try w.print("{t} directory was not present in optional header\n", .{entry});1449 try d.w.print("{t} directory was not present in optional header\n", .{entry});
1370 return null;1450 return null;
1371 }1451 }
13721452
...@@ -1382,19 +1462,18 @@ const coff = struct {...@@ -1382,19 +1462,18 @@ const coff = struct {
13821462
1383 fn order(ctx: @This(), section_index: u16) std.math.Order {1463 fn order(ctx: @This(), section_index: u16) std.math.Order {
1384 const h = &ctx.sections[section_index].header;1464 const h = &ctx.sections[section_index].header;
1385 const start = h.virtual_address;1465 if (ctx.rva < h.virtual_address) return .lt;
1386 if (ctx.rva < start) return .lt;
1387 const end = h.virtual_address + h.size_of_raw_data;1466 const end = h.virtual_address + h.size_of_raw_data;
1388 if (ctx.rva >= end) return .gt;1467 if (ctx.rva >= end) return .gt;
1389 return .eq;1468 return .eq;
1390 }1469 }
1391 };1470 };
13921471
1393 const index = std.sort.binarySearch(u16, indices, Context{1472 const indices_index = std.sort.binarySearch(u16, indices, Context{
1394 .rva = rva,1473 .rva = rva,
1395 .sections = sections,1474 .sections = sections,
1396 }, Context.order) orelse return null;1475 }, Context.order) orelse return null;
1397 return @intCast(index);1476 return @intCast(indices[indices_index]);
1398 }1477 }
13991478
1400 fn headerName(raw: *const [8]u8, string_table: []const u8) ![]const u8 {1479 fn headerName(raw: *const [8]u8, string_table: []const u8) ![]const u8 {
...@@ -1427,6 +1506,40 @@ const coff = struct {...@@ -1427,6 +1506,40 @@ const coff = struct {
1427 };1506 };
1428 }1507 }
14291508
1509 const FormatIntField = struct {
1510 val: ?u64,
1511 width: usize,
1512 zero_fill: bool,
1513 };
1514
1515 fn fmtIntField(
1516 d: *const DumpContext,
1517 val: anytype,
1518 params: struct {
1519 kind: ?FieldKind = null,
1520 width: ?usize = null,
1521 zero_fill: bool = false,
1522 },
1523 ) std.fmt.Alt(FormatIntField, intFieldString) {
1524 return .{
1525 .data = .{
1526 .val = if (d.redacted(params.kind)) null else val,
1527 .width = params.width orelse @typeInfo(@TypeOf(val)).int.bits / 4,
1528 .zero_fill = params.zero_fill,
1529 },
1530 };
1531 }
1532
1533 fn intFieldString(field: FormatIntField, w: *std.Io.Writer) std.Io.Writer.Error!void {
1534 if (field.val) |val| {
1535 try w.printInt(val, 16, .lower, .{
1536 .width = field.width,
1537 .alignment = .right,
1538 .fill = if (field.zero_fill) '0' else ' ',
1539 });
1540 } else try w.splatByteAll('x', field.width);
1541 }
1542
1430 fn dumpFlags(w: *Io.Writer, comptime fmt: []const u8, comptime T: type, flags: *const T, cols: u32) !void {1543 fn dumpFlags(w: *Io.Writer, comptime fmt: []const u8, comptime T: type, flags: *const T, cols: u32) !void {
1431 const s = @typeInfo(T).@"struct";1544 const s = @typeInfo(T).@"struct";
1432 inline for (s.fields) |flag_field| {1545 inline for (s.fields) |flag_field| {
...@@ -1437,33 +1550,53 @@ const coff = struct {...@@ -1437,33 +1550,53 @@ const coff = struct {
1437 }1550 }
1438 }1551 }
14391552
1440 fn dumpArchiveHeader(w: *Io.Writer, header: *const ArchiveHeader, pos: u32) !void {1553 fn dumpArchiveHeader(d: *const DumpContext, header: *const ArchiveHeader, pos: u32) !void {
1441 try w.print("Archive member at offset 0x{x}: '{s}'\n", .{ pos, header.name });1554 try d.w.print("Archive member at offset 0x{x}: '{s}'\n", .{ pos, header.name });
1442 try dumpHeader(w, ArchiveHeader, header, struct {1555 try dumpHeader(d, ArchiveHeader, header, struct {
1443 pub fn name(_: *const ArchiveHeader, _: *Io.Writer) !void {}1556 pub fn name(_: *const DumpContext, _: *const ArchiveHeader) !void {}
1444 pub fn file_mode(h: *const ArchiveHeader, cw: *Io.Writer) !void {1557 pub fn file_mode(id: *const DumpContext, h: *const ArchiveHeader) !void {
1445 try cw.print("{o: >16} file_mode\n", .{h.file_mode});1558 try id.w.print("{o: >16} file_mode\n", .{h.file_mode});
1446 }1559 }
1447 });1560 });
1448 }1561 }
14491562
1450 fn dumpHeader(w: *Io.Writer, comptime T: type, header: *const T, Custom: type) !void {1563 fn fieldKind(name: []const u8) ?FieldKind {
1564 if (std.mem.endsWith(u8, name, "_rva"))
1565 return .rva;
1566 if (std.mem.endsWith(u8, name, "_va") or
1567 std.mem.endsWith(u8, name, "_address") or
1568 std.mem.startsWith(u8, name, "pointer_"))
1569 return .va;
1570 if (std.mem.startsWith(u8, name, "number_"))
1571 return .size;
1572 return null;
1573 }
1574
1575 fn dumpHeader(
1576 d: *const DumpContext,
1577 comptime T: type,
1578 header: *const T,
1579 Custom: type,
1580 ) !void {
1451 inline for (@typeInfo(T).@"struct".fields) |field| {1581 inline for (@typeInfo(T).@"struct".fields) |field| {
1452 const val = &@field(header, field.name);1582 const val = &@field(header, field.name);
1453 if (@hasDecl(Custom, field.name)) {1583 if (@hasDecl(Custom, field.name)) {
1454 try @field(Custom, field.name)(header, w);1584 try @field(Custom, field.name)(d, header);
1455 } else {1585 } else {
1456 switch (@typeInfo(field.type)) {1586 switch (@typeInfo(field.type)) {
1457 .int => try w.print("{x: >16} {s}\n", .{ val.*, field.name }),1587 .int => try d.w.print("{f} {s}\n", .{ fmtIntField(d, val.*, .{
1458 .@"enum" => try w.print("{x: >16} {s} ({t})\n", .{ val.*, field.name, val.* }),1588 .kind = comptime fieldKind(field.name),
1589 .width = 16,
1590 }), field.name }),
1591 .@"enum" => try d.w.print("{x: >16} {s} ({t})\n", .{ val.*, field.name, val.* }),
1459 .@"struct" => |s| {1592 .@"struct" => |s| {
1460 switch (s.layout) {1593 switch (s.layout) {
1461 .auto,1594 .auto,
1462 .@"extern",1595 .@"extern",
1463 => try dumpHeader(w, field.type, val, Custom),1596 => try dumpHeader(d, field.type, val, Custom),
1464 .@"packed" => {1597 .@"packed" => {
1465 try w.print("{x: >16} {s}\n", .{ @as(s.backing_integer.?, @bitCast(val.*)), field.name });1598 try d.w.print("{x: >16} {s}\n", .{ @as(s.backing_integer.?, @bitCast(val.*)), field.name });
1466 try dumpFlags(w, "| {s}\n", field.type, val, 15);1599 try dumpFlags(d.w, "| {s}\n", field.type, val, 15);
1467 },1600 },
1468 }1601 }
1469 },1602 },
...@@ -1477,8 +1610,12 @@ const coff = struct {...@@ -1477,8 +1610,12 @@ const coff = struct {
1477 try w.print("{d: >13}.{x:0<2} {s}\n", .{ major, minor, name });1610 try w.print("{d: >13}.{x:0<2} {s}\n", .{ major, minor, name });
1478 }1611 }
14791612
1480 fn dumpRvaField(w: *Io.Writer, name: []const u8, rva: u64, base: u64) !void {1613 fn dumpRvaField(d: *const DumpContext, name: []const u8, rva: u64, base: u64) !void {
1481 try w.print("{x: >16} {s} ({x})\n", .{ rva, name, base + rva });1614 try d.w.print("{f} {s} ({f})\n", .{
1615 fmtIntField(d, rva, .{ .kind = .rva }),
1616 name,
1617 fmtIntField(d, base + rva, .{ .kind = .va }),
1618 });
1482 }1619 }
1483};1620};
14841621
...@@ -1492,18 +1629,32 @@ const usage =...@@ -1492,18 +1629,32 @@ const usage =
1492 \\Usage: zig objdump [options] file1629 \\Usage: zig objdump [options] file
1493 \\1630 \\
1494 \\Options:1631 \\Options:
1495 \\ -h, --help Print this help and exit1632 \\ -h, --help Print this help and exit
1496 \\ --all-headers Alias for --file-headers --member-headers --section-headers --relocs --symbols1633 \\ --all-headers Alias for --file-headers --member-headers --section-headers --relocs --symbols
1497 \\ --file-headers Display file-format specific headers1634 \\ --file-headers Display file-format specific headers
1498 \\ --imports Display imported symbols1635 \\ --imports Display imported symbols
1499 \\ --exports Display exported symbols1636 \\ --exports Display exported symbols
1500 \\ --linker-member[=1|2|longnames] (Coff) Display contents of the specified linker member (default 2)1637 \\ --linker-member[=1|2|longnames] (Coff) Display contents of the specified archive linker member (default 2)
1501 \\ --member-headers Display archive member headers1638 \\ --member-headers Display archive member headers
1502 \\ --only-member=[name] Only consider archive members names that contain [name]. Can be specified multiple times.1639 \\ --redact=[kind] Redact the specified field kind. Intended for snapshot testing.
1503 \\ --only-section=[name] Only consider section names that contain [name]. Can be specified multiple times.1640 \\ rva Relative virtual addresses
1504 \\ --relocs Display relocations1641 \\ va Virtual addresses and file offsets
1505 \\ --section-headers Display section headers1642 \\ ord Symbol ordinals / hints
1506 \\ --strings Display string tables1643 \\ size Sizes and lengths
1507 \\ --symbols Display symbol tables1644 \\ all All of the above
1508 \\ --tls Display TLS information1645 \\ --omit-element=[kind] Omit specific parts of the output. Intended for snapshot testing.
1646 \\ file-type File type summary
1647 \\ table-headers Table headers with column names
1648 \\ header-names Name that precedes a header block
1649 \\ newlines Newlines between output sections
1650 \\ all All of the above
1651 \\ --only-member=[name] Only consider archive members names that contain [name]. Can be specified multiple times.
1652 \\ --only-section=[name] Only consider section names that contain [name]. Can be specified multiple times.
1653 \\ --only-symbol=[name] Only consider symbol names that contain [name]. Can be specified multiple times.
1654 \\ --relocs Display relocations
1655 \\ -s, --snapshot Alias for --redact=all --omit-format=all
1656 \\ --section-headers Display section headers
1657 \\ --strings Display string tables
1658 \\ --symbols Display symbol tables
1659 \\ --tls Display TLS information
1509;1660;
lib/std/Build/Configuration.zig+5-1
...@@ -585,6 +585,8 @@ pub const Step = extern struct {...@@ -585,6 +585,8 @@ pub const Step = extern struct {
585 expect_stderr_match: Storage.FlagLengthPrefixedList(.flags2, .expect_stderr_match, Bytes),585 expect_stderr_match: Storage.FlagLengthPrefixedList(.flags2, .expect_stderr_match, Bytes),
586 expect_stdout_match: Storage.FlagLengthPrefixedList(.flags2, .expect_stdout_match, Bytes),586 expect_stdout_match: Storage.FlagLengthPrefixedList(.flags2, .expect_stdout_match, Bytes),
587 expect_term_value: Storage.FlagOptional(.flags2, .expect_term, u32),587 expect_term_value: Storage.FlagOptional(.flags2, .expect_term, u32),
588 expect_stdout_snapshot: Storage.FlagOptional(.flags2, .expect_stdout_snapshot, LazyPath.Index),
589 expect_stderr_snapshot: Storage.FlagOptional(.flags2, .expect_stderr_snapshot, LazyPath.Index),
588590
589 pub const CapturedStream = extern struct {591 pub const CapturedStream = extern struct {
590 generated_file: GeneratedFileIndex,592 generated_file: GeneratedFileIndex,
...@@ -683,7 +685,9 @@ pub const Step = extern struct {...@@ -683,7 +685,9 @@ pub const Step = extern struct {
683 expect_stdout_match: bool,685 expect_stdout_match: bool,
684 expect_term: bool,686 expect_term: bool,
685 expect_term_status: ExpectTermStatus,687 expect_term_status: ExpectTermStatus,
686 _: u25 = 0,688 expect_stdout_snapshot: bool,
689 expect_stderr_snapshot: bool,
690 _: u23 = 0,
687 };691 };
688 };692 };
689693
lib/std/Build/Step/Run.zig+9
...@@ -129,6 +129,8 @@ pub const StdIo = union(enum) {...@@ -129,6 +129,8 @@ pub const StdIo = union(enum) {
129 expect_stdout_exact: []const u8,129 expect_stdout_exact: []const u8,
130 expect_stdout_match: []const u8,130 expect_stdout_match: []const u8,
131 expect_term: process.Child.Term,131 expect_term: process.Child.Term,
132 expect_stderr_snapshot: std.Build.LazyPath,
133 expect_stdout_snapshot: std.Build.LazyPath,
132 };134 };
133};135};
134136
...@@ -632,6 +634,13 @@ pub fn addCheck(run: *Run, new_check: StdIo.Check) void {...@@ -632,6 +634,13 @@ pub fn addCheck(run: *Run, new_check: StdIo.Check) void {
632 .check => |*checks| checks.append(b.allocator, new_check) catch @panic("OOM"),634 .check => |*checks| checks.append(b.allocator, new_check) catch @panic("OOM"),
633 else => @panic("illegal call to addCheck: conflicting helper method calls. Suggest to directly set stdio field of Run instead"),635 else => @panic("illegal call to addCheck: conflicting helper method calls. Suggest to directly set stdio field of Run instead"),
634 }636 }
637
638 switch (new_check) {
639 .expect_stderr_snapshot,
640 .expect_stdout_snapshot,
641 => |file| run.addFileInput(file),
642 else => {},
643 }
635}644}
636645
637pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath {646pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath {
test/link.zig+24-11
...@@ -1,17 +1,30 @@...@@ -1,17 +1,30 @@
1pub fn addCases(cases: @import("tests.zig").LinkContext) void {1pub fn addCases(ctx: *@import("tests.zig").LinkContext) void {
2 if (cases.addTestStep("static-lib-exports")) |name| {2 if (ctx.includeTest("exports-static")) |prefix| {
3 const lib = cases.addStaticLibrary(.{3 const lib = ctx.addLibrary(.static, .{
4 .name = "lib",4 .name = "lib",
5 .zig_source_bytes =5 .zig_source_file = ctx.sourcePath("exports.zig"),
6 \\export fn foo() void {}
7 \\var bar: u32 = 1234;
8 \\comptime { @export(&bar, .{ .name = "bar", .linkage = .strong }); }
9 \\const baz: u64 = 5678;
10 \\comptime { @export(&baz, .{ .name = "baz", .linkage = .strong }); }
11 ,
12 });6 });
13 cases.verifyObjdump(name, lib, &.{"--symbols"}, .{ .os = true });7 ctx.verifyObjdump(prefix, lib, &.{
8 "-s",
9 "--symbols",
10 "--only-symbol=foo",
11 }, .{});
14 }12 }
13
14 if (ctx.includeTest("exports-dynamic")) |prefix| {
15 const lib = ctx.addLibrary(.dynamic, .{
16 .name = "lib",
17 .zig_source_file = ctx.sourcePath("exports.zig"),
18 });
19 ctx.verifyObjdump(prefix, lib, &.{
20 "-s",
21 "--exports",
22 "--only-symbol=foo",
23 }, .{});
24 }
25
26
27
15}28}
1629
17const std = @import("std");30const 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/snapshots/exports-dynamic.lib.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_const
13xxxx xxxx xxxxxxxx | foo_fn
14xxxx xxxx xxxxxxxx | foo_var
test/link/snapshots/exports-static.lib.dmp created+3
...@@ -0,0 +1,3 @@
1xxxx 00000000 4 NULL() EXTERNAL | foo_fn
2xxxx 00000000 2 NULL EXTERNAL | foo_var
3xxxx 00000008 3 NULL EXTERNAL | foo_const
test/src/Link.zig+43-24
...@@ -5,21 +5,29 @@ target: std.Build.ResolvedTarget,...@@ -5,21 +5,29 @@ target: std.Build.ResolvedTarget,
5use_llvm: bool,5use_llvm: bool,
6use_lld: bool,6use_lld: bool,
7link_libc: bool,7link_libc: bool,
8suffix: []const u8,
9test_filters: []const []const u8,8test_filters: []const []const u8,
9update_step: ?*Step.UpdateSourceFiles,
10updated_snapshots: std.StringArrayHashMapUnmanaged(void),
10max_rss: usize,11max_rss: usize,
1112
12pub fn addTestStep(self: *const Link, prefix: []const u8) ?[]const u8 {13pub fn includeTest(self: *const Link, prefix: []const u8) ?[]const u8 {
13 if (for (self.test_filters) |filter| {14 if (for (self.test_filters) |filter| {
14 if (std.mem.containsAtLeast(u8, prefix, 1, filter)) break false;15 if (std.mem.containsAtLeast(u8, prefix, 1, filter)) break false;
15 } else self.test_filters.len > 0) return null;16 } else self.test_filters.len > 0) return null;
17 return prefix;
18}
1619
17 return std.fmt.allocPrint(self.b.allocator, "test-{s}", .{prefix}) catch @panic("OOM");20pub fn sourcePath(self: *const Link, sub_path: []const u8) std.Build.LazyPath {
21 return self.b.path(self.b.pathJoin(&.{ "test/link", sub_path }));
18}22}
1923
20pub fn addStaticLibrary(self: *const Link, overlay: OverlayOptions) *Step.Compile {24pub fn addLibrary(
25 self: *const Link,
26 linkage: std.builtin.LinkMode,
27 overlay: OverlayOptions,
28) *Step.Compile {
21 return self.b.addLibrary(.{29 return self.b.addLibrary(.{
22 .linkage = .static,30 .linkage = linkage,
23 .name = overlay.name,31 .name = overlay.name,
24 .root_module = self.createModule(overlay),32 .root_module = self.createModule(overlay),
25 .use_llvm = self.use_llvm,33 .use_llvm = self.use_llvm,
...@@ -27,7 +35,6 @@ pub fn addStaticLibrary(self: *const Link, overlay: OverlayOptions) *Step.Compil...@@ -27,7 +35,6 @@ pub fn addStaticLibrary(self: *const Link, overlay: OverlayOptions) *Step.Compil
27 });35 });
28}36}
2937
30// TODO: Use std.meta.FieldEnum on TargetQuery?
31const SnapshotScope = packed struct {38const SnapshotScope = packed struct {
32 arch: bool = false,39 arch: bool = false,
33 os: bool = false,40 os: bool = false,
...@@ -38,33 +45,44 @@ const SnapshotScope = packed struct {...@@ -38,33 +45,44 @@ const SnapshotScope = packed struct {
38 link_libc: bool = false,45 link_libc: bool = false,
39};46};
4047
48/// Verify the results of a `zig objdump` call against a snapshot, which
49/// contains the expected output. Snapshots alias between all build
50/// configurations by default, but by specifying fields in `scope`,
51/// unique snapshot names are generated for each value of that field.
41pub fn verifyObjdump(52pub fn verifyObjdump(
42 self: *const Link,53 self: *Link,
43 name: []const u8,54 prefix: []const u8,
44 compile: *Step.Compile,55 compile: *Step.Compile,
45 args: []const []const u8,56 args: []const []const u8,
46 scope: SnapshotScope,57 scope: SnapshotScope,
47) void {58) void {
48 const snapshot_name = self.snapshotName(name, compile.name, scope) catch @panic("OOM");59 const snapshot_name = self.snapshotName(prefix, compile.name, scope) catch @panic("OOM");
60 const snapshot_sub_path = self.b.pathJoin(&.{ "test/link/snapshots/", snapshot_name });
61
62 // Many tests may read the same snapshot, so only use the first one to update.
63 // If there are differences in output, they will show up on the next test run.
64 if (self.update_step != null) {
65 const gop = self.updated_snapshots.getOrPut(self.b.allocator, snapshot_sub_path) catch @panic("OOM");
66 if (gop.found_existing) return;
67 }
68
49 const run_step = Step.Run.create(self.b, self.b.fmt("objdump {s}", .{snapshot_name}));69 const run_step = Step.Run.create(self.b, self.b.fmt("objdump {s}", .{snapshot_name}));
50 run_step.addArgs(&.{ self.b.graph.zig_exe, "objdump" });70 run_step.addArgs(&.{ self.b.graph.zig_exe, "objdump" });
51 run_step.addArtifactArg(compile);71 run_step.addArtifactArg(compile);
52 run_step.addArgs(args);72 run_step.addArgs(args);
53 run_step.addCheck(.{ .expect_term = .{ .exited = 0 } });73 run_step.addCheck(.{ .expect_term = .{ .exited = 0 } });
5474
55 const actual_path = run_step.captureStdOut(.{ .trim_whitespace = .none });75 if (self.update_step) |update_step| {
56 const expected_path = self.b.path(self.b.pathJoin(&.{ "test/link/snapshots/", snapshot_name }));76 // Workaround for the build system not realizing objdump itself has changed
77 run_step.has_side_effects = true;
5778
58 const check_step = self.b.addCheckFile(actual_path, .{79 const snapshot_update_path = run_step.captureStdOut(.{});
59 .expected_file = .{80 update_step.addCopyFileToSource(snapshot_update_path, snapshot_sub_path);
60 .file = expected_path,81 } else {
61 .if_missing = .fail,82 run_step.addCheck(.{ .snapshot = .{ .file = self.b.path(snapshot_sub_path) } });
62 // TODO: Option to do UpdateSourceFiles if not matching / missing?83 }
63 // TODO: Option to output to <name>-<self.suffix>.actual.dmp file?
64 },
65 });
6684
67 self.step.dependOn(&check_step.step);85 self.step.dependOn(&run_step.step);
68}86}
6987
70fn snapshotName(88fn snapshotName(
...@@ -81,9 +99,9 @@ fn snapshotName(...@@ -81,9 +99,9 @@ fn snapshotName(
81 if (scope.os) try w.print("-{t}", .{self.target.result.os.tag});99 if (scope.os) try w.print("-{t}", .{self.target.result.os.tag});
82 if (scope.abi) try w.print("-{t}", .{self.target.result.abi});100 if (scope.abi) try w.print("-{t}", .{self.target.result.abi});
83 if (scope.optimize) try w.print("-{t}", .{self.optimize});101 if (scope.optimize) try w.print("-{t}", .{self.optimize});
84 if (scope.use_llvm and self.use_llvm) try w.writeAll("-llvm");102 if (scope.use_llvm) try w.writeAll(if (self.use_llvm) "-llvm" else "-no-llvm");
85 if (scope.use_lld and self.use_lld) try w.writeAll("-lld");103 if (scope.use_lld) try w.writeAll(if (self.use_lld) "-lld" else "-no-lld");
86 if (scope.link_libc and self.link_libc) try w.writeAll("-libc");104 if (scope.link_libc) try w.writeAll(if (self.link_libc) "-libc" else "-no-libc");
87 try w.writeAll(".dmp");105 try w.writeAll(".dmp");
88106
89 return try snapshot_name.toOwnedSlice();107 return try snapshot_name.toOwnedSlice();
...@@ -95,7 +113,7 @@ fn createModule(self: *const Link, overlay: OverlayOptions) *Build.Module {...@@ -95,7 +113,7 @@ fn createModule(self: *const Link, overlay: OverlayOptions) *Build.Module {
95 const mod = self.b.createModule(.{113 const mod = self.b.createModule(.{
96 .target = self.target,114 .target = self.target,
97 .optimize = self.optimize,115 .optimize = self.optimize,
98 .root_source_file = rsf: {116 .root_source_file = overlay.zig_source_file orelse rsf: {
99 const bytes = overlay.zig_source_bytes orelse break :rsf null;117 const bytes = overlay.zig_source_bytes orelse break :rsf null;
100 const name = self.b.fmt("{s}.zig", .{overlay.name});118 const name = self.b.fmt("{s}.zig", .{overlay.name});
101 break :rsf write_files.add(name, bytes);119 break :rsf write_files.add(name, bytes);
...@@ -148,6 +166,7 @@ const OverlayOptions = struct {...@@ -148,6 +166,7 @@ const OverlayOptions = struct {
148 objcpp_source_bytes: ?[]const u8 = null,166 objcpp_source_bytes: ?[]const u8 = null,
149 objcpp_source_flags: []const []const u8 = &.{},167 objcpp_source_flags: []const []const u8 = &.{},
150 zig_source_bytes: ?[]const u8 = null,168 zig_source_bytes: ?[]const u8 = null,
169 zig_source_file: ?std.Build.LazyPath = null,
151 pic: ?bool = null,170 pic: ?bool = null,
152 strip: ?bool = null,171 strip: ?bool = null,
153};172};
test/tests.zig+24-9
...@@ -3148,6 +3148,11 @@ const LinkTestOptions = struct {...@@ -3148,6 +3148,11 @@ const LinkTestOptions = struct {
31483148
3149pub fn addLinkTests(b: *std.Build, options: LinkTestOptions) *Step {3149pub fn addLinkTests(b: *std.Build, options: LinkTestOptions) *Step {
3150 const step = b.step("test-link", "Run the linker tests");3150 const step = b.step("test-link", "Run the linker tests");
3151 const update_snapshots = b.option(
3152 bool,
3153 "link-snapshot-update",
3154 "Update linker test snapshots in-place instead of testing against them",
3155 ) orelse false;
31513156
3152 for (link_targets) |link_target| {3157 for (link_targets) |link_target| {
3153 if (options.skip_non_native and !link_target.target.isNative()) continue;3158 if (options.skip_non_native and !link_target.target.isNative()) continue;
...@@ -3168,24 +3173,34 @@ pub fn addLinkTests(b: *std.Build, options: LinkTestOptions) *Step {...@@ -3168,24 +3173,34 @@ pub fn addLinkTests(b: *std.Build, options: LinkTestOptions) *Step {
3168 if (options.skip_llvm and would_use_llvm) continue;3173 if (options.skip_llvm and would_use_llvm) continue;
3169 if (link_target.link_libc and target.abi == .msvc and b.graph.host.result.os.tag != .windows) continue;3174 if (link_target.link_libc and target.abi == .msvc and b.graph.host.result.os.tag != .windows) continue;
31703175
3171 link.addCases(.{3176 const opt_update_step = if (update_snapshots) update: {
3177 const update_step = Step.UpdateSourceFiles.create(b);
3178 step.dependOn(&update_step.step);
3179 break :update update_step;
3180 } else null;
3181
3182 var context: LinkContext = .{
3172 .b = b,3183 .b = b,
3173 .step = step,3184 .step = step,
3174 .optimize = optimize_mode,3185 .optimize = optimize_mode,
3175 .target = resolved_target,3186 .target = resolved_target,
3176 .suffix = std.fmt.allocPrint(b.allocator, "{s}-{t}{s}{s}{s}", .{3187 // .suffix = std.fmt.allocPrint(b.allocator, "{s}-{t}{s}{s}{s}", .{
3177 target.zigTriple(b.allocator) catch @panic("OOM"),3188 // target.zigTriple(b.allocator) catch @panic("OOM"),
3178 optimize_mode,3189 // optimize_mode,
3179 if (link_target.use_llvm) "-llvm" else "",3190 // if (link_target.use_llvm) "-llvm" else "",
3180 if (link_target.use_lld) "-lld" else "",3191 // if (link_target.use_lld) "-lld" else "",
3181 if (link_target.link_libc) "-libc" else "",3192 // if (link_target.link_libc) "-libc" else "",
3182 }) catch @panic("OOM"),3193 // }) catch @panic("OOM"),
3183 .use_llvm = link_target.use_llvm,3194 .use_llvm = link_target.use_llvm,
3184 .use_lld = link_target.use_lld,3195 .use_lld = link_target.use_lld,
3185 .link_libc = link_target.link_libc,3196 .link_libc = link_target.link_libc,
3186 .test_filters = options.test_filters,3197 .test_filters = options.test_filters,
3198 .update_step = opt_update_step,
3199 .updated_snapshots = .empty,
3187 .max_rss = options.max_rss,3200 .max_rss = options.max_rss,
3188 });3201 };
3202
3203 link.addCases(&context);
3189 }3204 }
3190 }3205 }
3191 return step;3206 return step;