xref: /freebsd/sys/arm/arm/machdep_ptrace.c (revision b00ab754)
1 /*-
2  * Copyright (c) 2004 Olivier Houchard
3  * Copyright (c) 1994-1998 Mark Brinicombe.
4  * Copyright (c) 1994 Brini.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31 
32 #include <sys/param.h>
33 #include <sys/proc.h>
34 #include <sys/ptrace.h>
35 #include <sys/lock.h>
36 #include <sys/mutex.h>
37 
38 #include <machine/machdep.h>
39 #include <machine/db_machdep.h>
40 
41 static int
42 ptrace_read_int(struct thread *td, vm_offset_t addr, uint32_t *v)
43 {
44 
45 	if (proc_readmem(td, td->td_proc, addr, v, sizeof(*v)) != sizeof(*v))
46 		return (ENOMEM);
47 	return (0);
48 }
49 
50 static int
51 ptrace_write_int(struct thread *td, vm_offset_t addr, uint32_t v)
52 {
53 
54 	if (proc_writemem(td, td->td_proc, addr, &v, sizeof(v)) != sizeof(v))
55 		return (ENOMEM);
56 	return (0);
57 }
58 
59 static u_int
60 ptrace_get_usr_reg(void *cookie, int reg)
61 {
62 	int ret;
63 	struct thread *td = cookie;
64 
65 	KASSERT(((reg >= 0) && (reg <= ARM_REG_NUM_PC)),
66 	 ("reg is outside range"));
67 
68 	switch(reg) {
69 	case ARM_REG_NUM_PC:
70 		ret = td->td_frame->tf_pc;
71 		break;
72 	case ARM_REG_NUM_LR:
73 		ret = td->td_frame->tf_usr_lr;
74 		break;
75 	case ARM_REG_NUM_SP:
76 		ret = td->td_frame->tf_usr_sp;
77 		break;
78 	default:
79 		ret = *((register_t*)&td->td_frame->tf_r0 + reg);
80 		break;
81 	}
82 
83 	return (ret);
84 }
85 
86 static u_int
87 ptrace_get_usr_int(void* cookie, vm_offset_t offset, u_int* val)
88 {
89 	struct thread *td = cookie;
90 	u_int error;
91 
92 	error = ptrace_read_int(td, offset, val);
93 
94 	return (error);
95 }
96 
97 /**
98  * This function parses current instruction opcode and decodes
99  * any possible jump (change in PC) which might occur after
100  * the instruction is executed.
101  *
102  * @param     td                Thread structure of analysed task
103  * @param     cur_instr         Currently executed instruction
104  * @param     alt_next_address  Pointer to the variable where
105  *                              the destination address of the
106  *                              jump instruction shall be stored.
107  *
108  * @return    <0>               when jump is possible
109  *            <EINVAL>          otherwise
110  */
111 static int
112 ptrace_get_alternative_next(struct thread *td, uint32_t cur_instr,
113     uint32_t *alt_next_address)
114 {
115 	int error;
116 
117 	if (inst_branch(cur_instr) || inst_call(cur_instr) ||
118 	    inst_return(cur_instr)) {
119 		error = arm_predict_branch(td, cur_instr, td->td_frame->tf_pc,
120 		    alt_next_address, ptrace_get_usr_reg, ptrace_get_usr_int);
121 
122 		return (error);
123 	}
124 
125 	return (EINVAL);
126 }
127 
128 int
129 ptrace_single_step(struct thread *td)
130 {
131 	struct proc *p;
132 	int error, error_alt;
133 	uint32_t cur_instr, alt_next = 0;
134 
135 	/* TODO: This needs to be updated for Thumb-2 */
136 	if ((td->td_frame->tf_spsr & PSR_T) != 0)
137 		return (EINVAL);
138 
139 	KASSERT(td->td_md.md_ptrace_instr == 0,
140 	 ("Didn't clear single step"));
141 	KASSERT(td->td_md.md_ptrace_instr_alt == 0,
142 	 ("Didn't clear alternative single step"));
143 	p = td->td_proc;
144 	PROC_UNLOCK(p);
145 
146 	error = ptrace_read_int(td, td->td_frame->tf_pc,
147 	    &cur_instr);
148 	if (error)
149 		goto out;
150 
151 	error = ptrace_read_int(td, td->td_frame->tf_pc + INSN_SIZE,
152 	    &td->td_md.md_ptrace_instr);
153 	if (error == 0) {
154 		error = ptrace_write_int(td, td->td_frame->tf_pc + INSN_SIZE,
155 		    PTRACE_BREAKPOINT);
156 		if (error) {
157 			td->td_md.md_ptrace_instr = 0;
158 		} else {
159 			td->td_md.md_ptrace_addr = td->td_frame->tf_pc +
160 			    INSN_SIZE;
161 		}
162 	}
163 
164 	error_alt = ptrace_get_alternative_next(td, cur_instr, &alt_next);
165 	if (error_alt == 0) {
166 		error_alt = ptrace_read_int(td, alt_next,
167 		    &td->td_md.md_ptrace_instr_alt);
168 		if (error_alt) {
169 			td->td_md.md_ptrace_instr_alt = 0;
170 		} else {
171 			error_alt = ptrace_write_int(td, alt_next,
172 			    PTRACE_BREAKPOINT);
173 			if (error_alt)
174 				td->td_md.md_ptrace_instr_alt = 0;
175 			else
176 				td->td_md.md_ptrace_addr_alt = alt_next;
177 		}
178 	}
179 
180 out:
181 	PROC_LOCK(p);
182 	return ((error != 0) && (error_alt != 0));
183 }
184 
185 int
186 ptrace_clear_single_step(struct thread *td)
187 {
188 	struct proc *p;
189 
190 	/* TODO: This needs to be updated for Thumb-2 */
191 	if ((td->td_frame->tf_spsr & PSR_T) != 0)
192 		return (EINVAL);
193 
194 	if (td->td_md.md_ptrace_instr != 0) {
195 		p = td->td_proc;
196 		PROC_UNLOCK(p);
197 		ptrace_write_int(td, td->td_md.md_ptrace_addr,
198 		    td->td_md.md_ptrace_instr);
199 		PROC_LOCK(p);
200 		td->td_md.md_ptrace_instr = 0;
201 	}
202 
203 	if (td->td_md.md_ptrace_instr_alt != 0) {
204 		p = td->td_proc;
205 		PROC_UNLOCK(p);
206 		ptrace_write_int(td, td->td_md.md_ptrace_addr_alt,
207 		    td->td_md.md_ptrace_instr_alt);
208 		PROC_LOCK(p);
209 		td->td_md.md_ptrace_instr_alt = 0;
210 	}
211 
212 	return (0);
213 }
214 
215 int
216 ptrace_set_pc(struct thread *td, unsigned long addr)
217 {
218 	td->td_frame->tf_pc = addr;
219 	return (0);
220 }
221 
222 int
223 arm_predict_branch(void *cookie, u_int insn, register_t pc, register_t *new_pc,
224     u_int (*fetch_reg)(void*, int),
225     u_int (*read_int)(void*, vm_offset_t, u_int*))
226 {
227 	u_int addr, nregs, offset = 0;
228 	int error = 0;
229 
230 	switch ((insn >> 24) & 0xf) {
231 	case 0x2:	/* add pc, reg1, #value */
232 	case 0x0:	/* add pc, reg1, reg2, lsl #offset */
233 		addr = fetch_reg(cookie, (insn >> 16) & 0xf);
234 		if (((insn >> 16) & 0xf) == 15)
235 			addr += 8;
236 		if (insn & 0x0200000) {
237 			offset = (insn >> 7) & 0x1e;
238 			offset = (insn & 0xff) << (32 - offset) |
239 			    (insn & 0xff) >> offset;
240 		} else {
241 
242 			offset = fetch_reg(cookie, insn & 0x0f);
243 			if ((insn & 0x0000ff0) != 0x00000000) {
244 				if (insn & 0x10)
245 					nregs = fetch_reg(cookie,
246 					    (insn >> 8) & 0xf);
247 				else
248 					nregs = (insn >> 7) & 0x1f;
249 				switch ((insn >> 5) & 3) {
250 				case 0:
251 					/* lsl */
252 					offset = offset << nregs;
253 					break;
254 				case 1:
255 					/* lsr */
256 					offset = offset >> nregs;
257 					break;
258 				default:
259 					break; /* XXX */
260 				}
261 
262 			}
263 			*new_pc = addr + offset;
264 			return (0);
265 
266 		}
267 
268 	case 0xa:	/* b ... */
269 	case 0xb:	/* bl ... */
270 		addr = ((insn << 2) & 0x03ffffff);
271 		if (addr & 0x02000000)
272 			addr |= 0xfc000000;
273 		*new_pc = (pc + 8 + addr);
274 		return (0);
275 	case 0x7:	/* ldr pc, [pc, reg, lsl #2] */
276 		addr = fetch_reg(cookie, insn & 0xf);
277 		addr = pc + 8 + (addr << 2);
278 		error = read_int(cookie, addr, &addr);
279 		*new_pc = addr;
280 		return (error);
281 	case 0x1:	/* mov pc, reg */
282 		*new_pc = fetch_reg(cookie, insn & 0xf);
283 		return (0);
284 	case 0x4:
285 	case 0x5:	/* ldr pc, [reg] */
286 		addr = fetch_reg(cookie, (insn >> 16) & 0xf);
287 		/* ldr pc, [reg, #offset] */
288 		if (insn & (1 << 24))
289 			offset = insn & 0xfff;
290 		if (insn & 0x00800000)
291 			addr += offset;
292 		else
293 			addr -= offset;
294 		error = read_int(cookie, addr, &addr);
295 		*new_pc = addr;
296 
297 		return (error);
298 	case 0x8:	/* ldmxx reg, {..., pc} */
299 	case 0x9:
300 		addr = fetch_reg(cookie, (insn >> 16) & 0xf);
301 		nregs = (insn  & 0x5555) + ((insn  >> 1) & 0x5555);
302 		nregs = (nregs & 0x3333) + ((nregs >> 2) & 0x3333);
303 		nregs = (nregs + (nregs >> 4)) & 0x0f0f;
304 		nregs = (nregs + (nregs >> 8)) & 0x001f;
305 		switch ((insn >> 23) & 0x3) {
306 		case 0x0:	/* ldmda */
307 			addr = addr - 0;
308 			break;
309 		case 0x1:	/* ldmia */
310 			addr = addr + 0 + ((nregs - 1) << 2);
311 			break;
312 		case 0x2:	/* ldmdb */
313 			addr = addr - 4;
314 			break;
315 		case 0x3:	/* ldmib */
316 			addr = addr + 4 + ((nregs - 1) << 2);
317 			break;
318 		}
319 		error = read_int(cookie, addr, &addr);
320 		*new_pc = addr;
321 
322 		return (error);
323 	default:
324 		return (EINVAL);
325 	}
326 }
327