| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
 | 2 |  *  linux/arch/m68knommu/kernel/ptrace.c | 
 | 3 |  * | 
 | 4 |  *  Copyright (C) 1994 by Hamish Macdonald | 
 | 5 |  *  Taken from linux/kernel/ptrace.c and modified for M680x0. | 
 | 6 |  *  linux/kernel/ptrace.c is by Ross Biro 1/23/92, edited by Linus Torvalds | 
 | 7 |  * | 
 | 8 |  * This file is subject to the terms and conditions of the GNU General | 
 | 9 |  * Public License.  See the file COPYING in the main directory of | 
 | 10 |  * this archive for more details. | 
 | 11 |  */ | 
 | 12 |  | 
 | 13 | #include <linux/kernel.h> | 
 | 14 | #include <linux/sched.h> | 
 | 15 | #include <linux/mm.h> | 
 | 16 | #include <linux/smp.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 17 | #include <linux/errno.h> | 
 | 18 | #include <linux/ptrace.h> | 
 | 19 | #include <linux/user.h> | 
| Jesper Juhl | 7ed20e1 | 2005-05-01 08:59:14 -0700 | [diff] [blame] | 20 | #include <linux/signal.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 21 |  | 
 | 22 | #include <asm/uaccess.h> | 
 | 23 | #include <asm/page.h> | 
 | 24 | #include <asm/pgtable.h> | 
 | 25 | #include <asm/system.h> | 
 | 26 | #include <asm/processor.h> | 
 | 27 |  | 
 | 28 | /* | 
 | 29 |  * does not yet catch signals sent when the child dies. | 
 | 30 |  * in exit.c or in signal.c. | 
 | 31 |  */ | 
 | 32 |  | 
 | 33 | /* determines which bits in the SR the user has access to. */ | 
 | 34 | /* 1 = access 0 = no access */ | 
 | 35 | #define SR_MASK 0x001f | 
 | 36 |  | 
 | 37 | /* sets the trace bits. */ | 
 | 38 | #define TRACE_BITS 0x8000 | 
 | 39 |  | 
 | 40 | /* Find the stack offset for a register, relative to thread.esp0. */ | 
 | 41 | #define PT_REG(reg)	((long)&((struct pt_regs *)0)->reg) | 
 | 42 | #define SW_REG(reg)	((long)&((struct switch_stack *)0)->reg \ | 
 | 43 | 			 - sizeof(struct switch_stack)) | 
 | 44 | /* Mapping from PT_xxx to the stack offset at which the register is | 
 | 45 |    saved.  Notice that usp has no stack-slot and needs to be treated | 
 | 46 |    specially (see get_reg/put_reg below). */ | 
 | 47 | static int regoff[] = { | 
 | 48 | 	PT_REG(d1), PT_REG(d2), PT_REG(d3), PT_REG(d4), | 
 | 49 | 	PT_REG(d5), SW_REG(d6), SW_REG(d7), PT_REG(a0), | 
 | 50 | 	PT_REG(a1), PT_REG(a2), SW_REG(a3), SW_REG(a4), | 
 | 51 | 	SW_REG(a5), SW_REG(a6), PT_REG(d0), -1, | 
 | 52 | 	PT_REG(orig_d0), PT_REG(sr), PT_REG(pc), | 
 | 53 | }; | 
 | 54 |  | 
 | 55 | /* | 
 | 56 |  * Get contents of register REGNO in task TASK. | 
 | 57 |  */ | 
 | 58 | static inline long get_reg(struct task_struct *task, int regno) | 
 | 59 | { | 
 | 60 | 	unsigned long *addr; | 
 | 61 |  | 
 | 62 | 	if (regno == PT_USP) | 
 | 63 | 		addr = &task->thread.usp; | 
| Ahmed S. Darwish | bf0059b | 2007-02-10 01:43:46 -0800 | [diff] [blame] | 64 | 	else if (regno < ARRAY_SIZE(regoff)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 65 | 		addr = (unsigned long *)(task->thread.esp0 + regoff[regno]); | 
 | 66 | 	else | 
 | 67 | 		return 0; | 
 | 68 | 	return *addr; | 
 | 69 | } | 
 | 70 |  | 
 | 71 | /* | 
 | 72 |  * Write contents of register REGNO in task TASK. | 
 | 73 |  */ | 
 | 74 | static inline int put_reg(struct task_struct *task, int regno, | 
 | 75 | 			  unsigned long data) | 
 | 76 | { | 
 | 77 | 	unsigned long *addr; | 
 | 78 |  | 
 | 79 | 	if (regno == PT_USP) | 
 | 80 | 		addr = &task->thread.usp; | 
| Ahmed S. Darwish | bf0059b | 2007-02-10 01:43:46 -0800 | [diff] [blame] | 81 | 	else if (regno < ARRAY_SIZE(regoff)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 82 | 		addr = (unsigned long *) (task->thread.esp0 + regoff[regno]); | 
 | 83 | 	else | 
 | 84 | 		return -1; | 
 | 85 | 	*addr = data; | 
 | 86 | 	return 0; | 
 | 87 | } | 
 | 88 |  | 
 | 89 | /* | 
 | 90 |  * Called by kernel/ptrace.c when detaching.. | 
 | 91 |  * | 
 | 92 |  * Make sure the single step bit is not set. | 
 | 93 |  */ | 
 | 94 | void ptrace_disable(struct task_struct *child) | 
 | 95 | { | 
 | 96 | 	unsigned long tmp; | 
 | 97 | 	/* make sure the single step bit is not set. */ | 
 | 98 | 	tmp = get_reg(child, PT_SR) & ~(TRACE_BITS << 16); | 
 | 99 | 	put_reg(child, PT_SR, tmp); | 
 | 100 | } | 
 | 101 |  | 
| Greg Ungerer | afc7cd8 | 2006-01-10 16:42:18 +1000 | [diff] [blame] | 102 | long arch_ptrace(struct task_struct *child, long request, long addr, long data) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 103 | { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 104 | 	int ret; | 
 | 105 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 106 | 	switch (request) { | 
 | 107 | 		/* when I and D space are separate, these will need to be fixed. */ | 
 | 108 | 		case PTRACE_PEEKTEXT: /* read word at location addr. */  | 
| Alexey Dobriyan | 7664732 | 2007-07-17 04:03:43 -0700 | [diff] [blame] | 109 | 		case PTRACE_PEEKDATA: | 
 | 110 | 			ret = generic_ptrace_peekdata(child, addr, data); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 111 | 			break; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 112 |  | 
 | 113 | 		/* read the word at location addr in the USER area. */ | 
 | 114 | 		case PTRACE_PEEKUSR: { | 
 | 115 | 			unsigned long tmp; | 
 | 116 | 			 | 
 | 117 | 			ret = -EIO; | 
 | 118 | 			if ((addr & 3) || addr < 0 || | 
 | 119 | 			    addr > sizeof(struct user) - 3) | 
 | 120 | 				break; | 
 | 121 | 			 | 
 | 122 | 			tmp = 0;  /* Default return condition */ | 
 | 123 | 			addr = addr >> 2; /* temporary hack. */ | 
 | 124 | 			ret = -EIO; | 
 | 125 | 			if (addr < 19) { | 
 | 126 | 				tmp = get_reg(child, addr); | 
 | 127 | 				if (addr == PT_SR) | 
 | 128 | 					tmp >>= 16; | 
 | 129 | 			} else if (addr >= 21 && addr < 49) { | 
 | 130 | 				tmp = child->thread.fp[addr - 21]; | 
 | 131 | #ifdef CONFIG_M68KFPU_EMU | 
 | 132 | 				/* Convert internal fpu reg representation | 
 | 133 | 				 * into long double format | 
 | 134 | 				 */ | 
 | 135 | 				if (FPU_IS_EMU && (addr < 45) && !(addr % 3)) | 
 | 136 | 					tmp = ((tmp & 0xffff0000) << 15) | | 
 | 137 | 					      ((tmp & 0x0000ffff) << 16); | 
 | 138 | #endif | 
 | 139 | 			} else if (addr == 49) { | 
 | 140 | 				tmp = child->mm->start_code; | 
 | 141 | 			} else if (addr == 50) { | 
 | 142 | 				tmp = child->mm->start_data; | 
 | 143 | 			} else if (addr == 51) { | 
 | 144 | 				tmp = child->mm->end_code; | 
 | 145 | 			} else | 
 | 146 | 				break; | 
 | 147 | 			ret = put_user(tmp,(unsigned long *) data); | 
 | 148 | 			break; | 
 | 149 | 		} | 
 | 150 |  | 
 | 151 | 		/* when I and D space are separate, this will have to be fixed. */ | 
 | 152 | 		case PTRACE_POKETEXT: /* write the word at location addr. */ | 
 | 153 | 		case PTRACE_POKEDATA: | 
| Alexey Dobriyan | f284ce7 | 2007-07-17 04:03:44 -0700 | [diff] [blame] | 154 | 			ret = generic_ptrace_pokedata(child, addr, data); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 155 | 			break; | 
 | 156 |  | 
 | 157 | 		case PTRACE_POKEUSR: /* write the word at location addr in the USER area */ | 
 | 158 | 			ret = -EIO; | 
 | 159 | 			if ((addr & 3) || addr < 0 || | 
 | 160 | 			    addr > sizeof(struct user) - 3) | 
 | 161 | 				break; | 
 | 162 |  | 
 | 163 | 			addr = addr >> 2; /* temporary hack. */ | 
 | 164 | 			     | 
 | 165 | 			if (addr == PT_SR) { | 
 | 166 | 				data &= SR_MASK; | 
 | 167 | 				data <<= 16; | 
 | 168 | 				data |= get_reg(child, PT_SR) & ~(SR_MASK << 16); | 
 | 169 | 			} | 
 | 170 | 			if (addr < 19) { | 
 | 171 | 				if (put_reg(child, addr, data)) | 
 | 172 | 					break; | 
 | 173 | 				ret = 0; | 
 | 174 | 				break; | 
 | 175 | 			} | 
 | 176 | 			if (addr >= 21 && addr < 48) | 
 | 177 | 			{ | 
 | 178 | #ifdef CONFIG_M68KFPU_EMU | 
 | 179 | 				/* Convert long double format | 
 | 180 | 				 * into internal fpu reg representation | 
 | 181 | 				 */ | 
 | 182 | 				if (FPU_IS_EMU && (addr < 45) && !(addr % 3)) { | 
 | 183 | 					data = (unsigned long)data << 15; | 
 | 184 | 					data = (data & 0xffff0000) | | 
 | 185 | 					       ((data & 0x0000ffff) >> 1); | 
 | 186 | 				} | 
 | 187 | #endif | 
 | 188 | 				child->thread.fp[addr - 21] = data; | 
 | 189 | 				ret = 0; | 
 | 190 | 			} | 
 | 191 | 			break; | 
 | 192 |  | 
 | 193 | 		case PTRACE_SYSCALL: /* continue and stop at next (return from) syscall */ | 
 | 194 | 		case PTRACE_CONT: { /* restart after signal. */ | 
 | 195 | 			long tmp; | 
 | 196 |  | 
 | 197 | 			ret = -EIO; | 
| Jesper Juhl | 7ed20e1 | 2005-05-01 08:59:14 -0700 | [diff] [blame] | 198 | 			if (!valid_signal(data)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 199 | 				break; | 
 | 200 | 			if (request == PTRACE_SYSCALL) | 
 | 201 | 				set_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | 
 | 202 | 			else | 
 | 203 | 				clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | 
 | 204 | 			child->exit_code = data; | 
 | 205 | 			/* make sure the single step bit is not set. */ | 
 | 206 | 			tmp = get_reg(child, PT_SR) & ~(TRACE_BITS << 16); | 
 | 207 | 			put_reg(child, PT_SR, tmp); | 
 | 208 | 			wake_up_process(child); | 
 | 209 | 			ret = 0; | 
 | 210 | 			break; | 
 | 211 | 		} | 
 | 212 |  | 
 | 213 | 		/* | 
 | 214 | 		 * make the child exit.  Best I can do is send it a sigkill.  | 
 | 215 | 		 * perhaps it should be put in the status that it wants to  | 
 | 216 | 		 * exit. | 
 | 217 | 		 */ | 
 | 218 | 		case PTRACE_KILL: { | 
 | 219 | 			long tmp; | 
 | 220 |  | 
 | 221 | 			ret = 0; | 
 | 222 | 			if (child->exit_state == EXIT_ZOMBIE) /* already dead */ | 
 | 223 | 				break; | 
 | 224 | 			child->exit_code = SIGKILL; | 
 | 225 | 			/* make sure the single step bit is not set. */ | 
 | 226 | 			tmp = get_reg(child, PT_SR) & ~(TRACE_BITS << 16); | 
 | 227 | 			put_reg(child, PT_SR, tmp); | 
 | 228 | 			wake_up_process(child); | 
 | 229 | 			break; | 
 | 230 | 		} | 
 | 231 |  | 
 | 232 | 		case PTRACE_SINGLESTEP: {  /* set the trap flag. */ | 
 | 233 | 			long tmp; | 
 | 234 |  | 
 | 235 | 			ret = -EIO; | 
| Jesper Juhl | 7ed20e1 | 2005-05-01 08:59:14 -0700 | [diff] [blame] | 236 | 			if (!valid_signal(data)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 237 | 				break; | 
 | 238 | 			clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE); | 
 | 239 | 			tmp = get_reg(child, PT_SR) | (TRACE_BITS << 16); | 
 | 240 | 			put_reg(child, PT_SR, tmp); | 
 | 241 |  | 
 | 242 | 			child->exit_code = data; | 
 | 243 | 			/* give it a chance to run. */ | 
 | 244 | 			wake_up_process(child); | 
 | 245 | 			ret = 0; | 
 | 246 | 			break; | 
 | 247 | 		} | 
 | 248 |  | 
 | 249 | 		case PTRACE_DETACH:	/* detach a process that was attached. */ | 
 | 250 | 			ret = ptrace_detach(child, data); | 
 | 251 | 			break; | 
 | 252 |  | 
 | 253 | 		case PTRACE_GETREGS: { /* Get all gp regs from the child. */ | 
 | 254 | 		  	int i; | 
 | 255 | 			unsigned long tmp; | 
 | 256 | 			for (i = 0; i < 19; i++) { | 
 | 257 | 			    tmp = get_reg(child, i); | 
 | 258 | 			    if (i == PT_SR) | 
 | 259 | 				tmp >>= 16; | 
 | 260 | 			    if (put_user(tmp, (unsigned long *) data)) { | 
 | 261 | 				ret = -EFAULT; | 
 | 262 | 				break; | 
 | 263 | 			    } | 
 | 264 | 			    data += sizeof(long); | 
 | 265 | 			} | 
 | 266 | 			ret = 0; | 
 | 267 | 			break; | 
 | 268 | 		} | 
 | 269 |  | 
 | 270 | 		case PTRACE_SETREGS: { /* Set all gp regs in the child. */ | 
 | 271 | 			int i; | 
 | 272 | 			unsigned long tmp; | 
 | 273 | 			for (i = 0; i < 19; i++) { | 
 | 274 | 			    if (get_user(tmp, (unsigned long *) data)) { | 
 | 275 | 				ret = -EFAULT; | 
 | 276 | 				break; | 
 | 277 | 			    } | 
 | 278 | 			    if (i == PT_SR) { | 
 | 279 | 				tmp &= SR_MASK; | 
 | 280 | 				tmp <<= 16; | 
 | 281 | 				tmp |= get_reg(child, PT_SR) & ~(SR_MASK << 16); | 
 | 282 | 			    } | 
 | 283 | 			    put_reg(child, i, tmp); | 
 | 284 | 			    data += sizeof(long); | 
 | 285 | 			} | 
 | 286 | 			ret = 0; | 
 | 287 | 			break; | 
 | 288 | 		} | 
 | 289 |  | 
 | 290 | #ifdef PTRACE_GETFPREGS | 
 | 291 | 		case PTRACE_GETFPREGS: { /* Get the child FPU state. */ | 
 | 292 | 			ret = 0; | 
 | 293 | 			if (copy_to_user((void *)data, &child->thread.fp, | 
 | 294 | 					 sizeof(struct user_m68kfp_struct))) | 
 | 295 | 				ret = -EFAULT; | 
 | 296 | 			break; | 
 | 297 | 		} | 
 | 298 | #endif | 
 | 299 |  | 
 | 300 | #ifdef PTRACE_SETFPREGS | 
 | 301 | 		case PTRACE_SETFPREGS: { /* Set the child FPU state. */ | 
 | 302 | 			ret = 0; | 
 | 303 | 			if (copy_from_user(&child->thread.fp, (void *)data, | 
 | 304 | 					   sizeof(struct user_m68kfp_struct))) | 
 | 305 | 				ret = -EFAULT; | 
 | 306 | 			break; | 
 | 307 | 		} | 
 | 308 | #endif | 
 | 309 |  | 
 | 310 | 		default: | 
 | 311 | 			ret = -EIO; | 
 | 312 | 			break; | 
 | 313 | 	} | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 314 | 	return ret; | 
 | 315 | } | 
 | 316 |  | 
 | 317 | asmlinkage void syscall_trace(void) | 
 | 318 | { | 
 | 319 | 	if (!test_thread_flag(TIF_SYSCALL_TRACE)) | 
 | 320 | 		return; | 
 | 321 | 	if (!(current->ptrace & PT_PTRACED)) | 
 | 322 | 		return; | 
 | 323 | 	ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) | 
 | 324 | 				 ? 0x80 : 0)); | 
 | 325 | 	/* | 
 | 326 | 	 * this isn't the same as continuing with a signal, but it will do | 
 | 327 | 	 * for normal use.  strace only continues with a signal if the | 
 | 328 | 	 * stopping signal is not SIGTRAP.  -brl | 
 | 329 | 	 */ | 
 | 330 | 	if (current->exit_code) { | 
 | 331 | 		send_sig(current->exit_code, current, 1); | 
 | 332 | 		current->exit_code = 0; | 
 | 333 | 	} | 
 | 334 | } |