| ... | ... | @@ -12,11 +12,15 @@ var stdout_buffer: [4000]u8 = undefined; |
| 12 | 12 | const Options = struct { |
| 13 | 13 | input_path: []const u8, |
| 14 | 14 | file_headers: bool, |
| 15 | member_filters: []const []const u8 = &.{}, |
| 16 | member_headers: bool, |
| 15 | 17 | section_filters: []const []const u8 = &.{}, |
| 16 | | section_table: bool, |
| 18 | section_headers: bool, |
| 17 | 19 | strings: bool, |
| 18 | 20 | symbols: bool, |
| 19 | | compact: bool, |
| 21 | |
| 22 | // Coff-specific |
| 23 | linker_member: ?std.coff.ArchiveMemberHeader.Kind, |
| 20 | 24 | }; |
| 21 | 25 | |
| 22 | 26 | pub fn main(init: std.process.Init) !void { |
| ... | ... | @@ -28,12 +32,14 @@ pub fn main(init: std.process.Init) !void { |
| 28 | 32 | |
| 29 | 33 | var opt_input_path: ?[]const u8 = null; |
| 30 | 34 | var opt_file_headers: ?bool = null; |
| 31 | | var opt_section_table: ?bool = null; |
| 35 | var opt_linker_member: ?std.coff.ArchiveMemberHeader.Kind = null; |
| 36 | var opt_member_headers: ?bool = null; |
| 37 | var opt_section_headers: ?bool = null; |
| 32 | 38 | var opt_strings: ?bool = null; |
| 33 | 39 | var opt_symbols: ?bool = null; |
| 34 | 40 | var opt_relocs: ?bool = null; |
| 35 | | var opt_compact: ?bool = null; |
| 36 | 41 | var section_filters: std.ArrayList([]const u8) = .empty; |
| 42 | var member_filters: std.ArrayList([]const u8) = .empty; |
| 37 | 43 | while (i < args.len) : (i += 1) { |
| 38 | 44 | const arg = args[i]; |
| 39 | 45 | if (mem.startsWith(u8, arg, "-")) { |
| ... | ... | @@ -41,19 +47,27 @@ pub fn main(init: std.process.Init) !void { |
| 41 | 47 | return Io.File.stdout().writeStreamingAll(io, usage); |
| 42 | 48 | } else if (mem.eql(u8, arg, "--all-headers")) { |
| 43 | 49 | opt_file_headers = true; |
| 44 | | opt_section_table = true; |
| 50 | opt_member_headers = true; |
| 51 | opt_section_headers = true; |
| 45 | 52 | opt_symbols = true; |
| 46 | 53 | opt_relocs = true; |
| 47 | | } else if (mem.eql(u8, arg, "--compact")) { |
| 48 | | opt_compact = true; |
| 49 | 54 | } else if (mem.eql(u8, arg, "--file-headers")) { |
| 50 | 55 | opt_file_headers = true; |
| 56 | } else if (mem.startsWith(u8, arg, "--linker-member")) { |
| 57 | if (mem.eql(u8, arg["--linker-member".len..], "=1")) |
| 58 | opt_linker_member = .first_linker |
| 59 | else |
| 60 | opt_linker_member = .second_linker; |
| 61 | } else if (mem.eql(u8, arg, "--member-headers")) { |
| 62 | opt_member_headers = true; |
| 51 | 63 | } else if (mem.startsWith(u8, arg, "--only-section=")) { |
| 52 | 64 | (try section_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-section=".len..]); |
| 65 | } else if (mem.startsWith(u8, arg, "--only-member=")) { |
| 66 | (try member_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-member=".len..]); |
| 53 | 67 | } else if (mem.eql(u8, arg, "--relocs")) { |
| 54 | 68 | opt_relocs = true; |
| 55 | 69 | } else if (mem.eql(u8, arg, "--section-headers")) { |
| 56 | | opt_section_table = true; |
| 70 | opt_section_headers = true; |
| 57 | 71 | } else if (mem.eql(u8, arg, "--strings")) { |
| 58 | 72 | opt_strings = true; |
| 59 | 73 | } else if (mem.eql(u8, arg, "--symbols")) { |
| ... | ... | @@ -70,12 +84,14 @@ pub fn main(init: std.process.Init) !void { |
| 70 | 84 | |
| 71 | 85 | const opts: Options = .{ |
| 72 | 86 | .input_path = opt_input_path orelse fatal("missing input file path positional argument", .{}), |
| 73 | | .compact = opt_compact orelse false, |
| 74 | 87 | .file_headers = opt_file_headers orelse false, |
| 75 | 88 | .section_filters = section_filters.items, |
| 76 | | .section_table = opt_section_table orelse false, |
| 89 | .section_headers = opt_section_headers orelse false, |
| 77 | 90 | .strings = opt_strings orelse false, |
| 78 | 91 | .symbols = opt_symbols orelse false, |
| 92 | .member_filters = member_filters.items, |
| 93 | .member_headers = opt_member_headers orelse false, |
| 94 | .linker_member = opt_linker_member, |
| 79 | 95 | }; |
| 80 | 96 | |
| 81 | 97 | var file = std.Io.Dir.cwd().openFile(io, opts.input_path, .{}) catch |err| |
| ... | ... | @@ -85,7 +101,7 @@ pub fn main(init: std.process.Init) !void { |
| 85 | 101 | var buffer: [4096]u8 = undefined; |
| 86 | 102 | var file_reader = file.reader(io, &buffer); |
| 87 | 103 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &stdout_buffer); |
| 88 | | dump(arena, &opts, &file_reader, &stdout_writer.interface) catch |err| switch (err) { |
| 104 | dump(init.gpa, &opts, &file_reader, &stdout_writer.interface) catch |err| switch (err) { |
| 89 | 105 | error.ReadFailed => return file_reader.err.?, |
| 90 | 106 | error.WriteFailed => return stdout_writer.err.?, |
| 91 | 107 | error.UnknownFile => fatal("unrecognized file: {s}", .{opts.input_path}), |
| ... | ... | @@ -95,7 +111,7 @@ pub fn main(init: std.process.Init) !void { |
| 95 | 111 | try stdout_writer.flush(); |
| 96 | 112 | } |
| 97 | 113 | |
| 98 | | fn dump(arena: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void { |
| 114 | fn dump(gpa: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void { |
| 99 | 115 | const r = &fr.interface; |
| 100 | 116 | try r.fill(4); |
| 101 | 117 | elf: { |
| ... | ... | @@ -125,16 +141,16 @@ fn dump(arena: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: |
| 125 | 141 | return error.ParseFailure; |
| 126 | 142 | } |
| 127 | 143 | |
| 128 | | if (!opts.compact) try w.print("{s}: PE/COFF image\n\n", .{std.fs.path.basename(opts.input_path)}); |
| 129 | | return coff.dumpObject(arena, opts, true, fr, w); |
| 144 | try w.print("{s}: PE/COFF image\n\n", .{std.fs.path.basename(opts.input_path)}); |
| 145 | return coff.dumpObject(gpa, opts, true, fr, w); |
| 130 | 146 | } else if (std.mem.eql(u8, ext, ".lib")) { |
| 131 | 147 | r.fill(std.coff.archive_signature.len) catch break :coff; |
| 132 | 148 | if (!mem.eql(u8, r.buffered()[0..std.coff.archive_signature.len], std.coff.archive_signature)) break :coff; |
| 133 | | if (!opts.compact) try w.print("{s}: COFF archive\n\n", .{std.fs.path.basename(opts.input_path)}); |
| 134 | | return coff.dumpArchive(opts, fr, w); |
| 149 | try w.print("{s}: COFF archive\n\n", .{std.fs.path.basename(opts.input_path)}); |
| 150 | return coff.dumpArchive(gpa, opts, fr, w); |
| 135 | 151 | } else if (std.mem.eql(u8, ext, ".obj")) { |
| 136 | | if (!opts.compact) try w.print("{s}: COFF object\n\n", .{std.fs.path.basename(opts.input_path)}); |
| 137 | | return coff.dumpObject(arena, opts, false, fr, w); |
| 152 | try w.print("{s}: COFF object\n\n", .{std.fs.path.basename(opts.input_path)}); |
| 153 | return coff.dumpObject(gpa, opts, false, fr, w); |
| 138 | 154 | } |
| 139 | 155 | } |
| 140 | 156 | return error.UnknownFile; |
| ... | ... | @@ -171,31 +187,249 @@ const wasm = struct { |
| 171 | 187 | }; |
| 172 | 188 | |
| 173 | 189 | const coff = struct { |
| 174 | | fn dumpArchive(opt: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void { |
| 175 | | _ = opt; |
| 176 | | _ = fr; |
| 177 | | try w.writeAll("TODO dump coff archive\n"); |
| 178 | | } |
| 190 | const ArchiveHeader = struct { |
| 191 | name: []const u8, |
| 192 | date: u40, |
| 193 | user_id: u20, |
| 194 | group_id: u20, |
| 195 | file_mode: u24, |
| 196 | size: u34, |
| 197 | |
| 198 | pub fn fromRaw(opts: *const Options, raw_header: *const std.coff.ArchiveMemberHeader, opt_longnames: ?[]const u8) @This() { |
| 199 | const name = raw_header.parseName(opt_longnames) catch |err| switch (err) { |
| 200 | error.BadName => failParse(opts, "malformed member name: '{s}'", .{&raw_header.name}), |
| 201 | error.NoLongNames => failParse(opts, "member uses a long name, but there was no longnames member", .{}), |
| 202 | }; |
| 179 | 203 | |
| 180 | | fn headerName(raw: *[8]u8, string_table: []const u8) ![]const u8 { |
| 181 | | return if (raw[0] == '/') name: { |
| 182 | | const name_offset = try std.fmt.parseUnsigned(u24, raw[1..], 10); |
| 183 | | if (name_offset >= string_table.len) |
| 184 | | return error.OutOfBounds; |
| 204 | return .{ |
| 205 | .name = name, |
| 206 | .date = raw_header.parseDate() catch |err| |
| 207 | failParse(opts, "unable to parse date '{s}' in member '{s}': {t}", .{ raw_header.date, name, err }), |
| 208 | .user_id = raw_header.parseUserId() catch |err| |
| 209 | failParse(opts, "unable to parse user_id '{s}' in member '{s}': {t}", .{ raw_header.user_id, name, err }), |
| 210 | .group_id = raw_header.parseGroupId() catch |err| |
| 211 | failParse(opts, "unable to parse group_id '{s}' in member '{s}': {t}", .{ raw_header.group_id, name, err }), |
| 212 | .file_mode = raw_header.parseFileMode() catch |err| |
| 213 | failParse(opts, "unable to parse file_mode '{s}' in member '{s}': {t}", .{ raw_header.file_mode, name, err }), |
| 214 | .size = raw_header.parseSize() catch |err| |
| 215 | failParse(opts, "unable to parse size '{s}' in member '{s}': {t}", .{ raw_header.size, name, err }), |
| 216 | }; |
| 217 | } |
| 218 | }; |
| 185 | 219 | |
| 186 | | break :name std.mem.sliceTo(string_table[name_offset..], 0); |
| 187 | | } else std.mem.sliceTo(raw, 0); |
| 220 | fn dumpArchive(gpa: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void { |
| 221 | const r = &fr.interface; |
| 222 | r.toss(std.coff.archive_signature.len); |
| 223 | |
| 224 | var members: std.ArrayList(struct { |
| 225 | offset: u32, |
| 226 | }) = .empty; |
| 227 | defer members.deinit(gpa); |
| 228 | var symbol_member_indices: std.ArrayList(u32) = .empty; |
| 229 | defer symbol_member_indices.deinit(gpa); |
| 230 | |
| 231 | var opt_expected_kind: ?std.coff.ArchiveMemberHeader.Kind = .first_linker; |
| 232 | var opt_longnames: ?[]const u8 = null; |
| 233 | defer if (opt_longnames) |l| gpa.free(l); |
| 234 | |
| 235 | var pos = fr.logicalPos(); |
| 236 | const size = try fr.getSize(); |
| 237 | while (pos < size) : (pos = fr.logicalPos()) { |
| 238 | if ((pos & 1) != 0) try r.discardAll(1); |
| 239 | const raw_header = try r.takeStruct(std.coff.ArchiveMemberHeader, .little); |
| 240 | const header: ArchiveHeader = .fromRaw(opts, &raw_header, opt_longnames); |
| 241 | |
| 242 | if (!std.mem.eql(u8, &raw_header.end_of_header, std.coff.archive_end_of_header)) |
| 243 | return failParse(opts, "malformed end-of-header field in member '{s}': {x}", .{ header.name, raw_header.end_of_header }); |
| 244 | |
| 245 | const dump_header = opts.member_headers and filterMatches(opts.member_filters, header.name); |
| 246 | if (dump_header) |
| 247 | try dumpArchiveHeader(w, &header, @intCast(pos)); |
| 248 | |
| 249 | const member_end = fr.logicalPos() + header.size; |
| 250 | if (member_end > size) |
| 251 | return failParse(opts, "out-of-bounds length 0x{x} in member '{s}'", .{ header.size, header.name }); |
| 252 | |
| 253 | if (opt_expected_kind) |expected_kind| switch (expected_kind) { |
| 254 | .first_linker => { |
| 255 | if (!std.mem.eql(u8, header.name, "/")) |
| 256 | return failParse(opts, "expected first linker member, found '{s}'", .{header.name}); |
| 257 | |
| 258 | const num_symbols = try r.takeInt(u32, .big); |
| 259 | if (dump_header) |
| 260 | try w.print( |
| 261 | \\{t: >16} type |
| 262 | \\ | {d} symbols |
| 263 | \\ |
| 264 | , .{ expected_kind, num_symbols }); |
| 265 | |
| 266 | if (opts.linker_member == .first_linker) { |
| 267 | try w.print( |
| 268 | \\Archives symbols ({d}): |
| 269 | \\& Member Symbol |
| 270 | \\ |
| 271 | , .{num_symbols}); |
| 272 | |
| 273 | const offsets = try r.readAlloc(gpa, num_symbols * 4); |
| 274 | defer gpa.free(offsets); |
| 275 | |
| 276 | for (0..num_symbols) |symbol_i| { |
| 277 | const symbol = r.takeDelimiter(0) catch |err| |
| 278 | return failParse(opts, "unable to read first linker member string table: {t}", .{err}); |
| 279 | try w.print("{x: >8} {s}\n", .{ std.mem.readInt(u32, offsets[symbol_i * 4 ..][0..4], .big), symbol.? }); |
| 280 | } |
| 281 | } |
| 282 | if (dump_header) try w.writeByte('\n'); |
| 283 | |
| 284 | try fr.seekTo(member_end); |
| 285 | opt_expected_kind = .second_linker; |
| 286 | continue; |
| 287 | }, |
| 288 | .second_linker => { |
| 289 | if (!std.mem.eql(u8, header.name, "/")) |
| 290 | return failParse(opts, "expected second linker member, found '{s}'", .{header.name}); |
| 291 | |
| 292 | // TODO: Figure out what endianness is actually used, there are no headers to say yet? |
| 293 | |
| 294 | const num_members = try r.takeInt(u32, .little); |
| 295 | pos = fr.logicalPos(); |
| 296 | if (pos + num_members * @sizeOf(u32) > member_end) |
| 297 | return failParse(opts, "invalid member count 0x{x} in second linker member", .{num_members}); |
| 298 | |
| 299 | try members.ensureTotalCapacity(gpa, num_members); |
| 300 | for (0..num_members) |_| |
| 301 | members.addOneAssumeCapacity().* = .{ |
| 302 | .offset = try r.takeInt(u32, .little), |
| 303 | }; |
| 304 | |
| 305 | const num_symbols = try r.takeInt(u32, .little); |
| 306 | pos = fr.logicalPos(); |
| 307 | if (pos + num_symbols * @sizeOf(u16) > member_end) |
| 308 | return failParse(opts, "invalid symbol count 0x{x} in second linker member", .{num_symbols}); |
| 309 | |
| 310 | if (dump_header) |
| 311 | try w.print( |
| 312 | \\{t: >16} type |
| 313 | \\ | {d} symbols |
| 314 | \\ | {d} members |
| 315 | \\ |
| 316 | , .{ expected_kind, num_symbols, num_members }); |
| 317 | |
| 318 | try symbol_member_indices.ensureTotalCapacity(gpa, num_symbols); |
| 319 | for (0..num_symbols) |_| |
| 320 | symbol_member_indices.addOneAssumeCapacity().* = (try r.takeInt(u16, .little)) - 1; |
| 321 | |
| 322 | if (opts.linker_member == .second_linker) { |
| 323 | try w.print( |
| 324 | \\Archive symbols ({d} members, {d} symbols): |
| 325 | \\& Member Symbol |
| 326 | \\ |
| 327 | , .{ num_members, num_symbols }); |
| 328 | |
| 329 | pos = fr.logicalPos(); |
| 330 | var symbol_i: u32 = 0; |
| 331 | while (pos < member_end and symbol_i < num_symbols) : ({ |
| 332 | pos = fr.logicalPos(); |
| 333 | symbol_i += 1; |
| 334 | }) { |
| 335 | const symbol_name = if (r.takeDelimiter(0) catch |err| switch (err) { |
| 336 | error.StreamTooLong => null, |
| 337 | else => |e| return e, |
| 338 | }) |n| n else return failParse(opts, "unterminated string found in second linker member", .{}); |
| 339 | |
| 340 | try w.print("{x: >8} {s}\n", .{ |
| 341 | members.items[symbol_member_indices.items[symbol_i]].offset, |
| 342 | symbol_name, |
| 343 | }); |
| 344 | } |
| 345 | |
| 346 | if (symbol_i != num_symbols) |
| 347 | return failParse( |
| 348 | opts, |
| 349 | " expected {d} entries in second linker member string table, but found {d}", |
| 350 | .{ num_symbols, symbol_i }, |
| 351 | ); |
| 352 | } |
| 353 | |
| 354 | try w.writeByte('\n'); |
| 355 | try fr.seekTo(member_end); |
| 356 | opt_expected_kind = .longnames; |
| 357 | continue; |
| 358 | }, |
| 359 | .longnames => { |
| 360 | // This member is optional |
| 361 | if (std.mem.eql(u8, header.name, "//")) { |
| 362 | opt_longnames = try r.readAlloc(gpa, header.size); |
| 363 | if (dump_header) |
| 364 | try w.print("{t: >16} type\n", .{expected_kind}); |
| 365 | } |
| 366 | |
| 367 | opt_expected_kind = null; |
| 368 | break; |
| 369 | }, |
| 370 | else => unreachable, |
| 371 | }; |
| 372 | } |
| 373 | |
| 374 | if (opt_expected_kind) |expected_kind| switch (expected_kind) { |
| 375 | .first_linker => failParse(opts, "missing first linker member", .{}), |
| 376 | .second_linker => failParse(opts, "missing second linker member", .{}), |
| 377 | else => {}, |
| 378 | }; |
| 379 | |
| 380 | for (members.items, 0..) |member, member_i| { |
| 381 | fr.seekTo(member.offset) catch |err| |
| 382 | failParse(opts, "unable to read member {d} at offset 0x{x}: {t}", .{ member_i, member.offset, err }); |
| 383 | |
| 384 | const raw_header = try r.takeStruct(std.coff.ArchiveMemberHeader, .little); |
| 385 | const header: ArchiveHeader = .fromRaw(opts, &raw_header, opt_longnames); |
| 386 | if (!filterMatches(opts.member_filters, header.name)) continue; |
| 387 | |
| 388 | const member_sig = try r.peek(4); |
| 389 | const machine: std.coff.IMAGE.FILE.MACHINE = |
| 390 | @enumFromInt(std.mem.readInt(u16, member_sig[0..2], .little)); |
| 391 | const sig = std.mem.readInt(u16, member_sig[2..4], .little); |
| 392 | |
| 393 | const is_import = machine == std.coff.IMAGE.FILE.MACHINE.UNKNOWN and sig == 0xffff; |
| 394 | |
| 395 | if (opts.member_headers) { |
| 396 | try dumpArchiveHeader(w, &header, member.offset); |
| 397 | if (is_import) { |
| 398 | try w.writeAll(" Import header type\n"); |
| 399 | // TODO: Dump import header |
| 400 | } else { |
| 401 | try w.writeAll(" COFF object type\n"); |
| 402 | } |
| 403 | try w.writeByte('\n'); |
| 404 | } |
| 405 | |
| 406 | if (opts.section_headers or |
| 407 | opts.file_headers or |
| 408 | opts.strings or |
| 409 | opts.symbols) |
| 410 | { |
| 411 | try w.print("{s}({s}): COFF object\n\n", .{ std.fs.path.basename(opts.input_path), header.name }); |
| 412 | try dumpObject(gpa, opts, false, fr, w); |
| 413 | } |
| 414 | } |
| 188 | 415 | } |
| 189 | 416 | |
| 190 | | fn dumpObject(arena: std.mem.Allocator, opts: *const Options, is_image: bool, fr: *Io.File.Reader, w: *Io.Writer) !void { |
| 417 | fn dumpObject( |
| 418 | gpa: std.mem.Allocator, |
| 419 | opts: *const Options, |
| 420 | is_image: bool, |
| 421 | fr: *Io.File.Reader, |
| 422 | w: *Io.Writer, |
| 423 | ) !void { |
| 424 | const file_location = fr.logicalPos(); |
| 191 | 425 | const r = &fr.interface; |
| 192 | 426 | const header = r.takeStruct(std.coff.Header, .little) catch |err| |
| 193 | 427 | return failParse(opts, "unable to read COFF header: {t}", .{err}); |
| 194 | 428 | |
| 195 | 429 | if (opts.file_headers) { |
| 196 | | if (!opts.compact) try w.writeAll("COFF Header:\n"); |
| 430 | try w.writeAll("COFF Header:\n"); |
| 197 | 431 | try dumpHeader(w, std.coff.Header, &header, struct {}); |
| 198 | | if (!opts.compact) try w.writeByte('\n'); |
| 432 | try w.writeByte('\n'); |
| 199 | 433 | } |
| 200 | 434 | |
| 201 | 435 | if (header.size_of_optional_header > 0) opt_header: { |
| ... | ... | @@ -204,7 +438,7 @@ const coff = struct { |
| 204 | 438 | break :opt_header; |
| 205 | 439 | } |
| 206 | 440 | |
| 207 | | if (!opts.compact) try w.writeAll("COFF Optional Header:\n"); |
| 441 | try w.writeAll("COFF Optional Header:\n"); |
| 208 | 442 | const magic: std.coff.OptionalHeader.Magic = @enumFromInt(try r.peekInt(u16, .little)); |
| 209 | 443 | const num_directory_entries = switch (magic) { |
| 210 | 444 | inline .PE32, .@"PE32+" => |v| data_dirs: { |
| ... | ... | @@ -252,14 +486,14 @@ const coff = struct { |
| 252 | 486 | } |
| 253 | 487 | pub fn minor_subsystem_version(_: *const OptionalHeader, _: *Io.Writer) !void {} |
| 254 | 488 | }); |
| 255 | | if (!opts.compact) try w.writeByte('\n'); |
| 489 | try w.writeByte('\n'); |
| 256 | 490 | |
| 257 | 491 | break :data_dirs optional_header.number_of_rva_and_sizes; |
| 258 | 492 | }, |
| 259 | 493 | else => return failParse(opts, "invalid optional header magic number: {x}", .{magic}), |
| 260 | 494 | }; |
| 261 | 495 | |
| 262 | | if (!opts.compact) try w.writeAll("Data Directories:\n"); |
| 496 | try w.writeAll("Data Directories:\n"); |
| 263 | 497 | for (0..num_directory_entries) |dir_i| { |
| 264 | 498 | const dir = r.takeStruct(std.coff.ImageDataDirectory, .little) catch |err| |
| 265 | 499 | return failParse(opts, "unable to read data directory {x}: {t}", .{ dir_i, err }); |
| ... | ... | @@ -269,45 +503,62 @@ const coff = struct { |
| 269 | 503 | .{ dir.virtual_address, dir.size, @as(std.coff.IMAGE.DIRECTORY_ENTRY, @enumFromInt(dir_i)) }, |
| 270 | 504 | ); |
| 271 | 505 | } |
| 272 | | if (!opts.compact) try w.writeByte('\n'); |
| 506 | try w.writeByte('\n'); |
| 273 | 507 | } else if (is_image) { |
| 274 | 508 | return failParse(opts, "image did not contain an optional header", .{}); |
| 275 | 509 | } |
| 276 | 510 | |
| 277 | 511 | // Section names in images don't use the string table, as they must fit inline in the header |
| 278 | 512 | const load_string_table = (opts.strings or !is_image) and header.pointer_to_symbol_table > 0; |
| 279 | | |
| 280 | 513 | const string_table = if (load_string_table) string_table: { |
| 281 | 514 | const pos = fr.logicalPos(); |
| 282 | | fr.seekTo(header.pointer_to_symbol_table + header.number_of_symbols * std.coff.Symbol.sizeOf()) catch |err| |
| 515 | fr.seekTo(file_location + header.pointer_to_symbol_table + header.number_of_symbols * std.coff.Symbol.sizeOf()) catch |err| |
| 283 | 516 | return failParse(opts, "unable to seek to string table: {t}", .{err}); |
| 284 | 517 | |
| 285 | 518 | const string_table_len = r.peekInt(u32, .little) catch |err| |
| 286 | 519 | return failParse(opts, "unable to read string table length: {t}", .{err}); |
| 287 | 520 | |
| 288 | | const table = r.readAlloc(arena, string_table_len) catch |err| |
| 521 | const table = r.readAlloc(gpa, string_table_len) catch |err| |
| 289 | 522 | return failParse(opts, "unable to read string table: {t}", .{err}); |
| 290 | 523 | |
| 291 | 524 | try fr.seekTo(pos); |
| 292 | 525 | break :string_table table; |
| 293 | 526 | } else &.{}; |
| 527 | defer gpa.free(string_table); |
| 528 | |
| 529 | if (opts.strings) { |
| 530 | try w.print( |
| 531 | \\String Table (0x{x} bytes): |
| 532 | \\ |
| 533 | , .{string_table.len}); |
| 534 | |
| 535 | var sr = Io.Reader.fixed(string_table[4..]); |
| 536 | while (try sr.takeDelimiter(0)) |str| { |
| 537 | try w.writeAll(str); |
| 538 | try w.writeByte('\n'); |
| 539 | } |
| 540 | |
| 541 | try w.writeByte('\n'); |
| 542 | } |
| 294 | 543 | |
| 295 | 544 | var sections: std.ArrayList(std.coff.SectionHeader) = .empty; |
| 296 | | const load_sections = opts.section_table or opts.symbols; |
| 545 | defer sections.deinit(gpa); |
| 546 | |
| 547 | const load_sections = opts.section_headers or opts.symbols; |
| 297 | 548 | if (load_sections) { |
| 298 | | if (!opts.compact and opts.section_table) |
| 549 | if (opts.section_headers) |
| 299 | 550 | try w.writeAll( |
| 300 | 551 | \\Section Table: |
| 301 | | \\Num Name RVA Virtual Size Data Size File Offset Relocs Offset Lines Offset # Relocs # Lines Flags |
| 552 | \\Num Name RVA Virt Size Data Size & Data & Relocs & Lines # Relocs # Lines Flags |
| 302 | 553 | \\ |
| 303 | 554 | ); |
| 304 | 555 | |
| 305 | | try sections.resize(arena, header.number_of_sections); |
| 556 | try sections.resize(gpa, header.number_of_sections); |
| 306 | 557 | for (sections.items, 0..) |*section, section_i| { |
| 307 | 558 | section.* = r.takeStruct(std.coff.SectionHeader, .little) catch |err| |
| 308 | 559 | return failParse(opts, "unable to read section header {x}: {t}", .{ section_i, err }); |
| 309 | 560 | |
| 310 | | if (opts.section_table) { |
| 561 | if (opts.section_headers) { |
| 311 | 562 | const name = headerName(&section.name, string_table) catch |err| switch (err) { |
| 312 | 563 | error.Overflow, |
| 313 | 564 | error.InvalidCharacter, |
| ... | ... | @@ -321,16 +572,13 @@ const coff = struct { |
| 321 | 572 | }), |
| 322 | 573 | }; |
| 323 | 574 | |
| 324 | | const matched = for (opts.section_filters) |filter| { |
| 325 | | if (std.mem.containsAtLeast(u8, name, 1, filter)) break true; |
| 326 | | } else opts.section_filters.len == 0; |
| 327 | | if (!matched) continue; |
| 328 | | |
| 575 | if (!filterMatches(opts.section_filters, name)) continue; |
| 576 | const raw_name = std.mem.sliceTo(&section.name, 0); |
| 329 | 577 | try w.print( |
| 330 | | "{x: >3} {s: <8} {x: >8} {x: >12} {x: >9} {x: >10} {x: >13} {x: >12} {x: >8} {x: >8} {x:0>8} ", |
| 578 | "{x: >3} {s: <8} {x: >8} {x: >9} {x: >9} {x: >8} {x: >8} {x: >8} {x: >8} {x: >8} {x:0>8} | ", |
| 331 | 579 | .{ |
| 332 | 580 | section_i + 1, |
| 333 | | std.mem.sliceTo(&section.name, 0), |
| 581 | raw_name, |
| 334 | 582 | section.virtual_address, |
| 335 | 583 | section.virtual_size, |
| 336 | 584 | section.size_of_raw_data, |
| ... | ... | @@ -343,28 +591,27 @@ const coff = struct { |
| 343 | 591 | }, |
| 344 | 592 | ); |
| 345 | 593 | |
| 594 | try dumpFlags(w, "{s} ", std.coff.SectionHeader.Flags, &section.flags, 0); |
| 346 | 595 | if (name.len > 8) |
| 347 | | try w.print(" | {s}", .{name}); |
| 596 | try w.print("| {s}", .{name}); |
| 348 | 597 | |
| 349 | | try dumpFlags(w, "{s} ", std.coff.SectionHeader.Flags, &section.flags, 0); |
| 350 | 598 | try w.writeByte('\n'); |
| 351 | 599 | } |
| 352 | 600 | } |
| 353 | 601 | |
| 354 | | if (!opts.compact and opts.section_table) try w.writeByte('\n'); |
| 602 | if (opts.section_headers) try w.writeByte('\n'); |
| 355 | 603 | } |
| 356 | 604 | |
| 357 | 605 | if (opts.symbols) { |
| 358 | 606 | if (header.pointer_to_symbol_table > 0) { |
| 359 | | fr.seekTo(header.pointer_to_symbol_table) catch |err| |
| 607 | fr.seekTo(file_location + header.pointer_to_symbol_table) catch |err| |
| 360 | 608 | return failParse(opts, "unable to seek to symbol table: {t}", .{err}); |
| 361 | 609 | |
| 362 | | if (!opts.compact and opts.symbols) |
| 363 | | try w.writeAll( |
| 364 | | \\Symbol Table: |
| 365 | | \\ Ord Value Sect Type Storage Name |
| 366 | | \\ |
| 367 | | ); |
| 610 | try w.writeAll( |
| 611 | \\Symbol Table: |
| 612 | \\ Ord Value Sect Type Storage Name |
| 613 | \\ |
| 614 | ); |
| 368 | 615 | |
| 369 | 616 | const symbol_size = std.coff.Symbol.sizeOf(); |
| 370 | 617 | var symbol_i: u32 = 0; |
| ... | ... | @@ -390,7 +637,7 @@ const coff = struct { |
| 390 | 637 | break :name string_table[index..]; |
| 391 | 638 | } else &symbol.name, 0); |
| 392 | 639 | |
| 393 | | try w.print("{x:0>4} {x:0>8} ", .{ symbol_i, symbol.value }); |
| 640 | try w.print("{x: >4} {x:0>8} ", .{ symbol_i, symbol.value }); |
| 394 | 641 | try switch (symbol.section_number) { |
| 395 | 642 | .UNDEFINED => w.writeAll("UNDEF"), |
| 396 | 643 | .ABSOLUTE => w.writeAll(" ABS"), |
| ... | ... | @@ -419,7 +666,7 @@ const coff = struct { |
| 419 | 666 | |
| 420 | 667 | for (0..symbol.number_of_aux_symbols) |aux_i| { |
| 421 | 668 | _ = aux_i; |
| 422 | | try w.writeAll(" AUX"); |
| 669 | try w.writeAll(" |"); |
| 423 | 670 | |
| 424 | 671 | if (symbol.storage_class == .EXTERNAL and |
| 425 | 672 | symbol.type == std.coff.SymType{ |
| ... | ... | @@ -512,7 +759,7 @@ const coff = struct { |
| 512 | 759 | continue; |
| 513 | 760 | } |
| 514 | 761 | |
| 515 | | try w.print(" [size: {x:0>8} chksum: {x:0>8} relocs: {x:0>4} lines: {x:0>4}]", .{ |
| 762 | try w.print(" [size {x:0>8} chksum {x:0>8} relocs {x:0>4} lines {x:0>4}]", .{ |
| 516 | 763 | section_def.length, |
| 517 | 764 | section_def.checksum, |
| 518 | 765 | section_def.number_of_relocations, |
| ... | ... | @@ -533,12 +780,24 @@ const coff = struct { |
| 533 | 780 | try w.writeByte('\n'); |
| 534 | 781 | } |
| 535 | 782 | } |
| 783 | |
| 784 | try w.writeByte('\n'); |
| 536 | 785 | } else { |
| 537 | | if (!opts.compact) try w.writeAll("No symbol table found\n"); |
| 786 | try w.writeAll("No symbol table found\n"); |
| 538 | 787 | } |
| 539 | 788 | } |
| 540 | 789 | } |
| 541 | 790 | |
| 791 | fn headerName(raw: *const [8]u8, string_table: []const u8) ![]const u8 { |
| 792 | return if (raw[0] == '/') name: { |
| 793 | const name_offset = try std.fmt.parseUnsigned(u24, std.mem.sliceTo(raw[1..], 0), 10); |
| 794 | if (name_offset >= string_table.len) |
| 795 | return error.OutOfBounds; |
| 796 | |
| 797 | break :name std.mem.sliceTo(string_table[name_offset..], 0); |
| 798 | } else std.mem.sliceTo(raw, 0); |
| 799 | } |
| 800 | |
| 542 | 801 | fn fmtSymbolType(sym_type: std.coff.SymType) std.fmt.Alt(std.coff.SymType, symbolTypeString) { |
| 543 | 802 | return .{ .data = sym_type }; |
| 544 | 803 | } |
| ... | ... | @@ -584,6 +843,18 @@ const coff = struct { |
| 584 | 843 | } |
| 585 | 844 | } |
| 586 | 845 | |
| 846 | fn dumpArchiveHeader(w: *Io.Writer, header: *const ArchiveHeader, pos: u32) !void { |
| 847 | try w.print("Archive member at offset 0x{x}: '{s}'\n", .{ pos, header.name }); |
| 848 | |
| 849 | // TODO: Date formatter |
| 850 | try dumpHeader(w, ArchiveHeader, header, struct { |
| 851 | pub fn name(_: *const ArchiveHeader, _: *Io.Writer) !void {} |
| 852 | pub fn file_mode(h: *const ArchiveHeader, cw: *Io.Writer) !void { |
| 853 | try cw.print("{o: >16} file_mode\n", .{h.file_mode}); |
| 854 | } |
| 855 | }); |
| 856 | } |
| 857 | |
| 587 | 858 | fn dumpHeader(w: *Io.Writer, comptime T: type, header: *const T, Custom: type) !void { |
| 588 | 859 | inline for (@typeInfo(T).@"struct".fields) |field| { |
| 589 | 860 | const val = &@field(header, field.name); |
| ... | ... | @@ -619,14 +890,21 @@ const coff = struct { |
| 619 | 890 | } |
| 620 | 891 | }; |
| 621 | 892 | |
| 893 | fn filterMatches(filters: []const []const u8, val: []const u8) bool { |
| 894 | return for (filters) |filter| { |
| 895 | if (std.mem.containsAtLeast(u8, val, 1, filter)) break true; |
| 896 | } else filters.len == 0; |
| 897 | } |
| 898 | |
| 622 | 899 | const usage = |
| 623 | 900 | \\Usage: zig objdump [options] file |
| 624 | 901 | \\ |
| 625 | 902 | \\Options: |
| 626 | 903 | \\ -h, --help Print this help and exit |
| 627 | | \\ --all-headers Alias for --file-headers --section-headers --relocs --symbols |
| 628 | | \\ --compact Minimal output mode that excludes extra newlines and headings. Intended for snapshot testing. |
| 904 | \\ --all-headers Alias for --file-headers --member-headers --section-headers --relocs --symbols |
| 629 | 905 | \\ --file-headers Display file-format specific headers |
| 906 | \\ --linker-member[=1|2] (Coff) Display contents of the specified linker member (default 2) |
| 907 | \\ --member-headers Display archive member headers |
| 630 | 908 | \\ --only-member=[name] Only consider archive members that contain [name]. Can be specified multiple times. |
| 631 | 909 | \\ --only-section=[name] Only consider sections that contain [name]. Can be specified multiple times. |
| 632 | 910 | \\ --section-headers Display section headers |