This is the mail archive of the gdb-patches@sourceware.cygnus.com mailing list for the GDB project. See the GDB home page for more information.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

GDB 4.17 Patch for stack aligned i386 code


This patch adds support for debugging functions where the
stack has been aligned in addition to fixing several problems
with my earlier patch to support debugging frame pointerless
i386 code (version 2).

There are no regressions evident from running the gdb 4.17
testsuite on FreeBSD 3.0 and Solaris 2.5.1 with gcc 2.7.2.1,
egcs (from cvs on 1999-01-14), and egcs -momit-leaf-frame-pointer.

Notes:

  1) The patch for debugging frame pointerless i386 code (version 2)
     should be installed prior to this patch.

  2) The SVR4 changes to support sigtramp backtraces are untested.

  3) Platforms other than those tested may require some work in order
     to properly support sigtramp backtraces.  Start by looking at
     the BSD and Solaris definitions of:

       i386_sigcontext_addr
       SIGCONTEXT_FP_OFFSET
       SIGCONTEXT_SP_OFFSET

ChangeLog:

Wed Jan 20 22:14:03 EST 1999  John Wehle  (john@feith.com)

	* tm-i386.h (FRAME_CHAIN): Call i386_frame_chain for
	signal handler callers.
	(EXTRA_FRAME_INFO): Define stack_mask.
	(FRAME_ARGS_ADDRESS, FRAME_LOCALS_ADDRESS): Use it.
	* i386-tdep.c (i386_get_frame_setup): Set it.
	(i386_frame_find_saved_regs): Use it.
	(i386_init_extra_frame_info): Initialize it.
	(i386_frame_find_saved_regs): Don't accidently zero
	fi->fsr.regs[FP_REGNUM].
	(i386_sigcontext_addr): New function.
	(i386v4_sigtramp_saved_pc): Use it.
	(i386_analyze_prologue): Use it.
	(i386_analyze_prologue): Handle dummy frames.
	(i386_analyze_prologue): If the start of a function can't
	be located than assume the frame is in the frame pointer.
	(i386_frame_chain): If the caller's prologue can't be
	analyze then return the current frame as the frame.
	(i386_frame_chain): Check the current frame for FP_REGNUM.
	(i386_frame_chain): Handle chaining back from a signal
	handler caller to a function which uses the stack pointer
	for the frame pointer.
	(i386_frame_chain): Include the stack space occupied by
	FP_REGNUM when caculating the caller's frame.
	(i386_pop_frame): Don't bother explicitly restoring PC_REGNUM,
	it is restored by the for loop.
	* tm-i386bsd.h (SIGCONTEXT_FP_OFFSET,
	SIGCONTEXT_SP_OFFSET): Define.
	* tm-i386sol2.h: Likewise.
	* tm-i386v4.h: Likewise.

-- John Wehle
------------------8<------------------------8<------------------------
*** gdb/i386-tdep.c.ORIGINAL	Wed Nov  4 22:28:13 1998
--- gdb/i386-tdep.c	Wed Jan 20 21:54:06 1999
*************** i386_get_frame_setup (fi)
*** 241,246 ****
--- 241,287 ----
    else
      codestream_seek (codestream_tell () - 1);
  
