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:57-04:00
log62892173839f4741e02b15dea117c9245bfe930b
tree1d8c4b0ef407840ccac3d0ff45e395bef455fadc
parent32377bb73cc70dfd141629eef0e5238b5debeb57

objdump: coff --exports


1 files changed, 294 insertions(+), 87 deletions(-)

lib/compiler/objdump.zig+294-87
......@@ -21,6 +21,7 @@ const Options = struct {
2121 section_headers: bool,
2222 strings: bool,
2323 symbols: bool,
24 tls: bool,
2425
2526 // Coff-specific
2627 linker_member: ?std.coff.ArchiveMemberHeader.Kind,
......@@ -43,6 +44,7 @@ pub fn main(init: std.process.Init) !void {
4344 var opt_section_headers: ?bool = null;
4445 var opt_strings: ?bool = null;
4546 var opt_symbols: ?bool = null;
47 var opt_tls: ?bool = null;
4648 var section_filters: std.ArrayList([]const u8) = .empty;
4749 var member_filters: std.ArrayList([]const u8) = .empty;
4850 while (i < args.len) : (i += 1) {
......@@ -83,6 +85,8 @@ pub fn main(init: std.process.Init) !void {
8385 opt_strings = true;
8486 } else if (mem.eql(u8, arg, "--symbols")) {
8587 opt_symbols = true;
88 } else if (mem.eql(u8, arg, "--tls")) {
89 opt_tls = true;
8690 } else {
8791 fatal("unrecognized argument: {s}", .{arg});
8892 }
......@@ -98,14 +102,15 @@ pub fn main(init: std.process.Init) !void {
98102 .exports = opt_exports orelse false,
99103 .file_headers = opt_file_headers orelse false,
100104 .imports = opt_imports orelse false,
105 .linker_member = opt_linker_member,
106 .member_filters = member_filters.items,
107 .member_headers = opt_member_headers orelse false,
101108 .section_filters = section_filters.items,
102109 .section_headers = opt_section_headers orelse false,
103110 .relocs = opt_relocs orelse false,
104111 .strings = opt_strings orelse false,
105112 .symbols = opt_symbols orelse false,
106 .member_filters = member_filters.items,
107 .member_headers = opt_member_headers orelse false,
108 .linker_member = opt_linker_member,
113 .tls = opt_tls orelse false,
109114 };
110115
111116 var file = std.Io.Dir.cwd().openFile(io, opts.input_path, .{}) catch |err|
......@@ -202,6 +207,21 @@ const wasm = struct {
202207};
203208
204209const coff = struct {
210 const DIRECTORY_ENTRY = std.coff.IMAGE.DIRECTORY_ENTRY;
211
212 const Section = struct {
213 header: std.coff.SectionHeader,
214 name: []const u8,
215
216 fn rvaFileOffset(section: *const Section, rva: u32) !u32 {
217 if (rva < section.header.virtual_address or
218 rva >= section.header.virtual_address + section.header.size_of_raw_data)
219 return error.OutOfBounds;
220
221 return section.header.pointer_to_raw_data + (rva - section.header.virtual_address);
222 }
223 };
224
205225 const ArchiveHeader = struct {
206226 name: []const u8,
207227 date: u40,
......@@ -308,8 +328,6 @@ const coff = struct {
308328 if (!std.mem.eql(u8, header.name, "/"))
309329 return failParse(opts, "expected second linker member, found '{s}'", .{header.name});
310330
311 // TODO: Figure out what endianness is actually used, there are no headers to say yet?
312
313331 const num_members = try r.takeInt(u32, .little);
314332 pos = fr.logicalPos();
315333 if (pos + num_members * @sizeOf(u32) > member_end)
......@@ -511,16 +529,22 @@ const coff = struct {
511529 else => {},
512530 }
513531
514 if (header.size_of_optional_header > 0) opt_header: {
515 if (!opts.file_headers) {
532 var known_dirs: [DIRECTORY_ENTRY.len]std.coff.ImageDataDirectory = undefined;
533 const needs_data_dirs =
534 opts.exports or
535 opts.imports or
536 opts.tls;
537
538 const data_dirs = if (header.size_of_optional_header > 0) data_dirs: {
539 if (!opts.file_headers and !needs_data_dirs) {
516540 try fr.seekBy(header.size_of_optional_header);
517 break :opt_header;
541 break :data_dirs &.{};
518542 }
519543
520 try w.writeAll("COFF Optional Header:\n");
544 if (opts.file_headers) try w.writeAll("COFF Optional Header:\n");
521545 const magic: std.coff.OptionalHeader.Magic = @enumFromInt(try r.peekInt(u16, .little));
522546 const num_directory_entries = switch (magic) {
523 inline .PE32, .@"PE32+" => |v| data_dirs: {
547 inline .PE32, .@"PE32+" => |v| num_data_dirs: {
524548 const OptionalHeader = if (v == .PE32)
525549 std.coff.OptionalHeader.PE32
526550 else
......@@ -529,63 +553,71 @@ const coff = struct {
529553 const optional_header = r.takeStruct(OptionalHeader, .little) catch |err|
530554 return failParse(opts, "unable to read optional header: {t}", .{err});
531555
532 try dumpHeader(w, OptionalHeader, &optional_header, struct {
533 pub fn base_of_code(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void {
534 const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base;
535 try dumpRvaField(cw, @src().fn_name, h.base_of_code, base);
536 }
556 if (opts.file_headers) {
557 try dumpHeader(w, OptionalHeader, &optional_header, struct {
558 pub fn base_of_code(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void {
559 const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base;
560 try dumpRvaField(cw, @src().fn_name, h.base_of_code, base);
561 }
537562
538 pub fn address_of_entry_point(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void {
539 const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base;
540 try dumpRvaField(cw, @src().fn_name, h.base_of_code, base);
541 }
563 pub fn address_of_entry_point(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void {
564 const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base;
565 try dumpRvaField(cw, @src().fn_name, h.base_of_code, base);
566 }
542567
543 pub fn major_linker_version(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void {
544 try dumpVersionField(cw, "linker_version", h.major_linker_version, h.minor_linker_version);
545 }
546 pub fn minor_linker_version(_: *const std.coff.OptionalHeader, _: *Io.Writer) !void {}
547
548 pub fn major_operating_system_version(h: *const OptionalHeader, cw: *Io.Writer) !void {
549 try dumpVersionField(
550 cw,
551 "operating_system_version",
552 h.major_operating_system_version,
553 h.minor_operating_system_version,
554 );
555 }
556 pub fn minor_operating_system_version(_: *const OptionalHeader, _: *Io.Writer) !void {}
568 pub fn major_linker_version(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void {
569 try dumpVersionField(cw, "linker_version", h.major_linker_version, h.minor_linker_version);
570 }
571 pub fn minor_linker_version(_: *const std.coff.OptionalHeader, _: *Io.Writer) !void {}
572
573 pub fn major_operating_system_version(h: *const OptionalHeader, cw: *Io.Writer) !void {
574 try dumpVersionField(
575 cw,
576 "operating_system_version",
577 h.major_operating_system_version,
578 h.minor_operating_system_version,
579 );
580 }
581 pub fn minor_operating_system_version(_: *const OptionalHeader, _: *Io.Writer) !void {}
557582
558 pub fn major_image_version(h: *const OptionalHeader, cw: *Io.Writer) !void {
559 try dumpVersionField(cw, "image_version", h.major_image_version, h.minor_image_version);
560 }
561 pub fn minor_image_version(_: *const OptionalHeader, _: *Io.Writer) !void {}
583 pub fn major_image_version(h: *const OptionalHeader, cw: *Io.Writer) !void {
584 try dumpVersionField(cw, "image_version", h.major_image_version, h.minor_image_version);
585 }
586 pub fn minor_image_version(_: *const OptionalHeader, _: *Io.Writer) !void {}
562587
563 pub fn major_subsystem_version(h: *const OptionalHeader, cw: *Io.Writer) !void {
564 try dumpVersionField(cw, "subsystem_version", h.major_subsystem_version, h.minor_subsystem_version);
565 }
566 pub fn minor_subsystem_version(_: *const OptionalHeader, _: *Io.Writer) !void {}
567 });
568 try w.writeByte('\n');
588 pub fn major_subsystem_version(h: *const OptionalHeader, cw: *Io.Writer) !void {
589 try dumpVersionField(cw, "subsystem_version", h.major_subsystem_version, h.minor_subsystem_version);
590 }
591 pub fn minor_subsystem_version(_: *const OptionalHeader, _: *Io.Writer) !void {}
592 });
593 try w.writeByte('\n');
594 }
569595
570 break :data_dirs optional_header.number_of_rva_and_sizes;
596 break :num_data_dirs optional_header.number_of_rva_and_sizes;
571597 },
572598 else => return failParse(opts, "invalid optional header magic number: {x}", .{magic}),
573599 };
574600
575 try w.writeAll("Data Directories:\n");
601 if (opts.file_headers) try w.writeAll("Data Directories:\n");
576602 for (0..num_directory_entries) |dir_i| {
577603 const dir = r.takeStruct(std.coff.ImageDataDirectory, .little) catch |err|
578604 return failParse(opts, "unable to read data directory {x}: {t}", .{ dir_i, err });
579605
580 try w.print(
581 "{x: >16} {x: >8} {t}\n",
582 .{ dir.virtual_address, dir.size, @as(std.coff.IMAGE.DIRECTORY_ENTRY, @enumFromInt(dir_i)) },
583 );
606 if (dir_i < known_dirs.len)
607 known_dirs[dir_i] = dir;
608
609 if (opts.file_headers)
610 try w.print(
611 "{x: >16} {x: >8} {t}\n",
612 .{ dir.virtual_address, dir.size, @as(DIRECTORY_ENTRY, @enumFromInt(dir_i)) },
613 );
584614 }
585 try w.writeByte('\n');
615 if (opts.file_headers) try w.writeByte('\n');
616
617 break :data_dirs known_dirs[0..@min(known_dirs.len, num_directory_entries)];
586618 } else if (is_image) {
587619 return failParse(opts, "image did not contain an optional header", .{});
588 }
620 } else &.{};
589621
590622 // Section names in images don't use the string table, as they must fit inline in the header
591623 const load_string_table = (opts.strings or !is_image) and header.pointer_to_symbol_table > 0;
......@@ -620,16 +652,15 @@ const coff = struct {
620652 try w.writeByte('\n');
621653 }
622654
623 var sections: std.ArrayList(struct {
624 header: std.coff.SectionHeader,
625 name: []const u8,
626 }) = .empty;
655 var sections: std.ArrayList(Section) = .empty;
627656 defer sections.deinit(gpa);
657 var sections_with_data: u16 = 0;
628658
629659 const load_sections =
630660 opts.section_headers or
631661 opts.symbols or
632 opts.relocs;
662 opts.relocs or
663 needs_data_dirs;
633664
634665 if (load_sections) {
635666 if (opts.section_headers)
......@@ -656,11 +687,12 @@ const coff = struct {
656687 }),
657688 };
658689
690 sections_with_data += @intFromBool(section.header.size_of_raw_data > 0);
659691 if (opts.section_headers) {
660692 if (!filterMatches(opts.section_filters, section.name)) continue;
661693 const raw_name = std.mem.sliceTo(&section.header.name, 0);
662694 try w.print(
663 "{x: >3} {s: <8} {x: >8} {x: >9} {x: >9} {x: >8} {x: >8} {x: >8} {x: >8} {x: >8} {x:0>8} | ",
695 "{x: >3} {s: <8} {x: >8} {x: >9} {x: >9} {x: >8} {x: >8} {x: >8} {x: >8} {x: >8} {x:0>8} |",
664696 .{
665697 section_i + 1,
666698 raw_name,
......@@ -676,7 +708,7 @@ const coff = struct {
676708 },
677709 );
678710
679 try dumpFlags(w, "{s} ", std.coff.SectionHeader.Flags, &section.header.flags, 1);
711 try dumpFlags(w, "{s}", std.coff.SectionHeader.Flags, &section.header.flags, 1);
680712 if (section.name.len > 8)
681713 try w.print("| {s}", .{section.name});
682714
......@@ -790,10 +822,7 @@ const coff = struct {
790822 (std.mem.eql(u8, name, ".bf") or std.mem.eql(u8, name, ".ef")))
791823 {
792824 try w.writeAll("TODO bf / ef aux symbol");
793 } else if (symbol.storage_class == .EXTERNAL and
794 symbol.section_number == .UNDEFINED and
795 symbol.value == 0)
796 {
825 } else if (symbol.storage_class == .WEAK_EXTERNAL and symbol.section_number == .UNDEFINED) {
797826 if (symbol.value != 0)
798827 return failParse(
799828 opts,
......@@ -815,6 +844,10 @@ const coff = struct {
815844
816845 // TODO
817846
847 try w.print(" Weak External [falls back to {x:0>8} via {t}]", .{
848 weak_external.tag_index,
849 weak_external.flag,
850 });
818851 } else if (symbol.storage_class == .FILE) {
819852 if (!std.mem.eql(u8, name, ".file")) {
820853 try w.print(" !! unexpected symbol name '{s}' for file symbol 0x{x}", .{ name, symbol_i });
......@@ -824,6 +857,7 @@ const coff = struct {
824857 var file: std.coff.FileDefinition = undefined;
825858 @memcpy(std.mem.asBytes(&file)[0..symbol_size], aux_symbols[0..symbol_size]);
826859
860 // TODO
827861 _ = file.getFileName();
828862 } else if (symbol.storage_class == .STATIC and
829863 symbol.type == std.coff.SymType{
......@@ -934,16 +968,205 @@ const coff = struct {
934968 .{ reloc_i, section_i + 1, reloc.symbol_table_index },
935969 );
936970
937 try w.print("{x: >8} ", .{reloc.symbol_table_index});
938
939971 const sym = &symbols.items[reloc.symbol_table_index];
940 try sectionNumberString(sym.section_number, w);
941
942 try w.print(" | {s}\n", .{sym.name});
972 try w.print("{x: >8} {f} | {s}\n", .{
973 reloc.symbol_table_index,
974 fmtSectionNumber(sym.section_number),
975 sym.name,
976 });
943977 }
944978 try w.writeByte('\n');
945979 }
946980 }
981
982 // Sections indices with raw data, sorted by RVA
983 const rva_index = if (needs_data_dirs) rva_index: {
984 const rva_index = try gpa.alloc(u16, sections_with_data);
985 var indices_i: u16 = 0;
986 for (sections.items, 0..) |*section, i| {
987 if (section.header.size_of_raw_data == 0) continue;
988 rva_index[indices_i] = @intCast(i);
989 indices_i += 1;
990 }
991
992 const Context = struct {
993 indices: []u16,
994 sections: []const Section,
995
996 pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool {
997 return ctx.sections[ctx.indices[lhs]].header.virtual_address <
998 ctx.sections[ctx.indices[rhs]].header.virtual_address;
999 }
1000
1001 pub fn swap(ctx: @This(), lhs: usize, rhs: usize) void {
1002 std.mem.swap(u16, &ctx.indices[lhs], &ctx.indices[rhs]);
1003 }
1004 };
1005
1006 std.sort.pdqContext(0, rva_index.len, Context{
1007 .indices = rva_index,
1008 .sections = sections.items,
1009 });
1010
1011 break :rva_index rva_index;
1012 } else &.{};
1013 defer gpa.free(rva_index);
1014
1015 if (opts.exports) {
1016 if (try seekToDataDirectory(opts, fr, w, rva_index, sections.items, data_dirs, .EXPORT)) |section_index| {
1017 const export_dir = r.takeStruct(std.coff.ExportDirectoryTable, .little) catch |err|
1018 return failParse(opts, "unable to read export directory: {t}", .{err});
1019
1020 try w.print("Export directory:\n", .{});
1021 try dumpHeader(w, std.coff.ExportDirectoryTable, &export_dir, struct {
1022 pub fn major_version(h: *const std.coff.ExportDirectoryTable, cw: *Io.Writer) !void {
1023 try dumpVersionField(cw, "version", h.major_version, h.minor_version);
1024 }
1025 pub fn minor_version(_: *const std.coff.ExportDirectoryTable, _: *Io.Writer) !void {}
1026 });
1027
1028 const section = sections.items[section_index];
1029 const name_loc = section.rvaFileOffset(export_dir.name_rva) catch
1030 return failParse(
1031 opts,
1032 "export name rva 0x{x} was not within the export section",
1033 .{export_dir.name_rva},
1034 );
1035
1036 const eat_loc = section.rvaFileOffset(export_dir.export_address_table_rva) catch
1037 return failParse(
1038 opts,
1039 "export address table rva 0x{x} was not within the export section",
1040 .{export_dir.export_address_table_rva},
1041 );
1042
1043 const name_pointer_loc = section.rvaFileOffset(export_dir.name_pointer_table_rva) catch
1044 return failParse(
1045 opts,
1046 "export name pointer table rva 0x{x} was not within the export section",
1047 .{export_dir.name_pointer_table_rva},
1048 );
1049
1050 const ord_loc = section.rvaFileOffset(export_dir.ordinal_table_rva) catch
1051 return failParse(
1052 opts,
1053 "export ordinal table rva 0x{x} was not within the export section",
1054 .{export_dir.ordinal_table_rva},
1055 );
1056
1057 // All the variable length fields should be contained within this directory.
1058 // Read it entirely to avoid needing to seek per-name when iterating.
1059 const dir = data_dirs[@intFromEnum(DIRECTORY_ENTRY.EXPORT)];
1060 const dir_end_rva = dir.virtual_address + dir.size;
1061 const dir_loc = fr.logicalPos();
1062 const dir_slice = try r.readAlloc(gpa, dir.size);
1063 defer gpa.free(dir_slice);
1064
1065 const dll_name = std.mem.sliceTo(dir_slice[name_loc - dir_loc ..], 0);
1066 try w.print(
1067 \\
1068 \\Exports from {s}:
1069 \\ Ord Hint RVA Name
1070 \\
1071 , .{dll_name});
1072
1073 const name_pointers = dir_slice[name_pointer_loc - dir_loc ..][0 .. export_dir.number_of_names * @sizeOf(u32)];
1074 const ords = dir_slice[ord_loc - dir_loc ..][0 .. export_dir.number_of_names * @sizeOf(u16)];
1075 const addrs = dir_slice[eat_loc - dir_loc ..][0 .. export_dir.number_of_entries * @sizeOf(u32)];
1076 const name_rva_to_offset = dir.virtual_address + @sizeOf(std.coff.ExportDirectoryTable);
1077 for (0..export_dir.number_of_names) |name_i| {
1078 const name_rva = std.mem.readInt(u32, name_pointers[name_i * @sizeOf(u32) ..][0..@sizeOf(u32)], .little);
1079 const ord = std.mem.readInt(u16, ords[name_i * @sizeOf(u16) ..][0..@sizeOf(u16)], .little);
1080 const addr = std.mem.readInt(u32, addrs[@as(u32, ord) * @sizeOf(u32) ..][0..@sizeOf(u32)], .little);
1081
1082 try w.print("{x: >4} {x: >4} ", .{ export_dir.ordinal_base + ord, name_i });
1083 const is_forwarder = addr >= dir.virtual_address and addr < dir_end_rva;
1084 if (is_forwarder) {
1085 try w.writeAll("forwards");
1086 } else {
1087 try w.print("{x: >8}", .{addr});
1088 }
1089
1090 const name = std.mem.sliceTo(dir_slice[name_rva - name_rva_to_offset ..], 0);
1091 try w.print(" | {s}", .{name});
1092 if (is_forwarder)
1093 try w.print(" -> {s}", .{std.mem.sliceTo(dir_slice[addr - name_rva_to_offset ..], 0)});
1094 try w.writeByte('\n');
1095 }
1096 }
1097 }
1098
1099 if (opts.imports) {
1100 if (try seekToDataDirectory(opts, fr, w, rva_index, sections.items, data_dirs, .IMPORT)) |_| {
1101 // TODO
1102 }
1103 }
1104
1105 if (opts.tls) {
1106 if (try seekToDataDirectory(opts, fr, w, rva_index, sections.items, data_dirs, .TLS)) |_| {
1107 // TODO
1108 }
1109 }
1110 }
1111
1112 fn seekToDataDirectory(
1113 opts: *const Options,
1114 fr: *Io.File.Reader,
1115 w: *Io.Writer,
1116 rva_index: []const u16,
1117 sections: []const Section,
1118 data_dirs: []const std.coff.ImageDataDirectory,
1119 entry: DIRECTORY_ENTRY,
1120 ) !?u16 {
1121 if (@intFromEnum(entry) < data_dirs.len) {
1122 const rva = data_dirs[@intFromEnum(entry)].virtual_address;
1123 const section_index = sectionContainingRva(rva_index, sections, rva) orelse
1124 return failParse(
1125 opts,
1126 "{t} directory rva 0x{x} was not found in any section",
1127 .{ entry, rva },
1128 );
1129
1130 const file_offset = sections[section_index].rvaFileOffset(rva) catch unreachable;
1131 fr.seekTo(file_offset) catch |err|
1132 return failParse(
1133 opts,
1134 "unable to seek to {t} directory at offset 0x{x}: {t}",
1135 .{ entry, file_offset, err },
1136 );
1137
1138 return section_index;
1139 }
1140
1141 try w.print("{t} directory was not present in optional header\n", .{entry});
1142 return null;
1143 }
1144
1145 fn sectionContainingRva(
1146 /// Indices into `sections` sorted by rva
1147 indices: []const u16,
1148 sections: []const Section,
1149 rva: u32,
1150 ) ?u16 {
1151 const Context = struct {
1152 rva: u32,
1153 sections: []const Section,
1154
1155 fn order(ctx: @This(), section_index: u16) std.math.Order {
1156 const h = &ctx.sections[section_index].header;
1157 const start = h.virtual_address;
1158 if (ctx.rva < start) return .lt;
1159 const end = h.virtual_address + h.size_of_raw_data;
1160 if (ctx.rva >= end) return .gt;
1161 return .eq;
1162 }
1163 };
1164
1165 const index = std.sort.binarySearch(u16, indices, Context{
1166 .rva = rva,
1167 .sections = sections,
1168 }, Context.order) orelse return null;
1169 return @intCast(index);
9471170 }
9481171
9491172 fn headerName(raw: *const [8]u8, string_table: []const u8) ![]const u8 {
......@@ -956,21 +1179,6 @@ const coff = struct {
9561179 } else std.mem.sliceTo(raw, 0);
9571180 }
9581181
959 fn fmtSymbolType(sym_type: std.coff.SymType) std.fmt.Alt(std.coff.SymType, symbolTypeString) {
960 return .{ .data = sym_type };
961 }
962
963 fn symbolTypeString(sym_type: std.coff.SymType, w: *std.Io.Writer) std.Io.Writer.Error!void {
964 try w.print("{t: >5}", .{sym_type.base_type});
965 if (try switch (sym_type.complex_type) {
966 .NULL => " ",
967 .POINTER => "* ",
968 .FUNCTION => "()",
969 .ARRAY => "[]",
970 else => null,
971 }) |suffix| try .printAll(suffix) else w.print("{x}", .{sym_type.complex_type});
972 }
973
9741182 fn fmtSectionNumber(section_number: std.coff.SectionNumber) std.fmt.Alt(std.coff.SectionNumber, sectionNumberString) {
9751183 return .{ .data = section_number };
9761184 }
......@@ -1003,8 +1211,6 @@ const coff = struct {
10031211
10041212 fn dumpArchiveHeader(w: *Io.Writer, header: *const ArchiveHeader, pos: u32) !void {
10051213 try w.print("Archive member at offset 0x{x}: '{s}'\n", .{ pos, header.name });
1006
1007 // TODO: Date formatter
10081214 try dumpHeader(w, ArchiveHeader, header, struct {
10091215 pub fn name(_: *const ArchiveHeader, _: *Io.Writer) !void {}
10101216 pub fn file_mode(h: *const ArchiveHeader, cw: *Io.Writer) !void {
......@@ -1065,10 +1271,11 @@ const usage =
10651271 \\ --exports Display exported symbols
10661272 \\ --linker-member[=1|2|longnames] (Coff) Display contents of the specified linker member (default 2)
10671273 \\ --member-headers Display archive member headers
1068 \\ --only-member=[name] Only consider archive members that contain [name]. Can be specified multiple times.
1069 \\ --only-section=[name] Only consider sections that contain [name]. Can be specified multiple times.
1274 \\ --only-member=[name] Only consider archive members names that contain [name]. Can be specified multiple times.
1275 \\ --only-section=[name] Only consider section names that contain [name]. Can be specified multiple times.
1276 \\ --relocs Display relocations
10701277 \\ --section-headers Display section headers
10711278 \\ --strings Display string tables
10721279 \\ --symbols Display symbol tables
1073 \\ --relocs Display relocations
1280 \\ --tls Display TLS information
10741281;