From 7c7374d820aef11c28cdbedf9e1499bf404ab1d3 Mon Sep 17 00:00:00 2001 From: Jora Troosh Date: Sat, 20 Jul 2024 15:27:04 +0300 Subject: [PATCH] feat: add debuggee restart --- src/Breakpoint.zig | 6 +-- src/abi.zig | 78 ++++++++++++++++++++++++++++++++++----- src/dobby.zig | 91 +++++++++++++++++++++++----------------------- src/main.zig | 18 +++------ src/ptrace.zig | 18 +++++++++ 5 files changed, 142 insertions(+), 69 deletions(-) diff --git a/src/Breakpoint.zig b/src/Breakpoint.zig index 886bbce..bd88c19 100644 --- a/src/Breakpoint.zig +++ b/src/Breakpoint.zig @@ -11,7 +11,7 @@ pub const Location = struct { line_num: u32, }; -saved_inst: abi.bp_inst_width_t = undefined, +saved_inst: abi.trap_inst_width_t = undefined, loc: ?Location = null, is_set: bool = false, pid: std.posix.pid_t, @@ -32,7 +32,7 @@ pub fn initLocation(pid: std.posix.pid_t, addr: usize, loc: Location) !Breakpoin pub fn set(self: *Breakpoint) !void { var inst: usize = undefined; try ptrace.readAddress(self.pid, self.addr, &inst); - try ptrace.writeAddress(self.pid, self.addr, (inst & abi.BP_INST_MASK) | abi.BP_INST); + try ptrace.writeAddress(self.pid, self.addr, (inst & abi.TRAP_INST_MASK) | abi.TRAP_INST); self.saved_inst = @truncate(inst); self.is_set = true; } @@ -40,7 +40,7 @@ pub fn set(self: *Breakpoint) !void { pub fn unset(self: *Breakpoint) !void { var inst: usize = undefined; try ptrace.readAddress(self.pid, self.addr, &inst); - try ptrace.writeAddress(self.pid, self.addr, (inst & abi.BP_INST_MASK) | self.saved_inst); + try ptrace.writeAddress(self.pid, self.addr, (inst & abi.TRAP_INST_MASK) | self.saved_inst); self.saved_inst = undefined; self.is_set = false; } diff --git a/src/abi.zig b/src/abi.zig index 1e03c78..56b92f0 100644 --- a/src/abi.zig +++ b/src/abi.zig @@ -7,22 +7,22 @@ pub const PC: Register = switch (builtin.cpu.arch) { else => unreachable, }; -pub const BP_INST: bp_inst_width_t = switch (builtin.cpu.arch) { +pub const TRAP_INST: trap_inst_width_t = switch (builtin.cpu.arch) { .x86_64 => 0xCC, // "int3" .aarch64 => 0xD420_0000, // "brk #0" else => unreachable, }; -pub const BP_INST_MASK: usize = ~@as(usize, std.math.maxInt(bp_inst_width_t)); +pub const TRAP_INST_MASK: usize = ~@as(usize, std.math.maxInt(trap_inst_width_t)); -pub const bp_inst_width_t = switch (builtin.cpu.arch) { +pub const trap_inst_width_t = switch (builtin.cpu.arch) { .x86_64 => u8, .aarch64 => u32, else => unreachable, }; pub const Register = switch (builtin.cpu.arch) { - .x86_64 => enum { + .x86_64 => enum(u5) { rax, rdx, rcx, @@ -40,16 +40,76 @@ pub const Register = switch (builtin.cpu.arch) { r14, r15, rip, - eflags, + xmm0, + xmm1, + xmm2, + xmm3, + xmm4, + xmm5, + xmm6, + xmm7, + xmm8, + xmm9, + xmm10, + xmm11, + xmm12, + xmm13, + xmm14, + xmm15, + st0, + st1, + st2, + st3, + st4, + st5, + st6, + st7, + mm0, + mm1, + mm2, + mm3, + mm4, + mm5, + mm6, + mm7, + rflags, + es, cs, ss, - fs_base, - gs_base, ds, - es, fs, gs, - orig_rax, + fs_base = 58, + gs_base, + tr = 62, + ldtr, + mxcsr, + fcw, + fsw, + xmm16, + xmm17, + xmm18, + xmm19, + xmm20, + xmm21, + xmm22, + xmm23, + xmm24, + xmm25, + xmm26, + xmm27, + xmm28, + xmm29, + xmm30, + xmm31, + k0 = 118, + k1, + k2, + k3, + k4, + k5, + k6, + k7, }, .aarch64 => enum { r0, diff --git a/src/dobby.zig b/src/dobby.zig index 561853b..a399a98 100644 --- a/src/dobby.zig +++ b/src/dobby.zig @@ -6,36 +6,26 @@ const LineTable = @import("LineTable.zig"); const Breakpoint = @import("Breakpoint.zig"); const HELP = - \\b - toggle breakpoint in file at line - \\a - assembly single step - \\s - source single step - \\l - list breakpoints - \\p - print variables - \\d - dump registers - \\v - step over - \\c - continue - \\o - step out - \\i - step in - \\r - restart - \\h - help - \\q - quit + \\ b : - toggle breakpoint in file at line + \\ TODO: w - toggle watchpoint at variable + \\ TODO: l - list breakpoints, watchpoints, threads + \\ TODO: t - switch to thread + \\ a - assembly-level single step + \\ TODO: s - source-level single step + \\ TODO: p - print variables + \\ d - dump registers + \\ TODO: v - step over + \\ o - step out + \\ c - continue + \\ r - restart + \\ h - help + \\ q - quit \\ ; pub fn debug(allocator: std.mem.Allocator, reader: anytype, writer: anytype, elf_file_path: []const u8) !void { - // Spawn debuggee - const pid = try std.posix.fork(); - - // Branch into debuggee - if (pid == 0) { - try std.posix.ptrace(std.os.linux.PTRACE.TRACEME, pid, 0, 0); - const posix_exe_file_path = try std.posix.toPosixPath(elf_file_path); - std.posix.execveZ(&posix_exe_file_path, @ptrCast(std.os.argv.ptr), @ptrCast(std.os.environ.ptr)) catch unreachable; - } - - // Wait for debuggee to be trapped - ptrace.waitForTrapSignal(pid); - try ptrace.killOnExit(pid); + // Trace debuggee + var pid = try ptrace.traceDebuggee(elf_file_path); // Read user program's DWARF information var sections: std.dwarf.DwarfInfo.SectionArray = std.dwarf.DwarfInfo.null_section_array; @@ -52,7 +42,7 @@ pub fn debug(allocator: std.mem.Allocator, reader: anytype, writer: anytype, elf try writer.writeAll(HELP); // Prompt user for input - try writer.writeAll(" \n"); + try writer.writeAll(" "); // Handle user commands var line_buf: [config.MAX_LINE_LEN]u8 = undefined; @@ -86,6 +76,16 @@ pub fn debug(allocator: std.mem.Allocator, reader: anytype, writer: anytype, elf ); } }, + 'w' => @panic("TODO"), + 'l' => { + var bps_iter = bps.valueIterator(); + while (bps_iter.next()) |bp| { + try writer.print("{}\n", .{bp.loc.?}); + } + //TODO: Add watchpoints + //TODO: Add threads + }, + 't' => @panic("TODO"), 'a' => { const pc = try ptrace.readRegister(pid, abi.PC); if (bps.getPtr(pc)) |bp| { @@ -96,12 +96,6 @@ pub fn debug(allocator: std.mem.Allocator, reader: anytype, writer: anytype, elf try LineTable.printSource(dwarf_info, allocator, writer, pc); }, 's' => @panic("TODO"), - 'l' => { - var bps_iter = bps.valueIterator(); - while (bps_iter.next()) |bp| { - try writer.print("{}\n", .{bp.loc.?}); - } - }, 'p' => @panic("TODO"), 'd' => { inline for (comptime std.enums.values(abi.Register)) |reg| { @@ -109,15 +103,6 @@ pub fn debug(allocator: std.mem.Allocator, reader: anytype, writer: anytype, elf } }, 'v' => @panic("TODO"), - 'c' => { - if (bps.getPtr(try ptrace.readRegister(pid, abi.PC))) |bp| { - if (bp.is_set) { - try bp.reset(); - } - } - const pc = try ptrace.continueExecution(pid); - try LineTable.printSource(dwarf_info, allocator, writer, pc); - }, 'o' => { const fp = try ptrace.readRegister(pid, .rbp); var ret_addr: usize = undefined; @@ -140,13 +125,29 @@ pub fn debug(allocator: std.mem.Allocator, reader: anytype, writer: anytype, elf try some_bp.unset(); } }, - 'i' => @panic("TODO"), - 'r' => @panic("TODO"), + 'c' => { + if (bps.getPtr(try ptrace.readRegister(pid, abi.PC))) |bp| { + if (bp.is_set) { + try bp.reset(); + } + } + const pc = try ptrace.continueExecution(pid); + try LineTable.printSource(dwarf_info, allocator, writer, pc); + }, + 'r' => { + try std.posix.kill(pid, std.posix.SIG.KILL); + pid = try ptrace.traceDebuggee(elf_file_path); + + var bps_iter = bps.valueIterator(); + while (bps_iter.next()) |bp| { + bp.pid = pid; + } + }, 'h' => try writer.writeAll(HELP), 'q' => break, else => |command| try writer.print("Unknown command: '{c}'\n", .{command}), } - try writer.writeAll(" \n"); + try writer.writeAll(" "); } } diff --git a/src/main.zig b/src/main.zig index 0ee284d..32f762c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3,8 +3,9 @@ const clap = @import("clap"); const dobby = @import("dobby.zig"); const PARAMS = clap.parseParamsComptime( - \\-h, --help Display help menu. - \\ ELF file path. + \\-a, --args Debuggee CLI arguments. + \\-h, --help Display help. + \\ Debugee ELF file path. \\ ); @@ -15,21 +16,16 @@ pub fn main() !void { }; var arena = std.heap.ArenaAllocator.init(gpa.allocator()); - defer arena.deinit(); const allocator = arena.allocator(); + defer arena.deinit(); const std_in = std.io.getStdIn(); const reader = std_in.reader(); const std_out = std.io.getStdOut(); - var buf_writer = std.io.bufferedWriter(std_out.writer()); - const writer = buf_writer.writer(); + const writer = std_out.writer(); - var diag = clap.Diagnostic{}; - var res = clap.parse(clap.Help, &PARAMS, clap.parsers.default, .{ .allocator = allocator, .diagnostic = &diag }) catch |err| { - diag.report(std.io.getStdErr().writer(), err) catch {}; - return err; - }; + var res = try clap.parse(clap.Help, &PARAMS, clap.parsers.default, .{ .allocator = allocator }); defer res.deinit(); var elf_file_path: []const u8 = "zig-out/bin/example"; @@ -43,6 +39,4 @@ pub fn main() !void { } try dobby.debug(allocator, reader, writer, elf_file_path); - - try buf_writer.flush(); } diff --git a/src/ptrace.zig b/src/ptrace.zig index 5d30b6c..4b757b2 100644 --- a/src/ptrace.zig +++ b/src/ptrace.zig @@ -3,8 +3,26 @@ const abi = @import("abi.zig"); const c = @cImport({ @cInclude("sys/user.h"); + // @cInclude("sys/personality.h"); }); +pub fn traceDebuggee(elf_file_path: []const u8) std.posix.pid_t { + const pid = try std.posix.fork(); + if (pid == 0) { + // c.personality(ADDR_NO_RANDOMIZE); + try std.posix.ptrace(std.os.linux.PTRACE.TRACEME, pid, 0, 0); + const posix_exe_file_path = try std.posix.toPosixPath(elf_file_path); + std.posix.execveZ( + &posix_exe_file_path, + @ptrCast(std.os.argv.ptr), + @ptrCast(std.os.environ.ptr), + ) catch @panic("Failed to execute debuggee!"); + } + waitForTrapSignal(pid); + try killOnExit(pid); + return pid; +} + pub fn continueExecution(pid: std.posix.pid_t) !usize { try std.posix.ptrace(std.os.linux.PTRACE.CONT, pid, 0, 0); waitForTrapSignal(pid);