+   if ( (fi->status & MY_FRAME_IN_SP) )
+     fi->stack_mask = -1;
+ 
+   /* check for stack alignment 
+    *
+    *  andl $XXX, %esp
+    *
+    * note: you can't and a 16 bit immediate
+    * with a 32 bit reg, so we don't have to worry
+    * about a data16 prefix 
+    */
+   op = codestream_peek ();
+   if (op == 0x83)
+     {
+       /* andl with 8 bit immed */
+       codestream_get ();
+       if (codestream_get () != 0xe4)
+ 	/* Some instruction starting with 0x83 other than andl.  */
+ 	codestream_seek (codestream_tell () - 2);
+       else
+ 	{
+           /* andl with signed byte immediate  */
+           fi->stack_mask = (char)codestream_get();
+ 	}
+     }
+   else if (op == 0x81)
+     {
+       char buf[4];
+       /* Maybe it is andl with 32 bit immediate.  */
+       codestream_get();
+       if (codestream_get () != 0xe4)
+ 	/* Some instruction starting with 0x81 other than andl.  */
+ 	codestream_seek (codestream_tell () - 2);
+       else
+ 	{
+           /* It is subl with 32 bit immediate.  */
+           codestream_read ((unsigned char *)buf, 4);
+           fi->stack_mask = extract_signed_integer (buf, 4);
+ 	}
+     }
+ 
    /* check for stack adjustment 
     *
     *  subl $XXX, %esp
*************** i386_frame_num_args (fi)
*** 360,365 ****
--- 401,477 ----
  #endif
  }
  
+ #if defined(I386V4_SIGTRAMP_SAVED_PC)
+ /*
+  * Determine the location of the registers saved in the signal context
+  * for all three variants of SVR4 sigtramps.
+  */
+ 
+ static CORE_ADDR
+ i386_sigcontext_addr (fi)
+      struct frame_info *fi;
+ {
+   int offset = 0;
+   char *name = NULL;
+ 
+   find_pc_partial_function (fi->pc, &name, NULL, NULL);
+   if (name)
+     {
+       if (STREQ (name, "_sigreturn"))
+ 	offset = 132;
+       else if (STREQ (name, "_sigacthandler"))
+ 	offset = 80;
+       else if (STREQ (name, "sigvechandler"))
+ 	offset = 120;
+     }
+ 
+   if (offset == 0)
+     return 0;
+ 
+   if (fi->next)
+     return fi->next->frame + offset;
+ 
+   return read_register (SP_REGNUM) + offset;
+ }
+ 
+ /* Get saved user PC for sigtramp from the pushed ucontext on the stack.  */
+ 
+ CORE_ADDR
+ i386v4_sigtramp_saved_pc (fi)
+      struct frame_info *frame;
+ {
+ 
+   return read_memory_integer (i386_sigcontext_addr (fi) + 14 * 4, 4);
+ }
+ 
+ #elif defined(SIGCONTEXT_FP_OFFSET)
+ /*
+  * Determine the location of the signal context.
+  */
+ 
+ static CORE_ADDR
+ i386_sigcontext_addr (fi)
+      struct frame_info *fi;
+ {
+   CORE_ADDR sigcontext_addr;
+   int ptrbytes = TARGET_PTR_BIT / TARGET_CHAR_BIT;
+   int sigcontext_offs = (2 * TARGET_INT_BIT) / TARGET_CHAR_BIT;
+ 
+   /* Get sigcontext address, it is the third parameter on the stack.  */
+   if (fi->next)
+     sigcontext_addr = read_memory_integer (FRAME_ARGS_ADDRESS (fi->next)
+ 					   + FRAME_ARGS_SKIP
+ 					   + sigcontext_offs,
+ 					   ptrbytes);
+   else
+     sigcontext_addr = read_memory_integer (read_register (SP_REGNUM)
+ 					    + sigcontext_offs,
+ 					   ptrbytes);
+ 
+   return sigcontext_addr;
+ }
+ #endif
+ 
  /*
   * Determine where the registers were stored.
   */
*************** i386_frame_find_saved_regs (fi)
*** 378,384 ****
     * been placed on the stack.
     */
  
