| 1 | const std = @import("std"); |
| 2 | const Io = std.Io; |
| 3 | const fatal = std.process.fatal; |
| 4 | const mem = std.mem; |
| 5 | const assert = std.debug.assert; |
| 6 | |
| 7 | const builtin = @import("builtin"); |
| 8 | const native_endian = builtin.cpu.arch.endian(); |
| 9 | |
| 10 | var stdout_buffer: [4000]u8 = undefined; |
| 11 | |
| 12 | const Options = struct { |
| 13 | exports: bool, |
| 14 | exports_sort: bool, |
| 15 | file_headers: bool, |
| 16 | imports: bool, |
| 17 | input_path: []const u8, |
| 18 | member_filters: []const []const u8 = &.{}, |
| 19 | member_headers: bool, |
| 20 | elements: std.enums.EnumArray(Element, bool), |
| 21 | redact: std.enums.EnumArray(FieldKind, bool), |
| 22 | relocs: bool, |
| 23 | section_filters: []const []const u8 = &.{}, |
| 24 | section_headers: bool, |
| 25 | symbol_filters: []const []const u8 = &.{}, |
| 26 | strings: bool, |
| 27 | symbols: bool, |
| 28 | tls: bool, |
| 29 | |
| 30 | // Coff-specific |
| 31 | linker_member: ?std.coff.ArchiveMemberHeader.Kind, |
| 32 | }; |
| 33 | |
| 34 | const FieldKind = enum { |
| 35 | va, |
| 36 | rva, |
| 37 | ord, |
| 38 | size, |
| 39 | }; |
| 40 | |
| 41 | const Element = enum { |
| 42 | @"file-type", |
| 43 | @"header-name", |
| 44 | @"member-path", |
| 45 | newlines, |
| 46 | @"table-header", |
| 47 | }; |
| 48 | |
| 49 | pub fn main(init: std.process.Init) !void { |
| 50 | const io = init.io; |
| 51 | const args = try init.minimal.args.toSlice(init.arena.allocator()); |
| 52 | const arena = init.arena.allocator(); |
| 53 | |
| 54 | var i: usize = 1; |
| 55 | |
| 56 | var opt_exports: ?bool = null; |
| 57 | var opt_exports_sort: ?bool = null; |
| 58 | var opt_file_headers: ?bool = null; |
| 59 | var opt_imports: ?bool = null; |
| 60 | var opt_input_path: ?[]const u8 = null; |
| 61 | var opt_linker_member: ?std.coff.ArchiveMemberHeader.Kind = null; |
| 62 | var opt_member_headers: ?bool = null; |
| 63 | var any_elements = false; |
| 64 | var elements: ?@FieldType(Options, "elements") = null; |
| 65 | var redact: @FieldType(Options, "redact") = .initFill(false); |
| 66 | var opt_relocs: ?bool = null; |
| 67 | var opt_section_headers: ?bool = null; |
| 68 | var opt_strings: ?bool = null; |
| 69 | var opt_symbols: ?bool = null; |
| 70 | var opt_tls: ?bool = null; |
| 71 | var section_filters: std.ArrayList([]const u8) = .empty; |
| 72 | var symbol_filters: std.ArrayList([]const u8) = .empty; |
| 73 | var member_filters: std.ArrayList([]const u8) = .empty; |
| 74 | while (i < args.len) : (i += 1) { |
| 75 | const arg = args[i]; |
| 76 | if (mem.startsWith(u8, arg, "-")) { |
| 77 | if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { |
| 78 | return Io.File.stdout().writeStreamingAll(io, usage); |
| 79 | } else if (mem.eql(u8, arg, "--all-headers")) { |
| 80 | opt_file_headers = true; |
| 81 | opt_linker_member = .second_linker; |
| 82 | opt_member_headers = true; |
| 83 | opt_section_headers = true; |
| 84 | opt_symbols = true; |
| 85 | opt_relocs = true; |
| 86 | } else if (mem.startsWith(u8, arg, "--exports")) { |
| 87 | opt_exports = true; |
| 88 | opt_linker_member = .second_linker; |
| 89 | if (mem.eql(u8, arg["--exports".len..], "=sort")) |
| 90 | opt_exports_sort = true; |
| 91 | } else if (mem.eql(u8, arg, "--file-headers")) { |
| 92 | opt_file_headers = true; |
| 93 | } else if (mem.eql(u8, arg, "--imports")) { |
| 94 | opt_imports = true; |
| 95 | } else if (mem.startsWith(u8, arg, "--linker-member")) { |
| 96 | if (mem.eql(u8, arg["--linker-member".len..], "=1")) |
| 97 | opt_linker_member = .first_linker |
| 98 | else if (mem.eql(u8, arg["--linker-member".len..], "=longnames")) |
| 99 | opt_linker_member = .longnames |
| 100 | else |
| 101 | opt_linker_member = .second_linker; |
| 102 | } else if (mem.eql(u8, arg, "--member-headers")) { |
| 103 | opt_member_headers = true; |
| 104 | } else if (mem.startsWith(u8, arg, "--elements=")) { |
| 105 | any_elements = true; |
| 106 | var split = std.mem.splitScalar(u8, arg["--elements=".len..], ','); |
| 107 | while (split.next()) |element| { |
| 108 | const kind, const add = if (element.len > 0 and element[0] == '-') |
| 109 | .{ element[1..], false } |
| 110 | else |
| 111 | .{ element, true }; |
| 112 | |
| 113 | if (elements == null) elements = .initFill(false); |
| 114 | if (std.meta.stringToEnum(Element, kind)) |format_kind| { |
| 115 | elements.?.set(format_kind, add); |
| 116 | } else if (std.mem.eql(u8, kind, "all")) { |
| 117 | elements.? = .initFill(add); |
| 118 | } else { |
| 119 | fatal("unrecognized element: '{s}'", .{kind}); |
| 120 | } |
| 121 | } |
| 122 | } else if (mem.startsWith(u8, arg, "--only-member=")) { |
| 123 | (try member_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-member=".len..]); |
| 124 | } else if (mem.startsWith(u8, arg, "--only-section=")) { |
| 125 | (try section_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-section=".len..]); |
| 126 | } else if (mem.startsWith(u8, arg, "--only-symbol=")) { |
| 127 | (try symbol_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-symbol=".len..]); |
| 128 | } else if (mem.startsWith(u8, arg, "--redact=")) { |
| 129 | const kind = arg["--redact=".len..]; |
| 130 | if (std.meta.stringToEnum(FieldKind, kind)) |field_kind| { |
| 131 | redact.set(field_kind, true); |
| 132 | } else if (std.mem.eql(u8, kind, "all")) { |
| 133 | redact = .initFill(true); |
| 134 | } else { |
| 135 | fatal("unrecognized redaction kind: {s}", .{kind}); |
| 136 | } |
| 137 | } else if (mem.eql(u8, arg, "--relocs")) { |
| 138 | opt_relocs = true; |
| 139 | } else if (mem.eql(u8, arg, "--section-headers")) { |
| 140 | opt_section_headers = true; |
| 141 | } else if (mem.eql(u8, arg, "-s") or mem.eql(u8, arg, "--snapshot")) { |
| 142 | elements = .initFill(false); |
| 143 | redact = .initFill(true); |
| 144 | } else if (mem.eql(u8, arg, "--strings")) { |
| 145 | opt_strings = true; |
| 146 | } else if (mem.eql(u8, arg, "--symbols")) { |
| 147 | opt_symbols = true; |
| 148 | } else if (mem.eql(u8, arg, "--tls")) { |
| 149 | opt_tls = true; |
| 150 | } else { |
| 151 | fatal("unrecognized argument: {s}", .{arg}); |
| 152 | } |
| 153 | } else if (opt_input_path == null) { |
| 154 | opt_input_path = arg; |
| 155 | } else { |
| 156 | fatal("unexpected positional: {s}", .{arg}); |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | const opts: Options = .{ |
| 161 | .input_path = opt_input_path orelse fatal("missing input file path positional argument", .{}), |
| 162 | .exports = opt_exports orelse false, |
| 163 | .exports_sort = opt_exports_sort orelse false, |
| 164 | .file_headers = opt_file_headers orelse false, |
| 165 | .imports = opt_imports orelse false, |
| 166 | .linker_member = opt_linker_member, |
| 167 | .member_filters = member_filters.items, |
| 168 | .member_headers = opt_member_headers orelse false, |
| 169 | .elements = elements orelse .initFill(true), |
| 170 | .redact = redact, |
| 171 | .relocs = opt_relocs orelse false, |
| 172 | .section_filters = section_filters.items, |
| 173 | .section_headers = opt_section_headers orelse false, |
| 174 | .strings = opt_strings orelse false, |
| 175 | .symbol_filters = symbol_filters.items, |
| 176 | .symbols = opt_symbols orelse false, |
| 177 | .tls = opt_tls orelse false, |
| 178 | }; |
| 179 | |
| 180 | var file = std.Io.Dir.cwd().openFile(io, opts.input_path, .{}) catch |err| |
| 181 | fatal("failed to open {s}: {t}", .{ opts.input_path, err }); |
| 182 | defer file.close(io); |
| 183 | |
| 184 | var buffer: [4096]u8 = undefined; |
| 185 | var file_reader = file.reader(io, &buffer); |
| 186 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &stdout_buffer); |
| 187 | |
| 188 | const ctx: DumpContext = .{ |
| 189 | .gpa = init.gpa, |
| 190 | .opts = &opts, |
| 191 | .fr = &file_reader, |
| 192 | .w = &stdout_writer.interface, |
| 193 | }; |
| 194 | |
| 195 | dump(&ctx) catch |err| switch (err) { |
| 196 | error.ReadFailed => return file_reader.err.?, |
| 197 | error.WriteFailed => return stdout_writer.err.?, |
| 198 | error.UnknownFile => fatal("unrecognized file: {s}", .{opts.input_path}), |
| 199 | error.ParseFailure => {}, |
| 200 | else => |e| return e, |
| 201 | }; |
| 202 | try stdout_writer.flush(); |
| 203 | } |
| 204 | |
| 205 | fn dump(d: *const DumpContext) !void { |
| 206 | const r = &d.fr.interface; |
| 207 | try r.fill(4); |
| 208 | elf: { |
| 209 | if (!mem.eql(u8, r.buffered()[0..4], std.elf.MAGIC)) break :elf; |
| 210 | return elf.dump(r, d.w); |
| 211 | } |
| 212 | macho: { |
| 213 | if (mem.readInt(u32, r.buffered()[0..4], .little) != std.macho.MH_MAGIC_64) break :macho; |
| 214 | return macho.dump(r, d.w); |
| 215 | } |
| 216 | wasm: { |
| 217 | comptime assert(std.wasm.magic.len == 4); |
| 218 | if (!mem.eql(u8, r.buffered()[0..4], &std.wasm.magic)) break :wasm; |
| 219 | return wasm.dump(r, d.w); |
| 220 | } |
| 221 | coff: { |
| 222 | const ext = std.fs.path.extension(d.opts.input_path); |
| 223 | const basename = std.fs.path.basename(d.opts.input_path); |
| 224 | if (std.mem.eql(u8, ext, ".exe") or std.mem.eql(u8, ext, ".dll")) { |
| 225 | if (!mem.eql(u8, r.buffered()[0..2], "MZ")) break :coff; |
| 226 | try r.discardAll(std.coff.pe_pointer_offset); |
| 227 | const sig_offset = try r.takeInt(u32, .little); |
| 228 | try d.fr.seekTo(sig_offset); |
| 229 | const sig = try r.take(4); |
| 230 | |
| 231 | if (!std.mem.eql(u8, sig, std.coff.pe_signature)) { |
| 232 | try d.w.print("invalid PE signature: {x}", .{sig}); |
| 233 | return error.ParseFailure; |
| 234 | } |
| 235 | |
| 236 | if (d.element(.@"file-type")) { |
| 237 | try d.w.print("{s}: PE/COFF image\n\n", .{basename}); |
| 238 | if (d.element(.newlines)) try d.w.writeByte('\n'); |
| 239 | } |
| 240 | |
| 241 | return coff.dumpObject(d, true, basename); |
| 242 | } else if (std.mem.eql(u8, ext, ".lib")) { |
| 243 | r.fill(std.coff.archive_signature.len) catch break :coff; |
| 244 | if (!mem.eql(u8, r.buffered()[0..std.coff.archive_signature.len], std.coff.archive_signature)) break :coff; |
| 245 | if (d.element(.@"file-type")) { |
| 246 | try d.w.print("{s}: COFF archive\n", .{basename}); |
| 247 | if (d.element(.newlines)) try d.w.writeByte('\n'); |
| 248 | } |
| 249 | |
| 250 | return coff.dumpArchive(d); |
| 251 | } else if (std.mem.eql(u8, ext, ".obj")) { |
| 252 | if (d.element(.@"file-type")) { |
| 253 | try d.w.print("{s}: COFF object\n", .{basename}); |
| 254 | if (d.element(.newlines)) try d.w.writeByte('\n'); |
| 255 | } |
| 256 | |
| 257 | return coff.dumpObject(d, false, basename); |
| 258 | } |
| 259 | } |
| 260 | return error.UnknownFile; |
| 261 | } |
| 262 | |
| 263 | const DumpContext = struct { |
| 264 | gpa: std.mem.Allocator, |
| 265 | opts: *const Options, |
| 266 | fr: *Io.File.Reader, |
| 267 | w: *Io.Writer, |
| 268 | |
| 269 | fn element(self: *const DumpContext, e: Element) bool { |
| 270 | return self.opts.elements.get(e); |
| 271 | } |
| 272 | |
| 273 | fn redacted(self: *const DumpContext, opt_kind: ?FieldKind) bool { |
| 274 | const kind = opt_kind orelse return false; |
| 275 | return self.opts.redact.get(kind); |
| 276 | } |
| 277 | |
| 278 | fn failParse( |
| 279 | ctx: *const DumpContext, |
| 280 | comptime fmt: []const u8, |
| 281 | args: anytype, |
| 282 | ) noreturn { |
| 283 | std.log.err("error parsing '{s}'", .{std.fs.path.basename(ctx.opts.input_path)}); |
| 284 | fatal(fmt, args); |
| 285 | } |
| 286 | }; |
| 287 | |
| 288 | const elf = struct { |
| 289 | fn dump(r: *Io.Reader, w: *Io.Writer) !void { |
| 290 | _ = r; |
| 291 | try w.writeAll("TODO dump elf file\n"); |
| 292 | } |
| 293 | }; |
| 294 | |
| 295 | const macho = struct { |
| 296 | fn dump(r: *Io.Reader, w: *Io.Writer) !void { |
| 297 | _ = r; |
| 298 | try w.writeAll("TODO dump macho file\n"); |
| 299 | } |
| 300 | }; |
| 301 | |
| 302 | const wasm = struct { |
| 303 | fn dump(r: *Io.Reader, w: *Io.Writer) !void { |
| 304 | _ = r; |
| 305 | try w.writeAll("TODO dump wasm file\n"); |
| 306 | } |
| 307 | }; |
| 308 | |
| 309 | const coff = struct { |
| 310 | const DIRECTORY_ENTRY = std.coff.IMAGE.DIRECTORY_ENTRY; |
| 311 | |
| 312 | const Section = struct { |
| 313 | header: std.coff.SectionHeader, |
| 314 | name: []const u8, |
| 315 | |
| 316 | fn rvaFileOffset(section: *const Section, rva: u32) !u32 { |
| 317 | if (rva < section.header.virtual_address or |
| 318 | rva >= section.header.virtual_address + section.header.size_of_raw_data) |
| 319 | return error.OutOfBounds; |
| 320 | |
| 321 | return section.header.pointer_to_raw_data + (rva - section.header.virtual_address); |
| 322 | } |
| 323 | }; |
| 324 | |
| 325 | const ArchiveHeader = struct { |
| 326 | name: []const u8, |
| 327 | date: u40, |
| 328 | user_id: u20, |
| 329 | group_id: u20, |
| 330 | file_mode: u24, |
| 331 | size: u34, |
| 332 | |
| 333 | pub fn fromRaw(d: *const DumpContext, raw_header: *const std.coff.ArchiveMemberHeader, opt_longnames: ?[]const u8) @This() { |
| 334 | const name = raw_header.parseName(opt_longnames) catch |err| switch (err) { |
| 335 | error.BadName => d.failParse("malformed member name: '{s}'", .{&raw_header.name}), |
| 336 | error.NoLongNames => d.failParse("member uses a long name, but there was no longnames member", .{}), |
| 337 | }; |
| 338 | |
| 339 | return .{ |
| 340 | .name = name, |
| 341 | .date = raw_header.parseDate() catch |err| |
| 342 | d.failParse("unable to parse date '{s}' in member '{s}': {t}", .{ raw_header.date, name, err }), |
| 343 | .user_id = raw_header.parseUserId() catch |err| |
| 344 | d.failParse("unable to parse user_id '{s}' in member '{s}': {t}", .{ raw_header.user_id, name, err }), |
| 345 | .group_id = raw_header.parseGroupId() catch |err| |
| 346 | d.failParse("unable to parse group_id '{s}' in member '{s}': {t}", .{ raw_header.group_id, name, err }), |
| 347 | .file_mode = raw_header.parseFileMode() catch |err| |
| 348 | d.failParse("unable to parse file_mode '{s}' in member '{s}': {t}", .{ raw_header.file_mode, name, err }), |
| 349 | .size = raw_header.parseSize() catch |err| |
| 350 | d.failParse("unable to parse size '{s}' in member '{s}': {t}", .{ raw_header.size, name, err }), |
| 351 | }; |
| 352 | } |
| 353 | }; |
| 354 | |
| 355 | fn dumpArchive(d: *const DumpContext) !void { |
| 356 | const gpa = d.gpa; |
| 357 | const fr = d.fr; |
| 358 | const w = d.w; |
| 359 | |
| 360 | const r = &fr.interface; |
| 361 | r.toss(std.coff.archive_signature.len); |
| 362 | |
| 363 | const Member = struct { |
| 364 | offset: u32, |
| 365 | order: ?u32, |
| 366 | }; |
| 367 | |
| 368 | var members: std.ArrayList(Member) = .empty; |
| 369 | defer members.deinit(gpa); |
| 370 | var symbol_member_indices: std.ArrayList(u32) = .empty; |
| 371 | defer symbol_member_indices.deinit(gpa); |
| 372 | |
| 373 | var opt_expected_kind: ?std.coff.ArchiveMemberHeader.Kind = .first_linker; |
| 374 | var opt_longnames: ?[]const u8 = null; |
| 375 | defer if (opt_longnames) |l| gpa.free(l); |
| 376 | |
| 377 | var pos = fr.logicalPos(); |
| 378 | const size = try fr.getSize(); |
| 379 | while (pos < size) : (pos = fr.logicalPos()) { |
| 380 | if ((pos & 1) != 0) try r.discardAll(1); |
| 381 | const raw_header = try r.takeStruct(std.coff.ArchiveMemberHeader, .little); |
| 382 | const header: ArchiveHeader = .fromRaw(d, &raw_header, opt_longnames); |
| 383 | |
| 384 | if (!std.mem.eql(u8, &raw_header.end_of_header, std.coff.archive_end_of_header)) |
| 385 | return d.failParse("malformed end-of-header field in member '{s}': {x}", .{ header.name, raw_header.end_of_header }); |
| 386 | |
| 387 | const dump_header = |
| 388 | (d.opts.member_headers and filterMatches(d.opts.member_filters, header.name)) or |
| 389 | (d.opts.linker_member == opt_expected_kind); |
| 390 | |
| 391 | if (dump_header) |
| 392 | try dumpArchiveHeader(d, &header, @intCast(pos)); |
| 393 | |
| 394 | const member_end = fr.logicalPos() + header.size; |
| 395 | if (member_end > size) |
| 396 | return d.failParse("out-of-bounds length 0x{x} in member '{s}'", .{ header.size, header.name }); |
| 397 | |
| 398 | if (opt_expected_kind) |expected_kind| switch (expected_kind) { |
| 399 | .first_linker => { |
| 400 | if (!std.mem.eql(u8, header.name, "/")) |
| 401 | return d.failParse("expected first linker member, found '{s}'", .{header.name}); |
| 402 | |
| 403 | const num_symbols = try r.takeInt(u32, .big); |
| 404 | if (dump_header) |
| 405 | try w.print( |
| 406 | \\{t: >16} type |
| 407 | \\ | {d} symbols |
| 408 | \\ |
| 409 | , .{ expected_kind, num_symbols }); |
| 410 | |
| 411 | if (d.opts.linker_member == .first_linker) { |
| 412 | if (d.element(.@"table-header")) |
| 413 | try w.writeAll( |
| 414 | \\ |
| 415 | \\Archive symbols: |
| 416 | \\& Member Symbol |
| 417 | \\ |
| 418 | ); |
| 419 | |
| 420 | const offsets = try r.readAlloc(gpa, num_symbols * 4); |
| 421 | defer gpa.free(offsets); |
| 422 | |
| 423 | for (0..num_symbols) |symbol_i| { |
| 424 | const symbol = r.takeDelimiter(0) catch |err| |
| 425 | return d.failParse("unable to read first linker member string table: {t}", .{err}); |
| 426 | |
| 427 | if (!filterMatches(d.opts.symbol_filters, symbol.?)) |
| 428 | continue; |
| 429 | |
| 430 | const offset = std.mem.readInt(u32, offsets[symbol_i * 4 ..][0..4], .big); |
| 431 | try w.print("{f} {s}\n", .{ |
| 432 | fmtIntField(d, offset, .{ .kind = .va }), |
| 433 | symbol.?, |
| 434 | }); |
| 435 | } |
| 436 | } |
| 437 | if (dump_header and d.element(.newlines)) try w.writeByte('\n'); |
| 438 | |
| 439 | try fr.seekTo(member_end); |
| 440 | opt_expected_kind = .second_linker; |
| 441 | continue; |
| 442 | }, |
| 443 | .second_linker => { |
| 444 | if (!std.mem.eql(u8, header.name, "/")) |
| 445 | return d.failParse("expected second linker member, found '{s}'", .{header.name}); |
| 446 | |
| 447 | const num_members = try r.takeInt(u32, .little); |
| 448 | pos = fr.logicalPos(); |
| 449 | if (pos + num_members * @sizeOf(u32) > member_end) |
| 450 | return d.failParse("invalid member count 0x{x} in second linker member", .{num_members}); |
| 451 | |
| 452 | try members.ensureTotalCapacity(gpa, num_members); |
| 453 | for (0..num_members) |_| |
| 454 | members.addOneAssumeCapacity().* = .{ |
| 455 | .offset = try r.takeInt(u32, .little), |
| 456 | .order = null, |
| 457 | }; |
| 458 | |
| 459 | const num_symbols = try r.takeInt(u32, .little); |
| 460 | pos = fr.logicalPos(); |
| 461 | if (pos + num_symbols * @sizeOf(u16) > member_end) |
| 462 | return d.failParse("invalid symbol count 0x{x} in second linker member", .{num_symbols}); |
| 463 | |
| 464 | if (dump_header) |
| 465 | try w.print( |
| 466 | \\{t: >16} type |
| 467 | \\ | {f} symbols |
| 468 | \\ | {f} members |
| 469 | \\ |
| 470 | , .{ |
| 471 | expected_kind, |
| 472 | fmtIntField(d, num_symbols, .{ .kind = .size, .width = .auto }), |
| 473 | fmtIntField(d, num_members, .{ .kind = .size, .width = .auto }), |
| 474 | }); |
| 475 | |
| 476 | try symbol_member_indices.ensureTotalCapacity(gpa, num_symbols); |
| 477 | for (0..num_symbols) |order| { |
| 478 | const index = (try r.takeInt(u16, .little)) - 1; |
| 479 | if (index >= members.items.len) |
| 480 | return d.failParse("invalid member index 0x{x} in seconds linker member indices array", .{index}); |
| 481 | |
| 482 | symbol_member_indices.addOneAssumeCapacity().* = index; |
| 483 | |
| 484 | if (members.items[index].order == null) |
| 485 | members.items[index].order = @intCast(order); |
| 486 | } |
| 487 | |
| 488 | if (d.opts.exports and d.opts.exports_sort) { |
| 489 | std.sort.pdq(Member, members.items, {}, struct { |
| 490 | fn lessThan(ctx: void, lhs: Member, rhs: Member) bool { |
| 491 | _ = ctx; |
| 492 | if (lhs.order == null and rhs.order == null) |
| 493 | return lhs.offset < rhs.offset |
| 494 | else if (lhs.order) |lhs_order| |
| 495 | return if (rhs.order) |rhs_order| lhs_order < rhs_order else false |
| 496 | else if (rhs.order) |rhs_order| |
| 497 | return if (lhs.order) |lhs_order| lhs_order < rhs_order else true |
| 498 | else |
| 499 | unreachable; |
| 500 | } |
| 501 | }.lessThan); |
| 502 | } |
| 503 | |
| 504 | if (d.opts.linker_member == .second_linker) { |
| 505 | if (d.element(.@"table-header")) |
| 506 | try w.writeAll( |
| 507 | \\ |
| 508 | \\Archive Symbols: |
| 509 | \\& Member Symbol |
| 510 | \\ |
| 511 | ); |
| 512 | |
| 513 | pos = fr.logicalPos(); |
| 514 | var symbol_i: u32 = 0; |
| 515 | while (pos < member_end and symbol_i < num_symbols) : ({ |
| 516 | pos = fr.logicalPos(); |
| 517 | symbol_i += 1; |
| 518 | }) { |
| 519 | const symbol_name = if (r.takeDelimiter(0) catch |err| switch (err) { |
| 520 | error.StreamTooLong => null, |
| 521 | else => |e| return e, |
| 522 | }) |n| n else return d.failParse("unterminated string found in second linker member", .{}); |
| 523 | |
| 524 | if (!filterMatches(d.opts.symbol_filters, symbol_name)) |
| 525 | continue; |
| 526 | |
| 527 | try w.print("{f} {s}\n", .{ |
| 528 | fmtIntField( |
| 529 | d, |
| 530 | members.items[symbol_member_indices.items[symbol_i]].offset, |
| 531 | .{ .kind = .va }, |
| 532 | ), |
| 533 | symbol_name, |
| 534 | }); |
| 535 | } |
| 536 | |
| 537 | if (symbol_i != num_symbols) |
| 538 | return d.failParse( |
| 539 | " expected {d} entries in second linker member string table, but found {d}", |
| 540 | .{ num_symbols, symbol_i }, |
| 541 | ); |
| 542 | } |
| 543 | |
| 544 | if (d.element(.newlines)) try w.writeByte('\n'); |
| 545 | try fr.seekTo(member_end); |
| 546 | opt_expected_kind = .longnames; |
| 547 | continue; |
| 548 | }, |
| 549 | .longnames => { |
| 550 | // This member is optional |
| 551 | if (std.mem.eql(u8, header.name, "//")) { |
| 552 | opt_longnames = try r.readAlloc(gpa, header.size); |
| 553 | if (dump_header) |
| 554 | try w.print("{t: >16} type\n", .{expected_kind}); |
| 555 | |
| 556 | if (d.opts.linker_member == .longnames) { |
| 557 | if (d.element(.@"table-header")) |
| 558 | try w.print( |
| 559 | \\ |
| 560 | \\Longnames (0x{x} bytes): |
| 561 | \\ |
| 562 | , .{opt_longnames.?.len}); |
| 563 | |
| 564 | var lr = Io.Reader.fixed(opt_longnames.?); |
| 565 | while (try lr.takeDelimiter(0)) |str| { |
| 566 | try w.writeAll(str); |
| 567 | try w.writeByte('\n'); |
| 568 | } |
| 569 | } |
| 570 | |
| 571 | if (d.element(.newlines)) try w.writeByte('\n'); |
| 572 | } |
| 573 | |
| 574 | opt_expected_kind = null; |
| 575 | break; |
| 576 | }, |
| 577 | else => unreachable, |
| 578 | }; |
| 579 | } |
| 580 | |
| 581 | if (opt_expected_kind) |expected_kind| switch (expected_kind) { |
| 582 | .first_linker => d.failParse("missing first linker member", .{}), |
| 583 | .second_linker => d.failParse("missing second linker member", .{}), |
| 584 | else => {}, |
| 585 | }; |
| 586 | |
| 587 | for (members.items, 0..) |member, member_i| { |
| 588 | fr.seekTo(member.offset) catch |err| |
| 589 | d.failParse("unable to read member {d} at offset 0x{x}: {t}", .{ member_i, member.offset, err }); |
| 590 | |
| 591 | const raw_header = try r.takeStruct(std.coff.ArchiveMemberHeader, .little); |
| 592 | const header: ArchiveHeader = .fromRaw(d, &raw_header, opt_longnames); |
| 593 | if (!filterMatches(d.opts.member_filters, header.name)) continue; |
| 594 | |
| 595 | const member_sig = try r.peek(4); |
| 596 | const machine: std.coff.IMAGE.FILE.MACHINE = |
| 597 | @fromBackingInt(@intCast(std.mem.readInt(u16, member_sig[0..2], .little))); |
| 598 | const sig = std.mem.readInt(u16, member_sig[2..4], .little); |
| 599 | |
| 600 | const is_imp_lib = machine == std.coff.IMAGE.FILE.MACHINE.UNKNOWN and sig == 0xffff; |
| 601 | if (d.opts.member_headers) |
| 602 | try dumpArchiveHeader(d, &header, member.offset); |
| 603 | |
| 604 | if (d.opts.member_headers or (d.opts.exports and is_imp_lib)) { |
| 605 | if (is_imp_lib) { |
| 606 | const imp_header = try r.takeStruct(std.coff.ImportHeader, .little); |
| 607 | const sym_name = (try r.takeDelimiter(0)).?; |
| 608 | const imp_dll = (try r.takeDelimiter(0)).?; |
| 609 | |
| 610 | if (!filterMatches(d.opts.symbol_filters, sym_name)) |
| 611 | continue; |
| 612 | |
| 613 | if (d.element(.@"header-name")) |
| 614 | try w.writeAll("\nImport header:\n"); |
| 615 | |
| 616 | try dumpHeader(d, std.coff.ImportHeader, &imp_header, struct { |
| 617 | pub fn sig1(_: *const DumpContext, _: *const std.coff.ImportHeader) !void {} |
| 618 | pub fn sig2(_: *const DumpContext, _: *const std.coff.ImportHeader) !void {} |
| 619 | pub fn types(id: *const DumpContext, h: *const std.coff.ImportHeader) !void { |
| 620 | try id.w.print( |
| 621 | \\{t: >16} import_type |
| 622 | \\{t: >16} name_type |
| 623 | \\ |
| 624 | , .{ h.types.type, h.types.name_type }); |
| 625 | } |
| 626 | }); |
| 627 | |
| 628 | const imp_name = imp_name: switch (imp_header.types.name_type) { |
| 629 | .NAME_NOPREFIX, |
| 630 | .NAME_UNDECORATE, |
| 631 | => |tag| { |
| 632 | var imp_name = std.mem.trimStart(u8, sym_name, "?@_"); |
| 633 | if (tag == .NAME_UNDECORATE) |
| 634 | imp_name = std.mem.sliceTo(imp_name, '@'); |
| 635 | break :imp_name imp_name; |
| 636 | }, |
| 637 | else => sym_name, |
| 638 | }; |
| 639 | |
| 640 | try w.print( |
| 641 | \\ symbol name | {s} |
| 642 | \\ import name | {s} |
| 643 | \\ dll | {s} |
| 644 | \\ |
| 645 | , .{ |
| 646 | sym_name, |
| 647 | imp_name, |
| 648 | imp_dll, |
| 649 | }); |
| 650 | } else { |
| 651 | try w.writeAll(" COFF object type\n"); |
| 652 | } |
| 653 | if (d.element(.newlines)) try w.writeByte('\n'); |
| 654 | } |
| 655 | |
| 656 | if (is_imp_lib) continue; |
| 657 | if (d.opts.section_headers or |
| 658 | d.opts.file_headers or |
| 659 | d.opts.relocs or |
| 660 | d.opts.strings or |
| 661 | d.opts.symbols) |
| 662 | { |
| 663 | const member_name = if (d.element(.@"member-path")) |
| 664 | header.name |
| 665 | else |
| 666 | std.fs.path.basename(header.name); |
| 667 | |
| 668 | if (d.element(.@"file-type")) { |
| 669 | try w.print("{s}({s}): COFF object\n", .{ |
| 670 | std.fs.path.basename(d.opts.input_path), |
| 671 | member_name, |
| 672 | }); |
| 673 | if (d.element(.newlines)) try w.writeByte('\n'); |
| 674 | } |
| 675 | try dumpObject(d, false, member_name); |
| 676 | } |
| 677 | } |
| 678 | } |
| 679 | |
| 680 | fn dumpObject( |
| 681 | d: *const DumpContext, |
| 682 | is_image: bool, |
| 683 | obj_name: []const u8, |
| 684 | ) !void { |
| 685 | const gpa = d.gpa; |
| 686 | const fr = d.fr; |
| 687 | const w = d.w; |
| 688 | |
| 689 | const file_location = fr.logicalPos(); |
| 690 | const r = &fr.interface; |
| 691 | const header = r.takeStruct(std.coff.Header, .little) catch |err| |
| 692 | return d.failParse("unable to read COFF header: {t}", .{err}); |
| 693 | |
| 694 | if (d.opts.file_headers) { |
| 695 | if (d.element(.@"header-name")) try w.writeAll("COFF Header:\n"); |
| 696 | try dumpHeader(d, std.coff.Header, &header, struct {}); |
| 697 | if (d.element(.newlines)) try w.writeByte('\n'); |
| 698 | } |
| 699 | |
| 700 | switch (header.machine) { |
| 701 | _ => return d.failParse("unknown machine type: {x}", .{header.machine}), |
| 702 | else => {}, |
| 703 | } |
| 704 | |
| 705 | var known_dirs: [DIRECTORY_ENTRY.len]std.coff.ImageDataDirectory = undefined; |
| 706 | const needs_data_dirs = |
| 707 | d.opts.exports or |
| 708 | d.opts.imports or |
| 709 | d.opts.tls; |
| 710 | |
| 711 | const ImageInfo = struct { |
| 712 | data_dirs: []const std.coff.ImageDataDirectory, |
| 713 | magic: std.coff.OptionalHeader.Magic, |
| 714 | image_base: u64, |
| 715 | }; |
| 716 | |
| 717 | const image_info: ?ImageInfo = if (header.size_of_optional_header > 0) image_info: { |
| 718 | if (!d.opts.file_headers and !needs_data_dirs) { |
| 719 | try fr.seekBy(header.size_of_optional_header); |
| 720 | break :image_info null; |
| 721 | } |
| 722 | |
| 723 | if (d.opts.file_headers and d.element(.@"header-name")) |
| 724 | try w.writeAll("COFF Optional Header:\n"); |
| 725 | |
| 726 | const magic: std.coff.OptionalHeader.Magic = @fromBackingInt(@intCast(try r.peekInt(u16, .little))); |
| 727 | const num_directory_entries, const image_base = switch (magic) { |
| 728 | inline .PE32, .@"PE32+" => |v| num_data_dirs: { |
| 729 | const OptionalHeader = if (v == .PE32) |
| 730 | std.coff.OptionalHeader.PE32 |
| 731 | else |
| 732 | std.coff.OptionalHeader.@"PE32+"; |
| 733 | |
| 734 | const optional_header = r.takeStruct(OptionalHeader, .little) catch |err| |
| 735 | return d.failParse("unable to read optional header: {t}", .{err}); |
| 736 | |
| 737 | if (d.opts.file_headers) { |
| 738 | try dumpHeader(d, OptionalHeader, &optional_header, struct { |
| 739 | pub fn base_of_code(id: *const DumpContext, h: *const std.coff.OptionalHeader) !void { |
| 740 | const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base; |
| 741 | try dumpRvaField(id, @src().fn_name, h.base_of_code, base); |
| 742 | } |
| 743 | |
| 744 | pub fn address_of_entry_point(id: *const DumpContext, h: *const std.coff.OptionalHeader) !void { |
| 745 | const base = @as(*const OptionalHeader, @ptrCast(@alignCast(h))).image_base; |
| 746 | try dumpRvaField(id, @src().fn_name, h.base_of_code, base); |
| 747 | } |
| 748 | |
| 749 | pub fn major_linker_version(id: *const DumpContext, h: *const std.coff.OptionalHeader) !void { |
| 750 | try dumpVersionField(id.w, "linker_version", h.major_linker_version, h.minor_linker_version); |
| 751 | } |
| 752 | pub fn minor_linker_version(_: *const DumpContext, _: *const std.coff.OptionalHeader) !void {} |
| 753 | |
| 754 | pub fn major_operating_system_version(id: *const DumpContext, h: *const OptionalHeader) !void { |
| 755 | try dumpVersionField( |
| 756 | id.w, |
| 757 | "operating_system_version", |
| 758 | h.major_operating_system_version, |
| 759 | h.minor_operating_system_version, |
| 760 | ); |
| 761 | } |
| 762 | pub fn minor_operating_system_version(_: *const DumpContext, _: *const OptionalHeader) !void {} |
| 763 | |
| 764 | pub fn major_image_version(id: *const DumpContext, h: *const OptionalHeader) !void { |
| 765 | try dumpVersionField(id.w, "image_version", h.major_image_version, h.minor_image_version); |
| 766 | } |
| 767 | pub fn minor_image_version(_: *const DumpContext, _: *const OptionalHeader) !void {} |
| 768 | |
| 769 | pub fn major_subsystem_version(id: *const DumpContext, h: *const OptionalHeader) !void { |
| 770 | try dumpVersionField(id.w, "subsystem_version", h.major_subsystem_version, h.minor_subsystem_version); |
| 771 | } |
| 772 | pub fn minor_subsystem_version(_: *const DumpContext, _: *const OptionalHeader) !void {} |
| 773 | }); |
| 774 | if (d.element(.newlines)) try w.writeByte('\n'); |
| 775 | } |
| 776 | |
| 777 | break :num_data_dirs .{ |
| 778 | optional_header.number_of_rva_and_sizes, |
| 779 | optional_header.image_base, |
| 780 | }; |
| 781 | }, |
| 782 | else => return d.failParse("invalid optional header magic number: {x}", .{magic}), |
| 783 | }; |
| 784 | |
| 785 | if (d.opts.file_headers and d.element(.@"header-name")) |
| 786 | try w.writeAll("Data Directories:\n"); |
| 787 | |
| 788 | for (0..num_directory_entries) |dir_i| { |
| 789 | const dir = r.takeStruct(std.coff.ImageDataDirectory, .little) catch |err| |
| 790 | return d.failParse("unable to read data directory {x}: {t}", .{ dir_i, err }); |
| 791 | |
| 792 | if (dir_i < known_dirs.len) |
| 793 | known_dirs[dir_i] = dir; |
| 794 | |
| 795 | if (d.opts.file_headers) |
| 796 | try w.print( |
| 797 | "{x: >16} {x: >8} {t}\n", |
| 798 | .{ dir.virtual_address, dir.size, @as(DIRECTORY_ENTRY, @fromBackingInt(@intCast(dir_i))) }, |
| 799 | ); |
| 800 | } |
| 801 | if (d.opts.file_headers and d.element(.newlines)) try w.writeByte('\n'); |
| 802 | |
| 803 | break :image_info .{ |
| 804 | .data_dirs = known_dirs[0..@min(known_dirs.len, num_directory_entries)], |
| 805 | .magic = magic, |
| 806 | .image_base = image_base, |
| 807 | }; |
| 808 | } else if (is_image) { |
| 809 | return d.failParse("image did not contain an optional header", .{}); |
| 810 | } else null; |
| 811 | |
| 812 | // Section names in images don't use the string table, as they must fit inline in the header |
| 813 | const load_string_table = (d.opts.strings or !is_image) and header.pointer_to_symbol_table > 0; |
| 814 | const string_table = if (load_string_table) string_table: { |
| 815 | const pos = fr.logicalPos(); |
| 816 | fr.seekTo(file_location + header.pointer_to_symbol_table + header.number_of_symbols * std.coff.Symbol.sizeOf()) catch |err| |
| 817 | return d.failParse("unable to seek to string table: {t}", .{err}); |
| 818 | |
| 819 | const string_table_len = r.peekInt(u32, .little) catch |err| |
| 820 | return d.failParse("unable to read string table length: {t}", .{err}); |
| 821 | |
| 822 | const table = r.readAlloc(gpa, string_table_len) catch |err| |
| 823 | return d.failParse("unable to read string table: {t}", .{err}); |
| 824 | |
| 825 | try fr.seekTo(pos); |
| 826 | break :string_table table; |
| 827 | } else &.{}; |
| 828 | defer gpa.free(string_table); |
| 829 | |
| 830 | if (d.opts.strings) { |
| 831 | if (d.element(.@"table-header")) |
| 832 | try w.print( |
| 833 | \\String Table (0x{x} bytes): |
| 834 | \\ |
| 835 | , .{string_table.len}); |
| 836 | |
| 837 | var sr = Io.Reader.fixed(string_table[@sizeOf(u32)..]); |
| 838 | while (try sr.takeDelimiter(0)) |str| { |
| 839 | try w.writeAll(str); |
| 840 | try w.writeByte('\n'); |
| 841 | } |
| 842 | |
| 843 | if (d.element(.newlines)) try w.writeByte('\n'); |
| 844 | } |
| 845 | |
| 846 | var sections: std.ArrayList(Section) = .empty; |
| 847 | defer sections.deinit(gpa); |
| 848 | var sections_with_data: u16 = 0; |
| 849 | |
| 850 | const load_sections = |
| 851 | d.opts.section_headers or |
| 852 | d.opts.symbols or |
| 853 | d.opts.relocs or |
| 854 | needs_data_dirs; |
| 855 | |
| 856 | if (load_sections) { |
| 857 | if (d.opts.section_headers and d.element(.@"table-header")) |
| 858 | try w.print( |
| 859 | \\Sections in '{s}': |
| 860 | \\Num Name RVA Virt Size Data Size & Data & Relocs & Lines # Relocs # Lines Flags |
| 861 | \\ |
| 862 | , .{obj_name}); |
| 863 | |
| 864 | try sections.resize(gpa, header.number_of_sections); |
| 865 | for (sections.items, 0..) |*section, section_i| { |
| 866 | section.header = r.takeStruct(std.coff.SectionHeader, .little) catch |err| |
| 867 | return d.failParse("unable to read section header {x}: {t}", .{ section_i, err }); |
| 868 | section.name = headerName(&section.header.name, string_table) catch |err| switch (err) { |
| 869 | error.Overflow, |
| 870 | error.InvalidCharacter, |
| 871 | => return d.failParse("unable to parse section name offset '{s}': {t}", .{ |
| 872 | section.name, |
| 873 | err, |
| 874 | }), |
| 875 | error.OutOfBounds => return d.failParse("section name offset '{s}' was out of bounds (>= {x})", .{ |
| 876 | section.name, |
| 877 | string_table.len, |
| 878 | }), |
| 879 | }; |
| 880 | |
| 881 | sections_with_data += @intFromBool(section.header.size_of_raw_data > 0); |
| 882 | if (d.opts.section_headers) { |
| 883 | if (!filterMatches(d.opts.section_filters, section.name)) continue; |
| 884 | const raw_name = std.mem.sliceTo(&section.header.name, 0); |
| 885 | try w.print( |
| 886 | "{x: >3} {s: <8} {f} {f} {f} {f} {f} {f} {f} {f} {x:0>8} |", |
| 887 | .{ |
| 888 | section_i + 1, |
| 889 | raw_name, |
| 890 | fmtIntField(d, section.header.virtual_address, .{ .kind = .va }), |
| 891 | fmtIntField(d, section.header.virtual_size, .{ .kind = .size, .width = .{ .explicit = 9 } }), |
| 892 | fmtIntField(d, section.header.size_of_raw_data, .{ .kind = .size, .width = .{ .explicit = 9 } }), |
| 893 | fmtIntField(d, section.header.pointer_to_raw_data, .{ .kind = .va }), |
| 894 | fmtIntField(d, section.header.pointer_to_relocations, .{ .kind = .va }), |
| 895 | fmtIntField(d, section.header.pointer_to_linenumbers, .{ .kind = .va }), |
| 896 | fmtIntField(d, section.header.number_of_relocations, .{ .kind = .va }), |
| 897 | fmtIntField(d, section.header.number_of_linenumbers, .{ .kind = .va }), |
| 898 | @as(u32, @bitCast(section.header.flags)), |
| 899 | }, |
| 900 | ); |
| 901 | |
| 902 | try dumpFlags(w, "{s}", std.coff.SectionHeader.Flags, &section.header.flags, 1); |
| 903 | if (section.name.len > 8) |
| 904 | try w.print("\n | {s}", .{section.name}); |
| 905 | |
| 906 | try w.writeByte('\n'); |
| 907 | } |
| 908 | } |
| 909 | |
| 910 | if (d.opts.section_headers and d.element(.newlines)) try w.writeByte('\n'); |
| 911 | } |
| 912 | |
| 913 | var symbols: std.ArrayList(struct { |
| 914 | name: []const u8, |
| 915 | section_number: std.coff.SectionNumber, |
| 916 | }) = .empty; |
| 917 | defer symbols.deinit(gpa); |
| 918 | |
| 919 | var name_arena: std.heap.ArenaAllocator = .init(gpa); |
| 920 | defer name_arena.deinit(); |
| 921 | |
| 922 | if (d.opts.relocs) |
| 923 | try symbols.ensureUnusedCapacity(gpa, header.number_of_symbols); |
| 924 | |
| 925 | if (d.opts.symbols or d.opts.relocs) { |
| 926 | if (header.pointer_to_symbol_table > 0) { |
| 927 | fr.seekTo(file_location + header.pointer_to_symbol_table) catch |err| |
| 928 | return d.failParse("unable to seek to symbol table: {t}", .{err}); |
| 929 | |
| 930 | if (d.opts.symbols and d.element(.@"table-header")) |
| 931 | try w.print( |
| 932 | \\Symbols in '{s}': |
| 933 | \\ Ord Value Sect Type Storage Name |
| 934 | \\ |
| 935 | , .{obj_name}); |
| 936 | |
| 937 | const symbol_size = std.coff.Symbol.sizeOf(); |
| 938 | var symbol_i: u32 = 0; |
| 939 | while (symbol_i < header.number_of_symbols) { |
| 940 | var symbol: std.coff.Symbol = undefined; |
| 941 | const symbol_bytes = r.take(symbol_size) catch |err| |
| 942 | return d.failParse("unable to read symbol {x}: {t}", .{ symbol_i, err }); |
| 943 | |
| 944 | @memcpy(std.mem.asBytes(&symbol)[0..symbol_size], symbol_bytes); |
| 945 | if (native_endian != .little) |
| 946 | std.mem.byteSwapAllFields(std.coff.Symbol, &symbol); |
| 947 | |
| 948 | const aux_symbols = if (symbol.number_of_aux_symbols > 0) |
| 949 | try r.take(symbol_size * symbol.number_of_aux_symbols) |
| 950 | else |
| 951 | &.{}; |
| 952 | defer symbol_i += symbol.number_of_aux_symbols + 1; |
| 953 | |
| 954 | const name = if (std.mem.eql(u8, symbol.name[0..4], "\x00\x00\x00\x00")) name: { |
| 955 | const index = std.mem.readInt(u32, symbol.name[4..], .little); |
| 956 | if (index >= string_table.len) |
| 957 | return d.failParse("invalid name offset for symbol {x} ({x} >= {x})", .{ |
| 958 | symbol_i, |
| 959 | index, |
| 960 | string_table.len, |
| 961 | }); |
| 962 | break :name std.mem.sliceTo(string_table[index..], 0); |
| 963 | } else try name_arena.allocator().dupe(u8, std.mem.sliceTo(&symbol.name, 0)); |
| 964 | |
| 965 | if (d.opts.relocs) |
| 966 | symbols.appendNTimesAssumeCapacity(.{ |
| 967 | .name = name, |
| 968 | .section_number = symbol.section_number, |
| 969 | }, 1 + symbol.number_of_aux_symbols); |
| 970 | |
| 971 | if (!d.opts.symbols or !filterMatches(d.opts.symbol_filters, name)) |
| 972 | continue; |
| 973 | |
| 974 | try w.print("{f} {x:0>8} ", .{ |
| 975 | fmtIntField(d, @as(u16, @intCast(symbol_i)), .{ .kind = .ord }), |
| 976 | symbol.value, |
| 977 | }); |
| 978 | try switch (symbol.section_number) { |
| 979 | .UNDEFINED => w.writeAll("UNDEF"), |
| 980 | .ABSOLUTE => w.writeAll(" ABS"), |
| 981 | .DEBUG => w.writeAll("DEBUG"), |
| 982 | else => |v| { |
| 983 | const backing = @backingInt(v); |
| 984 | const fmt = "{x: >5}"; |
| 985 | if (backing >= 0) |
| 986 | try w.print(fmt, .{@as(u15, @intCast(backing))}) |
| 987 | else |
| 988 | try w.print(fmt, .{backing}); |
| 989 | }, |
| 990 | }; |
| 991 | |
| 992 | try w.print("{t: >5}", .{symbol.type.base_type}); |
| 993 | if (switch (symbol.type.complex_type) { |
| 994 | .NULL => " ", |
| 995 | .POINTER => "* ", |
| 996 | .FUNCTION => "()", |
| 997 | .ARRAY => "[]", |
| 998 | else => null, |
| 999 | }) |suffix| try w.writeAll(suffix) else try w.print("{x}", .{symbol.type.complex_type}); |
| 1000 | |
| 1001 | try w.print("{t: >16} | {s}\n", .{ symbol.storage_class, name }); |
| 1002 | |
| 1003 | for (0..symbol.number_of_aux_symbols) |aux_i| { |
| 1004 | _ = aux_i; |
| 1005 | try w.writeAll(" |"); |
| 1006 | |
| 1007 | if (symbol.storage_class == .EXTERNAL and |
| 1008 | symbol.type == std.coff.SymType{ |
| 1009 | .complex_type = .FUNCTION, |
| 1010 | .base_type = .NULL, |
| 1011 | } and |
| 1012 | @backingInt(symbol.section_number) > 0) |
| 1013 | { |
| 1014 | try w.writeAll("TODO function aux symbol"); |
| 1015 | } else if (symbol.type == std.coff.SymType{ |
| 1016 | .complex_type = .FUNCTION, |
| 1017 | .base_type = .NULL, |
| 1018 | } and |
| 1019 | (std.mem.eql(u8, name, ".bf") or std.mem.eql(u8, name, ".ef"))) |
| 1020 | { |
| 1021 | try w.writeAll("TODO bf / ef aux symbol"); |
| 1022 | } else if (symbol.storage_class == .WEAK_EXTERNAL and symbol.section_number == .UNDEFINED) { |
| 1023 | if (symbol.value != 0) |
| 1024 | return d.failParse( |
| 1025 | "invalid value 0x{x} for weak external symbol 0x{x}", |
| 1026 | .{ symbol.value, symbol_i }, |
| 1027 | ); |
| 1028 | |
| 1029 | var weak_external: std.coff.WeakExternalDefinition = undefined; |
| 1030 | @memcpy(std.mem.asBytes(&weak_external)[0..symbol_size], aux_symbols[0..symbol_size]); |
| 1031 | if (native_endian != .little) |
| 1032 | std.mem.byteSwapAllFields(std.coff.WeakExternalDefinition, &weak_external); |
| 1033 | |
| 1034 | if (weak_external.tag_index >= header.number_of_symbols) |
| 1035 | return d.failParse( |
| 1036 | "invalid tag_index 0x{x} for weak external symbol 0x{x}", |
| 1037 | .{ weak_external.tag_index, symbol_i }, |
| 1038 | ); |
| 1039 | |
| 1040 | if (d.redacted(.ord)) |
| 1041 | try w.print(" Weak External [falls back to relative ordinal {x:0>8} via {t}]", .{ |
| 1042 | @as(i64, weak_external.tag_index) - symbol_i, |
| 1043 | weak_external.flag, |
| 1044 | }) |
| 1045 | else |
| 1046 | try w.print(" Weak External [falls back to ordinal {x:0>8} via {t}]", .{ |
| 1047 | weak_external.tag_index, |
| 1048 | weak_external.flag, |
| 1049 | }); |
| 1050 | } else if (symbol.storage_class == .FILE) { |
| 1051 | if (!std.mem.eql(u8, name, ".file")) { |
| 1052 | try w.print(" !! unexpected symbol name '{s}' for file symbol 0x{x}", .{ name, symbol_i }); |
| 1053 | continue; |
| 1054 | } |
| 1055 | |
| 1056 | const filename = std.mem.sliceTo(aux_symbols, 0); |
| 1057 | try w.print(" File '{s}'", .{filename}); |
| 1058 | break; |
| 1059 | } else if (symbol.storage_class == .STATIC and |
| 1060 | symbol.type == std.coff.SymType{ |
| 1061 | .complex_type = .NULL, |
| 1062 | .base_type = .NULL, |
| 1063 | } and |
| 1064 | symbol.value == 0 and |
| 1065 | switch (symbol.section_number) { |
| 1066 | .UNDEFINED, .DEBUG, .ABSOLUTE => false, |
| 1067 | else => |sn| @backingInt(sn) > 0, |
| 1068 | }) |
| 1069 | { |
| 1070 | const section_i: u15 = @intCast(@backingInt(symbol.section_number) - 1); |
| 1071 | try w.writeAll(" Section "); |
| 1072 | |
| 1073 | if (section_i >= sections.items.len) { |
| 1074 | try w.print(" !! invalid section number: {x}", .{section_i}); |
| 1075 | continue; |
| 1076 | } |
| 1077 | |
| 1078 | var section_def: std.coff.SectionDefinition = undefined; |
| 1079 | @memcpy(std.mem.asBytes(&section_def)[0..symbol_size], aux_symbols[0..symbol_size]); |
| 1080 | if (native_endian != .little) |
| 1081 | std.mem.byteSwapAllFields(std.coff.SectionDefinition, &section_def); |
| 1082 | |
| 1083 | const section = &sections.items[section_i]; |
| 1084 | if (section_def.number_of_relocations != section.header.number_of_relocations) { |
| 1085 | try w.print( |
| 1086 | " !! relocation count did not match section header: {d} vs {d}", |
| 1087 | .{ section_def.number_of_relocations, section.header.number_of_relocations }, |
| 1088 | ); |
| 1089 | continue; |
| 1090 | } |
| 1091 | |
| 1092 | if (section_def.number_of_linenumbers != section.header.number_of_linenumbers) { |
| 1093 | try w.print( |
| 1094 | " !! line number count did not match section header: {d} vs {d}", |
| 1095 | .{ section_def.number_of_linenumbers, section.header.number_of_linenumbers }, |
| 1096 | ); |
| 1097 | continue; |
| 1098 | } |
| 1099 | |
| 1100 | try w.print(" [size {f} chksum {x:0>8} relocs {x:0>4} lines {x:0>4}]", .{ |
| 1101 | fmtIntField(d, section_def.length, .{ .kind = .size, .zero_fill = true }), |
| 1102 | section_def.checksum, |
| 1103 | section_def.number_of_relocations, |
| 1104 | section_def.number_of_linenumbers, |
| 1105 | }); |
| 1106 | |
| 1107 | switch (section_def.selection) { |
| 1108 | .NONE => {}, |
| 1109 | else => |selection| { |
| 1110 | try w.print(" COMDAT({t}", .{selection}); |
| 1111 | if (selection == .ASSOCIATIVE) |
| 1112 | try w.print("->{x}", .{section_def.number}); |
| 1113 | try w.writeAll(")"); |
| 1114 | }, |
| 1115 | } |
| 1116 | } |
| 1117 | |
| 1118 | try w.writeByte('\n'); |
| 1119 | } |
| 1120 | } |
| 1121 | |
| 1122 | if (d.opts.symbols and d.element(.newlines)) try w.writeByte('\n'); |
| 1123 | } else if (d.opts.symbols) { |
| 1124 | try w.writeAll("No symbol table found\n"); |
| 1125 | } |
| 1126 | } |
| 1127 | |
| 1128 | if (d.opts.relocs) { |
| 1129 | const relocation_size = std.coff.Relocation.sizeOf(); |
| 1130 | |
| 1131 | for (sections.items, 0..) |section, section_i| { |
| 1132 | if (section.header.pointer_to_relocations == 0) continue; |
| 1133 | |
| 1134 | if (d.element(.@"table-header")) |
| 1135 | try w.print( |
| 1136 | \\Relocs for section {x} '{s}' in {s}: |
| 1137 | \\ Offset Type Symbol -> Sect Name |
| 1138 | \\ |
| 1139 | , .{ section_i + 1, section.name, obj_name }); |
| 1140 | |
| 1141 | fr.seekTo(file_location + section.header.pointer_to_relocations) catch |err| |
| 1142 | return d.failParse("unable to seek to section {x} relocation table: {t}", .{ section_i + 1, err }); |
| 1143 | |
| 1144 | for (0..section.header.number_of_relocations) |reloc_i| { |
| 1145 | var reloc: std.coff.Relocation = undefined; |
| 1146 | @memcpy(std.mem.asBytes(&reloc)[0..relocation_size], try r.take(relocation_size)); |
| 1147 | if (native_endian != .little) |
| 1148 | std.mem.byteSwapAllFields(std.coff.Relocation, &reloc); |
| 1149 | |
| 1150 | const sym = &symbols.items[reloc.symbol_table_index]; |
| 1151 | if (!filterMatches(d.opts.symbol_filters, sym.name)) |
| 1152 | continue; |
| 1153 | |
| 1154 | try w.print("{f} ", .{ |
| 1155 | fmtIntField(d, reloc.virtual_address, .{ .kind = .va, .zero_fill = true }), |
| 1156 | }); |
| 1157 | switch (header.machine) { |
| 1158 | _ => unreachable, |
| 1159 | inline else => |m| switch (m.RelocationType()) { |
| 1160 | void => try w.writeAll("(unknown arch)"), |
| 1161 | else => |RelocationType| try w.print( |
| 1162 | "{t: <17} ", |
| 1163 | .{@as(RelocationType, @fromBackingInt(@intCast(reloc.type)))}, |
| 1164 | ), |
| 1165 | }, |
| 1166 | } |
| 1167 | |
| 1168 | if (reloc.symbol_table_index >= symbols.items.len) |
| 1169 | return d.failParse( |
| 1170 | "reloc {x} in section {x} has out-of-bounds symbol index {x}", |
| 1171 | .{ reloc_i, section_i + 1, reloc.symbol_table_index }, |
| 1172 | ); |
| 1173 | |
| 1174 | try w.print("{f} {f} | {s}\n", .{ |
| 1175 | fmtIntField(d, reloc.symbol_table_index, .{ .kind = .ord }), |
| 1176 | fmtSectionNumber(sym.section_number), |
| 1177 | sym.name, |
| 1178 | }); |
| 1179 | } |
| 1180 | if (d.element(.newlines)) try w.writeByte('\n'); |
| 1181 | } |
| 1182 | } |
| 1183 | |
| 1184 | // Sections indices with raw data, sorted by RVA |
| 1185 | const rva_index = if (needs_data_dirs) rva_index: { |
| 1186 | const rva_index = try gpa.alloc(u16, sections_with_data); |
| 1187 | var indices_i: u16 = 0; |
| 1188 | for (sections.items, 0..) |*section, i| { |
| 1189 | if (section.header.size_of_raw_data == 0) continue; |
| 1190 | rva_index[indices_i] = @intCast(i); |
| 1191 | indices_i += 1; |
| 1192 | } |
| 1193 | |
| 1194 | const Context = struct { |
| 1195 | indices: []u16, |
| 1196 | sections: []const Section, |
| 1197 | |
| 1198 | pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool { |
| 1199 | return ctx.sections[ctx.indices[lhs]].header.virtual_address < |
| 1200 | ctx.sections[ctx.indices[rhs]].header.virtual_address; |
| 1201 | } |
| 1202 | |
| 1203 | pub fn swap(ctx: @This(), lhs: usize, rhs: usize) void { |
| 1204 | std.mem.swap(u16, &ctx.indices[lhs], &ctx.indices[rhs]); |
| 1205 | } |
| 1206 | }; |
| 1207 | |
| 1208 | std.sort.pdqContext(0, rva_index.len, Context{ |
| 1209 | .indices = rva_index, |
| 1210 | .sections = sections.items, |
| 1211 | }); |
| 1212 | |
| 1213 | break :rva_index rva_index; |
| 1214 | } else &.{}; |
| 1215 | defer gpa.free(rva_index); |
| 1216 | |
| 1217 | if (d.opts.exports) exports: { |
| 1218 | if (try seekToDataDirectory( |
| 1219 | d, |
| 1220 | rva_index, |
| 1221 | sections.items, |
| 1222 | (image_info orelse { |
| 1223 | try w.writeAll("COFF objects do not contain an export data directory"); |
| 1224 | break :exports; |
| 1225 | }).data_dirs, |
| 1226 | .EXPORT, |
| 1227 | )) |section_index| { |
| 1228 | const export_dir = r.takeStruct(std.coff.ExportDirectoryTable, .little) catch |err| |
| 1229 | return d.failParse("unable to read export directory: {t}", .{err}); |
| 1230 | |
| 1231 | try w.print("Export directory:\n", .{}); |
| 1232 | try dumpHeader(d, std.coff.ExportDirectoryTable, &export_dir, struct { |
| 1233 | pub fn major_version(id: *const DumpContext, h: *const std.coff.ExportDirectoryTable) !void { |
| 1234 | try dumpVersionField(id.w, "version", h.major_version, h.minor_version); |
| 1235 | } |
| 1236 | pub fn minor_version(_: *const DumpContext, _: *const std.coff.ExportDirectoryTable) !void {} |
| 1237 | }); |
| 1238 | |
| 1239 | const section = sections.items[section_index]; |
| 1240 | const name_loc = section.rvaFileOffset(export_dir.name_rva) catch |
| 1241 | return d.failParse( |
| 1242 | "export name rva 0x{x} was not within the export section", |
| 1243 | .{export_dir.name_rva}, |
| 1244 | ); |
| 1245 | |
| 1246 | const eat_loc = section.rvaFileOffset(export_dir.export_address_table_rva) catch |
| 1247 | return d.failParse( |
| 1248 | "export address table rva 0x{x} was not within the export section", |
| 1249 | .{export_dir.export_address_table_rva}, |
| 1250 | ); |
| 1251 | |
| 1252 | const name_pointer_loc = section.rvaFileOffset(export_dir.name_pointer_table_rva) catch |
| 1253 | return d.failParse( |
| 1254 | "export name pointer table rva 0x{x} was not within the export section", |
| 1255 | .{export_dir.name_pointer_table_rva}, |
| 1256 | ); |
| 1257 | |
| 1258 | const ord_loc = section.rvaFileOffset(export_dir.ordinal_table_rva) catch |
| 1259 | return d.failParse( |
| 1260 | "export ordinal table rva 0x{x} was not within the export section", |
| 1261 | .{export_dir.ordinal_table_rva}, |
| 1262 | ); |
| 1263 | |
| 1264 | // All the variable length fields should be contained within this directory. |
| 1265 | // Read it entirely to avoid needing to seek per-name when iterating. |
| 1266 | const dir = image_info.?.data_dirs[@backingInt(DIRECTORY_ENTRY.EXPORT)]; |
| 1267 | const dir_end_rva = dir.virtual_address + dir.size; |
| 1268 | const dir_loc = fr.logicalPos(); |
| 1269 | const dir_slice = try r.readAlloc(gpa, dir.size); |
| 1270 | defer gpa.free(dir_slice); |
| 1271 | |
| 1272 | const dll_name = std.mem.sliceTo(dir_slice[name_loc - dir_loc ..], 0); |
| 1273 | if (d.element(.@"table-header")) |
| 1274 | try w.print( |
| 1275 | \\ |
| 1276 | \\Exports from {s}: |
| 1277 | \\ Ord Hint RVA Name |
| 1278 | \\ |
| 1279 | , .{dll_name}); |
| 1280 | |
| 1281 | const name_pointers = dir_slice[name_pointer_loc - dir_loc ..][0 .. export_dir.number_of_names * @sizeOf(u32)]; |
| 1282 | const ords = dir_slice[ord_loc - dir_loc ..][0 .. export_dir.number_of_names * @sizeOf(u16)]; |
| 1283 | const addrs = dir_slice[eat_loc - dir_loc ..][0 .. export_dir.number_of_entries * @sizeOf(u32)]; |
| 1284 | const name_rva_to_offset = dir.virtual_address + @sizeOf(std.coff.ExportDirectoryTable); |
| 1285 | for (0..export_dir.number_of_names) |name_i| { |
| 1286 | const name_rva = std.mem.readInt(u32, name_pointers[name_i * @sizeOf(u32) ..][0..@sizeOf(u32)], .little); |
| 1287 | const name = std.mem.sliceTo(dir_slice[name_rva - name_rva_to_offset ..], 0); |
| 1288 | if (!filterMatches(d.opts.symbol_filters, name)) |
| 1289 | continue; |
| 1290 | |
| 1291 | const ord = std.mem.readInt(u16, ords[name_i * @sizeOf(u16) ..][0..@sizeOf(u16)], .little); |
| 1292 | const addr = std.mem.readInt(u32, addrs[@as(u32, ord) * @sizeOf(u32) ..][0..@sizeOf(u32)], .little); |
| 1293 | |
| 1294 | try w.print("{f} {f} ", .{ |
| 1295 | fmtIntField(d, @as(u16, @intCast(export_dir.ordinal_base + ord)), .{ .kind = .ord }), |
| 1296 | fmtIntField(d, @as(u16, @intCast(name_i)), .{ .kind = .ord }), |
| 1297 | }); |
| 1298 | const is_forwarder = addr >= dir.virtual_address and addr < dir_end_rva; |
| 1299 | if (is_forwarder) { |
| 1300 | try w.writeAll("forwards"); |
| 1301 | } else { |
| 1302 | try w.print("{f}", .{fmtIntField(d, addr, .{ .kind = .rva })}); |
| 1303 | } |
| 1304 | |
| 1305 | try w.print(" | {s}", .{name}); |
| 1306 | if (is_forwarder) |
| 1307 | try w.print(" -> {s}", .{std.mem.sliceTo(dir_slice[addr - name_rva_to_offset ..], 0)}); |
| 1308 | try w.writeByte('\n'); |
| 1309 | } |
| 1310 | } |
| 1311 | } |
| 1312 | |
| 1313 | if (d.opts.imports) imports: { |
| 1314 | if (try seekToDataDirectory( |
| 1315 | d, |
| 1316 | rva_index, |
| 1317 | sections.items, |
| 1318 | (image_info orelse { |
| 1319 | try w.writeAll("COFF objects do not contain an import data directory"); |
| 1320 | break :imports; |
| 1321 | }).data_dirs, |
| 1322 | .IMPORT, |
| 1323 | )) |_| { |
| 1324 | const Entry = std.coff.ImportDirectoryEntry; |
| 1325 | var directory_entries: std.ArrayList(Entry) = .empty; |
| 1326 | defer directory_entries.deinit(gpa); |
| 1327 | while (true) { |
| 1328 | const entry = r.takeStruct(Entry, .little) catch |err| |
| 1329 | return d.failParse( |
| 1330 | "unable to read import directory entry {x}: {t}", |
| 1331 | .{ directory_entries.items.len, err }, |
| 1332 | ); |
| 1333 | |
| 1334 | if (std.mem.allEqual(u8, std.mem.asBytes(&entry), 0)) break; |
| 1335 | (try directory_entries.addOne(gpa)).* = entry; |
| 1336 | } |
| 1337 | |
| 1338 | for (directory_entries.items) |entry| { |
| 1339 | const name_section = sectionContainingRva( |
| 1340 | rva_index, |
| 1341 | sections.items, |
| 1342 | entry.name_rva, |
| 1343 | ) orelse |
| 1344 | return d.failParse( |
| 1345 | "import directory entry name rva 0x{x} was not found in any section", |
| 1346 | .{entry.name_rva}, |
| 1347 | ); |
| 1348 | |
| 1349 | const name_loc = sections.items[name_section].rvaFileOffset( |
| 1350 | entry.name_rva, |
| 1351 | ) catch unreachable; |
| 1352 | fr.seekTo(name_loc) catch |err| |
| 1353 | return d.failParse( |
| 1354 | "unable to seek to import directory entry name at 0x{x}: {t}", |
| 1355 | .{ name_loc, err }, |
| 1356 | ); |
| 1357 | |
| 1358 | const dll_name = (try r.takeDelimiter(0)).?; |
| 1359 | |
| 1360 | if (d.element(.@"header-name")) |
| 1361 | try w.print("Import table entry for {s}:\n", .{dll_name}); |
| 1362 | try dumpHeader(d, Entry, &entry, struct {}); |
| 1363 | |
| 1364 | if (d.element(.@"table-header")) |
| 1365 | try w.print( |
| 1366 | \\ |
| 1367 | \\ Ord Hint Name |
| 1368 | \\ |
| 1369 | , .{}); |
| 1370 | |
| 1371 | const ilt_section = sectionContainingRva( |
| 1372 | rva_index, |
| 1373 | sections.items, |
| 1374 | entry.import_lookup_table_rva, |
| 1375 | ) orelse |
| 1376 | return d.failParse( |
| 1377 | "import directory entry ilt rva 0x{x} was not found in any section", |
| 1378 | .{entry.import_lookup_table_rva}, |
| 1379 | ); |
| 1380 | |
| 1381 | const ilt_loc = sections.items[ilt_section].rvaFileOffset( |
| 1382 | entry.import_lookup_table_rva, |
| 1383 | ) catch unreachable; |
| 1384 | fr.seekTo(ilt_loc) catch |err| |
| 1385 | return d.failParse( |
| 1386 | "unable to seek to import directory ilt at 0x{x}: {t}", |
| 1387 | .{ ilt_loc, err }, |
| 1388 | ); |
| 1389 | |
| 1390 | switch (image_info.?.magic) { |
| 1391 | _ => try w.writeAll("(unknown magic)"), |
| 1392 | inline else => |m| { |
| 1393 | const TableEntry = std.coff.ImportLookupTableEntry(m); |
| 1394 | const null_entry: TableEntry = @bitCast(@as(@typeInfo(TableEntry).@"struct".backing_integer.?, 0)); |
| 1395 | |
| 1396 | var ilt_entries: std.ArrayList(TableEntry) = .empty; |
| 1397 | defer ilt_entries.deinit(gpa); |
| 1398 | while (true) { |
| 1399 | const table_entry = r.takeStruct(TableEntry, .little) catch |err| |
| 1400 | return d.failParse( |
| 1401 | "unable to read ilt entry {s}:{x}: {t}", |
| 1402 | .{ dll_name, ilt_entries.items.len, err }, |
| 1403 | ); |
| 1404 | if (table_entry == null_entry) break; |
| 1405 | (try ilt_entries.addOne(gpa)).* = table_entry; |
| 1406 | } |
| 1407 | |
| 1408 | for (ilt_entries.items, 0..) |ilt_entry, ilt_entry_i| { |
| 1409 | if (ilt_entry.is_ordinal) { |
| 1410 | try w.print("{x: >4}", .{ilt_entry.payload.ordinal.ordinal}); |
| 1411 | } else { |
| 1412 | const hint_section = sectionContainingRva( |
| 1413 | rva_index, |
| 1414 | sections.items, |
| 1415 | ilt_entry.payload.hint_name_rva, |
| 1416 | ) orelse |
| 1417 | return d.failParse( |
| 1418 | "import directory ilt entry 0x{x}'s hint rva 0x{x} was not found in any section", |
| 1419 | .{ ilt_entry_i, ilt_entry.payload.hint_name_rva }, |
| 1420 | ); |
| 1421 | |
| 1422 | const hint_loc = sections.items[hint_section].rvaFileOffset( |
| 1423 | ilt_entry.payload.hint_name_rva, |
| 1424 | ) catch unreachable; |
| 1425 | fr.seekTo(hint_loc) catch |err| |
| 1426 | return d.failParse( |
| 1427 | "unable to seek to ilt entry 0x{x}'s hint at 0x{x}: {t}", |
| 1428 | .{ ilt_entry_i, hint_loc, err }, |
| 1429 | ); |
| 1430 | |
| 1431 | const hint = r.takeInt(u16, .little) catch |err| |
| 1432 | return d.failParse( |
| 1433 | "unable to read import directory ilt entry 0x{x}'s hint: {t}", |
| 1434 | .{ ilt_entry_i, err }, |
| 1435 | ); |
| 1436 | |
| 1437 | const name = r.takeDelimiter(0) catch |err| |
| 1438 | return d.failParse( |
| 1439 | "unable to read import directory ilt entry 0x{x}'s name: {t}", |
| 1440 | .{ ilt_entry_i, err }, |
| 1441 | ); |
| 1442 | |
| 1443 | try w.print(" {x: >4} | {s}\n", .{ hint, name.? }); |
| 1444 | } |
| 1445 | } |
| 1446 | if (d.element(.newlines)) try w.writeByte('\n'); |
| 1447 | }, |
| 1448 | } |
| 1449 | } |
| 1450 | } |
| 1451 | } |
| 1452 | |
| 1453 | if (d.opts.tls) tls: { |
| 1454 | if (try seekToDataDirectory( |
| 1455 | d, |
| 1456 | rva_index, |
| 1457 | sections.items, |
| 1458 | (image_info orelse { |
| 1459 | try w.writeAll("COFF objects do not contain a TLS data directory"); |
| 1460 | break :tls; |
| 1461 | }).data_dirs, |
| 1462 | .TLS, |
| 1463 | )) |_| { |
| 1464 | switch (image_info.?.magic) { |
| 1465 | _ => try w.writeAll("(unknown magic)"), |
| 1466 | inline else => |m| { |
| 1467 | const TlsDirectoryEntry = std.coff.TlsDirectoryEntry(m); |
| 1468 | const tls_entry = r.takeStruct(TlsDirectoryEntry, .little) catch |err| |
| 1469 | return d.failParse("unable to read tls directory: {t}", .{err}); |
| 1470 | |
| 1471 | try w.writeAll("TLS Directory:\n"); |
| 1472 | try dumpHeader(d, TlsDirectoryEntry, &tls_entry, struct {}); |
| 1473 | |
| 1474 | try w.writeAll(" | "); |
| 1475 | if (tls_entry.characteristics.alignment == .NONE) { |
| 1476 | try w.writeAll("Alignment not specified"); |
| 1477 | } else { |
| 1478 | try w.print( |
| 1479 | "Alignment: {d}", |
| 1480 | .{tls_entry.characteristics.alignment.toByteUnits().?}, |
| 1481 | ); |
| 1482 | } |
| 1483 | |
| 1484 | try w.writeAll( |
| 1485 | \\ |
| 1486 | \\ |
| 1487 | \\TLS Callbacks: |
| 1488 | \\ Address |
| 1489 | \\ |
| 1490 | ); |
| 1491 | |
| 1492 | const callbacks_rva: u32 = @intCast(tls_entry.callbacks_va - image_info.?.image_base); |
| 1493 | const section_index = sectionContainingRva( |
| 1494 | rva_index, |
| 1495 | sections.items, |
| 1496 | callbacks_rva, |
| 1497 | ) orelse |
| 1498 | return d.failParse( |
| 1499 | "tls callbacks rva 0x{x} was not found in any section", |
| 1500 | .{callbacks_rva}, |
| 1501 | ); |
| 1502 | |
| 1503 | const callbacks_loc = sections.items[section_index] |
| 1504 | .rvaFileOffset(callbacks_rva) catch unreachable; |
| 1505 | |
| 1506 | fr.seekTo(callbacks_loc) catch |err| |
| 1507 | return d.failParse( |
| 1508 | "unable to seek to tls callbacks array at offset 0x{x}: {t}", |
| 1509 | .{ callbacks_loc, err }, |
| 1510 | ); |
| 1511 | |
| 1512 | while (true) { |
| 1513 | const callback_va = r.takeInt(@FieldType(TlsDirectoryEntry, "callbacks_va"), .little) catch |err| |
| 1514 | return d.failParse( |
| 1515 | "unable to read tls callbacks array: {t}", |
| 1516 | .{err}, |
| 1517 | ); |
| 1518 | |
| 1519 | try w.print("{f}\n", .{fmtIntField(d, callback_va, .{ .kind = .va })}); |
| 1520 | if (callback_va == 0) break; |
| 1521 | } |
| 1522 | if (d.element(.newlines)) try w.writeByte('\n'); |
| 1523 | }, |
| 1524 | } |
| 1525 | } |
| 1526 | } |
| 1527 | } |
| 1528 | |
| 1529 | fn seekToDataDirectory( |
| 1530 | d: *const DumpContext, |
| 1531 | rva_index: []const u16, |
| 1532 | sections: []const Section, |
| 1533 | data_dirs: []const std.coff.ImageDataDirectory, |
| 1534 | entry: DIRECTORY_ENTRY, |
| 1535 | ) !?u16 { |
| 1536 | if (@backingInt(entry) < data_dirs.len) blk: { |
| 1537 | const rva = data_dirs[@backingInt(entry)].virtual_address; |
| 1538 | if (rva == 0) break :blk; |
| 1539 | |
| 1540 | const section_index = sectionContainingRva(rva_index, sections, rva) orelse |
| 1541 | return d.failParse( |
| 1542 | "{t} directory rva 0x{x} was not found in any section", |
| 1543 | .{ entry, rva }, |
| 1544 | ); |
| 1545 | |
| 1546 | const file_offset = sections[section_index].rvaFileOffset(rva) catch unreachable; |
| 1547 | d.fr.seekTo(file_offset) catch |err| |
| 1548 | return d.failParse( |
| 1549 | "unable to seek to {t} directory at offset 0x{x}: {t}", |
| 1550 | .{ entry, file_offset, err }, |
| 1551 | ); |
| 1552 | |
| 1553 | return section_index; |
| 1554 | } |
| 1555 | |
| 1556 | try d.w.print("{t} directory was not present in optional header\n", .{entry}); |
| 1557 | return null; |
| 1558 | } |
| 1559 | |
| 1560 | fn sectionContainingRva( |
| 1561 | /// Indices into `sections` sorted by rva |
| 1562 | indices: []const u16, |
| 1563 | sections: []const Section, |
| 1564 | rva: u32, |
| 1565 | ) ?u16 { |
| 1566 | const Context = struct { |
| 1567 | rva: u32, |
| 1568 | sections: []const Section, |
| 1569 | |
| 1570 | fn order(ctx: @This(), section_index: u16) std.math.Order { |
| 1571 | const h = &ctx.sections[section_index].header; |
| 1572 | if (ctx.rva < h.virtual_address) return .lt; |
| 1573 | const end = h.virtual_address + h.size_of_raw_data; |
| 1574 | if (ctx.rva >= end) return .gt; |
| 1575 | return .eq; |
| 1576 | } |
| 1577 | }; |
| 1578 | |
| 1579 | const indices_index = std.sort.binarySearch(u16, indices, Context{ |
| 1580 | .rva = rva, |
| 1581 | .sections = sections, |
| 1582 | }, Context.order) orelse return null; |
| 1583 | return @intCast(indices[indices_index]); |
| 1584 | } |
| 1585 | |
| 1586 | fn headerName(raw: *const [8]u8, string_table: []const u8) ![]const u8 { |
| 1587 | return if (raw[0] == '/') name: { |
| 1588 | const name_offset = try std.fmt.parseUnsigned(u24, std.mem.sliceTo(raw[1..], 0), 10); |
| 1589 | if (name_offset >= string_table.len) |
| 1590 | return error.OutOfBounds; |
| 1591 | |
| 1592 | break :name std.mem.sliceTo(string_table[name_offset..], 0); |
| 1593 | } else std.mem.sliceTo(raw, 0); |
| 1594 | } |
| 1595 | |
| 1596 | fn fmtSectionNumber(section_number: std.coff.SectionNumber) std.fmt.Alt(std.coff.SectionNumber, sectionNumberString) { |
| 1597 | return .{ .data = section_number }; |
| 1598 | } |
| 1599 | |
| 1600 | fn sectionNumberString(section_number: std.coff.SectionNumber, w: *std.Io.Writer) std.Io.Writer.Error!void { |
| 1601 | try switch (section_number) { |
| 1602 | .UNDEFINED => w.writeAll("UNDEF"), |
| 1603 | .ABSOLUTE => w.writeAll(" ABS"), |
| 1604 | .DEBUG => w.writeAll("DEBUG"), |
| 1605 | else => |v| { |
| 1606 | const backing = @backingInt(v); |
| 1607 | const fmt = "{x: >5}"; |
| 1608 | if (backing >= 0) |
| 1609 | try w.print(fmt, .{@as(u15, @intCast(backing))}) |
| 1610 | else |
| 1611 | try w.print(fmt, .{backing}); |
| 1612 | }, |
| 1613 | }; |
| 1614 | } |
| 1615 | |
| 1616 | const FormatIntField = struct { |
| 1617 | val: ?u64, |
| 1618 | width: ?usize, |
| 1619 | zero_fill: bool, |
| 1620 | }; |
| 1621 | |
| 1622 | fn fmtIntField( |
| 1623 | d: *const DumpContext, |
| 1624 | val: anytype, |
| 1625 | params: struct { |
| 1626 | kind: ?FieldKind = null, |
| 1627 | width: union(enum) { |
| 1628 | fit_max, |
| 1629 | auto, |
| 1630 | explicit: usize, |
| 1631 | } = .fit_max, |
| 1632 | zero_fill: bool = false, |
| 1633 | }, |
| 1634 | ) std.fmt.Alt(FormatIntField, intFieldString) { |
| 1635 | return .{ |
| 1636 | .data = .{ |
| 1637 | .val = if (d.redacted(params.kind)) null else val, |
| 1638 | .width = switch (params.width) { |
| 1639 | .fit_max => @typeInfo(@TypeOf(val)).int.bits / 4, |
| 1640 | .auto => null, |
| 1641 | .explicit => |w| w, |
| 1642 | }, |
| 1643 | .zero_fill = params.zero_fill, |
| 1644 | }, |
| 1645 | }; |
| 1646 | } |
| 1647 | |
| 1648 | fn intFieldString(field: FormatIntField, w: *std.Io.Writer) std.Io.Writer.Error!void { |
| 1649 | if (field.val) |val| { |
| 1650 | try w.printInt(val, 16, .lower, .{ |
| 1651 | .width = field.width, |
| 1652 | .alignment = .right, |
| 1653 | .fill = if (field.zero_fill) '0' else ' ', |
| 1654 | }); |
| 1655 | } else try w.splatByteAll('x', field.width orelse 1); |
| 1656 | } |
| 1657 | |
| 1658 | fn dumpFlags(w: *Io.Writer, comptime fmt: []const u8, comptime T: type, flags: *const T, cols: u32) !void { |
| 1659 | const s = @typeInfo(T).@"struct"; |
| 1660 | inline for (s.field_names, s.field_types) |field_name, field_type| { |
| 1661 | if (field_type == bool and @field(flags, field_name)) { |
| 1662 | try w.splatByteAll(' ', cols); |
| 1663 | try w.print(fmt, .{field_name}); |
| 1664 | } |
| 1665 | } |
| 1666 | } |
| 1667 | |
| 1668 | fn dumpArchiveHeader(d: *const DumpContext, header: *const ArchiveHeader, pos: u32) !void { |
| 1669 | if (d.element(.@"header-name")) |
| 1670 | try d.w.print("Archive member at offset 0x{x}: '{s}'\n", .{ pos, header.name }); |
| 1671 | try dumpHeader(d, ArchiveHeader, header, struct { |
| 1672 | pub fn name(_: *const DumpContext, _: *const ArchiveHeader) !void {} |
| 1673 | pub fn file_mode(id: *const DumpContext, h: *const ArchiveHeader) !void { |
| 1674 | try id.w.print("{o: >16} file_mode\n", .{h.file_mode}); |
| 1675 | } |
| 1676 | }); |
| 1677 | } |
| 1678 | |
| 1679 | fn fieldKind(name: []const u8) ?FieldKind { |
| 1680 | if (std.mem.endsWith(u8, name, "_rva")) |
| 1681 | return .rva; |
| 1682 | if (std.mem.endsWith(u8, name, "_va") or |
| 1683 | std.mem.endsWith(u8, name, "_address") or |
| 1684 | std.mem.startsWith(u8, name, "pointer_")) |
| 1685 | return .va; |
| 1686 | if (std.mem.startsWith(u8, name, "number_") or |
| 1687 | std.mem.startsWith(u8, name, "size")) |
| 1688 | return .size; |
| 1689 | if (std.mem.startsWith(u8, name, "hint")) |
| 1690 | return .ord; |
| 1691 | return null; |
| 1692 | } |
| 1693 | |
| 1694 | fn dumpHeader( |
| 1695 | d: *const DumpContext, |
| 1696 | comptime T: type, |
| 1697 | header: *const T, |
| 1698 | Custom: type, |
| 1699 | ) !void { |
| 1700 | const s = @typeInfo(T).@"struct"; |
| 1701 | inline for (s.field_names, s.field_types) |field_name, field_type| { |
| 1702 | const val = &@field(header, field_name); |
| 1703 | if (@hasDecl(Custom, field_name)) { |
| 1704 | try @field(Custom, field_name)(d, header); |
| 1705 | } else { |
| 1706 | switch (@typeInfo(field_type)) { |
| 1707 | .int => try d.w.print("{f} {s}\n", .{ fmtIntField(d, val.*, .{ |
| 1708 | .kind = comptime fieldKind(field_name), |
| 1709 | .width = .{ .explicit = 16 }, |
| 1710 | }), field_name }), |
| 1711 | .@"enum" => try d.w.print("{x: >16} {s} ({t})\n", .{ val.*, field_name, val.* }), |
| 1712 | .@"struct" => |s_field| { |
| 1713 | switch (s_field.layout) { |
| 1714 | .auto, |
| 1715 | .@"extern", |
| 1716 | => try dumpHeader(d, field_type, val, Custom), |
| 1717 | .@"packed" => { |
| 1718 | try d.w.print("{x: >16} {s}\n", .{ @as(s_field.backing_integer.?, @bitCast(val.*)), field_name }); |
| 1719 | try dumpFlags(d.w, "| {s}\n", field_type, val, 15); |
| 1720 | }, |
| 1721 | } |
| 1722 | }, |
| 1723 | else => unreachable, |
| 1724 | } |
| 1725 | } |
| 1726 | } |
| 1727 | } |
| 1728 | |
| 1729 | fn dumpVersionField(w: *Io.Writer, name: []const u8, major: anytype, minor: anytype) !void { |
| 1730 | try w.print("{d: >13}.{x:0<2} {s}\n", .{ major, minor, name }); |
| 1731 | } |
| 1732 | |
| 1733 | fn dumpRvaField(d: *const DumpContext, name: []const u8, rva: u64, base: u64) !void { |
| 1734 | try d.w.print("{f} {s} ({f})\n", .{ |
| 1735 | fmtIntField(d, rva, .{ .kind = .rva }), |
| 1736 | name, |
| 1737 | fmtIntField(d, base + rva, .{ .kind = .va }), |
| 1738 | }); |
| 1739 | } |
| 1740 | }; |
| 1741 | |
| 1742 | fn filterMatches(filters: []const []const u8, val: []const u8) bool { |
| 1743 | return for (filters) |filter| { |
| 1744 | if (std.mem.containsAtLeast(u8, val, 1, filter)) break true; |
| 1745 | } else filters.len == 0; |
| 1746 | } |
| 1747 | |
| 1748 | const usage = |
| 1749 | \\Usage: zig objdump [options] file |
| 1750 | \\ |
| 1751 | \\Options: |
| 1752 | \\ -h, --help Print this help and exit |
| 1753 | \\ --all-headers Alias for --file-headers --linker-member=2 --member-headers --section-headers --relocs --symbols |
| 1754 | \\ --exports[=sort] Display exported symbols. |
| 1755 | \\ In the case of COFF import libraries, displays the symbol list and import headers. |
| 1756 | \\ Specify =sort to optionally sort the import headers by symbol name. |
| 1757 | \\ --file-headers Display file-format specific headers |
| 1758 | \\ --imports Display imported symbols |
| 1759 | \\ --linker-member[=1|2|longnames] (Coff) Display contents of the specified archive linker member (default 2) |
| 1760 | \\ --member-headers Display archive member headers |
| 1761 | \\ --elements=[e1],[e2],-[e3],... Select which formatting elements are displayed. Intended for snapshot testing. |
| 1762 | \\ file-type File type summary |
| 1763 | \\ header-name Name that precedes a header block |
| 1764 | \\ member-path Display full member paths. If removed, only basenames will be used. |
| 1765 | \\ newlines Newlines between output sections |
| 1766 | \\ table-header Table headers with column names |
| 1767 | \\ all (default) All of the above |
| 1768 | \\ --only-member=[name] Only consider archive members names that contain [name]. Can be specified multiple times. |
| 1769 | \\ --only-section=[name] Only consider section names that contain [name]. Can be specified multiple times. |
| 1770 | \\ --only-symbol=[name] Only consider symbol names that contain [name]. Can be specified multiple times. |
| 1771 | \\ --redact=[kind] Redact the specified field kind. Intended for snapshot testing. |
| 1772 | \\ rva Relative virtual addresses |
| 1773 | \\ va Virtual addresses and file offsets |
| 1774 | \\ ord Symbol ordinals / hints |
| 1775 | \\ size Sizes and lengths |
| 1776 | \\ all All of the above |
| 1777 | \\ --relocs Display relocations |
| 1778 | \\ -s, --snapshot Alias for --redact=all --elements=-all |
| 1779 | \\ --section-headers Display section headers |
| 1780 | \\ --strings Display string tables |
| 1781 | \\ --symbols Display symbol tables |
| 1782 | \\ --tls Display TLS information |
| 1783 | \\ |
| 1784 | ; |