| 
									
										
										
										
											2010-05-28 23:09:12 -04:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright 2010 Tilera Corporation. All Rights Reserved. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *   This program is free software; you can redistribute it and/or | 
					
						
							|  |  |  |  *   modify it under the terms of the GNU General Public License | 
					
						
							|  |  |  |  *   as published by the Free Software Foundation, version 2. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *   This program is distributed in the hope that it will be useful, but | 
					
						
							|  |  |  |  *   WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  |  *   MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or | 
					
						
							|  |  |  |  *   NON INFRINGEMENT.  See the GNU General Public License for | 
					
						
							|  |  |  |  *   more details. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copied from i386: Ross Biro 1/23/92 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/kernel.h>
 | 
					
						
							|  |  |  | #include <linux/ptrace.h>
 | 
					
						
							|  |  |  | #include <linux/kprobes.h>
 | 
					
						
							|  |  |  | #include <linux/compat.h>
 | 
					
						
							|  |  |  | #include <linux/uaccess.h>
 | 
					
						
							| 
									
										
										
										
											2012-12-17 20:08:09 -05:00
										 |  |  | #include <linux/regset.h>
 | 
					
						
							|  |  |  | #include <linux/elf.h>
 | 
					
						
							| 
									
										
										
										
											2012-12-22 00:21:10 -05:00
										 |  |  | #include <linux/tracehook.h>
 | 
					
						
							| 
									
										
										
										
											2010-06-25 17:04:17 -04:00
										 |  |  | #include <asm/traps.h>
 | 
					
						
							| 
									
										
										
										
											2012-12-17 20:08:09 -05:00
										 |  |  | #include <arch/chip.h>
 | 
					
						
							| 
									
										
										
										
											2010-05-28 23:09:12 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-01-21 19:54:57 -05:00
										 |  |  | #define CREATE_TRACE_POINTS
 | 
					
						
							|  |  |  | #include <trace/events/syscalls.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-05-28 23:09:12 -04:00
										 |  |  | void user_enable_single_step(struct task_struct *child) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	set_tsk_thread_flag(child, TIF_SINGLESTEP); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void user_disable_single_step(struct task_struct *child) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	clear_tsk_thread_flag(child, TIF_SINGLESTEP); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * Called by kernel/ptrace.c when detaching.. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | void ptrace_disable(struct task_struct *child) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	clear_tsk_thread_flag(child, TIF_SINGLESTEP); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * These two are currently unused, but will be set by arch_ptrace() | 
					
						
							|  |  |  | 	 * and used in the syscall assembly when we do support them. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-12-12 17:24:39 -05:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Get registers from task and ready the result for userspace. | 
					
						
							|  |  |  |  * Note that we localize the API issues to getregs() and putregs() at | 
					
						
							|  |  |  |  * some cost in performance, e.g. we need a full pt_regs copy for | 
					
						
							|  |  |  |  * PEEKUSR, and two copies for POKEUSR.  But in general we expect | 
					
						
							|  |  |  |  * GETREGS/PUTREGS to be the API of choice anyway. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | static char *getregs(struct task_struct *child, struct pt_regs *uregs) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	*uregs = *task_pt_regs(child); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Set up flags ABI bits. */ | 
					
						
							|  |  |  | 	uregs->flags = 0; | 
					
						
							|  |  |  | #ifdef CONFIG_COMPAT
 | 
					
						
							|  |  |  | 	if (task_thread_info(child)->status & TS_COMPAT) | 
					
						
							|  |  |  | 		uregs->flags |= PT_FLAGS_COMPAT; | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return (char *)uregs; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Put registers back to task. */ | 
					
						
							|  |  |  | static void putregs(struct task_struct *child, struct pt_regs *uregs) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct pt_regs *regs = task_pt_regs(child); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Don't allow overwriting the kernel-internal flags word. */ | 
					
						
							|  |  |  | 	uregs->flags = regs->flags; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Only allow setting the ICS bit in the ex1 word. */ | 
					
						
							|  |  |  | 	uregs->ex1 = PL_ICS_EX1(USER_PL, EX1_ICS(uregs->ex1)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	*regs = *uregs; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-12-17 20:08:09 -05:00
										 |  |  | enum tile_regset { | 
					
						
							|  |  |  | 	REGSET_GPR, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int tile_gpr_get(struct task_struct *target, | 
					
						
							|  |  |  | 			  const struct user_regset *regset, | 
					
						
							|  |  |  | 			  unsigned int pos, unsigned int count, | 
					
						
							|  |  |  | 			  void *kbuf, void __user *ubuf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct pt_regs regs; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	getregs(target, ®s); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return user_regset_copyout(&pos, &count, &kbuf, &ubuf, ®s, 0, | 
					
						
							|  |  |  | 				   sizeof(regs)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int tile_gpr_set(struct task_struct *target, | 
					
						
							|  |  |  | 			  const struct user_regset *regset, | 
					
						
							|  |  |  | 			  unsigned int pos, unsigned int count, | 
					
						
							|  |  |  | 			  const void *kbuf, const void __user *ubuf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 	struct pt_regs regs; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, ®s, 0, | 
					
						
							|  |  |  | 				 sizeof(regs)); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	putregs(target, ®s); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const struct user_regset tile_user_regset[] = { | 
					
						
							|  |  |  | 	[REGSET_GPR] = { | 
					
						
							|  |  |  | 		.core_note_type = NT_PRSTATUS, | 
					
						
							|  |  |  | 		.n = ELF_NGREG, | 
					
						
							|  |  |  | 		.size = sizeof(elf_greg_t), | 
					
						
							|  |  |  | 		.align = sizeof(elf_greg_t), | 
					
						
							|  |  |  | 		.get = tile_gpr_get, | 
					
						
							|  |  |  | 		.set = tile_gpr_set, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const struct user_regset_view tile_user_regset_view = { | 
					
						
							|  |  |  | 	.name = CHIP_ARCH_NAME, | 
					
						
							|  |  |  | 	.e_machine = ELF_ARCH, | 
					
						
							|  |  |  | 	.ei_osabi = ELF_OSABI, | 
					
						
							|  |  |  | 	.regsets = tile_user_regset, | 
					
						
							|  |  |  | 	.n = ARRAY_SIZE(tile_user_regset), | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const struct user_regset_view *task_user_regset_view(struct task_struct *task) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return &tile_user_regset_view; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-10-27 15:33:47 -07:00
										 |  |  | long arch_ptrace(struct task_struct *child, long request, | 
					
						
							|  |  |  | 		 unsigned long addr, unsigned long data) | 
					
						
							| 
									
										
										
										
											2010-05-28 23:09:12 -04:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2010-10-14 16:48:00 -04:00
										 |  |  | 	unsigned long __user *datap = (long __user __force *)data; | 
					
						
							| 
									
										
										
										
											2010-05-28 23:09:12 -04:00
										 |  |  | 	unsigned long tmp; | 
					
						
							|  |  |  | 	long ret = -EIO; | 
					
						
							| 
									
										
										
										
											2010-10-14 16:48:00 -04:00
										 |  |  | 	char *childreg; | 
					
						
							| 
									
										
										
										
											2010-10-28 15:47:06 -04:00
										 |  |  | 	struct pt_regs copyregs; | 
					
						
							| 
									
										
										
										
											2010-05-28 23:09:12 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	switch (request) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case PTRACE_PEEKUSR:  /* Read register from pt_regs. */ | 
					
						
							| 
									
										
										
										
											2010-10-27 15:34:04 -07:00
										 |  |  | 		if (addr >= PTREGS_SIZE) | 
					
						
							| 
									
										
										
										
											2010-05-28 23:09:12 -04:00
										 |  |  | 			break; | 
					
						
							| 
									
										
										
										
											2012-12-12 17:24:39 -05:00
										 |  |  | 		childreg = getregs(child, ©regs) + addr; | 
					
						
							| 
									
										
										
										
											2010-10-14 16:48:00 -04:00
										 |  |  | #ifdef CONFIG_COMPAT
 | 
					
						
							|  |  |  | 		if (is_compat_task()) { | 
					
						
							|  |  |  | 			if (addr & (sizeof(compat_long_t)-1)) | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			ret = put_user(*(compat_long_t *)childreg, | 
					
						
							|  |  |  | 				       (compat_long_t __user *)datap); | 
					
						
							|  |  |  | 		} else | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			if (addr & (sizeof(long)-1)) | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			ret = put_user(*(long *)childreg, datap); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2010-05-28 23:09:12 -04:00
										 |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case PTRACE_POKEUSR:  /* Write register in pt_regs. */ | 
					
						
							| 
									
										
										
										
											2010-10-27 15:34:04 -07:00
										 |  |  | 		if (addr >= PTREGS_SIZE) | 
					
						
							| 
									
										
										
										
											2010-05-28 23:09:12 -04:00
										 |  |  | 			break; | 
					
						
							| 
									
										
										
										
											2012-12-12 17:24:39 -05:00
										 |  |  | 		childreg = getregs(child, ©regs) + addr; | 
					
						
							| 
									
										
										
										
											2010-10-14 16:48:00 -04:00
										 |  |  | #ifdef CONFIG_COMPAT
 | 
					
						
							|  |  |  | 		if (is_compat_task()) { | 
					
						
							|  |  |  | 			if (addr & (sizeof(compat_long_t)-1)) | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			*(compat_long_t *)childreg = data; | 
					
						
							|  |  |  | 		} else | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			if (addr & (sizeof(long)-1)) | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			*(long *)childreg = data; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2012-12-12 17:24:39 -05:00
										 |  |  | 		putregs(child, ©regs); | 
					
						
							| 
									
										
										
										
											2010-07-02 14:17:52 -04:00
										 |  |  | 		ret = 0; | 
					
						
							| 
									
										
										
										
											2010-05-28 23:09:12 -04:00
										 |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case PTRACE_GETREGS:  /* Get all registers from the child. */ | 
					
						
							| 
									
										
										
										
											2012-12-17 20:08:10 -05:00
										 |  |  | 		ret = copy_regset_to_user(child, &tile_user_regset_view, | 
					
						
							|  |  |  | 					  REGSET_GPR, 0, | 
					
						
							|  |  |  | 					  sizeof(struct pt_regs), datap); | 
					
						
							| 
									
										
										
										
											2010-05-28 23:09:12 -04:00
										 |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case PTRACE_SETREGS:  /* Set all registers in the child. */ | 
					
						
							| 
									
										
										
										
											2012-12-17 20:08:10 -05:00
										 |  |  | 		ret = copy_regset_from_user(child, &tile_user_regset_view, | 
					
						
							|  |  |  | 					    REGSET_GPR, 0, | 
					
						
							|  |  |  | 					    sizeof(struct pt_regs), datap); | 
					
						
							| 
									
										
										
										
											2010-05-28 23:09:12 -04:00
										 |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case PTRACE_GETFPREGS:  /* Get the child FPU state. */ | 
					
						
							|  |  |  | 	case PTRACE_SETFPREGS:  /* Set the child FPU state. */ | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case PTRACE_SETOPTIONS: | 
					
						
							|  |  |  | 		/* Support TILE-specific ptrace options. */ | 
					
						
							| 
									
										
										
										
											2012-12-13 11:34:45 -05:00
										 |  |  | 		BUILD_BUG_ON(PTRACE_O_MASK_TILE & PTRACE_O_MASK); | 
					
						
							| 
									
										
										
										
											2010-05-28 23:09:12 -04:00
										 |  |  | 		tmp = data & PTRACE_O_MASK_TILE; | 
					
						
							|  |  |  | 		data &= ~PTRACE_O_MASK_TILE; | 
					
						
							|  |  |  | 		ret = ptrace_request(child, request, addr, data); | 
					
						
							| 
									
										
										
										
											2012-12-13 11:34:45 -05:00
										 |  |  | 		if (ret == 0) { | 
					
						
							|  |  |  | 			unsigned int flags = child->ptrace; | 
					
						
							|  |  |  | 			flags &= ~(PTRACE_O_MASK_TILE << PT_OPT_FLAG_SHIFT); | 
					
						
							|  |  |  | 			flags |= (tmp << PT_OPT_FLAG_SHIFT); | 
					
						
							|  |  |  | 			child->ptrace = flags; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2010-05-28 23:09:12 -04:00
										 |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | #ifdef CONFIG_COMPAT
 | 
					
						
							|  |  |  | 		if (task_thread_info(current)->status & TS_COMPAT) { | 
					
						
							|  |  |  | 			ret = compat_ptrace_request(child, request, | 
					
						
							|  |  |  | 						    addr, data); | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 		ret = ptrace_request(child, request, addr, data); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #ifdef CONFIG_COMPAT
 | 
					
						
							|  |  |  | /* Not used; we handle compat issues in arch_ptrace() directly. */ | 
					
						
							|  |  |  | long compat_arch_ptrace(struct task_struct *child, compat_long_t request, | 
					
						
							|  |  |  | 			       compat_ulong_t addr, compat_ulong_t data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	BUG(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-12-22 00:21:10 -05:00
										 |  |  | int do_syscall_trace_enter(struct pt_regs *regs) | 
					
						
							| 
									
										
										
										
											2010-05-28 23:09:12 -04:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2013-01-21 19:54:57 -05:00
										 |  |  | 	if (test_thread_flag(TIF_SYSCALL_TRACE)) { | 
					
						
							|  |  |  | 		if (tracehook_report_syscall_entry(regs)) | 
					
						
							|  |  |  | 			regs->regs[TREG_SYSCALL_NR] = -1; | 
					
						
							| 
									
										
										
										
											2012-12-22 00:21:10 -05:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2010-05-28 23:09:12 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-01-21 19:54:57 -05:00
										 |  |  | 	if (test_thread_flag(TIF_SYSCALL_TRACEPOINT)) | 
					
						
							|  |  |  | 		trace_sys_enter(regs, regs->regs[TREG_SYSCALL_NR]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-12-22 00:21:10 -05:00
										 |  |  | 	return regs->regs[TREG_SYSCALL_NR]; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2010-05-28 23:09:12 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-12-22 00:21:10 -05:00
										 |  |  | void do_syscall_trace_exit(struct pt_regs *regs) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2013-08-09 15:42:56 -04:00
										 |  |  | 	long errno; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * The standard tile calling convention returns the value (or negative | 
					
						
							|  |  |  | 	 * errno) in r0, and zero (or positive errno) in r1. | 
					
						
							|  |  |  | 	 * It saves a couple of cycles on the hot path to do this work in | 
					
						
							|  |  |  | 	 * registers only as we return, rather than updating the in-memory | 
					
						
							|  |  |  | 	 * struct ptregs. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	errno = (long) regs->regs[0]; | 
					
						
							|  |  |  | 	if (errno < 0 && errno > -4096) | 
					
						
							|  |  |  | 		regs->regs[1] = -errno; | 
					
						
							|  |  |  | 	else | 
					
						
							|  |  |  | 		regs->regs[1] = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-01-21 19:54:57 -05:00
										 |  |  | 	if (test_thread_flag(TIF_SYSCALL_TRACE)) | 
					
						
							|  |  |  | 		tracehook_report_syscall_exit(regs, 0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (test_thread_flag(TIF_SYSCALL_TRACEPOINT)) | 
					
						
							| 
									
										
										
										
											2013-04-17 11:01:22 -04:00
										 |  |  | 		trace_sys_exit(regs, regs->regs[0]); | 
					
						
							| 
									
										
										
										
											2010-05-28 23:09:12 -04:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-06 16:04:13 -04:00
										 |  |  | void send_sigtrap(struct task_struct *tsk, struct pt_regs *regs) | 
					
						
							| 
									
										
										
										
											2010-05-28 23:09:12 -04:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct siginfo info; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	memset(&info, 0, sizeof(info)); | 
					
						
							|  |  |  | 	info.si_signo = SIGTRAP; | 
					
						
							|  |  |  | 	info.si_code  = TRAP_BRKPT; | 
					
						
							|  |  |  | 	info.si_addr  = (void __user *) regs->pc; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Send us the fakey SIGTRAP */ | 
					
						
							|  |  |  | 	force_sig_info(SIGTRAP, &info, tsk); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Handle synthetic interrupt delivered only by the simulator. */ | 
					
						
							|  |  |  | void __kprobes do_breakpoint(struct pt_regs* regs, int fault_num) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2013-08-06 16:04:13 -04:00
										 |  |  | 	send_sigtrap(current, regs); | 
					
						
							| 
									
										
										
										
											2010-05-28 23:09:12 -04:00
										 |  |  | } |