1 /****************************************************************************
3 THIS SOFTWARE IS NOT COPYRIGHTED
5 HP offers the following for use in the public domain. HP makes no
6 warranty with regard to the software or it's performance and the
7 user accepts the software "AS IS" with all faults.
9 HP DISCLAIMS ANY WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD
10 TO THIS SOFTWARE INCLUDING BUT NOT LIMITED TO THE WARRANTIES
11 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
13 ****************************************************************************/
15 /****************************************************************************
16 * Header: remcom.c,v 1.34 91/03/09 12:29:49 glenne Exp $
18 * Module name: remcom.c $
20 * Date: 91/03/09 12:29:49 $
21 * Contributor: Lake Stevens Instrument Division$
23 * Description: low level support for gdb debugger. $
25 * Considerations: only works on target hardware $
27 * Written by: Glenn Engel $
28 * ModuleState: Experimental $
32 * Modified for SPARC by Stu Grossman, Cygnus Support.
33 * Based on sparc-stub.c, it's modified for SPARClite Debug Unit hardware
34 * breakpoint support to create sparclite-stub.c, by Kung Hsu, Cygnus Support.
36 * This code has been extensively tested on the Fujitsu SPARClite demo board.
38 * To enable debugger support, two things need to happen. One, a
39 * call to set_debug_traps() is necessary in order to allow any breakpoints
40 * or error conditions to be properly intercepted and reported to gdb.
41 * Two, a breakpoint needs to be generated to begin communication. This
42 * is most easily accomplished by a call to breakpoint(). Breakpoint()
43 * simulates a breakpoint by executing a trap #1.
47 * The following gdb commands are supported:
49 * command function Return value
51 * g return the value of the CPU registers hex data or ENN
52 * G set the value of the CPU registers OK or ENN
53 * P set the value of a single CPU register OK or ENN
55 * mAA..AA,LLLL Read LLLL bytes at address AA..AA hex data or ENN
56 * MAA..AA,LLLL: Write LLLL bytes at address AA.AA OK or ENN
58 * c Resume at current address SNN ( signal NN)
59 * cAA..AA Continue at address AA..AA SNN
61 * s Step one instruction SNN
62 * sAA..AA Step one instruction from AA..AA SNN
66 * ? What was the last sigval ? SNN (signal NN)
68 * All commands and responses are sent with a packet which includes a
69 * checksum. A packet consists of
71 * $<packet info>#<checksum>.
74 * <packet info> :: <characters representing the command or response>
75 * <checksum> :: < two hex digits computed as modulo 256 sum of <packetinfo>>
77 * When a packet is received, it is first acknowledged with either '+' or '-'.
78 * '+' indicates a successful transfer. '-' indicates a failed transfer.
83 * $m0,10#2a +$00010203040506070809101112131415#42
85 ****************************************************************************/
89 #include <sparclite.h>
91 /************************************************************************
93 * external low-level support routines
96 extern void putDebugChar (int c
); /* write a single character */
97 extern int getDebugChar (void); /* read and return a single char */
99 /************************************************************************/
100 /* BUFMAX defines the maximum number of characters in inbound/outbound buffers*/
101 /* at least NUMREGBYTES*2 are needed for register packets */
104 static int initialized
= 0; /* !0 means we've been initialized */
106 extern void breakinst ();
107 static void set_mem_fault_trap (int enable
);
108 static void get_in_break_mode (void);
110 static const char hexchars
[]="0123456789abcdef";
114 /* Number of bytes of registers. */
115 #define NUMREGBYTES (NUMREGS * 4)
116 enum regnames
{G0
, G1
, G2
, G3
, G4
, G5
, G6
, G7
,
117 O0
, O1
, O2
, O3
, O4
, O5
, SP
, O7
,
118 L0
, L1
, L2
, L3
, L4
, L5
, L6
, L7
,
119 I0
, I1
, I2
, I3
, I4
, I5
, FP
, I7
,
121 F0
, F1
, F2
, F3
, F4
, F5
, F6
, F7
,
122 F8
, F9
, F10
, F11
, F12
, F13
, F14
, F15
,
123 F16
, F17
, F18
, F19
, F20
, F21
, F22
, F23
,
124 F24
, F25
, F26
, F27
, F28
, F29
, F30
, F31
,
125 Y
, PSR
, WIM
, TBR
, PC
, NPC
, FPSR
, CPSR
,
126 DIA1
, DIA2
, DDA1
, DDA2
, DDV1
, DDV2
, DCR
, DSR
};
128 /*************************** ASSEMBLY CODE MACROS *************************/
131 extern void trap_low();
133 /* Create private copies of common functions used by the stub. This prevents
134 nasty interactions between app code and the stub (for instance if user steps
135 into strlen, etc..) */
138 strcpy (char *dst
, const char *src
)
142 while ((*dst
++ = *src
++) != '\000');
148 memcpy (void *vdst
, const void *vsrc
, int n
)
151 const char *src
= vsrc
;
161 .reserve trapstack, 1000 * 4, \"bss\", 8
172 ! This function is called when any SPARC trap (except window overflow or
173 ! underflow) occurs. It makes sure that the invalid register window is still
174 ! available before jumping into C code. It will also restore the world if you
175 ! return from handle_exception.
177 ! On entry, trap_low expects l1 and l2 to contain pc and npc respectivly.
178 ! Register usage throughout the routine is as follows:
184 ! l4 - scratch and y reg
185 ! l5 - scratch and tbr
194 srl %l3, %l0, %l4 ! wim >> cwp
196 bne window_fine ! Branch if not in the invalid window
199 ! Handle window overflow
201 mov %g1, %l4 ! Save g1, we use it to hold the wim
202 srl %l3, 1, %g1 ! Rotate wim right
204 bg good_wim ! Branch if new wim is non-zero
207 ! At this point, we need to bring a 1 into the high order bit of the wim.
208 ! Since we don't want to make any assumptions about the number of register
209 ! windows, we figure it out dynamically so as to setup the wim correctly.
211 not %g1 ! Fill g1 with ones
212 mov %g1, %wim ! Fill the wim with ones
216 mov %wim, %g1 ! Read back the wim
217 inc %g1 ! Now g1 has 1 just to left of wim
218 srl %g1, 1, %g1 ! Now put 1 at top of wim
219 mov %g0, %wim ! Clear wim so that subsequent save
225 save %g0, %g0, %g0 ! Slip into next window
226 mov %g1, %wim ! Install the new wim
228 std %l0, [%sp + 0 * 4] ! save L & I registers
229 std %l2, [%sp + 2 * 4]
230 std %l4, [%sp + 4 * 4]
231 std %l6, [%sp + 6 * 4]
233 std %i0, [%sp + 8 * 4]
234 std %i2, [%sp + 10 * 4]
235 std %i4, [%sp + 12 * 4]
236 std %i6, [%sp + 14 * 4]
238 restore ! Go back to trap window.
239 mov %l4, %g1 ! Restore %g1
242 sethi %hi(in_trap_handler), %l4
243 ld [%lo(in_trap_handler) + %l4], %l5
248 set trapstack+1000*4, %sp ! Switch to trap stack
251 st %l5, [%lo(in_trap_handler) + %l4]
252 sub %sp,(16+1+6+1+80)*4,%sp ! Make room for input & locals
253 ! + hidden arg + arg spill
254 ! + doubleword alignment
255 ! + registers[72] local var
257 std %g0, [%sp + (24 + 0) * 4] ! registers[Gx]
258 std %g2, [%sp + (24 + 2) * 4]
259 std %g4, [%sp + (24 + 4) * 4]
260 std %g6, [%sp + (24 + 6) * 4]
262 std %i0, [%sp + (24 + 8) * 4] ! registers[Ox]
263 std %i2, [%sp + (24 + 10) * 4]
264 std %i4, [%sp + (24 + 12) * 4]
265 std %i6, [%sp + (24 + 14) * 4]
269 st %l4, [%sp + (24 + 64) * 4] ! Y
270 st %l0, [%sp + (24 + 65) * 4] ! PSR
271 st %l3, [%sp + (24 + 66) * 4] ! WIM
272 st %l5, [%sp + (24 + 67) * 4] ! TBR
273 st %l1, [%sp + (24 + 68) * 4] ! PC
274 st %l2, [%sp + (24 + 69) * 4] ! NPC
277 mov %l4, %psr ! Turn on traps, disable interrupts
280 btst %l1, %l0 ! FP enabled?
284 ! Must save fsr first, to flush the FQ. This may cause a deferred fp trap, so
285 ! traps must be enabled to allow the trap handler to clean things up.
287 st %fsr, [%sp + (24 + 70) * 4]
289 std %f0, [%sp + (24 + 32) * 4]
290 std %f2, [%sp + (24 + 34) * 4]
291 std %f4, [%sp + (24 + 36) * 4]
292 std %f6, [%sp + (24 + 38) * 4]
293 std %f8, [%sp + (24 + 40) * 4]
294 std %f10, [%sp + (24 + 42) * 4]
295 std %f12, [%sp + (24 + 44) * 4]
296 std %f14, [%sp + (24 + 46) * 4]
297 std %f16, [%sp + (24 + 48) * 4]
298 std %f18, [%sp + (24 + 50) * 4]
299 std %f20, [%sp + (24 + 52) * 4]
300 std %f22, [%sp + (24 + 54) * 4]
301 std %f24, [%sp + (24 + 56) * 4]
302 std %f26, [%sp + (24 + 58) * 4]
303 std %f28, [%sp + (24 + 60) * 4]
304 std %f30, [%sp + (24 + 62) * 4]
307 call _handle_exception
308 add %sp, 24 * 4, %o0 ! Pass address of registers
310 ! Reload all of the registers that aren't on the stack
312 ld [%sp + (24 + 1) * 4], %g1 ! registers[Gx]
313 ldd [%sp + (24 + 2) * 4], %g2
314 ldd [%sp + (24 + 4) * 4], %g4
315 ldd [%sp + (24 + 6) * 4], %g6
317 ldd [%sp + (24 + 8) * 4], %i0 ! registers[Ox]
318 ldd [%sp + (24 + 10) * 4], %i2
319 ldd [%sp + (24 + 12) * 4], %i4
320 ldd [%sp + (24 + 14) * 4], %i6
323 ldd [%sp + (24 + 64) * 4], %l0 ! Y & PSR
324 ldd [%sp + (24 + 68) * 4], %l2 ! PC & NPC
327 btst %l5, %l1 ! FP enabled?
331 ldd [%sp + (24 + 32) * 4], %f0
332 ldd [%sp + (24 + 34) * 4], %f2
333 ldd [%sp + (24 + 36) * 4], %f4
334 ldd [%sp + (24 + 38) * 4], %f6
335 ldd [%sp + (24 + 40) * 4], %f8
336 ldd [%sp + (24 + 42) * 4], %f10
337 ldd [%sp + (24 + 44) * 4], %f12
338 ldd [%sp + (24 + 46) * 4], %f14
339 ldd [%sp + (24 + 48) * 4], %f16
340 ldd [%sp + (24 + 50) * 4], %f18
341 ldd [%sp + (24 + 52) * 4], %f20
342 ldd [%sp + (24 + 54) * 4], %f22
343 ldd [%sp + (24 + 56) * 4], %f24
344 ldd [%sp + (24 + 58) * 4], %f26
345 ldd [%sp + (24 + 60) * 4], %f28
346 ldd [%sp + (24 + 62) * 4], %f30
348 ld [%sp + (24 + 70) * 4], %fsr
351 restore ! Ensure that previous window is valid
352 save %g0, %g0, %g0 ! by causing a window_underflow trap
355 mov %l1, %psr ! Make sure that traps are disabled
357 sethi %hi(in_trap_handler), %l4
358 ld [%lo(in_trap_handler) + %l4], %l5
360 st %l5, [%lo(in_trap_handler) + %l4]
362 jmpl %l2, %g0 ! Restore old PC
363 rett %l3 ! Restore old nPC
366 /* Convert ch from a hex digit to an int */
369 hex (unsigned char ch
)
371 if (ch
>= 'a' && ch
<= 'f')
373 if (ch
>= '0' && ch
<= '9')
375 if (ch
>= 'A' && ch
<= 'F')
380 static char remcomInBuffer
[BUFMAX
];
381 static char remcomOutBuffer
[BUFMAX
];
383 /* scan for the sequence $<data>#<checksum> */
388 unsigned char *buffer
= &remcomInBuffer
[0];
389 unsigned char checksum
;
390 unsigned char xmitcsum
;
396 /* wait around for the start character, ignore all other characters */
397 while ((ch
= getDebugChar ()) != '$')
405 /* now, read until a # or end of buffer is found */
406 while (count
< BUFMAX
)
408 ch
= getDebugChar ();
413 checksum
= checksum
+ ch
;
421 ch
= getDebugChar ();
422 xmitcsum
= hex (ch
) << 4;
423 ch
= getDebugChar ();
424 xmitcsum
+= hex (ch
);
426 if (checksum
!= xmitcsum
)
428 putDebugChar ('-'); /* failed checksum */
432 putDebugChar ('+'); /* successful transfer */
434 /* if a sequence char is present, reply the sequence ID */
435 if (buffer
[2] == ':')
437 putDebugChar (buffer
[0]);
438 putDebugChar (buffer
[1]);
449 /* send the packet in buffer. */
452 putpacket (unsigned char *buffer
)
454 unsigned char checksum
;
458 /* $<packet info>#<checksum>. */
465 while (ch
= buffer
[count
])
473 putDebugChar(hexchars
[checksum
>> 4]);
474 putDebugChar(hexchars
[checksum
& 0xf]);
477 while (getDebugChar() != '+');
480 /* Indicate to caller of mem2hex or hex2mem that there has been an
482 static volatile int mem_err
= 0;
484 /* Convert the memory pointed to by mem into hex, placing result in buf.
485 * Return a pointer to the last char put in buf (null), in case of mem fault,
487 * If MAY_FAULT is non-zero, then we will handle memory faults by returning
488 * a 0, else treat a fault like any other fault in the stub.
491 static unsigned char *
492 mem2hex (unsigned char *mem
, unsigned char *buf
, int count
, int may_fault
)
496 set_mem_fault_trap(may_fault
);
503 *buf
++ = hexchars
[ch
>> 4];
504 *buf
++ = hexchars
[ch
& 0xf];
509 set_mem_fault_trap(0);
514 /* convert the hex array pointed to by buf into binary to be placed in mem
515 * return a pointer to the character AFTER the last byte written */
518 hex2mem (unsigned char *buf
, unsigned char *mem
, int count
, int may_fault
)
523 set_mem_fault_trap(may_fault
);
525 for (i
=0; i
<count
; i
++)
527 ch
= hex(*buf
++) << 4;
534 set_mem_fault_trap(0);
539 /* This table contains the mapping between SPARC hardware trap types, and
540 signals, which are primarily what GDB understands. It also indicates
541 which hardware traps we need to commandeer when initializing the stub. */
543 static struct hard_trap_info
545 unsigned char tt
; /* Trap type code for SPARClite */
546 unsigned char signo
; /* Signal that we map this trap into */
547 } hard_trap_info
[] = {
548 {0x01, SIGSEGV
}, /* instruction access error */
549 {0x02, SIGILL
}, /* privileged instruction */
550 {0x03, SIGILL
}, /* illegal instruction */
551 {0x04, SIGEMT
}, /* fp disabled */
552 {0x07, SIGBUS
}, /* mem address not aligned */
553 {0x09, SIGSEGV
}, /* data access exception */
554 {0x0a, SIGEMT
}, /* tag overflow */
555 {0x20, SIGBUS
}, /* r register access error */
556 {0x21, SIGBUS
}, /* instruction access error */
557 {0x24, SIGEMT
}, /* cp disabled */
558 {0x29, SIGBUS
}, /* data access error */
559 {0x2a, SIGFPE
}, /* divide by zero */
560 {0x2b, SIGBUS
}, /* data store error */
561 {0x80+1, SIGTRAP
}, /* ta 1 - normal breakpoint instruction */
562 {0xff, SIGTRAP
}, /* hardware breakpoint */
563 {0, 0} /* Must be last */
566 /* Set up exception handlers for tracing and breakpoints */
569 set_debug_traps (void)
571 struct hard_trap_info
*ht
;
573 /* Only setup fp traps if the FP is disabled. */
575 for (ht
= hard_trap_info
;
576 ht
->tt
!= 0 && ht
->signo
!= 0;
578 if (ht
->tt
!= 4 || ! (read_psr () & 0x1000))
579 exceptionHandler(ht
->tt
, trap_low
);
585 ! Trap handler for memory errors. This just sets mem_err to be non-zero. It
586 ! assumes that %l1 is non-zero. This should be safe, as it is doubtful that
587 ! 0 would ever contain code that could mem fault. This routine will skip
588 ! past the faulting instruction after setting mem_err.
594 sethi %hi(_mem_err), %l0
595 st %l1, [%l0 + %lo(_mem_err)]
601 set_mem_fault_trap (int enable
)
603 extern void fltr_set_mem_err();
607 exceptionHandler(9, fltr_set_mem_err
);
609 exceptionHandler(9, trap_low
);
616 _dummy_hw_breakpoint:
624 get_in_break_mode (void)
626 extern void dummy_hw_breakpoint();
628 exceptionHandler (255, dummy_hw_breakpoint
);
632 exceptionHandler (255, trap_low
);
635 /* Convert the SPARC hardware trap type code to a unix signal number. */
638 computeSignal (int tt
)
640 struct hard_trap_info
*ht
;
642 for (ht
= hard_trap_info
; ht
->tt
&& ht
->signo
; ht
++)
646 return SIGHUP
; /* default for things we don't know about */
650 * While we find nice hex chars, build an int.
651 * Return number of chars processed.
655 hexToInt(char **ptr
, int *intValue
)
664 hexValue
= hex(**ptr
);
668 *intValue
= (*intValue
<< 4) | hexValue
;
678 * This function does all command procesing for interfacing to gdb. It
679 * returns 1 if you should skip the instruction at the trap address, 0
684 handle_exception (unsigned long *registers
)
686 int tt
; /* Trap type */
694 /* First, we must force all of the windows to be spilled out */
696 asm(" save %sp, -64, %sp
714 get_in_break_mode (); /* Enable DSU register writes */
716 registers
[DIA1
] = read_asi (1, 0xff00);
717 registers
[DIA2
] = read_asi (1, 0xff04);
718 registers
[DDA1
] = read_asi (1, 0xff08);
719 registers
[DDA2
] = read_asi (1, 0xff0c);
720 registers
[DDV1
] = read_asi (1, 0xff10);
721 registers
[DDV2
] = read_asi (1, 0xff14);
722 registers
[DCR
] = read_asi (1, 0xff18);
723 registers
[DSR
] = read_asi (1, 0xff1c);
725 if (registers
[PC
] == (unsigned long)breakinst
)
727 registers
[PC
] = registers
[NPC
];
730 sp
= (unsigned long *)registers
[SP
];
732 dsr
= (unsigned long)registers
[DSR
];
736 tt
= (registers
[TBR
] >> 4) & 0xff;
738 /* reply to host that an exception has occurred */
739 sigval
= computeSignal(tt
);
740 ptr
= remcomOutBuffer
;
743 *ptr
++ = hexchars
[sigval
>> 4];
744 *ptr
++ = hexchars
[sigval
& 0xf];
746 *ptr
++ = hexchars
[PC
>> 4];
747 *ptr
++ = hexchars
[PC
& 0xf];
749 ptr
= mem2hex((char *)®isters
[PC
], ptr
, 4, 0);
752 *ptr
++ = hexchars
[FP
>> 4];
753 *ptr
++ = hexchars
[FP
& 0xf];
755 ptr
= mem2hex(sp
+ 8 + 6, ptr
, 4, 0); /* FP */
758 *ptr
++ = hexchars
[SP
>> 4];
759 *ptr
++ = hexchars
[SP
& 0xf];
761 ptr
= mem2hex((char *)&sp
, ptr
, 4, 0);
764 *ptr
++ = hexchars
[NPC
>> 4];
765 *ptr
++ = hexchars
[NPC
& 0xf];
767 ptr
= mem2hex((char *)®isters
[NPC
], ptr
, 4, 0);
770 *ptr
++ = hexchars
[O7
>> 4];
771 *ptr
++ = hexchars
[O7
& 0xf];
773 ptr
= mem2hex((char *)®isters
[O7
], ptr
, 4, 0);
778 putpacket(remcomOutBuffer
);
782 remcomOutBuffer
[0] = 0;
788 remcomOutBuffer
[0] = 'S';
789 remcomOutBuffer
[1] = hexchars
[sigval
>> 4];
790 remcomOutBuffer
[2] = hexchars
[sigval
& 0xf];
791 remcomOutBuffer
[3] = 0;
795 /* toggle debug flag */
798 case 'g': /* return the value of the CPU registers */
799 memcpy (®isters
[L0
], sp
, 16 * 4); /* Copy L & I regs from stack */
800 mem2hex ((char *)registers
, remcomOutBuffer
, NUMREGBYTES
, 0);
803 case 'G': /* Set the value of all registers */
804 case 'P': /* Set the value of one register */
806 unsigned long *newsp
, psr
;
808 psr
= registers
[PSR
];
814 if (hexToInt (&ptr
, ®no
)
816 if (regno
>= L0
&& regno
<= I7
)
817 hex2mem (ptr
, sp
+ regno
- L0
, 4, 0);
819 hex2mem (ptr
, (char *)®isters
[regno
], 4, 0);
822 strcpy (remcomOutBuffer
, "E01");
828 hex2mem (ptr
, (char *)registers
, NUMREGBYTES
, 0);
829 memcpy (sp
, ®isters
[L0
], 16 * 4); /* Copy L & I regs to stack */
832 /* See if the stack pointer has moved. If so, then copy the saved
833 locals and ins to the new location. This keeps the window
834 overflow and underflow routines happy. */
836 newsp
= (unsigned long *)registers
[SP
];
838 sp
= memcpy(newsp
, sp
, 16 * 4);
840 /* Don't allow CWP to be modified. */
842 if (psr
!= registers
[PSR
])
843 registers
[PSR
] = (psr
& 0x1f) | (registers
[PSR
] & ~0x1f);
845 strcpy(remcomOutBuffer
,"OK");
849 case 'm': /* mAA..AA,LLLL Read LLLL bytes at address AA..AA */
850 /* Try to read %x,%x. */
852 if (hexToInt(&ptr
, &addr
)
854 && hexToInt(&ptr
, &length
))
856 if (mem2hex((char *)addr
, remcomOutBuffer
, length
, 1))
859 strcpy (remcomOutBuffer
, "E03");
862 strcpy(remcomOutBuffer
,"E01");
865 case 'M': /* MAA..AA,LLLL: Write LLLL bytes at address AA.AA return OK */
866 /* Try to read '%x,%x:'. */
868 if (hexToInt(&ptr
, &addr
)
870 && hexToInt(&ptr
, &length
)
873 if (hex2mem(ptr
, (char *)addr
, length
, 1))
874 strcpy(remcomOutBuffer
, "OK");
876 strcpy(remcomOutBuffer
, "E03");
879 strcpy(remcomOutBuffer
, "E02");
882 case 'c': /* cAA..AA Continue at address AA..AA(optional) */
883 /* try to read optional parameter, pc unchanged if no parm */
884 if (hexToInt(&ptr
, &addr
))
886 registers
[PC
] = addr
;
887 registers
[NPC
] = addr
+ 4;
890 /* Need to flush the instruction cache here, as we may have deposited a
891 breakpoint, and the icache probably has no way of knowing that a data ref to
892 some location may have changed something that is in the instruction cache.
897 if (!(registers
[DSR
] & 0x1) /* DSU enabled? */
898 && !(registers
[DCR
] & 0x200)) /* Are we in break state? */
899 { /* Yes, set the DSU regs */
900 write_asi (1, 0xff00, registers
[DIA1
]);
901 write_asi (1, 0xff04, registers
[DIA2
]);
902 write_asi (1, 0xff08, registers
[DDA1
]);
903 write_asi (1, 0xff0c, registers
[DDA2
]);
904 write_asi (1, 0xff10, registers
[DDV1
]);
905 write_asi (1, 0xff14, registers
[DDV2
]);
906 write_asi (1, 0xff1c, registers
[DSR
]);
907 write_asi (1, 0xff18, registers
[DCR
] | 0x200); /* Clear break */
912 /* kill the program */
913 case 'k' : /* do nothing */
916 case 't': /* Test feature */
917 asm (" std %f30,[%sp]");
920 case 'r': /* Reset */
926 /* reply to the request */
927 putpacket(remcomOutBuffer
);
931 /* This function will generate a breakpoint exception. It is used at the
932 beginning of a program to sync up with a debugger and can be used
933 otherwise as a quick means to stop program execution and "break" into
942 asm(" .globl _breakinst
This page took 0.048659 seconds and 4 git commands to generate.