├── .gitmodules ├── .github └── workflows │ └── ci.yml ├── README.md ├── LICENSE └── src ├── main.zig ├── Library.zig └── Object.zig /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | name: Build and test 8 | runs-on: ${{ matrix.os }}-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | os: [macos, ubuntu, windows] 13 | 14 | steps: 15 | - if: matrix.os == 'windows' 16 | run: git config --global core.autocrlf false 17 | - uses: actions/checkout@v2 18 | with: 19 | submodules: true 20 | - uses: mlugg/setup-zig@v2 21 | with: 22 | version: 0.15.1 23 | - run: zig fmt --check src 24 | - run: zig build install 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zcoff 2 | 3 | Like `dumpbin.exe` but cross-platform. 4 | 5 | ## Usage 6 | 7 | Available options: 8 | 9 | ``` 10 | > zcoff /? 11 | Usage: zcoff [options] file 12 | 13 | General options: 14 | -archivemembers Print archive members summary. 15 | -archivesymbols Print archive symbol table. 16 | -directives Print linker directives. 17 | -headers Print headers. 18 | -symbols Print symbol table. 19 | -imports Print import table. 20 | -relocations Print relocations. 21 | -help, /? Display this help and exit. 22 | ``` 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jakub Konka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Library = @import("Library.zig"); 3 | const Object = @import("Object.zig"); 4 | 5 | var allocator = std.heap.GeneralPurposeAllocator(.{}){}; 6 | const gpa = allocator.allocator(); 7 | 8 | const usage = 9 | \\Usage: zcoff [options] file 10 | \\ 11 | \\General options: 12 | \\-archivemembers Print archive members summary. 13 | \\-archivesymbols Print archive symbol table. 14 | \\-directives Print linker directives. 15 | \\-headers Print headers. 16 | \\-symbols Print symbol table. 17 | \\-imports Print import table. 18 | \\-relocations Print relocations. 19 | \\-help, /? Display this help and exit. 20 | \\ 21 | ; 22 | 23 | fn fatal(comptime format: []const u8, args: anytype) noreturn { 24 | ret: { 25 | const msg = std.fmt.allocPrint(gpa, format ++ "\n", args) catch break :ret; 26 | std.fs.File.stdout().writeAll(msg) catch {}; 27 | } 28 | std.process.exit(1); 29 | } 30 | 31 | const ArgsIterator = struct { 32 | args: []const []const u8, 33 | i: usize = 0, 34 | 35 | fn next(it: *@This()) ?[]const u8 { 36 | if (it.i >= it.args.len) { 37 | return null; 38 | } 39 | defer it.i += 1; 40 | return it.args[it.i]; 41 | } 42 | 43 | fn nextOrFatal(it: *@This()) []const u8 { 44 | return it.next() orelse fatal("expected parameter after {s}", .{it.args[it.i - 1]}); 45 | } 46 | }; 47 | 48 | const ArgsParser = struct { 49 | next_arg: []const u8 = undefined, 50 | it: *ArgsIterator, 51 | 52 | pub fn hasMore(p: *ArgsParser) bool { 53 | p.next_arg = p.it.next() orelse return false; 54 | return true; 55 | } 56 | 57 | pub fn flagAny(p: *ArgsParser, comptime pat: []const u8) bool { 58 | return p.flagPrefix(pat, "-") or p.flagWindows(pat); 59 | } 60 | 61 | pub fn flagWindows(p: *ArgsParser, comptime pat: []const u8) bool { 62 | return p.flagPrefix(pat, "/"); 63 | } 64 | 65 | fn flagPrefix(p: *ArgsParser, comptime pat: []const u8, comptime prefix: []const u8) bool { 66 | if (std.mem.startsWith(u8, p.next_arg, prefix)) { 67 | const actual_arg = p.next_arg[prefix.len..]; 68 | if (std.mem.eql(u8, actual_arg, pat)) { 69 | return true; 70 | } 71 | } 72 | return false; 73 | } 74 | 75 | pub fn arg(p: *ArgsParser, comptime pat: []const u8) ?[]const u8 { 76 | return p.argPrefix(pat, "-") orelse p.argPrefix(pat, "/"); 77 | } 78 | 79 | fn argPrefix(p: *ArgsParser, comptime pat: []const u8, comptime prefix: []const u8) ?[]const u8 { 80 | if (std.mem.startsWith(u8, p.next_arg, prefix)) { 81 | const actual_arg = p.next_arg[prefix.len..]; 82 | if (std.mem.startsWith(u8, actual_arg, pat)) { 83 | if (std.mem.indexOf(u8, actual_arg, ":")) |index| { 84 | if (index == pat.len) { 85 | const value = actual_arg[index + 1 ..]; 86 | return value; 87 | } 88 | } 89 | } 90 | } 91 | return null; 92 | } 93 | }; 94 | 95 | pub fn main() !void { 96 | var arena_allocator = std.heap.ArenaAllocator.init(gpa); 97 | defer arena_allocator.deinit(); 98 | const arena = arena_allocator.allocator(); 99 | 100 | const all_args = try std.process.argsAlloc(arena); 101 | const args = all_args[1..]; 102 | 103 | if (args.len == 0) fatal(usage, .{}); 104 | 105 | var filename: ?[]const u8 = null; 106 | var print_matrix: PrintMatrix = .{}; 107 | 108 | var it = ArgsIterator{ .args = args }; 109 | var p = ArgsParser{ .it = &it }; 110 | while (p.hasMore()) { 111 | if (p.flagAny("help") or p.flagWindows("?")) { 112 | fatal(usage, .{}); 113 | } else if (p.flagAny("archivemembers")) { 114 | print_matrix.archive_members = true; 115 | } else if (p.flagAny("archivesymbols")) { 116 | print_matrix.archive_symbols = true; 117 | } else if (p.flagAny("directives")) { 118 | print_matrix.directives = true; 119 | } else if (p.flagAny("headers")) { 120 | print_matrix.headers = true; 121 | } else if (p.flagAny("symbols")) { 122 | print_matrix.symbols = true; 123 | } else if (p.flagAny("imports")) { 124 | print_matrix.imports = true; 125 | } else if (p.flagAny("relocations")) { 126 | print_matrix.relocations = true; 127 | } else { 128 | if (filename != null) fatal("too many positional arguments specified", .{}); 129 | filename = p.next_arg; 130 | } 131 | } 132 | 133 | const fname = filename orelse fatal("no input file specified", .{}); 134 | const file = try std.fs.cwd().openFile(fname, .{}); 135 | defer file.close(); 136 | const data = try file.readToEndAlloc(arena, std.math.maxInt(u32)); 137 | 138 | var buffer: [1024]u8 = undefined; 139 | var fw = std.fs.File.stdout().writer(&buffer); 140 | var stdout = &fw.interface; 141 | defer stdout.flush() catch fatal("could not write to stdout", .{}); 142 | try stdout.print("Dump of file {s}\n\n", .{fname}); 143 | 144 | if (Library.isLibrary(data)) { 145 | var library = Library{ .gpa = gpa, .data = data }; 146 | try library.parse(); 147 | try stdout.writeAll("File Type: LIBRARY\n\n"); 148 | try library.print(stdout, print_matrix); 149 | } else { 150 | var object = Object{ .gpa = gpa, .data = data }; 151 | 152 | const msdos_magic = "MZ"; 153 | const pe_pointer_offset = 0x3C; 154 | const pe_magic = "PE\x00\x00"; 155 | const is_image = std.mem.eql(u8, msdos_magic, data[0..2]); 156 | object.is_image = is_image; 157 | 158 | if (is_image) { 159 | var stream = std.io.fixedBufferStream(data); 160 | const reader = stream.reader(); 161 | try stream.seekTo(pe_pointer_offset); 162 | const coff_header_offset = try reader.readInt(u32, .little); 163 | try stream.seekTo(coff_header_offset); 164 | var buf: [4]u8 = undefined; 165 | try reader.readNoEof(&buf); 166 | if (!std.mem.eql(u8, pe_magic, &buf)) 167 | fatal("invalid PE file - invalid magic bytes", .{}); 168 | 169 | // Do some basic validation upfront 170 | object.coff_header_offset = coff_header_offset + 4; 171 | const coff_header = object.getCoffHeader(); 172 | if (coff_header.size_of_optional_header == 0) 173 | fatal("invalid PE file - missing PE header", .{}); 174 | } 175 | 176 | if (is_image) { 177 | try stdout.writeAll("File Type: EXECUTABLE IMAGE\n\n"); 178 | } else { 179 | try stdout.writeAll("File Type: COFF OBJECT\n\n"); 180 | } 181 | 182 | try object.print(stdout, print_matrix); 183 | } 184 | } 185 | 186 | pub const PrintMatrix = packed struct { 187 | archive_members: bool = false, 188 | archive_symbols: bool = false, 189 | directives: bool = false, 190 | headers: bool = false, 191 | symbols: bool = false, 192 | imports: bool = false, 193 | relocations: bool = false, 194 | summary: bool = true, 195 | 196 | const Int = blk: { 197 | const bits = @typeInfo(@This()).Struct.fields.len; 198 | break :blk @Type(.{ 199 | .Int = .{ 200 | .signedness = .unsigned, 201 | .bits = bits, 202 | }, 203 | }); 204 | }; 205 | 206 | fn enableAll() @This() { 207 | return @as(@This(), @bitCast(~@as(Int, 0))); 208 | } 209 | 210 | fn isUnset(pm: @This()) bool { 211 | return @as(Int, @bitCast(pm)) == 0; 212 | } 213 | 214 | fn add(pm: *@This(), other: @This()) void { 215 | pm.* = @as(@This(), @bitCast(@as(Int, @bitCast(pm.*)) | @as(Int, @bitCast(other)))); 216 | } 217 | }; 218 | -------------------------------------------------------------------------------- /src/Library.zig: -------------------------------------------------------------------------------- 1 | gpa: Allocator, 2 | data: []const u8, 3 | 4 | symdef: Symdef = .{}, 5 | symdef_sorted: SymdefSorted = .{}, 6 | longnames: []const u8 = &[0]u8{}, 7 | members: std.MultiArrayList(Member) = .{}, 8 | 9 | pub fn isLibrary(data: []const u8) bool { 10 | return std.mem.eql(u8, data[0..magic.len], magic); 11 | } 12 | 13 | pub fn deinit(self: *Library) void { 14 | self.symdef.deinit(self.gpa); 15 | self.symdef_sorted.deinit(self.gpa); 16 | self.members.deinit(self.gpa); 17 | } 18 | 19 | pub fn parse(self: *Library) !void { 20 | var check: packed struct { 21 | symdef: bool = false, 22 | symdef_sorted: bool = false, 23 | longnames: bool = false, 24 | } = .{}; 25 | var member_count: usize = 0; 26 | 27 | var pos: usize = magic.len; 28 | while (true) { 29 | if (!std.mem.isAligned(pos, 2)) pos += 1; 30 | if (pos >= self.data.len) break; 31 | 32 | const hdr = @as(*align(1) const Header, @ptrCast(self.data.ptr + pos)); 33 | pos += @sizeOf(Header); 34 | 35 | if (!std.mem.eql(u8, &hdr.end, end)) return error.InvalidHeaderDelimiter; 36 | 37 | const size = try hdr.getSize(); 38 | defer { 39 | pos += size; 40 | member_count += 1; 41 | } 42 | 43 | if (hdr.isLinkerMember()) { 44 | if (!check.symdef) { 45 | if (member_count != 0) return error.InvalidLinkerMember; 46 | try self.symdef.parse(self.gpa, self.data[pos..][0..size]); 47 | check.symdef = true; 48 | continue; 49 | } 50 | 51 | if (!check.symdef_sorted) { 52 | if (member_count != 1) return error.InvalidLinkerMember; 53 | try self.symdef_sorted.parse(self.gpa, self.data[pos..][0..size]); 54 | check.symdef_sorted = true; 55 | continue; 56 | } 57 | 58 | return error.InvalidLinkerMember; 59 | } 60 | 61 | if (hdr.isLongnamesMember()) { 62 | if (!check.longnames) { 63 | if (member_count != 2) return error.InvalidLinkerMember; 64 | self.longnames = self.data[pos..][0..size]; 65 | check.longnames = true; 66 | continue; 67 | } 68 | } 69 | 70 | // https://reviews.llvm.org/D120645 71 | if (hdr.isHybridmapMember() or hdr.isEcsymbolsMember() or hdr.isXfgmapMember()) continue; // TODO: what the heck are these anyhow? 72 | 73 | try self.members.append(self.gpa, .{ 74 | .offset = pos - @sizeOf(Header), 75 | .header = hdr, 76 | .object = .{ 77 | .gpa = self.gpa, 78 | .data = self.data[pos..][0..size], 79 | }, 80 | }); 81 | } 82 | } 83 | 84 | pub fn print(self: *const Library, writer: anytype, options: anytype) !void { 85 | if (options.archive_symbols) try self.printArchiveSymbols(writer); 86 | 87 | for (self.members.items(.offset), self.members.items(.header), self.members.items(.object)) |off, header, object| { 88 | if (options.archive_members) try self.printArchiveMember(off, header, writer); 89 | if (isImportHeader(object.data)) { 90 | if (!options.headers) continue; 91 | 92 | const hdr = @as(*align(1) const coff.ImportHeader, @ptrCast(object.data.ptr)).*; 93 | const strings = object.data[@sizeOf(coff.ImportHeader)..][0..hdr.size_of_data]; 94 | const import_name = std.mem.sliceTo(@as([*:0]const u8, @ptrCast(strings.ptr)), 0); 95 | const dll_name = std.mem.sliceTo(@as([*:0]const u8, @ptrCast(strings.ptr + import_name.len + 1)), 0); 96 | 97 | try writer.print(" {s: <13}: {X}\n", .{ "Version", hdr.version }); 98 | try writer.print(" {s: <13}: {s}\n", .{ "Machine", @tagName(hdr.machine) }); 99 | try writer.print(" {s: <13}: {X:0>8}\n", .{ "TimeDateStamp", hdr.time_date_stamp }); 100 | try writer.print(" {s: <13}: {X:0>8}\n", .{ "SizeOfData", hdr.size_of_data }); 101 | try writer.print(" {s: <13}: {s}\n", .{ "DLL name", dll_name }); 102 | try writer.print(" {s: <13}: {s}\n", .{ "Symbol name", import_name }); 103 | try writer.print(" {s: <13}: {s}\n", .{ "Type", @tagName(hdr.types.type) }); 104 | try writer.print(" {s: <13}: {s}\n", .{ "Name type", @tagName(hdr.types.name_type) }); 105 | if (hdr.types.name_type == .ORDINAL) { 106 | try writer.print(" {s: <13}: {X}\n", .{ "Ordinal", hdr.hint }); 107 | } else { 108 | try writer.print(" {s: <13}: {X}\n", .{ "Hint", hdr.hint }); 109 | } 110 | switch (hdr.types.name_type) { 111 | .ORDINAL => {}, 112 | .NAME => try writer.print(" {s: <13}: {s}\n", .{ "Name", import_name }), 113 | .NAME_NOPREFIX => try writer.print(" {s: <13}: {s}\n", .{ "Name", std.mem.trimLeft(u8, import_name, "?@_") }), 114 | .NAME_UNDECORATE => { 115 | const trimmed = std.mem.trimLeft(u8, import_name, "?@_"); 116 | const index = std.mem.indexOf(u8, trimmed, "@") orelse trimmed.len; 117 | try writer.print(" {s: <13}: {s}\n", .{ "Name", trimmed[0..index] }); 118 | }, 119 | .NAME_EXPORTAS => { 120 | const actual_name = std.mem.sliceTo(@as([*:0]const u8, @ptrCast(strings.ptr + import_name.len + 1 + dll_name.len + 1)), 0); 121 | try writer.print(" {s: <13}: {s}\n", .{ "Name", actual_name }); 122 | }, 123 | else => unreachable, 124 | } 125 | try writer.writeByte('\n'); 126 | } else { 127 | var opts = options; 128 | opts.summary = false; 129 | try object.print(writer, opts); 130 | } 131 | } 132 | 133 | if (options.summary) try self.printSummary(writer); 134 | } 135 | 136 | fn printArchiveSymbols(self: *const Library, writer: anytype) !void { 137 | try writer.print("Archive symbol table: #{X} symbols\n", .{self.symdef_sorted.indexes.items.len}); 138 | for (self.symdef_sorted.indexes.items) |entry| { 139 | const offset = self.symdef_sorted.members.items[entry.index - 1]; 140 | try writer.print(" {s} at {X}\n", .{ entry.name, offset }); 141 | } 142 | try writer.writeByte('\n'); 143 | } 144 | 145 | fn printArchiveMember(self: *const Library, off: usize, hdr: *const Header, writer: anytype) !void { 146 | const name = hdr.getName() orelse self.getLongname((try hdr.getLongnameOffset()).?); 147 | try writer.print("Archive member name at {X}: {s}\n", .{ off, name }); 148 | try writer.print("{X: >8} time/date\n", .{try hdr.getDate()}); 149 | if (try hdr.getUserId()) |uid| { 150 | try writer.print("{X: >8} uid\n", .{uid}); 151 | } else { 152 | try writer.print("{s: >8} uid\n", .{" "}); 153 | } 154 | if (try hdr.getGroupId()) |gid| { 155 | try writer.print("{X: >8} gid\n", .{gid}); 156 | } else { 157 | try writer.print("{s: >8} gid\n", .{" "}); 158 | } 159 | try writer.print("{X: >8} mode\n", .{try hdr.getMode()}); 160 | try writer.print("{X: >8} size\n", .{try hdr.getSize()}); 161 | if (!std.mem.eql(u8, &hdr.end, end)) { 162 | try writer.writeAll("invalid header end\n"); 163 | } else { 164 | try writer.writeAll("correct header end\n"); 165 | } 166 | try writer.writeByte('\n'); 167 | } 168 | 169 | fn printSummary(self: *const Library, writer: anytype) !void { 170 | try writer.writeAll(" Summary\n\n"); 171 | 172 | var arena = std.heap.ArenaAllocator.init(self.gpa); 173 | defer arena.deinit(); 174 | 175 | var summary = std.StringArrayHashMap(u64).init(arena.allocator()); 176 | 177 | for (self.members.items(.object)) |object| { 178 | if (isImportHeader(object.data)) continue; 179 | const sections = object.getSectionHeaders(); 180 | try summary.ensureUnusedCapacity(sections.len); 181 | 182 | for (sections) |sect| { 183 | const name = sect.getName() orelse object.getStrtab().?.get(sect.getNameOffset().?); 184 | const gop = summary.getOrPutAssumeCapacity(try arena.allocator().dupe(u8, name)); 185 | if (!gop.found_existing) gop.value_ptr.* = 0; 186 | gop.value_ptr.* += sect.size_of_raw_data; 187 | } 188 | } 189 | 190 | const Sort = struct { 191 | fn lessThan(ctx: void, lhs: []const u8, rhs: []const u8) bool { 192 | _ = ctx; 193 | return std.mem.order(u8, lhs, rhs) == .lt; 194 | } 195 | }; 196 | var keys = try std.ArrayList([]const u8).initCapacity(arena.allocator(), summary.keys().len); 197 | keys.appendSliceAssumeCapacity(summary.keys()); 198 | std.mem.sort([]const u8, keys.items, {}, Sort.lessThan); 199 | 200 | for (keys.items) |key| { 201 | const size = summary.get(key).?; 202 | try writer.print(" {X: >8} {s}\n", .{ size, key }); 203 | } 204 | } 205 | 206 | fn getLongname(self: *const Library, off: u32) [:0]const u8 { 207 | assert(off < self.longnames.len); 208 | return std.mem.sliceTo(@as([*:0]const u8, @ptrCast(self.longnames.ptr + off)), 0); 209 | } 210 | 211 | fn genMemberName(comptime name: []const u8) *const [16]u8 { 212 | assert(name.len <= 16); 213 | const padding = 16 - name.len; 214 | return name ++ &[_]u8{' '} ** padding; 215 | } 216 | 217 | const Header = extern struct { 218 | name: [16]u8, 219 | date: [12]u8, 220 | user_id: [6]u8, 221 | group_id: [6]u8, 222 | mode: [8]u8, 223 | size: [10]u8, 224 | end: [2]u8, 225 | 226 | fn getName(hdr: *const Header) ?[]const u8 { 227 | const value = &hdr.name; 228 | if (value[0] == '/') return null; 229 | const sentinel = std.mem.indexOfScalar(u8, value, '/') orelse value.len; 230 | return value[0..sentinel]; 231 | } 232 | 233 | fn getLongnameOffset(hdr: *const Header) !?u32 { 234 | const value = &hdr.name; 235 | if (value[0] != '/') return null; 236 | const trimmed = std.mem.trimRight(u8, value, " "); 237 | return try std.fmt.parseInt(u32, trimmed[1..], 10); 238 | } 239 | 240 | fn getDate(hdr: *const Header) !u32 { 241 | const value = std.mem.trimRight(u8, &hdr.date, " "); 242 | const parsed = try std.fmt.parseInt(i32, value, 10); 243 | return @bitCast(parsed); 244 | } 245 | 246 | fn getUserId(hdr: *const Header) !?u32 { 247 | const value = std.mem.trimRight(u8, &hdr.user_id, " "); 248 | if (value.len == 0) return null; 249 | return try std.fmt.parseInt(u32, value, 10); 250 | } 251 | 252 | fn getGroupId(hdr: *const Header) !?u32 { 253 | const value = std.mem.trimRight(u8, &hdr.group_id, " "); 254 | if (value.len == 0) return null; 255 | return try std.fmt.parseInt(u32, value, 10); 256 | } 257 | 258 | fn getMode(hdr: *const Header) !u32 { 259 | const value = std.mem.trimRight(u8, &hdr.mode, " "); 260 | return std.fmt.parseInt(u32, value, 10); 261 | } 262 | 263 | fn getSize(hdr: *const Header) !u32 { 264 | const value = std.mem.trimRight(u8, &hdr.size, " "); 265 | return std.fmt.parseInt(u32, value, 10); 266 | } 267 | 268 | fn isLinkerMember(hdr: *const Header) bool { 269 | return std.mem.eql(u8, &hdr.name, linker_member); 270 | } 271 | 272 | fn isLongnamesMember(hdr: *const Header) bool { 273 | return std.mem.eql(u8, &hdr.name, longnames_member); 274 | } 275 | 276 | fn isHybridmapMember(hdr: *const Header) bool { 277 | return std.mem.eql(u8, &hdr.name, hybridmap_member); 278 | } 279 | 280 | fn isEcsymbolsMember(hdr: *const Header) bool { 281 | return std.mem.eql(u8, &hdr.name, ecsymbols_member); 282 | } 283 | 284 | fn isXfgmapMember(hdr: *const Header) bool { 285 | return std.mem.eql(u8, &hdr.name, xfgmap_member); 286 | } 287 | }; 288 | 289 | const Symdef = struct { 290 | entries: std.ArrayListUnmanaged(Entry) = .{}, 291 | 292 | fn deinit(tab: *Symdef, allocator: Allocator) void { 293 | tab.entries.deinit(allocator); 294 | } 295 | 296 | fn parse(tab: *Symdef, allocator: Allocator, data: []const u8) !void { 297 | var stream = std.io.fixedBufferStream(data); 298 | const reader = stream.reader(); 299 | 300 | const num = try reader.readInt(u32, .big); 301 | try tab.entries.ensureTotalCapacityPrecise(allocator, num); 302 | 303 | for (0..num) |_| { 304 | const file = try reader.readInt(u32, .big); 305 | tab.entries.appendAssumeCapacity(.{ .name = undefined, .file = file }); 306 | } 307 | 308 | const strtab_off = (num + 1) * @sizeOf(u32); 309 | const strtab_len = data.len - strtab_off; 310 | const strtab = data[strtab_off..]; 311 | 312 | var next: usize = 0; 313 | var i: usize = 0; 314 | while (i < strtab_len) : (next += 1) { 315 | const name = std.mem.sliceTo(@as([*:0]const u8, @ptrCast(strtab.ptr + i)), 0); 316 | tab.entries.items[next].name = name; 317 | i += name.len + 1; 318 | } 319 | } 320 | 321 | const Entry = struct { 322 | /// Symbol name 323 | name: [:0]const u8, 324 | /// Offset of the object member 325 | file: u32, 326 | }; 327 | }; 328 | 329 | const SymdefSorted = struct { 330 | members: std.ArrayListUnmanaged(u32) = .{}, 331 | indexes: std.ArrayListUnmanaged(Entry) = .{}, 332 | 333 | fn deinit(tab: *SymdefSorted, allocator: Allocator) void { 334 | tab.members.deinit(allocator); 335 | tab.indexes.deinit(allocator); 336 | } 337 | 338 | fn parse(tab: *SymdefSorted, allocator: Allocator, data: []const u8) !void { 339 | var stream = std.io.fixedBufferStream(data); 340 | const reader = stream.reader(); 341 | 342 | const num_members = try reader.readInt(u32, .little); 343 | try tab.members.ensureTotalCapacityPrecise(allocator, num_members); 344 | 345 | for (0..num_members) |_| { 346 | const offset = try reader.readInt(u32, .little); 347 | tab.members.appendAssumeCapacity(offset); 348 | } 349 | 350 | const num_indexes = try reader.readInt(u32, .little); 351 | try tab.indexes.ensureTotalCapacityPrecise(allocator, num_indexes); 352 | 353 | for (0..num_indexes) |_| { 354 | const index = try reader.readInt(u16, .little); 355 | tab.indexes.appendAssumeCapacity(.{ .index = index, .name = undefined }); 356 | } 357 | 358 | const strtab_off = 2 * @sizeOf(u32) + num_members * @sizeOf(u32) + num_indexes * @sizeOf(u16); 359 | const strtab_len = data.len - strtab_off; 360 | const strtab = data[strtab_off..]; 361 | 362 | var next: usize = 0; 363 | var i: usize = 0; 364 | while (i < strtab_len) : (next += 1) { 365 | const name = std.mem.sliceTo(@as([*:0]const u8, @ptrCast(strtab.ptr + i)), 0); 366 | tab.indexes.items[next].name = name; 367 | i += name.len + 1; 368 | } 369 | } 370 | 371 | const Entry = struct { 372 | /// Index into the members table. 373 | index: u16, 374 | /// Name of the symbol 375 | name: [:0]const u8, 376 | }; 377 | }; 378 | 379 | const Member = struct { 380 | offset: usize, 381 | header: *const Header, 382 | object: Object, 383 | }; 384 | 385 | fn isImportHeader(data: []const u8) bool { 386 | const sig1 = std.mem.readInt(u16, data[0..2], .little); 387 | const sig2 = std.mem.readInt(u16, data[2..4], .little); 388 | return @as(coff.MachineType, @enumFromInt(sig1)) == .UNKNOWN and sig2 == 0xFFFF; 389 | } 390 | 391 | const magic = "!\n"; 392 | const end = "`\n"; 393 | const pad = "\n"; 394 | const linker_member = genMemberName("/"); 395 | const longnames_member = genMemberName("//"); 396 | const hybridmap_member = genMemberName("//"); 397 | const ecsymbols_member = genMemberName("//"); 398 | const xfgmap_member = genMemberName("//"); 399 | 400 | const assert = std.debug.assert; 401 | const coff = std.coff; 402 | const std = @import("std"); 403 | 404 | const Allocator = std.mem.Allocator; 405 | const Library = @This(); 406 | const Object = @import("Object.zig"); 407 | -------------------------------------------------------------------------------- /src/Object.zig: -------------------------------------------------------------------------------- 1 | gpa: Allocator, 2 | data: []const u8, 3 | 4 | is_image: bool = false, 5 | coff_header_offset: usize = 0, 6 | 7 | pub fn print(self: *const Object, writer: anytype, options: anytype) !void { 8 | if (options.headers) try self.printHeaders(writer); 9 | if (options.directives) try self.printDirectives(writer); 10 | 11 | var base_relocs_dir: ?coff.ImageDataDirectory = null; 12 | var imports_dir: ?coff.ImageDataDirectory = null; 13 | 14 | if (self.is_image) { 15 | const data_dirs = self.getDataDirectories(); 16 | base_relocs_dir = if (options.relocations and @intFromEnum(coff.DirectoryEntry.BASERELOC) < data_dirs.len) 17 | data_dirs[@intFromEnum(coff.DirectoryEntry.BASERELOC)] 18 | else 19 | null; 20 | imports_dir = if (options.imports and @intFromEnum(coff.DirectoryEntry.IMPORT) < data_dirs.len) 21 | data_dirs[@intFromEnum(coff.DirectoryEntry.IMPORT)] 22 | else 23 | null; 24 | } 25 | 26 | const sections = self.getSectionHeaders(); 27 | for (sections, 0..) |*sect_hdr, sect_id| { 28 | if (options.headers) try self.printSectionHeader(writer, @intCast(sect_id), sect_hdr); 29 | if (options.relocations and sect_hdr.number_of_relocations > 0) try self.printRelocations(writer, @intCast(sect_id), sect_hdr); 30 | 31 | if (base_relocs_dir) |dir| { 32 | if (self.getSectionByAddress(dir.virtual_address)) |search| blk: { 33 | if (search != sect_id) break :blk; 34 | try writer.print("BASE RELOCATIONS #{X}\n", .{sect_id + 1}); 35 | const offset = dir.virtual_address - sect_hdr.virtual_address + sect_hdr.pointer_to_raw_data; 36 | const base_relocs = self.data[offset..][0..dir.size]; 37 | 38 | var slice = base_relocs; 39 | while (slice.len > 0) { 40 | const block = @as(*align(1) const coff.BaseRelocationDirectoryEntry, @ptrCast(slice)).*; 41 | const num_relocs = @divExact(block.block_size - 8, @sizeOf(coff.BaseRelocation)); 42 | const block_relocs = @as([*]align(1) const coff.BaseRelocation, @ptrCast(slice[8..]))[0..num_relocs]; 43 | slice = slice[block.block_size..]; 44 | 45 | try writer.print("{x: >8} RVA, {x: >8} SizeOfBlock\n", .{ block.page_rva, block.block_size }); 46 | for (block_relocs) |brel| { 47 | try writer.print("{x: >8} {s: <20}", .{ brel.offset, @tagName(brel.type) }); 48 | switch (brel.type) { 49 | .ABSOLUTE => {}, 50 | .DIR64 => { 51 | const rebase_offset = self.getFileOffsetForAddress(block.page_rva + brel.offset); 52 | const pointer = mem.readInt(u64, self.data[rebase_offset..][0..8], .little); 53 | try writer.print(" {x:0>16}", .{pointer}); 54 | }, 55 | else => {}, // TODO 56 | } 57 | try writer.writeByte('\n'); 58 | } 59 | } 60 | 61 | try writer.writeByte('\n'); 62 | } 63 | } 64 | 65 | if (imports_dir) |dir| { 66 | if (self.getSectionByAddress(dir.virtual_address)) |search| blk: { 67 | if (search != sect_id) break :blk; 68 | try writer.writeAll("Section contains the following imports:\n\n"); 69 | const offset = dir.virtual_address - sect_hdr.virtual_address + sect_hdr.pointer_to_raw_data; 70 | const raw_imports = self.data[offset..][0..dir.size]; 71 | const num_imports = @divExact(dir.size, @sizeOf(coff.ImportDirectoryEntry)) - 1; // We exclude the NULL entry 72 | const imports = @as([*]align(1) const coff.ImportDirectoryEntry, @ptrCast(raw_imports))[0..num_imports]; 73 | 74 | const hdr = self.getOptionalHeader(); 75 | const is_32bit = hdr.magic == coff.IMAGE_NT_OPTIONAL_HDR32_MAGIC; 76 | const image_base = self.getImageBase(); 77 | 78 | for (imports) |import| { 79 | const name_offset = self.getFileOffsetForAddress(import.name_rva); 80 | const name = mem.sliceTo(@as([*:0]const u8, @ptrCast(self.data.ptr + name_offset)), 0); 81 | 82 | try writer.print(" {s}\n", .{name}); 83 | try writer.print("{x: >20} Import Address Table\n", .{import.import_address_table_rva + image_base}); 84 | try writer.print("{x: >20} Import Name Table\n", .{import.import_lookup_table_rva + image_base}); 85 | try writer.print("{x: >20} time date stamp\n", .{import.time_date_stamp}); 86 | try writer.print("{x: >20} Index of first forwarder reference\n", .{import.forwarder_chain}); 87 | 88 | const lookup_table_offset = self.getFileOffsetForAddress(import.import_lookup_table_rva); 89 | if (is_32bit) { 90 | const raw_ptr = try std.math.alignCast(.@"4", self.data.ptr + lookup_table_offset); 91 | const raw_lookups = mem.sliceTo(@as([*:0]const u32, @ptrCast(raw_ptr)), 0); 92 | for (raw_lookups) |rl| { 93 | if (coff.ImportLookupEntry32.getImportByOrdinal(rl)) |_| { 94 | // TODO 95 | } else if (coff.ImportLookupEntry32.getImportByName(rl)) |by_name| { 96 | const by_name_offset = self.getFileOffsetForAddress(by_name.name_table_rva); 97 | const by_name_entry = @as(*align(1) const coff.ImportHintNameEntry, @ptrCast(self.data.ptr + by_name_offset)); 98 | const symbol_hint = by_name_entry.hint; 99 | const symbol_name = mem.sliceTo(@as([*:0]const u8, @ptrCast(&by_name_entry.name)), 0); 100 | try writer.print("{x: >30} {s}\n", .{ symbol_hint, symbol_name }); 101 | } else unreachable; 102 | } 103 | } else { 104 | const raw_ptr = try std.math.alignCast(.@"8", self.data.ptr + lookup_table_offset); 105 | const raw_lookups = mem.sliceTo(@as([*:0]const u64, @ptrCast(raw_ptr)), 0); 106 | for (raw_lookups) |rl| { 107 | if (coff.ImportLookupEntry64.getImportByOrdinal(rl)) |_| { 108 | // TODO 109 | } else if (coff.ImportLookupEntry64.getImportByName(rl)) |by_name| { 110 | const by_name_offset = self.getFileOffsetForAddress(by_name.name_table_rva); 111 | const by_name_entry = @as(*align(1) const coff.ImportHintNameEntry, @ptrCast(self.data.ptr + by_name_offset)); 112 | const symbol_hint = by_name_entry.hint; 113 | const symbol_name = mem.sliceTo(@as([*:0]const u8, @ptrCast(&by_name_entry.name)), 0); 114 | try writer.print("{x: >30} {s}\n", .{ symbol_hint, symbol_name }); 115 | } else unreachable; 116 | } 117 | } 118 | } 119 | 120 | try writer.writeByte('\n'); 121 | } 122 | } 123 | } 124 | 125 | if (options.symbols) try self.printSymbols(writer); 126 | if (options.summary) try self.printSummary(writer); 127 | } 128 | 129 | fn printHeaders(self: *const Object, writer: anytype) !void { 130 | const coff_header = self.getCoffHeader(); 131 | 132 | // COFF header (object and image) 133 | try writer.writeAll("FILE HEADER VALUES\n"); 134 | try writer.print("{x: >20} machine ({s})\n", .{ @intFromEnum(coff_header.machine), @tagName(coff_header.machine) }); 135 | { 136 | const fields = std.meta.fields(coff.CoffHeader); 137 | inline for (&[_][]const u8{ 138 | "number of sections", 139 | "time date stamp", 140 | "file pointer to symbol table", 141 | "number of symbols", 142 | "size of optional header", 143 | }, 0..) |desc, i| { 144 | const field = fields[i + 1]; 145 | try writer.print("{x: >20} {s}\n", .{ @field(coff_header, field.name), desc }); 146 | } 147 | } 148 | try writer.print("{x: >20} {s}\n", .{ @as(u16, @bitCast(coff_header.flags)), "flags" }); 149 | { 150 | const fields = std.meta.fields(coff.CoffHeaderFlags); 151 | inline for (&[_][]const u8{ 152 | "Relocs stripped", 153 | "Executable", 154 | "COFF line numbers have been removed", 155 | "COFF symbol table entries for local symbols have been removed", 156 | "Aggressively trim working set", 157 | "Application can handle > 2-GB addresses", 158 | "Reserved", 159 | "Little endian", 160 | "32-bit", 161 | "Debugging information removed", 162 | "Fully load and copy to swap file from removable media", 163 | "Fully load and copy to swap file from network media", 164 | "System file", 165 | "DLL", 166 | "Uniprocessor machine only", 167 | "Big endian", 168 | }, 0..) |desc, i| { 169 | const field = fields[i]; 170 | if (@field(coff_header.flags, field.name) == 0b1) { 171 | try writer.print("{s: >22} {s}\n", .{ "", desc }); 172 | } 173 | } 174 | } 175 | try writer.writeByte('\n'); 176 | 177 | if (coff_header.size_of_optional_header > 0) { 178 | const common_hdr = self.getOptionalHeader(); 179 | switch (common_hdr.magic) { 180 | coff.IMAGE_NT_OPTIONAL_HDR32_MAGIC => { 181 | const pe_header = self.getOptionalHeader32(); 182 | const fields = std.meta.fields(coff.OptionalHeaderPE32); 183 | inline for (&[_][]const u8{ 184 | "magic", 185 | "linker version (major)", 186 | "linker version (minor)", 187 | "size of code", 188 | "size of initialized data", 189 | "size of uninitialized data", 190 | "entry point", 191 | "base of code", 192 | "base of data", 193 | "image base", 194 | "section alignment", 195 | "file alignment", 196 | "OS version (major)", 197 | "OS version (minor)", 198 | "image version (major)", 199 | "image version (minor)", 200 | "subsystem version (major)", 201 | "subsystem version (minor)", 202 | "Win32 version", 203 | "size of image", 204 | "size of headers", 205 | "checksum", 206 | "subsystem", 207 | "DLL flags", 208 | "size of stack reserve", 209 | "size of stack commit", 210 | "size of heap reserve", 211 | "size of heap commit", 212 | "loader flags", 213 | "number of RVA and sizes", 214 | }, 0..) |desc, i| { 215 | const field = fields[i]; 216 | if (comptime mem.eql(u8, field.name, "dll_flags")) { 217 | try writer.print("{x: >20} {s}\n", .{ @as(u16, @bitCast(pe_header.dll_flags)), desc }); 218 | try printDllFlags(pe_header.dll_flags, writer); 219 | } else if (comptime mem.eql(u8, field.name, "subsystem")) { 220 | try writer.print("{x: >20} {s} # ({s})\n", .{ 221 | @intFromEnum(pe_header.subsystem), 222 | desc, 223 | @tagName(pe_header.subsystem), 224 | }); 225 | } else { 226 | try writer.print("{x: >20} {s}", .{ @field(pe_header, field.name), desc }); 227 | if (comptime mem.eql(u8, field.name, "magic")) { 228 | try writer.writeAll(" # (PE32+)"); 229 | } 230 | try writer.writeByte('\n'); 231 | } 232 | } 233 | }, 234 | coff.IMAGE_NT_OPTIONAL_HDR64_MAGIC => { 235 | const pe_header = self.getOptionalHeader64(); 236 | const fields = std.meta.fields(coff.OptionalHeaderPE64); 237 | inline for (&[_][]const u8{ 238 | "magic", 239 | "linker version (major)", 240 | "linker version (minor)", 241 | "size of code", 242 | "size of initialized data", 243 | "size of uninitialized data", 244 | "entry point", 245 | "base of code", 246 | "image base", 247 | "section alignment", 248 | "file alignment", 249 | "OS version (major)", 250 | "OS version (minor)", 251 | "image version (major)", 252 | "image version (minor)", 253 | "subsystem version (major)", 254 | "subsystem version (minor)", 255 | "Win32 version", 256 | "size of image", 257 | "size of headers", 258 | "checksum", 259 | "subsystem", 260 | "DLL flags", 261 | "size of stack reserve", 262 | "size of stack commit", 263 | "size of heap reserve", 264 | "size of heap commit", 265 | "loader flags", 266 | "number of directories", 267 | }, 0..) |desc, i| { 268 | const field = fields[i]; 269 | if (comptime mem.eql(u8, field.name, "dll_flags")) { 270 | try writer.print("{x: >20} {s}\n", .{ @as(u16, @bitCast(pe_header.dll_flags)), desc }); 271 | try printDllFlags(pe_header.dll_flags, writer); 272 | } else if (comptime mem.eql(u8, field.name, "subsystem")) { 273 | try writer.print("{x: >20} {s} # ({s})\n", .{ 274 | @intFromEnum(pe_header.subsystem), 275 | desc, 276 | @tagName(pe_header.subsystem), 277 | }); 278 | } else { 279 | try writer.print("{x: >20} {s}", .{ @field(pe_header, field.name), desc }); 280 | if (comptime mem.eql(u8, field.name, "magic")) { 281 | try writer.writeAll(" # (PE32+)"); 282 | } 283 | try writer.writeByte('\n'); 284 | } 285 | } 286 | }, 287 | else => { 288 | std.log.err("unknown PE optional header magic: {x}", .{common_hdr.magic}); 289 | return error.UnknownPEOptionalHeaderMagic; 290 | }, 291 | } 292 | 293 | if (self.getNumberOfDataDirectories() > 0) { 294 | const data_dirs = self.getDataDirectories(); 295 | inline for (&[_][]const u8{ 296 | "Export Directory", 297 | "Import Directory", 298 | "Resource Directory", 299 | "Exception Directory", 300 | "Certificates Directory", 301 | "Base Relocation Directory", 302 | "Debug Directory", 303 | "Architecture Directory", 304 | "Global Pointer Directory", 305 | "Thread Storage Directory", 306 | "Load Configuration Directory", 307 | "Bound Import Directory", 308 | "Import Address Table Directory", 309 | "Delay Import Directory", 310 | "COM Descriptor Directory", 311 | "Reserved Directory", 312 | }, 0..) |desc, i| { 313 | if (i < self.getNumberOfDataDirectories()) { 314 | const data_dir = data_dirs[i]; 315 | try writer.print("{x: >20} [{x: >10}] RVA [size] of {s}\n", .{ 316 | data_dir.virtual_address, 317 | data_dir.size, 318 | desc, 319 | }); 320 | } 321 | } 322 | } 323 | } 324 | } 325 | 326 | fn printDllFlags(flags: coff.DllFlags, writer: anytype) !void { 327 | inline for (std.meta.fields(coff.DllFlags)) |field| { 328 | if (field.type == u1) { 329 | if (@field(flags, field.name) == 0b1) { 330 | try writer.print("{s: >22} {s}\n", .{ "", field.name }); 331 | } 332 | } 333 | } 334 | } 335 | 336 | fn printSectionHeader(self: *const Object, writer: anytype, sect_id: u16, sect_hdr: *align(1) const coff.SectionHeader) !void { 337 | const fields = std.meta.fields(coff.SectionHeader); 338 | 339 | try writer.print("SECTION HEADER #{X}\n", .{sect_id + 1}); 340 | 341 | const name = self.getSectionName(sect_hdr); 342 | try writer.print("{s: >20} name\n", .{name}); 343 | 344 | inline for (&[_][]const u8{ 345 | "virtual size", 346 | "virtual address", 347 | "size of raw data", 348 | "file pointer to raw data", 349 | "file pointer to relocation table", 350 | "file pointer to line numbers", 351 | "number of relocations", 352 | "number of line numbers", 353 | }, 0..) |desc, field_i| { 354 | const field = fields[field_i + 1]; 355 | try writer.print("{x: >20} {s}\n", .{ @field(sect_hdr, field.name), desc }); 356 | } 357 | 358 | try writer.print("{x: >20} flags\n", .{@as(u32, @bitCast(sect_hdr.flags))}); 359 | inline for (std.meta.fields(coff.SectionHeaderFlags)) |flag_field| { 360 | if (flag_field.type == u1) { 361 | if (@field(sect_hdr.flags, flag_field.name) == 0b1) { 362 | try writer.print("{s: >22} {s}\n", .{ "", flag_field.name }); 363 | } 364 | } 365 | } 366 | if (sect_hdr.getAlignment()) |alignment| { 367 | try writer.print("{s: >22} {d} byte align\n", .{ "", alignment }); 368 | } 369 | try writer.writeByte('\n'); 370 | } 371 | 372 | fn printDirectives(self: *const Object, writer: anytype) !void { 373 | // TODO handle UTF-8 374 | const sect = self.getSectionByName(".drectve") orelse return; 375 | if (sect.flags.LNK_INFO == 0) return; 376 | const data = self.data[sect.pointer_to_raw_data..][0..sect.size_of_raw_data]; 377 | try writer.writeAll( 378 | \\ Linker Directives 379 | \\ _________________ 380 | \\ 381 | ); 382 | var it = std.mem.splitScalar(u8, data, ' '); 383 | while (it.next()) |dir| { 384 | if (dir.len == 0) continue; 385 | try writer.print(" {s}\n", .{dir}); 386 | } 387 | try writer.writeByte('\n'); 388 | } 389 | 390 | fn printRelocations(self: *const Object, writer: anytype, sect_id: u16, sect_hdr: *align(1) const coff.SectionHeader) !void { 391 | try writer.print("RELOCATIONS #{X}\n\n", .{sect_id + 1}); 392 | const machine = self.getCoffHeader().machine; 393 | var i: usize = 0; 394 | var offset = sect_hdr.pointer_to_relocations; 395 | const data = self.getSectionData(sect_id); 396 | const symtab = self.getSymtab().?; 397 | const strtab = self.getStrtab().?; 398 | try writer.print(" {s: <8} {s: <16} {s: <16} {s: <12} {s}\n", .{ "Offset", "Type", "Applied To", "Symbol Index", "Symbol Name" }); 399 | try writer.print(" {s:_<8} {s:_<16} {s:_<16} {s:_<12} {s:_<11}\n", .{ "_", "_", "_", "_", "_" }); 400 | while (i < sect_hdr.number_of_relocations) : (i += 1) { 401 | const reloc = @as(*align(1) const coff.Relocation, @ptrCast(self.data.ptr + offset)).*; 402 | // Reloc type 403 | var rel_type_buffer: [16]u8 = [_]u8{' '} ** 16; 404 | const rel_type = switch (machine) { 405 | .X64 => @tagName(@as(coff.ImageRelAmd64, @enumFromInt(reloc.type))), 406 | .ARM64 => @tagName(@as(coff.ImageRelArm64, @enumFromInt(reloc.type))), 407 | else => "unknown", 408 | }; 409 | @memcpy(rel_type_buffer[0..rel_type.len], rel_type); // TODO check we don't overflow 410 | _ = std.ascii.upperString(&rel_type_buffer, &rel_type_buffer); 411 | try writer.print(" {X:0>8} {s: <16}", .{ 412 | reloc.virtual_address, 413 | &rel_type_buffer, 414 | }); 415 | // Applied To 416 | const code_size = getCodeSize(reloc, self.*); 417 | const code = switch (code_size) { 418 | 0 => 0, 419 | 1 => data[reloc.virtual_address], 420 | 2 => mem.readInt(u16, data[reloc.virtual_address..][0..2], .little), 421 | 4 => mem.readInt(u32, data[reloc.virtual_address..][0..4], .little), 422 | 8 => mem.readInt(u64, data[reloc.virtual_address..][0..8], .little), 423 | else => unreachable, 424 | }; 425 | switch (code_size) { 426 | 0 => try writer.print("{s: <16}", .{" "}), 427 | 1 => try writer.print("{s: <15}{X:0>2}", .{ " ", code }), 428 | 2 => try writer.print("{s: <12}{X:0>4}", .{ " ", code }), 429 | 4 => try writer.print("{s: <8}{X:0>8}", .{ " ", code }), 430 | 8 => try writer.print("{X:0>16}", .{code}), 431 | else => unreachable, 432 | } 433 | // Symbol Index + Name 434 | const sym = symtab.at(reloc.symbol_table_index, .symbol).symbol; 435 | const name = sym.getName() orelse blk: { 436 | const off = sym.getNameOffset() orelse return error.MalformedSymbolRecord; 437 | break :blk strtab.get(off); 438 | }; 439 | 440 | try writer.print(" {X: >12} {s}\n", .{ 441 | reloc.symbol_table_index, 442 | name, 443 | }); 444 | 445 | offset += 10; 446 | } 447 | try writer.writeByte('\n'); 448 | } 449 | 450 | fn printSymbols(self: *const Object, writer: anytype) !void { 451 | const symtab = self.getSymtab() orelse { 452 | return writer.writeAll("No symbol table found.\n"); 453 | }; 454 | 455 | try writer.writeAll("COFF SYMBOL TABLE\n"); 456 | 457 | const sections = self.getSectionHeaders(); 458 | const strtab = self.getStrtab().?; 459 | var slice = symtab.slice(0, null); 460 | 461 | var index: usize = 0; 462 | var aux_counter: usize = 0; 463 | var aux_tag: ?coff.Symtab.Tag = null; 464 | while (slice.next()) |sym| { 465 | if (aux_counter == 0) { 466 | try writer.print("{X:0>3} {X:0>8} ", .{ index, sym.value }); 467 | switch (sym.section_number) { 468 | .UNDEFINED, 469 | .ABSOLUTE, 470 | .DEBUG, 471 | => try writer.print("{s: <9} ", .{@tagName(sym.section_number)}), 472 | else => try writer.print("SECT{X: <5} ", .{@intFromEnum(sym.section_number)}), 473 | } 474 | const name = sym.getName() orelse blk: { 475 | const offset = sym.getNameOffset() orelse return error.MalformedSymbolRecord; 476 | break :blk strtab.get(offset); 477 | }; 478 | try writer.print("{s: <6} {s: <8} {s: <20} | {s}\n", .{ 479 | @tagName(sym.type.base_type), 480 | @tagName(sym.type.complex_type), 481 | @tagName(sym.storage_class), 482 | name, 483 | }); 484 | 485 | aux_tag = aux_tag: { 486 | switch (sym.section_number) { 487 | .UNDEFINED => { 488 | if (sym.storage_class == .WEAK_EXTERNAL and sym.value == 0) { 489 | break :aux_tag .weak_ext; 490 | } 491 | }, 492 | .ABSOLUTE => {}, 493 | .DEBUG => { 494 | if (sym.storage_class == .FILE) { 495 | break :aux_tag .file_def; 496 | } 497 | }, 498 | else => { 499 | if (sym.storage_class == .FUNCTION) { 500 | break :aux_tag .debug_info; 501 | } 502 | if (sym.storage_class == .EXTERNAL and sym.type.complex_type == .FUNCTION) { 503 | break :aux_tag .func_def; 504 | } 505 | if (sym.storage_class == .STATIC) { 506 | for (sections) |*sect| { 507 | const sect_name = self.getSectionName(sect); 508 | if (mem.eql(u8, sect_name, name)) { 509 | break :aux_tag .sect_def; 510 | } 511 | } 512 | } 513 | }, 514 | } 515 | break :aux_tag null; 516 | }; 517 | 518 | aux_counter = sym.number_of_aux_symbols; 519 | } else { 520 | if (aux_tag) |tag| switch (symtab.at(index, tag)) { 521 | .weak_ext => |weak_ext| { 522 | try writer.print(" Default index {x: >8} {s}\n", .{ weak_ext.tag_index, @tagName(weak_ext.flag) }); 523 | }, 524 | .file_def => |*file_def| { 525 | try writer.print(" {s}\n", .{file_def.getFileName()}); 526 | }, 527 | .sect_def => |sect_def| { 528 | try writer.print(" Section length {x: >4}, #relocs {x: >4}, #linenums {x: >4}, checksum {x: >8}", .{ 529 | sect_def.length, 530 | sect_def.number_of_relocations, 531 | sect_def.number_of_linenumbers, 532 | sect_def.checksum, 533 | }); 534 | const st_sym = symtab.at(index - aux_counter, .symbol).symbol; 535 | const sect = sections[@intFromEnum(st_sym.section_number) - 1]; 536 | if (sect.isComdat()) { 537 | try writer.print(", selection {d} ({s})", .{ @intFromEnum(sect_def.selection), @tagName(sect_def.selection) }); 538 | } else { 539 | assert(sect_def.selection == .NONE); // Expected non COMDAT section would not set the selection field in aux record. 540 | } 541 | try writer.writeByte('\n'); 542 | }, 543 | .func_def => |func_def| { 544 | try writer.print(" Tag index {x:0>8}, size {x:0>8}, lines {x:0>8}, next function {x:0>8}\n", .{ 545 | func_def.tag_index, 546 | func_def.total_size, 547 | func_def.pointer_to_linenumber, 548 | func_def.pointer_to_next_function, 549 | }); 550 | }, 551 | .debug_info => |debug_info| { 552 | try writer.print(" Line# {x}", .{debug_info.linenumber}); 553 | const st_sym = symtab.at(index - aux_counter, .symbol).symbol; 554 | const name = st_sym.getName() orelse blk: { 555 | const offset = st_sym.getNameOffset() orelse return error.MalformedSymbolRecord; 556 | break :blk strtab.get(offset); 557 | }; 558 | if (mem.eql(u8, name, ".bf")) { 559 | try writer.print(", end {x:0>8}", .{debug_info.pointer_to_next_function}); 560 | } 561 | try writer.writeByte('\n'); 562 | }, 563 | else => |other| { 564 | std.log.warn("Unhandled auxiliary symbol: {}", .{other}); 565 | }, 566 | }; 567 | 568 | aux_counter -= 1; 569 | } 570 | 571 | index += 1; 572 | } 573 | 574 | try writer.print("\nString table size = 0x{x} bytes\n\n", .{strtab.buffer.len}); 575 | } 576 | 577 | fn printSummary(self: *const Object, writer: anytype) !void { 578 | try writer.writeAll(" Summary\n\n"); 579 | 580 | const sections = self.getSectionHeaders(); 581 | 582 | var arena = std.heap.ArenaAllocator.init(self.gpa); 583 | defer arena.deinit(); 584 | 585 | var summary = std.StringArrayHashMap(u64).init(arena.allocator()); 586 | try summary.ensureUnusedCapacity(sections.len); 587 | 588 | for (sections) |sect| { 589 | const name = sect.getName() orelse self.getStrtab().?.get(sect.getNameOffset().?); 590 | const gop = summary.getOrPutAssumeCapacity(try arena.allocator().dupe(u8, name)); 591 | if (!gop.found_existing) gop.value_ptr.* = 0; 592 | gop.value_ptr.* += if (self.is_image) 593 | mem.alignForward(u64, sect.virtual_size, 0x1000) // TODO don't always assume 0x1000 page size 594 | else 595 | sect.size_of_raw_data; 596 | } 597 | 598 | const Sort = struct { 599 | fn lessThan(ctx: void, lhs: []const u8, rhs: []const u8) bool { 600 | _ = ctx; 601 | return std.mem.order(u8, lhs, rhs) == .lt; 602 | } 603 | }; 604 | var keys = try std.ArrayList([]const u8).initCapacity(arena.allocator(), summary.keys().len); 605 | keys.appendSliceAssumeCapacity(summary.keys()); 606 | std.mem.sort([]const u8, keys.items, {}, Sort.lessThan); 607 | 608 | for (keys.items) |key| { 609 | const size = summary.get(key).?; 610 | try writer.print(" {X: >8} {s}\n", .{ size, key }); 611 | } 612 | } 613 | 614 | pub fn getCoffHeader(self: Object) coff.CoffHeader { 615 | return @as(*align(1) const coff.CoffHeader, @ptrCast(self.data[self.coff_header_offset..][0..@sizeOf(coff.CoffHeader)])).*; 616 | } 617 | 618 | pub fn getOptionalHeader(self: Object) coff.OptionalHeader { 619 | assert(self.is_image); 620 | const offset = self.coff_header_offset + @sizeOf(coff.CoffHeader); 621 | return @as(*align(1) const coff.OptionalHeader, @ptrCast(self.data[offset..][0..@sizeOf(coff.OptionalHeader)])).*; 622 | } 623 | 624 | pub fn getOptionalHeader32(self: Object) coff.OptionalHeaderPE32 { 625 | assert(self.is_image); 626 | const offset = self.coff_header_offset + @sizeOf(coff.CoffHeader); 627 | return @as(*align(1) const coff.OptionalHeaderPE32, @ptrCast(self.data[offset..][0..@sizeOf(coff.OptionalHeaderPE32)])).*; 628 | } 629 | 630 | pub fn getOptionalHeader64(self: Object) coff.OptionalHeaderPE64 { 631 | assert(self.is_image); 632 | const offset = self.coff_header_offset + @sizeOf(coff.CoffHeader); 633 | return @as(*align(1) const coff.OptionalHeaderPE64, @ptrCast(self.data[offset..][0..@sizeOf(coff.OptionalHeaderPE64)])).*; 634 | } 635 | 636 | pub fn getImageBase(self: Object) u64 { 637 | const hdr = self.getOptionalHeader(); 638 | return switch (hdr.magic) { 639 | coff.IMAGE_NT_OPTIONAL_HDR32_MAGIC => self.getOptionalHeader32().image_base, 640 | coff.IMAGE_NT_OPTIONAL_HDR64_MAGIC => self.getOptionalHeader64().image_base, 641 | else => unreachable, // We assume we have validated the header already 642 | }; 643 | } 644 | 645 | pub fn getNumberOfDataDirectories(self: Object) u32 { 646 | const hdr = self.getOptionalHeader(); 647 | return switch (hdr.magic) { 648 | coff.IMAGE_NT_OPTIONAL_HDR32_MAGIC => self.getOptionalHeader32().number_of_rva_and_sizes, 649 | coff.IMAGE_NT_OPTIONAL_HDR64_MAGIC => self.getOptionalHeader64().number_of_rva_and_sizes, 650 | else => unreachable, // We assume we have validated the header already 651 | }; 652 | } 653 | 654 | pub fn getDataDirectories(self: *const Object) []align(1) const coff.ImageDataDirectory { 655 | const hdr = self.getOptionalHeader(); 656 | const size: usize = switch (hdr.magic) { 657 | coff.IMAGE_NT_OPTIONAL_HDR32_MAGIC => @sizeOf(coff.OptionalHeaderPE32), 658 | coff.IMAGE_NT_OPTIONAL_HDR64_MAGIC => @sizeOf(coff.OptionalHeaderPE64), 659 | else => unreachable, // We assume we have validated the header already 660 | }; 661 | const offset = self.coff_header_offset + @sizeOf(coff.CoffHeader) + size; 662 | return @as([*]align(1) const coff.ImageDataDirectory, @ptrCast(self.data[offset..]))[0..self.getNumberOfDataDirectories()]; 663 | } 664 | 665 | pub fn getSymtab(self: *const Object) ?coff.Symtab { 666 | const coff_header = self.getCoffHeader(); 667 | if (coff_header.pointer_to_symbol_table == 0) return null; 668 | 669 | const offset = coff_header.pointer_to_symbol_table; 670 | const size = coff_header.number_of_symbols * coff.Symbol.sizeOf(); 671 | return .{ .buffer = self.data[offset..][0..size] }; 672 | } 673 | 674 | pub fn getStrtab(self: *const Object) ?coff.Strtab { 675 | const coff_header = self.getCoffHeader(); 676 | if (coff_header.pointer_to_symbol_table == 0) return null; 677 | 678 | const offset = coff_header.pointer_to_symbol_table + coff.Symbol.sizeOf() * coff_header.number_of_symbols; 679 | const size = mem.readInt(u32, self.data[offset..][0..4], .little); 680 | return .{ .buffer = self.data[offset..][0..size] }; 681 | } 682 | 683 | pub fn getSectionHeaders(self: *const Object) []align(1) const coff.SectionHeader { 684 | const coff_header = self.getCoffHeader(); 685 | const offset = self.coff_header_offset + @sizeOf(coff.CoffHeader) + coff_header.size_of_optional_header; 686 | return @as([*]align(1) const coff.SectionHeader, @ptrCast(self.data.ptr + offset))[0..coff_header.number_of_sections]; 687 | } 688 | 689 | pub fn getSectionName(self: *const Object, sect_hdr: *align(1) const coff.SectionHeader) []const u8 { 690 | const name = sect_hdr.getName() orelse blk: { 691 | const strtab = self.getStrtab().?; 692 | const name_offset = sect_hdr.getNameOffset().?; 693 | break :blk strtab.get(name_offset); 694 | }; 695 | return name; 696 | } 697 | 698 | pub fn getSectionByName(self: *const Object, comptime name: []const u8) ?*align(1) const coff.SectionHeader { 699 | for (self.getSectionHeaders()) |*sect| { 700 | if (mem.eql(u8, self.getSectionName(sect), name)) { 701 | return sect; 702 | } 703 | } 704 | return null; 705 | } 706 | 707 | pub fn getSectionData(self: *const Object, sect_id: u16) []const u8 { 708 | const sec = self.getSectionHeaders()[sect_id]; 709 | return self.data[sec.pointer_to_raw_data..][0..sec.size_of_raw_data]; 710 | } 711 | 712 | pub fn getSectionByAddress(self: Object, rva: u32) ?u16 { 713 | for (self.getSectionHeaders(), 0..) |*sect_hdr, sect_id| { 714 | if (rva >= sect_hdr.virtual_address and rva < sect_hdr.virtual_address + sect_hdr.virtual_size) 715 | return @as(u16, @intCast(sect_id)); 716 | } else return null; 717 | } 718 | 719 | pub fn getFileOffsetForAddress(self: Object, rva: u32) u32 { 720 | const sections = self.getSectionHeaders(); 721 | const sect_id = self.getSectionByAddress(rva).?; 722 | const sect = §ions[sect_id]; 723 | return rva - sect.virtual_address + sect.pointer_to_raw_data; 724 | } 725 | 726 | fn getCodeSize(rel: coff.Relocation, obj: Object) u8 { 727 | const machine = obj.getCoffHeader().machine; 728 | return switch (machine) { 729 | .X64 => switch (@as(coff.ImageRelAmd64, @enumFromInt(rel.type))) { 730 | .absolute => 0, 731 | .addr64 => 8, 732 | .addr32, 733 | .addr32nb, 734 | .rel32, 735 | .rel32_1, 736 | .rel32_2, 737 | .rel32_3, 738 | .rel32_4, 739 | .rel32_5, 740 | => 4, 741 | .section => 2, 742 | .secrel => 4, 743 | .secrel7 => 1, 744 | .token => 8, 745 | .srel32 => 4, 746 | .pair, 747 | .sspan32, 748 | => 4, 749 | else => unreachable, 750 | }, 751 | .ARM64 => switch (@as(coff.ImageRelArm64, @enumFromInt(rel.type))) { 752 | .absolute => 0, 753 | .addr32, 754 | .addr32nb, 755 | => 4, 756 | .branch26, 757 | .pagebase_rel21, 758 | .rel21, 759 | .pageoffset_12a, 760 | .pageoffset_12l, 761 | .low12a, 762 | .high12a, 763 | .low12l, 764 | => 4, 765 | .secrel => 4, 766 | .token => 8, 767 | .section => 2, 768 | .addr64 => 8, 769 | .branch19, .branch14 => 4, 770 | .rel32 => 4, 771 | else => unreachable, 772 | }, 773 | else => @panic("TODO this arch support"), 774 | }; 775 | } 776 | 777 | const assert = std.debug.assert; 778 | const coff = std.coff; 779 | const fs = std.fs; 780 | const mem = std.mem; 781 | const std = @import("std"); 782 | 783 | const Allocator = mem.Allocator; 784 | const Object = @This(); 785 | --------------------------------------------------------------------------------