596 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			596 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/****************************************************************************
 | 
						|
 *
 | 
						|
 *  Module name: remcom.c $
 | 
						|
 *  Revision: 1.34 $
 | 
						|
 *  Date: 91/03/09 12:29:49 $
 | 
						|
 *  Contributor:     Lake Stevens Instrument Division$
 | 
						|
 *
 | 
						|
 *  Description:     low level support for gdb debugger. $
 | 
						|
 *
 | 
						|
 *  Considerations:  only works on target hardware $
 | 
						|
 *
 | 
						|
 *  Written by:      Glenn Engel $
 | 
						|
 *  ModuleState:     Experimental $
 | 
						|
 *
 | 
						|
 *  NOTES:           See Below $
 | 
						|
 *
 | 
						|
 *  Modified for SPARC by Stu Grossman, Cygnus Support.
 | 
						|
 *
 | 
						|
 *  This code has been extensively tested on the Fujitsu SPARClite demo board.
 | 
						|
 *
 | 
						|
 *  To enable debugger support, two things need to happen.  One, a
 | 
						|
 *  call to set_debug_traps() is necessary in order to allow any breakpoints
 | 
						|
 *  or error conditions to be properly intercepted and reported to gdb.
 | 
						|
 *  Two, a breakpoint needs to be generated to begin communication.  This
 | 
						|
 *  is most easily accomplished by a call to breakpoint().  Breakpoint()
 | 
						|
 *  simulates a breakpoint by executing a trap #1.
 | 
						|
 *
 | 
						|
 *************
 | 
						|
 *
 | 
						|
 *    The following gdb commands are supported:
 | 
						|
 *
 | 
						|
 * command          function                               Return value
 | 
						|
 *
 | 
						|
 *    g             return the value of the CPU registers  hex data or ENN
 | 
						|
 *    G             set the value of the CPU registers     OK or ENN
 | 
						|
 *
 | 
						|
 *    mAA..AA,LLLL  Read LLLL bytes at address AA..AA      hex data or ENN
 | 
						|
 *    MAA..AA,LLLL: Write LLLL bytes at address AA.AA      OK or ENN
 | 
						|
 *
 | 
						|
 *    c             Resume at current address              SNN   ( signal NN)
 | 
						|
 *    cAA..AA       Continue at address AA..AA             SNN
 | 
						|
 *
 | 
						|
 *    s             Step one instruction                   SNN
 | 
						|
 *    sAA..AA       Step one instruction from AA..AA       SNN
 | 
						|
 *
 | 
						|
 *    k             kill
 | 
						|
 *
 | 
						|
 *    ?             What was the last sigval ?             SNN   (signal NN)
 | 
						|
 *
 | 
						|
 *    bBB..BB	    Set baud rate to BB..BB		   OK or BNN, then sets
 | 
						|
 *							   baud rate
 | 
						|
 *
 | 
						|
 * All commands and responses are sent with a packet which includes a
 | 
						|
 * checksum.  A packet consists of
 | 
						|
 *
 | 
						|
 * $<packet info>#<checksum>.
 | 
						|
 *
 | 
						|
 * where
 | 
						|
 * <packet info> :: <characters representing the command or response>
 | 
						|
 * <checksum>    :: < two hex digits computed as modulo 256 sum of <packetinfo>>
 | 
						|
 *
 | 
						|
 * When a packet is received, it is first acknowledged with either '+' or '-'.
 | 
						|
 * '+' indicates a successful transfer.  '-' indicates a failed transfer.
 | 
						|
 *
 | 
						|
 * Example:
 | 
						|
 *
 | 
						|
 * Host:                  Reply:
 | 
						|
 * $m0,10#2a               +$00010203040506070809101112131415#42
 | 
						|
 *
 | 
						|
 ****************************************************************************/
 | 
						|
 | 
						|
#include <string.h>
 | 
						|
#include <signal.h>
 | 
						|
#include "dbgmon.h"
 | 
						|
#include "parser.h"
 | 
						|
#include "ctype.h"
 | 
						|
 | 
						|
