const std = @import("std"); const cs = @import("capstone"); pub fn main() !void { var args = std.process.args(); _ = args.skip(); // skip argv[0] const bw = std.debug.lockStderrWriter(&.{}); defer std.debug.unlockStderrWriter(); const ttyconf = std.io.tty.detectConfig(.stderr()); var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; const allocator = gpa.allocator(); try printElf(allocator, args.next() orelse "./study-samples/split", bw, ttyconf); } pub fn printElf( allocator: std.mem.Allocator, path: []const u8, bw: *std.Io.Writer, ttyconf: std.io.tty.Config, ) !void { const f = try std.fs.cwd().openFile(path, .{ .mode = .read_only }); var buffer = try allocator.alloc(u8, 1024 * 100); // const buffer = try allocator.alloc(u8, 1024 * 10000000); var reader = f.reader(buffer); const header = try std.elf.Header.read(&reader.interface); var handle: usize = undefined; std.debug.assert(cs.cs_open(cs.CS_ARCH_X86, cs.CS_MODE_64, @ptrCast(&handle)) == cs.CS_ERR_OK); std.debug.print("capstone handle {x}\n", .{handle}); const shstr = blk: { var section_it = header.iterateSectionHeaders(&reader); var section_idx: u32 = 0; while (try section_it.next()) |s| { defer section_idx += 1; if (section_idx == header.shstrndx) { std.debug.assert(s.sh_type == std.elf.SHT_STRTAB); break :blk s; } } break :blk null; }; // during the program's runtime, how will be this information accessed? const elf_strtab_slice = blk: { try reader.seekTo(shstr.?.sh_offset); const slice = try reader.interface.take(shstr.?.sh_size); const owned_slice = try allocator.alloc(u8, slice.len); @memcpy(owned_slice, slice); break :blk owned_slice; }; var strs: std.ArrayList([]const u8) = try .initCapacity(allocator, 8); { var str_it = std.mem.splitScalar(u8, elf_strtab_slice, 0); while (str_it.next()) |str| { const owned_str = try allocator.alloc(u8, str.len); @memcpy(owned_str, str); try strs.append(allocator, owned_str); } } var sections: std.ArrayList(std.elf.Elf64_Shdr) = try .initCapacity(allocator, 8); { var section_it = header.iterateSectionHeaders(&reader); while (try section_it.next()) |section| { try sections.append(allocator, section); } std.mem.sort(std.elf.Elf64_Shdr, sections.items, {}, struct { pub fn inner(_: void, x: std.elf.Elf64_Shdr, y: std.elf.Elf64_Shdr) bool { // NOTE: use the running mem or the static elf mem? // return x.sh_offset < y.sh_offset; return x.sh_addr < y.sh_addr; } }.inner); } for (sections.items) |section| { if (section.sh_size > 0 and section.sh_addr > 0) { try ttyconf.setColor(bw, .bright_green); try bw.print("\n{s}", .{std.mem.sliceTo(elf_strtab_slice[section.sh_name..], 0)}); try ttyconf.setColor(bw, .reset); try ttyconf.setColor(bw, .dim); try bw.print(" -- {x}-{x}\n", .{ section.sh_addr, section.sh_addr + section.sh_size, // section, }); try ttyconf.setColor(bw, .reset); try reader.seekTo(section.sh_offset); if (buffer.len < section.sh_size) { buffer = try allocator.realloc(buffer, section.sh_size); reader = f.reader(buffer); } // FIXME: this is buggy const section_slice = try reader.interface.take(section.sh_size); if (section.sh_type == std.elf.SHT_PROGBITS) { const instrs: []cs.cs_insn = blk: { var insn: [*]cs.cs_insn = undefined; const count = cs.cs_disasm(handle, section_slice.ptr, section_slice.len, section.sh_addr, 0, @ptrCast(&insn)); break :blk insn[0..count]; }; try dumpInstr(bw, ttyconf, instrs); } else { std.debug.print("section pointer {x}\n", .{@intFromPtr(section_slice.ptr)}); try dumpHexFallible(u64, bw, ttyconf, section_slice, section.sh_addr); } } } } fn dumpInstr( bw: *std.Io.Writer, ttyconf: std.io.tty.Config, instrs: []cs.cs_insn, ) !void { for (instrs) |instr| { try ttyconf.setColor(bw, .dim); try bw.print("{x:0>[1]} ", .{ instr.address, @sizeOf(usize) * 2 }); try ttyconf.setColor(bw, .reset); try bw.print("{s} {s}\n", .{ instr.mnemonic, instr.op_str }); } } /// Prints a hexadecimal view of the bytes, returning any error that occurs. pub fn dumpHexFallible(_: type, bw: *std.Io.Writer, ttyconf: std.io.tty.Config, bytes: []const u8, offset: u64) !void { // @breakpoint(); const nbytes = 16; var chunks = std.mem.window(u8, @ptrCast(@alignCast(bytes)), nbytes, nbytes); while (chunks.next()) |window| { // 1. Print the address. const address = ((0x10 * (std.math.divCeil(usize, chunks.index orelse bytes.len, nbytes) catch unreachable)) - 0x10) + offset; try ttyconf.setColor(bw, .dim); // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more. // Also, make sure all lines are aligned by padding the address. try bw.print("{x:0>[1]} ", .{ address, @sizeOf(usize) * 2 }); try ttyconf.setColor(bw, .reset); // 2. Print the bytes. for (window, 0..) |byte, index| { try bw.print("{X:0>2} ", .{byte}); if (index == 7) try bw.writeByte(' '); } try bw.writeByte(' '); if (window.len < 16) { var missing_columns = (16 - window.len) * 3; if (window.len < 8) missing_columns += 1; try bw.splatByteAll(' ', missing_columns); } const window_bytes: []const u8 = @ptrCast(@alignCast(window)); // 3. Print the characters. for (window_bytes) |byte| { if (std.ascii.isPrint(byte)) { try bw.writeByte(byte); } else { // Related: https://github.com/ziglang/zig/issues/7600 if (ttyconf == .windows_api) { try bw.writeByte('.'); continue; } // Let's print some common control codes as graphical Unicode symbols. // We don't want to do this for all control codes because most control codes apart from // the ones that Zig has escape sequences for are likely not very useful to print as symbols. switch (byte) { '\n' => try bw.writeAll("␊"), '\r' => try bw.writeAll("␍"), '\t' => try bw.writeAll("␉"), else => try bw.writeByte('.'), } } } try bw.writeByte('\n'); } }