1139735Simp/*-
2129198Scognet * Copyright (c) 2004 Olivier Houchard
3129198Scognet * Copyright (c) 1994-1998 Mark Brinicombe.
4129198Scognet * Copyright (c) 1994 Brini.
5129198Scognet * All rights reserved.
6129198Scognet *
7129198Scognet * Redistribution and use in source and binary forms, with or without
8129198Scognet * modification, are permitted provided that the following conditions
9129198Scognet * are met:
10129198Scognet * 1. Redistributions of source code must retain the above copyright
11129198Scognet *    notice, this list of conditions and the following disclaimer.
12129198Scognet * 2. Redistributions in binary form must reproduce the above copyright
13129198Scognet *    notice, this list of conditions and the following disclaimer in the
14129198Scognet *    documentation and/or other materials provided with the distribution.
15129198Scognet *
16315059Smmel * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17315059Smmel * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18315059Smmel * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19315059Smmel * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20315059Smmel * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21315059Smmel * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22315059Smmel * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23129198Scognet * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24129198Scognet * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25129198Scognet * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26129198Scognet * SUCH DAMAGE.
27129198Scognet */
28129198Scognet
29129198Scognet#include <sys/cdefs.h>
30129198Scognet__FBSDID("$FreeBSD: stable/11/sys/arm/arm/machdep_ptrace.c 317004 2017-04-16 07:21:20Z mmel $");
31129198Scognet
32129198Scognet#include <sys/param.h>
33141378Snjl#include <sys/proc.h>
34315059Smmel#include <sys/ptrace.h>
35129198Scognet#include <sys/mutex.h>
36129198Scognet
37315059Smmel#include <machine/machdep.h>
38290273Szbb#include <machine/db_machdep.h>
39129198Scognet
40140001Scognetstatic int
41291961Smarkjptrace_read_int(struct thread *td, vm_offset_t addr, uint32_t *v)
42140001Scognet{
43155922Sjhb
44291961Smarkj	if (proc_readmem(td, td->td_proc, addr, v, sizeof(*v)) != sizeof(*v))
45291961Smarkj		return (ENOMEM);
46291961Smarkj	return (0);
47140001Scognet}
48140001Scognet
49140001Scognetstatic int
50291961Smarkjptrace_write_int(struct thread *td, vm_offset_t addr, uint32_t v)
51140001Scognet{
52155922Sjhb
53291961Smarkj	if (proc_writemem(td, td->td_proc, addr, &v, sizeof(v)) != sizeof(v))
54291961Smarkj		return (ENOMEM);
55291961Smarkj	return (0);
56140001Scognet}
57140001Scognet
58290273Szbbstatic u_int
59290273Szbbptrace_get_usr_reg(void *cookie, int reg)
60290273Szbb{
61290273Szbb	int ret;
62290273Szbb	struct thread *td = cookie;
63290273Szbb
64290273Szbb	KASSERT(((reg >= 0) && (reg <= ARM_REG_NUM_PC)),
65290273Szbb	 ("reg is outside range"));
66290273Szbb
67290273Szbb	switch(reg) {
68290273Szbb	case ARM_REG_NUM_PC:
69290273Szbb		ret = td->td_frame->tf_pc;
70290273Szbb		break;
71290273Szbb	case ARM_REG_NUM_LR:
72290273Szbb		ret = td->td_frame->tf_usr_lr;
73290273Szbb		break;
74290273Szbb	case ARM_REG_NUM_SP:
75290273Szbb		ret = td->td_frame->tf_usr_sp;
76290273Szbb		break;
77290273Szbb	default:
78290273Szbb		ret = *((register_t*)&td->td_frame->tf_r0 + reg);
79290273Szbb		break;
80290273Szbb	}
81290273Szbb
82290273Szbb	return (ret);
83290273Szbb}
84290273Szbb
85290273Szbbstatic u_int
86290273Szbbptrace_get_usr_int(void* cookie, vm_offset_t offset, u_int* val)
87290273Szbb{
88290273Szbb	struct thread *td = cookie;
89290273Szbb	u_int error;
90290273Szbb
91290273Szbb	error = ptrace_read_int(td, offset, val);
92290273Szbb
93290273Szbb	return (error);
94290273Szbb}
95290273Szbb
96290273Szbb/**
97290273Szbb * This function parses current instruction opcode and decodes
98290273Szbb * any possible jump (change in PC) which might occur after
99290273Szbb * the instruction is executed.
100290273Szbb *
101290273Szbb * @param     td                Thread structure of analysed task
102290273Szbb * @param     cur_instr         Currently executed instruction
103290273Szbb * @param     alt_next_address  Pointer to the variable where
104290273Szbb *                              the destination address of the
105290273Szbb *                              jump instruction shall be stored.
106290273Szbb *
107290273Szbb * @return    <0>               when jump is possible
108290273Szbb *            <EINVAL>          otherwise
109290273Szbb */
110290273Szbbstatic int
111290273Szbbptrace_get_alternative_next(struct thread *td, uint32_t cur_instr,
112290273Szbb    uint32_t *alt_next_address)
113290273Szbb{
114290273Szbb	int error;
115290273Szbb
116290273Szbb	if (inst_branch(cur_instr) || inst_call(cur_instr) ||
117290273Szbb	    inst_return(cur_instr)) {
118290273Szbb		error = arm_predict_branch(td, cur_instr, td->td_frame->tf_pc,
119290273Szbb		    alt_next_address, ptrace_get_usr_reg, ptrace_get_usr_int);
120290273Szbb
121290273Szbb		return (error);
122290273Szbb	}
123290273Szbb
124290273Szbb	return (EINVAL);
125290273Szbb}
126290273Szbb
127129198Scognetint
128129198Scognetptrace_single_step(struct thread *td)
129129198Scognet{
130155922Sjhb	struct proc *p;
131290273Szbb	int error, error_alt;
132290273Szbb	uint32_t cur_instr, alt_next = 0;
133283366Sandrew
134282779Sandrew	/* TODO: This needs to be updated for Thumb-2 */
135282779Sandrew	if ((td->td_frame->tf_spsr & PSR_T) != 0)
136282779Sandrew		return (EINVAL);
137282779Sandrew
138140001Scognet	KASSERT(td->td_md.md_ptrace_instr == 0,
139140001Scognet	 ("Didn't clear single step"));
140290273Szbb	KASSERT(td->td_md.md_ptrace_instr_alt == 0,
141290273Szbb	 ("Didn't clear alternative single step"));
142155922Sjhb	p = td->td_proc;
143155922Sjhb	PROC_UNLOCK(p);
144290273Szbb
145290273Szbb	error = ptrace_read_int(td, td->td_frame->tf_pc,
146290273Szbb	    &cur_instr);
147140001Scognet	if (error)
148155922Sjhb		goto out;
149290273Szbb
150290273Szbb	error = ptrace_read_int(td, td->td_frame->tf_pc + INSN_SIZE,
151290273Szbb	    &td->td_md.md_ptrace_instr);
152290273Szbb	if (error == 0) {
153290273Szbb		error = ptrace_write_int(td, td->td_frame->tf_pc + INSN_SIZE,
154290273Szbb		    PTRACE_BREAKPOINT);
155290273Szbb		if (error) {
156290273Szbb			td->td_md.md_ptrace_instr = 0;
157290273Szbb		} else {
158290273Szbb			td->td_md.md_ptrace_addr = td->td_frame->tf_pc +
159290273Szbb			    INSN_SIZE;
160290273Szbb		}
161290273Szbb	}
162290273Szbb
163290273Szbb	error_alt = ptrace_get_alternative_next(td, cur_instr, &alt_next);
164290273Szbb	if (error_alt == 0) {
165290273Szbb		error_alt = ptrace_read_int(td, alt_next,
166290273Szbb		    &td->td_md.md_ptrace_instr_alt);
167290273Szbb		if (error_alt) {
168290273Szbb			td->td_md.md_ptrace_instr_alt = 0;
169290273Szbb		} else {
170290273Szbb			error_alt = ptrace_write_int(td, alt_next,
171290273Szbb			    PTRACE_BREAKPOINT);
172290273Szbb			if (error_alt)
173290273Szbb				td->td_md.md_ptrace_instr_alt = 0;
174290273Szbb			else
175290273Szbb				td->td_md.md_ptrace_addr_alt = alt_next;
176290273Szbb		}
177290273Szbb	}
178290273Szbb
179155922Sjhbout:
180155922Sjhb	PROC_LOCK(p);
181290273Szbb	return ((error != 0) && (error_alt != 0));
182129198Scognet}
183129198Scognet
184129198Scognetint
185132474Scognetptrace_clear_single_step(struct thread *td)
186132474Scognet{
187155922Sjhb	struct proc *p;
188155922Sjhb
189282779Sandrew	/* TODO: This needs to be updated for Thumb-2 */
190282779Sandrew	if ((td->td_frame->tf_spsr & PSR_T) != 0)
191282779Sandrew		return (EINVAL);
192282779Sandrew
193290273Szbb	if (td->td_md.md_ptrace_instr != 0) {
194155922Sjhb		p = td->td_proc;
195155922Sjhb		PROC_UNLOCK(p);
196140001Scognet		ptrace_write_int(td, td->td_md.md_ptrace_addr,
197140001Scognet		    td->td_md.md_ptrace_instr);
198155922Sjhb		PROC_LOCK(p);
199140001Scognet		td->td_md.md_ptrace_instr = 0;
200140001Scognet	}
201290273Szbb
202290273Szbb	if (td->td_md.md_ptrace_instr_alt != 0) {
203290273Szbb		p = td->td_proc;
204290273Szbb		PROC_UNLOCK(p);
205290273Szbb		ptrace_write_int(td, td->td_md.md_ptrace_addr_alt,
206290273Szbb		    td->td_md.md_ptrace_instr_alt);
207290273Szbb		PROC_LOCK(p);
208290273Szbb		td->td_md.md_ptrace_instr_alt = 0;
209290273Szbb	}
210290273Szbb
211132474Scognet	return (0);
212132474Scognet}
213132474Scognet
214132474Scognetint
215129198Scognetptrace_set_pc(struct thread *td, unsigned long addr)
216129198Scognet{
217132474Scognet	td->td_frame->tf_pc = addr;
218129198Scognet	return (0);
219129198Scognet}
220129198Scognet
221129198Scognetint
222290273Szbbarm_predict_branch(void *cookie, u_int insn, register_t pc, register_t *new_pc,
223315059Smmel    u_int (*fetch_reg)(void*, int),
224315059Smmel    u_int (*read_int)(void*, vm_offset_t, u_int*))
225290273Szbb{
226290273Szbb	u_int addr, nregs, offset = 0;
227290273Szbb	int error = 0;
228290273Szbb
229290273Szbb	switch ((insn >> 24) & 0xf) {
230290273Szbb	case 0x2:	/* add pc, reg1, #value */
231290273Szbb	case 0x0:	/* add pc, reg1, reg2, lsl #offset */
232290273Szbb		addr = fetch_reg(cookie, (insn >> 16) & 0xf);
233290273Szbb		if (((insn >> 16) & 0xf) == 15)
234290273Szbb			addr += 8;
235290273Szbb		if (insn & 0x0200000) {
236290273Szbb			offset = (insn >> 7) & 0x1e;
237290273Szbb			offset = (insn & 0xff) << (32 - offset) |
238290273Szbb			    (insn & 0xff) >> offset;
239290273Szbb		} else {
240290273Szbb
241290273Szbb			offset = fetch_reg(cookie, insn & 0x0f);
242290273Szbb			if ((insn & 0x0000ff0) != 0x00000000) {
243290273Szbb				if (insn & 0x10)
244290273Szbb					nregs = fetch_reg(cookie,
245290273Szbb					    (insn >> 8) & 0xf);
246290273Szbb				else
247290273Szbb					nregs = (insn >> 7) & 0x1f;
248290273Szbb				switch ((insn >> 5) & 3) {
249290273Szbb				case 0:
250290273Szbb					/* lsl */
251290273Szbb					offset = offset << nregs;
252290273Szbb					break;
253290273Szbb				case 1:
254290273Szbb					/* lsr */
255290273Szbb					offset = offset >> nregs;
256290273Szbb					break;
257290273Szbb				default:
258290273Szbb					break; /* XXX */
259290273Szbb				}
260290273Szbb
261290273Szbb			}
262290273Szbb			*new_pc = addr + offset;
263290273Szbb			return (0);
264290273Szbb
265290273Szbb		}
266290273Szbb
267290273Szbb	case 0xa:	/* b ... */
268290273Szbb	case 0xb:	/* bl ... */
269290273Szbb		addr = ((insn << 2) & 0x03ffffff);
270290273Szbb		if (addr & 0x02000000)
271290273Szbb			addr |= 0xfc000000;
272290273Szbb		*new_pc = (pc + 8 + addr);
273290273Szbb		return (0);
274290273Szbb	case 0x7:	/* ldr pc, [pc, reg, lsl #2] */
275290273Szbb		addr = fetch_reg(cookie, insn & 0xf);
276290273Szbb		addr = pc + 8 + (addr << 2);
277290273Szbb		error = read_int(cookie, addr, &addr);
278290273Szbb		*new_pc = addr;
279290273Szbb		return (error);
280290273Szbb	case 0x1:	/* mov pc, reg */
281290273Szbb		*new_pc = fetch_reg(cookie, insn & 0xf);
282290273Szbb		return (0);
283290273Szbb	case 0x4:
284290273Szbb	case 0x5:	/* ldr pc, [reg] */
285290273Szbb		addr = fetch_reg(cookie, (insn >> 16) & 0xf);
286290273Szbb		/* ldr pc, [reg, #offset] */
287290273Szbb		if (insn & (1 << 24))
288290273Szbb			offset = insn & 0xfff;
289290273Szbb		if (insn & 0x00800000)
290290273Szbb			addr += offset;
291290273Szbb		else
292290273Szbb			addr -= offset;
293290273Szbb		error = read_int(cookie, addr, &addr);
294290273Szbb		*new_pc = addr;
295290273Szbb
296290273Szbb		return (error);
297290273Szbb	case 0x8:	/* ldmxx reg, {..., pc} */
298290273Szbb	case 0x9:
299290273Szbb		addr = fetch_reg(cookie, (insn >> 16) & 0xf);
300290273Szbb		nregs = (insn  & 0x5555) + ((insn  >> 1) & 0x5555);
301290273Szbb		nregs = (nregs & 0x3333) + ((nregs >> 2) & 0x3333);
302290273Szbb		nregs = (nregs + (nregs >> 4)) & 0x0f0f;
303290273Szbb		nregs = (nregs + (nregs >> 8)) & 0x001f;
304290273Szbb		switch ((insn >> 23) & 0x3) {
305290273Szbb		case 0x0:	/* ldmda */
306290273Szbb			addr = addr - 0;
307290273Szbb			break;
308290273Szbb		case 0x1:	/* ldmia */
309290273Szbb			addr = addr + 0 + ((nregs - 1) << 2);
310290273Szbb			break;
311290273Szbb		case 0x2:	/* ldmdb */
312290273Szbb			addr = addr - 4;
313290273Szbb			break;
314290273Szbb		case 0x3:	/* ldmib */
315290273Szbb			addr = addr + 4 + ((nregs - 1) << 2);
316290273Szbb			break;
317290273Szbb		}
318290273Szbb		error = read_int(cookie, addr, &addr);
319290273Szbb		*new_pc = addr;
320290273Szbb
321290273Szbb		return (error);
322290273Szbb	default:
323290273Szbb		return (EINVAL);
324290273Szbb	}
325290273Szbb}
326