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:26:56-04:00
log224d7e7999fbe81c17abf7a154260b1a63675f63
treeaff839438464c24a55a790ecae0121d0490e55f8
parent601da46f154678e4a28f9c5496376deadb78d718

objdump: more coff progress


3 files changed, 425 insertions(+), 108 deletions(-)

lib/compiler/objdump.zig+346-68
......@@ -12,11 +12,15 @@ var stdout_buffer: [4000]u8 = undefined;
1212const Options = struct {
1313 input_path: []const u8,
1414 file_headers: bool,
15 member_filters: []const []const u8 = &.{},
16 member_headers: bool,
1517 section_filters: []const []const u8 = &.{},
16 section_table: bool,
18 section_headers: bool,
1719 strings: bool,
1820 symbols: bool,
19 compact: bool,
21
22 // Coff-specific
23 linker_member: ?std.coff.ArchiveMemberHeader.Kind,
2024};
2125
2226pub fn main(init: std.process.Init) !void {
......@@ -28,12 +32,14 @@ pub fn main(init: std.process.Init) !void {
2832
2933 var opt_input_path: ?[]const u8 = null;
3034 var opt_file_headers: ?bool = null;
31 var opt_section_table: ?bool = null;
35 var opt_linker_member: ?std.coff.ArchiveMemberHeader.Kind = null;
36 var opt_member_headers: ?bool = null;
37 var opt_section_headers: ?bool = null;
3238 var opt_strings: ?bool = null;
3339 var opt_symbols: ?bool = null;
3440 var opt_relocs: ?bool = null;
35 var opt_compact: ?bool = null;
3641 var section_filters: std.ArrayList([]const u8) = .empty;
42 var member_filters: std.ArrayList([]const u8) = .empty;
3743 while (i < args.len) : (i += 1) {
3844 const arg = args[i];
3945 if (mem.startsWith(u8, arg, "-")) {
......@@ -41,19 +47,27 @@ pub fn main(init: std.process.Init) !void {
4147 return Io.File.stdout().writeStreamingAll(io, usage);
4248 } else if (mem.eql(u8, arg, "--all-headers")) {
4349 opt_file_headers = true;
44 opt_section_table = true;
50 opt_member_headers = true;
51 opt_section_headers = true;
4552 opt_symbols = true;
4653 opt_relocs = true;
47 } else if (mem.eql(u8, arg, "--compact")) {
48 opt_compact = true;
4954 } else if (mem.eql(u8, arg, "--file-headers")) {
5055 opt_file_headers = true;
56 } else if (mem.startsWith(u8, arg, "--linker-member")) {
57 if (mem.eql(u8, arg["--linker-member".len..], "=1"))
58 opt_linker_member = .first_linker
59 else
60 opt_linker_member = .second_linker;
61 } else if (mem.eql(u8, arg, "--member-headers")) {
62 opt_member_headers = true;
5163 } else if (mem.startsWith(u8, arg, "--only-section=")) {
5264 (try section_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-section=".len..]);
65 } else if (mem.startsWith(u8, arg, "--only-member=")) {
66 (try member_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-member=".len..]);
5367 } else if (mem.eql(u8, arg, "--relocs")) {
5468 opt_relocs = true;
5569 } else if (mem.eql(u8, arg, "--section-headers")) {
56 opt_section_table = true;
70 opt_section_headers = true;
5771 } else if (mem.eql(u8, arg, "--strings")) {
5872 opt_strings = true;
5973 } else if (mem.eql(u8, arg, "--symbols")) {
......@@ -70,12 +84,14 @@ pub fn main(init: std.process.Init) !void {
7084
7185 const opts: Options = .{
7286 .input_path = opt_input_path orelse fatal("missing input file path positional argument", .{}),
73 .compact = opt_compact orelse false,
7487 .file_headers = opt_file_headers orelse false,
7588 .section_filters = section_filters.items,
76 .section_table = opt_section_table orelse false,
89 .section_headers = opt_section_headers orelse false,
7790 .strings = opt_strings orelse false,
7891 .symbols = opt_symbols orelse false,
92 .member_filters = member_filters.items,
93 .member_headers = opt_member_headers orelse false,
94 .linker_member = opt_linker_member,
7995 };
8096
8197 var file = std.Io.Dir.cwd().openFile(io, opts.input_path, .{}) catch |err|
......@@ -85,7 +101,7 @@ pub fn main(init: std.process.Init) !void {
85101 var buffer: [4096]u8 = undefined;
86102 var file_reader = file.reader(io, &buffer);
87103 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &stdout_buffer);
88 dump(arena, &opts, &file_reader, &stdout_writer.interface) catch |err| switch (err) {
104 dump(init.gpa, &opts, &file_reader, &stdout_writer.interface) catch |err| switch (err) {
89105 error.ReadFailed => return file_reader.err.?,
90106 error.WriteFailed => return stdout_writer.err.?,
91107 error.UnknownFile => fatal("unrecognized file: {s}", .{opts.input_path}),
......@@ -95,7 +111,7 @@ pub fn main(init: std.process.Init) !void {
95111 try stdout_writer.flush();
96112}
97113
98fn dump(arena: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void {
114fn dump(gpa: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void {
99115 const r = &fr.interface;
100116 try r.fill(4);
101117 elf: {
......@@ -125,16 +141,16 @@ fn dump(arena: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w:
125141 return error.ParseFailure;
126142 }
127143
128 if (!opts.compact) try w.print("{s}: PE/COFF image\n\n", .{std.fs.path.basename(opts.input_path)});
129 return coff.dumpObject(arena, opts, true, fr, w);
144 try w.print("{s}: PE/COFF image\n\n", .{std.fs.path.basename(opts.input_path)});
145 return coff.dumpObject(gpa, opts, true, fr, w);
130146 } else if (std.mem.eql(u8, ext, ".lib")) {
131147 r.fill(std.coff.archive_signature.len) catch break :coff;
132148 if (!mem.eql(u8, r.buffered()[0..std.coff.archive_signature.len], std.coff.archive_signature)) break :coff;
133 if (!opts.compact) try w.print("{s}: COFF archive\n\n", .{std.fs.path.basename(opts.input_path)});
134 return coff.dumpArchive(opts, fr, w);
149 try w.print("{s}: COFF archive\n\n", .{std.fs.path.basename(opts.input_path)});
150 return coff.dumpArchive(gpa, opts, fr, w);
135151 } else if (std.mem.eql(u8, ext, ".obj")) {
136 if (!opts.compact) try w.print("{s}: COFF object\n\n", .{std.fs.path.basename(opts.input_path)});
137 return coff.dumpObject(arena, opts, false, fr, w);
152 try w.print("{s}: COFF object\n\n", .{std.fs.path.basename(opts.input_path)});
153 return coff.dumpObject(gpa, opts, false, fr, w);
138154 }
139155 }
140156 return error.UnknownFile;
......@@ -171,31 +187,249 @@ const wasm = struct {
171187};
172188
173189const coff = struct {
174 fn dumpArchive(opt: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void {
175 _ = opt;
176 _ = fr;
177 try w.writeAll("TODO dump coff archive\n");
178 }
190 const ArchiveHeader = struct {
191 name: []const u8,
192 date: u40,
193 user_id: u20,
194 group_id: u20,
195 file_mode: u24,
196 size: u34,
197
198 pub fn fromRaw(opts: *const Options, raw_header: *const std.coff.ArchiveMemberHeader, opt_longnames: ?[]const u8) @This() {
199 const name = raw_header.parseName(opt_longnames) catch |err| switch (err) {
200 error.BadName => failParse(opts, "malformed member name: '{s}'", .{&raw_header.name}),
201 error.NoLongNames => failParse(opts, "member uses a long name, but there was no longnames member", .{}),
202 };
179203
180 fn headerName(raw: *[8]u8, string_table: []const u8) ![]const u8 {
181 return if (raw[0] == '/') name: {
182 const name_offset = try std.fmt.parseUnsigned(u24, raw[1..], 10);
183 if (name_offset >= string_table.len)
184 return error.OutOfBounds;
204 return .{
205 .name = name,
206 .date = raw_header.parseDate() catch |err|
207 failParse(opts, "unable to parse date '{s}' in member '{s}': {t}", .{ raw_header.date, name, err }),
208 .user_id = raw_header.parseUserId() catch |err|
209 failParse(opts, "unable to parse user_id '{s}' in member '{s}': {t}", .{ raw_header.user_id, name, err }),
210 .group_id = raw_header.parseGroupId() catch |err|
211 failParse(opts, "unable to parse group_id '{s}' in member '{s}': {t}", .{ raw_header.group_id, name, err }),
212 .file_mode = raw_header.parseFileMode() catch |err|
213 failParse(opts, "unable to parse file_mode '{s}' in member '{s}': {t}", .{ raw_header.file_mode, name, err }),
214 .size = raw_header.parseSize() catch |err|
215 failParse(opts, "unable to parse size '{s}' in member '{s}': {t}", .{ raw_header.size, name, err }),
216 };
217 }
218 };
185219
186 break :name std.mem.sliceTo(string_table[name_offset..], 0);
187 } else std.mem.sliceTo(raw, 0);
220 fn dumpArchive(gpa: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void {
221 const r = &fr.interface;
222 r.toss(std.coff.archive_signature.len);
223
224 var members: std.ArrayList(struct {
225 offset: u32,
226 }) = .empty;
227 defer members.deinit(gpa);
228 var symbol_member_indices: std.ArrayList(u32) = .empty;
229 defer symbol_member_indices.deinit(gpa);
230
231 var opt_expected_kind: ?std.coff.ArchiveMemberHeader.Kind = .first_linker;
232 var opt_longnames: ?[]const u8 = null;
233 defer if (opt_longnames) |l| gpa.free(l);
234
235 var pos = fr.logicalPos();
236 const size = try fr.getSize();
237 while (pos < size) : (pos = fr.logicalPos()) {
238 if ((pos & 1) != 0) try r.discardAll(1);
239 const raw_header = try r.takeStruct(std.coff.ArchiveMemberHeader, .little);
240 const header: ArchiveHeader = .fromRaw(opts, &raw_header, opt_longnames);
241
242 if (!std.mem.eql(u8, &raw_header.end_of_header, std.coff.archive_end_of_header))
243 return failParse(opts, "malformed end-of-header field in member '{s}': {x}", .{ header.name, raw_header.end_of_header });
244
245 const dump_header = opts.member_headers and filterMatches(opts.member_filters, header.name);
246 if (dump_header)
247 try dumpArchiveHeader(w, &header, @intCast(pos));
248
249 const member_end = fr.logicalPos() + header.size;
250 if (member_end > size)
251 return failParse(opts, "out-of-bounds length 0x{x} in member '{s}'", .{ header.size, header.name });
252
253 if (opt_expected_kind) |expected_kind| switch (expected_kind) {
254 .first_linker => {
255 if (!std.mem.eql(u8, header.name, "/"))
256 return failParse(opts, "expected first linker member, found '{s}'", .{header.name});
257
258 const num_symbols = try r.takeInt(u32, .big);
259 if (dump_header)
260 try w.print(
261 \\{t: >16} type
262 \\ | {d} symbols
263 \\
264 , .{ expected_kind, num_symbols });
265
266 if (opts.linker_member == .first_linker) {
267 try w.print(
268 \\Archives symbols ({d}):
269 \\& Member Symbol
270 \\
271 , .{num_symbols});
272
273 const offsets = try r.readAlloc(gpa, num_symbols * 4);
274 defer gpa.free(offsets);
275
276 for (0..num_symbols) |symbol_i| {
277 const symbol = r.takeDelimiter(0) catch |err|
278 return failParse(opts, "unable to read first linker member string table: {t}", .{err});
279 try w.print("{x: >8} {s}\n", .{ std.mem.readInt(u32, offsets[symbol_i * 4 ..][0..4], .big), symbol.? });
280 }
281 }
282 if (dump_header) try w.writeByte('\n');
283
284 try fr.seekTo(member_end);
285 opt_expected_kind = .second_linker;
286 continue;
287 },
288 .second_linker => {
289 if (!std.mem.eql(u8, header.name, "/"))
290 return failParse(opts, "expected second linker member, found '{s}'", .{header.name});
291
292 // TODO: Figure out what endianness is actually used, there are no headers to say yet?
293
294 const num_members = try r.takeInt(u32, .little);
295 pos = fr.logicalPos();
296 if (pos + num_members * @sizeOf(u32) > member_end)
297 return failParse(opts, "invalid member count 0x{x} in second linker member", .{num_members});
298
299 try members.ensureTotalCapacity(gpa, num_members);
300 for (0..num_members) |_|
301 members.addOneAssumeCapacity().* = .{
302 .offset = try r.takeInt(u32, .little),
303 };
304
305 const num_symbols = try r.takeInt(u32, .little);
306 pos = fr.logicalPos();
307 if (pos + num_symbols * @sizeOf(u16) > member_end)
308 return failParse(opts, "invalid symbol count 0x{x} in second linker member", .{num_symbols});
309
310 if (dump_header)
311 try w.print(
312 \\{t: >16} type
313 \\ | {d} symbols
314 \\ | {d} members
315 \\
316 , .{ expected_kind, num_symbols, num_members });
317
318 try symbol_member_indices.ensureTotalCapacity(gpa, num_symbols);
319 for (0..num_symbols) |_|
320 symbol_member_indices.addOneAssumeCapacity().* = (try r.takeInt(u16, .little)) - 1;
321
322 if (opts.linker_member == .second_linker) {
323 try w.print(
324 \\Archive symbols ({d} members, {d} symbols):
325 \\& Member Symbol
326 \\
327 , .{ num_members, num_symbols });
328
329 pos = fr.logicalPos();
330 var symbol_i: u32 = 0;
331 while (pos < member_end and symbol_i < num_symbols) : ({
332 pos = fr.logicalPos();
333 symbol_i += 1;
334 }) {
335 const symbol_name = if (r.takeDelimiter(0) catch |err| switch (err) {
336 error.StreamTooLong => null,
337 else => |e| return e,
338 }) |n| n else return failParse(opts, "unterminated string found in second linker member", .{});
339
340 try w.print("{x: >8} {s}\n", .{
341 members.items[symbol_member_indices.items[symbol_i]].offset,
342 symbol_name,
343 });
344 }
345
346 if (symbol_i != num_symbols)
347 return failParse(
348 opts,
349 " expected {d} entries in second linker member string table, but found {d}",
350 .{ num_symbols, symbol_i },
351 );
352 }
353
354 try w.writeByte('\n');
355 try fr.seekTo(member_end);
356 opt_expected_kind = .longnames;
357 continue;
358 },
359 .longnames => {
360 // This member is optional
361 if (std.mem.eql(u8, header.name, "//")) {
362 opt_longnames = try r.readAlloc(gpa, header.size);
363 if (dump_header)
364 try w.print("{t: >16} type\n", .{expected_kind});
365 }
366
367 opt_expected_kind = null;
368 break;
369 },
370 else => unreachable,
371 };
372 }
373
374 if (opt_expected_kind) |expected_kind| switch (expected_kind) {
375 .first_linker => failParse(opts, "missing first linker member", .{}),
376 .second_linker => failParse(opts, "missing second linker member", .{}),
377 else => {},
378 };
379
380 for (members.items, 0..) |member, member_i| {
381 fr.seekTo(member.offset) catch |err|
382 failParse(opts, "unable to read member {d} at offset 0x{x}: {t}", .{ member_i, member.offset, err });
383
384 const raw_header = try r.takeStruct(std.coff.ArchiveMemberHeader, .little);
385 const header: ArchiveHeader = .fromRaw(opts, &raw_header, opt_longnames);
386 if (!filterMatches(opts.member_filters, header.name)) continue;
387
388 const member_sig = try r.peek(4);
389 const machine: std.coff.IMAGE.FILE.MACHINE =
390 @enumFromInt(std.mem.readInt(u16, member_sig[0..2], .little));
391 const sig = std.mem.readInt(u16, member_sig[2..4], .little);
392
393 const is_import = machine == std.coff.IMAGE.FILE.MACHINE.UNKNOWN and sig == 0xffff;
394
395 if (opts.member_headers) {
396 try dumpArchiveHeader(w, &header, member.offset);
397 if (is_import) {
398 try w.writeAll(" Import header type\n");
399 // TODO: Dump import header
400 } else {
401 try w.writeAll(" COFF object type\n");
402 }
403 try w.writeByte('\n');
404 }
405
406 if (opts.section_headers or
407 opts.file_headers or
408 opts.strings or
409 opts.symbols)
410 {
411 try w.print("{s}({s}): COFF object\n\n", .{ std.fs.path.basename(opts.input_path), header.name });
412 try dumpObject(gpa, opts, false, fr, w);
413 }
414 }
188415 }
189416
190 fn dumpObject(arena: std.mem.Allocator, opts: *const Options, is_image: bool, fr: *Io.File.Reader, w: *Io.Writer) !void {
417 fn dumpObject(
418 gpa: std.mem.Allocator,
419 opts: *const Options,
420 is_image: bool,
421 fr: *Io.File.Reader,
422 w: *Io.Writer,
423 ) !void {
424 const file_location = fr.logicalPos();
191425 const r = &fr.interface;
192426 const header = r.takeStruct(std.coff.Header, .little) catch |err|
193427 return failParse(opts, "unable to read COFF header: {t}", .{err});
194428
195429 if (opts.file_headers) {
196 if (!opts.compact) try w.writeAll("COFF Header:\n");
430 try w.writeAll("COFF Header:\n");
197431 try dumpHeader(w, std.coff.Header, &header, struct {});
198 if (!opts.compact) try w.writeByte('\n');
432 try w.writeByte('\n');
199433 }
200434
201435 if (header.size_of_optional_header > 0) opt_header: {
......@@ -204,7 +438,7 @@ const coff = struct {
204438 break :opt_header;
205439 }
206440
207 if (!opts.compact) try w.writeAll("COFF Optional Header:\n");
441 try w.writeAll("COFF Optional Header:\n");
208442 const magic: std.coff.OptionalHeader.Magic = @enumFromInt(try r.peekInt(u16, .little));
209443 const num_directory_entries = switch (magic) {
210444 inline .PE32, .@"PE32+" => |v| data_dirs: {
......@@ -252,14 +486,14 @@ const coff = struct {
252486 }
253487 pub fn minor_subsystem_version(_: *const OptionalHeader, _: *Io.Writer) !void {}
254488 });
255 if (!opts.compact) try w.writeByte('\n');
489 try w.writeByte('\n');
256490
257491 break :data_dirs optional_header.number_of_rva_and_sizes;
258492 },
259493 else => return failParse(opts, "invalid optional header magic number: {x}", .{magic}),
260494 };
261495
262 if (!opts.compact) try w.writeAll("Data Directories:\n");
496 try w.writeAll("Data Directories:\n");
263497 for (0..num_directory_entries) |dir_i| {
264498 const dir = r.takeStruct(std.coff.ImageDataDirectory, .little) catch |err|
265499 return failParse(opts, "unable to read data directory {x}: {t}", .{ dir_i, err });
......@@ -269,45 +503,62 @@ const coff = struct {
269503 .{ dir.virtual_address, dir.size, @as(std.coff.IMAGE.DIRECTORY_ENTRY, @enumFromInt(dir_i)) },
270504 );
271505 }
272 if (!opts.compact) try w.writeByte('\n');
506 try w.writeByte('\n');
273507 } else if (is_image) {
274508 return failParse(opts, "image did not contain an optional header", .{});
275509 }
276510
277511 // Section names in images don't use the string table, as they must fit inline in the header
278512 const load_string_table = (opts.strings or !is_image) and header.pointer_to_symbol_table > 0;
279
280513 const string_table = if (load_string_table) string_table: {
281514 const pos = fr.logicalPos();
282 fr.seekTo(header.pointer_to_symbol_table + header.number_of_symbols * std.coff.Symbol.sizeOf()) catch |err|
515 fr.seekTo(file_location + header.pointer_to_symbol_table + header.number_of_symbols * std.coff.Symbol.sizeOf()) catch |err|
283516 return failParse(opts, "unable to seek to string table: {t}", .{err});
284517
285518 const string_table_len = r.peekInt(u32, .little) catch |err|
286519 return failParse(opts, "unable to read string table length: {t}", .{err});
287520
288 const table = r.readAlloc(arena, string_table_len) catch |err|
521 const table = r.readAlloc(gpa, string_table_len) catch |err|
289522 return failParse(opts, "unable to read string table: {t}", .{err});
290523
291524 try fr.seekTo(pos);
292525 break :string_table table;
293526 } else &.{};
527 defer gpa.free(string_table);
528
529 if (opts.strings) {
530 try w.print(
531 \\String Table (0x{x} bytes):
532 \\
533 , .{string_table.len});
534
535 var sr = Io.Reader.fixed(string_table[4..]);
536 while (try sr.takeDelimiter(0)) |str| {
537 try w.writeAll(str);
538 try w.writeByte('\n');
539 }
540
541 try w.writeByte('\n');
542 }
294543
295544 var sections: std.ArrayList(std.coff.SectionHeader) = .empty;
296 const load_sections = opts.section_table or opts.symbols;
545 defer sections.deinit(gpa);
546
547 const load_sections = opts.section_headers or opts.symbols;
297548 if (load_sections) {
298 if (!opts.compact and opts.section_table)
549 if (opts.section_headers)
299550 try w.writeAll(
300551 \\Section Table:
301 \\Num Name RVA Virtual Size Data Size File Offset Relocs Offset Lines Offset # Relocs # Lines Flags
552 \\Num Name RVA Virt Size Data Size & Data & Relocs & Lines # Relocs # Lines Flags
302553 \\
303554 );
304555
305 try sections.resize(arena, header.number_of_sections);
556 try sections.resize(gpa, header.number_of_sections);
306557 for (sections.items, 0..) |*section, section_i| {
307558 section.* = r.takeStruct(std.coff.SectionHeader, .little) catch |err|
308559 return failParse(opts, "unable to read section header {x}: {t}", .{ section_i, err });
309560
310 if (opts.section_table) {
561 if (opts.section_headers) {
311562 const name = headerName(&section.name, string_table) catch |err| switch (err) {
312563 error.Overflow,
313564 error.InvalidCharacter,
......@@ -321,16 +572,13 @@ const coff = struct {
321572 }),
322573 };
323574
324 const matched = for (opts.section_filters) |filter| {
325 if (std.mem.containsAtLeast(u8, name, 1, filter)) break true;
326 } else opts.section_filters.len == 0;
327 if (!matched) continue;
328
575 if (!filterMatches(opts.section_filters, name)) continue;
576 const raw_name = std.mem.sliceTo(&section.name, 0);
329577 try w.print(
330 "{x: >3} {s: <8} {x: >8} {x: >12} {x: >9} {x: >10} {x: >13} {x: >12} {x: >8} {x: >8} {x:0>8} ",
578 "{x: >3} {s: <8} {x: >8} {x: >9} {x: >9} {x: >8} {x: >8} {x: >8} {x: >8} {x: >8} {x:0>8} | ",
331579 .{
332580 section_i + 1,
333 std.mem.sliceTo(&section.name, 0),
581 raw_name,
334582 section.virtual_address,
335583 section.virtual_size,
336584 section.size_of_raw_data,
......@@ -343,28 +591,27 @@ const coff = struct {
343591 },
344592 );
345593
594 try dumpFlags(w, "{s} ", std.coff.SectionHeader.Flags, &section.flags, 0);
346595 if (name.len > 8)
347 try w.print(" | {s}", .{name});
596 try w.print("| {s}", .{name});
348597
349 try dumpFlags(w, "{s} ", std.coff.SectionHeader.Flags, &section.flags, 0);
350598 try w.writeByte('\n');
351599 }
352600 }
353601
354 if (!opts.compact and opts.section_table) try w.writeByte('\n');
602 if (opts.section_headers) try w.writeByte('\n');
355603 }
356604
357605 if (opts.symbols) {
358606 if (header.pointer_to_symbol_table > 0) {
359 fr.seekTo(header.pointer_to_symbol_table) catch |err|
607 fr.seekTo(file_location + header.pointer_to_symbol_table) catch |err|
360608 return failParse(opts, "unable to seek to symbol table: {t}", .{err});
361609
362 if (!opts.compact and opts.symbols)
363 try w.writeAll(
364 \\Symbol Table:
365 \\ Ord Value Sect Type Storage Name
366 \\
367 );
610 try w.writeAll(
611 \\Symbol Table:
612 \\ Ord Value Sect Type Storage Name
613 \\
614 );
368615
369616 const symbol_size = std.coff.Symbol.sizeOf();
370617 var symbol_i: u32 = 0;
......@@ -390,7 +637,7 @@ const coff = struct {
390637 break :name string_table[index..];
391638 } else &symbol.name, 0);
392639
393 try w.print("{x:0>4} {x:0>8} ", .{ symbol_i, symbol.value });
640 try w.print("{x: >4} {x:0>8} ", .{ symbol_i, symbol.value });
394641 try switch (symbol.section_number) {
395642 .UNDEFINED => w.writeAll("UNDEF"),
396643 .ABSOLUTE => w.writeAll(" ABS"),
......@@ -419,7 +666,7 @@ const coff = struct {
419666
420667 for (0..symbol.number_of_aux_symbols) |aux_i| {
421668 _ = aux_i;
422 try w.writeAll(" AUX");
669 try w.writeAll(" |");
423670
424671 if (symbol.storage_class == .EXTERNAL and
425672 symbol.type == std.coff.SymType{
......@@ -512,7 +759,7 @@ const coff = struct {
512759 continue;
513760 }
514761
515 try w.print(" [size: {x:0>8} chksum: {x:0>8} relocs: {x:0>4} lines: {x:0>4}]", .{
762 try w.print(" [size {x:0>8} chksum {x:0>8} relocs {x:0>4} lines {x:0>4}]", .{
516763 section_def.length,
517764 section_def.checksum,
518765 section_def.number_of_relocations,
......@@ -533,12 +780,24 @@ const coff = struct {
533780 try w.writeByte('\n');
534781 }
535782 }
783
784 try w.writeByte('\n');
536785 } else {
537 if (!opts.compact) try w.writeAll("No symbol table found\n");
786 try w.writeAll("No symbol table found\n");
538787 }
539788 }
540789 }
541790
791 fn headerName(raw: *const [8]u8, string_table: []const u8) ![]const u8 {
792 return if (raw[0] == '/') name: {
793 const name_offset = try std.fmt.parseUnsigned(u24, std.mem.sliceTo(raw[1..], 0), 10);
794 if (name_offset >= string_table.len)
795 return error.OutOfBounds;
796
797 break :name std.mem.sliceTo(string_table[name_offset..], 0);
798 } else std.mem.sliceTo(raw, 0);
799 }
800
542801 fn fmtSymbolType(sym_type: std.coff.SymType) std.fmt.Alt(std.coff.SymType, symbolTypeString) {
543802 return .{ .data = sym_type };
544803 }
......@@ -584,6 +843,18 @@ const coff = struct {
584843 }
585844 }
586845
846 fn dumpArchiveHeader(w: *Io.Writer, header: *const ArchiveHeader, pos: u32) !void {
847 try w.print("Archive member at offset 0x{x}: '{s}'\n", .{ pos, header.name });
848
849 // TODO: Date formatter
850 try dumpHeader(w, ArchiveHeader, header, struct {
851 pub fn name(_: *const ArchiveHeader, _: *Io.Writer) !void {}
852 pub fn file_mode(h: *const ArchiveHeader, cw: *Io.Writer) !void {
853 try cw.print("{o: >16} file_mode\n", .{h.file_mode});
854 }
855 });
856 }
857
587858 fn dumpHeader(w: *Io.Writer, comptime T: type, header: *const T, Custom: type) !void {
588859 inline for (@typeInfo(T).@"struct".fields) |field| {
589860 const val = &@field(header, field.name);
......@@ -619,14 +890,21 @@ const coff = struct {
619890 }
620891};
621892
893fn filterMatches(filters: []const []const u8, val: []const u8) bool {
894 return for (filters) |filter| {
895 if (std.mem.containsAtLeast(u8, val, 1, filter)) break true;
896 } else filters.len == 0;
897}
898
622899const usage =
623900 \\Usage: zig objdump [options] file
624901 \\
625902 \\Options:
626903 \\ -h, --help Print this help and exit
627 \\ --all-headers Alias for --file-headers --section-headers --relocs --symbols
628 \\ --compact Minimal output mode that excludes extra newlines and headings. Intended for snapshot testing.
904 \\ --all-headers Alias for --file-headers --member-headers --section-headers --relocs --symbols
629905 \\ --file-headers Display file-format specific headers
906 \\ --linker-member[=1|2] (Coff) Display contents of the specified linker member (default 2)
907 \\ --member-headers Display archive member headers
630908 \\ --only-member=[name] Only consider archive members that contain [name]. Can be specified multiple times.
631909 \\ --only-section=[name] Only consider sections that contain [name]. Can be specified multiple times.
632910 \\ --section-headers Display section headers
lib/std/coff.zig+68
......@@ -3,6 +3,7 @@ const assert = std.debug.assert;
33const mem = std.mem;
44
55pub const archive_signature = "!<arch>\n";
6pub const archive_end_of_header = "`\n";
67
78pub const pe_signature = "PE\x00\x00";
89pub const pe_pointer_offset = 0x3C;
......@@ -1990,6 +1991,73 @@ pub const ArchiveMemberHeader = extern struct {
19901991 size: [10]u8,
19911992 /// The literal string '`\n'
19921993 end_of_header: [2]u8,
1994
1995 /// Extracts the name of the member by either reading it directly from
1996 /// the header, or by finding it inside the longnames member, if provided.
1997 pub fn parseName(
1998 self: *const ArchiveMemberHeader,
1999 opt_longnames: ?[]const u8,
2000 ) ![]const u8 {
2001 const trim = std.mem.trimEnd(u8, &self.name, &.{' '});
2002
2003 if (trim.len == 0) return error.BadName;
2004 return if (trim[0] == '/') name: {
2005 if (trim.len == 1 or
2006 trim.len == 2 and trim[1] == '/')
2007 break :name trim;
2008
2009 const offset = std.fmt.parseUnsigned(u50, trim[1..], 10) catch
2010 return error.BadName;
2011
2012 if (opt_longnames) |longnames| {
2013 if (offset >= longnames.len) return error.BadName;
2014 break :name std.mem.sliceTo(longnames[offset..], 0);
2015 } else return error.NoLongNames;
2016 } else if (trim[trim.len - 1] == '/')
2017 trim[0 .. trim.len - 1]
2018 else
2019 return error.BadName;
2020 }
2021
2022 fn parseField(field: []const u8, T: type, base: u8) !T {
2023 if (std.mem.allEqual(u8, field, ' ')) return 0;
2024 if (field[0] == '-')
2025 return @bitCast(try std.fmt.parseInt(
2026 @Int(.signed, @typeInfo(T).int.bits),
2027 std.mem.trimEnd(u8, field, &.{' '}),
2028 base,
2029 ));
2030
2031 return std.fmt.parseUnsigned(T, std.mem.trimEnd(u8, field, &.{' '}), base);
2032 }
2033
2034 pub fn parseDate(self: *const ArchiveMemberHeader) !u40 {
2035 return parseField(&self.date, u40, 10);
2036 }
2037
2038 pub fn parseUserId(self: *const ArchiveMemberHeader) !u20 {
2039 return parseField(&self.user_id, u20, 10);
2040 }
2041
2042 pub fn parseGroupId(self: *const ArchiveMemberHeader) !u20 {
2043 return parseField(&self.group_id, u20, 10);
2044 }
2045
2046 pub fn parseFileMode(self: *const ArchiveMemberHeader) !u20 {
2047 return parseField(&self.group_id, u20, 8);
2048 }
2049
2050 pub fn parseSize(self: *const ArchiveMemberHeader) !u34 {
2051 return parseField(&self.size, u34, 10);
2052 }
2053
2054 pub const Kind = enum {
2055 first_linker,
2056 second_linker,
2057 longnames,
2058 coff,
2059 import,
2060 };
19932061};
19942062
19952063pub const LineNumber = extern struct {
src/link/Coff.zig+11-40
......@@ -90,8 +90,6 @@ pub const default_size_of_stack_commit: u32 = 0x1000;
9090pub const default_size_of_heap_reserve: u32 = 0x100000;
9191pub const default_size_of_heap_commit: u32 = 0x1000;
9292
93pub const archive_end_of_header = "`\n";
94
9593pub const imp_prefix = "__imp_";
9694
9795const header_name_max_len = @typeInfo(@FieldType(std.coff.SectionHeader, "name")).array.len;
......@@ -472,7 +470,7 @@ pub const InputObject = struct {
472470};
473471
474472pub const Member = struct {
475 kind: Kind,
473 kind: std.coff.ArchiveMemberHeader.Kind,
476474 header_ni: MappedFile.Node.Index,
477475 content_ni: MappedFile.Node.Index,
478476 first_linker_indices: std.AutoArrayHashMapUnmanaged(struct {
......@@ -480,14 +478,6 @@ pub const Member = struct {
480478 name: String,
481479 }, FirstLinkerIndex),
482480
483 pub const Kind = enum {
484 first_linker,
485 second_linker,
486 longnames,
487 coff,
488 import,
489 };
490
491481 pub const Index = enum(u16) {
492482 first,
493483 second,
......@@ -569,7 +559,7 @@ pub const Member = struct {
569559 member.content_ni.location(&coff.mf).resolve(&coff.mf)[1],
570560 );
571561
572 @memcpy(&header.end_of_header, archive_end_of_header);
562 @memcpy(&header.end_of_header, std.coff.archive_end_of_header);
573563 }
574564
575565 pub fn storeHeaderDecimalStr(field_ptr: anytype, value: u64) void {
......@@ -2832,7 +2822,7 @@ pub fn getVAddr(coff: *Coff, reloc_info: link.File.RelocInfo, target_si: Symbol.
28322822}
28332823
28342824/// Caller guarantees there is capacity for one member and two nodes
2835fn addMemberAssumeCapacity(coff: *Coff, kind: Member.Kind, size: usize) !Member.Index {
2825fn addMemberAssumeCapacity(coff: *Coff, kind: std.coff.ArchiveMemberHeader.Kind, size: usize) !Member.Index {
28362826 const comp = coff.base.comp;
28372827 const gpa = comp.gpa;
28382828
......@@ -4731,41 +4721,22 @@ fn parseArchiveMemberHeader(
47314721 opt_longnames: ?[]const u8,
47324722) !ArchiveMemberHeader {
47334723 return parseArchiveMemberHeaderInner(header, opt_longnames) catch |err| switch (err) {
4734 error.BadName => return diags.failParse(path, "malformed member header name: '{s}'", .{&header.name}),
4735 error.BadSize => return diags.failParse(path, "malformed member header size: '{s}'", .{&header.size}),
4736 error.BadEndOfHeader => return diags.failParse(path, "bad member header end of header", .{}),
4724 error.BadName => return diags.failParse(path, "malformed member name: '{s}'", .{&header.name}),
4725 error.BadSize => return diags.failParse(path, "malformed member size: '{s}'", .{&header.size}),
4726 error.BadEndOfHeader => return diags.failParse(path, "end of header was invalid", .{}),
47374727 error.NoLongNames => return diags.failParse(path, "long name used without longnames member", .{}),
47384728 };
47394729}
47404730
4731// TODO: Move to std.coff?
47414732fn parseArchiveMemberHeaderInner(
47424733 header: *const std.coff.ArchiveMemberHeader,
47434734 opt_longnames: ?[]const u8,
47444735) !ArchiveMemberHeader {
4745 const trim = std.mem.trimEnd(u8, &header.name, &.{' '});
4746
4747 if (trim.len == 0) return error.BadName;
4748 const name = if (trim[0] == '/') name: {
4749 if (trim.len == 1 or
4750 trim.len == 2 and trim[1] == '/')
4751 break :name trim;
4752
4753 const offset = std.fmt.parseUnsigned(u50, trim[1..], 10) catch
4754 return error.BadName;
4755
4756 if (opt_longnames) |longnames| {
4757 if (offset >= longnames.len) return error.BadName;
4758 break :name std.mem.sliceTo(longnames[offset..], 0);
4759 } else return error.NoLongNames;
4760 } else if (trim[trim.len - 1] == '/')
4761 trim[0 .. trim.len - 1]
4762 else
4763 return error.BadName;
4764
4765 const size = std.fmt.parseUnsigned(u34, std.mem.trimEnd(u8, &header.size, &.{' '}), 10) catch
4766 return error.BadSize;
4736 const name = try header.parseName(opt_longnames);
4737 const size = header.parseSize() catch return error.BadSize;
47674738
4768 if (!std.mem.eql(u8, &header.end_of_header, archive_end_of_header))
4739 if (!std.mem.eql(u8, &header.end_of_header, std.coff.archive_end_of_header))
47694740 return error.BadEndOfHeader;
47704741
47714742 return .{
......@@ -4787,7 +4758,7 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo
47874758 if (!std.mem.eql(u8, signature, std.coff.archive_signature))
47884759 return diags.failParse(path, "bad signature", .{});
47894760
4790 var opt_expected_kind: ?Member.Kind = .first_linker;
4761 var opt_expected_kind: ?std.coff.ArchiveMemberHeader.Kind = .first_linker;
47914762 var opt_longnames: ?[]const u8 = null;
47924763 defer if (opt_longnames) |l| gpa.free(l);
47934764