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