/* * debug.c * pdb debug functionality for processes. */ #include #include #include #include #include #include #include #include "pdb_module.h" #include "pdb_debug.h" #define BWC_DEBUG 1 #define BWC_INT3 3 typedef struct bwcpoint /* break/watch/catch point */ { struct list_head list; memory_t address; u32 domain; u32 process; u8 old_value; /* old value for software bkpt */ u8 type; /* BWC_??? */ } bwcpoint_t, *bwcpoint_p; static bwcpoint_t bwcpoint_list; void pdb_initialize_bwcpoint (void) { memset((void *) &bwcpoint_list, 0, sizeof(bwcpoint_t)); INIT_LIST_HEAD(&bwcpoint_list.list); return; } int pdb_suspend (struct task_struct *target) { u32 rc = 0; force_sig(SIGSTOP, target); /* force_sig_specific ??? */ return rc; } int pdb_resume (struct task_struct *target) { int rc = 0; wake_up_process(target); return rc; } /* * from linux-2.6.11/arch/i386/kernel/ptrace.c::getreg() */ static unsigned long _pdb_get_register (struct task_struct *target, int reg) { unsigned long result = ~0UL; unsigned long offset; unsigned char *stack = 0L; switch (reg) { case LINUX_FS: result = target->thread.fs; break; case LINUX_GS: result = target->thread.gs; break; case LINUX_DS: case LINUX_ES: case LINUX_SS: case LINUX_CS: result = 0xffff; /* fall through */ default: if (reg > LINUX_GS) reg -= 2; offset = reg * sizeof(long); offset -= sizeof(struct pt_regs); stack = (unsigned char *)target->thread.esp0; stack += offset; result &= *((int *)stack); } return result; } /* * from linux-2.6.11/arch/i386/kernel/ptrace.c::putreg() */ static void _pdb_set_register (struct task_struct *target, int reg, unsigned long val) { unsigned long offset; unsigned char *stack; unsigned long value = val; switch (reg) { case LINUX_FS: target->thread.fs = value; return; case LINUX_GS: target->thread.gs = value; return; case LINUX_DS: case LINUX_ES: value &= 0xffff; break; case LINUX_SS: case LINUX_CS: value &= 0xffff; break; case LINUX_EFL: break; } if (reg > LINUX_GS) reg -= 2; offset = reg * sizeof(long); offset -= sizeof(struct pt_regs); stack = (unsigned char *)target->thread.esp0; stack += offset; *(unsigned long *) stack = value; return; } int pdb_read_registers (struct task_struct *target, pdb_op_rd_regs_p op) { int rc = 0; op->reg[ 0] = _pdb_get_register(target, LINUX_EAX); op->reg[ 1] = _pdb_get_register(target, LINUX_ECX); op->reg[ 2] = _pdb_get_register(target, LINUX_EDX); op->reg[ 3] = _pdb_get_register(target, LINUX_EBX); op->reg[ 4] = _pdb_get_register(target, LINUX_ESP); op->reg[ 5] = _pdb_get_register(target, LINUX_EBP); op->reg[ 6] = _pdb_get_register(target, LINUX_ESI); op->reg[ 7] = _pdb_get_register(target, LINUX_EDI); op->reg[ 8] = _pdb_get_register(target, LINUX_EIP); op->reg[ 9] = _pdb_get_register(target, LINUX_EFL); op->reg[10] = _pdb_get_register(target, LINUX_CS); op->reg[11] = _pdb_get_register(target, LINUX_SS); op->reg[12] = _pdb_get_register(target, LINUX_DS); op->reg[13] = _pdb_get_register(target, LINUX_ES); op->reg[14] = _pdb_get_register(target, LINUX_FS); op->reg[15] = _pdb_get_register(target, LINUX_GS); return rc; } int pdb_write_register (struct task_struct *target, pdb_op_wr_reg_p op) { int rc = 0; _pdb_set_register(target, op->reg, op->value); return rc; } int pdb_access_memory (struct task_struct *target, unsigned long address, void *buffer, int length, int write) { int rc = 0; access_process_vm(target, address, buffer, length, write); return rc; } int pdb_continue (struct task_struct *target) { int rc = 0; unsigned long eflags; eflags = _pdb_get_register(target, LINUX_EFL); eflags &= ~X86_EFLAGS_TF; _pdb_set_register(target, LINUX_EFL, eflags); wake_up_process(target); return rc; } int pdb_step (struct task_struct *target) { int rc = 0; unsigned long eflags; bwcpoint_p bkpt; eflags = _pdb_get_register(target, LINUX_EFL); eflags |= X86_EFLAGS_TF; _pdb_set_register(target, LINUX_EFL, eflags); bkpt = kmalloc(sizeof(bwcpoint_t), GFP_KERNEL); if ( bkpt == NULL ) { printk("error: unable to allocation memory\n"); return -1; } bkpt->process = target->pid; bkpt->address = 0; bkpt->type = BWC_DEBUG; list_add(&bkpt->list, &bwcpoint_list.list); wake_up_process(target); return rc; } int pdb_insert_memory_breakpoint (struct task_struct *target, memory_t address, u32 length) { int rc = 0; bwcpoint_p bkpt; u8 breakpoint_opcode = 0xcc; printk("insert breakpoint %d:%lx len: %d\n", target->pid, address, length); bkpt = kmalloc(sizeof(bwcpoint_t), GFP_KERNEL); if ( bkpt == NULL ) { printk("error: unable to allocation memory\n"); return -1; } if ( length != 1 ) { printk("error: breakpoint length should be 1\n"); kfree(bkpt); return -1; } bkpt->process = target->pid; bkpt->address = address; bkpt->type = BWC_INT3; pdb_access_memory(target, address, &bkpt->old_value, 1, 0); pdb_access_memory(target, address, &breakpoint_opcode, 1, 1); list_add(&bkpt->list, &bwcpoint_list.list); printk("breakpoint_set %d:%lx OLD: 0x%x\n", target->pid, address, bkpt->old_value); return rc; } int pdb_remove_memory_breakpoint (struct task_struct *target, memory_t address, u32 length) { int rc = 0; bwcpoint_p bkpt = NULL; printk ("remove breakpoint %d:%lx\n", target->pid, address); struct list_head *entry; list_for_each(entry, &bwcpoint_list.list) { bkpt = list_entry(entry, bwcpoint_t, list); if ( target->pid == bkpt->process && address == bkpt->address && bkpt->type == BWC_INT3 ) break; } if (bkpt == &bwcpoint_list || bkpt == NULL) { printk ("error: no breakpoint found\n"); return -1; } list_del(&bkpt->list); pdb_access_memory(target, address, &bkpt->old_value, 1, 1); kfree(bkpt); return rc; } /***************************************************************/ int pdb_exceptions_notify (struct notifier_block *self, unsigned long val, void *data) { struct die_args *args = (struct die_args *)data; switch (val) { case DIE_DEBUG: if (pdb_debug_fn(args->regs, args->trapnr, args->err)) return NOTIFY_STOP; break; case DIE_TRAP: if (args->trapnr == 3 && pdb_int3_fn(args->regs, args->err)) return NOTIFY_STOP; break; case DIE_INT3: /* without kprobes, we should never see DIE_INT3 */ case DIE_GPF: case DIE_PAGE_FAULT: default: break; } return NOTIFY_DONE; } int pdb_debug_fn (struct pt_regs *regs, long error_code, unsigned int condition) { pdb_response_t resp; bwcpoint_p bkpt = NULL; struct list_head *entry; list_for_each(entry, &bwcpoint_list.list) { bkpt = list_entry(entry, bwcpoint_t, list); if ( current->pid == bkpt->process && bkpt->type == BWC_DEBUG ) break; } if (bkpt == &bwcpoint_list || bkpt == NULL) { printk("not my debug 0x%x 0x%lx\n", current->pid, regs->eip); return 0; } list_del(&bkpt->list); pdb_suspend(current); printk("(pdb) debug pid: %d, eip: 0x%08lx\n", current->pid, regs->eip); regs->eflags &= ~X86_EFLAGS_TF; set_tsk_thread_flag(current, TIF_SINGLESTEP); resp.operation = PDB_OPCODE_STEP; resp.process = current->pid; resp.status = PDB_RESPONSE_OKAY; pdb_send_response(&resp); return 1; } int pdb_int3_fn (struct pt_regs *regs, long error_code) { pdb_response_t resp; bwcpoint_p bkpt = NULL; struct list_head *entry; list_for_each(entry, &bwcpoint_list.list) { bkpt = list_entry(entry, bwcpoint_t, list); if ( current->pid == bkpt->process && regs->eip == bkpt->address && bkpt->type == BWC_INT3 ) break; } if (bkpt == &bwcpoint_list || bkpt == NULL) { printk("not my int3 bkpt 0x%x 0x%lx\n", current->pid, regs->eip); return 0; } printk("(pdb) int3 pid: %d, eip: 0x%08lx\n", current->pid, regs->eip); pdb_suspend(current); resp.operation = PDB_OPCODE_CONTINUE; resp.process = current->pid; resp.status = PDB_RESPONSE_OKAY; pdb_send_response(&resp); return 1; } /* * Local variables: * mode: C * c-set-style: "BSD" * c-basic-offset: 4 * tab-width: 4 * indent-tabs-mode: nil * End: */