xref: /freebsd-14.2/sys/arm/arm/machdep_ptrace.c (revision 685dc743)
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 #include <sys/param.h>
31 #include <sys/proc.h>
32 #include <sys/ptrace.h>
33 #include <sys/lock.h>
34 #include <sys/mutex.h>
35 
36 #include <machine/machdep.h>
37 #include <machine/db_machdep.h>
38 
39 static int
ptrace_read_int(struct thread * td,vm_offset_t addr,uint32_t * v)40 ptrace_read_int(struct thread *td, vm_offset_t addr, uint32_t *v)
41 {
42 
43 	if (proc_readmem(td, td->td_proc, addr, v, sizeof(*v)) != sizeof(*v))
44 		return (ENOMEM);
45 	return (0);
46 }
47 
48 static int
ptrace_write_int(struct thread * td,vm_offset_t addr,uint32_t v)49 ptrace_write_int(struct thread *td, vm_offset_t addr, uint32_t v)
50 {
51 
52 	if (proc_writemem(td, td->td_proc, addr, &v, sizeof(v)) != sizeof(v))
53 		return (ENOMEM);
54 	return (0);
55 }
56 
57 static u_int
ptrace_get_usr_reg(void * cookie,int reg)58 ptrace_get_usr_reg(void *cookie, int reg)
59 {
60 	int ret;
61 	struct thread *td = cookie;
62 
63 	KASSERT(((reg >= 0) && (reg <= ARM_REG_NUM_PC)),
64 	 ("reg is outside range"));
65 
66 	switch(reg) {
67 	case ARM_REG_NUM_PC:
68 		ret = td->td_frame->tf_pc;
69 		break;
70 	case ARM_REG_NUM_LR:
71 		ret = td->td_frame->tf_usr_lr;
72 		break;
73 	case ARM_REG_NUM_SP:
74 		ret = td->td_frame->tf_usr_sp;
75 		break;
76 	default:
77 		ret = *((register_t*)&td->td_frame->tf_r0 + reg);
78 		break;
79 	}
80 
81 	return (ret);
82 }
83 
84 static u_int
ptrace_get_usr_int(void * cookie,vm_offset_t offset,u_int * val)85 ptrace_get_usr_int(void* cookie, vm_offset_t offset, u_int* val)
86 {
87 	struct thread *td = cookie;
88 	u_int error;
89 
90 	error = ptrace_read_int(td, offset, val);
91 
92 	return (error);
93 }
94 
95 /**
96  * This function parses current instruction opcode and decodes
97  * any possible jump (change in PC) which might occur after
98  * the instruction is executed.
99  *
100  * @param     td                Thread structure of analysed task
101  * @param     cur_instr         Currently executed instruction
102  * @param     alt_next_address  Pointer to the variable where
103  *                              the destination address of the
104  *                              jump instruction shall be stored.
105  *
106  * @return    <0>               when jump is possible
107  *            <EINVAL>          otherwise
108  */
109 static int
ptrace_get_alternative_next(struct thread * td,uint32_t cur_instr,uint32_t * alt_next_address)110 ptrace_get_alternative_next(struct thread *td, uint32_t cur_instr,
111     uint32_t *alt_next_address)
112 {
113 	int error;
114 
115 	if (inst_branch(cur_instr) || inst_call(cur_instr) ||
116 	    inst_return(cur_instr)) {
117 		error = arm_predict_branch(td, cur_instr, td->td_frame->tf_pc,
118 		    alt_next_address, ptrace_get_usr_reg, ptrace_get_usr_int);
119 
120 		return (error);
121 	}
122 
123 	return (EINVAL);
124 }
125 
126 int
ptrace_single_step(struct thread * td)127 ptrace_single_step(struct thread *td)
128 {
129 	struct proc *p;
130 	int error, error_alt;
131 	uint32_t cur_instr, alt_next = 0;
132 
133 	/* TODO: This needs to be updated for Thumb-2 */
134 	if ((td->td_frame->tf_spsr & PSR_T) != 0)
135 		return (EINVAL);
136 
137 	KASSERT(td->td_md.md_ptrace_instr == 0,
138 	 ("Didn't clear single step"));
139 	KASSERT(td->td_md.md_ptrace_instr_alt == 0,
140 	 ("Didn't clear alternative single step"));
141 	p = td->td_proc;
142 	PROC_UNLOCK(p);
143 
144 	error = ptrace_read_int(td, td->td_frame->tf_pc,
145 	    &cur_instr);
146 	if (error)
147 		goto out;
148 
149 	error = ptrace_read_int(td, td->td_frame->tf_pc + INSN_SIZE,
150 	    &td->td_md.md_ptrace_instr);
151 	if (error == 0) {
152 		error = ptrace_write_int(td, td->td_frame->tf_pc + INSN_SIZE,
153 		    PTRACE_BREAKPOINT);
154 		if (error) {
155 			td->td_md.md_ptrace_instr = 0;
156 		} else {
157 			td->td_md.md_ptrace_addr = td->td_frame->tf_pc +
158 			    INSN_SIZE;
159 		}
160 	}
161 
162 	error_alt = ptrace_get_alternative_next(td, cur_instr, &alt_next);
163 	if (error_alt == 0) {
164 		error_alt = ptrace_read_int(td, alt_next,
165 		    &td->td_md.md_ptrace_instr_alt);
166 		if (error_alt) {
167 			td->td_md.md_ptrace_instr_alt = 0;
168 		} else {
169 			error_alt = ptrace_write_int(td, alt_next,
170 			    PTRACE_BREAKPOINT);
171 			if (error_alt)
172 				td->td_md.md_ptrace_instr_alt = 0;
173 			else
174 				td->td_md.md_ptrace_addr_alt = alt_next;
175 		}
176 	}
177 
178 out:
179 	PROC_LOCK(p);
180 	return ((error != 0) && (error_alt != 0));
181 }
182 
183 int
ptrace_clear_single_step(struct thread * td)184 ptrace_clear_single_step(struct thread *td)
185 {
186 	struct proc *p;
187 
188 	/* TODO: This needs to be updated for Thumb-2 */
189 	if ((td->td_frame->tf_spsr & PSR_T) != 0)
190 		return (EINVAL);
191 
192 	if (td->td_md.md_ptrace_instr != 0) {
193 		p = td->td_proc;
194 		PROC_UNLOCK(p);
195 		ptrace_write_int(td, td->td_md.md_ptrace_addr,
196 		    td->td_md.md_ptrace_instr);
197 		PROC_LOCK(p);
198 		td->td_md.md_ptrace_instr = 0;
199 	}
200 
201 	if (td->td_md.md_ptrace_instr_alt != 0) {
202 		p = td->td_proc;
203 		PROC_UNLOCK(p);
204 		ptrace_write_int(td, td->td_md.md_ptrace_addr_alt,
205 		    td->td_md.md_ptrace_instr_alt);
206 		PROC_LOCK(p);
207 		td->td_md.md_ptrace_instr_alt = 0;
208 	}
209 
210 	return (0);
211 }
212 
213 int
ptrace_set_pc(struct thread * td,unsigned long addr)214 ptrace_set_pc(struct thread *td, unsigned long addr)
215 {
216 	td->td_frame->tf_pc = addr;
217 	return (0);
218 }
219 
220 int
arm_predict_branch(void * cookie,u_int insn,register_t pc,register_t * new_pc,u_int (* fetch_reg)(void *,int),u_int (* read_int)(void *,vm_offset_t,u_int *))221 arm_predict_branch(void *cookie, u_int insn, register_t pc, register_t *new_pc,
222     u_int (*fetch_reg)(void*, int),
223     u_int (*read_int)(void*, vm_offset_t, u_int*))
224 {
225 	u_int addr, nregs, offset = 0;
226 	int error = 0;
227 
228 	switch ((insn >> 24) & 0xf) {
229 	case 0x2:	/* add pc, reg1, #value */
230 	case 0x0:	/* add pc, reg1, reg2, lsl #offset */
231 		addr = fetch_reg(cookie, (insn >> 16) & 0xf);
232 		if (((insn >> 16) & 0xf) == 15)
233 			addr += 8;
234 		if (insn & 0x0200000) {
235 			offset = (insn >> 7) & 0x1e;
236 			offset = (insn & 0xff) << (32 - offset) |
237 			    (insn & 0xff) >> offset;
238 		} else {
239 			offset = fetch_reg(cookie, insn & 0x0f);
240 			if ((insn & 0x0000ff0) != 0x00000000) {
241 				if (insn & 0x10)
242 					nregs = fetch_reg(cookie,
243 					    (insn >> 8) & 0xf);
244 				else
245 					nregs = (insn >> 7) & 0x1f;
246 				switch ((insn >> 5) & 3) {
247 				case 0:
248 					/* lsl */
249 					offset = offset << nregs;
250 					break;
251 				case 1:
252 					/* lsr */
253 					offset = offset >> nregs;
254 					break;
255 				default:
256 					break; /* XXX */
257 				}
258 			}
259 			*new_pc = addr + offset;
260 			return (0);
261 		}
262 
263 	case 0xa:	/* b ... */
264 	case 0xb:	/* bl ... */
265 		addr = ((insn << 2) & 0x03ffffff);
266 		if (addr & 0x02000000)
267 			addr |= 0xfc000000;
268 		*new_pc = (pc + 8 + addr);
269 		return (0);
270 	case 0x7:	/* ldr pc, [pc, reg, lsl #2] */
271 		addr = fetch_reg(cookie, insn & 0xf);
272 		addr = pc + 8 + (addr << 2);
273 		error = read_int(cookie, addr, &addr);
274 		*new_pc = addr;
275 		return (error);
276 	case 0x1:	/* mov pc, reg */
277 		*new_pc = fetch_reg(cookie, insn & 0xf);
278 		return (0);
279 	case 0x4:
280 	case 0x5:	/* ldr pc, [reg] */
281 		addr = fetch_reg(cookie, (insn >> 16) & 0xf);
282 		/* ldr pc, [reg, #offset] */
283 		if (insn & (1 << 24))
284 			offset = insn & 0xfff;
285 		if (insn & 0x00800000)
286 			addr += offset;
287 		else
288 			addr -= offset;
289 		error = read_int(cookie, addr, &addr);
290 		*new_pc = addr;
291 
292 		return (error);
293 	case 0x8:	/* ldmxx reg, {..., pc} */
294 	case 0x9:
295 		addr = fetch_reg(cookie, (insn >> 16) & 0xf);
296 		nregs = (insn  & 0x5555) + ((insn  >> 1) & 0x5555);
297 		nregs = (nregs & 0x3333) + ((nregs >> 2) & 0x3333);
298 		nregs = (nregs + (nregs >> 4)) & 0x0f0f;
299 		nregs = (nregs + (nregs >> 8)) & 0x001f;
300 		switch ((insn >> 23) & 0x3) {
301 		case 0x0:	/* ldmda */
302 			addr = addr - 0;
303 			break;
304 		case 0x1:	/* ldmia */
305 			addr = addr + 0 + ((nregs - 1) << 2);
306 			break;
307 		case 0x2:	/* ldmdb */
308 			addr = addr - 4;
309 			break;
310 		case 0x3:	/* ldmib */
311 			addr = addr + 4 + ((nregs - 1) << 2);
312 			break;
313 		}
314 		error = read_int(cookie, addr, &addr);
315 		*new_pc = addr;
316 
317 		return (error);
318 	default:
319 		return (EINVAL);
320 	}
321 }
322