!   offset = 4 + fi->stack_size;
  
    for (i = 0; i < 8; i++) 
      {
--- 490,496 ----
     * been placed on the stack.
     */
  
!   offset = fi->stack_size;
  
    for (i = 0; i < 8; i++) 
      {
*************** i386_frame_find_saved_regs (fi)
*** 386,422 ****
        if (op < 0x50 || op > 0x57)
  	break;
        codestream_get ();
  #ifdef I386_REGNO_TO_SYMMETRY
        /* Dynix uses different internal numbering.  Ick.  */
        fi->fsr.regs[I386_REGNO_TO_SYMMETRY(op - 0x50)] = offset;
  #else
        fi->fsr.regs[op - 0x50] = offset;
  #endif
-       offset += 4;
      }
  
    /*
!    * Then determine where some important registers are located
!    * and fix fi->frame if it's bogus.
     */
  
!   if (fi->status == MY_FRAME_IN_FP)
!     {
!       fi->fsr.regs[FP_REGNUM] = fi->frame;
!       fi->fsr.regs[PC_REGNUM] = fi->frame + 4;
!     }
!   else if (fi->status == MY_FRAME_IN_SP)
      {
        /* Fix fi->frame if it's bogus at this point.  */
        if (fi->next == NULL)
! 	fi->frame = read_sp() + (offset - 4);
!       fi->fsr.regs[PC_REGNUM] = fi->frame;
      }
  
!   /*
!    * Finally determine the actual location of each register
!    * that has been placed on the stack.
!    */
  
    for (i = 0; i < 8; i++) 
      {
--- 498,525 ----
        if (op < 0x50 || op > 0x57)
  	break;
        codestream_get ();
+       offset += 4;
  #ifdef I386_REGNO_TO_SYMMETRY
        /* Dynix uses different internal numbering.  Ick.  */
        fi->fsr.regs[I386_REGNO_TO_SYMMETRY(op - 0x50)] = offset;
  #else
        fi->fsr.regs[op - 0x50] = offset;
  #endif
      }
  
    /*
!    * Then determine the actual location of each register
!    * that has been placed on the stack.
     */
  
!   if (fi->status == MY_FRAME_IN_SP)
      {
        /* Fix fi->frame if it's bogus at this point.  */
        if (fi->next == NULL)
! 	fi->frame = read_sp() + offset;
      }
  
!   adr = fi->stack_mask ? (fi->frame & fi->stack_mask) : fi->frame;
  
    for (i = 0; i < 8; i++) 
      {
*************** i386_frame_find_saved_regs (fi)
*** 424,435 ****
        /* Dynix uses different internal numbering.  Ick.  */
        if (fi->fsr.regs[I386_REGNO_TO_SYMMETRY(i)])
  	fi->fsr.regs[I386_REGNO_TO_SYMMETRY(i)] =
! 	  fi->frame - fi->fsr.regs[I386_REGNO_TO_SYMMETRY(i)];
  #else
        if (fi->fsr.regs[i])
! 	fi->fsr.regs[i] = fi->frame - fi->fsr.regs[i];
  #endif
      }
  }
  
  /*
--- 527,550 ----
        /* Dynix uses different internal numbering.  Ick.  */
        if (fi->fsr.regs[I386_REGNO_TO_SYMMETRY(i)])
  	fi->fsr.regs[I386_REGNO_TO_SYMMETRY(i)] =
! 	  adr - fi->fsr.regs[I386_REGNO_TO_SYMMETRY(i)];
  #else
        if (fi->fsr.regs[i])
! 	fi->fsr.regs[i] = adr - fi->fsr.regs[i];
  #endif
      }
+ 
+   /*
+    * Finally determine where some important registers are located
+    */
+ 
+   if (fi->status == MY_FRAME_IN_FP)
+     {
+       fi->fsr.regs[FP_REGNUM] = fi->frame;
+       fi->fsr.regs[PC_REGNUM] = fi->frame + 4;
+     }
+   else if (fi->status == MY_FRAME_IN_SP)
+     fi->fsr.regs[PC_REGNUM] = fi->frame;
  }
  
  /*
*************** i386_analyze_prologue (fi, skip_prologue
*** 467,483 ****
      struct frame_info *fi;
      int skip_prologue;
  {
    CORE_ADDR func_addr, func_end;
    char *name;
    unsigned char op;
    int status;
  
    /* Find the start of this function.  */
    status = find_pc_partial_function (fi->pc, &name, &func_addr, &func_end);
  
!   /* Do nothing if we couldn't find the start of this function.  */
    if (status == 0)
!     return;
  
    /* If we're in start, then give up.  */
    if (strcmp (name, "start") == 0)
