/* * pervasive debugger * www.cl.cam.ac.uk/netos/pdb * * alex ho * 2004 * university of cambridge computer laboratory * * code adapted originally from kgdb, nemesis, & gdbserver */ #include #include #include #include #include #include /* [un]map_domain_mem */ #include #include #include #include #undef PDB_DEBUG_TRACE #ifdef PDB_DEBUG_TRACE #define TRC(_x) _x #else #define TRC(_x) #endif #define DEBUG_EXCEPTION 0x01 #define BREAKPT_EXCEPTION 0x03 #define PDB_LIVE_EXCEPTION 0x58 #define KEYPRESS_EXCEPTION 0x88 #define BUFMAX 400 static const char hexchars[] = "0123456789abcdef"; static int remote_debug; #define PDB_BUFMAX 1024 static char pdb_in_buffer[PDB_BUFMAX]; static char pdb_out_buffer[PDB_BUFMAX]; static char pdb_buffer[PDB_BUFMAX]; static int pdb_in_buffer_ptr; static unsigned char pdb_in_checksum; static unsigned char pdb_xmit_checksum; struct pdb_context pdb_ctx; int pdb_continue_thread = 0; int pdb_general_thread = 0; void pdb_put_packet (unsigned char *buffer, int ack); void pdb_bkpt_check (u_char *buffer, int length, unsigned long cr3, unsigned long addr); int pdb_initialized = 0; int pdb_page_fault_possible = 0; int pdb_page_fault_scratch = 0; /* just a handy variable */ int pdb_page_fault = 0; static int pdb_serhnd = -1; static int pdb_stepping = 0; int pdb_system_call = 0; unsigned char pdb_system_call_enter_instr = 0; /* original enter instr */ unsigned char pdb_system_call_leave_instr = 0; /* original next instr */ unsigned long pdb_system_call_next_addr = 0; /* instr after int 0x80 */ unsigned long pdb_system_call_eflags_addr = 0; /* saved eflags on stack */ static inline void pdb_put_char(unsigned char c) { serial_putc(pdb_serhnd, c); } static inline unsigned char pdb_get_char(void) { return serial_getc(pdb_serhnd); } int get_char (char *addr) { return *addr; } void set_char (char *addr, int val) { *addr = val; } void pdb_process_query (char *ptr) { if (strcmp(ptr, "C") == 0) { /* empty string */ } else if (strcmp(ptr, "fThreadInfo") == 0) { #ifdef PDB_PAST struct task_struct *p; u_long flags; #endif /* PDB_PAST */ int buf_idx = 0; pdb_out_buffer[buf_idx++] = 'l'; pdb_out_buffer[buf_idx++] = 0; #ifdef PDB_PAST switch (pdb_level) { case PDB_LVL_XEN: /* return a list of domains */ { int count = 0; read_lock_irqsave (&tasklist_lock, flags); pdb_out_buffer[buf_idx++] = 'm'; for_each_domain ( p ) { domid_t domain = p->domain + PDB_ID_OFFSET; if (count > 0) { pdb_out_buffer[buf_idx++] = ','; } if (domain > 15) { pdb_out_buffer[buf_idx++] = hexchars[domain >> 4]; } pdb_out_buffer[buf_idx++] = hexchars[domain % 16]; count++; } pdb_out_buffer[buf_idx++] = 0; read_unlock_irqrestore(&tasklist_lock, flags); break; } case PDB_LVL_GUESTOS: /* return a list of processes */ { int foobar[20]; int loop, total; /* this cr3 is wrong! */ total = pdb_linux_process_list(pdb_ctx[pdb_level].info_cr3, foobar, 20); pdb_out_buffer[buf_idx++] = 'm'; pdb_out_buffer[buf_idx++] = '1'; /* 1 is to go back */ for (loop = 0; loop < total; loop++) { int pid = foobar[loop] + PDB_ID_OFFSET; pdb_out_buffer[buf_idx++] = ','; if (pid > 15) { pdb_out_buffer[buf_idx++] = hexchars[pid >> 4]; } pdb_out_buffer[buf_idx++] = hexchars[pid % 16]; } pdb_out_buffer[buf_idx++] = 0; break; } case PDB_LVL_PROCESS: /* hmmm... */ { pdb_out_buffer[buf_idx++] = 'm'; pdb_out_buffer[buf_idx++] = '1'; /* 1 is to go back */ break; } default: break; } #endif /* PDB_PAST */ } else if (strcmp(ptr, "sThreadInfo") == 0) { int buf_idx = 0; pdb_out_buffer[buf_idx++] = 'l'; pdb_out_buffer[buf_idx++] = 0; } else if (strncmp(ptr, "ThreadExtraInfo,", 16) == 0) { int thread = 0; char *message = "foobar ?"; ptr += 16; if (hexToInt (&ptr, &thread)) { mem2hex (message, pdb_out_buffer, strlen(message) + 1); } #ifdef PDB_PAST int thread = 0; char message[16]; struct task_struct *p; p = find_domain_by_id(pdb_ctx[pdb_level].info); strncpy (message, p->name, 16); put_task_struct(p); ptr += 16; if (hexToInt (&ptr, &thread)) { mem2hex ((char *)message, pdb_out_buffer, strlen(message) + 1); } #endif /* PDB_PAST */ #ifdef PDB_FUTURE { char string[task_struct_comm_length]; string[0] = 0; pdb_linux_process_details (cr3, pid, string); printk (" (%s)", string); } #endif /* PDB_FUTURE*/ } else if (strcmp(ptr, "Offsets") == 0) { /* empty string */ } else if (strncmp(ptr, "Symbol", 6) == 0) { strcpy (pdb_out_buffer, "OK"); } else { printk("pdb: error, unknown query [%s]\n", ptr); } } void pdb_x86_to_gdb_regs (char *buffer, struct pt_regs *regs) { int idx = 0; mem2hex ((char *)®s->eax, &buffer[idx], sizeof(regs->eax)); idx += sizeof(regs->eax) * 2; mem2hex ((char *)®s->ecx, &buffer[idx], sizeof(regs->ecx)); idx += sizeof(regs->ecx) * 2; mem2hex ((char *)®s->edx, &buffer[idx], sizeof(regs->edx)); idx += sizeof(regs->edx) * 2; mem2hex ((char *)®s->ebx, &buffer[idx], sizeof(regs->ebx)); idx += sizeof(regs->ebx) * 2; mem2hex ((char *)®s->esp, &buffer[idx], sizeof(regs->esp)); idx += sizeof(regs->esp) * 2; mem2hex ((char *)®s->ebp, &buffer[idx], sizeof(regs->ebp)); idx += sizeof(regs->ebp) * 2; mem2hex ((char *)®s->esi, &buffer[idx], sizeof(regs->esi)); idx += sizeof(regs->esi) * 2; mem2hex ((char *)®s->edi, &buffer[idx], sizeof(regs->edi)); idx += sizeof(regs->edi) * 2; mem2hex ((char *)®s->eip, &buffer[idx], sizeof(regs->eip)); idx += sizeof(regs->eip) * 2; mem2hex ((char *)®s->eflags, &buffer[idx], sizeof(regs->eflags)); idx += sizeof(regs->eflags) * 2; mem2hex ((char *)®s->xcs, &buffer[idx], sizeof(regs->xcs)); idx += sizeof(regs->xcs) * 2; mem2hex ((char *)®s->xss, &buffer[idx], sizeof(regs->xss)); idx += sizeof(regs->xss) * 2; mem2hex ((char *)®s->xds, &buffer[idx], sizeof(regs->xds)); idx += sizeof(regs->xds) * 2; mem2hex ((char *)®s->xes, &buffer[idx], sizeof(regs->xes)); idx += sizeof(regs->xes) * 2; mem2hex ((char *)®s->xfs, &buffer[idx], sizeof(regs->xfs)); idx += sizeof(regs->xfs) * 2; mem2hex ((char *)®s->xgs, &buffer[idx], sizeof(regs->xgs)); } /* at this point we allow any register to be changed, caveat emptor */ void pdb_gdb_to_x86_regs (struct pt_regs *regs, char *buffer) { hex2mem(buffer, (char *)®s->eax, sizeof(regs->eax)); buffer += sizeof(regs->eax) * 2; hex2mem(buffer, (char *)®s->ecx, sizeof(regs->ecx)); buffer += sizeof(regs->ecx) * 2; hex2mem(buffer, (char *)®s->edx, sizeof(regs->edx)); buffer += sizeof(regs->edx) * 2; hex2mem(buffer, (char *)®s->ebx, sizeof(regs->ebx)); buffer += sizeof(regs->ebx) * 2; hex2mem(buffer, (char *)®s->esp, sizeof(regs->esp)); buffer += sizeof(regs->esp) * 2; hex2mem(buffer, (char *)®s->ebp, sizeof(regs->ebp)); buffer += sizeof(regs->ebp) * 2; hex2mem(buffer, (char *)®s->esi, sizeof(regs->esi)); buffer += sizeof(regs->esi) * 2; hex2mem(buffer, (char *)®s->edi, sizeof(regs->edi)); buffer += sizeof(regs->edi) * 2; hex2mem(buffer, (char *)®s->eip, sizeof(regs->eip)); buffer += sizeof(regs->eip) * 2; hex2mem(buffer, (char *)®s->eflags, sizeof(regs->eflags)); buffer += sizeof(regs->eflags) * 2; hex2mem(buffer, (char *)®s->xcs, sizeof(regs->xcs)); buffer += sizeof(regs->xcs) * 2; hex2mem(buffer, (char *)®s->xss, sizeof(regs->xss)); buffer += sizeof(regs->xss) * 2; hex2mem(buffer, (char *)®s->xds, sizeof(regs->xds)); buffer += sizeof(regs->xds) * 2; hex2mem(buffer, (char *)®s->xes, sizeof(regs->xes)); buffer += sizeof(regs->xes) * 2; hex2mem(buffer, (char *)®s->xfs, sizeof(regs->xfs)); buffer += sizeof(regs->xfs) * 2; hex2mem(buffer, (char *)®s->xgs, sizeof(regs->xgs)); } int pdb_process_command (char *ptr, struct pt_regs *regs, unsigned long cr3, int sigval) { int length; unsigned long addr; int ack = 1; /* wait for ack in pdb_put_packet */ int go = 0; TRC(printf("pdb: [%s]\n", ptr)); pdb_out_buffer[0] = 0; if (pdb_ctx.valid == 1) { if (pdb_ctx.domain == -1) /* pdb context: xen */ { struct task_struct *p; p = &idle0_task; if (p->mm.shadow_mode) pdb_ctx.ptbr = pagetable_val(p->mm.shadow_table); else pdb_ctx.ptbr = pagetable_val(p->mm.pagetable); } else if (pdb_ctx.process == -1) /* pdb context: guest os */ { struct task_struct *p; if (pdb_ctx.domain == -2) { p = find_last_domain(); } else { p = find_domain_by_id(pdb_ctx.domain); } if (p == NULL) { printk ("pdb error: unknown domain [0x%x]\n", pdb_ctx.domain); strcpy (pdb_out_buffer, "E01"); pdb_ctx.domain = -1; goto exit; } if (p->mm.shadow_mode) pdb_ctx.ptbr = pagetable_val(p->mm.shadow_table); else pdb_ctx.ptbr = pagetable_val(p->mm.pagetable); put_task_struct(p); } else /* pdb context: process */ { struct task_struct *p; unsigned long domain_ptbr; p = find_domain_by_id(pdb_ctx.domain); if (p == NULL) { printk ("pdb error: unknown domain [0x%x][0x%x]\n", pdb_ctx.domain, pdb_ctx.process); strcpy (pdb_out_buffer, "E01"); pdb_ctx.domain = -1; goto exit; } if (p->mm.shadow_mode) domain_ptbr = pagetable_val(p->mm.shadow_table); else domain_ptbr = pagetable_val(p->mm.pagetable); put_task_struct(p); pdb_ctx.ptbr = domain_ptbr; /*pdb_ctx.ptbr=pdb_linux_pid_ptbr(domain_ptbr, pdb_ctx.process);*/ } pdb_ctx.valid = 0; TRC(printk ("pdb change context (dom:%d, proc:%d) now 0x%lx\n", pdb_ctx.domain, pdb_ctx.process, pdb_ctx.ptbr)); } switch (*ptr++) { case '?': pdb_out_buffer[0] = 'S'; pdb_out_buffer[1] = hexchars[sigval >> 4]; pdb_out_buffer[2] = hexchars[sigval % 16]; pdb_out_buffer[3] = 0; break; case 'S': /* step with signal */ case 's': /* step */ { if ( pdb_system_call_eflags_addr != 0 ) { unsigned long eflags; char eflags_buf[sizeof(eflags)*2]; /* STUPID STUPID STUPID */ pdb_linux_get_values((u_char*)&eflags, sizeof(eflags), pdb_system_call_eflags_addr, pdb_ctx.process, pdb_ctx.ptbr); eflags |= X86_EFLAGS_TF; mem2hex ((u_char *)&eflags, eflags_buf, sizeof(eflags)); pdb_linux_set_values(eflags_buf, sizeof(eflags), pdb_system_call_eflags_addr, pdb_ctx.process, pdb_ctx.ptbr); } regs->eflags |= X86_EFLAGS_TF; pdb_stepping = 1; return 1; /* not reached */ } case 'C': /* continue with signal */ case 'c': /* continue */ { if ( pdb_system_call_eflags_addr != 0 ) { unsigned long eflags; char eflags_buf[sizeof(eflags)*2]; /* STUPID STUPID STUPID */ pdb_linux_get_values((u_char*)&eflags, sizeof(eflags), pdb_system_call_eflags_addr, pdb_ctx.process, pdb_ctx.ptbr); eflags &= ~X86_EFLAGS_TF; mem2hex ((u_char *)&eflags, eflags_buf, sizeof(eflags)); pdb_linux_set_values(eflags_buf, sizeof(eflags), pdb_system_call_eflags_addr, pdb_ctx.process, pdb_ctx.ptbr); } regs->eflags &= ~X86_EFLAGS_TF; return 1; /* jump out before replying to gdb */ /* not reached */ } case 'd': remote_debug = !(remote_debug); /* toggle debug flag */ break; case 'D': /* detach */ return go; /* not reached */ case 'g': /* return the value of the CPU registers */ { pdb_x86_to_gdb_regs (pdb_out_buffer, regs); break; } case 'G': /* set the value of the CPU registers - return OK */ { pdb_gdb_to_x86_regs (regs, ptr); break; } case 'H': { int thread; char *next = &ptr[1]; if (hexToInt (&next, &thread)) { if (*ptr == 'c') { pdb_continue_thread = thread; } else if (*ptr == 'g') { pdb_general_thread = thread; } else { printk ("pdb error: unknown set thread command %c (%d)\n", *ptr, thread); strcpy (pdb_out_buffer, "E00"); break; } } strcpy (pdb_out_buffer, "OK"); break; } case 'k': /* kill request */ { strcpy (pdb_out_buffer, "OK"); /* ack for fun */ printk ("don't kill bill...\n"); ack = 0; break; } case 'q': { pdb_process_query(ptr); break; } /* mAA..AA,LLLL Read LLLL bytes at address AA..AA */ case 'm': { /* TRY TO READ %x,%x. IF SUCCEED, SET PTR = 0 */ if (hexToInt (&ptr, (int *)&addr)) if (*(ptr++) == ',') if (hexToInt (&ptr, &length)) { ptr = 0; pdb_page_fault_possible = 1; pdb_page_fault = 0; if (addr >= PAGE_OFFSET) { mem2hex ((char *) addr, pdb_out_buffer, length); } else if (pdb_ctx.process != -1) { pdb_linux_get_values(pdb_buffer, length, addr, pdb_ctx.process, pdb_ctx.ptbr); mem2hex (pdb_buffer, pdb_out_buffer, length); } else { pdb_get_values (pdb_buffer, length, pdb_ctx.ptbr, addr); mem2hex (pdb_buffer, pdb_out_buffer, length); } pdb_page_fault_possible = 0; if (pdb_page_fault) { strcpy (pdb_out_buffer, "E03"); } } if (ptr) { strcpy (pdb_out_buffer, "E01"); } break; } /* MAA..AA,LLLL: Write LLLL bytes at address AA.AA return OK */ case 'M': { /* TRY TO READ '%x,%x:'. IF SUCCEED, SET PTR = 0 */ if (hexToInt (&ptr, (int *)&addr)) if (*(ptr++) == ',') if (hexToInt (&ptr, &length)) if (*(ptr++) == ':') { pdb_page_fault_possible = 1; pdb_page_fault = 0; if (addr >= PAGE_OFFSET) { hex2mem (ptr, (char *)addr, length); pdb_bkpt_check(ptr, length, pdb_ctx.ptbr, addr); } else if (pdb_ctx.process != -1) { pdb_linux_set_values(ptr, length, addr, pdb_ctx.process, pdb_ctx.ptbr); pdb_bkpt_check(ptr, length, pdb_ctx.ptbr, addr); } else { pdb_set_values (ptr, length, pdb_ctx.ptbr, addr); pdb_bkpt_check(ptr, length, pdb_ctx.ptbr, addr); } pdb_page_fault_possible = 0; if (pdb_page_fault) { strcpy (pdb_out_buffer, "E03"); } else { strcpy (pdb_out_buffer, "OK"); } ptr = 0; } if (ptr) { strcpy (pdb_out_buffer, "E02"); } break; } case 'T': { int id; if (hexToInt (&ptr, &id)) { strcpy (pdb_out_buffer, "E00"); #ifdef PDB_PAST switch (pdb_level) /* previous level */ { case PDB_LVL_XEN: { struct task_struct *p; id -= PDB_ID_OFFSET; if ( (p = find_domain_by_id(id)) == NULL) strcpy (pdb_out_buffer, "E00"); else strcpy (pdb_out_buffer, "OK"); put_task_struct(p); pdb_level = PDB_LVL_GUESTOS; pdb_ctx[pdb_level].ctrl = id; pdb_ctx[pdb_level].info = id; break; } case PDB_LVL_GUESTOS: { if (pdb_level == -1) { pdb_level = PDB_LVL_XEN; } else { pdb_level = PDB_LVL_PROCESS; pdb_ctx[pdb_level].ctrl = id; pdb_ctx[pdb_level].info = id; } break; } case PDB_LVL_PROCESS: { if (pdb_level == -1) { pdb_level = PDB_LVL_GUESTOS; } break; } default: { printk ("pdb internal error: invalid level [%d]\n", pdb_level); } } #endif /* PDB_PAST */ } break; } } exit: /* reply to the request */ pdb_put_packet (pdb_out_buffer, ack); return go; } /* * process an input character from the serial line. * * return "1" if the character is a gdb debug string * (and hence shouldn't be further processed). */ int pdb_debug_state = 0; /* small parser state machine */ int pdb_serial_input(u_char c, struct pt_regs *regs) { int out = 1; int loop, count; unsigned long cr3; __asm__ __volatile__ ("movl %%cr3,%0" : "=r" (cr3) : ); switch (pdb_debug_state) { case 0: /* not currently processing debug string */ if ( c == '$' ) /* start token */ { pdb_debug_state = 1; pdb_in_buffer_ptr = 0; pdb_in_checksum = 0; pdb_xmit_checksum = 0; } else { out = 0; } break; case 1: /* saw '$' */ if ( c == '#' ) /* checksum token */ { pdb_debug_state = 2; pdb_in_buffer[pdb_in_buffer_ptr] = 0; } else { pdb_in_checksum += c; pdb_in_buffer[pdb_in_buffer_ptr++] = c; } break; case 2: /* 1st checksum digit */ pdb_xmit_checksum = hex(c) << 4; pdb_debug_state = 3; break; case 3: /* 2nd checksum digit */ pdb_xmit_checksum += hex(c); if (pdb_in_checksum != pdb_xmit_checksum) { pdb_put_char('-'); /* checksum failure */ printk ("checksum failure [%s.%02x.%02x]\n", pdb_in_buffer, pdb_in_checksum, pdb_xmit_checksum); } else { pdb_put_char('+'); /* checksum okay */ if ( pdb_in_buffer_ptr > 1 && pdb_in_buffer[2] == ':' ) { pdb_put_char(pdb_in_buffer[0]); pdb_put_char(pdb_in_buffer[1]); /* remove sequence chars from buffer */ count = strlen(pdb_in_buffer); for (loop = 3; loop < count; loop++) pdb_in_buffer[loop - 3] = pdb_in_buffer[loop]; } pdb_process_command (pdb_in_buffer, regs, cr3, PDB_LIVE_EXCEPTION); } pdb_debug_state = 0; break; } return out; } int hex(char ch) { if ((ch >= 'a') && (ch <= 'f')) return (ch-'a'+10); if ((ch >= '0') && (ch <= '9')) return (ch-'0'); if ((ch >= 'A') && (ch <= 'F')) return (ch-'A'+10); return (-1); } /* convert the memory pointed to by mem into hex, placing result in buf */ /* return a pointer to the last char put in buf (null) */ char * mem2hex (mem, buf, count) char *mem; char *buf; int count; { int i; unsigned char ch; for (i = 0; i < count; i++) { ch = get_char (mem++); *buf++ = hexchars[ch >> 4]; *buf++ = hexchars[ch % 16]; } *buf = 0; return (buf); } /* convert the hex array pointed to by buf into binary to be placed in mem */ /* return a pointer to the character AFTER the last byte written */ char * hex2mem (buf, mem, count) char *buf; char *mem; int count; { int i; unsigned char ch; for (i = 0; i < count; i++) { ch = hex (*buf++) << 4; ch = ch + hex (*buf++); set_char (mem++, ch); } return (mem); } int hexToInt (char **ptr, int *intValue) { int numChars = 0; int hexValue; int negative = 0; *intValue = 0; if (**ptr == '-') { negative = 1; numChars++; (*ptr)++; } while (**ptr) { hexValue = hex (**ptr); if (hexValue >= 0) { *intValue = (*intValue << 4) | hexValue; numChars++; } else break; (*ptr)++; } if ( negative ) *intValue *= -1; return (numChars); } /***********************************************************************/ /***********************************************************************/ /* * Add a breakpoint to the list of known breakpoints. * For now there should only be two or three breakpoints so * we use a simple linked list. In the future, maybe a red-black tree? */ struct pdb_breakpoint breakpoints; void pdb_bkpt_add (unsigned long cr3, unsigned long address) { struct pdb_breakpoint *bkpt = kmalloc(sizeof(*bkpt), GFP_KERNEL); bkpt->cr3 = cr3; bkpt->address = address; list_add(&bkpt->list, &breakpoints.list); } /* * Check to see of the breakpoint is in the list of known breakpoints * Return 1 if it has been set, NULL otherwise. */ struct pdb_breakpoint* pdb_bkpt_search (unsigned long cr3, unsigned long address) { struct list_head *list_entry; struct pdb_breakpoint *bkpt; list_for_each(list_entry, &breakpoints.list) { bkpt = list_entry(list_entry, struct pdb_breakpoint, list); if ( bkpt->cr3 == cr3 && bkpt->address == address ) return bkpt; } return NULL; } /* * Remove a breakpoint to the list of known breakpoints. * Return 1 if the element was not found, otherwise 0. */ int pdb_bkpt_remove (unsigned long cr3, unsigned long address) { struct list_head *list_entry; struct pdb_breakpoint *bkpt; list_for_each(list_entry, &breakpoints.list) { bkpt = list_entry(list_entry, struct pdb_breakpoint, list); if ( bkpt->cr3 == cr3 && bkpt->address == address ) { list_del(&bkpt->list); kfree(bkpt); return 0; } } return 1; } /* * Check to see if a memory write is really gdb setting a breakpoint */ void pdb_bkpt_check (u_char *buffer, int length, unsigned long cr3, unsigned long addr) { if (length == 1 && buffer[0] == 'c' && buffer[1] == 'c') { /* inserting a new breakpoint */ pdb_bkpt_add(cr3, addr); TRC(printk("pdb breakpoint detected at 0x%lx:0x%lx\n", cr3, addr)); } else if ( pdb_bkpt_remove(cr3, addr) == 0 ) { /* removing a breakpoint */ TRC(printk("pdb breakpoint cleared at 0x%lx:0x%lx\n", cr3, addr)); } } /***********************************************************************/ int pdb_change_values(u_char *buffer, int length, unsigned long cr3, unsigned long addr, int rw); int pdb_change_values_one_page(u_char *buffer, int length, unsigned long cr3, unsigned long addr, int rw); #define __PDB_GET_VAL 1 #define __PDB_SET_VAL 2 /* * Set memory in a domain's address space * Set "length" bytes at "address" from "domain" to the values in "buffer". * Return the number of bytes set, 0 if there was a problem. */ int pdb_set_values(u_char *buffer, int length, unsigned long cr3, unsigned long addr) { int count = pdb_change_values(buffer, length, cr3, addr, __PDB_SET_VAL); return count; } /* * Read memory from a domain's address space. * Fetch "length" bytes at "address" from "domain" into "buffer". * Return the number of bytes read, 0 if there was a problem. */ int pdb_get_values(u_char *buffer, int length, unsigned long cr3, unsigned long addr) { return pdb_change_values(buffer, length, cr3, addr, __PDB_GET_VAL); } /* * Read or write memory in an address space */ int pdb_change_values(u_char *buffer, int length, unsigned long cr3, unsigned long addr, int rw) { int remaining; /* number of bytes to touch past this page */ int bytes = 0; while ( (remaining = (addr + length - 1) - (addr | (PAGE_SIZE - 1))) > 0) { bytes += pdb_change_values_one_page(buffer, length - remaining, cr3, addr, rw); buffer = buffer + (2 * (length - remaining)); length = remaining; addr = (addr | (PAGE_SIZE - 1)) + 1; } bytes += pdb_change_values_one_page(buffer, length, cr3, addr, rw); return bytes; } /* * Change memory in a process' address space in one page * Read or write "length" bytes at "address" into/from "buffer" * from the virtual address space referenced by "cr3". * Return the number of bytes read, 0 if there was a problem. */ int pdb_change_values_one_page(u_char *buffer, int length, unsigned long cr3, unsigned long addr, int rw) { l2_pgentry_t* l2_table = NULL; l1_pgentry_t* l1_table = NULL; u_char *page; int bytes = 0; l2_table = map_domain_mem(cr3); l2_table += l2_table_offset(addr); if (!(l2_pgentry_val(*l2_table) & _PAGE_PRESENT)) { if (pdb_page_fault_possible == 1) { pdb_page_fault = 1; TRC(printk("pdb: L2 error (0x%lx)\n", addr)); } else { struct task_struct *p = find_domain_by_id(0); printk ("pdb error: cr3: 0x%lx dom0cr3: 0x%lx\n", cr3, p->mm.shadow_mode ? pagetable_val(p->mm.shadow_table) : pagetable_val(p->mm.pagetable)); put_task_struct(p); printk ("pdb error: L2:0x%p (0x%lx)\n", l2_table, l2_pgentry_val(*l2_table)); } goto exit2; } if (l2_pgentry_val(*l2_table) & _PAGE_PSE) { #define PSE_PAGE_SHIFT L2_PAGETABLE_SHIFT #define PSE_PAGE_SIZE (1UL << PSE_PAGE_SHIFT) #define PSE_PAGE_MASK (~(PSE_PAGE_SIZE-1)) #define L1_PAGE_BITS ( (ENTRIES_PER_L1_PAGETABLE - 1) << L1_PAGETABLE_SHIFT ) #define pse_pgentry_to_phys(_x) (l2_pgentry_val(_x) & PSE_PAGE_MASK) page = map_domain_mem(pse_pgentry_to_phys(*l2_table) + /* 10 bits */ (addr & L1_PAGE_BITS)); /* 10 bits */ page += addr & (PAGE_SIZE - 1); /* 12 bits */ } else { l1_table = map_domain_mem(l2_pgentry_to_phys(*l2_table)); l1_table += l1_table_offset(addr); if (!(l1_pgentry_val(*l1_table) & _PAGE_PRESENT)) { if (pdb_page_fault_possible == 1) { pdb_page_fault = 1; TRC(printk ("pdb: L1 error (0x%lx)\n", addr)); } else { printk ("L2:0x%p (0x%lx) L1:0x%p (0x%lx)\n", l2_table, l2_pgentry_val(*l2_table), l1_table, l1_pgentry_val(*l1_table)); } goto exit1; } page = map_domain_mem(l1_pgentry_to_phys(*l1_table)); page += addr & (PAGE_SIZE - 1); } switch (rw) { case __PDB_GET_VAL: /* read */ memcpy (buffer, page, length); bytes = length; break; case __PDB_SET_VAL: /* write */ hex2mem (buffer, page, length); bytes = length; break; default: /* unknown */ printk ("error: unknown RW flag: %d\n", rw); return 0; } unmap_domain_mem((void *)page); exit1: if (l1_table != NULL) unmap_domain_mem((void *)l1_table); exit2: unmap_domain_mem((void *)l2_table); return bytes; } /***********************************************************************/ void breakpoint(void); /* send the packet in buffer. */ void pdb_put_packet (unsigned char *buffer, int ack) { unsigned char checksum; int count; char ch; /* $# */ /* do */ { pdb_put_char ('$'); checksum = 0; count = 0; while ((ch = buffer[count])) { pdb_put_char (ch); checksum += ch; count += 1; } pdb_put_char('#'); pdb_put_char(hexchars[checksum >> 4]); pdb_put_char(hexchars[checksum % 16]); } if (ack) { if ((ch = pdb_get_char()) != '+') { printk(" pdb return error: %c 0x%x [%s]\n", ch, ch, buffer); } } } void pdb_get_packet(char *buffer) { int count; char ch; unsigned char checksum = 0; unsigned char xmitcsum = 0; do { while ((ch = pdb_get_char()) != '$'); count = 0; checksum = 0; while (count < BUFMAX) { ch = pdb_get_char(); if (ch == '#') break; checksum += ch; buffer[count] = ch; count++; } buffer[count] = 0; if (ch == '#') { xmitcsum = hex(pdb_get_char()) << 4; xmitcsum += hex(pdb_get_char()); if (xmitcsum == checksum) { pdb_put_char('+'); if (buffer[2] == ':') { printk ("pdb: obsolete gdb packet (sequence ID)\n"); } } else { pdb_put_char('-'); } } } while (checksum != xmitcsum); return; } /* * process a machine interrupt or exception * Return 1 if pdb is not interested in the exception; it should * be propagated to the guest os. */ int pdb_handle_exception(int exceptionVector, struct pt_regs *xen_regs) { int signal = 0; struct pdb_breakpoint* bkpt; int watchdog_save; unsigned long cr3; __asm__ __volatile__ ("movl %%cr3,%0" : "=r" (cr3) : ); /* If the exception is an int3 from user space then pdb is only interested if it re-wrote an instruction set the breakpoint. This occurs when leaving a system call from a domain. */ if ( exceptionVector == 3 && (xen_regs->xcs & 3) == 3 && xen_regs->eip != pdb_system_call_next_addr + 1) { TRC(printf("pdb: user bkpt (0x%x) at 0x%x:0x%lx:0x%lx\n", exceptionVector, xen_regs->xcs & 3, cr3, xen_regs->eip)); return 1; } /* * If PDB didn't set the breakpoint, is not single stepping, * is not entering a system call in a domain, * the user didn't press the magic debug key, * then we don't handle the exception. */ bkpt = pdb_bkpt_search(cr3, xen_regs->eip - 1); if ( (bkpt == NULL) && !pdb_stepping && !pdb_system_call && xen_regs->eip != pdb_system_call_next_addr + 1 && (exceptionVector != KEYPRESS_EXCEPTION) && xen_regs->eip < 0xc0000000) /* Linux-specific for now! */ { TRC(printf("pdb: user bkpt (0x%x) at 0x%lx:0x%lx\n", exceptionVector, cr3, xen_regs->eip)); return 1; } printk("pdb_handle_exception [0x%x][0x%lx:0x%lx]\n", exceptionVector, cr3, xen_regs->eip); if ( pdb_stepping ) { /* Stepped one instruction; now return to normal execution. */ xen_regs->eflags &= ~X86_EFLAGS_TF; pdb_stepping = 0; } if ( pdb_system_call ) { pdb_system_call = 0; pdb_linux_syscall_exit_bkpt (xen_regs, &pdb_ctx); /* we don't have a saved breakpoint so we need to rewind eip */ xen_regs->eip--; /* if ther user doesn't care about breaking when entering a system call then we'll just ignore the exception */ if ( (pdb_ctx.system_call & 0x01) == 0 ) { return 0; } } if ( exceptionVector == BREAKPT_EXCEPTION && bkpt != NULL) { /* Executed Int3: replace breakpoint byte with real program byte. */ xen_regs->eip--; } /* returning to user space after a system call */ if ( xen_regs->eip == pdb_system_call_next_addr + 1) { u_char instr[2]; /* REALLY REALLY REALLY STUPID */ mem2hex (&pdb_system_call_leave_instr, instr, sizeof(instr)); pdb_linux_set_values (instr, 1, pdb_system_call_next_addr, pdb_ctx.process, pdb_ctx.ptbr); pdb_system_call_next_addr = 0; pdb_system_call_leave_instr = 0; /* manually rewind eip */ xen_regs->eip--; /* if the user doesn't care about breaking when returning to user space after a system call then we'll just ignore the exception */ if ( (pdb_ctx.system_call & 0x02) == 0 ) { return 0; } } /* Generate a signal for GDB. */ switch ( exceptionVector ) { case KEYPRESS_EXCEPTION: signal = 2; break; /* SIGINT */ case DEBUG_EXCEPTION: signal = 5; break; /* SIGTRAP */ case BREAKPT_EXCEPTION: signal = 5; break; /* SIGTRAP */ default: printk("pdb: can't generate signal for unknown exception vector %d\n", exceptionVector); break; } pdb_out_buffer[0] = 'S'; pdb_out_buffer[1] = hexchars[signal >> 4]; pdb_out_buffer[2] = hexchars[signal % 16]; pdb_out_buffer[3] = 0; pdb_put_packet(pdb_out_buffer, 1); watchdog_save = watchdog_on; watchdog_on = 0; do { pdb_out_buffer[0] = 0; pdb_get_packet(pdb_in_buffer); } while ( pdb_process_command(pdb_in_buffer, xen_regs, cr3, signal) == 0 ); watchdog_on = watchdog_save; return 0; } void pdb_key_pressed(u_char key, void *dev_id, struct pt_regs *regs) { pdb_handle_exception(KEYPRESS_EXCEPTION, regs); return; } void initialize_pdb() { extern char opt_pdb[]; /* Certain state must be initialised even when PDB will not be used. */ memset((void *) &breakpoints, 0, sizeof(breakpoints)); INIT_LIST_HEAD(&breakpoints.list); pdb_stepping = 0; if ( strcmp(opt_pdb, "none") == 0 ) return; if ( (pdb_serhnd = parse_serial_handle(opt_pdb)) == -1 ) { printk("error: failed to initialize PDB on port %s\n", opt_pdb); return; } pdb_ctx.valid = 1; pdb_ctx.domain = -1; pdb_ctx.process = -1; pdb_ctx.system_call = 0; pdb_ctx.ptbr = 0; printk("pdb: pervasive debugger (%s) www.cl.cam.ac.uk/netos/pdb\n", opt_pdb); /* Acknowledge any spurious GDB packets. */ pdb_put_char('+'); add_key_handler('D', pdb_key_pressed, "enter pervasive debugger"); pdb_initialized = 1; } void breakpoint(void) { if ( pdb_initialized ) asm("int $3"); }