├── .gitmodules ├── .gitignore ├── .github └── workflows │ └── ci.yml ├── LICENSE ├── src ├── Context.zig ├── Context │ ├── Elf.zig │ ├── MachO.zig │ └── Wasm.zig ├── main.zig ├── AbbrevTable.zig ├── CompileUnit.zig └── DwarfDump.zig └── README.md /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache/ 2 | zig-out/ -------------------------------------------------------------------------------- /.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: goto-bus-stop/setup-zig@v1 21 | with: 22 | version: master 23 | - run: zig fmt --check src 24 | - run: zig build install 25 | 26 | -------------------------------------------------------------------------------- /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. 22 | 23 | -------------------------------------------------------------------------------- /src/Context.zig: -------------------------------------------------------------------------------- 1 | const Context = @This(); 2 | 3 | const std = @import("std"); 4 | const assert = std.debug.assert; 5 | const mem = std.mem; 6 | 7 | const Allocator = mem.Allocator; 8 | 9 | pub const Elf = @import("Context/Elf.zig"); 10 | pub const MachO = @import("Context/MachO.zig"); 11 | pub const Wasm = @import("Context/Wasm.zig"); 12 | 13 | tag: Tag, 14 | data: []const u8, 15 | 16 | pub const Tag = enum { 17 | elf, 18 | macho, 19 | wasm, 20 | }; 21 | 22 | pub fn cast(base: *Context, comptime T: type) ?*T { 23 | if (base.tag != T.base_tag) 24 | return null; 25 | 26 | return @fieldParentPtr(T, "base", base); 27 | } 28 | 29 | pub fn constCast(base: *const Context, comptime T: type) ?*const T { 30 | if (base.tag != T.base_tag) 31 | return null; 32 | 33 | return @fieldParentPtr(T, "base", base); 34 | } 35 | 36 | pub fn deinit(base: *Context, gpa: Allocator) void { 37 | gpa.free(base.data); 38 | } 39 | 40 | pub fn destroy(base: *Context, gpa: Allocator) void { 41 | base.deinit(gpa); 42 | switch (base.tag) { 43 | .elf => { 44 | const parent = @fieldParentPtr(Elf, "base", base); 45 | parent.deinit(gpa); 46 | gpa.destroy(parent); 47 | }, 48 | .macho => { 49 | const parent = @fieldParentPtr(MachO, "base", base); 50 | parent.deinit(gpa); 51 | gpa.destroy(parent); 52 | }, 53 | .wasm => { 54 | const parent = @fieldParentPtr(Wasm, "base", base); 55 | parent.deinit(gpa); 56 | gpa.destroy(parent); 57 | }, 58 | } 59 | } 60 | 61 | pub fn parse(gpa: Allocator, data: []const u8) !*Context { 62 | if (Elf.isElfFile(data)) { 63 | return &(try Elf.parse(gpa, data)).base; 64 | } 65 | if (MachO.isMachOFile(data)) { 66 | return &(try MachO.parse(gpa, data)).base; 67 | } 68 | if (Wasm.isWasmFile(data)) { 69 | return &(try Wasm.parse(gpa, data)).base; 70 | } 71 | return error.UnknownFileFormat; 72 | } 73 | 74 | pub fn getDebugInfoData(base: *const Context) ?[]const u8 { 75 | return switch (base.tag) { 76 | .elf => @fieldParentPtr(Elf, "base", base).getDebugInfoData(), 77 | .macho => @fieldParentPtr(MachO, "base", base).getDebugInfoData(), 78 | .wasm => @fieldParentPtr(Wasm, "base", base).getDebugInfoData(), 79 | }; 80 | } 81 | 82 | pub fn getDebugStringData(base: *const Context) ?[]const u8 { 83 | return switch (base.tag) { 84 | .elf => @fieldParentPtr(Elf, "base", base).getDebugStringData(), 85 | .macho => @fieldParentPtr(MachO, "base", base).getDebugStringData(), 86 | .wasm => @fieldParentPtr(Wasm, "base", base).getDebugStringData(), 87 | }; 88 | } 89 | 90 | pub fn getDebugAbbrevData(base: *const Context) ?[]const u8 { 91 | return switch (base.tag) { 92 | .elf => @fieldParentPtr(Elf, "base", base).getDebugAbbrevData(), 93 | .macho => @fieldParentPtr(MachO, "base", base).getDebugAbbrevData(), 94 | .wasm => @fieldParentPtr(Wasm, "base", base).getDebugAbbrevData(), 95 | }; 96 | } 97 | 98 | pub fn getArch(base: *const Context) ?std.Target.Cpu.Arch { 99 | return switch (base.tag) { 100 | .elf => @fieldParentPtr(Elf, "base", base).getArch(), 101 | .macho => @fieldParentPtr(MachO, "base", base).getArch(), 102 | .wasm => .wasm32, 103 | }; 104 | } 105 | pub fn getDwarfString(base: *const Context, off: u64) []const u8 { 106 | const debug_str = base.getDebugStringData().?; 107 | assert(off < debug_str.len); 108 | return mem.sliceTo(@as([*:0]const u8, @ptrCast(debug_str.ptr + off)), 0); 109 | } 110 | -------------------------------------------------------------------------------- /src/Context/Elf.zig: -------------------------------------------------------------------------------- 1 | const Elf = @This(); 2 | 3 | const std = @import("std"); 4 | 5 | const Allocator = std.mem.Allocator; 6 | const Context = @import("../Context.zig"); 7 | 8 | pub const base_tag: Context.Tag = .elf; 9 | 10 | base: Context, 11 | 12 | header: std.elf.Elf64_Ehdr, 13 | debug_info_sect: ?std.elf.Elf64_Shdr = null, 14 | debug_string_sect: ?std.elf.Elf64_Shdr = null, 15 | debug_abbrev_sect: ?std.elf.Elf64_Shdr = null, 16 | debug_frame: ?std.elf.Elf64_Shdr = null, 17 | eh_frame: ?std.elf.Elf64_Shdr = null, 18 | 19 | pub fn isElfFile(data: []const u8) bool { 20 | // TODO: 32bit ELF files 21 | const header = @as(*const std.elf.Elf64_Ehdr, @ptrCast(@alignCast(data.ptr))).*; 22 | return std.mem.eql(u8, "\x7fELF", header.e_ident[0..4]); 23 | } 24 | 25 | pub fn deinit(elf: *Elf, gpa: Allocator) void { 26 | _ = elf; 27 | _ = gpa; 28 | } 29 | 30 | pub fn parse(gpa: Allocator, data: []const u8) !*Elf { 31 | const elf = try gpa.create(Elf); 32 | errdefer gpa.destroy(elf); 33 | 34 | elf.* = .{ 35 | .base = .{ 36 | .tag = .elf, 37 | .data = data, 38 | }, 39 | .header = undefined, 40 | }; 41 | elf.header = @as(*const std.elf.Elf64_Ehdr, @ptrCast(@alignCast(data.ptr))).*; 42 | 43 | const shdrs = elf.getShdrs(); 44 | for (shdrs) |shdr| switch (shdr.sh_type) { 45 | std.elf.SHT_PROGBITS => { 46 | const sh_name = elf.getShString(@as(u32, @intCast(shdr.sh_name))); 47 | if (std.mem.eql(u8, sh_name, ".debug_info")) { 48 | elf.debug_info_sect = shdr; 49 | } 50 | if (std.mem.eql(u8, sh_name, ".debug_abbrev")) { 51 | elf.debug_abbrev_sect = shdr; 52 | } 53 | if (std.mem.eql(u8, sh_name, ".debug_str")) { 54 | elf.debug_string_sect = shdr; 55 | } 56 | if (std.mem.eql(u8, sh_name, ".debug_frame")) { 57 | elf.debug_frame = shdr; 58 | } 59 | if (std.mem.eql(u8, sh_name, ".eh_frame")) { 60 | elf.eh_frame = shdr; 61 | } 62 | }, 63 | else => {}, 64 | }; 65 | 66 | return elf; 67 | } 68 | 69 | pub fn getDebugInfoData(elf: *const Elf) ?[]const u8 { 70 | const shdr = elf.debug_info_sect orelse return null; 71 | return elf.getShdrData(shdr); 72 | } 73 | 74 | pub fn getDebugStringData(elf: *const Elf) ?[]const u8 { 75 | const shdr = elf.debug_string_sect orelse return null; 76 | return elf.getShdrData(shdr); 77 | } 78 | 79 | pub fn getDebugAbbrevData(elf: *const Elf) ?[]const u8 { 80 | const shdr = elf.debug_abbrev_sect orelse return null; 81 | return elf.getShdrData(shdr); 82 | } 83 | 84 | pub fn getDebugFrameData(elf: *const Elf) ?[]const u8 { 85 | const shdr = elf.debug_frame orelse return null; 86 | return elf.getShdrData(shdr); 87 | } 88 | 89 | pub fn getEhFrameData(elf: *const Elf) ?[]const u8 { 90 | const shdr = elf.eh_frame orelse return null; 91 | return elf.getShdrData(shdr); 92 | } 93 | 94 | pub fn getShdrByName(elf: *const Elf, name: []const u8) ?std.elf.Elf64_Shdr { 95 | const shdrs = elf.getShdrs(); 96 | for (shdrs) |shdr| { 97 | const shdr_name = elf.getShString(shdr.sh_name); 98 | if (std.mem.eql(u8, shdr_name, name)) return shdr; 99 | } 100 | return null; 101 | } 102 | 103 | fn getShdrs(elf: *const Elf) []const std.elf.Elf64_Shdr { 104 | const shdrs = @as( 105 | [*]const std.elf.Elf64_Shdr, 106 | @ptrCast(@alignCast(elf.base.data.ptr + elf.header.e_shoff)), 107 | )[0..elf.header.e_shnum]; 108 | return shdrs; 109 | } 110 | 111 | fn getShdrData(elf: *const Elf, shdr: std.elf.Elf64_Shdr) []const u8 { 112 | return elf.base.data[shdr.sh_offset..][0..shdr.sh_size]; 113 | } 114 | 115 | fn getShString(elf: *const Elf, off: u32) []const u8 { 116 | const shdr = elf.getShdrs()[elf.header.e_shstrndx]; 117 | const shstrtab = elf.getShdrData(shdr); 118 | std.debug.assert(off < shstrtab.len); 119 | return std.mem.sliceTo(@as([*:0]const u8, @ptrCast(shstrtab.ptr + off)), 0); 120 | } 121 | 122 | pub fn getArch(elf: *const Elf) ?std.Target.Cpu.Arch { 123 | return elf.header.e_machine.toTargetCpuArch(); 124 | } 125 | -------------------------------------------------------------------------------- /src/Context/MachO.zig: -------------------------------------------------------------------------------- 1 | const MachO = @This(); 2 | 3 | const std = @import("std"); 4 | 5 | const Allocator = std.mem.Allocator; 6 | const Context = @import("../Context.zig"); 7 | 8 | pub const base_tag: Context.Tag = .macho; 9 | 10 | base: Context, 11 | 12 | header: std.macho.mach_header_64, 13 | debug_info_sect: ?std.macho.section_64 = null, 14 | debug_abbrev_sect: ?std.macho.section_64 = null, 15 | debug_string_sect: ?std.macho.section_64 = null, 16 | 17 | pub fn isMachOFile(data: []const u8) bool { 18 | const header = @as(*const std.macho.mach_header_64, @ptrCast(@alignCast(data.ptr))).*; 19 | return header.magic == std.macho.MH_MAGIC_64; 20 | } 21 | 22 | pub fn deinit(macho: *MachO, gpa: Allocator) void { 23 | _ = macho; 24 | _ = gpa; 25 | } 26 | 27 | pub fn parse(gpa: Allocator, data: []const u8) !*MachO { 28 | const macho = try gpa.create(MachO); 29 | errdefer gpa.destroy(macho); 30 | 31 | macho.* = .{ 32 | .base = .{ 33 | .tag = .macho, 34 | .data = data, 35 | }, 36 | .header = undefined, 37 | }; 38 | 39 | macho.header = @as(*const std.macho.mach_header_64, @ptrCast(@alignCast(data.ptr))).*; 40 | 41 | var it = macho.getLoadCommandsIterator(); 42 | while (it.next()) |lc| switch (lc.cmd()) { 43 | .SEGMENT_64 => { 44 | for (lc.getSections()) |sect| { 45 | if (std.mem.eql(u8, sect.segName(), "__DWARF")) { 46 | if (std.mem.eql(u8, sect.sectName(), "__debug_info")) { 47 | macho.debug_info_sect = sect; 48 | } 49 | if (std.mem.eql(u8, sect.sectName(), "__debug_abbrev")) { 50 | macho.debug_abbrev_sect = sect; 51 | } 52 | if (std.mem.eql(u8, sect.sectName(), "__debug_str")) { 53 | macho.debug_string_sect = sect; 54 | } 55 | } 56 | } 57 | }, 58 | else => {}, 59 | }; 60 | 61 | return macho; 62 | } 63 | 64 | pub fn getDebugInfoData(macho: *const MachO) ?[]const u8 { 65 | const sect = macho.debug_info_sect orelse return null; 66 | return macho.getSectionData(sect); 67 | } 68 | 69 | pub fn getDebugStringData(macho: *const MachO) ?[]const u8 { 70 | const sect = macho.debug_string_sect orelse return null; 71 | return macho.getSectionData(sect); 72 | } 73 | 74 | pub fn getDebugAbbrevData(macho: *const MachO) ?[]const u8 { 75 | const sect = macho.debug_abbrev_sect orelse return null; 76 | return macho.getSectionData(sect); 77 | } 78 | 79 | pub fn getSectionByName(macho: *const MachO, segname: []const u8, sectname: []const u8) ?std.macho.section_64 { 80 | var it = macho.getLoadCommandsIterator(); 81 | while (it.next()) |lc| switch (lc.cmd()) { 82 | .SEGMENT_64 => { 83 | for (lc.getSections()) |sect| { 84 | if (std.mem.eql(u8, sect.segName(), segname) and std.mem.eql(u8, sect.sectName(), sectname)) { 85 | return sect; 86 | } 87 | } 88 | }, 89 | else => {}, 90 | }; 91 | return null; 92 | } 93 | 94 | pub fn getSectionData(macho: *const MachO, sect: std.macho.section_64) []const u8 { 95 | const size = @as(usize, @intCast(sect.size)); 96 | return macho.base.data[sect.offset..][0..size]; 97 | } 98 | 99 | pub fn isX86(macho: *const MachO) bool { 100 | return macho.header.cputype == std.macho.CPU_TYPE_X86_64; 101 | } 102 | 103 | pub fn isARM(macho: *const MachO) bool { 104 | return macho.header.cputype == std.macho.CPU_TYPE_ARM64; 105 | } 106 | 107 | fn getLoadCommandsIterator(macho: *const MachO) std.macho.LoadCommandIterator { 108 | const data = @as([]align(8) const u8, @alignCast(macho.base.data[@sizeOf(std.macho.mach_header_64)..]))[0..macho.header.sizeofcmds]; 109 | return .{ 110 | .ncmds = macho.header.ncmds, 111 | .buffer = data, 112 | }; 113 | } 114 | 115 | pub fn getArch(macho: *const MachO) ?std.Target.Cpu.Arch { 116 | return switch (macho.header.cputype) { 117 | std.macho.CPU_TYPE_ARM64 => .aarch64, 118 | std.macho.CPU_TYPE_X86_64 => .x86_64, 119 | else => null, 120 | }; 121 | } 122 | -------------------------------------------------------------------------------- /src/Context/Wasm.zig: -------------------------------------------------------------------------------- 1 | base: Context, 2 | version: u32, 3 | 4 | debug_info: []const u8 = &.{}, 5 | debug_line: []const u8 = &.{}, 6 | debug_loc: []const u8 = &.{}, 7 | debug_ranges: []const u8 = &.{}, 8 | debug_pubnames: []const u8 = &.{}, 9 | debug_pubtypes: []const u8 = &.{}, 10 | debug_str: []const u8 = &.{}, 11 | debug_abbrev: []const u8 = &.{}, 12 | 13 | pub fn isWasmFile(data: []const u8) bool { 14 | return data.len >= 4 and std.mem.eql(u8, &std.wasm.magic, data[0..4]); 15 | } 16 | 17 | pub fn deinit(wasm_file: *Wasm, gpa: std.mem.Allocator) void { 18 | _ = wasm_file; 19 | _ = gpa; 20 | } 21 | 22 | pub fn parse(gpa: std.mem.Allocator, data: []const u8) !*Wasm { 23 | const wasm = try gpa.create(Wasm); 24 | errdefer gpa.destroy(wasm); 25 | 26 | wasm.* = .{ 27 | .base = .{ 28 | .tag = .wasm, 29 | .data = data, 30 | }, 31 | .version = 0, 32 | }; 33 | 34 | var fbs = std.io.fixedBufferStream(data); 35 | const reader = fbs.reader(); 36 | try reader.skipBytes(4, .{}); 37 | wasm.version = try reader.readInt(u32, .little); 38 | 39 | while (reader.readByte()) |byte| { 40 | const tag = try std.meta.intToEnum(std.wasm.Section, byte); 41 | const len = try std.leb.readULEB128(u32, reader); 42 | switch (tag) { 43 | .custom => { 44 | const name_len = try std.leb.readULEB128(u32, reader); 45 | var buf: [200]u8 = undefined; 46 | try reader.readNoEof(buf[0..name_len]); 47 | const name = buf[0..name_len]; 48 | const remaining_size = 49 | len - getULEB128Size(name_len) - name_len; 50 | if (std.mem.startsWith(u8, name, ".debug")) { 51 | const debug_info = data[reader.context.pos..][0..remaining_size]; 52 | if (std.mem.eql(u8, name, ".debug_info")) { 53 | wasm.debug_info = debug_info; 54 | } else if (std.mem.eql(u8, name, ".debug_line")) { 55 | wasm.debug_line = debug_info; 56 | } else if (std.mem.eql(u8, name, ".debug_loc")) { 57 | wasm.debug_loc = debug_info; 58 | } else if (std.mem.eql(u8, name, ".debug_ranges")) { 59 | wasm.debug_ranges = debug_info; 60 | } else if (std.mem.eql(u8, name, ".debug_pubnames")) { 61 | wasm.debug_pubnames = debug_info; 62 | } else if (std.mem.eql(u8, name, ".debug_pubtypes")) { 63 | wasm.debug_pubtypes = debug_info; 64 | } else if (std.mem.eql(u8, name, ".debug_abbrev")) { 65 | wasm.debug_abbrev = debug_info; 66 | } else if (std.mem.eql(u8, name, ".debug_str")) { 67 | wasm.debug_str = debug_info; 68 | } 69 | } 70 | try reader.skipBytes(remaining_size, .{}); 71 | }, 72 | else => try reader.skipBytes(len, .{}), 73 | } 74 | } else |err| switch (err) { 75 | error.EndOfStream => {}, // finished parsing 76 | else => |e| return e, 77 | } 78 | return wasm; 79 | } 80 | 81 | pub fn getDebugAbbrevData(wasm_file: *const Wasm) ?[]const u8 { 82 | if (wasm_file.debug_abbrev.len == 0) return null; 83 | return wasm_file.debug_abbrev; 84 | } 85 | 86 | pub fn getDebugStringData(wasm_file: *const Wasm) ?[]const u8 { 87 | if (wasm_file.debug_str.len == 0) return null; 88 | return wasm_file.debug_str; 89 | } 90 | 91 | pub fn getDebugInfoData(wasm_file: *const Wasm) ?[]const u8 { 92 | if (wasm_file.debug_info.len == 0) return null; 93 | return wasm_file.debug_info; 94 | } 95 | 96 | /// From a given unsigned integer, returns the size it takes 97 | /// in bytes to store the integer using leb128-encoding. 98 | fn getULEB128Size(uint_value: anytype) u32 { 99 | const T = @TypeOf(uint_value); 100 | const U = if (@typeInfo(T).Int.bits < 8) u8 else T; 101 | var value = @as(U, @intCast(uint_value)); 102 | 103 | var size: u32 = 0; 104 | while (value != 0) : (size += 1) { 105 | value >>= 7; 106 | } 107 | return size; 108 | } 109 | 110 | const std = @import("std"); 111 | const Context = @import("../Context.zig"); 112 | const Wasm = @This(); 113 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const process = std.process; 3 | 4 | const DwarfDump = @import("DwarfDump.zig"); 5 | 6 | const usage = 7 | \\Usage: zig-dwarfdump [options] file 8 | \\ 9 | \\General options: 10 | \\--debug-info Display .debug_info section contents (default) 11 | \\--debug-abbrev Display .debug_abbrev section contents 12 | \\--eh-frame Display .eh_frame section contents 13 | \\--llvm-compatibility Output is formatted exactly like llvm-dwarfdump, with no extra information 14 | \\--all, -a Display all debug info sections 15 | \\--help Display this help and exit 16 | \\ 17 | ; 18 | 19 | fn fatal(comptime format: []const u8, args: anytype) noreturn { 20 | ret: { 21 | const msg = std.fmt.allocPrint(gpa, format ++ "\n", args) catch break :ret; 22 | std.io.getStdErr().writeAll(msg) catch {}; 23 | } 24 | std.process.exit(1); 25 | } 26 | 27 | const ArgsIterator = struct { 28 | args: []const []const u8, 29 | i: usize = 0, 30 | 31 | fn next(it: *@This()) ?[]const u8 { 32 | if (it.i >= it.args.len) { 33 | return null; 34 | } 35 | defer it.i += 1; 36 | return it.args[it.i]; 37 | } 38 | 39 | fn nextOrFatal(it: *@This()) []const u8 { 40 | return it.next() orelse fatal("expected parameter after {s}", .{it.args[it.i - 1]}); 41 | } 42 | }; 43 | 44 | var global_alloc = std.heap.GeneralPurposeAllocator(.{}){}; 45 | const gpa = global_alloc.allocator(); 46 | 47 | pub fn main() !void { 48 | var arena_allocator = std.heap.ArenaAllocator.init(gpa); 49 | defer arena_allocator.deinit(); 50 | const arena = arena_allocator.allocator(); 51 | 52 | const all_args = try std.process.argsAlloc(arena); 53 | const args = all_args[1..]; 54 | 55 | if (args.len == 0) fatal(usage, .{}); 56 | 57 | var filename: ?[]const u8 = null; 58 | var llvm_compat: bool = false; 59 | 60 | const PrintMatrix = packed struct { 61 | debug_info: bool = false, 62 | debug_abbrev: bool = false, 63 | eh_frame: bool = false, 64 | 65 | const Int = blk: { 66 | const bits = @typeInfo(@This()).Struct.fields.len; 67 | break :blk @Type(.{ 68 | .Int = .{ 69 | .signedness = .unsigned, 70 | .bits = bits, 71 | }, 72 | }); 73 | }; 74 | 75 | fn enableAll() @This() { 76 | return @as(@This(), @bitCast(~@as(Int, 0))); 77 | } 78 | 79 | fn isSet(pm: @This()) bool { 80 | return @as(Int, @bitCast(pm)) != 0; 81 | } 82 | 83 | fn add(pm: *@This(), other: @This()) void { 84 | pm.* = @as(@This(), @bitCast(@as(Int, @bitCast(pm.*)) | @as(Int, @bitCast(other)))); 85 | } 86 | }; 87 | var print_matrix: PrintMatrix = .{}; 88 | 89 | var it = ArgsIterator{ .args = args }; 90 | while (it.next()) |arg| { 91 | if (std.mem.startsWith(u8, arg, "-")) blk: { 92 | var i: usize = 1; 93 | var tmp = PrintMatrix{}; 94 | while (i < arg.len) : (i += 1) switch (arg[i]) { 95 | '-' => break :blk, 96 | 'a' => tmp = PrintMatrix.enableAll(), 97 | else => break :blk, 98 | }; 99 | print_matrix.add(tmp); 100 | continue; 101 | } 102 | 103 | if (std.mem.eql(u8, arg, "--help")) { 104 | fatal(usage, .{}); 105 | } else if (std.mem.eql(u8, arg, "--all")) { 106 | print_matrix = PrintMatrix.enableAll(); 107 | } else if (std.mem.eql(u8, arg, "--debug-info")) { 108 | print_matrix.debug_info = true; 109 | } else if (std.mem.eql(u8, arg, "--debug-abbrev")) { 110 | print_matrix.debug_abbrev = true; 111 | } else if (std.mem.eql(u8, arg, "--eh-frame")) { 112 | print_matrix.eh_frame = true; 113 | } else if (std.mem.eql(u8, arg, "--llvm-compatibility")) { 114 | llvm_compat = true; 115 | } else { 116 | if (filename != null) fatal("too many positional arguments specified", .{}); 117 | filename = arg; 118 | } 119 | } 120 | 121 | const fname = filename orelse fatal("no input file specified", .{}); 122 | const file = try std.fs.cwd().openFile(fname, .{}); 123 | defer file.close(); 124 | 125 | var dd = try DwarfDump.parse(gpa, file); 126 | defer dd.deinit(); 127 | 128 | const stdout = std.io.getStdOut().writer(); 129 | 130 | if (print_matrix.debug_info) { 131 | try dd.printCompileUnits(stdout); 132 | try stdout.writeAll("\n"); 133 | } 134 | if (print_matrix.debug_abbrev) { 135 | try dd.printAbbrevTables(stdout); 136 | try stdout.writeAll("\n"); 137 | } 138 | if (print_matrix.eh_frame) { 139 | try stdout.print("{s}:\tfile format {s}{s}\n", .{ 140 | fname, 141 | switch (dd.ctx.tag) { 142 | .elf => "elf64-", 143 | .macho => "Mach-O ", 144 | .wasm => "Wasm", 145 | }, 146 | if (dd.ctx.getArch()) |arch| switch (arch) { 147 | .x86_64 => "x86_64", 148 | .aarch64 => "arm64", 149 | .wasm32 => "", // wasm arch does not require any printing 150 | else => @tagName(arch), 151 | } else "unknown", 152 | }); 153 | 154 | try dd.printEhFrames(stdout, llvm_compat); 155 | try stdout.writeAll("\n"); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zig-dwarfdump 2 | 3 | `dwarfdump` utility but in Zig. 4 | 5 | ### Why? 6 | 7 | Turns out the best way of learning a new file/data format is to write a parser yourself, and so here we are. 8 | Perhaps in the future we will be able to use this project as a full substitute for `llvm-dwarfdump`, but time 9 | will tell. 10 | 11 | ### Usage 12 | 13 | ``` 14 | $ zig-dwarfdump --help 15 | Usage: zig-dwarfdump [options] file 16 | 17 | General options: 18 | --debug-info Display .debug_info section contents (default) 19 | --eh-frame Display .eh_frame section contents 20 | --llvm-compatibility Output is formatted exactly like llvm-dwarfdump, with no extra information 21 | --all, -a Display all debug info sections 22 | --help Display this help and exit 23 | 24 | ``` 25 | 26 | ### Example usage (MachO) 27 | 28 | ``` 29 | $ zig-dwarfdump main.o 30 | 31 | __debug_info contents: 32 | 0x0000000000000000: Compile Unit: length = 0x00000000000000a1, format = DWARF32, version = 0x0004, abbr_offset = 0x0000000000000000, addr_size = 0x08 (next unit at 0x00000000000000a5) 33 | 34 | 0x000000000000000b: DW_TAG_compile_unit 35 | DW_AT_producer (clang version 15.0.0 (git@github.com:ziglang/zig-bootstrap ae458b715c229ee49397c2c156461ababc7ed98c)) 36 | DW_AT_language (c) 37 | DW_AT_name (main.c) 38 | DW_AT_LLVM_sysroot (/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk) 39 | DW_AT_APPLE_sdk (MacOSX12.3.sdk) 40 | DW_AT_stmt_list (0000000000000000) 41 | DW_AT_comp_dir (/Users/kubkon/dev/zld/examples/hello_c) 42 | DW_AT_low_pc (0000000000000000) 43 | DW_AT_high_pc (0000000000000+48) 44 | 45 | 0x0000000000000032: DW_TAG_variable 46 | DW_AT_type (43) 47 | DW_AT_decl_file (1) 48 | DW_AT_decl_line (4) 49 | DW_AT_location ( 3 48 0 0 0 0 0 0 0 ) 50 | 51 | 0x0000000000000043: DW_TAG_array_type 52 | DW_AT_type (4f) 53 | 54 | 0x0000000000000048: DW_TAG_subrange_type 55 | DW_AT_type (56) 56 | DW_AT_count (8) 57 | 58 | 0x000000000000004e: NULL 59 | 60 | 0x000000000000004f: DW_TAG_base_type 61 | DW_AT_name (char) 62 | DW_AT_encoding (6) 63 | DW_AT_byte_size (1) 64 | 65 | 0x0000000000000056: DW_TAG_base_type 66 | DW_AT_name (__ARRAY_SIZE_TYPE__) 67 | DW_AT_byte_size (8) 68 | DW_AT_encoding (7) 69 | 70 | 0x000000000000005d: DW_TAG_subprogram 71 | DW_AT_low_pc (0000000000000000) 72 | DW_AT_high_pc (0000000000000+48) 73 | DW_AT_framebase ( 6d ) 74 | DW_AT_name (main) 75 | DW_AT_decl_file (1) 76 | DW_AT_decl_line (3) 77 | DW_AT_prototyped (true) 78 | DW_AT_type (93) 79 | DW_AT_external (true) 80 | 81 | 0x0000000000000076: DW_TAG_formal_parameter 82 | DW_AT_location ( 91 78 ) 83 | DW_AT_name (argc) 84 | DW_AT_decl_file (1) 85 | DW_AT_decl_line (3) 86 | DW_AT_type (93) 87 | 88 | 0x0000000000000084: DW_TAG_formal_parameter 89 | DW_AT_location ( 8f 10 ) 90 | DW_AT_name (argv) 91 | DW_AT_decl_file (1) 92 | DW_AT_decl_line (3) 93 | DW_AT_type (9a) 94 | 95 | 0x0000000000000092: NULL 96 | 97 | 0x0000000000000093: DW_TAG_base_type 98 | DW_AT_name (int) 99 | DW_AT_encoding (5) 100 | DW_AT_byte_size (4) 101 | 102 | 0x000000000000009a: DW_TAG_pointer_type 103 | DW_AT_type (9f) 104 | 105 | 0x000000000000009f: DW_TAG_pointer_type 106 | DW_AT_type (4f) 107 | 108 | 0x00000000000000a4: NULL 109 | ``` 110 | 111 | ``` 112 | $ zig-dwarfdump --eh-frame main.o 113 | __TEXT,__eh_frame contents: 114 | 115 | CIE: 116 | Length : 16 117 | Id : 0 118 | Version : 1 119 | Augmentation : zR 120 | Code Alignment Factor : 1 121 | Data Alignment Factor : -8 122 | Return Address Register : 30 123 | Augmentation Length : 1 124 | FDE Pointer Encoding : 16 125 | LSDA Pointer Encoding : 0 126 | Augmentation Data : 0x10 127 | Initial Instructions : 0x0c1f00 128 | 129 | FDE: 130 | Length : 32 131 | CIE Pointer : 0x18 132 | PC Begin : 0xffffffffffffffe4 133 | PC Range : 64 134 | Augmentation Length : 0 135 | Instructions : 0x440e10780e000000000000 136 | 137 | FDE: 138 | Length : 44 139 | CIE Pointer : 0x3c 140 | PC Begin : 0xffffffffffffffc0 141 | PC Range : 56 142 | Augmentation Length : 0 143 | Instructions : 0x440e30480c1d109e019d02600c1f30480e00dedd000000 144 | ``` 145 | 146 | ### Building 147 | 148 | Use latest `zig` master: 149 | 150 | ``` 151 | $ git clone https://github.com/kubkon/zig-dwarfdump.git 152 | $ zig build 153 | ``` 154 | -------------------------------------------------------------------------------- /src/AbbrevTable.zig: -------------------------------------------------------------------------------- 1 | decls: std.ArrayListUnmanaged(Decl) = .{}, 2 | loc: Loc, 3 | 4 | pub fn deinit(table: *AbbrevTable, gpa: Allocator) void { 5 | for (table.decls.items) |*decl| { 6 | decl.deinit(gpa); 7 | } 8 | table.decls.deinit(gpa); 9 | } 10 | 11 | pub fn getDecl(table: AbbrevTable, code: u64) ?Decl { 12 | for (table.decls.items) |decl| { 13 | if (decl.code == code) return decl; 14 | } 15 | return null; 16 | } 17 | 18 | pub fn format( 19 | table: AbbrevTable, 20 | comptime unused_fmt_string: []const u8, 21 | options: std.fmt.FormatOptions, 22 | writer: anytype, 23 | ) !void { 24 | _ = unused_fmt_string; 25 | _ = options; 26 | for (table.decls.items) |decl| { 27 | try writer.print("{}\n", .{decl}); 28 | } 29 | } 30 | 31 | pub const Decl = struct { 32 | code: u64, 33 | tag: u64, 34 | children: bool, 35 | attrs: std.ArrayListUnmanaged(Attr) = .{}, 36 | loc: Loc, 37 | 38 | pub fn deinit(decl: *Decl, gpa: Allocator) void { 39 | decl.attrs.deinit(gpa); 40 | } 41 | 42 | pub fn format( 43 | decl: Decl, 44 | comptime unused_fmt_string: []const u8, 45 | options: std.fmt.FormatOptions, 46 | writer: anytype, 47 | ) !void { 48 | _ = unused_fmt_string; 49 | _ = options; 50 | 51 | try writer.print("[{d}] ", .{decl.code}); 52 | 53 | try writer.print("{}", .{fmtTag(decl.tag)}); 54 | try writer.print(" DW_CHILDREN_{s}\n", .{if (decl.children) "yes" else "no"}); 55 | 56 | const nattrs = decl.attrs.items.len; 57 | if (nattrs == 0) return; 58 | 59 | for (decl.attrs.items[0 .. nattrs - 1]) |attr| { 60 | try writer.print("{}\n", .{attr}); 61 | } 62 | try writer.print("{}", .{decl.attrs.items[nattrs - 1]}); 63 | } 64 | }; 65 | 66 | pub fn fmtTag(tag: u64) std.fmt.Formatter(formatTag) { 67 | return .{ .data = tag }; 68 | } 69 | 70 | fn formatTag( 71 | tag: u64, 72 | comptime unused_fmt_string: []const u8, 73 | options: std.fmt.FormatOptions, 74 | writer: anytype, 75 | ) !void { 76 | _ = unused_fmt_string; 77 | _ = options; 78 | const is_tag_known = switch (tag) { 79 | dwarf.TAG.lo_user...dwarf.TAG.hi_user => switch (tag) { 80 | 0x4109, 0x410a => true, 81 | else => false, 82 | }, 83 | else => inline for (@typeInfo(dwarf.TAG).Struct.decls) |x| { 84 | if (@field(dwarf.TAG, x.name) == tag) break true; 85 | } else false, 86 | }; 87 | if (is_tag_known) { 88 | const tag_s = switch (tag) { 89 | dwarf.TAG.lo_user...dwarf.TAG.hi_user => switch (tag) { 90 | 0x4109 => "DW_TAG_GNU_call_site", 91 | 0x410a => "DW_TAG_GNU_call_site_parameter", 92 | else => unreachable, // sync'd with is_tag_known check above 93 | }, 94 | else => inline for (@typeInfo(dwarf.TAG).Struct.decls) |x| { 95 | if (@field(dwarf.TAG, x.name) == tag) { 96 | break "DW_TAG_" ++ x.name; 97 | } 98 | } else unreachable, // sync'd with is_tag_known check above 99 | }; 100 | try writer.print("{s}", .{tag_s}); 101 | } else { 102 | try writer.print("DW_TAG_unknown_{x}", .{tag}); 103 | } 104 | } 105 | 106 | pub const Attr = struct { 107 | at: u64, 108 | form: u64, 109 | loc: Loc, 110 | 111 | pub fn getFlag(attr: Attr, value: []const u8) ?bool { 112 | return switch (attr.form) { 113 | dwarf.FORM.flag => value[0] == 1, 114 | dwarf.FORM.flag_present => true, 115 | else => null, 116 | }; 117 | } 118 | 119 | pub fn getString(attr: Attr, value: []const u8, dwf: DwarfDump.Format, ctx: *const Context) ?[]const u8 { 120 | switch (attr.form) { 121 | dwarf.FORM.string => { 122 | return mem.sliceTo(@as([*:0]const u8, @ptrCast(value.ptr)), 0); 123 | }, 124 | dwarf.FORM.strp => { 125 | const off = switch (dwf) { 126 | .dwarf64 => mem.readInt(u64, value[0..8], .little), 127 | .dwarf32 => mem.readInt(u32, value[0..4], .little), 128 | }; 129 | return ctx.getDwarfString(off); 130 | }, 131 | else => return null, 132 | } 133 | } 134 | 135 | pub fn getSecOffset(attr: Attr, value: []const u8, dwf: DwarfDump.Format) ?u64 { 136 | return switch (attr.form) { 137 | dwarf.FORM.sec_offset => switch (dwf) { 138 | .dwarf32 => mem.readInt(u32, value[0..4], .little), 139 | .dwarf64 => mem.readInt(u64, value[0..8], .little), 140 | }, 141 | else => null, 142 | }; 143 | } 144 | 145 | pub fn getConstant(attr: Attr, value: []const u8) !?i128 { 146 | var stream = std.io.fixedBufferStream(value); 147 | const reader = stream.reader(); 148 | return switch (attr.form) { 149 | dwarf.FORM.data1 => value[0], 150 | dwarf.FORM.data2 => mem.readInt(u16, value[0..2], .little), 151 | dwarf.FORM.data4 => mem.readInt(u32, value[0..4], .little), 152 | dwarf.FORM.data8 => mem.readInt(u64, value[0..8], .little), 153 | dwarf.FORM.udata => try leb.readULEB128(u64, reader), 154 | dwarf.FORM.sdata => try leb.readILEB128(i64, reader), 155 | else => null, 156 | }; 157 | } 158 | 159 | pub fn getReference(attr: Attr, value: []const u8, dwf: DwarfDump.Format) !?u64 { 160 | var stream = std.io.fixedBufferStream(value); 161 | const reader = stream.reader(); 162 | return switch (attr.form) { 163 | dwarf.FORM.ref1 => value[0], 164 | dwarf.FORM.ref2 => mem.readInt(u16, value[0..2], .little), 165 | dwarf.FORM.ref4 => mem.readInt(u32, value[0..4], .little), 166 | dwarf.FORM.ref8 => mem.readInt(u64, value[0..8], .little), 167 | dwarf.FORM.ref_udata => try leb.readULEB128(u64, reader), 168 | dwarf.FORM.ref_addr => switch (dwf) { 169 | .dwarf32 => mem.readInt(u32, value[0..4], .little), 170 | .dwarf64 => mem.readInt(u64, value[0..8], .little), 171 | }, 172 | else => null, 173 | }; 174 | } 175 | 176 | pub fn getAddr(attr: Attr, value: []const u8, cuh: CompileUnit.Header) ?u64 { 177 | return switch (attr.form) { 178 | dwarf.FORM.addr => switch (cuh.address_size) { 179 | 1 => value[0], 180 | 2 => mem.readInt(u16, value[0..2], .little), 181 | 4 => mem.readInt(u32, value[0..4], .little), 182 | 8 => mem.readInt(u64, value[0..8], .little), 183 | else => null, 184 | }, 185 | else => null, 186 | }; 187 | } 188 | 189 | pub fn getExprloc(attr: Attr, value: []const u8) !?[]const u8 { 190 | if (attr.form != dwarf.FORM.exprloc) return null; 191 | var stream = std.io.fixedBufferStream(value); 192 | var creader = std.io.countingReader(stream.reader()); 193 | const reader = creader.reader(); 194 | const expr_len = try leb.readULEB128(u64, reader); 195 | return value[creader.bytes_read..][0..expr_len]; 196 | } 197 | 198 | pub fn format( 199 | attr: Attr, 200 | comptime unused_fmt_string: []const u8, 201 | options: std.fmt.FormatOptions, 202 | writer: anytype, 203 | ) !void { 204 | _ = unused_fmt_string; 205 | _ = options; 206 | try writer.writeAll(" "); 207 | try writer.print("{}", .{fmtAt(attr.at)}); 208 | try writer.writeAll(" "); 209 | inline for (@typeInfo(dwarf.FORM).Struct.decls) |x| { 210 | if (@field(dwarf.FORM, x.name) == attr.form) { 211 | try writer.print("DW_FORM_{s}", .{x.name}); 212 | break; 213 | } 214 | } else try writer.print("DW_FORM_unknown_{x}", .{attr.form}); 215 | } 216 | }; 217 | 218 | pub fn fmtAt(at: u64) std.fmt.Formatter(formatAt) { 219 | return .{ .data = at }; 220 | } 221 | 222 | fn formatAt( 223 | at: u64, 224 | comptime unused_fmt_string: []const u8, 225 | options: std.fmt.FormatOptions, 226 | writer: anytype, 227 | ) !void { 228 | _ = unused_fmt_string; 229 | _ = options; 230 | const is_at_known = switch (at) { 231 | dwarf.AT.lo_user...dwarf.AT.hi_user => switch (at) { 232 | 0x2111, 0x2113, 0x2115, 0x2117, 0x3e02, 0x3fef => true, 233 | else => false, 234 | }, 235 | else => inline for (@typeInfo(dwarf.AT).Struct.decls) |x| { 236 | if (@field(dwarf.AT, x.name) == at) break true; 237 | } else false, 238 | }; 239 | if (is_at_known) { 240 | const name = switch (at) { 241 | dwarf.AT.lo_user...dwarf.AT.hi_user => switch (at) { 242 | 0x2111 => "DW_AT_GNU_call_site_value", 243 | 0x2113 => "DW_AT_GNU_call_site_target", 244 | 0x2115 => "DW_AT_GNU_tail_cail", 245 | 0x2117 => "DW_AT_GNU_all_call_sites", 246 | 0x3e02 => "DW_AT_LLVM_sysroot", 247 | 0x3fef => "DW_AT_APPLE_sdk", 248 | else => unreachable, 249 | }, 250 | else => inline for (@typeInfo(dwarf.AT).Struct.decls) |x| { 251 | if (@field(dwarf.AT, x.name) == at) { 252 | break "DW_AT_" ++ x.name; 253 | } 254 | } else unreachable, 255 | }; 256 | try writer.print("{s}", .{name}); 257 | } else { 258 | try writer.print("DW_AT_unknown_{x}", .{at}); 259 | } 260 | } 261 | 262 | const AbbrevTable = @This(); 263 | 264 | const assert = std.debug.assert; 265 | const dwarf = std.dwarf; 266 | const leb = std.leb; 267 | const mem = std.mem; 268 | const std = @import("std"); 269 | 270 | const Allocator = mem.Allocator; 271 | const Context = @import("Context.zig"); 272 | const CompileUnit = @import("CompileUnit.zig"); 273 | const DwarfDump = @import("DwarfDump.zig"); 274 | const Loc = DwarfDump.Loc; 275 | -------------------------------------------------------------------------------- /src/CompileUnit.zig: -------------------------------------------------------------------------------- 1 | header: Header, 2 | loc: Loc, 3 | dies: std.ArrayListUnmanaged(DebugInfoEntry) = .{}, 4 | children: std.ArrayListUnmanaged(usize) = .{}, 5 | 6 | pub fn deinit(cu: *CompileUnit, gpa: Allocator) void { 7 | for (cu.dies.items) |*die| { 8 | die.deinit(gpa); 9 | } 10 | cu.dies.deinit(gpa); 11 | cu.children.deinit(gpa); 12 | } 13 | 14 | pub fn addDie(cu: *CompileUnit, gpa: Allocator) !usize { 15 | const index = cu.dies.items.len; 16 | _ = try cu.dies.addOne(gpa); 17 | return index; 18 | } 19 | 20 | pub fn diePtr(cu: *CompileUnit, index: usize) *DebugInfoEntry { 21 | return &cu.dies.items[index]; 22 | } 23 | 24 | pub fn nextCompileUnitOffset(cu: CompileUnit) u64 { 25 | return cu.loc.pos + switch (cu.header.dw_format) { 26 | .dwarf32 => @as(u64, 4), 27 | .dwarf64 => 12, 28 | } + cu.header.length; 29 | } 30 | 31 | pub fn format( 32 | cu: CompileUnit, 33 | comptime unused_fmt_string: []const u8, 34 | options: std.fmt.FormatOptions, 35 | writer: anytype, 36 | ) !void { 37 | _ = cu; 38 | _ = unused_fmt_string; 39 | _ = options; 40 | _ = writer; 41 | @compileError("do not format CompileUnit directly; use fmtCompileUnit"); 42 | } 43 | 44 | pub fn fmtCompileUnit( 45 | cu: *CompileUnit, 46 | table: AbbrevTable, 47 | ctx: *const Context, 48 | ) std.fmt.Formatter(formatCompileUnit) { 49 | return .{ .data = .{ 50 | .cu = cu, 51 | .table = table, 52 | .ctx = ctx, 53 | } }; 54 | } 55 | 56 | const FormatCompileUnitCtx = struct { 57 | cu: *CompileUnit, 58 | table: AbbrevTable, 59 | ctx: *const Context, 60 | }; 61 | 62 | pub fn formatCompileUnit( 63 | ctx: FormatCompileUnitCtx, 64 | comptime unused_fmt_string: []const u8, 65 | options: std.fmt.FormatOptions, 66 | writer: anytype, 67 | ) !void { 68 | _ = unused_fmt_string; 69 | _ = options; 70 | const cu = ctx.cu; 71 | try writer.print("{}: Compile Unit: {} (next unit at {})\n\n", .{ 72 | cu.header.dw_format.fmtOffset(cu.loc.pos), 73 | cu.header, 74 | cu.header.dw_format.fmtOffset(cu.nextCompileUnitOffset()), 75 | }); 76 | for (cu.children.items) |die_index| { 77 | const die = cu.diePtr(die_index); 78 | try writer.print("{}\n", .{die.fmtDie(ctx.table, cu, ctx.ctx, null, 0)}); 79 | } 80 | } 81 | 82 | pub const Header = struct { 83 | dw_format: DwarfDump.Format, 84 | length: u64, 85 | version: u16, 86 | debug_abbrev_offset: u64, 87 | address_size: u8, 88 | 89 | pub fn format( 90 | header: Header, 91 | comptime unused_fmt_string: []const u8, 92 | options: std.fmt.FormatOptions, 93 | writer: anytype, 94 | ) !void { 95 | _ = unused_fmt_string; 96 | _ = options; 97 | try writer.print( 98 | "length = {}, " ++ 99 | "format = {s}, " ++ 100 | "version = 0x{x:0>4}, " ++ 101 | "abbr_offset = {}, " ++ 102 | "address_size = 0x{x:0>2}", 103 | .{ 104 | header.dw_format.fmtOffset(header.length), 105 | @tagName(header.dw_format), 106 | header.version, 107 | header.dw_format.fmtOffset(header.debug_abbrev_offset), 108 | header.address_size, 109 | }, 110 | ); 111 | } 112 | }; 113 | 114 | pub const DebugInfoEntry = struct { 115 | code: u64, 116 | loc: Loc, 117 | values: std.ArrayListUnmanaged([]const u8) = .{}, 118 | children: std.ArrayListUnmanaged(usize) = .{}, 119 | 120 | pub fn deinit(die: *DebugInfoEntry, gpa: Allocator) void { 121 | die.values.deinit(gpa); 122 | die.children.deinit(gpa); 123 | } 124 | 125 | pub fn format( 126 | die: DebugInfoEntry, 127 | comptime unused_fmt_string: []const u8, 128 | options: std.fmt.FormatOptions, 129 | writer: anytype, 130 | ) !void { 131 | _ = die; 132 | _ = unused_fmt_string; 133 | _ = options; 134 | _ = writer; 135 | @compileError("do not format DebugInfoEntry directly; use fmtDie instead"); 136 | } 137 | 138 | pub fn fmtDie( 139 | die: DebugInfoEntry, 140 | table: AbbrevTable, 141 | cu: *CompileUnit, 142 | ctx: *const Context, 143 | low_pc: ?u64, 144 | indent: usize, 145 | ) std.fmt.Formatter(formatDie) { 146 | return .{ .data = .{ 147 | .die = die, 148 | .table = table, 149 | .cu = cu, 150 | .ctx = ctx, 151 | .low_pc = low_pc, 152 | .indent = indent, 153 | } }; 154 | } 155 | 156 | const FormatDieCtx = struct { 157 | die: DebugInfoEntry, 158 | table: AbbrevTable, 159 | cu: *CompileUnit, 160 | ctx: *const Context, 161 | low_pc: ?u64 = null, 162 | indent: usize = 0, 163 | }; 164 | 165 | fn formatDie( 166 | ctx: FormatDieCtx, 167 | comptime unused_fmt_string: []const u8, 168 | options: std.fmt.FormatOptions, 169 | writer: anytype, 170 | ) !void { 171 | _ = unused_fmt_string; 172 | _ = options; 173 | 174 | try writer.print("{}: ", .{ctx.cu.header.dw_format.fmtOffset(ctx.die.loc.pos)}); 175 | const align_base: usize = 4 + switch (ctx.cu.header.dw_format) { 176 | .dwarf32 => @as(usize, 8), 177 | .dwarf64 => 16, 178 | }; 179 | try fmtIndent(ctx.indent, writer); 180 | 181 | if (ctx.die.code == 0) { 182 | try writer.writeAll("NULL\n\n"); 183 | return; 184 | } 185 | 186 | const decl = ctx.table.getDecl(ctx.die.code).?; 187 | try writer.print("{}\n", .{AbbrevTable.fmtTag(decl.tag)}); 188 | 189 | var low_pc: ?u64 = ctx.low_pc; 190 | for (decl.attrs.items, ctx.die.values.items) |attr, value| { 191 | try fmtIndent(ctx.indent + align_base + 2, writer); 192 | try writer.print("{} (", .{AbbrevTable.fmtAt(attr.at)}); 193 | 194 | formatAtFormInner(attr, value, ctx.cu, &low_pc, ctx.ctx, writer) catch |err| switch (err) { 195 | error.UnhandledForm => try writer.print("error: unhandled FORM {x} for attribute", .{attr.form}), 196 | error.UnexpectedForm => try writer.print("error: unexpected FORM {x}", .{attr.form}), 197 | error.MalformedDwarf => try writer.print("error: malformed DWARF while parsing FORM {x}", .{attr.form}), 198 | error.Overflow, error.EndOfStream => unreachable, 199 | else => |e| return e, 200 | }; 201 | 202 | try writer.writeAll(")\n"); 203 | } 204 | try writer.writeByte('\n'); 205 | 206 | for (ctx.die.children.items) |child_index| { 207 | const child = ctx.cu.diePtr(child_index); 208 | try writer.print("{}", .{child.fmtDie(ctx.table, ctx.cu, ctx.ctx, low_pc, ctx.indent + 2)}); 209 | } 210 | } 211 | 212 | fn formatAtFormInner( 213 | attr: Attr, 214 | value: []const u8, 215 | cu: *CompileUnit, 216 | low_pc: *?u64, 217 | ctx: *const Context, 218 | writer: anytype, 219 | ) !void { 220 | switch (attr.at) { 221 | dwarf.AT.stmt_list, 222 | dwarf.AT.ranges, 223 | => { 224 | const sec_offset = attr.getSecOffset(value, cu.header.dw_format) orelse 225 | return error.MalformedDwarf; 226 | try writer.print("{x:0>16}", .{sec_offset}); 227 | }, 228 | 229 | dwarf.AT.low_pc => { 230 | const addr = attr.getAddr(value, cu.header) orelse 231 | return error.MalformedDwarf; 232 | low_pc.* = addr; 233 | try writer.print("{x:0>16}", .{addr}); 234 | }, 235 | 236 | dwarf.AT.high_pc => { 237 | if (try attr.getConstant(value)) |offset| { 238 | try writer.print("{x:0>16}", .{offset + low_pc.*.?}); 239 | } else if (attr.getAddr(value, cu.header)) |addr| { 240 | try writer.print("{x:0>16}", .{addr}); 241 | } else return error.MalformedDwarf; 242 | }, 243 | 244 | dwarf.AT.type, 245 | dwarf.AT.abstract_origin, 246 | => { 247 | const off = (try attr.getReference(value, cu.header.dw_format)) orelse 248 | return error.MalformedDwarf; 249 | try writer.print("{x}", .{off}); 250 | }, 251 | 252 | dwarf.AT.comp_dir, 253 | dwarf.AT.producer, 254 | dwarf.AT.name, 255 | dwarf.AT.linkage_name, 256 | => { 257 | const str = attr.getString(value, cu.header.dw_format, ctx) orelse 258 | return error.MalformedDwarf; 259 | try writer.print("\"{s}\"", .{str}); 260 | }, 261 | 262 | dwarf.AT.language, 263 | dwarf.AT.calling_convention, 264 | dwarf.AT.encoding, 265 | dwarf.AT.decl_column, 266 | dwarf.AT.decl_file, 267 | dwarf.AT.decl_line, 268 | dwarf.AT.alignment, 269 | dwarf.AT.data_bit_offset, 270 | dwarf.AT.call_file, 271 | dwarf.AT.call_line, 272 | dwarf.AT.call_column, 273 | dwarf.AT.@"inline", 274 | => { 275 | const x = (try attr.getConstant(value)) orelse return error.MalformedDwarf; 276 | try writer.print("{x:0>16}", .{x}); 277 | }, 278 | 279 | dwarf.AT.location, 280 | dwarf.AT.frame_base, 281 | => { 282 | if (try attr.getExprloc(value)) |list| { 283 | try writer.print("<0x{x}> {x}", .{ list.len, std.fmt.fmtSliceHexLower(list) }); 284 | } else { 285 | try writer.print("error: TODO check and parse loclist", .{}); 286 | } 287 | }, 288 | 289 | dwarf.AT.data_member_location => { 290 | if (try attr.getConstant(value)) |x| { 291 | try writer.print("{x:0>16}", .{x}); 292 | } else if (try attr.getExprloc(value)) |list| { 293 | try writer.print("<0x{x}> {x}", .{ list.len, std.fmt.fmtSliceHexLower(list) }); 294 | } else { 295 | try writer.print("error: TODO check and parse loclist", .{}); 296 | } 297 | }, 298 | 299 | dwarf.AT.const_value => { 300 | if (try attr.getConstant(value)) |x| { 301 | try writer.print("{x:0>16}", .{x}); 302 | } else if (attr.getString(value, cu.header.dw_format, ctx)) |str| { 303 | try writer.print("\"{s}\"", .{str}); 304 | } else { 305 | try writer.print("error: TODO check and parse block", .{}); 306 | } 307 | }, 308 | 309 | dwarf.AT.count => { 310 | if (try attr.getConstant(value)) |x| { 311 | try writer.print("{x:0>16}", .{x}); 312 | } else if (try attr.getExprloc(value)) |list| { 313 | try writer.print("<0x{x}> {x}", .{ list.len, std.fmt.fmtSliceHexLower(list) }); 314 | } else if (try attr.getReference(value, cu.header.dw_format)) |off| { 315 | try writer.print("{x:0>16}", .{off}); 316 | } else return error.MalformedDwarf; 317 | }, 318 | 319 | dwarf.AT.byte_size, 320 | dwarf.AT.bit_size, 321 | => { 322 | if (try attr.getConstant(value)) |x| { 323 | try writer.print("{x}", .{x}); 324 | } else if (try attr.getReference(value, cu.header.dw_format)) |off| { 325 | try writer.print("{x}", .{off}); 326 | } else if (try attr.getExprloc(value)) |list| { 327 | try writer.print("<0x{x}> {x}", .{ list.len, std.fmt.fmtSliceHexLower(list) }); 328 | } else return error.MalformedDwarf; 329 | }, 330 | 331 | dwarf.AT.noreturn, 332 | dwarf.AT.external, 333 | dwarf.AT.variable_parameter, 334 | dwarf.AT.trampoline, 335 | => { 336 | const flag = attr.getFlag(value) orelse return error.MalformedDwarf; 337 | try writer.print("{}", .{flag}); 338 | }, 339 | 340 | else => { 341 | if (dwarf.AT.lo_user <= attr.at and attr.at <= dwarf.AT.hi_user) { 342 | if (try attr.getConstant(value)) |x| { 343 | try writer.print("{x}", .{x}); 344 | } else if (attr.getString(value, cu.header.dw_format, ctx)) |string| { 345 | try writer.print("\"{s}\"", .{string}); 346 | } else return error.UnhandledForm; 347 | } else return error.UnexpectedForm; 348 | }, 349 | } 350 | } 351 | }; 352 | 353 | fn fmtIndent(indent: usize, writer: anytype) !void { 354 | for (0..indent) |_| try writer.writeByte(' '); 355 | } 356 | 357 | const dwarf = std.dwarf; 358 | const std = @import("std"); 359 | const AbbrevTable = @import("AbbrevTable.zig"); 360 | const Attr = AbbrevTable.Attr; 361 | const Allocator = std.mem.Allocator; 362 | const CompileUnit = @This(); 363 | const Context = @import("Context.zig"); 364 | const DwarfDump = @import("DwarfDump.zig"); 365 | const Loc = DwarfDump.Loc; 366 | -------------------------------------------------------------------------------- /src/DwarfDump.zig: -------------------------------------------------------------------------------- 1 | gpa: Allocator, 2 | ctx: *Context, 3 | abbrev_tables: std.ArrayListUnmanaged(AbbrevTable) = .{}, 4 | compile_units: std.ArrayListUnmanaged(CompileUnit) = .{}, 5 | 6 | pub fn deinit(self: *DwarfDump) void { 7 | self.ctx.destroy(self.gpa); 8 | for (self.abbrev_tables.items) |*table| { 9 | table.deinit(self.gpa); 10 | } 11 | self.abbrev_tables.deinit(self.gpa); 12 | for (self.compile_units.items) |*cu| { 13 | cu.deinit(self.gpa); 14 | } 15 | self.compile_units.deinit(self.gpa); 16 | } 17 | 18 | pub fn parse(gpa: Allocator, file: fs.File) !DwarfDump { 19 | const file_size = try file.getEndPos(); 20 | const data = try file.readToEndAlloc(gpa, @intCast(file_size)); 21 | errdefer gpa.free(data); 22 | 23 | var self = DwarfDump{ 24 | .gpa = gpa, 25 | .ctx = undefined, 26 | }; 27 | 28 | self.ctx = try Context.parse(gpa, data); 29 | 30 | try self.parseAbbrevTables(); 31 | try self.parseCompileUnits(); 32 | 33 | return self; 34 | } 35 | 36 | fn parseAbbrevTables(self: *DwarfDump) !void { 37 | const debug_abbrev = self.ctx.getDebugAbbrevData() orelse return; 38 | var stream = std.io.fixedBufferStream(debug_abbrev); 39 | var creader = std.io.countingReader(stream.reader()); 40 | const reader = creader.reader(); 41 | 42 | while (true) { 43 | if (creader.bytes_read >= debug_abbrev.len) break; 44 | 45 | const table = try self.abbrev_tables.addOne(self.gpa); 46 | table.* = .{ .loc = .{ .pos = creader.bytes_read, .len = 0 } }; 47 | 48 | while (true) { 49 | const code = try leb.readULEB128(u64, reader); 50 | if (code == 0) break; 51 | 52 | const decl = try table.decls.addOne(self.gpa); 53 | decl.* = .{ 54 | .code = code, 55 | .tag = undefined, 56 | .children = false, 57 | .loc = .{ .pos = creader.bytes_read, .len = 1 }, 58 | }; 59 | decl.tag = try leb.readULEB128(u64, reader); 60 | decl.children = (try reader.readByte()) > 0; 61 | 62 | while (true) { 63 | const at = try leb.readULEB128(u64, reader); 64 | const form = try leb.readULEB128(u64, reader); 65 | if (at == 0 and form == 0) break; 66 | 67 | const attr = try decl.attrs.addOne(self.gpa); 68 | attr.* = .{ 69 | .at = at, 70 | .form = form, 71 | .loc = .{ .pos = creader.bytes_read, .len = 0 }, 72 | }; 73 | attr.loc.len = creader.bytes_read - attr.loc.pos; 74 | } 75 | 76 | decl.loc.len = creader.bytes_read - decl.loc.pos; 77 | } 78 | 79 | table.loc.len = creader.bytes_read - table.loc.pos; 80 | } 81 | } 82 | 83 | fn parseCompileUnits(self: *DwarfDump) !void { 84 | const debug_info = self.ctx.getDebugInfoData() orelse return; 85 | var stream = std.io.fixedBufferStream(debug_info); 86 | var creader = std.io.countingReader(stream.reader()); 87 | const reader = creader.reader(); 88 | 89 | while (true) { 90 | if (creader.bytes_read == debug_info.len) break; 91 | 92 | const cu = try self.compile_units.addOne(self.gpa); 93 | cu.* = .{ 94 | .header = undefined, 95 | .loc = .{ .pos = creader.bytes_read, .len = 0 }, 96 | }; 97 | 98 | var length: u64 = try reader.readInt(u32, .little); 99 | const is_64bit = length == 0xffffffff; 100 | if (is_64bit) { 101 | length = try reader.readInt(u64, .little); 102 | } 103 | cu.header.dw_format = if (is_64bit) .dwarf64 else .dwarf32; 104 | cu.header.length = length; 105 | cu.header.version = try reader.readInt(u16, .little); 106 | cu.header.debug_abbrev_offset = try readOffset(cu.header.dw_format, reader); 107 | cu.header.address_size = try reader.readInt(u8, .little); 108 | 109 | const table = self.getAbbrevTable(cu.header.debug_abbrev_offset).?; 110 | try self.parseDebugInfoEntry(cu, table, null, &creader); 111 | 112 | cu.loc.len = creader.bytes_read - cu.loc.pos; 113 | } 114 | } 115 | 116 | fn parseDebugInfoEntry( 117 | self: *DwarfDump, 118 | cu: *CompileUnit, 119 | table: AbbrevTable, 120 | parent: ?usize, 121 | creader: anytype, 122 | ) anyerror!void { 123 | while (creader.bytes_read < cu.nextCompileUnitOffset()) { 124 | const die = try cu.addDie(self.gpa); 125 | cu.diePtr(die).* = .{ 126 | .code = undefined, 127 | .loc = .{ .pos = creader.bytes_read, .len = 0 }, 128 | }; 129 | if (parent) |p| { 130 | try cu.diePtr(p).children.append(self.gpa, die); 131 | } else { 132 | try cu.children.append(self.gpa, die); 133 | } 134 | 135 | const code = try leb.readULEB128(u64, creader.reader()); 136 | cu.diePtr(die).code = code; 137 | 138 | if (code == 0) { 139 | if (parent == null) continue; 140 | return; // Close scope 141 | } 142 | 143 | const decl = table.getDecl(code) orelse @panic("no suitable abbreviation decl found"); 144 | const data = self.ctx.getDebugInfoData().?; 145 | try cu.diePtr(die).values.ensureTotalCapacityPrecise(self.gpa, decl.attrs.items.len); 146 | 147 | for (decl.attrs.items) |attr| { 148 | const start = creader.bytes_read; 149 | try advanceByFormSize(cu, attr.form, creader); 150 | const end = creader.bytes_read; 151 | cu.diePtr(die).values.appendAssumeCapacity(data[start..end]); 152 | } 153 | 154 | if (decl.children) { 155 | // Open scope 156 | try self.parseDebugInfoEntry(cu, table, die, creader); 157 | } 158 | 159 | cu.diePtr(die).loc.len = creader.bytes_read - cu.diePtr(die).loc.pos; 160 | } 161 | } 162 | 163 | fn advanceByFormSize(cu: *CompileUnit, form: u64, creader: anytype) !void { 164 | const reader = creader.reader(); 165 | switch (form) { 166 | dwarf.FORM.strp, 167 | dwarf.FORM.sec_offset, 168 | dwarf.FORM.ref_addr, 169 | => { 170 | _ = try readOffset(cu.header.dw_format, reader); 171 | }, 172 | 173 | dwarf.FORM.addr => try reader.skipBytes(cu.header.address_size, .{}), 174 | 175 | dwarf.FORM.block1, 176 | dwarf.FORM.block2, 177 | dwarf.FORM.block4, 178 | dwarf.FORM.block, 179 | => { 180 | const len: u64 = switch (form) { 181 | dwarf.FORM.block1 => try reader.readInt(u8, .little), 182 | dwarf.FORM.block2 => try reader.readInt(u16, .little), 183 | dwarf.FORM.block4 => try reader.readInt(u32, .little), 184 | dwarf.FORM.block => try leb.readULEB128(u64, reader), 185 | else => unreachable, 186 | }; 187 | for (0..len) |_| { 188 | _ = try reader.readByte(); 189 | } 190 | }, 191 | 192 | dwarf.FORM.exprloc => { 193 | const len = try leb.readULEB128(u64, reader); 194 | for (0..len) |_| { 195 | _ = try reader.readByte(); 196 | } 197 | }, 198 | dwarf.FORM.flag_present => {}, 199 | 200 | dwarf.FORM.data1, 201 | dwarf.FORM.ref1, 202 | dwarf.FORM.flag, 203 | => try reader.skipBytes(1, .{}), 204 | 205 | dwarf.FORM.data2, 206 | dwarf.FORM.ref2, 207 | => try reader.skipBytes(2, .{}), 208 | 209 | dwarf.FORM.data4, 210 | dwarf.FORM.ref4, 211 | => try reader.skipBytes(4, .{}), 212 | 213 | dwarf.FORM.data8, 214 | dwarf.FORM.ref8, 215 | dwarf.FORM.ref_sig8, 216 | => try reader.skipBytes(8, .{}), 217 | 218 | dwarf.FORM.udata, 219 | dwarf.FORM.ref_udata, 220 | => { 221 | _ = try leb.readULEB128(u64, reader); 222 | }, 223 | 224 | dwarf.FORM.sdata => { 225 | _ = try leb.readILEB128(i64, reader); 226 | }, 227 | 228 | dwarf.FORM.string => { 229 | while (true) { 230 | const byte = try reader.readByte(); 231 | if (byte == 0x0) break; 232 | } 233 | }, 234 | 235 | else => { 236 | log.err("unhandled DW_FORM_* value with identifier {x}", .{form}); 237 | return error.UnhandledDwFormValue; 238 | }, 239 | } 240 | } 241 | 242 | pub fn printAbbrevTables(self: DwarfDump, writer: anytype) !void { 243 | try writer.writeAll(".debug_abbrev contents:\n"); 244 | for (self.abbrev_tables.items) |table| { 245 | try writer.print("{}\n", .{table}); 246 | } 247 | } 248 | 249 | pub fn printCompileUnits(self: DwarfDump, writer: anytype) !void { 250 | try writer.writeAll(".debug_info contents:\n"); 251 | for (self.compile_units.items) |*cu| { 252 | const table = self.getAbbrevTable(cu.header.debug_abbrev_offset).?; 253 | try writer.print("{}\n", .{cu.fmtCompileUnit(table, self.ctx)}); 254 | } 255 | } 256 | 257 | fn getAbbrevTable(self: DwarfDump, off: u64) ?AbbrevTable { 258 | for (self.abbrev_tables.items) |table| { 259 | if (table.loc.pos == off) return table; 260 | } 261 | return null; 262 | } 263 | 264 | fn readOffset(format: Format, reader: anytype) !u64 { 265 | return switch (format) { 266 | .dwarf32 => try reader.readInt(u32, .little), 267 | .dwarf64 => try reader.readInt(u64, .little), 268 | }; 269 | } 270 | 271 | const CieWithHeader = struct { 272 | cie: dwarf.CommonInformationEntry, 273 | header: dwarf.EntryHeader, 274 | 275 | vm: VirtualMachine = .{}, 276 | 277 | // Instead of re-running the CIE instructions to print each FDE, the vm state 278 | // is restored to the post-CIE state instead. 279 | vm_snapshot_columns: usize = undefined, 280 | vm_snapshot_row: VirtualMachine.Row = undefined, 281 | 282 | pub fn deinit(self: *CieWithHeader, allocator: mem.Allocator) void { 283 | self.vm.deinit(allocator); 284 | } 285 | }; 286 | 287 | const WriteOptions = struct { 288 | llvm_compatibility: bool, 289 | frame_type: dwarf.DwarfSection, 290 | reg_ctx: abi.RegisterContext, 291 | addr_size: u8, 292 | endian: std.builtin.Endian, 293 | }; 294 | 295 | const Section = struct { 296 | data: []const u8, 297 | offset: u64, 298 | frame_type: dwarf.DwarfSection, 299 | }; 300 | 301 | pub fn printEhFrames(self: DwarfDump, writer: anytype, llvm_compatibility: bool) !void { 302 | switch (self.ctx.tag) { 303 | .elf => { 304 | const elf = self.ctx.cast(Context.Elf).?; 305 | const sections = [_]struct { 306 | name: []const u8, 307 | section: ?std.elf.Elf64_Shdr, 308 | data: ?[]const u8, 309 | frame_type: dwarf.DwarfSection, 310 | }{ 311 | .{ 312 | .name = ".debug_frame", 313 | .section = elf.debug_frame, 314 | .data = elf.getDebugFrameData(), 315 | .frame_type = .debug_frame, 316 | }, 317 | .{ 318 | .name = ".eh_frame", 319 | .section = elf.eh_frame, 320 | .data = elf.getEhFrameData(), 321 | .frame_type = .eh_frame, 322 | }, 323 | }; 324 | 325 | for (sections, 0..) |section, i| { 326 | if (i > 0) try writer.writeByte('\n'); 327 | try writer.print("{s} contents:\n\n", .{section.name}); 328 | if (section.section) |s| { 329 | if (s.sh_type != std.elf.SHT_NULL and s.sh_type != std.elf.SHT_NOBITS) { 330 | try self.printEhFrame( 331 | writer, 332 | llvm_compatibility, 333 | .{ 334 | .data = section.data.?, 335 | .offset = s.sh_addr, 336 | .frame_type = section.frame_type, 337 | }, 338 | false, 339 | ); 340 | } 341 | } 342 | } 343 | }, 344 | .macho => { 345 | const macho = self.ctx.cast(Context.MachO).?; 346 | const sections = [_]struct { 347 | name: []const u8, 348 | frame_type: dwarf.DwarfSection, 349 | }{ 350 | .{ 351 | .name = "__debug_frame", 352 | .frame_type = .debug_frame, 353 | }, 354 | .{ 355 | .name = "__eh_frame", 356 | .frame_type = .eh_frame, 357 | }, 358 | }; 359 | 360 | for (sections) |section| { 361 | try writer.print("\n.{s} contents:\n\n", .{@tagName(section.frame_type)}); 362 | if (macho.getSectionByName("__TEXT", section.name)) |s| { 363 | try self.printEhFrame( 364 | writer, 365 | llvm_compatibility, 366 | .{ 367 | .data = macho.getSectionData(s), 368 | .offset = s.addr, 369 | .frame_type = section.frame_type, 370 | }, 371 | true, 372 | ); 373 | } 374 | } 375 | }, 376 | .wasm => {}, // WebAssembly does not have the eh_frame section 377 | } 378 | } 379 | 380 | pub fn printEhFrame(self: DwarfDump, writer: anytype, llvm_compatibility: bool, section: Section, is_macho: bool) !void { 381 | const write_options = WriteOptions{ 382 | .llvm_compatibility = llvm_compatibility, 383 | .frame_type = section.frame_type, 384 | 385 | .reg_ctx = .{ 386 | .eh_frame = section.frame_type == .eh_frame, 387 | .is_macho = is_macho, 388 | }, 389 | 390 | // TODO: Use the addr size / endianness of the file, provide in section 391 | .addr_size = @sizeOf(usize), 392 | .endian = .little, 393 | }; 394 | 395 | var cies = std.AutoArrayHashMap(u64, CieWithHeader).init(self.gpa); 396 | defer { 397 | for (cies.keys()) |cie_offset| cies.getPtr(cie_offset).?.deinit(self.gpa); 398 | cies.deinit(); 399 | } 400 | 401 | var stream: std.dwarf.FixedBufferReader = .{ .buf = section.data, .endian = write_options.endian }; 402 | while (stream.pos < stream.buf.len) { 403 | const entry_header = try dwarf.EntryHeader.read(&stream, section.frame_type); 404 | switch (entry_header.type) { 405 | .cie => { 406 | const cie = try dwarf.CommonInformationEntry.parse( 407 | entry_header.entry_bytes, 408 | @as(i64, @intCast(section.offset)) - @as(i64, @intCast(@intFromPtr(section.data.ptr))), 409 | false, 410 | entry_header.format, 411 | section.frame_type, 412 | entry_header.length_offset, 413 | write_options.addr_size, 414 | write_options.endian, 415 | ); 416 | 417 | const entry = try cies.getOrPut(entry_header.length_offset); 418 | assert(!entry.found_existing); 419 | entry.value_ptr.* = .{ .cie = cie, .header = entry_header }; 420 | 421 | try self.writeCie(writer, write_options, entry.value_ptr); 422 | }, 423 | .fde => |cie_offset| { 424 | const cie_with_header = cies.getPtr(cie_offset) orelse return error.InvalidFDE; 425 | const fde = try dwarf.FrameDescriptionEntry.parse( 426 | entry_header.entry_bytes, 427 | @as(i64, @intCast(section.offset)) - @as(i64, @intCast(@intFromPtr(section.data.ptr))), 428 | false, 429 | cie_with_header.cie, 430 | write_options.addr_size, 431 | write_options.endian, 432 | ); 433 | 434 | try self.writeFde(writer, write_options, cie_with_header, entry_header, fde); 435 | }, 436 | .terminator => { 437 | try writer.print("{x:0>8} ZERO terminator\n", .{entry_header.length_offset}); 438 | break; 439 | }, 440 | } 441 | } 442 | } 443 | 444 | fn headerFormat(format: std.dwarf.Format) []const u8 { 445 | return if (format == .@"64") "{x:0>16}" else "{x:0>8}"; 446 | } 447 | 448 | fn writeCie( 449 | self: DwarfDump, 450 | writer: anytype, 451 | options: WriteOptions, 452 | cie_with_header: *CieWithHeader, 453 | ) !void { 454 | const expression_context = dwarf.expressions.ExpressionContext{ 455 | .format = cie_with_header.header.format, 456 | }; 457 | 458 | switch (cie_with_header.header.format) { 459 | inline else => |format| { 460 | const length_fmt = comptime headerFormat(format); 461 | try writer.print("{x:0>8} " ++ length_fmt ++ " " ++ length_fmt ++ " CIE\n", .{ 462 | cie_with_header.cie.length_offset, 463 | cie_with_header.header.entryLength(), 464 | @as(u64, switch (options.frame_type) { 465 | .eh_frame => dwarf.CommonInformationEntry.eh_id, 466 | .debug_frame => if (format == .@"64") dwarf.CommonInformationEntry.dwarf64_id else dwarf.CommonInformationEntry.dwarf32_id, 467 | else => unreachable, 468 | }), 469 | }); 470 | }, 471 | } 472 | 473 | const cie = &cie_with_header.cie; 474 | try writeFormat(writer, cie_with_header.header.format, true); 475 | try writer.print(" {s: <23}{}\n", .{ "Version:", cie.version }); 476 | try writer.print(" {s: <23}\"{s}\"\n", .{ "Augmentation:", cie.aug_str }); 477 | if (cie_with_header.cie.version == 4) { 478 | try writer.print(" {s: <23}{}\n", .{ "Address size:", cie.address_size }); 479 | try writer.print(" {s: <23}{}\n", .{ "Segment desc size:", cie.segment_selector_size.? }); 480 | } 481 | try writer.print(" {s: <23}{}\n", .{ "Code alignment factor:", cie.code_alignment_factor }); 482 | try writer.print(" {s: <23}{}\n", .{ "Data alignment factor:", cie.data_alignment_factor }); 483 | try writer.print(" {s: <23}{}\n", .{ "Return address column:", cie.return_address_register }); 484 | 485 | // Oddly llvm-dwarfdump does not align this field with the rest 486 | if (cie.personality_routine_pointer) |p| try writer.print(" {s: <21}{x:0>16}\n", .{ "Personality Address:", p }); 487 | 488 | if (cie.aug_data.len > 0) { 489 | try writer.print(" {s: <22}", .{"Augmentation data:"}); 490 | for (cie.aug_data) |byte| { 491 | try writer.print(" {X:0>2}", .{byte}); 492 | } 493 | try writer.writeByte('\n'); 494 | } 495 | 496 | if (!options.llvm_compatibility) { 497 | try writer.writeAll("\n"); 498 | if (cie.personality_enc) |p| try writer.print(" {s: <23}{X}\n", .{ "Personality Pointer Encoding:", p }); 499 | try writer.print(" {s: <23}{X}\n", .{ "LSDA Pointer Encoding:", cie.lsda_pointer_enc }); 500 | try writer.print(" {s: <23}{X}\n", .{ "FDE Pointer Encoding:", cie.fde_pointer_enc }); 501 | } 502 | 503 | try writer.writeAll("\n"); 504 | 505 | { 506 | var instruction_stream = std.io.fixedBufferStream(cie.initial_instructions); 507 | while (instruction_stream.pos < instruction_stream.buffer.len) { 508 | const instruction = try dwarf.call_frame.Instruction.read(&instruction_stream, options.addr_size, options.endian); 509 | const opcode = std.meta.activeTag(instruction); 510 | try writer.print(" DW_CFA_{s}:", .{@tagName(opcode)}); 511 | try writeOperands( 512 | instruction, 513 | writer, 514 | cie.*, 515 | self.ctx.getArch(), 516 | expression_context, 517 | options.reg_ctx, 518 | options.addr_size, 519 | options.endian, 520 | ); 521 | _ = try cie_with_header.vm.step(self.gpa, cie.*, true, instruction); 522 | try writer.writeByte('\n'); 523 | } 524 | } 525 | 526 | try writer.writeAll("\n"); 527 | if (cie_with_header.vm.current_row.cfa.rule != .default) try writer.writeAll(" "); 528 | try self.writeRow( 529 | writer, 530 | cie_with_header.vm, 531 | cie_with_header.vm.current_row, 532 | expression_context, 533 | options.reg_ctx, 534 | options.addr_size, 535 | options.endian, 536 | ); 537 | try writer.writeByte('\n'); 538 | 539 | cie_with_header.vm_snapshot_columns = cie_with_header.vm.columns.items.len; 540 | cie_with_header.vm_snapshot_row = cie_with_header.vm.current_row; 541 | } 542 | 543 | fn writeFde( 544 | self: DwarfDump, 545 | writer: anytype, 546 | options: WriteOptions, 547 | cie_with_header: *CieWithHeader, 548 | header: dwarf.EntryHeader, 549 | fde: dwarf.FrameDescriptionEntry, 550 | ) !void { 551 | const cie = &cie_with_header.cie; 552 | const expression_context = dwarf.expressions.ExpressionContext{ 553 | .format = cie.format, 554 | }; 555 | 556 | // TODO: Print for cie if it didn't point to an actual CIE 557 | switch (cie_with_header.header.format) { 558 | inline else => |format| { 559 | const length_fmt = comptime headerFormat(format); 560 | try writer.print("{x:0>8} " ++ length_fmt ++ " " ++ length_fmt ++ " FDE cie={x:0>8} pc={x:0>8}...{x:0>8}\n", .{ 561 | header.length_offset, 562 | header.entryLength(), 563 | switch (options.frame_type) { 564 | .eh_frame => (header.length_offset + @as(u8, if (header.format == .@"64") 12 else 4)) - fde.cie_length_offset, 565 | .debug_frame => fde.cie_length_offset, 566 | else => unreachable, 567 | }, 568 | fde.cie_length_offset, 569 | fde.pc_begin, 570 | fde.pc_begin + fde.pc_range, 571 | }); 572 | }, 573 | } 574 | 575 | try writeFormat(writer, cie_with_header.header.format, false); 576 | if (fde.lsda_pointer) |p| try writer.print(" LSDA Address: {x:0>16}\n", .{p}); 577 | 578 | if (!options.llvm_compatibility) { 579 | if (fde.aug_data.len > 0) try writer.print(" {s: <23}{}\n", .{ "Augmentation data:", std.fmt.fmtSliceHexUpper(cie.aug_data) }); 580 | } 581 | 582 | var instruction_stream = std.io.fixedBufferStream(fde.instructions); 583 | 584 | // First pass to print instructions and their operands 585 | while (instruction_stream.pos < instruction_stream.buffer.len) { 586 | const instruction = try dwarf.call_frame.Instruction.read(&instruction_stream, options.addr_size, options.endian); 587 | const opcode = std.meta.activeTag(instruction); 588 | try writer.print(" DW_CFA_{s}:", .{@tagName(opcode)}); 589 | try writeOperands( 590 | instruction, 591 | writer, 592 | cie.*, 593 | self.ctx.getArch(), 594 | expression_context, 595 | options.reg_ctx, 596 | options.addr_size, 597 | options.endian, 598 | ); 599 | try writer.writeByte('\n'); 600 | } 601 | 602 | try writer.writeByte('\n'); 603 | 604 | // Second pass to run them and print the generated table 605 | instruction_stream.pos = 0; 606 | while (instruction_stream.pos < instruction_stream.buffer.len) { 607 | const instruction = try dwarf.call_frame.Instruction.read(&instruction_stream, options.addr_size, options.endian); 608 | const prev_row = try cie_with_header.vm.step(self.gpa, cie.*, false, instruction); 609 | if (cie_with_header.vm.current_row.offset != prev_row.offset) { 610 | try writer.print(" 0x{x}: ", .{fde.pc_begin + prev_row.offset}); 611 | try self.writeRow( 612 | writer, 613 | cie_with_header.vm, 614 | prev_row, 615 | expression_context, 616 | options.reg_ctx, 617 | options.addr_size, 618 | options.endian, 619 | ); 620 | } 621 | } 622 | 623 | try writer.print(" 0x{x}: ", .{fde.pc_begin + cie_with_header.vm.current_row.offset}); 624 | try self.writeRow( 625 | writer, 626 | cie_with_header.vm, 627 | cie_with_header.vm.current_row, 628 | expression_context, 629 | options.reg_ctx, 630 | options.addr_size, 631 | options.endian, 632 | ); 633 | 634 | // Restore the VM state to the result of the initial CIE instructions 635 | cie_with_header.vm.columns.items.len = cie_with_header.vm_snapshot_columns; 636 | cie_with_header.vm.current_row = cie_with_header.vm_snapshot_row; 637 | cie_with_header.vm.cie_row = null; 638 | 639 | try writer.writeByte('\n'); 640 | } 641 | 642 | fn writeRow( 643 | self: DwarfDump, 644 | writer: anytype, 645 | vm: VirtualMachine, 646 | row: VirtualMachine.Row, 647 | expression_context: dwarf.expressions.ExpressionContext, 648 | reg_ctx: abi.RegisterContext, 649 | addr_size: u8, 650 | endian: std.builtin.Endian, 651 | ) !void { 652 | const columns = vm.rowColumns(row); 653 | 654 | var wrote_anything = false; 655 | var wrote_separator = false; 656 | if (try writeColumnRule( 657 | row.cfa, 658 | writer, 659 | true, 660 | self.ctx.getArch(), 661 | expression_context, 662 | reg_ctx, 663 | addr_size, 664 | endian, 665 | )) { 666 | wrote_anything = true; 667 | } 668 | 669 | // llvm-dwarfdump prints columns sorted by register number 670 | var num_printed: usize = 0; 671 | for (0..256) |register| { 672 | for (columns) |column| { 673 | if (column.register == @as(u8, @intCast(register))) { 674 | if (column.rule != .default and !wrote_separator) { 675 | try writer.writeAll(": "); 676 | wrote_separator = true; 677 | } 678 | 679 | if (try writeColumnRule( 680 | column, 681 | writer, 682 | false, 683 | self.ctx.getArch(), 684 | expression_context, 685 | reg_ctx, 686 | addr_size, 687 | endian, 688 | )) { 689 | if (num_printed != columns.len - 1) { 690 | try writer.writeAll(", "); 691 | } 692 | wrote_anything = true; 693 | } 694 | 695 | num_printed += 1; 696 | } 697 | } 698 | 699 | if (num_printed == columns.len) break; 700 | } 701 | 702 | if (wrote_anything) try writer.writeByte('\n'); 703 | } 704 | 705 | pub fn writeColumnRule( 706 | column: VirtualMachine.Column, 707 | writer: anytype, 708 | is_cfa: bool, 709 | arch: ?std.Target.Cpu.Arch, 710 | expression_context: dwarf.expressions.ExpressionContext, 711 | reg_ctx: abi.RegisterContext, 712 | addr_size_bytes: u8, 713 | endian: std.builtin.Endian, 714 | ) !bool { 715 | if (column.rule == .default) return false; 716 | 717 | if (is_cfa) { 718 | try writer.writeAll("CFA"); 719 | } else { 720 | try writeRegisterName(writer, arch, column.register.?, reg_ctx); 721 | } 722 | 723 | try writer.writeByte('='); 724 | switch (column.rule) { 725 | .default => {}, 726 | .undefined => try writer.writeAll("undefined"), 727 | .same_value => try writer.writeAll("S"), 728 | .offset, .val_offset => |offset| { 729 | if (offset == 0) { 730 | if (is_cfa) { 731 | if (column.register) |cfa_register| { 732 | try writer.print("{}", .{fmtRegister(cfa_register, reg_ctx, arch)}); 733 | } else { 734 | try writer.writeAll("undefined"); 735 | } 736 | } else { 737 | try writer.writeAll("[CFA]"); 738 | } 739 | } else { 740 | if (is_cfa) { 741 | if (column.register) |cfa_register| { 742 | try writer.print("{}{d:<1}", .{ fmtRegister(cfa_register, reg_ctx, arch), offset }); 743 | } else { 744 | try writer.print("undefined{d:<1}", .{offset}); 745 | } 746 | } else { 747 | try writer.print("[CFA{d:<1}]", .{offset}); 748 | } 749 | } 750 | }, 751 | .register => |register| try writeRegisterName(writer, arch, register, reg_ctx), 752 | .expression => |expression| { 753 | if (!is_cfa) try writer.writeByte('['); 754 | try writeExpression(writer, expression, arch, expression_context, reg_ctx, addr_size_bytes, endian); 755 | if (!is_cfa) try writer.writeByte(']'); 756 | }, 757 | .val_expression => try writer.writeAll("TODO(val_expression)"), 758 | .architectural => try writer.writeAll("TODO(architectural)"), 759 | } 760 | 761 | return true; 762 | } 763 | 764 | fn writeExpression( 765 | writer: anytype, 766 | block: []const u8, 767 | arch: ?std.Target.Cpu.Arch, 768 | expression_context: dwarf.expressions.ExpressionContext, 769 | reg_ctx: abi.RegisterContext, 770 | addr_size_bytes: u8, 771 | endian: std.builtin.Endian, 772 | ) !void { 773 | var stream = std.io.fixedBufferStream(block); 774 | 775 | // Generate a lookup table from opcode value to name 776 | const opcode_lut_len = 256; 777 | const opcode_lut: [opcode_lut_len]?[]const u8 = comptime blk: { 778 | var lut: [opcode_lut_len]?[]const u8 = [_]?[]const u8{null} ** opcode_lut_len; 779 | for (@typeInfo(dwarf.OP).Struct.decls) |decl| { 780 | lut[@as(u8, @field(dwarf.OP, decl.name))] = decl.name; 781 | } 782 | 783 | break :blk lut; 784 | }; 785 | 786 | switch (endian) { 787 | inline .little, .big => |e| { 788 | switch (addr_size_bytes) { 789 | inline 2, 4, 8 => |size| { 790 | const StackMachine = dwarf.expressions.StackMachine(.{ 791 | .addr_size = size, 792 | .endian = e, 793 | .call_frame_context = true, 794 | }); 795 | 796 | const reader = stream.reader(); 797 | while (stream.pos < stream.buffer.len) { 798 | if (stream.pos > 0) try writer.writeAll(", "); 799 | 800 | const opcode = try reader.readByte(); 801 | if (opcode_lut[opcode]) |opcode_name| { 802 | try writer.print("DW_OP_{s}", .{opcode_name}); 803 | } else { 804 | // TODO: See how llvm-dwarfdump prints these? 805 | if (opcode >= dwarf.OP.lo_user and opcode <= dwarf.OP.hi_user) { 806 | try writer.print("", .{opcode}); 807 | } else { 808 | try writer.print("", .{opcode}); 809 | } 810 | } 811 | 812 | if (try StackMachine.readOperand(&stream, opcode, expression_context)) |value| { 813 | switch (value) { 814 | .generic => {}, // Constant values are implied by the opcode name 815 | .register => |v| try writer.print(" {}", .{fmtRegister(v, reg_ctx, arch)}), 816 | .base_register => |v| try writer.print(" {}{d:<1}", .{ fmtRegister(v.base_register, reg_ctx, arch), v.offset }), 817 | else => try writer.print(" TODO({s})", .{@tagName(value)}), 818 | } 819 | } 820 | } 821 | }, 822 | else => return error.InvalidAddrSize, 823 | } 824 | }, 825 | } 826 | } 827 | 828 | fn writeOperands( 829 | instruction: dwarf.call_frame.Instruction, 830 | writer: anytype, 831 | cie: dwarf.CommonInformationEntry, 832 | arch: ?std.Target.Cpu.Arch, 833 | expression_context: dwarf.expressions.ExpressionContext, 834 | reg_ctx: abi.RegisterContext, 835 | addr_size_bytes: u8, 836 | endian: std.builtin.Endian, 837 | ) !void { 838 | switch (instruction) { 839 | .set_loc => |i| try writer.print(" 0x{x}", .{i.address}), 840 | inline .advance_loc, 841 | .advance_loc1, 842 | .advance_loc2, 843 | .advance_loc4, 844 | => |i| try writer.print(" {}", .{i.delta * cie.code_alignment_factor}), 845 | inline .offset, 846 | .offset_extended, 847 | .offset_extended_sf, 848 | => |i| try writer.print(" {} {d}", .{ 849 | fmtRegister(i.register, reg_ctx, arch), 850 | @as(i64, @intCast(i.offset)) * cie.data_alignment_factor, 851 | }), 852 | inline .restore, 853 | .restore_extended, 854 | .undefined, 855 | .same_value, 856 | => |i| try writer.print(" {}", .{fmtRegister(i.register, reg_ctx, arch)}), 857 | .nop => {}, 858 | .register => |i| try writer.print(" {} {}", .{ fmtRegister(i.register, reg_ctx, arch), fmtRegister(i.target_register, reg_ctx, arch) }), 859 | .remember_state => {}, 860 | .restore_state => {}, 861 | .def_cfa => |i| try writer.print(" {} {d:<1}", .{ fmtRegister(i.register, reg_ctx, arch), @as(i64, @intCast(i.offset)) }), 862 | .def_cfa_sf => |i| try writer.print(" {} {d:<1}", .{ fmtRegister(i.register, reg_ctx, arch), i.offset * cie.data_alignment_factor }), 863 | .def_cfa_register => |i| try writer.print(" {}", .{fmtRegister(i.register, reg_ctx, arch)}), 864 | .def_cfa_offset => |i| try writer.print(" {d:<1}", .{@as(i64, @intCast(i.offset))}), 865 | .def_cfa_offset_sf => |i| try writer.print(" {d:<1}", .{i.offset * cie.data_alignment_factor}), 866 | .def_cfa_expression => |i| { 867 | try writer.writeByte(' '); 868 | try writeExpression(writer, i.block, arch, expression_context, reg_ctx, addr_size_bytes, endian); 869 | }, 870 | .expression => |i| { 871 | try writer.print(" {} ", .{fmtRegister(i.register, reg_ctx, arch)}); 872 | try writeExpression(writer, i.block, arch, expression_context, reg_ctx, addr_size_bytes, endian); 873 | }, 874 | .val_offset => {}, 875 | .val_offset_sf => {}, 876 | .val_expression => {}, 877 | } 878 | } 879 | 880 | fn writeFormat(writer: anytype, format: std.dwarf.Format, comptime is_cie: bool) !void { 881 | try writer.print(" {s: <" ++ (if (is_cie) "23" else "14") ++ "}{s}\n", .{ "Format:", if (format == .@"64") "DWARF64" else "DWARF32" }); 882 | } 883 | 884 | fn writeUnknownReg(writer: anytype, reg_number: u8) !void { 885 | try writer.print("reg{}", .{reg_number}); 886 | } 887 | 888 | pub fn writeRegisterName( 889 | writer: anytype, 890 | arch: ?std.Target.Cpu.Arch, 891 | reg_number: u8, 892 | reg_ctx: abi.RegisterContext, 893 | ) !void { 894 | if (arch) |a| { 895 | switch (a) { 896 | .x86 => { 897 | switch (reg_number) { 898 | 0 => try writer.writeAll("EAX"), 899 | 1 => try writer.writeAll("EDX"), 900 | 2 => try writer.writeAll("ECX"), 901 | 3 => try writer.writeAll("EBX"), 902 | 4 => if (reg_ctx.eh_frame and reg_ctx.is_macho) try writer.writeAll("EBP") else try writer.writeAll("ESP"), 903 | 5 => if (reg_ctx.eh_frame and reg_ctx.is_macho) try writer.writeAll("ESP") else try writer.writeAll("EBP"), 904 | 6 => try writer.writeAll("ESI"), 905 | 7 => try writer.writeAll("EDI"), 906 | 8 => try writer.writeAll("EIP"), 907 | 9 => try writer.writeAll("EFL"), 908 | 10 => try writer.writeAll("CS"), 909 | 11 => try writer.writeAll("SS"), 910 | 12 => try writer.writeAll("DS"), 911 | 13 => try writer.writeAll("ES"), 912 | 14 => try writer.writeAll("FS"), 913 | 15 => try writer.writeAll("GS"), 914 | 16...23 => try writer.print("ST{}", .{reg_number - 16}), 915 | 32...39 => try writer.print("XMM{}", .{reg_number - 32}), 916 | else => try writeUnknownReg(writer, reg_number), 917 | } 918 | }, 919 | .x86_64 => { 920 | switch (reg_number) { 921 | 0 => try writer.writeAll("RAX"), 922 | 1 => try writer.writeAll("RDX"), 923 | 2 => try writer.writeAll("RCX"), 924 | 3 => try writer.writeAll("RBX"), 925 | 4 => try writer.writeAll("RSI"), 926 | 5 => try writer.writeAll("RDI"), 927 | 6 => try writer.writeAll("RBP"), 928 | 7 => try writer.writeAll("RSP"), 929 | 8...15 => try writer.print("R{}", .{reg_number}), 930 | 16 => try writer.writeAll("RIP"), 931 | 17...32 => try writer.print("XMM{}", .{reg_number - 17}), 932 | 33...40 => try writer.print("ST{}", .{reg_number - 33}), 933 | 41...48 => try writer.print("MM{}", .{reg_number - 41}), 934 | 49 => try writer.writeAll("RFLAGS"), 935 | 50 => try writer.writeAll("ES"), 936 | 51 => try writer.writeAll("CS"), 937 | 52 => try writer.writeAll("SS"), 938 | 53 => try writer.writeAll("DS"), 939 | 54 => try writer.writeAll("FS"), 940 | 55 => try writer.writeAll("GS"), 941 | // 56-57 Reserved 942 | 58 => try writer.writeAll("FS.BASE"), 943 | 59 => try writer.writeAll("GS.BASE"), 944 | // 60-61 Reserved 945 | 62 => try writer.writeAll("TR"), 946 | 63 => try writer.writeAll("LDTR"), 947 | 64 => try writer.writeAll("MXCSR"), 948 | 65 => try writer.writeAll("FCW"), 949 | 66 => try writer.writeAll("FSW"), 950 | 67...82 => try writer.print("XMM{}", .{reg_number - 51}), 951 | // 83-117 Reserved 952 | 118...125 => try writer.print("K{}", .{reg_number - 118}), 953 | // 126-129 Reserved 954 | else => try writeUnknownReg(writer, reg_number), 955 | } 956 | }, 957 | .arm => { 958 | switch (reg_number) { 959 | 0...15 => try writer.print("R{}", .{reg_number}), 960 | // 16-63 None 961 | 64...95 => try writer.print("S{}", .{reg_number - 64}), 962 | 96...103 => try writer.print("F{}", .{reg_number - 96}), 963 | 964 | // Could also be ACC0-ACC7 965 | 104...111 => try writer.print("wCGR0{}", .{reg_number - 104}), 966 | 112...127 => try writer.print("wR0{}", .{reg_number - 112}), 967 | 128 => try writer.writeAll("SPSR"), 968 | 129 => try writer.writeAll("SPSR_FIQ"), 969 | 130 => try writer.writeAll("SPSR_IRQ"), 970 | 131 => try writer.writeAll("SPSR_ABT"), 971 | 132 => try writer.writeAll("SPSR_UND"), 972 | 133 => try writer.writeAll("SPSR_SVC"), 973 | // 134-142 None 974 | else => try writeUnknownReg(writer, reg_number), 975 | } 976 | }, 977 | .aarch64 => { 978 | switch (reg_number) { 979 | 0...30 => try writer.print("W{}", .{reg_number}), 980 | 31 => try writer.writeAll("WSP"), 981 | 32 => try writer.writeAll("PC"), 982 | 33 => try writer.writeAll("ELR_mode"), 983 | 34 => try writer.writeAll("RA_SIGN_STATE"), 984 | 35 => try writer.writeAll("TPIDRRO_ELO"), 985 | 36 => try writer.writeAll("TPIDR_ELO"), 986 | 37 => try writer.writeAll("TPIDR_EL1"), 987 | 38 => try writer.writeAll("TPIDR_EL2"), 988 | 39 => try writer.writeAll("TPIDR_EL3"), 989 | // 40-45 Reserved 990 | 46 => try writer.writeAll("VG"), 991 | 47 => try writer.writeAll("FFR"), 992 | 48...63 => try writer.print("P{}", .{reg_number - 48}), 993 | 64...95 => try writer.print("B{}", .{reg_number - 64}), 994 | 96...127 => try writer.print("Z{}", .{reg_number - 96}), 995 | else => try writeUnknownReg(writer, reg_number), 996 | } 997 | }, 998 | else => try writeUnknownReg(writer, reg_number), 999 | } 1000 | } else try writeUnknownReg(writer, reg_number); 1001 | } 1002 | 1003 | const FormatRegisterData = struct { 1004 | reg_number: u8, 1005 | reg_ctx: abi.RegisterContext, 1006 | arch: ?std.Target.Cpu.Arch, 1007 | }; 1008 | 1009 | pub fn formatRegister( 1010 | data: FormatRegisterData, 1011 | comptime fmt: []const u8, 1012 | options: std.fmt.FormatOptions, 1013 | writer: anytype, 1014 | ) !void { 1015 | _ = fmt; 1016 | _ = options; 1017 | try writeRegisterName(writer, data.arch, data.reg_number, data.reg_ctx); 1018 | } 1019 | 1020 | pub fn fmtRegister( 1021 | reg_number: u8, 1022 | reg_ctx: abi.RegisterContext, 1023 | arch: ?std.Target.Cpu.Arch, 1024 | ) std.fmt.Formatter(formatRegister) { 1025 | return .{ 1026 | .data = .{ 1027 | .reg_number = reg_number, 1028 | .reg_ctx = reg_ctx, 1029 | .arch = arch, 1030 | }, 1031 | }; 1032 | } 1033 | 1034 | pub const Loc = struct { 1035 | pos: usize, 1036 | len: usize, 1037 | }; 1038 | 1039 | pub const Format = enum { 1040 | dwarf32, 1041 | dwarf64, 1042 | 1043 | pub fn fmtOffset(format: Format, offset: u64) std.fmt.Formatter(formatOffset) { 1044 | return .{ .data = .{ 1045 | .format = format, 1046 | .offset = offset, 1047 | } }; 1048 | } 1049 | 1050 | const FmtOffsetCtx = struct { 1051 | format: Format, 1052 | offset: u64, 1053 | }; 1054 | 1055 | fn formatOffset( 1056 | ctx: FmtOffsetCtx, 1057 | comptime unused_format_string: []const u8, 1058 | options: std.fmt.FormatOptions, 1059 | writer: anytype, 1060 | ) !void { 1061 | _ = unused_format_string; 1062 | _ = options; 1063 | switch (ctx.format) { 1064 | .dwarf32 => try writer.print("0x{x:0>8}", .{ctx.offset}), 1065 | .dwarf64 => try writer.print("0x{x:0>16}", .{ctx.offset}), 1066 | } 1067 | } 1068 | }; 1069 | 1070 | const DwarfDump = @This(); 1071 | 1072 | const std = @import("std"); 1073 | const assert = std.debug.assert; 1074 | const dwarf = std.dwarf; 1075 | const abi = dwarf.abi; 1076 | const leb = std.leb; 1077 | const log = std.log; 1078 | const fs = std.fs; 1079 | const mem = std.mem; 1080 | 1081 | const Allocator = mem.Allocator; 1082 | const AbbrevLookupTable = std.AutoHashMap(u64, struct { pos: usize, len: usize }); 1083 | const AbbrevTable = @import("AbbrevTable.zig"); 1084 | const CompileUnit = @import("CompileUnit.zig"); 1085 | const Context = @import("Context.zig"); 1086 | const VirtualMachine = dwarf.call_frame.VirtualMachine; 1087 | --------------------------------------------------------------------------------