freebsd-dev/sys/arm/arm/machdep_ptrace.c
2020-09-01 21:17:24 +00:00

324 lines
8.1 KiB
C

/*-
* Copyright (c) 2004 Olivier Houchard
* Copyright (c) 1994-1998 Mark Brinicombe.
* Copyright (c) 1994 Brini.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/ptrace.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <machine/machdep.h>
#include <machine/db_machdep.h>
static int
ptrace_read_int(struct thread *td, vm_offset_t addr, uint32_t *v)
{
if (proc_readmem(td, td->td_proc, addr, v, sizeof(*v)) != sizeof(*v))
return (ENOMEM);
return (0);
}
static int
ptrace_write_int(struct thread *td, vm_offset_t addr, uint32_t v)
{
if (proc_writemem(td, td->td_proc, addr, &v, sizeof(v)) != sizeof(v))
return (ENOMEM);
return (0);
}
static u_int
ptrace_get_usr_reg(void *cookie, int reg)
{
int ret;
struct thread *td = cookie;
KASSERT(((reg >= 0) && (reg <= ARM_REG_NUM_PC)),
("reg is outside range"));
switch(reg) {
case ARM_REG_NUM_PC:
ret = td->td_frame->tf_pc;
break;
case ARM_REG_NUM_LR:
ret = td->td_frame->tf_usr_lr;
break;
case ARM_REG_NUM_SP:
ret = td->td_frame->tf_usr_sp;
break;
default:
ret = *((register_t*)&td->td_frame->tf_r0 + reg);
break;
}
return (ret);
}
static u_int
ptrace_get_usr_int(void* cookie, vm_offset_t offset, u_int* val)
{
struct thread *td = cookie;
u_int error;
error = ptrace_read_int(td, offset, val);
return (error);
}
/**
* This function parses current instruction opcode and decodes
* any possible jump (change in PC) which might occur after
* the instruction is executed.
*
* @param td Thread structure of analysed task
* @param cur_instr Currently executed instruction
* @param alt_next_address Pointer to the variable where
* the destination address of the
* jump instruction shall be stored.
*
* @return <0> when jump is possible
* <EINVAL> otherwise
*/
static int
ptrace_get_alternative_next(struct thread *td, uint32_t cur_instr,
uint32_t *alt_next_address)
{
int error;
if (inst_branch(cur_instr) || inst_call(cur_instr) ||
inst_return(cur_instr)) {
error = arm_predict_branch(td, cur_instr, td->td_frame->tf_pc,
alt_next_address, ptrace_get_usr_reg, ptrace_get_usr_int);
return (error);
}
return (EINVAL);
}
int
ptrace_single_step(struct thread *td)
{
struct proc *p;
int error, error_alt;
uint32_t cur_instr, alt_next = 0;
/* TODO: This needs to be updated for Thumb-2 */
if ((td->td_frame->tf_spsr & PSR_T) != 0)
return (EINVAL);
KASSERT(td->td_md.md_ptrace_instr == 0,
("Didn't clear single step"));
KASSERT(td->td_md.md_ptrace_instr_alt == 0,
("Didn't clear alternative single step"));
p = td->td_proc;
PROC_UNLOCK(p);
error = ptrace_read_int(td, td->td_frame->tf_pc,
&cur_instr);
if (error)
goto out;
error = ptrace_read_int(td, td->td_frame->tf_pc + INSN_SIZE,
&td->td_md.md_ptrace_instr);
if (error == 0) {
error = ptrace_write_int(td, td->td_frame->tf_pc + INSN_SIZE,
PTRACE_BREAKPOINT);
if (error) {
td->td_md.md_ptrace_instr = 0;
} else {
td->td_md.md_ptrace_addr = td->td_frame->tf_pc +
INSN_SIZE;
}
}
error_alt = ptrace_get_alternative_next(td, cur_instr, &alt_next);
if (error_alt == 0) {
error_alt = ptrace_read_int(td, alt_next,
&td->td_md.md_ptrace_instr_alt);
if (error_alt) {
td->td_md.md_ptrace_instr_alt = 0;
} else {
error_alt = ptrace_write_int(td, alt_next,
PTRACE_BREAKPOINT);
if (error_alt)
td->td_md.md_ptrace_instr_alt = 0;
else
td->td_md.md_ptrace_addr_alt = alt_next;
}
}
out:
PROC_LOCK(p);
return ((error != 0) && (error_alt != 0));
}
int
ptrace_clear_single_step(struct thread *td)
{
struct proc *p;
/* TODO: This needs to be updated for Thumb-2 */
if ((td->td_frame->tf_spsr & PSR_T) != 0)
return (EINVAL);
if (td->td_md.md_ptrace_instr != 0) {
p = td->td_proc;
PROC_UNLOCK(p);
ptrace_write_int(td, td->td_md.md_ptrace_addr,
td->td_md.md_ptrace_instr);
PROC_LOCK(p);
td->td_md.md_ptrace_instr = 0;
}
if (td->td_md.md_ptrace_instr_alt != 0) {
p = td->td_proc;
PROC_UNLOCK(p);
ptrace_write_int(td, td->td_md.md_ptrace_addr_alt,
td->td_md.md_ptrace_instr_alt);
PROC_LOCK(p);
td->td_md.md_ptrace_instr_alt = 0;
}
return (0);
}
int
ptrace_set_pc(struct thread *td, unsigned long addr)
{
td->td_frame->tf_pc = addr;
return (0);
}
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*))
{
u_int addr, nregs, offset = 0;
int error = 0;
switch ((insn >> 24) & 0xf) {
case 0x2: /* add pc, reg1, #value */
case 0x0: /* add pc, reg1, reg2, lsl #offset */
addr = fetch_reg(cookie, (insn >> 16) & 0xf);
if (((insn >> 16) & 0xf) == 15)
addr += 8;
if (insn & 0x0200000) {
offset = (insn >> 7) & 0x1e;
offset = (insn & 0xff) << (32 - offset) |
(insn & 0xff) >> offset;
} else {
offset = fetch_reg(cookie, insn & 0x0f);
if ((insn & 0x0000ff0) != 0x00000000) {
if (insn & 0x10)
nregs = fetch_reg(cookie,
(insn >> 8) & 0xf);
else
nregs = (insn >> 7) & 0x1f;
switch ((insn >> 5) & 3) {
case 0:
/* lsl */
offset = offset << nregs;
break;
case 1:
/* lsr */
offset = offset >> nregs;
break;
default:
break; /* XXX */
}
}
*new_pc = addr + offset;
return (0);
}
case 0xa: /* b ... */
case 0xb: /* bl ... */
addr = ((insn << 2) & 0x03ffffff);
if (addr & 0x02000000)
addr |= 0xfc000000;
*new_pc = (pc + 8 + addr);
return (0);
case 0x7: /* ldr pc, [pc, reg, lsl #2] */
addr = fetch_reg(cookie, insn & 0xf);
addr = pc + 8 + (addr << 2);
error = read_int(cookie, addr, &addr);
*new_pc = addr;
return (error);
case 0x1: /* mov pc, reg */
*new_pc = fetch_reg(cookie, insn & 0xf);
return (0);
case 0x4:
case 0x5: /* ldr pc, [reg] */
addr = fetch_reg(cookie, (insn >> 16) & 0xf);
/* ldr pc, [reg, #offset] */
if (insn & (1 << 24))
offset = insn & 0xfff;
if (insn & 0x00800000)
addr += offset;
else
addr -= offset;
error = read_int(cookie, addr, &addr);
*new_pc = addr;
return (error);
case 0x8: /* ldmxx reg, {..., pc} */
case 0x9:
addr = fetch_reg(cookie, (insn >> 16) & 0xf);
nregs = (insn & 0x5555) + ((insn >> 1) & 0x5555);
nregs = (nregs & 0x3333) + ((nregs >> 2) & 0x3333);
nregs = (nregs + (nregs >> 4)) & 0x0f0f;
nregs = (nregs + (nregs >> 8)) & 0x001f;
switch ((insn >> 23) & 0x3) {
case 0x0: /* ldmda */
addr = addr - 0;
break;
case 0x1: /* ldmia */
addr = addr + 0 + ((nregs - 1) << 2);
break;
case 0x2: /* ldmdb */
addr = addr - 4;
break;
case 0x3: /* ldmib */
addr = addr + 4 + ((nregs - 1) << 2);
break;
}
error = read_int(cookie, addr, &addr);
*new_pc = addr;
return (error);
default:
return (EINVAL);
}
}