| ... | @@ -12,11 +12,15 @@ var stdout_buffer: [4000]u8 = undefined; | ... | @@ -12,11 +12,15 @@ var stdout_buffer: [4000]u8 = undefined; |
| 12 | const Options = struct { | 12 | const Options = struct { |
| 13 | input_path: []const u8, | 13 | input_path: []const u8, |
| 14 | file_headers: bool, | 14 | file_headers: bool, |
| | 15 | member_filters: []const []const u8 = &.{}, |
| | 16 | member_headers: bool, |
| 15 | section_filters: []const []const u8 = &.{}, | 17 | section_filters: []const []const u8 = &.{}, |
| 16 | section_table: bool, | 18 | section_headers: bool, |
| 17 | strings: bool, | 19 | strings: bool, |
| 18 | symbols: bool, | 20 | symbols: bool, |
| 19 | compact: bool, | 21 | |
| | 22 | // Coff-specific |
| | 23 | linker_member: ?std.coff.ArchiveMemberHeader.Kind, |
| 20 | }; | 24 | }; |
| 21 | | 25 | |
| 22 | pub fn main(init: std.process.Init) !void { | 26 | pub fn main(init: std.process.Init) !void { |
| ... | @@ -28,12 +32,14 @@ pub fn main(init: std.process.Init) !void { | ... | @@ -28,12 +32,14 @@ pub fn main(init: std.process.Init) !void { |
| 28 | | 32 | |
| 29 | var opt_input_path: ?[]const u8 = null; | 33 | var opt_input_path: ?[]const u8 = null; |
| 30 | var opt_file_headers: ?bool = null; | 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 | var opt_strings: ?bool = null; | 38 | var opt_strings: ?bool = null; |
| 33 | var opt_symbols: ?bool = null; | 39 | var opt_symbols: ?bool = null; |
| 34 | var opt_relocs: ?bool = null; | 40 | var opt_relocs: ?bool = null; |
| 35 | var opt_compact: ?bool = null; | | |
| 36 | var section_filters: std.ArrayList([]const u8) = .empty; | 41 | var section_filters: std.ArrayList([]const u8) = .empty; |
| | 42 | var member_filters: std.ArrayList([]const u8) = .empty; |
| 37 | while (i < args.len) : (i += 1) { | 43 | while (i < args.len) : (i += 1) { |
| 38 | const arg = args[i]; | 44 | const arg = args[i]; |
| 39 | if (mem.startsWith(u8, arg, "-")) { | 45 | if (mem.startsWith(u8, arg, "-")) { |
| ... | @@ -41,19 +47,27 @@ pub fn main(init: std.process.Init) !void { | ... | @@ -41,19 +47,27 @@ pub fn main(init: std.process.Init) !void { |
| 41 | return Io.File.stdout().writeStreamingAll(io, usage); | 47 | return Io.File.stdout().writeStreamingAll(io, usage); |
| 42 | } else if (mem.eql(u8, arg, "--all-headers")) { | 48 | } else if (mem.eql(u8, arg, "--all-headers")) { |
| 43 | opt_file_headers = true; | 49 | opt_file_headers = true; |
| 44 | opt_section_table = true; | 50 | opt_member_headers = true; |
| | 51 | opt_section_headers = true; |
| 45 | opt_symbols = true; | 52 | opt_symbols = true; |
| 46 | opt_relocs = true; | 53 | opt_relocs = true; |
| 47 | } else if (mem.eql(u8, arg, "--compact")) { | | |
| 48 | opt_compact = true; | | |
| 49 | } else if (mem.eql(u8, arg, "--file-headers")) { | 54 | } else if (mem.eql(u8, arg, "--file-headers")) { |
| 50 | opt_file_headers = true; | 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 | } else if (mem.startsWith(u8, arg, "--only-section=")) { | 63 | } else if (mem.startsWith(u8, arg, "--only-section=")) { |
| 52 | (try section_filters.addOne(arena)).* = try arena.dupe(u8, arg["--only-section=".len..]); | 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 | } else if (mem.eql(u8, arg, "--relocs")) { | 67 | } else if (mem.eql(u8, arg, "--relocs")) { |
| 54 | opt_relocs = true; | 68 | opt_relocs = true; |
| 55 | } else if (mem.eql(u8, arg, "--section-headers")) { | 69 | } else if (mem.eql(u8, arg, "--section-headers")) { |
| 56 | opt_section_table = true; | 70 | opt_section_headers = true; |
| 57 | } else if (mem.eql(u8, arg, "--strings")) { | 71 | } else if (mem.eql(u8, arg, "--strings")) { |
| 58 | opt_strings = true; | 72 | opt_strings = true; |
| 59 | } else if (mem.eql(u8, arg, "--symbols")) { | 73 | } else if (mem.eql(u8, arg, "--symbols")) { |
| ... | @@ -70,12 +84,14 @@ pub fn main(init: std.process.Init) !void { | ... | @@ -70,12 +84,14 @@ pub fn main(init: std.process.Init) !void { |
| 70 | | 84 | |
| 71 | const opts: Options = .{ | 85 | const opts: Options = .{ |
| 72 | .input_path = opt_input_path orelse fatal("missing input file path positional argument", .{}), | 86 | .input_path = opt_input_path orelse fatal("missing input file path positional argument", .{}), |
| 73 | .compact = opt_compact orelse false, | | |
| 74 | .file_headers = opt_file_headers orelse false, | 87 | .file_headers = opt_file_headers orelse false, |
| 75 | .section_filters = section_filters.items, | 88 | .section_filters = section_filters.items, |
| 76 | .section_table = opt_section_table orelse false, | 89 | .section_headers = opt_section_headers orelse false, |
| 77 | .strings = opt_strings orelse false, | 90 | .strings = opt_strings orelse false, |
| 78 | .symbols = opt_symbols orelse false, | 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 | var file = std.Io.Dir.cwd().openFile(io, opts.input_path, .{}) catch |err| | 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,7 +101,7 @@ pub fn main(init: std.process.Init) !void { |
| 85 | var buffer: [4096]u8 = undefined; | 101 | var buffer: [4096]u8 = undefined; |
| 86 | var file_reader = file.reader(io, &buffer); | 102 | var file_reader = file.reader(io, &buffer); |
| 87 | var stdout_writer = std.Io.File.stdout().writerStreaming(io, &stdout_buffer); | 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 | error.ReadFailed => return file_reader.err.?, | 105 | error.ReadFailed => return file_reader.err.?, |
| 90 | error.WriteFailed => return stdout_writer.err.?, | 106 | error.WriteFailed => return stdout_writer.err.?, |
| 91 | error.UnknownFile => fatal("unrecognized file: {s}", .{opts.input_path}), | 107 | error.UnknownFile => fatal("unrecognized file: {s}", .{opts.input_path}), |
| ... | @@ -95,7 +111,7 @@ pub fn main(init: std.process.Init) !void { | ... | @@ -95,7 +111,7 @@ pub fn main(init: std.process.Init) !void { |
| 95 | try stdout_writer.flush(); | 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 | const r = &fr.interface; | 115 | const r = &fr.interface; |
| 100 | try r.fill(4); | 116 | try r.fill(4); |
| 101 | elf: { | 117 | elf: { |
| ... | @@ -125,16 +141,16 @@ fn dump(arena: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: | ... | @@ -125,16 +141,16 @@ fn dump(arena: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: |
| 125 | return error.ParseFailure; | 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)}); | 144 | 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); | 145 | return coff.dumpObject(gpa, opts, true, fr, w); |
| 130 | } else if (std.mem.eql(u8, ext, ".lib")) { | 146 | } else if (std.mem.eql(u8, ext, ".lib")) { |
| 131 | r.fill(std.coff.archive_signature.len) catch break :coff; | 147 | r.fill(std.coff.archive_signature.len) catch break :coff; |
| 132 | if (!mem.eql(u8, r.buffered()[0..std.coff.archive_signature.len], std.coff.archive_signature)) break :coff; | 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)}); | 149 | try w.print("{s}: COFF archive\n\n", .{std.fs.path.basename(opts.input_path)}); |
| 134 | return coff.dumpArchive(opts, fr, w); | 150 | return coff.dumpArchive(gpa, opts, fr, w); |
| 135 | } else if (std.mem.eql(u8, ext, ".obj")) { | 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)}); | 152 | try w.print("{s}: COFF object\n\n", .{std.fs.path.basename(opts.input_path)}); |
| 137 | return coff.dumpObject(arena, opts, false, fr, w); | 153 | return coff.dumpObject(gpa, opts, false, fr, w); |
| 138 | } | 154 | } |
| 139 | } | 155 | } |
| 140 | return error.UnknownFile; | 156 | return error.UnknownFile; |
| ... | @@ -171,31 +187,249 @@ const wasm = struct { | ... | @@ -171,31 +187,249 @@ const wasm = struct { |
| 171 | }; | 187 | }; |
| 172 | | 188 | |
| 173 | const coff = struct { | 189 | const coff = struct { |
| 174 | fn dumpArchive(opt: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void { | 190 | const ArchiveHeader = struct { |
| 175 | _ = opt; | 191 | name: []const u8, |
| 176 | _ = fr; | 192 | date: u40, |
| 177 | try w.writeAll("TODO dump coff archive\n"); | 193 | user_id: u20, |
| 178 | } | 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 { | 204 | return .{ |
| 181 | return if (raw[0] == '/') name: { | 205 | .name = name, |
| 182 | const name_offset = try std.fmt.parseUnsigned(u24, raw[1..], 10); | 206 | .date = raw_header.parseDate() catch |err| |
| 183 | if (name_offset >= string_table.len) | 207 | failParse(opts, "unable to parse date '{s}' in member '{s}': {t}", .{ raw_header.date, name, err }), |
| 184 | return error.OutOfBounds; | 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); | 220 | fn dumpArchive(gpa: std.mem.Allocator, opts: *const Options, fr: *Io.File.Reader, w: *Io.Writer) !void { |
| 187 | } else std.mem.sliceTo(raw, 0); | 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 | const r = &fr.interface; | 425 | const r = &fr.interface; |
| 192 | const header = r.takeStruct(std.coff.Header, .little) catch |err| | 426 | const header = r.takeStruct(std.coff.Header, .little) catch |err| |
| 193 | return failParse(opts, "unable to read COFF header: {t}", .{err}); | 427 | return failParse(opts, "unable to read COFF header: {t}", .{err}); |
| 194 | | 428 | |
| 195 | if (opts.file_headers) { | 429 | if (opts.file_headers) { |
| 196 | if (!opts.compact) try w.writeAll("COFF Header:\n"); | 430 | try w.writeAll("COFF Header:\n"); |
| 197 | try dumpHeader(w, std.coff.Header, &header, struct {}); | 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 | if (header.size_of_optional_header > 0) opt_header: { | 435 | if (header.size_of_optional_header > 0) opt_header: { |
| ... | @@ -204,7 +438,7 @@ const coff = struct { | ... | @@ -204,7 +438,7 @@ const coff = struct { |
| 204 | break :opt_header; | 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 | const magic: std.coff.OptionalHeader.Magic = @enumFromInt(try r.peekInt(u16, .little)); | 442 | const magic: std.coff.OptionalHeader.Magic = @enumFromInt(try r.peekInt(u16, .little)); |
| 209 | const num_directory_entries = switch (magic) { | 443 | const num_directory_entries = switch (magic) { |
| 210 | inline .PE32, .@"PE32+" => |v| data_dirs: { | 444 | inline .PE32, .@"PE32+" => |v| data_dirs: { |
| ... | @@ -252,14 +486,14 @@ const coff = struct { | ... | @@ -252,14 +486,14 @@ const coff = struct { |
| 252 | } | 486 | } |
| 253 | pub fn minor_subsystem_version(_: *const OptionalHeader, _: *Io.Writer) !void {} | 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 | break :data_dirs optional_header.number_of_rva_and_sizes; | 491 | break :data_dirs optional_header.number_of_rva_and_sizes; |
| 258 | }, | 492 | }, |
| 259 | else => return failParse(opts, "invalid optional header magic number: {x}", .{magic}), | 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 | for (0..num_directory_entries) |dir_i| { | 497 | for (0..num_directory_entries) |dir_i| { |
| 264 | const dir = r.takeStruct(std.coff.ImageDataDirectory, .little) catch |err| | 498 | const dir = r.takeStruct(std.coff.ImageDataDirectory, .little) catch |err| |
| 265 | return failParse(opts, "unable to read data directory {x}: {t}", .{ dir_i, err }); | 499 | return failParse(opts, "unable to read data directory {x}: {t}", .{ dir_i, err }); |
| ... | @@ -269,45 +503,62 @@ const coff = struct { | ... | @@ -269,45 +503,62 @@ const coff = struct { |
| 269 | .{ dir.virtual_address, dir.size, @as(std.coff.IMAGE.DIRECTORY_ENTRY, @enumFromInt(dir_i)) }, | 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 | } else if (is_image) { | 507 | } else if (is_image) { |
| 274 | return failParse(opts, "image did not contain an optional header", .{}); | 508 | return failParse(opts, "image did not contain an optional header", .{}); |
| 275 | } | 509 | } |
| 276 | | 510 | |
| 277 | // Section names in images don't use the string table, as they must fit inline in the header | 511 | // Section names in images don't use the string table, as they must fit inline in the header |
| 278 | const load_string_table = (opts.strings or !is_image) and header.pointer_to_symbol_table > 0; | 512 | const load_string_table = (opts.strings or !is_image) and header.pointer_to_symbol_table > 0; |
| 279 | | | |
| 280 | const string_table = if (load_string_table) string_table: { | 513 | const string_table = if (load_string_table) string_table: { |
| 281 | const pos = fr.logicalPos(); | 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 | return failParse(opts, "unable to seek to string table: {t}", .{err}); | 516 | return failParse(opts, "unable to seek to string table: {t}", .{err}); |
| 284 | | 517 | |
| 285 | const string_table_len = r.peekInt(u32, .little) catch |err| | 518 | const string_table_len = r.peekInt(u32, .little) catch |err| |
| 286 | return failParse(opts, "unable to read string table length: {t}", .{err}); | 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 | return failParse(opts, "unable to read string table: {t}", .{err}); | 522 | return failParse(opts, "unable to read string table: {t}", .{err}); |
| 290 | | 523 | |
| 291 | try fr.seekTo(pos); | 524 | try fr.seekTo(pos); |
| 292 | break :string_table table; | 525 | break :string_table table; |
| 293 | } else &.{}; | 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 | var sections: std.ArrayList(std.coff.SectionHeader) = .empty; | 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 | if (load_sections) { | 548 | if (load_sections) { |
| 298 | if (!opts.compact and opts.section_table) | 549 | if (opts.section_headers) |
| 299 | try w.writeAll( | 550 | try w.writeAll( |
| 300 | \\Section Table: | 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 | for (sections.items, 0..) |*section, section_i| { | 557 | for (sections.items, 0..) |*section, section_i| { |
| 307 | section.* = r.takeStruct(std.coff.SectionHeader, .little) catch |err| | 558 | section.* = r.takeStruct(std.coff.SectionHeader, .little) catch |err| |
| 308 | return failParse(opts, "unable to read section header {x}: {t}", .{ section_i, err }); | 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 | const name = headerName(&section.name, string_table) catch |err| switch (err) { | 562 | const name = headerName(&section.name, string_table) catch |err| switch (err) { |
| 312 | error.Overflow, | 563 | error.Overflow, |
| 313 | error.InvalidCharacter, | 564 | error.InvalidCharacter, |
| ... | @@ -321,16 +572,13 @@ const coff = struct { | ... | @@ -321,16 +572,13 @@ const coff = struct { |
| 321 | }), | 572 | }), |
| 322 | }; | 573 | }; |
| 323 | | 574 | |
| 324 | const matched = for (opts.section_filters) |filter| { | 575 | if (!filterMatches(opts.section_filters, name)) continue; |
| 325 | if (std.mem.containsAtLeast(u8, name, 1, filter)) break true; | 576 | const raw_name = std.mem.sliceTo(&section.name, 0); |
| 326 | } else opts.section_filters.len == 0; | | |
| 327 | if (!matched) continue; | | |
| 328 | | | |
| 329 | try w.print( | 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 | section_i + 1, | 580 | section_i + 1, |
| 333 | std.mem.sliceTo(&section.name, 0), | 581 | raw_name, |
| 334 | section.virtual_address, | 582 | section.virtual_address, |
| 335 | section.virtual_size, | 583 | section.virtual_size, |
| 336 | section.size_of_raw_data, | 584 | section.size_of_raw_data, |
| ... | @@ -343,28 +591,27 @@ const coff = struct { | ... | @@ -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 | if (name.len > 8) | 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 | try w.writeByte('\n'); | 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 | if (opts.symbols) { | 605 | if (opts.symbols) { |
| 358 | if (header.pointer_to_symbol_table > 0) { | 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 | return failParse(opts, "unable to seek to symbol table: {t}", .{err}); | 608 | return failParse(opts, "unable to seek to symbol table: {t}", .{err}); |
| 361 | | 609 | |
| 362 | if (!opts.compact and opts.symbols) | 610 | try w.writeAll( |
| 363 | try w.writeAll( | 611 | \\Symbol Table: |
| 364 | \\Symbol Table: | 612 | \\ Ord Value Sect Type Storage Name |
| 365 | \\ Ord Value Sect Type Storage Name | 613 | \\ |
| 366 | \\ | 614 | ); |
| 367 | ); | | |
| 368 | | 615 | |
| 369 | const symbol_size = std.coff.Symbol.sizeOf(); | 616 | const symbol_size = std.coff.Symbol.sizeOf(); |
| 370 | var symbol_i: u32 = 0; | 617 | var symbol_i: u32 = 0; |
| ... | @@ -390,7 +637,7 @@ const coff = struct { | ... | @@ -390,7 +637,7 @@ const coff = struct { |
| 390 | break :name string_table[index..]; | 637 | break :name string_table[index..]; |
| 391 | } else &symbol.name, 0); | 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 | try switch (symbol.section_number) { | 641 | try switch (symbol.section_number) { |
| 395 | .UNDEFINED => w.writeAll("UNDEF"), | 642 | .UNDEFINED => w.writeAll("UNDEF"), |
| 396 | .ABSOLUTE => w.writeAll(" ABS"), | 643 | .ABSOLUTE => w.writeAll(" ABS"), |
| ... | @@ -419,7 +666,7 @@ const coff = struct { | ... | @@ -419,7 +666,7 @@ const coff = struct { |
| 419 | | 666 | |
| 420 | for (0..symbol.number_of_aux_symbols) |aux_i| { | 667 | for (0..symbol.number_of_aux_symbols) |aux_i| { |
| 421 | _ = aux_i; | 668 | _ = aux_i; |
| 422 | try w.writeAll(" AUX"); | 669 | try w.writeAll(" |"); |
| 423 | | 670 | |
| 424 | if (symbol.storage_class == .EXTERNAL and | 671 | if (symbol.storage_class == .EXTERNAL and |
| 425 | symbol.type == std.coff.SymType{ | 672 | symbol.type == std.coff.SymType{ |
| ... | @@ -512,7 +759,7 @@ const coff = struct { | ... | @@ -512,7 +759,7 @@ const coff = struct { |
| 512 | continue; | 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 | section_def.length, | 763 | section_def.length, |
| 517 | section_def.checksum, | 764 | section_def.checksum, |
| 518 | section_def.number_of_relocations, | 765 | section_def.number_of_relocations, |
| ... | @@ -533,12 +780,24 @@ const coff = struct { | ... | @@ -533,12 +780,24 @@ const coff = struct { |
| 533 | try w.writeByte('\n'); | 780 | try w.writeByte('\n'); |
| 534 | } | 781 | } |
| 535 | } | 782 | } |
| | 783 | |
| | 784 | try w.writeByte('\n'); |
| 536 | } else { | 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 | fn fmtSymbolType(sym_type: std.coff.SymType) std.fmt.Alt(std.coff.SymType, symbolTypeString) { | 801 | fn fmtSymbolType(sym_type: std.coff.SymType) std.fmt.Alt(std.coff.SymType, symbolTypeString) { |
| 543 | return .{ .data = sym_type }; | 802 | return .{ .data = sym_type }; |
| 544 | } | 803 | } |
| ... | @@ -584,6 +843,18 @@ const coff = struct { | ... | @@ -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 | fn dumpHeader(w: *Io.Writer, comptime T: type, header: *const T, Custom: type) !void { | 858 | fn dumpHeader(w: *Io.Writer, comptime T: type, header: *const T, Custom: type) !void { |
| 588 | inline for (@typeInfo(T).@"struct".fields) |field| { | 859 | inline for (@typeInfo(T).@"struct".fields) |field| { |
| 589 | const val = &@field(header, field.name); | 860 | const val = &@field(header, field.name); |
| ... | @@ -619,14 +890,21 @@ const coff = struct { | ... | @@ -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 | const usage = | 899 | const usage = |
| 623 | \\Usage: zig objdump [options] file | 900 | \\Usage: zig objdump [options] file |
| 624 | \\ | 901 | \\ |
| 625 | \\Options: | 902 | \\Options: |
| 626 | \\ -h, --help Print this help and exit | 903 | \\ -h, --help Print this help and exit |
| 627 | \\ --all-headers Alias for --file-headers --section-headers --relocs --symbols | 904 | \\ --all-headers Alias for --file-headers --member-headers --section-headers --relocs --symbols |
| 628 | \\ --compact Minimal output mode that excludes extra newlines and headings. Intended for snapshot testing. | | |
| 629 | \\ --file-headers Display file-format specific headers | 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 | \\ --only-member=[name] Only consider archive members that contain [name]. Can be specified multiple times. | 908 | \\ --only-member=[name] Only consider archive members that contain [name]. Can be specified multiple times. |
| 631 | \\ --only-section=[name] Only consider sections that contain [name]. Can be specified multiple times. | 909 | \\ --only-section=[name] Only consider sections that contain [name]. Can be specified multiple times. |
| 632 | \\ --section-headers Display section headers | 910 | \\ --section-headers Display section headers |