--- 582,642 ----
      struct frame_info *fi;
      int skip_prologue;
  {
+   CORE_ADDR adr;
+   CORE_ADDR dummy_bottom;
    CORE_ADDR func_addr, func_end;
    char *name;
    unsigned char op;
+   int i;
    int status;
  
    /* Find the start of this function.  */
    status = find_pc_partial_function (fi->pc, &name, &func_addr, &func_end);
  
!   /* If we're in a sigtramp which has a sigcontext then record the location
!      of the frame and the stack pointer so that i386_frame_chain will work.
!      At some point it may be worth while to also record the location of the
!      other registers.  */
! #if defined(SIGCONTEXT_FP_OFFSET)
!   if (IN_SIGTRAMP (fi->pc, name))
!     {
!       fi->fsr.regs[FP_REGNUM]
! 	= i386_sigcontext_addr (fi) + SIGCONTEXT_FP_OFFSET;
!       fi->fsr.regs[SP_REGNUM]
! 	= i386_sigcontext_addr (fi) + SIGCONTEXT_SP_OFFSET;
!       return;
!     }
! #endif
! 
!   /* if frame is the end of a dummy, compute where the
!    * beginning would be
!    */
!   dummy_bottom = fi->frame - 4 - REGISTER_BYTES - CALL_DUMMY_LENGTH;
!   
!   /* check if the PC is in the stack, in a dummy frame */
!   if (dummy_bottom <= fi->pc && fi->pc <= fi->frame) 
!     {
!       /* all regs were saved by i386_push_dummy_frame */
!       fi->status = MY_FRAME_IN_FP;
!       adr = fi->frame;
!       for (i = 0; i < NUM_REGS; i++) 
! 	{
! 	  adr -= REGISTER_RAW_SIZE (i);
! 	  fi->fsr.regs[i] = adr;
! 	}
!       fi->fsr.regs[FP_REGNUM] = fi->frame;
!       fi->fsr.regs[PC_REGNUM] = fi->frame + 4;
!       return;
!     }
! 
!   /* If we couldn't find the start of this function then assume that
!      it's frame is in the frame pointer so that we can chain back to
!      a dummy frame.  */
    if (status == 0)
!     {
!       fi->status = MY_FRAME_IN_FP;
!       return;
!     }
  
    /* If we're in start, then give up.  */
    if (strcmp (name, "start") == 0)
*************** i386_analyze_prologue (fi, skip_prologue
*** 488,494 ****
  
    /* At the start of a function our frame is in the stack pointer.  */
    fi->status = MY_FRAME_IN_SP;
-   fi->stack_size = 0;
  
    /* If we're physically on the first insn of a prologue, then our frame
       hasn't been allocated yet.  And if we're physically on an "return"
--- 647,652 ----
*************** i386_frame_chain (fi)
*** 566,577 ****
--- 724,740 ----
    dummy_frame.frame = fi->frame;
    i386_init_extra_frame_info (&dummy_frame);
  
+   if (! dummy_frame.status)
+     return fi->frame;
+ 
    if (dummy_frame.status & MY_FRAME_IN_FP)
      {
        char buf[MAX_REGISTER_RAW_SIZE];
  
        /* Our caller has a frame pointer.  So find the frame in %ebp or
           in the stack.  */
+       if (fi->fsr.regs[FP_REGNUM])
+ 	return read_memory_integer (fi->fsr.regs[FP_REGNUM], 4);
        get_saved_register(buf, NULL, NULL, fi, FP_REGNUM, NULL);
        return extract_address (buf, REGISTER_RAW_SIZE (FP_REGNUM));
      }
*************** i386_frame_chain (fi)
*** 580,586 ****
        int offset;
        int i;
  
!       offset = 4 + dummy_frame.stack_size;
  
        for (i = 0; i < 8; i++)
  #ifdef I386_REGNO_TO_SYMMETRY
--- 743,749 ----
        int offset;
        int i;
  
!       offset = dummy_frame.stack_size;
  
        for (i = 0; i < 8; i++)
  #ifdef I386_REGNO_TO_SYMMETRY
*************** i386_frame_chain (fi)
*** 592,597 ****
--- 755,765 ----
  	  offset += 4;
  #endif
  
+       if (fi->signal_handler_caller && fi->fsr.regs[SP_REGNUM])
+ 	return read_memory_integer (fi->fsr.regs[SP_REGNUM], 4) + offset;
+ 
+       offset += (fi->status & MY_FRAME_IN_FP) ? 8 : 4;
+ 
        /* Our caller does not have a frame pointer.  Assume his stack
  	 pointer was constant which means that his frame starts at
  	 the base of our frame (fi->frame) + pc save space +
*************** i386_skip_prologue (pc)
*** 614,620 ****
    CORE_ADDR pos;
    struct frame_info fi;
    
!   fi.frame = NULL;
    fi.pc = pc;
  
    i386_analyze_prologue (&fi, 1);
--- 782,788 ----
    CORE_ADDR pos;
    struct frame_info fi;
    
!   fi.frame = 0;
    fi.pc = pc;
  
    i386_analyze_prologue (&fi, 1);
*************** i386_pop_frame ()
*** 699,704 ****
--- 867,873 ----
    char regbuf[MAX_REGISTER_RAW_SIZE];
    
    get_frame_saved_regs (frame, &fsr);
+ 
    for (regnum = 0; regnum < NUM_REGS; regnum++) 
      {
        CORE_ADDR adr;
*************** i386_pop_frame ()
*** 710,717 ****
  				REGISTER_RAW_SIZE (regnum));
  	}
      }
!   write_register (PC_REGNUM, read_memory_integer (fsr.regs[PC_REGNUM], 4));
    write_register (SP_REGNUM, fsr.regs[PC_REGNUM] + 4);
    flush_cached_frames ();
  }
  
--- 879,887 ----
  				REGISTER_RAW_SIZE (regnum));
  	}
      }
! 
    write_register (SP_REGNUM, fsr.regs[PC_REGNUM] + 4);
+ 
    flush_cached_frames ();
  }
  
*************** i386_extract_return_value(type, regbuf, 
*** 773,806 ****
      }
  }
  
- #ifdef I386V4_SIGTRAMP_SAVED_PC
- /* Get saved user PC for sigtramp from the pushed ucontext on the stack
-    for all three variants of SVR4 sigtramps.  */
- 
- CORE_ADDR
- i386v4_sigtramp_saved_pc (frame)
-      struct frame_info *frame;
- {
-   CORE_ADDR saved_pc_offset = 4;
-   char *name = NULL;
- 
-   find_pc_partial_function (frame->pc, &name, NULL, NULL);
-   if (name)
-     {
-       if (STREQ (name, "_sigreturn"))
- 	saved_pc_offset = 132 + 14 * 4;
-       else if (STREQ (name, "_sigacthandler"))
- 	saved_pc_offset = 80 + 14 * 4;
-       else if (STREQ (name, "sigvechandler"))
- 	saved_pc_offset = 120 + 14 * 4;
-     }
- 
-   if (frame->next)
-     return read_memory_integer (frame->next->frame + saved_pc_offset, 4);
-   return read_memory_integer (read_register (SP_REGNUM) + saved_pc_offset, 4);
- }
- #endif /* I386V4_SIGTRAMP_SAVED_PC */
- 
  #ifdef STATIC_TRANSFORM_NAME
  /* SunPRO encodes the static variables.  This is not related to C++ mangling,
     it is done for C too.  */
--- 943,948 ----
*************** i386_init_extra_frame_info (fi)
*** 877,882 ****
--- 1019,1025 ----
  
    memset (fi->fsr.regs, '\000', sizeof fi->fsr.regs);
    fi->status = 0;
+   fi->stack_mask = 0;
    fi->stack_size = 0;
  
    i386_analyze_prologue (fi, 0);
*** gdb/config/i386/tm-i386.h.ORIGINAL	Mon Nov  2 23:04:58 1998
--- gdb/config/i386/tm-i386.h	Wed Jan 20 21:10:34 1999
*************** extern void i386_extract_return_value PA
*** 188,194 ****
  
  #define EXTRACT_STRUCT_VALUE_ADDRESS(REGBUF) (*(int *)(REGBUF))
  
! #define EXTRA_FRAME_INFO struct frame_saved_regs fsr; int status; int stack_size;
  
  #define INIT_EXTRA_FRAME_INFO(fromleaf, fi) i386_init_extra_frame_info (fi)
  #define INIT_FRAME_PC		/* Not necessary */
--- 188,198 ----
  
  #define EXTRACT_STRUCT_VALUE_ADDRESS(REGBUF) (*(int *)(REGBUF))
  
! #define EXTRA_FRAME_INFO \
!   int status; \
!   int stack_mask; \
!   int stack_size; \
!   struct frame_saved_regs fsr;
  
  #define INIT_EXTRA_FRAME_INFO(fromleaf, fi) i386_init_extra_frame_info (fi)
  #define INIT_FRAME_PC		/* Not necessary */
*************** extern void i386_init_extra_frame_info P
*** 207,213 ****
  
  #define FRAME_CHAIN(thisframe)  \
    ((thisframe)->signal_handler_caller \
!    ? (thisframe)->frame \
     : (!inside_entry_file ((thisframe)->pc) \
        ? i386_frame_chain (thisframe) \
        : 0))
--- 211,217 ----
  
  #define FRAME_CHAIN(thisframe)  \
    ((thisframe)->signal_handler_caller \
!    ? i386_frame_chain (thisframe) \
     : (!inside_entry_file ((thisframe)->pc) \
        ? i386_frame_chain (thisframe) \
        : 0))
*************** extern CORE_ADDR i386_frame_chain PARAMS
*** 224,232 ****
  
  extern CORE_ADDR sigtramp_saved_pc PARAMS ((struct frame_info *));
  
! #define FRAME_ARGS_ADDRESS(fi) ((fi)->frame)
  
! #define FRAME_LOCALS_ADDRESS(fi) ((fi)->frame)
  
  /* Return number of args passed to a frame.  Can return -1, meaning no way
     to tell, which is typical now that the C compiler delays popping them.  */
--- 228,251 ----
  
  extern CORE_ADDR sigtramp_saved_pc PARAMS ((struct frame_info *));
  
! /* There are several meanings associated with stack_mask:
  
!      stack_mask == 0      Both the args and locals use %ebp.
! 
!      stack_mask == -1     Both the args and locals use %esp.
! 
!      stack_mask != 0      The args use %ebp and the locals use %esp.
!      && stack_mask != -1  */
! 
! #define FRAME_ARGS_ADDRESS(fi) \
!   (((fi)->stack_mask == -1) \
!    ? ((fi)->frame - (fi)->stack_size) \
!    : (fi)->frame)
! 
! #define FRAME_LOCALS_ADDRESS(fi) \
!   ((fi)->stack_mask \
!    ? (((fi)->frame & (fi)->stack_mask) - (fi)->stack_size) \
!    : (fi)->frame)
  
  /* Return number of args passed to a frame.  Can return -1, meaning no way
     to tell, which is typical now that the C compiler delays popping them.  */
*** gdb/config/i386/tm-i386bsd.h.ORIGINAL	Sun May 26 17:41:33 1996
--- gdb/config/i386/tm-i386bsd.h	Wed Jan 20 21:13:16 1999
*************** Foundation, Inc., 59 Temple Place - Suit
*** 39,43 ****
--- 39,45 ----
  
  /* Offset to saved PC in sigcontext, from <sys/signal.h>.  */
  #define SIGCONTEXT_PC_OFFSET 20
+ #define SIGCONTEXT_FP_OFFSET 12
+ #define SIGCONTEXT_SP_OFFSET 8
  
  #endif  /* ifndef TM_I386BSD_H */
*** gdb/config/i386/tm-i386sol2.h.ORIGINAL	Sat Apr 11 01:39:47 1998
--- gdb/config/i386/tm-i386sol2.h	Wed Jan 20 21:10:38 1999
*************** Foundation, Inc., 59 Temple Place - Suit
*** 28,33 ****
--- 28,35 ----
  #undef sigtramp_saved_pc
  #undef I386V4_SIGTRAMP_SAVED_PC
  #define SIGCONTEXT_PC_OFFSET (36 + 14 * 4)
+ #define SIGCONTEXT_FP_OFFSET (36 + 6 * 4)
+ #define SIGCONTEXT_SP_OFFSET (36 + 17 * 4)
  #undef IN_SIGTRAMP
  #define IN_SIGTRAMP(pc, name) (pc == 0xFFFFFFFF)
  
*** gdb/config/i386/tm-i386v4.h.ORIGINAL	Thu Nov  2 10:20:44 1995
--- gdb/config/i386/tm-i386v4.h	Wed Jan 20 21:33:08 1999
*************** get_longjmp_target PARAMS ((CORE_ADDR *)
*** 65,70 ****
--- 65,72 ----
     user stack. Unfortunately there are three variants of sigtramp handlers.  */
  
  #define I386V4_SIGTRAMP_SAVED_PC
+ #define SIGCONTEXT_FP_OFFSET (6 * 4)
+ #define SIGCONTEXT_SP_OFFSET (17 * 4)
  #define IN_SIGTRAMP(pc, name) ((name)					\
  			       && (STREQ ("_sigreturn", name)		\
  				   || STREQ ("_sigacthandler", name)	\
-------------------------------------------------------------------------
|   Feith Systems  |   Voice: 1-215-646-8000  |  Email: john@feith.com  |
|    John Wehle    |     Fax: 1-215-540-5495  |                         |
-------------------------------------------------------------------------