The major contribution here is completely re-worked breakpoint hit/recoil handling. This work fixes #4907 and lays the ground work for future native debugger improvements (multi-threading, etc). * Give a human friendly type to enums * Change many wait functions to return RDebugReasonType * Better return checking (from r_debug_reg_sync, r_bp_restore) * Optimized register synchronization * Lots of comments and whitespace changes * Improved inferior death detection Handle EXIT_PID events differently than DEAD process events * Move breakpoint/recoil handling to wait/cont/step Rather than handing breakpoint related things inside cmd_debug.c, do that inside the r_debug API functions. This seems like the most logical place for it to live since it should apply to just about any platform/architecture. This also centralizes calling into "cmd.bp" handling via the CoreBind callback. * Track how the caller wishes to continue It turns out that handling break point recoils is very complicated. The ptrace API on Linux returns SIGTRAP for just about every type of operation (not just breakpoints getting hit). Add the "recoil_mode" flag to indicate whether we are single-stepping or continuing and whether or not we are inside the recoil. * Proper handling for swstep=true Since r_debug_step_soft calls r_debug_continue, it's already hitting the recoil case there. Move the recoil handling from r_debug_step to r_debug_step_hard only. For the swstep=true case, special handling is required inside r_debug_recoil. By resetting all of the breakpoints except the one we just hit, we ensure we can step the original instruction and hit the new swstep breakpoint. Add a new bp function called r_bp_restore_except to do this. To make matters worse, we cannot use a BreakpointItem pointer because that leads to a use-after-free condition. Instead, we the breakpoint address instead. Now breakpoints should work regardless of the swtep setting. * Always call the recoil before continuing Some callers of r_debug_continue might not have ever inserted any breakpoints before. If we don't restore breakpoints before each call to the underlying continue we won't hit them. * Hide software step breakpoint events from the user When a breakpoint even happens due to a software-step, hide it from the user. They aren't really breakpoints as far as they are concerned. * Improve process exit handling on Linux There are three types of process exiting events on Linux: 1. PTRACE_EVENT_EXIT occurs just before a process exits. It's not possible to prevent it from exiting, but it can be used to inspect the pre-exit state. 2. The process can exit for a variety of reasons and we can notice when we call waitpid(2). 3. The process could die randomly on us :-/ On Windows, h->wait will return R_DEBUG_REASON_EXIT_PID, but it's more likely on Linux to find out the process is already dead. * Check more bits within waitpid status We can often make a decision about what happened strictly by looking at the status returned from waitpid. In other cases, we need to call r_debug_handle_signals. If we reach the end of this function without knowing what happened, consider it an error.
90 lines
2.4 KiB
C
90 lines
2.4 KiB
C
/* __
|
|
-=(o '.
|
|
\.-.\
|
|
/| \\
|
|
'| ||
|
|
_\_):,_
|
|
*/
|
|
|
|
#include <limits.h>
|
|
#include <sys/ptrace.h>
|
|
|
|
struct user_regs_struct_x86_64 {
|
|
ut64 r15; ut64 r14; ut64 r13; ut64 r12; ut64 rbp; ut64 rbx; ut64 r11;
|
|
ut64 r10; ut64 r9; ut64 r8; ut64 rax; ut64 rcx; ut64 rdx; ut64 rsi;
|
|
ut64 rdi; ut64 orig_rax; ut64 rip; ut64 cs; ut64 eflags; ut64 rsp;
|
|
ut64 ss; ut64 fs_base; ut64 gs_base; ut64 ds; ut64 es; ut64 fs; ut64 gs;
|
|
};
|
|
|
|
struct user_regs_struct_x86_32 {
|
|
ut32 ebx; ut32 ecx; ut32 edx; ut32 esi; ut32 edi; ut32 ebp; ut32 eax;
|
|
ut32 xds; ut32 xes; ut32 xfs; ut32 xgs; ut32 orig_eax; ut32 eip;
|
|
ut32 xcs; ut32 eflags; ut32 esp; ut32 xss;
|
|
};
|
|
|
|
#if __ANDROID__
|
|
|
|
#if __arm64__ || __aarch64__
|
|
#define R_DEBUG_REG_T struct user_pt_regs
|
|
|
|
#ifndef NT_PRSTATUS
|
|
#define NT_PRSTATUS 1
|
|
#endif
|
|
|
|
#else
|
|
#define R_DEBUG_REG_T struct pt_regs
|
|
#endif
|
|
|
|
#else
|
|
|
|
#include <sys/user.h>
|
|
#if __i386__ || __x86_64__
|
|
#define R_DEBUG_REG_T struct user_regs_struct
|
|
#elif __arm64__ || __aarch64__
|
|
#define R_DEBUG_REG_T struct user_pt_regs
|
|
#elif __arm__
|
|
#define R_DEBUG_REG_T struct user_regs
|
|
#elif __POWERPC__
|
|
struct powerpc_regs_t {
|
|
unsigned long gpr[32];
|
|
unsigned long nip;
|
|
unsigned long msr;
|
|
unsigned long orig_gpr3; /* Used for restarting system calls */
|
|
unsigned long ctr;
|
|
unsigned long link;
|
|
unsigned long xer;
|
|
unsigned long ccr;
|
|
#ifdef __powerpc64__
|
|
unsigned long softe; /* Soft enabled/disabled */
|
|
#else
|
|
unsigned long mq; /* 601 only (not used at present) */
|
|
/* Used on APUS to hold IPL value. */
|
|
#endif
|
|
unsigned long trap; /* Reason for being here */
|
|
/* N.B. for critical exceptions on 4xx, the dar and dsisr
|
|
fields are overloaded to hold srr0 and srr1. */
|
|
unsigned long dar; /* Fault registers */
|
|
unsigned long dsisr; /* on 4xx/Book-E used for ESR */
|
|
unsigned long result; /* Result of a system call */
|
|
};
|
|
#define R_DEBUG_REG_T struct powerpc_regs_t
|
|
#elif __mips__
|
|
|
|
#include <sys/ucontext.h>
|
|
typedef ut64 mips64_regs_t [274];
|
|
#define R_DEBUG_REG_T mips64_regs_t
|
|
#endif
|
|
#endif
|
|
|
|
|
|
//API
|
|
int linux_step (RDebug *dbg);
|
|
RDebugReasonType linux_ptrace_event (RDebug *dbg, int pid, int status);
|
|
int linux_attach (RDebug *dbg, int pid);
|
|
RDebugInfo *linux_info (RDebug *dbg, const char *arg);
|
|
RList *linux_thread_list (int pid, RList *list);
|
|
int linux_reg_read (RDebug *dbg, int type, ut8 *buf, int size);
|
|
int linux_reg_write (RDebug *dbg, int type, const ut8 *buf, int size);
|
|
RList *linux_desc_list (int pid);
|
|
int linux_handle_signals (RDebug *dbg);
|
|
const char *linux_reg_profile (RDebug *dbg);
|