/* Copyright (C) 1999 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1998 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1997 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1996 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */ /* exception handling support by Pierre Muller */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern char __libdbg_ident_string[]; static char *id = __libdbg_ident_string; #define MEM_HANDLE_COUNT 256 #define DESCRIPTOR_COUNT 128 #define DOS_DESCRIPTOR_COUNT 128 #define DPMI_EXCEPTION_COUNT 18 #define DS_SIZE_COUNT 128 #define USE_FSEXT #define CLOSE_UNREGISTERED_FILES #define SAVE_FP /* debug splitted into 3 parts */ /* #define DEBUG_ALL_DBGCOM */ #ifdef DEBUG_ALL_DBGCOM /* general debug infos */ #define DEBUG_DBGCOM /* files open/close infos */ #define DEBUG_DBGCOM_FILES /* exceptions infos */ #define DEBUG_EXCEPTIONS #endif /* DEBUG_ALL_DBGCOM */ long mem_handles[MEM_HANDLE_COUNT]; unsigned short descriptors[DESCRIPTOR_COUNT]; unsigned short dos_descriptors[DOS_DESCRIPTOR_COUNT]; /* these all need to be static because ss can be different from ds in dbgsig !! */ static int excep_stack[1000]; static int errcode,cs,eflags,eip,ss,esp,ret_cs,ret_eip; static int *cur_pos; static int child_exception_level; ExternalDebuggerInfo edi; TSS a_tss; static jmp_buf jumper; static int my_ds,my_cs,app_cs,app_exit_cs,app_ds; static unsigned int app_ds_size[DS_SIZE_COUNT]; static int app_ds_index = 0; static jmp_buf load_state; static int nset, breakhandle[4]; static __dpmi_paddr our_handler[DPMI_EXCEPTION_COUNT],app_handler[DPMI_EXCEPTION_COUNT]; #ifdef DEBUG_EXCEPTIONS typedef struct { short excp_cs; int excp_eip; short excp_nb; } texcp_info; static texcp_info excp_info[20]; static int excp_index; static int excp_count; static int redir_excp_count; #endif NPX npx; NPX debugger_npx; /* ------------------------------------------------------------------------- */ /* Store the contents of the NPX in the global variable `npx'. */ #define FPU_PRESENT 0x04 void save_npx (void) { #ifdef SAVE_FP int i; if ((__dpmi_get_coprocessor_status() & FPU_PRESENT) == 0) return; asm ("movb $0x0b, %%al outb %%al, $0xa0 inb $0xa0, %%al testb $0x20, %%al jz 1f xorb %%al, %%al outb %%al, $0xf0 movb $0x20, %%al outb %%al, $0xa0 outb %%al, $0x20 1: fnsave %0 fwait" : "=m" (npx) : /* No input */ : "%eax"); npx.top = (npx.status & NPX_TOP_MASK) >> NPX_TOP_SHIFT; npx.in_mmx_mode = (npx.top == 0); for (i=0;i<8;i++) { /* tag is a array of 8 2 bits that contain info about FPU registers st(0) is register(top) and st(1) is register (top+1) ... */ npx.st_valid[i] = ((npx.tag >> (((npx.top+i) & 7) << 1)) & 3) != 3; if (npx.st_valid[i]) { npx.st[i]= * (long double *) &(npx.reg[i]); /* On my Pentium II the two last bytes are set to 0xff on MMX instructions, but on the Intel docs it was only specified that the exponent part has all bits set ! Moreover this are only set if the specific mmx register is used */ if (npx.reg[i].exponent!=0x7fff) if ((npx.tag >> ((((npx.top+i) & 7) << 1)) & 3) == 2) npx.in_mmx_mode=0; } else { npx.st[i]=0; npx.in_mmx_mode=0; } } if (npx.in_mmx_mode) for (i=0;i<8;i++) { npx.mmx[i]= * (long double *) &(npx.reg[i]); } /* Restore debugger's FPU state. */ asm("frstor %0" : :"m" (debugger_npx)); #endif } /* ------------------------------------------------------------------------- */ /* Reload the contents of the NPX from the global variable `npx'. */ void load_npx (void) { if ((__dpmi_get_coprocessor_status() & FPU_PRESENT) == 0) return; /* Save debugger's FPU state. */ asm ("fnsave %0" : :"m" (debugger_npx)); #if 0 /* This code is disabled because npx.mmx[] and npx.st[] are supposed to be read-only, they exist to make it easier for a debugger to display the FP registers either as long doubles or as 64-bit MMX registers. If the debugger wants to *change* the values, it should always change in npx.reg[]. Otherwise, we will need a whole slew of flags to know which one of the different views should be used to restore child's FPU state, or else the debugger will be forced to handle the extra burden of copying the same value into each one of the three views of the same registers. */ if (npx.in_mmx_mode) { int i; /* change reg to mmx */ for (i=0;i<8;i++) if (npx.mmx[i]!= * (long double *) &(npx.reg[i])) { memcpy(&(npx.reg[i]),&(npx.mmx[i]),10); } } else { int i; /* change reg to st */ for (i=0;i<8;i++) { if ((npx.st_valid[i]) && (npx.st[i]!= * (long double *) &(npx.reg[i]))) { memcpy(&(npx.reg[i]),&(npx.st[i]),10); } } } #endif asm ("frstor %0" : "=m" (npx)); } static int _DPMIsetBreak(unsigned short sizetype, unsigned vaddr) { int handle; asm volatile( "\n\ movw %1,%%dx \n\ movl %2,%%ecx \n\ movl %%ecx,%%ebx \n\ shrl $16,%%ebx \n\ movw $0x0b00,%%ax \n\ int $0x31 \n\ jnc 3f \n\ xorl %%ebx,%%ebx \n\ decl %%ebx \n\ jmp 1f \n\ 3: movzwl %%bx,%%ebx \n\ 1: movl %%ebx,%0 \n\ " : "=g" (handle) /* outputs */ : "g" (sizetype), "g" (vaddr) /* inputs */ : "ax", "bx", "cx", "dx" /* regs used */ ); return handle; } static int _DPMIcancelBreak(int handle) { unsigned state; asm volatile( "\n\ movl %1,%%ebx \n\ movw $0x0b02,%%ax \n\ int $0x31 \n\ jnc 2f \n\ xorl %%eax,%%eax \n\ 2: andl $1,%%eax \n\ pushl %%eax \n\ movw $0x0b01,%%ax \n\ int $0x31 \n\ popl %0 \n\ " : "=g" (state) /* outputs */ : "g" (handle) /* inputs */ : "ax", "bx" /* regs used */ ); return state; } /* Can't be static because called in asm below; -O3 inlines if static */ void _set_break_DPMI(void); void _set_break_DPMI(void) { int i; unsigned extract; unsigned short sizetype; unsigned long vbase; if(__dpmi_get_segment_base_address(app_ds, &vbase) == -1) return; extract = edi.dr[7] >> 16; nset = 0; edi.app_base = vbase; for(i=0;i<4;i++) if( (edi.dr[7] >> (i*2))&3 ) { /* enabled? */ sizetype = (extract >> (i*4)) & 3; /* extract the type */ if(sizetype == 3) sizetype = 2; /* convert for DPMI brain damage */ sizetype = (sizetype << 8) + ((extract >> (i*4+2)) & 3) + 1; /* & size */ breakhandle[i] = _DPMIsetBreak(sizetype, edi.dr[i]+vbase); if(breakhandle[i] == -1) printf("Error allocating DPMI breakpoint at address 0x%08lx\n",edi.dr[i]); else { if(breakhandle[i] == ((edi.dr[i]+vbase) >> 16)) /* Win 2K bug */ breakhandle[i] = nset; nset++; } } else breakhandle[i] = -1; return; } /* Can't be static because called in asm below; -O3 inlines if static */ void _clear_break_DPMI(void); void _clear_break_DPMI(void) { int i,bt; if(!nset) { edi.dr[6] = 0; return; } bt = 0; for(i=3;i>=0;i--) { bt = bt << 1; /* Shift for next bit */ if(breakhandle[i] != -1) bt |= _DPMIcancelBreak(breakhandle[i]); /* Set low bit if active */ } edi.dr[6] = bt; } static __dpmi_paddr old_i31,old_i21,user_i31,user_i21; static int user_int_set = 0; static __dpmi_paddr my_i9,user_i9,my_i8,user_i8; static void (*oldNOFP)(int); static void dbgsig(int); static void hook_dpmi(void) { int i; __dpmi_paddr new_int; extern void i21_hook(void),i31_hook(void),__dbgcom_kbd_hdlr(void); __dpmi_get_protected_mode_interrupt_vector(0x21, &old_i21); __dpmi_get_protected_mode_interrupt_vector(0x31, &old_i31); /* Save our current interrupt vectors for the keyboard and the timer */ __dpmi_get_protected_mode_interrupt_vector(0x09, &my_i9); __dpmi_get_protected_mode_interrupt_vector(0x08, &my_i8); for (i=0;i__eip; cs = load_state->__cs; esp = load_state->__esp; ss = load_state->__ss; eflags = load_state->__eflags; /* reset the debug trace bit */ /* we don't want to step inside the exception_table code */ load_state->__eflags &= 0xfffffeffU; errcode = load_state->__sigmask; load_state->__eip=app_handler[signum].offset32; load_state->__cs=app_handler[signum].selector; /* use our own exception stack */ child_exception_level++; cur_pos -= 8; if (cur_pos < &excep_stack[0]) { /* We have a problem here, but this should never happen. */ fprintf (stderr, "Level of nesting in debugger exceptions too high: %d\n", child_exception_level); exit(-1); } load_state->__ss = my_ds; load_state->__esp= (int) cur_pos; /* where to return */ ret_cs = my_cs; if (return_to_debuggee) ret_eip = (int) &dbgcom_exception_return_to_debuggee; else ret_eip = (int) &dbgcom_exception_return_to_here; cur_pos[0] = ret_eip; cur_pos[1] = ret_cs; cur_pos[2] = errcode; cur_pos[3] = eip; cur_pos[4] = cs; cur_pos[5] = eflags; cur_pos[6] = esp; cur_pos[7] = ss; longjmp(load_state, load_state->__eax); } static void dbgsig(int sig) { unsigned int ds_size; int signum = __djgpp_exception_state->__signum; asm ("movl _app_ds,%%eax lsl %%eax,%%eax movl %%eax,%0" : "=g" (ds_size) ); /* correct ds limit here */ if ((ds_size==0xfff) && (signum==0xc || signum==0xd)) { /* If forced_address is known then signum contains the fake exception value (PM) */ if (forced_address_known) { movedata(app_cs,forced_address,my_ds,(int) &signum,4); } else signum=0x1B; /* else we default to SIGINT */ if (app_ds_index>1) { /* set the limit correctly */ __dpmi_set_segment_limit(app_ds,app_ds_size[app_ds_index-2]); } /* let app restore the ds selector */ if (!setjmp(here)) { *load_state = *__djgpp_exception_state; /* exception was in other process */ load_state->__eip = here->__eip; load_state->__esp = here->__esp; load_state->__cs = here->__cs; load_state->__ss = here->__ss; /* do use ds exception */ load_state->__signum = 0xc; /* longjmp returns eax value */ load_state->__eax = 1; call_app_exception(__djgpp_exception_state->__signum, RETURN_TO_HERE); } __djgpp_exception_state->__signum=signum; } #ifdef DEBUG_EXCEPTIONS excp_info[excp_index].excp_eip=__djgpp_exception_state->__eip; excp_info[excp_index].excp_cs=__djgpp_exception_state->__cs; excp_info[excp_index].excp_nb=signum; excp_index++; excp_count++; if (excp_index==20) excp_index=0; #endif if(__djgpp_exception_state->__cs == app_cs) /* || sig == SIGTRAP) */ { *load_state = *__djgpp_exception_state; /* exception was in other process */ longjmp(jumper, 1); } else { extern int invalid_sel_addr(short sel, unsigned a, unsigned len, char for_write); if ((signum__cs = a_tss.tss_cs; load_state->__ss = a_tss.tss_ss; load_state->__ds = a_tss.tss_ds; load_state->__es = a_tss.tss_es; load_state->__fs = a_tss.tss_fs; load_state->__gs = a_tss.tss_gs; load_state->__eip = a_tss.tss_eip; load_state->__eflags = a_tss.tss_eflags; load_state->__eax = a_tss.tss_eax; load_state->__ebx = a_tss.tss_ebx; load_state->__ecx = a_tss.tss_ecx; load_state->__edx = a_tss.tss_edx; load_state->__esp = a_tss.tss_esp; load_state->__ebp = a_tss.tss_ebp; load_state->__esi = a_tss.tss_esi; load_state->__edi = a_tss.tss_edi; if(!setjmp(jumper)){ extern int invalid_sel_addr(short sel, unsigned a, unsigned len, char for_write); /* jump to tss */ _set_break_DPMI(); hook_dpmi(); if (a_tss.tss_trap == 0xffff) { /* We were asked by the debugger to deliver exception to the child when it is resumed. */ if (a_tss.tss_irqn >= DPMI_EXCEPTION_COUNT && forced_address_known) { unsigned app_ds_size = __dpmi_get_segment_limit (app_ds); if (app_ds_size > 0xfff) { /* This is a fake exception (SIGINT, SIGALRM, etc.). We need to poke the `forced' variable in the child with the fake exception number. */ _farpokel (app_ds, forced_address, a_tss.tss_irqn); /* We also need to save the child's DS limit in the child's ds_limit variable, because the child's fake exception handling code will try to restore the DS limit from the value of ds_limit. ds_limit is defined in exceptn.S at offset -4 relative to the forced variable (PM). */ _farpokel (app_ds, forced_address - 4, app_ds_size); } a_tss.tss_irqn = 0x0d; /* simulate a GPF in the child */ } if ((a_tss.tss_irqn < DPMI_EXCEPTION_COUNT) && (app_handler[a_tss.tss_irqn].offset32) && (app_handler[a_tss.tss_irqn].selector) && !invalid_sel_addr(app_handler[a_tss.tss_irqn].selector, app_handler[a_tss.tss_irqn].offset32,1,0)) { call_app_exception(a_tss.tss_irqn, RETURN_TO_DEBUGGEE); } else { a_tss.tss_irqn = 0; longjmp(load_state, load_state->__eax); } } else longjmp(load_state, load_state->__eax); /* we never return here, execption routine will longjump */ } /* exception routine: save state, copy to tss, return */ a_tss.tss_cs = load_state->__cs; a_tss.tss_ss = load_state->__ss; a_tss.tss_ds = load_state->__ds; a_tss.tss_es = load_state->__es; a_tss.tss_fs = load_state->__fs; a_tss.tss_gs = load_state->__gs; a_tss.tss_eip = load_state->__eip; a_tss.tss_esp = load_state->__esp; a_tss.tss_eflags = load_state->__eflags; a_tss.tss_eax = load_state->__eax; a_tss.tss_ebx = load_state->__ebx; a_tss.tss_ecx = load_state->__ecx; a_tss.tss_edx = load_state->__edx; a_tss.tss_esi = load_state->__esi; a_tss.tss_edi = load_state->__edi; a_tss.tss_ebp = load_state->__ebp; a_tss.tss_irqn = load_state->__signum; a_tss.tss_error = load_state->__sigmask; a_tss.tss_trap = 0; unhook_dpmi(); _clear_break_DPMI(); } static int invalid_addr(unsigned a, unsigned len) { /* Here we assume expand up writable code. We could check the rights to be sure, but that's a waste unless *_child routines fixed to know about different selectors. */ unsigned limit; limit = __dpmi_get_segment_limit(app_ds); if(4096 <= a /* First page is used for NULL pointer detection. */ && a <= limit /* To guard against limit < len. */ && a - 1 <= limit - len /* To guard against limit <= a + len - 1. */ ) return 0; /* printf("Invalid access to child, address %#x length %#x limit: %#x\n", a, len, limit); if (can_longjmp) longjmp(debugger_jmpbuf, 1); */ return 1; } int read_child(unsigned child_addr, void *buf, unsigned len) { if (invalid_addr(child_addr, len)) return 1; movedata(app_ds, child_addr, my_ds, (int)buf, len); return 0; } int write_child(unsigned child_addr, void *buf, unsigned len) { if (invalid_addr(child_addr, len)) return 1; movedata(my_ds, (int)buf, app_ds, child_addr, len); return 0; } int invalid_sel_addr(short sel, unsigned a, unsigned len, char for_write) { /* Here we assume expand up writable code. We could check the rights to be sure, but that's a waste unless *_child routines fixed to know about different selectors. */ unsigned limit; char read_allowed = 0; char write_allowed = 0; asm(" movw %2,%%ax verr %%ax jnz .Ldoes_not_has_read_right movb $1,%0 .Ldoes_not_has_read_right: verw %%ax jnz .Ldoes_not_has_write_right movb $1,%1 .Ldoes_not_has_write_right: " : "=g" (read_allowed), "=g" (write_allowed) : "g" (sel) ); if (for_write) { if (!write_allowed) return 1; } else if (!read_allowed) return 1; limit = __dpmi_get_segment_limit(sel); /* some selectors don't have zero page protection like the protected interrupt stack */ if(/*a >= 4096 && */ (a+len-1) <= limit) return 0; /* printf("Invalid access to child, address %#x length %#x limit: %#x\n", a, len, limit); if (can_longjmp) longjmp(debugger_jmpbuf, 1); */ return 1; } int read_sel_addr(unsigned child_addr, void *buf, unsigned len, unsigned sel) { /* first clear memory */ memcpy(buf,0,len); if (invalid_sel_addr(sel, child_addr, len, 0)) return 1; movedata(sel, child_addr, my_ds, (int)buf, len); return 0; } int write_sel_addr(unsigned sel, unsigned child_addr, void *buf, unsigned len) { if (invalid_sel_addr(sel, child_addr, len, 1)) return 1; movedata(my_ds, (int)buf, sel, child_addr, len); return 0; } static _GO32_StubInfo si; static void (*oldTRAP)(int); static void (*oldSEGV)(int); static void (*oldFPE)(int); static void (*oldINT)(int); static void (*oldQUIT)(int); static void (*oldILL)(int); void edi_init(jmp_buf start_state) { int i; my_ds = 0; asm("mov %%ds,%0" : "=g" (my_ds) ); my_cs = 0; asm("mov %%cs,%0" : "=g" (my_cs) ); for (i=0;i__cs; a_tss.tss_ss = load_state->__ss; a_tss.tss_ds = load_state->__ds; a_tss.tss_es = load_state->__es; a_tss.tss_fs = load_state->__fs; a_tss.tss_gs = load_state->__gs; a_tss.tss_eip = load_state->__eip; a_tss.tss_esp = load_state->__esp; a_tss.tss_eflags = load_state->__eflags; a_tss.tss_trap = 0; app_ds = a_tss.tss_ds; app_cs = a_tss.tss_cs; if (__dpmi_get_segment_base_address(app_ds, &edi.app_base) == -1) abort (); /* Save debugger's FPU state. */ asm ("fnsave %0" : :"m" (debugger_npx)); /* Fill the debuggee's FPU state with the default values, taken from the equivalent of FNINIT performed by FNSAVE above. */ memset(&npx,0,sizeof(npx)); save_npx(); /* Save all the changed signal handlers */ oldTRAP = signal(SIGTRAP, dbgsig); oldSEGV = signal(SIGSEGV, dbgsig); oldFPE = signal(SIGFPE, dbgsig); oldINT = signal(SIGINT, dbgsig); oldQUIT = signal(SIGQUIT, dbgsig); oldILL = signal(SIGILL, dbgsig); movedata(a_tss.tss_fs,0,my_ds,(unsigned)&si,sizeof(si)); memset(mem_handles,0,sizeof(mem_handles)); mem_handles[0] = si.memory_handle; memset(descriptors,0,sizeof(descriptors)); descriptors[0] = si.cs_selector; descriptors[1] = si.ds_selector; descriptors[2] = app_ds; descriptors[3] = app_cs; app_exit_cs=si.cs_selector; memset(dos_descriptors,0,sizeof(dos_descriptors)); dos_descriptors[0] = _farpeekw(si.psp_selector,0x2c); dos_descriptors[1] = si.psp_selector; /* set initial value of cur_pos */ cur_pos = &excep_stack[1000-40]; /* pattern fill exception stack for debugging */ memset(&excep_stack,0xAB,sizeof(excep_stack)); child_exception_level = 0; } static void close_handles(void); /* Forward declaration */ void cleanup_client(void) { int i; /* restore __djgpp_app_DS for Ctrl-C !! */ __djgpp_app_DS = __djgpp_our_DS; #ifdef DEBUG_EXCEPTIONS fprintf(stderr,"excp_count = %d\n",excp_count); fprintf(stderr,"redir_excp_count = %d\n",redir_excp_count); fprintf(stderr,"excp_index = %d\n",excp_index); fprintf(stderr,"app_cs %04x\tapp_ds %04x\n",app_cs,app_ds); fprintf(stderr,"my_cs %04x\tmy_ds %04x\n",my_cs,my_ds); for (i=0;i %d\n",filename,retval); #endif in_dbg_fsext--; if (retval != -1) { handles[retval] = retval; __FSEXT_set_function(retval,dbg_fsext); } break; case __FSEXT_open: filename = va_arg(_args,const char *); oflag = va_arg(_args,int); in_dbg_fsext++; retval = _open(filename,oflag); #ifdef DEBUG_DBGCOM_FILES fprintf(stderr,"_open(%s) => %d\n",filename,retval); #endif in_dbg_fsext--; if (retval != -1) { handles[retval] = retval; __FSEXT_set_function(retval,dbg_fsext); } break; case __FSEXT_close: handle = va_arg(_args,int); in_dbg_fsext++; #ifdef DEBUG_DBGCOM_FILES fprintf(stderr,"_close(%d)\n",handle); #endif retval = _close(handle); in_dbg_fsext--; if (retval == 0) { handles[handle] = 0xff; __FSEXT_set_function(handle,NULL); } break; } *_rv = retval; return 1; } /* With attribute constructor to be called automaticaly before main */ static void __attribute__((__constructor__)) _init_dbg_fsext(void) { __dpmi_regs r; int psp_la; int jft_ofs; int jft_count; /* Get our PSP address. */ r.x.ax = 0x6200; __dpmi_int (0x21, &r); psp_la = ( (int)r.x.bx ) << 4; /* Get the offset of the JFT table by (seg << 4) + offset */ jft_ofs = (_farpeekw(_dos_ds, psp_la + 0x36) << 4) + _farpeekw(_dos_ds, psp_la + 0x34); /* Number of used entries in the JFT table */ jft_count = _farpeekw(_dos_ds, psp_la + 0x32); /* Add the handler for opening/creating files */ __FSEXT_add_open_handler(dbg_fsext); /* Initialize all the handles to 0xff */ memset(handles,0xff,sizeof(handles)); /* Get a copy of all already opened handles */ movedata(_dos_ds,jft_ofs,_my_ds(),(int)handles,jft_count); /* enable the fsext function */ in_dbg_fsext = 0; } #endif /* def USE_FSEXT */