/************************************************************************
 | 
						|
 *
 | 
						|
 * external low-level support routines
 | 
						|
 */
 | 
						|
 | 
						|
extern putchar();   /* write a single character      */
 | 
						|
extern getchar();   /* read and return a single char */
 | 
						|
 | 
						|
/************************************************************************/
 | 
						|
 | 
						|
/* Stuff for stdio-like gets_debugger_check() */
 | 
						|
 | 
						|
#define CTRL(x)   ('x'&0x1f)
 | 
						|
#define DEL       0x7f
 | 
						|
#define INTR      CTRL(C)
 | 
						|
#define BELL      0x7
 | 
						|
#define PROMPT    "? "
 | 
						|
 | 
						|
#define BUFSIZE 512                              /* Big enough for register packets */
 | 
						|
 | 
						|
static int initialized = 0;                      /* !0 means we've been initialized */
 | 
						|
 | 
						|
static char hexchars[]="0123456789abcdef";
 | 
						|
 | 
						|
extern unsigned int _regs[];                     /* Saved registers from client    */
 | 
						|
 | 
						|
/* Convert ch from a hex digit to an int */
 | 
						|
 | 
						|
static int    
 | 
						|
hex(ch)
 | 
						|
     unsigned 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;
 | 
						|
}
 | 
						|
 | 
						|
/* scan for the sequence $<data>#<checksum>     */
 | 
						|
 | 
						|
static void
 | 
						|
