diff options
Diffstat (limited to 'tools/debugger/pdb/linux-2.6-module/debug.c')
-rw-r--r-- | tools/debugger/pdb/linux-2.6-module/debug.c | 535 |
1 files changed, 484 insertions, 51 deletions
diff --git a/tools/debugger/pdb/linux-2.6-module/debug.c b/tools/debugger/pdb/linux-2.6-module/debug.c index 983e02dccc..ae1064ebad 100644 --- a/tools/debugger/pdb/linux-2.6-module/debug.c +++ b/tools/debugger/pdb/linux-2.6-module/debug.c @@ -9,33 +9,143 @@ #include <asm-i386/kdebug.h> #include <asm-xen/asm-i386/processor.h> #include <asm-xen/asm-i386/ptrace.h> +#include <asm-xen/asm-i386/tlbflush.h> #include <asm-xen/xen-public/xen.h> #include "pdb_module.h" #include "pdb_debug.h" -#define BWC_DEBUG 1 -#define BWC_INT3 3 + +static int pdb_debug_fn (struct pt_regs *regs, long error_code, + unsigned int condition); +static int pdb_int3_fn (struct pt_regs *regs, long error_code); +static int pdb_page_fault_fn (struct pt_regs *regs, long error_code, + unsigned int condition); + +/***********************************************************************/ + 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 */ + int length; + u8 type; /* BWC_??? */ + u8 mode; /* for BWC_PAGE, the current protection mode */ + u32 process; + u8 error; /* error occured when enabling: don't disable. */ + + /* original values */ + u8 orig_bkpt; /* single byte breakpoint */ + pte_t orig_pte; + + struct list_head watchpt_read_list; /* read watchpoints on this page */ + struct list_head watchpt_write_list; /* write */ + struct list_head watchpt_access_list; /* access */ + struct list_head watchpt_disabled_list; /* disabled */ + + struct bwcpoint *parent; /* watchpoint: bwc_watch (the page) */ + struct bwcpoint *watchpoint; /* bwc_watch_step: original watchpoint */ } bwcpoint_t, *bwcpoint_p; -static bwcpoint_t bwcpoint_list; +static struct list_head bwcpoint_list = LIST_HEAD_INIT(bwcpoint_list); + +#define _pdb_bwcpoint_alloc(_var) \ +{ \ + if ( (_var = kmalloc(sizeof(bwcpoint_t), GFP_KERNEL)) == NULL ) \ + printk("error: unable to allocate memory %d\n", __LINE__); \ + else { \ + memset(_var, 0, sizeof(bwcpoint_t)); \ + INIT_LIST_HEAD(&_var->watchpt_read_list); \ + INIT_LIST_HEAD(&_var->watchpt_write_list); \ + INIT_LIST_HEAD(&_var->watchpt_access_list); \ + INIT_LIST_HEAD(&_var->watchpt_disabled_list); \ + } \ +} + +/***********************************************************************/ + +static void _pdb_bwc_print_list (struct list_head *, char *, int); + +static void +_pdb_bwc_print (bwcpoint_p bwc, char *label, int level) +{ + printk("%s%03d 0x%08lx:0x%02x %c\n", label, bwc->type, + bwc->address, bwc->length, bwc->error ? 'e' : '-'); + + if ( !list_empty(&bwc->watchpt_read_list) ) + _pdb_bwc_print_list(&bwc->watchpt_read_list, "r", level); + if ( !list_empty(&bwc->watchpt_write_list) ) + _pdb_bwc_print_list(&bwc->watchpt_write_list, "w", level); + if ( !list_empty(&bwc->watchpt_access_list) ) + _pdb_bwc_print_list(&bwc->watchpt_access_list, "a", level); + if ( !list_empty(&bwc->watchpt_disabled_list) ) + _pdb_bwc_print_list(&bwc->watchpt_disabled_list, "d", level); +} + +static void +_pdb_bwc_print_list (struct list_head *bwc_list, char *label, int level) +{ + struct list_head *ptr; + int counter = 0; + + list_for_each(ptr, bwc_list) + { + bwcpoint_p bwc = list_entry(ptr, bwcpoint_t, list); + printk(" %s[%02d]%s ", level > 0 ? " " : "", counter++, + level > 0 ? "" : " "); + _pdb_bwc_print(bwc, label, level+1); + } + + if (counter == 0) + { + printk(" empty list\n"); + } +} void -pdb_initialize_bwcpoint (void) +pdb_bwc_print_list (void) +{ + _pdb_bwc_print_list(&bwcpoint_list, " ", 0); +} + +bwcpoint_p +pdb_search_watchpoint (u32 process, memory_t address) { - memset((void *) &bwcpoint_list, 0, sizeof(bwcpoint_t)); - INIT_LIST_HEAD(&bwcpoint_list.list); + bwcpoint_p bwc_watch = (bwcpoint_p) 0; + bwcpoint_p bwc_entry = (bwcpoint_p) 0; + struct list_head *ptr; - return; + list_for_each(ptr, &bwcpoint_list) /* find bwc page entry */ + { + bwc_watch = list_entry(ptr, bwcpoint_t, list); + if (bwc_watch->address == (address & PAGE_MASK)) break; + } + + if ( !bwc_watch ) + { + return (bwcpoint_p) 0; + } + +#define __pdb_search_watchpoint_list(__list) \ + list_for_each(ptr, (__list)) \ + { \ + bwc_entry = list_entry(ptr, bwcpoint_t, list); \ + if ( bwc_entry->process == process && \ + bwc_entry->address <= address && \ + bwc_entry->address + bwc_entry->length > address ) \ + return bwc_entry; \ + } + + __pdb_search_watchpoint_list(&bwc_watch->watchpt_read_list); + __pdb_search_watchpoint_list(&bwc_watch->watchpt_write_list); + __pdb_search_watchpoint_list(&bwc_watch->watchpt_access_list); + +#undef __pdb_search_watchpoint_list + + return (bwcpoint_p) 0; } +/*************************************************************/ int pdb_suspend (struct task_struct *target) @@ -137,6 +247,35 @@ _pdb_set_register (struct task_struct *target, int reg, unsigned long val) } int +pdb_read_register (struct task_struct *target, pdb_op_rd_reg_p op) +{ + int rc = 0; + + switch (op->reg) + { + case 0: op->value = _pdb_get_register(target, LINUX_EAX); break; + case 1: op->value = _pdb_get_register(target, LINUX_ECX); break; + case 2: op->value = _pdb_get_register(target, LINUX_EDX); break; + case 3: op->value = _pdb_get_register(target, LINUX_EBX); break; + case 4: op->value = _pdb_get_register(target, LINUX_ESP); break; + case 5: op->value = _pdb_get_register(target, LINUX_EBP); break; + case 6: op->value = _pdb_get_register(target, LINUX_ESI); break; + case 7: op->value = _pdb_get_register(target, LINUX_EDI); break; + case 8: op->value = _pdb_get_register(target, LINUX_EIP); break; + case 9: op->value = _pdb_get_register(target, LINUX_EFL); break; + + case 10: op->value = _pdb_get_register(target, LINUX_CS); break; + case 11: op->value = _pdb_get_register(target, LINUX_SS); break; + case 12: op->value = _pdb_get_register(target, LINUX_DS); break; + case 13: op->value = _pdb_get_register(target, LINUX_ES); break; + case 14: op->value = _pdb_get_register(target, LINUX_FS); break; + case 15: op->value = _pdb_get_register(target, LINUX_GS); break; + } + + return rc; +} + +int pdb_read_registers (struct task_struct *target, pdb_op_rd_regs_p op) { int rc = 0; @@ -209,18 +348,14 @@ pdb_step (struct task_struct *target) 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; - } + _pdb_bwcpoint_alloc(bkpt); + if ( bkpt == NULL ) return -1; bkpt->process = target->pid; bkpt->address = 0; bkpt->type = BWC_DEBUG; - list_add(&bkpt->list, &bwcpoint_list.list); + list_add_tail(&bkpt->list, &bwcpoint_list); wake_up_process(target); @@ -237,31 +372,27 @@ pdb_insert_memory_breakpoint (struct task_struct *target, 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; } + _pdb_bwcpoint_alloc(bkpt); + if ( bkpt == NULL ) 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); + pdb_access_memory(target, address, &bkpt->orig_bkpt, 1, PDB_MEM_READ); + pdb_access_memory(target, address, &breakpoint_opcode, 1, PDB_MEM_WRITE); - list_add(&bkpt->list, &bwcpoint_list.list); + list_add_tail(&bkpt->list, &bwcpoint_list); printk("breakpoint_set %d:%lx OLD: 0x%x\n", - target->pid, address, bkpt->old_value); + target->pid, address, bkpt->orig_bkpt); + pdb_bwc_print_list(); return rc; } @@ -276,7 +407,7 @@ pdb_remove_memory_breakpoint (struct task_struct *target, printk ("remove breakpoint %d:%lx\n", target->pid, address); struct list_head *entry; - list_for_each(entry, &bwcpoint_list.list) + list_for_each(entry, &bwcpoint_list) { bkpt = list_entry(entry, bwcpoint_t, list); if ( target->pid == bkpt->process && @@ -285,17 +416,223 @@ pdb_remove_memory_breakpoint (struct task_struct *target, break; } - if (bkpt == &bwcpoint_list || bkpt == NULL) + if (entry == &bwcpoint_list) { printk ("error: no breakpoint found\n"); return -1; } + pdb_access_memory(target, address, &bkpt->orig_bkpt, 1, PDB_MEM_WRITE); + list_del(&bkpt->list); + kfree(bkpt); - pdb_access_memory(target, address, &bkpt->old_value, 1, 1); + pdb_bwc_print_list(); - kfree(bkpt); + return rc; +} + +#define PDB_PTE_UPDATE 1 +#define PDB_PTE_RESTORE 2 + +int +pdb_change_pte (struct task_struct *target, bwcpoint_p bwc, int mode) +{ + int rc = 0; + pgd_t *pgd; + pud_t *pud; + pmd_t *pmd; + pte_t *ptep; + + pgd = pgd_offset(target->mm, bwc->address); + if (pgd_none(*pgd) || unlikely(pgd_bad(*pgd))) return -1; + + pud = pud_offset(pgd, bwc->address); + if (pud_none(*pud) || unlikely(pud_bad(*pud))) return -2; + + pmd = pmd_offset(pud, bwc->address); + if (pmd_none(*pmd) || unlikely(pmd_bad(*pmd))) return -3; + + ptep = pte_offset_map(pmd, bwc->address); + if (!ptep) return -4; + + switch ( mode ) + { + case PDB_PTE_UPDATE: /* added or removed a watchpoint. update pte. */ + { + pte_t new_pte; + + if ( pte_val(bwc->parent->orig_pte) == 0 ) /* new watchpoint page */ + { + bwc->parent->orig_pte = *ptep; + } + + new_pte = bwc->parent->orig_pte; + + if ( !list_empty(&bwc->parent->watchpt_read_list) || + !list_empty(&bwc->parent->watchpt_access_list) ) + { + new_pte = pte_rdprotect(new_pte); + } + + if ( !list_empty(&bwc->parent->watchpt_write_list) || + !list_empty(&bwc->parent->watchpt_access_list) ) + { + new_pte = pte_wrprotect(new_pte); + } + + if ( pte_val(new_pte) != pte_val(*ptep) ) + { + *ptep = new_pte; + flush_tlb_mm(target->mm); + } + break; + } + case PDB_PTE_RESTORE : /* suspend watchpoint by restoring original pte */ + { + *ptep = bwc->parent->orig_pte; + flush_tlb_mm(target->mm); + break; + } + default : + { + printk("(linux) unknown mode %d %d\n", mode, __LINE__); + break; + } + } + + pte_unmap(ptep); /* can i flush the tlb before pte_unmap? */ + + return rc; +} + +int +pdb_insert_watchpoint (struct task_struct *target, pdb_op_watchpt_p watchpt) +{ + int rc = 0; + + bwcpoint_p bwc_watch; + bwcpoint_p bwc_entry; + struct list_head *ptr; + unsigned long page = watchpt->address & PAGE_MASK; + struct list_head *watchpoint_list; + + printk("insert watchpoint: %d %x %x\n", + watchpt->type, watchpt->address, watchpt->length); + + list_for_each(ptr, &bwcpoint_list) /* find existing bwc page entry */ + { + bwc_watch = list_entry(ptr, bwcpoint_t, list); + + if (bwc_watch->address == page) goto got_bwc_watch; + } + + _pdb_bwcpoint_alloc(bwc_watch); /* create new bwc:watch */ + if ( bwc_watch == NULL ) return -1; + + bwc_watch->type = BWC_WATCH; + bwc_watch->process = target->pid; + bwc_watch->address = page; + + list_add_tail(&bwc_watch->list, &bwcpoint_list); + + got_bwc_watch: + + switch (watchpt->type) + { + case BWC_WATCH_READ: + watchpoint_list = &bwc_watch->watchpt_read_list; break; + case BWC_WATCH_WRITE: + watchpoint_list = &bwc_watch->watchpt_write_list; break; + case BWC_WATCH_ACCESS: + watchpoint_list = &bwc_watch->watchpt_access_list; break; + default: + printk("unknown type %d\n", watchpt->type); return -2; + } + + _pdb_bwcpoint_alloc(bwc_entry); /* create new bwc:entry */ + if ( bwc_entry == NULL ) return -1; + + bwc_entry->process = target->pid; + bwc_entry->address = watchpt->address; + bwc_entry->length = watchpt->length; + bwc_entry->type = watchpt->type; + bwc_entry->parent = bwc_watch; + + list_add_tail(&bwc_entry->list, watchpoint_list); + pdb_change_pte(target, bwc_entry, PDB_PTE_UPDATE); + + pdb_bwc_print_list(); + + return rc; +} + +int +pdb_remove_watchpoint (struct task_struct *target, pdb_op_watchpt_p watchpt) +{ + int rc = 0; + bwcpoint_p bwc_watch = (bwcpoint_p) NULL; + bwcpoint_p bwc_entry = (bwcpoint_p) NULL; + unsigned long page = watchpt->address & PAGE_MASK; + struct list_head *ptr; + struct list_head *watchpoint_list; + + printk("remove watchpoint: %d %x %x\n", + watchpt->type, watchpt->address, watchpt->length); + + list_for_each(ptr, &bwcpoint_list) /* find bwc page entry */ + { + bwc_watch = list_entry(ptr, bwcpoint_t, list); + if (bwc_watch->address == page) break; + } + + if ( !bwc_watch ) + { + printk("(linux) delete watchpoint: can't find bwc page 0x%08x\n", + watchpt->address); + return -1; + } + + switch (watchpt->type) + { + case BWC_WATCH_READ: + watchpoint_list = &bwc_watch->watchpt_read_list; break; + case BWC_WATCH_WRITE: + watchpoint_list = &bwc_watch->watchpt_write_list; break; + case BWC_WATCH_ACCESS: + watchpoint_list = &bwc_watch->watchpt_access_list; break; + default: + printk("unknown type %d\n", watchpt->type); return -2; + } + + list_for_each(ptr, watchpoint_list) /* find watchpoint */ + { + bwc_entry = list_entry(ptr, bwcpoint_t, list); + if ( bwc_entry->address == watchpt->address && + bwc_entry->length == watchpt->length ) break; + } + + if ( !bwc_entry ) /* or ptr == watchpoint_list */ + { + printk("(linux) delete watchpoint: can't find watchpoint 0x%08x\n", + watchpt->address); + return -1; + } + + list_del(&bwc_entry->list); + pdb_change_pte(target, bwc_entry, PDB_PTE_UPDATE); + kfree(bwc_entry); + + + if ( list_empty(&bwc_watch->watchpt_read_list) && + list_empty(&bwc_watch->watchpt_write_list) && + list_empty(&bwc_watch->watchpt_access_list) ) + { + list_del(&bwc_watch->list); + kfree(bwc_watch); + } + + pdb_bwc_print_list(); return rc; } @@ -312,16 +649,24 @@ pdb_exceptions_notify (struct notifier_block *self, unsigned long val, switch (val) { case DIE_DEBUG: - if (pdb_debug_fn(args->regs, args->trapnr, args->err)) + 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)) + 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: + if ( pdb_int3_fn(args->regs, args->err) ) + return NOTIFY_STOP; + break; case DIE_PAGE_FAULT: + if ( pdb_page_fault_fn(args->regs, args->trapnr, args->err) ) + return NOTIFY_STOP; + break; + case DIE_GPF: + printk("---------------GPF\n"); + break; default: break; } @@ -330,70 +675,110 @@ pdb_exceptions_notify (struct notifier_block *self, unsigned long val, } -int +static 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) + + printk("pdb_debug_fn\n"); + + list_for_each(entry, &bwcpoint_list) { bkpt = list_entry(entry, bwcpoint_t, list); if ( current->pid == bkpt->process && - bkpt->type == BWC_DEBUG ) + (bkpt->type == BWC_DEBUG || /* single step */ + bkpt->type == BWC_WATCH_STEP)) /* single step over watchpoint */ break; } - if (bkpt == &bwcpoint_list || bkpt == NULL) + if (entry == &bwcpoint_list) { 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); + printk("(pdb) %s pid: %d, eip: 0x%08lx\n", + bkpt->type == BWC_DEBUG ? "debug" : "watch-step", + current->pid, regs->eip); regs->eflags &= ~X86_EFLAGS_TF; set_tsk_thread_flag(current, TIF_SINGLESTEP); - resp.operation = PDB_OPCODE_STEP; + switch (bkpt->type) + { + case BWC_DEBUG: + resp.operation = PDB_OPCODE_STEP; + break; + case BWC_WATCH_STEP: + { + struct list_head *watchpoint_list; + bwcpoint_p watch_page = bkpt->watchpoint->parent; + + switch (bkpt->watchpoint->type) + { + case BWC_WATCH_READ: + watchpoint_list = &watch_page->watchpt_read_list; break; + case BWC_WATCH_WRITE: + watchpoint_list = &watch_page->watchpt_write_list; break; + case BWC_WATCH_ACCESS: + watchpoint_list = &watch_page->watchpt_access_list; break; + default: + printk("unknown type %d\n", bkpt->watchpoint->type); return 0; + } + + resp.operation = PDB_OPCODE_WATCHPOINT; + list_del_init(&bkpt->watchpoint->list); + list_add_tail(&bkpt->watchpoint->list, watchpoint_list); + pdb_change_pte(current, bkpt->watchpoint, PDB_PTE_UPDATE); + pdb_bwc_print_list(); + break; + } + default: + printk("unknown breakpoint type %d %d\n", __LINE__, bkpt->type); + return 0; + } + resp.process = current->pid; resp.status = PDB_RESPONSE_OKAY; pdb_send_response(&resp); + list_del(&bkpt->list); + kfree(bkpt); + return 1; } -int +static int pdb_int3_fn (struct pt_regs *regs, long error_code) { pdb_response_t resp; bwcpoint_p bkpt = NULL; + memory_t address = regs->eip - 1; struct list_head *entry; - list_for_each(entry, &bwcpoint_list.list) + list_for_each(entry, &bwcpoint_list) { bkpt = list_entry(entry, bwcpoint_t, list); if ( current->pid == bkpt->process && - regs->eip == bkpt->address && + address == bkpt->address && bkpt->type == BWC_INT3 ) break; } - if (bkpt == &bwcpoint_list || bkpt == NULL) + if (entry == &bwcpoint_list) { - printk("not my int3 bkpt 0x%x 0x%lx\n", current->pid, regs->eip); + printk("not my int3 bkpt 0x%x 0x%lx\n", current->pid, address); return 0; } - printk("(pdb) int3 pid: %d, eip: 0x%08lx\n", current->pid, regs->eip); + printk("(pdb) int3 pid: %d, eip: 0x%08lx\n", current->pid, address); pdb_suspend(current); @@ -406,6 +791,54 @@ pdb_int3_fn (struct pt_regs *regs, long error_code) return 1; } +static int +pdb_page_fault_fn (struct pt_regs *regs, long error_code, + unsigned int condition) +{ + unsigned long cr2; + unsigned long cr3; + bwcpoint_p bwc; + bwcpoint_p watchpt; + bwcpoint_p bkpt; + + __asm__ __volatile__ ("movl %%cr3,%0" : "=r" (cr3) : ); + __asm__ __volatile__ ("movl %%cr2,%0" : "=r" (cr2) : ); + + bwc = pdb_search_watchpoint(current->pid, cr2); + if ( !bwc ) + { + return 0; /* not mine */ + } + + printk("page_fault cr2:%08lx err:%lx eip:%08lx\n", + cr2, error_code, regs->eip); + + /* disable the watchpoint */ + watchpt = bwc->watchpoint; + list_del_init(&bwc->list); + list_add_tail(&bwc->list, &bwc->parent->watchpt_disabled_list); + pdb_change_pte(current, bwc, PDB_PTE_RESTORE); + + /* single step the faulting instruction */ + regs->eflags |= X86_EFLAGS_TF; + + /* create a bwcpoint entry so we know what to do once we regain control */ + _pdb_bwcpoint_alloc(bkpt); + if ( bkpt == NULL ) return -1; + + bkpt->process = current->pid; + bkpt->address = 0; + bkpt->type = BWC_WATCH_STEP; + bkpt->watchpoint = bwc; + + /* add to head so we see it first the next time we break */ + list_add(&bkpt->list, &bwcpoint_list); + + pdb_bwc_print_list(); + return 1; +} + + /* * Local variables: * mode: C |