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
log9df2ca0d3382170a2d616d110073e7be25793778
tree706a2759138d92278273568fa43b982c537ca604
parent224d7e7999fbe81c17abf7a154260b1a63675f63

objdump: more coff progress

- Linker member selection - Dump implib headers - Dump relocs - Add more info to table headers for easier grepping

2 files changed, 238 insertions(+), 64 deletions(-)

lib/compiler/objdump.zig+208-64
......@@ -10,10 +10,13 @@ const native_endian = builtin.cpu.arch.endian();
1010var stdout_buffer: [4000]u8 = undefined;
1111
1212const Options = struct {
13 input_path: []const u8,
13 exports: bool,
1414 file_headers: bool,
15 imports: bool,
16 input_path: []const u8,
1517 member_filters: []const []const u8 = &.{},
1618 member_headers: bool,
19 relocs: bool,
1720 section_filters: []const []const u8 = &.{},
1821 section_headers: bool,
1922 strings: bool,
......@@ -30,14 +33,16 @@ pub fn main(init: std.process.Init) !void {
3033
3134 var i: usize = 1;
3235
33 var opt_input_path: ?[]const u8 = null;
36 var opt_exports: ?bool = null;
3437 var opt_file_headers: ?bool = null;
38 var opt_imports: ?bool = null;
39 var opt_input_path: ?[]const u8 = null;
3540 var opt_linker_member: ?std.coff.ArchiveMemberHeader.Kind = null;
3641 var opt_member_headers: ?bool = null;
42 var opt_relocs: ?bool = null;
3743 var opt_section_headers: ?bool = null;
3844 var opt_strings: ?bool = null;
3945 var opt_symbols: ?bool = null;
40 var opt_relocs: ?bool = null;
4146 var section_filters: std.ArrayList([]const u8) = .empty;
4247 var member_filters: std.ArrayList([]const u8) = .empty;
4348 while (i < args.len) : (i += 1) {
......@@ -51,11 +56,17 @@ pub fn main(init: std.process.Init) !void {
5156 opt_section_headers = true;
5257 opt_symbols = true;
5358 opt_relocs = true;
59 } else if (mem.eql(u8, arg, "--exports")) {
60 opt_exports = true;
5461 } else if (mem.eql(u8, arg, "--file-headers")) {
5562 opt_file_headers = true;
63 } else if (mem.eql(u8, arg, "--imports")) {
64 opt_imports = true;
5665 } else if (mem.startsWith(u8, arg, "--linker-member")) {
5766 if (mem.eql(u8, arg["--linker-member".len..], "=1"))
5867 opt_linker_member = .first_linker
68 else if (mem.eql(u8, arg["--linker-member".len..], "=longnames"))
69 opt_linker_member = .longnames
5970 else
6071 opt_linker_member = .second_linker;
6172 } else if (mem.eql(u8, arg, "--member-headers")) {
......@@ -84,9 +95,12 @@ pub fn main(init: std.process.Init) !void {
8495
8596 const opts: Options = .{
8697 .input_path = opt_input_path orelse fatal("missing input file path positional argument", .{}),
98 .exports = opt_exports orelse false,
8799 .file_headers = opt_file_headers orelse false,
100 .imports = opt_imports orelse false,
88101 .section_filters = section_filters.items,
89102 .section_headers = opt_section_headers orelse false,
103 .relocs = opt_relocs orelse false,
90104 .strings = opt_strings orelse false,
91105 .symbols = opt_symbols orelse false,
92106 .member_filters = member_filters.items,
......@@ -129,6 +143,7 @@ fn dump(gpa: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: *I
129143 }
130144 coff: {
131145 const ext = std.fs.path.extension(opts.input_path);
146 const basename = std.fs.path.basename(opts.input_path);
132147 if (std.mem.eql(u8, ext, ".exe") or std.mem.eql(u8, ext, ".dll")) {
133148 if (!mem.eql(u8, r.buffered()[0..2], "MZ")) break :coff;
134149 try r.discardAll(std.coff.pe_pointer_offset);
......@@ -141,16 +156,16 @@ fn dump(gpa: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: *I
141156 return error.ParseFailure;
142157 }
143158
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);
159 try w.print("{s}: PE/COFF image\n\n", .{basename});
160 return coff.dumpObject(gpa, opts, true, basename, fr, w);
146161 } else if (std.mem.eql(u8, ext, ".lib")) {
147162 r.fill(std.coff.archive_signature.len) catch break :coff;
148163 if (!mem.eql(u8, r.buffered()[0..std.coff.archive_signature.len], std.coff.archive_signature)) break :coff;
149 try w.print("{s}: COFF archive\n\n", .{std.fs.path.basename(opts.input_path)});
164 try w.print("{s}: COFF archive\n\n", .{basename});
150165 return coff.dumpArchive(gpa, opts, fr, w);
151166 } else if (std.mem.eql(u8, ext, ".obj")) {
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);
167 try w.print("{s}: COFF object\n\n", .{basename});
168 return coff.dumpObject(gpa, opts, false, basename, fr, w);
154169 }
155170 }
156171 return error.UnknownFile;
......@@ -242,7 +257,10 @@ const coff = struct {
242257 if (!std.mem.eql(u8, &raw_header.end_of_header, std.coff.archive_end_of_header))
243258 return failParse(opts, "malformed end-of-header field in member '{s}': {x}", .{ header.name, raw_header.end_of_header });
244259
245 const dump_header = opts.member_headers and filterMatches(opts.member_filters, header.name);
260 const dump_header =
261 (opts.member_headers and filterMatches(opts.member_filters, header.name)) or
262 (opts.linker_member == opt_expected_kind);
263
246264 if (dump_header)
247265 try dumpArchiveHeader(w, &header, @intCast(pos));
248266
......@@ -264,11 +282,12 @@ const coff = struct {
264282 , .{ expected_kind, num_symbols });
265283
266284 if (opts.linker_member == .first_linker) {
267 try w.print(
268 \\Archives symbols ({d}):
285 try w.writeAll(
286 \\
287 \\Archive symbols:
269288 \\& Member Symbol
270289 \\
271 , .{num_symbols});
290 );
272291
273292 const offsets = try r.readAlloc(gpa, num_symbols * 4);
274293 defer gpa.free(offsets);
......@@ -320,11 +339,12 @@ const coff = struct {
320339 symbol_member_indices.addOneAssumeCapacity().* = (try r.takeInt(u16, .little)) - 1;
321340
322341 if (opts.linker_member == .second_linker) {
323 try w.print(
324 \\Archive symbols ({d} members, {d} symbols):
342 try w.writeAll(
343 \\
344 \\Archive Symbols:
325345 \\& Member Symbol
326346 \\
327 , .{ num_members, num_symbols });
347 );
328348
329349 pos = fr.logicalPos();
330350 var symbol_i: u32 = 0;
......@@ -362,6 +382,21 @@ const coff = struct {
362382 opt_longnames = try r.readAlloc(gpa, header.size);
363383 if (dump_header)
364384 try w.print("{t: >16} type\n", .{expected_kind});
385
386 if (opts.linker_member == .longnames) {
387 try w.print(
388 \\
389 \\Longnames (0x{x} bytes):
390 \\
391 , .{opt_longnames.?.len});
392
393 var lr = Io.Reader.fixed(opt_longnames.?);
394 while (try lr.takeDelimiter(0)) |str| {
395 try w.writeAll(str);
396 try w.writeByte('\n');
397 }
398 try w.writeByte('\n');
399 }
365400 }
366401
367402 opt_expected_kind = null;
......@@ -390,13 +425,50 @@ const coff = struct {
390425 @enumFromInt(std.mem.readInt(u16, member_sig[0..2], .little));
391426 const sig = std.mem.readInt(u16, member_sig[2..4], .little);
392427
393 const is_import = machine == std.coff.IMAGE.FILE.MACHINE.UNKNOWN and sig == 0xffff;
428 const is_imp_lib = machine == std.coff.IMAGE.FILE.MACHINE.UNKNOWN and sig == 0xffff;
394429
395 if (opts.member_headers) {
430 if (opts.member_headers or (opts.exports and is_imp_lib)) {
396431 try dumpArchiveHeader(w, &header, member.offset);
397 if (is_import) {
398 try w.writeAll(" Import header type\n");
399 // TODO: Dump import header
432 if (is_imp_lib) {
433 try w.writeAll("\nImport header:\n");
434
435 const imp_header = try r.takeStruct(std.coff.ImportHeader, .little);
436 try dumpHeader(w, std.coff.ImportHeader, &imp_header, struct {
437 pub fn sig1(_: *const std.coff.ImportHeader, _: *Io.Writer) !void {}
438 pub fn sig2(_: *const std.coff.ImportHeader, _: *Io.Writer) !void {}
439 pub fn types(h: *const std.coff.ImportHeader, cw: *Io.Writer) !void {
440 try cw.print(
441 \\{t: >16} import_type
442 \\{t: >16} name_type
443 \\
444 , .{ h.types.type, h.types.name_type });
445 }
446 });
447
448 const sym_name = (try r.takeDelimiter(0)).?;
449 const imp_dll = (try r.takeDelimiter(0)).?;
450 const imp_name = imp_name: switch (imp_header.types.name_type) {
451 .NAME_NOPREFIX,
452 .NAME_UNDECORATE,
453 => |tag| {
454 var imp_name = std.mem.trimStart(u8, sym_name, "?@_");
455 if (tag == .NAME_UNDECORATE)
456 imp_name = std.mem.sliceTo(imp_name, '@');
457 break :imp_name imp_name;
458 },
459 else => sym_name,
460 };
461
462 try w.print(
463 \\ symbol name | {s}
464 \\ import name | {s}
465 \\ dll | {s}
466 \\
467 , .{
468 sym_name,
469 imp_name,
470 imp_dll,
471 });
400472 } else {
401473 try w.writeAll(" COFF object type\n");
402474 }
......@@ -409,7 +481,7 @@ const coff = struct {
409481 opts.symbols)
410482 {
411483 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);
484 try dumpObject(gpa, opts, false, header.name, fr, w);
413485 }
414486 }
415487 }
......@@ -418,6 +490,7 @@ const coff = struct {
418490 gpa: std.mem.Allocator,
419491 opts: *const Options,
420492 is_image: bool,
493 obj_name: []const u8,
421494 fr: *Io.File.Reader,
422495 w: *Io.Writer,
423496 ) !void {
......@@ -432,6 +505,11 @@ const coff = struct {
432505 try w.writeByte('\n');
433506 }
434507
508 switch (header.machine) {
509 _ => return failParse(opts, "unknown machine type: {x}", .{header.machine}),
510 else => {},
511 }
512
435513 if (header.size_of_optional_header > 0) opt_header: {
436514 if (!opts.file_headers) {
437515 try fr.seekBy(header.size_of_optional_header);
......@@ -541,59 +619,65 @@ const coff = struct {
541619 try w.writeByte('\n');
542620 }
543621
544 var sections: std.ArrayList(std.coff.SectionHeader) = .empty;
622 var sections: std.ArrayList(struct {
623 header: std.coff.SectionHeader,
624 name: []const u8,
625 }) = .empty;
545626 defer sections.deinit(gpa);
546627
547 const load_sections = opts.section_headers or opts.symbols;
628 const load_sections =
629 opts.section_headers or
630 opts.symbols or
631 opts.relocs;
632
548633 if (load_sections) {
549634 if (opts.section_headers)
550 try w.writeAll(
551 \\Section Table:
635 try w.print(
636 \\Sections ({s}):
552637 \\Num Name RVA Virt Size Data Size & Data & Relocs & Lines # Relocs # Lines Flags
553638 \\
554 );
639 , .{obj_name});
555640
556641 try sections.resize(gpa, header.number_of_sections);
557642 for (sections.items, 0..) |*section, section_i| {
558 section.* = r.takeStruct(std.coff.SectionHeader, .little) catch |err|
643 section.header = r.takeStruct(std.coff.SectionHeader, .little) catch |err|
559644 return failParse(opts, "unable to read section header {x}: {t}", .{ section_i, err });
645 section.name = headerName(&section.header.name, string_table) catch |err| switch (err) {
646 error.Overflow,
647 error.InvalidCharacter,
648 => return failParse(opts, "unable to parse section name offset '{s}': {t}", .{
649 section.name,
650 err,
651 }),
652 error.OutOfBounds => return failParse(opts, "section name offset '{s}' was out of bounds (>= {x})", .{
653 section.name,
654 string_table.len,
655 }),
656 };
560657
561658 if (opts.section_headers) {
562 const name = headerName(&section.name, string_table) catch |err| switch (err) {
563 error.Overflow,
564 error.InvalidCharacter,
565 => return failParse(opts, "unable to parse section name offset '{s}': {t}", .{
566 section.name,
567 err,
568 }),
569 error.OutOfBounds => return failParse(opts, "section name offset '{s}' was out of bounds (>= {x})", .{
570 section.name,
571 string_table.len,
572 }),
573 };
574
575 if (!filterMatches(opts.section_filters, name)) continue;
576 const raw_name = std.mem.sliceTo(&section.name, 0);
659 if (!filterMatches(opts.section_filters, section.name)) continue;
660 const raw_name = std.mem.sliceTo(&section.header.name, 0);
577661 try w.print(
578662 "{x: >3} {s: <8} {x: >8} {x: >9} {x: >9} {x: >8} {x: >8} {x: >8} {x: >8} {x: >8} {x:0>8} | ",
579663 .{
580664 section_i + 1,
581665 raw_name,
582 section.virtual_address,
583 section.virtual_size,
584 section.size_of_raw_data,
585 section.pointer_to_raw_data,
586 section.pointer_to_relocations,
587 section.pointer_to_linenumbers,
588 section.number_of_relocations,
589 section.number_of_linenumbers,
590 @as(u32, @bitCast(section.flags)),
666 section.header.virtual_address,
667 section.header.virtual_size,
668 section.header.size_of_raw_data,
669 section.header.pointer_to_raw_data,
670 section.header.pointer_to_relocations,
671 section.header.pointer_to_linenumbers,
672 section.header.number_of_relocations,
673 section.header.number_of_linenumbers,
674 @as(u32, @bitCast(section.header.flags)),
591675 },
592676 );
593677
594 try dumpFlags(w, "{s} ", std.coff.SectionHeader.Flags, &section.flags, 0);
595 if (name.len > 8)
596 try w.print("| {s}", .{name});
678 try dumpFlags(w, "{s} ", std.coff.SectionHeader.Flags, &section.header.flags, 1);
679 if (section.name.len > 8)
680 try w.print("| {s}", .{section.name});
597681
598682 try w.writeByte('\n');
599683 }
......@@ -602,16 +686,21 @@ const coff = struct {
602686 if (opts.section_headers) try w.writeByte('\n');
603687 }
604688
689 var symbol_names: std.ArrayList([]const u8) = .empty;
690 defer symbol_names.deinit(gpa);
691 if (opts.relocs)
692 try symbol_names.ensureUnusedCapacity(gpa, header.number_of_symbols);
693
605694 if (opts.symbols) {
606695 if (header.pointer_to_symbol_table > 0) {
607696 fr.seekTo(file_location + header.pointer_to_symbol_table) catch |err|
608697 return failParse(opts, "unable to seek to symbol table: {t}", .{err});
609698
610 try w.writeAll(
611 \\Symbol Table:
699 try w.print(
700 \\Symbols ({s}):
612701 \\ Ord Value Sect Type Storage Name
613702 \\
614 );
703 , .{obj_name});
615704
616705 const symbol_size = std.coff.Symbol.sizeOf();
617706 var symbol_i: u32 = 0;
......@@ -633,10 +722,17 @@ const coff = struct {
633722 const name = std.mem.sliceTo(if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: {
634723 const index = std.mem.readInt(u32, symbol.name[4..], .little);
635724 if (index >= string_table.len)
636 return failParse(opts, "invalid name offset for symbol {x} ({x} >= {x})", .{ symbol_i, index, string_table.len });
725 return failParse(opts, "invalid name offset for symbol {x} ({x} >= {x})", .{
726 symbol_i,
727 index,
728 string_table.len,
729 });
637730 break :name string_table[index..];
638731 } else &symbol.name, 0);
639732
733 if (opts.relocs)
734 symbol_names.appendNTimesAssumeCapacity(name, 1 + symbol.number_of_aux_symbols);
735
640736 try w.print("{x: >4} {x:0>8} ", .{ symbol_i, symbol.value });
641737 try switch (symbol.section_number) {
642738 .UNDEFINED => w.writeAll("UNDEF"),
......@@ -743,18 +839,18 @@ const coff = struct {
743839 std.mem.byteSwapAllFields(std.coff.SectionDefinition, &section_def);
744840
745841 const section = &sections.items[section_i];
746 if (section_def.number_of_relocations != section.number_of_relocations) {
842 if (section_def.number_of_relocations != section.header.number_of_relocations) {
747843 try w.print(
748844 " !! relocation count did not match section header: {d} vs {d}",
749 .{ section_def.number_of_relocations, section.number_of_relocations },
845 .{ section_def.number_of_relocations, section.header.number_of_relocations },
750846 );
751847 continue;
752848 }
753849
754 if (section_def.number_of_linenumbers != section.number_of_linenumbers) {
850 if (section_def.number_of_linenumbers != section.header.number_of_linenumbers) {
755851 try w.print(
756852 " !! line number count did not match section header: {d} vs {d}",
757 .{ section_def.number_of_linenumbers, section.number_of_linenumbers },
853 .{ section_def.number_of_linenumbers, section.header.number_of_linenumbers },
758854 );
759855 continue;
760856 }
......@@ -786,6 +882,52 @@ const coff = struct {
786882 try w.writeAll("No symbol table found\n");
787883 }
788884 }
885
886 if (opts.relocs) {
887 const relocation_size = std.coff.Relocation.sizeOf();
888
889 for (sections.items, 0..) |section, section_i| {
890 if (section.header.pointer_to_relocations == 0) continue;
891
892 try w.print(
893 \\Relocs for section {x} '{s}' in {s}:
894 \\ Offset Type Symbol Name
895 \\
896 , .{ section_i + 1, section.name, obj_name });
897
898 fr.seekTo(file_location + section.header.pointer_to_relocations) catch |err|
899 return failParse(opts, "unable to seek to section {x} relocation table: {t}", .{ section_i + 1, err });
900
901 for (0..section.header.number_of_relocations) |reloc_i| {
902 var reloc: std.coff.Relocation = undefined;
903 @memcpy(std.mem.asBytes(&reloc)[0..relocation_size], try r.take(relocation_size));
904 if (native_endian != .little)
905 std.mem.byteSwapAllFields(std.coff.Relocation, &reloc);
906
907 try w.print("{x:0>8} ", .{reloc.virtual_address});
908 switch (header.machine) {
909 _ => unreachable,
910 inline else => |m| switch (m.RelocationType()) {
911 void => try w.writeAll("(unknown arch)"),
912 else => |RelocationType| try w.print(
913 "{t: <17} ",
914 .{@as(RelocationType, @enumFromInt(reloc.type))},
915 ),
916 },
917 }
918
919 if (reloc.symbol_table_index >= symbol_names.items.len)
920 return failParse(
921 opts,
922 "reloc {x} in section {x} has out-of-bounds symbol index {x}",
923 .{ reloc_i, section_i + 1, reloc.symbol_table_index },
924 );
925
926 try w.print("{x: >8} | {s}\n", .{ reloc.symbol_table_index, symbol_names.items[reloc.symbol_table_index] });
927 }
928 try w.writeByte('\n');
929 }
930 }
789931 }
790932
791933 fn headerName(raw: *const [8]u8, string_table: []const u8) ![]const u8 {
......@@ -903,12 +1045,14 @@ const usage =
9031045 \\ -h, --help Print this help and exit
9041046 \\ --all-headers Alias for --file-headers --member-headers --section-headers --relocs --symbols
9051047 \\ --file-headers Display file-format specific headers
906 \\ --linker-member[=1|2] (Coff) Display contents of the specified linker member (default 2)
1048 \\ --imports Display imported symbols
1049 \\ --exports Display exported symbols
1050 \\ --linker-member[=1|2|longnames] (Coff) Display contents of the specified linker member (default 2)
9071051 \\ --member-headers Display archive member headers
9081052 \\ --only-member=[name] Only consider archive members that contain [name]. Can be specified multiple times.
9091053 \\ --only-section=[name] Only consider sections that contain [name]. Can be specified multiple times.
9101054 \\ --section-headers Display section headers
911 \\ --strings Display string table
1055 \\ --strings Display string tables
9121056 \\ --symbols Display symbol tables
9131057 \\ --relocs Display relocations
9141058;
lib/std/coff.zig+30
......@@ -1520,6 +1520,36 @@ pub const IMAGE = struct {
15201520 _,
15211521 /// AXP 64 (Same as Alpha 64)
15221522 pub const AXP64: IMAGE.FILE.MACHINE = .ALPHA64;
1523
1524 pub fn RelocationType(comptime machine: IMAGE.FILE.MACHINE) type {
1525 return switch (machine) {
1526 .AMD64,
1527 => REL.AMD64,
1528 .ARM,
1529 .ARMNT,
1530 => REL.ARM,
1531 .ARM64,
1532 .ARM64EC,
1533 .ARM64X,
1534 => REL.ARM64,
1535 .I386 => REL.I386,
1536 .IA64 => REL.IA64,
1537 .M32R => REL.M32R,
1538 .MIPS16,
1539 .MIPSFPU,
1540 .MIPSFPU16,
1541 => REL.MIPS,
1542 .POWERPC,
1543 .POWERPCFP,
1544 => REL.PPC,
1545 .SH3,
1546 .SH3DSP,
1547 .SH4,
1548 .SH5,
1549 => REL.SH,
1550 else => void,
1551 };
1552 }
15231553 };
15241554 };
15251555