getpacket(buffer)
 | 
						|
     char *buffer;
 | 
						|
{
 | 
						|
  unsigned char checksum;
 | 
						|
  unsigned char xmitcsum;
 | 
						|
  int i;
 | 
						|
  int count;
 | 
						|
  unsigned char ch;
 | 
						|
 | 
						|
    /* At this point, the start character ($) has been received through
 | 
						|
     * the debug monitor parser. Get the remaining characters and 
 | 
						|
     * process them.
 | 
						|
     */
 | 
						|
 | 
						|
    checksum = 0;
 | 
						|
    xmitcsum = -1;
 | 
						|
    count = 0;
 | 
						|
 | 
						|
    /* read until a # or end of buffer is found */
 | 
						|
 | 
						|
    while (count < BUFSIZE)
 | 
						|
    {
 | 
						|
        ch = getchar();
 | 
						|
        if (ch == '#')
 | 
						|
            break;
 | 
						|
        checksum = checksum + ch;
 | 
						|
        buffer[count] = ch;
 | 
						|
        count = count + 1;
 | 
						|
    }
 | 
						|
 | 
						|
    if (count >= BUFSIZE)
 | 
						|
        buffer[count] = 0;
 | 
						|
 | 
						|
    if (ch == '#')
 | 
						|
    {
 | 
						|
        xmitcsum = hex(getchar()) << 4;
 | 
						|
        xmitcsum |= hex(getchar());
 | 
						|
#if 0
 | 
						|
    /* Humans shouldn't have to figure out checksums to type to it. */
 | 
						|
        putchar ('+');
 | 
						|
        return;
 | 
						|
#endif
 | 
						|
 | 
						|
        if (checksum != xmitcsum)
 | 
						|
        {
 | 
						|
            putchar('-');                      /* failed checksum      */
 | 
						|
            return;                            /* Back to monitor loop */
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            putchar('+');                      /* successful transfer */
 | 
						|
 | 
						|
            /* if a sequence char is present, reply the sequence ID */
 | 
						|
 | 
						|
            if (buffer[2] == ':')
 | 
						|
            {
 | 
						|
                putchar(buffer[0]);
 | 
						|
                putchar(buffer[1]);
 | 
						|
 | 
						|
               /* remove sequence chars from buffer */
 | 
						|
 | 
						|
               count = strlen(buffer);
 | 
						|
               for (i=3; i <= count; i++)
 | 
						|
                   buffer[i-3] = buffer[i];
 | 
						|
            }
 | 
						|
            
 | 
						|
            /* Buffer command received- go and process it. */
 | 
						|
 | 
						|
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/* send the packet in buffer.  */
 | 
						|
 | 
						|
static void
 | 
						|
putpacket(buffer)
 | 
						|
     unsigned char *buffer;
 | 
						|
{
 | 
						|
  unsigned char checksum;
 | 
						|
  int count;
 | 
						|
  unsigned char ch;
 | 
						|
 | 
						|
  /*  $<packet info>#<checksum>. */
 | 
						|
  do
 | 
						|
    {
 | 
						|
      putchar('$');
 | 
						|
      checksum = 0;
 | 
						|
      count = 0;
 | 
						|
 | 
						|
      while (ch = buffer[count])
 | 
						|
	{
 | 
						|
	  if (! putchar(ch))
 | 
						|
	    return;
 | 
						|
	  checksum += ch;
 | 
						|
	  count += 1;
 | 
						|
	}
 | 
						|
 | 
						|
      putchar('#');
 | 
						|
      putchar(hexchars[checksum >> 4]);
 | 
						|
      putchar(hexchars[checksum & 0xf]);
 | 
						|
 | 
						|
    }
 | 
						|
  while (getchar() != '+');
 | 
						|
}
 | 
						|
 | 
						|
static char remcomInBuffer[BUFSIZE];
 | 
						|
static char remcomOutBuffer[BUFSIZE];
 | 
						|
 | 
						|
/* Indicate to caller of mem2hex or hex2mem that there has been an error.  */
 | 
						|
 | 
						|
static volatile int mem_err = 0;
 | 
						|
 | 
						|
/* Convert the memory pointed to by mem into hex, placing result in buf.
 | 
						|
 * Return a pointer to the last char put in buf (null), in case of mem fault,
 | 
						|
 * return 0.
 | 
						|
 * If MAY_FAULT is non-zero, then we will handle memory faults by returning
 | 
						|
 * a 0, else treat a fault like any other fault in the stub.
 | 
						|
 */
 | 
						|
 | 
						|
static unsigned char *
 | 
						|
mem2hex(mem, buf, count, may_fault)
 | 
						|
     unsigned char *mem;
 | 
						|
     unsigned char *buf;
 | 
						|
     int count;
 | 
						|
     int may_fault;
 | 
						|
{
 | 
						|
  unsigned char ch;
 | 
						|
 | 
						|
  while (count-- > 0)
 | 
						|
    {
 | 
						|
      ch = *mem++;
 | 
						|
      if (mem_err)
 | 
						|
	return 0;
 | 
						|
      *buf++ = hexchars[ch >> 4];
 | 
						|
      *buf++ = hexchars[ch & 0xf];
 | 
						|
    }
 | 
						|
 | 
						|
  *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 */
 | 
						|
 | 
						|
static char *
 | 
						|
hex2mem(buf, mem, count, may_fault)
 | 
						|
     unsigned char *buf;
 | 
						|
     unsigned char *mem;
 | 
						|
     int count;
 | 
						|
     int may_fault;
 | 
						|
{
 | 
						|
  int i;
 | 
						|
  unsigned char ch;
 | 
						|
 | 
						|
  for (i=0; i<count; i++)
 | 
						|
    {
 | 
						|
      ch = hex(*buf++) << 4;
 | 
						|
      ch |= hex(*buf++);
 | 
						|
      *mem++ = ch;
 | 
						|
      if (mem_err)
 | 
						|
	return 0;
 | 
						|
    }
 | 
						|
 | 
						|
  return mem;
 | 
						|
}
 | 
						|
 | 
						|
/* This table contains the mapping between SPARC hardware trap types, and
 | 
						|
   signals, which are primarily what GDB understands.  It also indicates
 | 
						|
   which hardware traps we need to commandeer when initializing the stub. */
 | 
						|
 | 
						|
static struct hard_trap_info
 | 
						|
{
 | 
						|
  unsigned char tt;       /* Trap type code for SPARClite */
 | 
						|
  unsigned char signo;    /* Signal that we map this trap into */
 | 
						|
} hard_trap_info[] = {
 | 
						|
  {0x06, SIGSEGV},        /* instruction access error */
 | 
						|
  {0x0a, SIGILL},         /* privileged instruction */
 | 
						|
  {0x0a, SIGILL},         /* illegal instruction */
 | 
						|
  {0x0b, SIGEMT},         /* cp disabled */
 | 
						|
  {0x07, SIGSEGV},        /* data access exception */
 | 
						|
  {0x09, SIGTRAP},        /* ta 1 - normal breakpoint instruction */
 | 
						|
  {0, 0}                  /* Must be last */
 | 
						|
};
 | 
						|
 | 
						|
/* Convert the SPARC hardware trap type code to a unix signal number. */
 | 
						|
 | 
						|
static int
 | 
						|
computeSignal(tt)
 | 
						|
     int tt;
 | 
						|
{
 | 
						|
  struct hard_trap_info *ht;
 | 
						|
 | 
						|
  for (ht = hard_trap_info; ht->tt && ht->signo; ht++)
 | 
						|
    if (ht->tt == tt)
 | 
						|
      return ht->signo;
 | 
						|
 | 
						|
  return SIGHUP;		/* default for things we don't know about */
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * While we find nice hex chars, build an int.
 | 
						|
 * Return number of chars processed.
 | 
						|
 */
 | 
						|
 | 
						|
static int
 | 
						|
hexToInt(char **ptr, int *intValue)
 | 
						|
{
 | 
						|
  int numChars = 0;
 | 
						|
  int hexValue;
 | 
						|
 | 
						|
  *intValue = 0;
 | 
						|
 | 
						|
  while (**ptr)
 | 
						|
    {
 | 
						|
      hexValue = hex(**ptr);
 | 
						|
      if (hexValue < 0)
 | 
						|
	break;
 | 
						|
 | 
						|
      *intValue = (*intValue << 4) | hexValue;
 | 
						|
      numChars ++;
 | 
						|
 | 
						|
      (*ptr)++;
 | 
						|
    }
 | 
						|
 | 
						|
  return (numChars);
 | 
						|
}
 | 
						|
 | 
						|
/* This function lets GDB know that an exception has occured. */
 | 
						|
 | 
						|
static void
 | 
						|
debug_handle_exception ()
 | 
						|
{
 | 
						|
    int           tt;            /* Trap type */
 | 
						|
    int           sigval;
 | 
						|
    char          *ptr;
 | 
						|
 | 
						|
    tt = (_regs[R_CAUSE] >> 2) & 0x0f;
 | 
						|
 | 
						|
    /* reply to host that an exception has occurred */
 | 
						|
    sigval = computeSignal(tt);
 | 
						|
    ptr = remcomOutBuffer;
 | 
						|
  
 | 
						|
    *ptr++ = 'T';
 | 
						|
    *ptr++ = hexchars[sigval >> 4];
 | 
						|
    *ptr++ = hexchars[sigval & 0xf];
 | 
						|
  
 | 
						|
    *ptr++ = hexchars[R_EPC >> 4];
 | 
						|
    *ptr++ = hexchars[R_EPC & 0xf];
 | 
						|
    *ptr++ = ':';
 | 
						|
    ptr = mem2hex((char *)&_regs[R_EPC], ptr, 4, 0);
 | 
						|
    *ptr++ = ';';
 | 
						|
  
 | 
						|
    *ptr++ = hexchars[R_FP >> 4];
 | 
						|
    *ptr++ = hexchars[R_FP & 0xf];
 | 
						|
    *ptr++ = ':';
 | 
						|
    ptr = mem2hex((char *)&_regs[R_FP], ptr, 4, 0);
 | 
						|
    *ptr++ = ';';
 | 
						|
  
 | 
						|
    *ptr++ = hexchars[R_SP >> 4];
 | 
						|
    *ptr++ = hexchars[R_SP & 0xf];
 | 
						|
    *ptr++ = ':';
 | 
						|
    ptr = mem2hex((char *)&_regs[R_SP], ptr, 4, 0);
 | 
						|
    *ptr++ = ';';
 | 
						|
  
 | 
						|
    *ptr++ = 0;
 | 
						|
  
 | 
						|
    putpacket(remcomOutBuffer);
 | 
						|
    
 | 
						|
    return;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void process_packet()
 | 
						|
{
 | 
						|
 | 
						|
    char          *ptr;
 | 
						|
    int           length;
 | 
						|
    int           addr;
 | 
						|
    int           sigval;
 | 
						|
    int           tt;            /* Trap type */
 | 
						|
 | 
						|
    remcomOutBuffer[0] = 0;
 | 
						|
    getpacket(remcomInBuffer);
 | 
						|
    switch (remcomInBuffer[0])
 | 
						|
    {
 | 
						|
 | 
						|
/* Return Last SIGVAL */
 | 
						|
 | 
						|
case '?':
 | 
						|
        tt = (_regs[R_CAUSE] >> 2) & 0x0f;
 | 
						|
        sigval = computeSignal(tt);
 | 
						|
        remcomOutBuffer[0] = 'S';
 | 
						|
        remcomOutBuffer[1] = hexchars[sigval >> 4];
 | 
						|
        remcomOutBuffer[2] = hexchars[sigval & 0xf];
 | 
						|
        remcomOutBuffer[3] = 0;
 | 
						|
        break;
 | 
						|
 | 
						|
        /* toggle debug flag */
 | 
						|
 | 
						|
        case 'd':
 | 
						|
            break;
 | 
						|
 | 
						|
        /* Return the values of the CPU registers */
 | 
						|
 | 
						|
        case 'g':
 | 
						|
            ptr = remcomOutBuffer;
 | 
						|
            ptr = mem2hex((char *)_regs, ptr, 32 * 4, 0);        /* General Purpose Registers */
 | 
						|
            ptr = mem2hex((char *)&_regs[R_EPC], ptr, 9 * 4, 0); /* CP0 Registers             */
 | 
						|
            break;
 | 
						|
 | 
						|
        /*  set the value of the CPU registers - return OK */
 | 
						|
 | 
						|
        case 'G':
 | 
						|
            ptr = &remcomInBuffer[1];
 | 
						|
            hex2mem(ptr, (char *)_regs, 32 * 4, 0);                     /* General Purpose Registers */
 | 
						|
            hex2mem(ptr + 32 * 4 * 2, (char *)&_regs[R_EPC], 9 * 4, 0); /* CP0 Registers             */
 | 
						|
            strcpy(remcomOutBuffer,"OK");
 | 
						|
            break;
 | 
						|
 | 
						|
        /* mAA..AA,LLLL  Read LLLL bytes at address AA..AA */
 | 
						|
 | 
						|
        case 'm':
 | 
						|
            ptr = &remcomInBuffer[1];
 | 
						|
            if (hexToInt(&ptr, &addr) && *ptr++ == ',' && hexToInt(&ptr, &length))
 | 
						|
            {
 | 
						|
                if (mem2hex((char *)addr, remcomOutBuffer, length, 1))
 | 
						|
                    break;
 | 
						|
                strcpy (remcomOutBuffer, "E03");
 | 
						|
            }
 | 
						|
            else
 | 
						|
                strcpy(remcomOutBuffer,"E01");
 | 
						|
            break;
 | 
						|
 | 
						|
        /* MAA..AA,LLLL: Write LLLL bytes at address AA.AA return OK */
 | 
						|
 | 
						|
        case 'M':
 | 
						|
            ptr = &remcomInBuffer[1];
 | 
						|
            if (hexToInt(&ptr, &addr) && *ptr++ == ',' && hexToInt(&ptr, &length) && *ptr++ == ':')
 | 
						|
            {
 | 
						|
                if (hex2mem(ptr, (char *)addr, length, 1))
 | 
						|
                    strcpy(remcomOutBuffer, "OK");
 | 
						|
                else
 | 
						|
                    strcpy(remcomOutBuffer, "E03");
 | 
						|
            }
 | 
						|
            else
 | 
						|
                strcpy(remcomOutBuffer, "E02");
 | 
						|
            break;
 | 
						|
 | 
						|
        /* cAA..AA    Continue at address AA..AA(optional) */
 | 
						|
 | 
						|
        case 'c':
 | 
						|
 | 
						|
        /* try to read optional parameter, pc unchanged if no parm */
 | 
						|
 | 
						|
            ptr = &remcomInBuffer[1];
 | 
						|
            if (hexToInt(&ptr, &addr))
 | 
						|
            {
 | 
						|
                gdb_go ( addr );
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                dbg_cont();
 | 
						|
            }
 | 
						|
            return;
 | 
						|
 | 
						|
        /* kill the program */
 | 
						|
 | 
						|
        case 'k':	
 | 
						|
            break;
 | 
						|
 | 
						|
        /* Reset */
 | 
						|
 | 
						|
        case 'r':
 | 
						|
            break;
 | 
						|
 | 
						|
	/* switch */
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
    /* Reply to the request */
 | 
						|
 | 
						|
    putpacket(remcomOutBuffer);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * gets_debugger_check - This is the same as the stdio gets, but we also
 | 
						|
 *                       check for a leading $ in the buffer. This so we
 | 
						|
 *                       gracefully handle the GDB protocol packets.
 | 
						|
 */
 | 
						|
 | 
						|
char *
 | 
						|
gets_debugger_check(buf)
 | 
						|
char *buf;
 | 
						|
{
 | 
						|
    register char c;
 | 
						|
    char *bufp;
 | 
						|
 | 
						|
    bufp = buf;
 | 
						|
    for (;;) 
 | 
						|
    {
 | 
						|
        c = getchar();
 | 
						|
        switch (c) 
 | 
						|
        {
 | 
						|
 | 
						|
            /* quote next char */
 | 
						|
 | 
						|
            case '$':
 | 
						|
                if ( buf == bufp )
 | 
						|
                    process_packet();
 | 
						|
                break;
 | 
						|
                 
 | 
						|
            case CTRL(V):
 | 
						|
                c = getchar();
 | 
						|
                if (bufp < &buf[LINESIZE-3]) 
 | 
						|
                {
 | 
						|
                    rmw_byte (bufp++,c);
 | 
						|
                    showchar(c);
 | 
						|
                 } 
 | 
						|
                 else
 | 
						|
                 {
 | 
						|
                    putchar(BELL);
 | 
						|
                 }
 | 
						|
                 break;
 | 
						|
 | 
						|
            case '\n':
 | 
						|
            case '\r':
 | 
						|
                putchar('\n');
 | 
						|
                rmw_byte (bufp,0);
 | 
						|
                return(buf);
 | 
						|
 | 
						|
            case CTRL(H):
 | 
						|
            case DEL:
 | 
						|
                if (bufp > buf) 
 | 
						|
                {
 | 
						|
                    bufp--;
 | 
						|
                    putchar(CTRL(H));
 | 
						|
                    putchar(' ');
 | 
						|
                    putchar(CTRL(H));
 | 
						|
                }
 | 
						|
                break;
 | 
						|
 | 
						|
            case CTRL(U):
 | 
						|
                if (bufp > buf) 
 | 
						|
                {
 | 
						|
                    printf("^U\n%s", PROMPT);
 | 
						|
                    bufp = buf;
 | 
						|
                }
 | 
						|
                break;
 | 
						|
 | 
						|
            case '\t':
 | 
						|
                c = ' ';
 | 
						|
 | 
						|
            default:
 | 
						|
                /*
 | 
						|
                 * Make sure there's room for this character
 | 
						|
                 * plus a trailing \n and 0 byte
 | 
						|
                 */
 | 
						|
                if (isprint(c) && bufp < &buf[LINESIZE-3]) 
 | 
						|
                {
 | 
						|
                    rmw_byte ( bufp++, c );
 | 
						|
                    putchar(c);
 | 
						|
                } 
 | 
						|
                else
 | 
						|
                {
 | 
						|
                    putchar(BELL);
 | 
						|
                }
 | 
						|
                break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |