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
log601da46f154678e4a28f9c5496376deadb78d718
tree8b9f5f20fddbc54c53447b48f9f7058199857959
parent5d8f8b571e245a9a4d72922d3939d2b1def0fb51

objdump: initial COFF implementation


3 files changed, 567 insertions(+), 23 deletions(-)

lib/compiler/objdump.zig+552-9
...@@ -4,19 +4,60 @@ const fatal = std.process.fatal;...@@ -4,19 +4,60 @@ const fatal = std.process.fatal;
4const mem = std.mem;4const mem = std.mem;
5const assert = std.debug.assert;5const assert = std.debug.assert;
66
7const builtin = @import("builtin");
8const native_endian = builtin.cpu.arch.endian();
9
7var stdout_buffer: [4000]u8 = undefined;10var stdout_buffer: [4000]u8 = undefined;
811
12const Options = struct {
13 input_path: []const u8,
14 file_headers: bool,
15 section_filters: []const []const u8 = &.{},
16 section_table: bool,
17 strings: bool,
18 symbols: bool,
19 compact: bool,
20};
21
9pub fn main(init: std.process.Init) !void {22pub fn main(init: std.process.Init) !void {
10 const io = init.io;23 const io = init.io;
11 const args = try init.minimal.args.toSlice(init.arena.allocator());24 const args = try init.minimal.args.toSlice(init.arena.allocator());
25 const arena = init.arena.allocator();
1226
13 var opt_input_path: ?[]const u8 = null;
14 var i: usize = 1;27 var i: usize = 1;
28
29 var opt_input_path: ?[]const u8 = null;
30 var opt_file_headers: ?bool = null;
31 var opt_section_table: ?bool = null;
32 var opt_strings: ?bool = null;
33 var opt_symbols: ?bool = null;
34 var opt_relocs: ?bool = null;
35 var opt_compact: ?bool = null;
36 var section_filters: std.ArrayList([]const u8) = .empty;
15 while (i < args.len) : (i += 1) {37 while (i < args.len) : (i += 1) {
16 const arg = args[i];38 const arg = args[i];
17 if (mem.startsWith(u8, arg, "-")) {39 if (mem.startsWith(u8, arg, "-")) {
18 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {40 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
19 return Io.File.stdout().writeStreamingAll(io, usage);41 return Io.File.stdout().writeStreamingAll(io, usage);
42 } else if (mem.eql(u8, arg, "--all-headers")) {
43 opt_file_headers = true;
44 opt_section_table = true;
45 opt_symbols = true;
46 opt_relocs = true;
47 } else if (mem.eql(u8, arg, "--compact")) {
48 opt_compact = true;
49 } else if (mem.eql(u8, arg, "--file-headers")) {
50 opt_file_headers = true;
51 } else if (mem.startsWith(u8, arg, "--only-section=")) {
52 (try section_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-section=".len..]);
53 } else if (mem.eql(u8, arg, "--relocs")) {
54 opt_relocs = true;
55 } else if (mem.eql(u8, arg, "--section-headers")) {
56 opt_section_table = true;
57 } else if (mem.eql(u8, arg, "--strings")) {
58 opt_strings = true;
59 } else if (mem.eql(u8, arg, "--symbols")) {
60 opt_symbols = true;
20 } else {61 } else {
21 fatal("unrecognized argument: {s}", .{arg});62 fatal("unrecognized argument: {s}", .{arg});
22 }63 }
...@@ -27,25 +68,35 @@ pub fn main(init: std.process.Init) !void {...@@ -27,25 +68,35 @@ pub fn main(init: std.process.Init) !void {
27 }68 }
28 }69 }
2970
30 const input_path = opt_input_path orelse fatal("missing input file path positional argument", .{});71 const opts: Options = .{
72 .input_path = opt_input_path orelse fatal("missing input file path positional argument", .{}),
73 .compact = opt_compact orelse false,
74 .file_headers = opt_file_headers orelse false,
75 .section_filters = section_filters.items,
76 .section_table = opt_section_table orelse false,
77 .strings = opt_strings orelse false,
78 .symbols = opt_symbols orelse false,
79 };
3180
32 var file = std.Io.Dir.cwd().openFile(io, input_path, .{}) catch |err|81 var file = std.Io.Dir.cwd().openFile(io, opts.input_path, .{}) catch |err|
33 fatal("failed to open {s}: {t}", .{ input_path, err });82 fatal("failed to open {s}: {t}", .{ opts.input_path, err });
34 defer file.close(io);83 defer file.close(io);
3584
36 var buffer: [4000]u8 = undefined;85 var buffer: [4096]u8 = undefined;
37 var file_reader = file.reader(io, &buffer);86 var file_reader = file.reader(io, &buffer);
38 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &stdout_buffer);87 var stdout_writer = std.Io.File.stdout().writerStreaming(io, &stdout_buffer);
39 dump(&file_reader.interface, &stdout_writer.interface) catch |err| switch (err) {88 dump(arena, &opts, &file_reader, &stdout_writer.interface) catch |err| switch (err) {
40 error.ReadFailed => return file_reader.err.?,89 error.ReadFailed => return file_reader.err.?,
41 error.WriteFailed => return stdout_writer.err.?,90 error.WriteFailed => return stdout_writer.err.?,
42 error.UnknownFile => fatal("unrecognized file: {s}", .{input_path}),91 error.UnknownFile => fatal("unrecognized file: {s}", .{opts.input_path}),
92 error.ParseFailure => {},
43 else => |e| return e,93 else => |e| return e,
44 };94 };
45 try stdout_writer.flush();95 try stdout_writer.flush();
46}96}
4797
48fn dump(r: *Io.Reader, w: *Io.Writer) !void {98fn dump(arena: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void {
99 const r = &fr.interface;
49 try r.fill(4);100 try r.fill(4);
50 elf: {101 elf: {
51 if (!mem.eql(u8, r.buffered()[0..4], std.elf.MAGIC)) break :elf;102 if (!mem.eql(u8, r.buffered()[0..4], std.elf.MAGIC)) break :elf;
...@@ -60,9 +111,44 @@ fn dump(r: *Io.Reader, w: *Io.Writer) !void {...@@ -60,9 +111,44 @@ fn dump(r: *Io.Reader, w: *Io.Writer) !void {
60 if (!mem.eql(u8, r.buffered()[0..4], &std.wasm.magic)) break :wasm;111 if (!mem.eql(u8, r.buffered()[0..4], &std.wasm.magic)) break :wasm;
61 return wasm.dump(r, w);112 return wasm.dump(r, w);
62 }113 }
114 coff: {
115 const ext = std.fs.path.extension(opts.input_path);
116 if (std.mem.eql(u8, ext, ".exe") or std.mem.eql(u8, ext, ".dll")) {
117 if (!mem.eql(u8, r.buffered()[0..2], "MZ")) break :coff;
118 try r.discardAll(std.coff.pe_pointer_offset);
119 const sig_offset = try r.takeInt(u32, .little);
120 try fr.seekTo(sig_offset);
121 const sig = try r.take(4);
122
123 if (!std.mem.eql(u8, sig, std.coff.pe_signature)) {
124 try w.print("invalid PE signature: {x}", .{sig});
125 return error.ParseFailure;
126 }
127
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);
130 } else if (std.mem.eql(u8, ext, ".lib")) {
131 r.fill(std.coff.archive_signature.len) catch break :coff;
132 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);
135 } 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);
138 }
139 }
63 return error.UnknownFile;140 return error.UnknownFile;
64}141}
65142
143fn failParse(
144 opts: *const Options,
145 comptime fmt: []const u8,
146 args: anytype,
147) noreturn {
148 std.log.err("error parsing '{s}'", .{std.fs.path.basename(opts.input_path)});
149 fatal(fmt, args);
150}
151
66const elf = struct {152const elf = struct {
67 fn dump(r: *Io.Reader, w: *Io.Writer) !void {153 fn dump(r: *Io.Reader, w: *Io.Writer) !void {
68 _ = r;154 _ = r;
...@@ -84,10 +170,467 @@ const wasm = struct {...@@ -84,10 +170,467 @@ const wasm = struct {
84 }170 }
85};171};
86172
173const 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 }
179
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;
185
186 break :name std.mem.sliceTo(string_table[name_offset..], 0);
187 } else std.mem.sliceTo(raw, 0);
188 }
189
190 fn dumpObject(arena: std.mem.Allocator, opts: *const Options, is_image: bool, fr: *Io.File.Reader, w: *Io.Writer) !void {
191 const r = &fr.interface;
192 const header = r.takeStruct(std.coff.Header, .little) catch |err|
193 return failParse(opts, "unable to read COFF header: {t}", .{err});
194
195 if (opts.file_headers) {
196 if (!opts.compact) try w.writeAll("COFF Header:\n");
197 try dumpHeader(w, std.coff.Header, &header, struct {});
198 if (!opts.compact) try w.writeByte('\n');
199 }
200
201 if (header.size_of_optional_header > 0) opt_header: {
202 if (!opts.file_headers) {
203 try fr.seekBy(header.size_of_optional_header);
204 break :opt_header;
205 }
206
207 if (!opts.compact) try w.writeAll("COFF Optional Header:\n");
208 const magic: std.coff.OptionalHeader.Magic = @enumFromInt(try r.peekInt(u16, .little));
209 const num_directory_entries = switch (magic) {
210 inline .PE32, .@"PE32+" => |v| data_dirs: {
211 const OptionalHeader = if (v == .PE32)
212 std.coff.OptionalHeader.PE32
213 else
214 std.coff.OptionalHeader.@"PE32+";
215
216 const optional_header = r.takeStruct(OptionalHeader, .little) catch |err|
217 return failParse(opts, "unable to read optional header: {t}", .{err});
218
219 try dumpHeader(w, OptionalHeader, &optional_header, struct {
220 pub fn base_of_code(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void {
221 const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base;
222 try dumpRvaField(cw, @src().fn_name, h.base_of_code, base);
223 }
224
225 pub fn address_of_entry_point(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void {
226 const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base;
227 try dumpRvaField(cw, @src().fn_name, h.base_of_code, base);
228 }
229
230 pub fn major_linker_version(h: *const std.coff.OptionalHeader, cw: *Io.Writer) !void {
231 try dumpVersionField(cw, "linker_version", h.major_linker_version, h.minor_linker_version);
232 }
233 pub fn minor_linker_version(_: *const std.coff.OptionalHeader, _: *Io.Writer) !void {}
234
235 pub fn major_operating_system_version(h: *const OptionalHeader, cw: *Io.Writer) !void {
236 try dumpVersionField(
237 cw,
238 "operating_system_version",
239 h.major_operating_system_version,
240 h.minor_operating_system_version,
241 );
242 }
243 pub fn minor_operating_system_version(_: *const OptionalHeader, _: *Io.Writer) !void {}
244
245 pub fn major_image_version(h: *const OptionalHeader, cw: *Io.Writer) !void {
246 try dumpVersionField(cw, "image_version", h.major_image_version, h.minor_image_version);
247 }
248 pub fn minor_image_version(_: *const OptionalHeader, _: *Io.Writer) !void {}
249
250 pub fn major_subsystem_version(h: *const OptionalHeader, cw: *Io.Writer) !void {
251 try dumpVersionField(cw, "subsystem_version", h.major_subsystem_version, h.minor_subsystem_version);
252 }
253 pub fn minor_subsystem_version(_: *const OptionalHeader, _: *Io.Writer) !void {}
254 });
255 if (!opts.compact) try w.writeByte('\n');
256
257 break :data_dirs optional_header.number_of_rva_and_sizes;
258 },
259 else => return failParse(opts, "invalid optional header magic number: {x}", .{magic}),
260 };
261
262 if (!opts.compact) try w.writeAll("Data Directories:\n");
263 for (0..num_directory_entries) |dir_i| {
264 const dir = r.takeStruct(std.coff.ImageDataDirectory, .little) catch |err|
265 return failParse(opts, "unable to read data directory {x}: {t}", .{ dir_i, err });
266
267 try w.print(
268 "{x: >16} {x: >8} {t}\n",
269 .{ dir.virtual_address, dir.size, @as(std.coff.IMAGE.DIRECTORY_ENTRY, @enumFromInt(dir_i)) },
270 );
271 }
272 if (!opts.compact) try w.writeByte('\n');
273 } else if (is_image) {
274 return failParse(opts, "image did not contain an optional header", .{});
275 }
276
277 // Section names in images don't use the string table, as they must fit inline in the header
278 const load_string_table = (opts.strings or !is_image) and header.pointer_to_symbol_table > 0;
279
280 const string_table = if (load_string_table) string_table: {
281 const pos = fr.logicalPos();
282 fr.seekTo(header.pointer_to_symbol_table + header.number_of_symbols * std.coff.Symbol.sizeOf()) catch |err|
283 return failParse(opts, "unable to seek to string table: {t}", .{err});
284
285 const string_table_len = r.peekInt(u32, .little) catch |err|
286 return failParse(opts, "unable to read string table length: {t}", .{err});
287
288 const table = r.readAlloc(arena, string_table_len) catch |err|
289 return failParse(opts, "unable to read string table: {t}", .{err});
290
291 try fr.seekTo(pos);
292 break :string_table table;
293 } else &.{};
294
295 var sections: std.ArrayList(std.coff.SectionHeader) = .empty;
296 const load_sections = opts.section_table or opts.symbols;
297 if (load_sections) {
298 if (!opts.compact and opts.section_table)
299 try w.writeAll(
300 \\Section Table:
301 \\Num Name RVA Virtual Size Data Size File Offset Relocs Offset Lines Offset # Relocs # Lines Flags
302 \\
303 );
304
305 try sections.resize(arena, header.number_of_sections);
306 for (sections.items, 0..) |*section, section_i| {
307 section.* = r.takeStruct(std.coff.SectionHeader, .little) catch |err|
308 return failParse(opts, "unable to read section header {x}: {t}", .{ section_i, err });
309
310 if (opts.section_table) {
311 const name = headerName(&section.name, string_table) catch |err| switch (err) {
312 error.Overflow,
313 error.InvalidCharacter,
314 => return failParse(opts, "unable to parse section name offset '{s}': {t}", .{
315 section.name,
316 err,
317 }),
318 error.OutOfBounds => return failParse(opts, "section name offset '{s}' was out of bounds (>= {x})", .{
319 section.name,
320 string_table.len,
321 }),
322 };
323
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
329 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} ",
331 .{
332 section_i + 1,
333 std.mem.sliceTo(&section.name, 0),
334 section.virtual_address,
335 section.virtual_size,
336 section.size_of_raw_data,
337 section.pointer_to_raw_data,
338 section.pointer_to_relocations,
339 section.pointer_to_linenumbers,
340 section.number_of_relocations,
341 section.number_of_linenumbers,
342 @as(u32, @bitCast(section.flags)),
343 },
344 );
345
346 if (name.len > 8)
347 try w.print(" | {s}", .{name});
348
349 try dumpFlags(w, "{s} ", std.coff.SectionHeader.Flags, &section.flags, 0);
350 try w.writeByte('\n');
351 }
352 }
353
354 if (!opts.compact and opts.section_table) try w.writeByte('\n');
355 }
356
357 if (opts.symbols) {
358 if (header.pointer_to_symbol_table > 0) {
359 fr.seekTo(header.pointer_to_symbol_table) catch |err|
360 return failParse(opts, "unable to seek to symbol table: {t}", .{err});
361
362 if (!opts.compact and opts.symbols)
363 try w.writeAll(
364 \\Symbol Table:
365 \\ Ord Value Sect Type Storage Name
366 \\
367 );
368
369 const symbol_size = std.coff.Symbol.sizeOf();
370 var symbol_i: u32 = 0;
371 while (symbol_i < header.number_of_symbols) {
372 var symbol: std.coff.Symbol = undefined;
373 const symbol_bytes = r.take(symbol_size) catch |err|
374 return failParse(opts, "unable to read symbol {x}: {t}", .{ symbol_i, err });
375
376 @memcpy(std.mem.asBytes(&symbol)[0..symbol_size], symbol_bytes);
377 if (native_endian != .little)
378 std.mem.byteSwapAllFields(std.coff.Symbol, &symbol);
379
380 const aux_symbols = if (symbol.number_of_aux_symbols > 0)
381 try r.take(symbol_size * symbol.number_of_aux_symbols)
382 else
383 &.{};
384 defer symbol_i += symbol.number_of_aux_symbols + 1;
385
386 const name = std.mem.sliceTo(if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: {
387 const index = std.mem.readInt(u32, symbol.name[4..], .little);
388 if (index >= string_table.len)
389 return failParse(opts, "invalid name offset for symbol {x} ({x} >= {x})", .{ symbol_i, index, string_table.len });
390 break :name string_table[index..];
391 } else &symbol.name, 0);
392
393 try w.print("{x:0>4} {x:0>8} ", .{ symbol_i, symbol.value });
394 try switch (symbol.section_number) {
395 .UNDEFINED => w.writeAll("UNDEF"),
396 .ABSOLUTE => w.writeAll(" ABS"),
397 .DEBUG => w.writeAll("DEBUG"),
398 else => |v| {
399 const backing = @intFromEnum(v);
400 const fmt = "{x: >5}";
401 if (backing >= 0)
402 try w.print(fmt, .{@as(u15, @intCast(backing))})
403 else
404 try w.print(fmt, .{backing});
405 },
406 };
407
408 try w.print("{t: >5}", .{symbol.type.base_type});
409 if (switch (symbol.type.complex_type) {
410 .NULL => " ",
411 .POINTER => "* ",
412 .FUNCTION => "()",
413 .ARRAY => "[]",
414 else => null,
415 }) |suffix| try w.writeAll(suffix) else try w.print("{x}", .{symbol.type.complex_type});
416
417 try w.print("{t: >16} | {s}", .{ symbol.storage_class, name });
418 try w.writeByte('\n');
419
420 for (0..symbol.number_of_aux_symbols) |aux_i| {
421 _ = aux_i;
422 try w.writeAll(" AUX");
423
424 if (symbol.storage_class == .EXTERNAL and
425 symbol.type == std.coff.SymType{
426 .complex_type = .FUNCTION,
427 .base_type = .NULL,
428 } and
429 @intFromEnum(symbol.section_number) > 0)
430 {
431 try w.writeAll("TODO function aux symbol");
432 } else if (symbol.type == std.coff.SymType{
433 .complex_type = .FUNCTION,
434 .base_type = .NULL,
435 } and
436 (std.mem.eql(u8, name, ".bf") or std.mem.eql(u8, name, ".ef")))
437 {
438 try w.writeAll("TODO bf / ef aux symbol");
439 } else if (symbol.storage_class == .EXTERNAL and
440 symbol.section_number == .UNDEFINED and
441 symbol.value == 0)
442 {
443 if (symbol.value != 0)
444 return failParse(
445 opts,
446 "invalid value 0x{x} for weak external symbol 0x{x}",
447 .{ symbol.value, symbol_i },
448 );
449
450 var weak_external: std.coff.WeakExternalDefinition = undefined;
451 @memcpy(std.mem.asBytes(&weak_external)[0..symbol_size], aux_symbols[0..symbol_size]);
452 if (native_endian != .little)
453 std.mem.byteSwapAllFields(std.coff.SectionDefinition, &weak_external);
454
455 if (weak_external.tag_index >= header.number_of_symbols)
456 return failParse(
457 opts,
458 "invalid tag_index 0x{x} for weak external symbol 0x{x}",
459 .{ weak_external.tag_index, symbol_i },
460 );
461
462 // TODO
463
464 } else if (symbol.storage_class == .FILE) {
465 if (!std.mem.eql(u8, name, ".file")) {
466 try w.print(" !! unexpected symbol name '{s}' for file symbol 0x{x}", .{ name, symbol_i });
467 continue;
468 }
469
470 var file: std.coff.FileDefinition = undefined;
471 @memcpy(std.mem.asBytes(&file)[0..symbol_size], aux_symbols[0..symbol_size]);
472
473 _ = file.getFileName();
474 } else if (symbol.storage_class == .STATIC and
475 symbol.type == std.coff.SymType{
476 .complex_type = .NULL,
477 .base_type = .NULL,
478 } and
479 symbol.value == 0 and
480 switch (symbol.section_number) {
481 .UNDEFINED, .DEBUG, .ABSOLUTE => false,
482 else => |sn| @intFromEnum(sn) > 0,
483 })
484 {
485 const section_i: u15 = @intCast(@intFromEnum(symbol.section_number) - 1);
486 try w.writeAll(" Section ");
487
488 if (section_i >= sections.items.len) {
489 try w.print(" !! invalid section number: {x}", .{section_i});
490 continue;
491 }
492
493 var section_def: std.coff.SectionDefinition = undefined;
494 @memcpy(std.mem.asBytes(&section_def)[0..symbol_size], aux_symbols[0..symbol_size]);
495 if (native_endian != .little)
496 std.mem.byteSwapAllFields(std.coff.SectionDefinition, &section_def);
497
498 const section = &sections.items[section_i];
499 if (section_def.number_of_relocations != section.number_of_relocations) {
500 try w.print(
501 " !! relocation count did not match section header: {d} vs {d}",
502 .{ section_def.number_of_relocations, section.number_of_relocations },
503 );
504 continue;
505 }
506
507 if (section_def.number_of_linenumbers != section.number_of_linenumbers) {
508 try w.print(
509 " !! line number count did not match section header: {d} vs {d}",
510 .{ section_def.number_of_linenumbers, section.number_of_linenumbers },
511 );
512 continue;
513 }
514
515 try w.print(" [size: {x:0>8} chksum: {x:0>8} relocs: {x:0>4} lines: {x:0>4}]", .{
516 section_def.length,
517 section_def.checksum,
518 section_def.number_of_relocations,
519 section_def.number_of_linenumbers,
520 });
521
522 switch (section_def.selection) {
523 .NONE => {},
524 else => |selection| {
525 try w.print(" COMDAT({t}", .{selection});
526 if (selection == .ASSOCIATIVE)
527 try w.print("->{x}", .{section_def.number});
528 try w.writeAll(")");
529 },
530 }
531 } else {}
532
533 try w.writeByte('\n');
534 }
535 }
536 } else {
537 if (!opts.compact) try w.writeAll("No symbol table found\n");
538 }
539 }
540 }
541
542 fn fmtSymbolType(sym_type: std.coff.SymType) std.fmt.Alt(std.coff.SymType, symbolTypeString) {
543 return .{ .data = sym_type };
544 }
545
546 fn symbolTypeString(sym_type: std.coff.SymType, w: *std.Io.Writer) std.Io.Writer.Error!void {
547 try w.print("{t: >5}", .{sym_type.base_type});
548 if (try switch (sym_type.complex_type) {
549 .NULL => " ",
550 .POINTER => "* ",
551 .FUNCTION => "()",
552 .ARRAY => "[]",
553 else => null,
554 }) |suffix| try .printAll(suffix) else w.print("{x}", .{sym_type.complex_type});
555 }
556
557 fn fmtSectionNumber(section_number: std.coff.SectionNumber) std.fmt.Alt(std.coff.SectionNumber, sectionNumberString) {
558 return .{ .data = section_number };
559 }
560
561 fn sectionNumberString(section_number: std.coff.SectionNumber, w: *std.Io.Writer) std.Io.Writer.Error!void {
562 try switch (section_number) {
563 .UNDEFINED => w.writeAll("UNDEF"),
564 .ABSOLUTE => w.writeAll(" ABS"),
565 .DEBUG => w.writeAll("DEBUG"),
566 else => |v| {
567 const backing = @intFromEnum(v);
568 const fmt = "{x: >5}";
569 if (backing >= 0)
570 try w.print(fmt, .{@as(u15, @intCast(backing))})
571 else
572 try w.print(fmt, .{backing});
573 },
574 };
575 }
576
577 fn dumpFlags(w: *Io.Writer, comptime fmt: []const u8, comptime T: type, flags: *const T, cols: u32) !void {
578 const s = @typeInfo(T).@"struct";
579 inline for (s.fields) |flag_field| {
580 if (flag_field.type == bool and @field(flags, flag_field.name)) {
581 try w.splatByteAll(' ', cols);
582 try w.print(fmt, .{flag_field.name});
583 }
584 }
585 }
586
587 fn dumpHeader(w: *Io.Writer, comptime T: type, header: *const T, Custom: type) !void {
588 inline for (@typeInfo(T).@"struct".fields) |field| {
589 const val = &@field(header, field.name);
590 if (@hasDecl(Custom, field.name)) {
591 try @field(Custom, field.name)(header, w);
592 } else {
593 switch (@typeInfo(field.type)) {
594 .int => try w.print("{x: >16} {s}\n", .{ val.*, field.name }),
595 .@"enum" => try w.print("{x: >16} {s} ({t})\n", .{ val.*, field.name, val.* }),
596 .@"struct" => |s| {
597 switch (s.layout) {
598 .auto,
599 .@"extern",
600 => try dumpHeader(w, field.type, val, Custom),
601 .@"packed" => {
602 try w.print("{x: >16} {s}\n", .{ @as(s.backing_integer.?, @bitCast(val.*)), field.name });
603 try dumpFlags(w, "| {s}\n", field.type, val, 15);
604 },
605 }
606 },
607 else => unreachable,
608 }
609 }
610 }
611 }
612
613 fn dumpVersionField(w: *Io.Writer, name: []const u8, major: anytype, minor: anytype) !void {
614 try w.print("{d: >13}.{x:0<2} {s}\n", .{ major, minor, name });
615 }
616
617 fn dumpRvaField(w: *Io.Writer, name: []const u8, rva: u64, base: u64) !void {
618 try w.print("{x: >16} {s} ({x})\n", .{ rva, name, base + rva });
619 }
620};
621
87const usage =622const usage =
88 \\Usage: zig objdump [options] file623 \\Usage: zig objdump [options] file
89 \\624 \\
90 \\Options:625 \\Options:
91 \\ -h, --help Print this help and exit626 \\ -h, --help Print this help and exit
92 \\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.
629 \\ --file-headers Display file-format specific headers
630 \\ --only-member=[name] Only consider archive members that contain [name]. Can be specified multiple times.
631 \\ --only-section=[name] Only consider sections that contain [name]. Can be specified multiple times.
632 \\ --section-headers Display section headers
633 \\ --strings Display string table
634 \\ --symbols Display symbol tables
635 \\ --relocs Display relocations
93;636;
lib/std/coff.zig+6-4
...@@ -2,6 +2,11 @@ const std = @import("std.zig");...@@ -2,6 +2,11 @@ const std = @import("std.zig");
2const assert = std.debug.assert;2const assert = std.debug.assert;
3const mem = std.mem;3const mem = std.mem;
44
5pub const archive_signature = "!<arch>\n";
6
7pub const pe_signature = "PE\x00\x00";
8pub const pe_pointer_offset = 0x3C;
9
5pub const Header = extern struct {10pub const Header = extern struct {
6 /// The number that identifies the type of target machine.11 /// The number that identifies the type of target machine.
7 machine: IMAGE.FILE.MACHINE,12 machine: IMAGE.FILE.MACHINE,
...@@ -1019,13 +1024,10 @@ pub const Coff = struct {...@@ -1019,13 +1024,10 @@ pub const Coff = struct {
10191024
1020 // The lifetime of `data` must be longer than the lifetime of the returned Coff1025 // The lifetime of `data` must be longer than the lifetime of the returned Coff
1021 pub fn init(data: []const u8, is_loaded: bool) error{ EndOfStream, MissingPEHeader }!Coff {1026 pub fn init(data: []const u8, is_loaded: bool) error{ EndOfStream, MissingPEHeader }!Coff {
1022 const pe_pointer_offset = 0x3C;
1023 const pe_magic = "PE\x00\x00";
1024
1025 if (data.len < pe_pointer_offset + 4) return error.EndOfStream;1027 if (data.len < pe_pointer_offset + 4) return error.EndOfStream;
1026 const header_offset = mem.readInt(u32, data[pe_pointer_offset..][0..4], .little);1028 const header_offset = mem.readInt(u32, data[pe_pointer_offset..][0..4], .little);
1027 if (data.len < header_offset + 4) return error.EndOfStream;1029 if (data.len < header_offset + 4) return error.EndOfStream;
1028 const is_image = mem.eql(u8, data[header_offset..][0..4], pe_magic);1030 const is_image = mem.eql(u8, data[header_offset..][0..4], pe_signature);
10291031
1030 const coff: Coff = .{1032 const coff: Coff = .{
1031 .data = data,1033 .data = data,
src/link/Coff.zig+9-10
...@@ -90,7 +90,6 @@ pub const default_size_of_stack_commit: u32 = 0x1000;...@@ -90,7 +90,6 @@ pub const default_size_of_stack_commit: u32 = 0x1000;
90pub const default_size_of_heap_reserve: u32 = 0x100000;90pub const default_size_of_heap_reserve: u32 = 0x100000;
91pub const default_size_of_heap_commit: u32 = 0x1000;91pub const default_size_of_heap_commit: u32 = 0x1000;
9292
93pub const archive_signature = "!<arch>\n";
94pub const archive_end_of_header = "`\n";93pub const archive_end_of_header = "`\n";
9594
96pub const imp_prefix = "__imp_";95pub const imp_prefix = "__imp_";
...@@ -1763,14 +1762,12 @@ fn initHeaders(...@@ -1763,14 +1762,12 @@ fn initHeaders(
1763 }));1762 }));
1764 coff.nodes.appendAssumeCapacity(.header);1763 coff.nodes.appendAssumeCapacity(.header);
17651764
1766 const pe_signature = "PE\x00\x00";
1767
1768 const signature_ni = Node.known.signature;1765 const signature_ni = Node.known.signature;
1769 assert(signature_ni == try coff.mf.addLastChildNode(gpa, if (is_image or !is_archive) header_ni else Node.known.file, .{1766 assert(signature_ni == try coff.mf.addLastChildNode(gpa, if (is_image or !is_archive) header_ni else Node.known.file, .{
1770 .size = if (is_image)1767 .size = if (is_image)
1771 msdos_stub.len + pe_signature.len1768 msdos_stub.len + std.coff.pe_signature.len
1772 else if (is_archive)1769 else if (is_archive)
1773 archive_signature.len1770 std.coff.archive_signature.len
1774 else1771 else
1775 0,1772 0,
1776 .alignment = .@"4",1773 .alignment = .@"4",
...@@ -1781,9 +1778,9 @@ fn initHeaders(...@@ -1781,9 +1778,9 @@ fn initHeaders(
1781 const signature_slice = signature_ni.slice(&coff.mf);1778 const signature_slice = signature_ni.slice(&coff.mf);
1782 if (is_image) {1779 if (is_image) {
1783 @memcpy(signature_slice[0..msdos_stub.len], &msdos_stub);1780 @memcpy(signature_slice[0..msdos_stub.len], &msdos_stub);
1784 @memcpy(signature_slice[signature_slice.len - pe_signature.len ..], pe_signature);1781 @memcpy(signature_slice[signature_slice.len - std.coff.pe_signature.len ..], std.coff.pe_signature);
1785 } else if (is_archive) {1782 } else if (is_archive) {
1786 @memcpy(signature_slice, archive_signature);1783 @memcpy(signature_slice, std.coff.archive_signature);
1787 }1784 }
17881785
1789 const opt_coff_parent_ni = if (is_archive) parent: {1786 const opt_coff_parent_ni = if (is_archive) parent: {
...@@ -3679,7 +3676,7 @@ fn loadObject(...@@ -3679,7 +3676,7 @@ fn loadObject(
36793676
3680 log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtMemberNameString(member_name) });3677 log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtMemberNameString(member_name) });
36813678
3682 const header = try r.peekStruct(std.coff.Header, coff.targetEndian());3679 const header = try r.peekStruct(std.coff.Header, .little());
3683 if (header.machine != target.toCoffMachine())3680 if (header.machine != target.toCoffMachine())
3684 return diags.failParse(path, "machine mismatch: expected {t}, found {t}", .{3681 return diags.failParse(path, "machine mismatch: expected {t}, found {t}", .{
3685 target.toCoffMachine(),3682 target.toCoffMachine(),
...@@ -4786,8 +4783,8 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo...@@ -4786,8 +4783,8 @@ fn loadArchive(coff: *Coff, path: std.Build.Cache.Path, fr: *Io.File.Reader) !vo
47864783
4787 log.debug("loadArchive({f})", .{path.fmtEscapeString()});4784 log.debug("loadArchive({f})", .{path.fmtEscapeString()});
47884785
4789 const signature = try r.take(archive_signature.len);4786 const signature = try r.take(std.coff.archive_signature.len);
4790 if (!std.mem.eql(u8, signature, archive_signature))4787 if (!std.mem.eql(u8, signature, std.coff.archive_signature))
4791 return diags.failParse(path, "bad signature", .{});4788 return diags.failParse(path, "bad signature", .{});
47924789
4793 var opt_expected_kind: ?Member.Kind = .first_linker;4790 var opt_expected_kind: ?Member.Kind = .first_linker;
...@@ -6375,6 +6372,8 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {...@@ -6375,6 +6372,8 @@ fn flushGlobal(coff: *Coff, gmi: Node.GlobalMapIndex) !bool {
6375 const iat_offset: u32 = @intCast(addr_info.size * iat_symbol_gop.value_ptr.*);6372 const iat_offset: u32 = @intCast(addr_info.size * iat_symbol_gop.value_ptr.*);
6376 switch (import.kind) {6373 switch (import.kind) {
6377 .iat_ptr => {6374 .iat_ptr => {
6375 // TODO: Currently the codegen is wrong for loading the address of these globals,
6376 // we generate lea [<iat_ptr>] when it should be mov [<iat_ptr>]
6378 const iat_sym = gop.value_ptr.import_address_table_si.get(coff);6377 const iat_sym = gop.value_ptr.import_address_table_si.get(coff);
6379 sym.section_number = iat_sym.section_number;6378 sym.section_number = iat_sym.section_number;
6380 sym.ni = iat_sym.ni;6379 sym.ni = iat_sym.ni;