2012-03-05 18:49:33 +07:00
|
|
|
/*
|
|
|
|
* Based on arch/arm/kernel/ptrace.c
|
|
|
|
*
|
|
|
|
* By Ross Biro 1/23/92
|
|
|
|
* edited by Linus Torvalds
|
|
|
|
* ARM modifications Copyright (C) 2000 Russell King
|
|
|
|
* Copyright (C) 2012 ARM Ltd.
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
|
|
* published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2014-07-04 14:28:31 +07:00
|
|
|
#include <linux/audit.h>
|
2014-04-30 16:51:32 +07:00
|
|
|
#include <linux/compat.h>
|
2012-03-05 18:49:33 +07:00
|
|
|
#include <linux/kernel.h>
|
2017-02-09 00:51:30 +07:00
|
|
|
#include <linux/sched/signal.h>
|
2017-02-09 00:51:37 +07:00
|
|
|
#include <linux/sched/task_stack.h>
|
2012-03-05 18:49:33 +07:00
|
|
|
#include <linux/mm.h>
|
2018-04-25 23:13:40 +07:00
|
|
|
#include <linux/nospec.h>
|
2012-03-05 18:49:33 +07:00
|
|
|
#include <linux/smp.h>
|
|
|
|
#include <linux/ptrace.h>
|
|
|
|
#include <linux/user.h>
|
2014-11-28 12:26:39 +07:00
|
|
|
#include <linux/seccomp.h>
|
2012-03-05 18:49:33 +07:00
|
|
|
#include <linux/security.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/signal.h>
|
arm64/sve: ptrace and ELF coredump support
This patch defines and implements a new regset NT_ARM_SVE, which
describes a thread's SVE register state. This allows a debugger to
manipulate the SVE state, as well as being included in ELF
coredumps for post-mortem debugging.
Because the regset size and layout are dependent on the thread's
current vector length, it is not possible to define a C struct to
describe the regset contents as is done for existing regsets.
Instead, and for the same reasons, NT_ARM_SVE is based on the
freeform variable-layout approach used for the SVE signal frame.
Additionally, to reduce debug overhead when debugging threads that
might or might not have live SVE register state, NT_ARM_SVE may be
presented in one of two different formats: the old struct
user_fpsimd_state format is embedded for describing the state of a
thread with no live SVE state, whereas a new variable-layout
structure is embedded for describing live SVE state. This avoids a
debugger needing to poll NT_PRFPREG in addition to NT_ARM_SVE, and
allows existing userspace code to handle the non-SVE case without
too much modification.
For this to work, NT_ARM_SVE is defined with a fixed-format header
of type struct user_sve_header, which the recipient can use to
figure out the content, size and layout of the reset of the regset.
Accessor macros are defined to allow the vector-length-dependent
parts of the regset to be manipulated.
Signed-off-by: Alan Hayward <alan.hayward@arm.com>
Signed-off-by: Dave Martin <Dave.Martin@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Cc: Alex Bennée <alex.bennee@linaro.org>
Cc: Okamoto Takayuki <tokamoto@jp.fujitsu.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
2017-10-31 22:51:13 +07:00
|
|
|
#include <linux/string.h>
|
2012-03-05 18:49:33 +07:00
|
|
|
#include <linux/uaccess.h>
|
|
|
|
#include <linux/perf_event.h>
|
|
|
|
#include <linux/hw_breakpoint.h>
|
|
|
|
#include <linux/regset.h>
|
|
|
|
#include <linux/tracehook.h>
|
|
|
|
#include <linux/elf.h>
|
|
|
|
|
|
|
|
#include <asm/compat.h>
|
arm64/sve: ptrace and ELF coredump support
This patch defines and implements a new regset NT_ARM_SVE, which
describes a thread's SVE register state. This allows a debugger to
manipulate the SVE state, as well as being included in ELF
coredumps for post-mortem debugging.
Because the regset size and layout are dependent on the thread's
current vector length, it is not possible to define a C struct to
describe the regset contents as is done for existing regsets.
Instead, and for the same reasons, NT_ARM_SVE is based on the
freeform variable-layout approach used for the SVE signal frame.
Additionally, to reduce debug overhead when debugging threads that
might or might not have live SVE register state, NT_ARM_SVE may be
presented in one of two different formats: the old struct
user_fpsimd_state format is embedded for describing the state of a
thread with no live SVE state, whereas a new variable-layout
structure is embedded for describing live SVE state. This avoids a
debugger needing to poll NT_PRFPREG in addition to NT_ARM_SVE, and
allows existing userspace code to handle the non-SVE case without
too much modification.
For this to work, NT_ARM_SVE is defined with a fixed-format header
of type struct user_sve_header, which the recipient can use to
figure out the content, size and layout of the reset of the regset.
Accessor macros are defined to allow the vector-length-dependent
parts of the regset to be manipulated.
Signed-off-by: Alan Hayward <alan.hayward@arm.com>
Signed-off-by: Dave Martin <Dave.Martin@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Cc: Alex Bennée <alex.bennee@linaro.org>
Cc: Okamoto Takayuki <tokamoto@jp.fujitsu.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
2017-10-31 22:51:13 +07:00
|
|
|
#include <asm/cpufeature.h>
|
2012-03-05 18:49:33 +07:00
|
|
|
#include <asm/debug-monitors.h>
|
2018-04-12 22:47:20 +07:00
|
|
|
#include <asm/fpsimd.h>
|
2012-03-05 18:49:33 +07:00
|
|
|
#include <asm/pgtable.h>
|
2017-07-20 18:26:48 +07:00
|
|
|
#include <asm/stacktrace.h>
|
2014-07-04 14:28:31 +07:00
|
|
|
#include <asm/syscall.h>
|
2012-03-05 18:49:33 +07:00
|
|
|
#include <asm/traps.h>
|
|
|
|
#include <asm/system_misc.h>
|
|
|
|
|
2014-04-30 16:54:36 +07:00
|
|
|
#define CREATE_TRACE_POINTS
|
|
|
|
#include <trace/events/syscalls.h>
|
|
|
|
|
2016-07-08 23:35:45 +07:00
|
|
|
struct pt_regs_offset {
|
|
|
|
const char *name;
|
|
|
|
int offset;
|
|
|
|
};
|
|
|
|
|
|
|
|
#define REG_OFFSET_NAME(r) {.name = #r, .offset = offsetof(struct pt_regs, r)}
|
|
|
|
#define REG_OFFSET_END {.name = NULL, .offset = 0}
|
|
|
|
#define GPR_OFFSET_NAME(r) \
|
|
|
|
{.name = "x" #r, .offset = offsetof(struct pt_regs, regs[r])}
|
|
|
|
|
|
|
|
static const struct pt_regs_offset regoffset_table[] = {
|
|
|
|
GPR_OFFSET_NAME(0),
|
|
|
|
GPR_OFFSET_NAME(1),
|
|
|
|
GPR_OFFSET_NAME(2),
|
|
|
|
GPR_OFFSET_NAME(3),
|
|
|
|
GPR_OFFSET_NAME(4),
|
|
|
|
GPR_OFFSET_NAME(5),
|
|
|
|
GPR_OFFSET_NAME(6),
|
|
|
|
GPR_OFFSET_NAME(7),
|
|
|
|
GPR_OFFSET_NAME(8),
|
|
|
|
GPR_OFFSET_NAME(9),
|
|
|
|
GPR_OFFSET_NAME(10),
|
|
|
|
GPR_OFFSET_NAME(11),
|
|
|
|
GPR_OFFSET_NAME(12),
|
|
|
|
GPR_OFFSET_NAME(13),
|
|
|
|
GPR_OFFSET_NAME(14),
|
|
|
|
GPR_OFFSET_NAME(15),
|
|
|
|
GPR_OFFSET_NAME(16),
|
|
|
|
GPR_OFFSET_NAME(17),
|
|
|
|
GPR_OFFSET_NAME(18),
|
|
|
|
GPR_OFFSET_NAME(19),
|
|
|
|
GPR_OFFSET_NAME(20),
|
|
|
|
GPR_OFFSET_NAME(21),
|
|
|
|
GPR_OFFSET_NAME(22),
|
|
|
|
GPR_OFFSET_NAME(23),
|
|
|
|
GPR_OFFSET_NAME(24),
|
|
|
|
GPR_OFFSET_NAME(25),
|
|
|
|
GPR_OFFSET_NAME(26),
|
|
|
|
GPR_OFFSET_NAME(27),
|
|
|
|
GPR_OFFSET_NAME(28),
|
|
|
|
GPR_OFFSET_NAME(29),
|
|
|
|
GPR_OFFSET_NAME(30),
|
|
|
|
{.name = "lr", .offset = offsetof(struct pt_regs, regs[30])},
|
|
|
|
REG_OFFSET_NAME(sp),
|
|
|
|
REG_OFFSET_NAME(pc),
|
|
|
|
REG_OFFSET_NAME(pstate),
|
|
|
|
REG_OFFSET_END,
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* regs_query_register_offset() - query register offset from its name
|
|
|
|
* @name: the name of a register
|
|
|
|
*
|
|
|
|
* regs_query_register_offset() returns the offset of a register in struct
|
|
|
|
* pt_regs from its name. If the name is invalid, this returns -EINVAL;
|
|
|
|
*/
|
|
|
|
int regs_query_register_offset(const char *name)
|
|
|
|
{
|
|
|
|
const struct pt_regs_offset *roff;
|
|
|
|
|
|
|
|
for (roff = regoffset_table; roff->name != NULL; roff++)
|
|
|
|
if (!strcmp(roff->name, name))
|
|
|
|
return roff->offset;
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* regs_within_kernel_stack() - check the address in the stack
|
|
|
|
* @regs: pt_regs which contains kernel stack pointer.
|
|
|
|
* @addr: address which is checked.
|
|
|
|
*
|
|
|
|
* regs_within_kernel_stack() checks @addr is within the kernel stack page(s).
|
|
|
|
* If @addr is within the kernel stack, it returns true. If not, returns false.
|
|
|
|
*/
|
|
|
|
static bool regs_within_kernel_stack(struct pt_regs *regs, unsigned long addr)
|
|
|
|
{
|
|
|
|
return ((addr & ~(THREAD_SIZE - 1)) ==
|
|
|
|
(kernel_stack_pointer(regs) & ~(THREAD_SIZE - 1))) ||
|
arm64: unwind: avoid percpu indirection for irq stack
Our IRQ_STACK_PTR() and on_irq_stack() helpers both take a cpu argument,
used to generate a percpu address. In all cases, they are passed
{raw_,}smp_processor_id(), so this parameter is redundant.
Since {raw_,}smp_processor_id() use a percpu variable internally, this
approach means we generate a percpu offset to find the current cpu, then
use this to index an array of percpu offsets, which we then use to find
the current CPU's IRQ stack pointer. Thus, most of the work is
redundant.
Instead, we can consistently use raw_cpu_ptr() to generate the CPU's
irq_stack pointer by simply adding the percpu offset to the irq_stack
address, which is simpler in both respects.
Signed-off-by: Mark Rutland <mark.rutland@arm.com>
Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: James Morse <james.morse@arm.com>
Cc: Will Deacon <will.deacon@arm.com>
2017-07-20 20:01:01 +07:00
|
|
|
on_irq_stack(addr);
|
2016-07-08 23:35:45 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* regs_get_kernel_stack_nth() - get Nth entry of the stack
|
|
|
|
* @regs: pt_regs which contains kernel stack pointer.
|
|
|
|
* @n: stack entry number.
|
|
|
|
*
|
|
|
|
* regs_get_kernel_stack_nth() returns @n th entry of the kernel stack which
|
|
|
|
* is specified by @regs. If the @n th entry is NOT in the kernel stack,
|
|
|
|
* this returns 0.
|
|
|
|
*/
|
|
|
|
unsigned long regs_get_kernel_stack_nth(struct pt_regs *regs, unsigned int n)
|
|
|
|
{
|
|
|
|
unsigned long *addr = (unsigned long *)kernel_stack_pointer(regs);
|
|
|
|
|
|
|
|
addr += n;
|
|
|
|
if (regs_within_kernel_stack(regs, (unsigned long)addr))
|
|
|
|
return *addr;
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-03-05 18:49:33 +07:00
|
|
|
/*
|
|
|
|
* TODO: does not yet catch signals sent when the child dies.
|
|
|
|
* in exit.c or in signal.c.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Called by kernel/ptrace.c when detaching..
|
|
|
|
*/
|
|
|
|
void ptrace_disable(struct task_struct *child)
|
|
|
|
{
|
2015-12-07 18:50:34 +07:00
|
|
|
/*
|
|
|
|
* This would be better off in core code, but PTRACE_DETACH has
|
|
|
|
* grown its fair share of arch-specific worts and changing it
|
|
|
|
* is likely to cause regressions on obscure architectures.
|
|
|
|
*/
|
|
|
|
user_disable_single_step(child);
|
2012-03-05 18:49:33 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_HAVE_HW_BREAKPOINT
|
|
|
|
/*
|
|
|
|
* Handle hitting a HW-breakpoint.
|
|
|
|
*/
|
|
|
|
static void ptrace_hbptriggered(struct perf_event *bp,
|
|
|
|
struct perf_sample_data *data,
|
|
|
|
struct pt_regs *regs)
|
|
|
|
{
|
|
|
|
struct arch_hw_breakpoint *bkpt = counter_arch_bp(bp);
|
2018-01-23 03:58:57 +07:00
|
|
|
siginfo_t info;
|
|
|
|
|
|
|
|
clear_siginfo(&info);
|
|
|
|
info.si_signo = SIGTRAP;
|
|
|
|
info.si_errno = 0;
|
|
|
|
info.si_code = TRAP_HWBKPT;
|
|
|
|
info.si_addr = (void __user *)(bkpt->trigger);
|
2012-03-05 18:49:33 +07:00
|
|
|
|
|
|
|
#ifdef CONFIG_COMPAT
|
2018-01-23 03:50:53 +07:00
|
|
|
if (is_compat_task()) {
|
2018-01-23 03:37:25 +07:00
|
|
|
int si_errno = 0;
|
2018-01-23 03:50:53 +07:00
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARM_MAX_BRP; ++i) {
|
|
|
|
if (current->thread.debug.hbp_break[i] == bp) {
|
2018-01-23 03:37:25 +07:00
|
|
|
si_errno = (i << 1) + 1;
|
2018-01-23 03:50:53 +07:00
|
|
|
break;
|
|
|
|
}
|
2012-03-05 18:49:33 +07:00
|
|
|
}
|
2014-08-22 20:13:24 +07:00
|
|
|
|
2018-01-23 03:50:53 +07:00
|
|
|
for (i = 0; i < ARM_MAX_WRP; ++i) {
|
|
|
|
if (current->thread.debug.hbp_watch[i] == bp) {
|
2018-01-23 03:37:25 +07:00
|
|
|
si_errno = -((i << 1) + 1);
|
2018-01-23 03:50:53 +07:00
|
|
|
break;
|
|
|
|
}
|
2012-03-05 18:49:33 +07:00
|
|
|
}
|
2018-01-23 03:37:25 +07:00
|
|
|
force_sig_ptrace_errno_trap(si_errno, (void __user *)bkpt->trigger);
|
2012-03-05 18:49:33 +07:00
|
|
|
}
|
|
|
|
#endif
|
2018-02-20 22:18:13 +07:00
|
|
|
arm64_force_sig_info(&info, "Hardware breakpoint trap (ptrace)", current);
|
2012-03-05 18:49:33 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Unregister breakpoints from this task and reset the pointers in
|
|
|
|
* the thread_struct.
|
|
|
|
*/
|
|
|
|
void flush_ptrace_hw_breakpoint(struct task_struct *tsk)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
struct thread_struct *t = &tsk->thread;
|
|
|
|
|
|
|
|
for (i = 0; i < ARM_MAX_BRP; i++) {
|
|
|
|
if (t->debug.hbp_break[i]) {
|
|
|
|
unregister_hw_breakpoint(t->debug.hbp_break[i]);
|
|
|
|
t->debug.hbp_break[i] = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < ARM_MAX_WRP; i++) {
|
|
|
|
if (t->debug.hbp_watch[i]) {
|
|
|
|
unregister_hw_breakpoint(t->debug.hbp_watch[i]);
|
|
|
|
t->debug.hbp_watch[i] = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ptrace_hw_copy_thread(struct task_struct *tsk)
|
|
|
|
{
|
|
|
|
memset(&tsk->thread.debug, 0, sizeof(struct debug_info));
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct perf_event *ptrace_hbp_get_event(unsigned int note_type,
|
|
|
|
struct task_struct *tsk,
|
|
|
|
unsigned long idx)
|
|
|
|
{
|
|
|
|
struct perf_event *bp = ERR_PTR(-EINVAL);
|
|
|
|
|
|
|
|
switch (note_type) {
|
|
|
|
case NT_ARM_HW_BREAK:
|
2018-04-25 23:13:40 +07:00
|
|
|
if (idx >= ARM_MAX_BRP)
|
|
|
|
goto out;
|
|
|
|
idx = array_index_nospec(idx, ARM_MAX_BRP);
|
|
|
|
bp = tsk->thread.debug.hbp_break[idx];
|
2012-03-05 18:49:33 +07:00
|
|
|
break;
|
|
|
|
case NT_ARM_HW_WATCH:
|
2018-04-25 23:13:40 +07:00
|
|
|
if (idx >= ARM_MAX_WRP)
|
|
|
|
goto out;
|
|
|
|
idx = array_index_nospec(idx, ARM_MAX_WRP);
|
|
|
|
bp = tsk->thread.debug.hbp_watch[idx];
|
2012-03-05 18:49:33 +07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-04-25 23:13:40 +07:00
|
|
|
out:
|
2012-03-05 18:49:33 +07:00
|
|
|
return bp;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ptrace_hbp_set_event(unsigned int note_type,
|
|
|
|
struct task_struct *tsk,
|
|
|
|
unsigned long idx,
|
|
|
|
struct perf_event *bp)
|
|
|
|
{
|
|
|
|
int err = -EINVAL;
|
|
|
|
|
|
|
|
switch (note_type) {
|
|
|
|
case NT_ARM_HW_BREAK:
|
|
|
|
if (idx < ARM_MAX_BRP) {
|
|
|
|
tsk->thread.debug.hbp_break[idx] = bp;
|
|
|
|
err = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case NT_ARM_HW_WATCH:
|
|
|
|
if (idx < ARM_MAX_WRP) {
|
|
|
|
tsk->thread.debug.hbp_watch[idx] = bp;
|
|
|
|
err = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct perf_event *ptrace_hbp_create(unsigned int note_type,
|
|
|
|
struct task_struct *tsk,
|
|
|
|
unsigned long idx)
|
|
|
|
{
|
|
|
|
struct perf_event *bp;
|
|
|
|
struct perf_event_attr attr;
|
|
|
|
int err, type;
|
|
|
|
|
|
|
|
switch (note_type) {
|
|
|
|
case NT_ARM_HW_BREAK:
|
|
|
|
type = HW_BREAKPOINT_X;
|
|
|
|
break;
|
|
|
|
case NT_ARM_HW_WATCH:
|
|
|
|
type = HW_BREAKPOINT_RW;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
ptrace_breakpoint_init(&attr);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialise fields to sane defaults
|
|
|
|
* (i.e. values that will pass validation).
|
|
|
|
*/
|
|
|
|
attr.bp_addr = 0;
|
|
|
|
attr.bp_len = HW_BREAKPOINT_LEN_4;
|
|
|
|
attr.bp_type = type;
|
|
|
|
attr.disabled = 1;
|
|
|
|
|
|
|
|
bp = register_user_hw_breakpoint(&attr, ptrace_hbptriggered, NULL, tsk);
|
|
|
|
if (IS_ERR(bp))
|
|
|
|
return bp;
|
|
|
|
|
|
|
|
err = ptrace_hbp_set_event(note_type, tsk, idx, bp);
|
|
|
|
if (err)
|
|
|
|
return ERR_PTR(err);
|
|
|
|
|
|
|
|
return bp;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ptrace_hbp_fill_attr_ctrl(unsigned int note_type,
|
|
|
|
struct arch_hw_breakpoint_ctrl ctrl,
|
|
|
|
struct perf_event_attr *attr)
|
|
|
|
{
|
2016-11-14 21:02:43 +07:00
|
|
|
int err, len, type, offset, disabled = !ctrl.enabled;
|
2012-10-18 21:17:00 +07:00
|
|
|
|
2013-12-18 00:09:08 +07:00
|
|
|
attr->disabled = disabled;
|
|
|
|
if (disabled)
|
|
|
|
return 0;
|
|
|
|
|
2016-11-14 21:02:43 +07:00
|
|
|
err = arch_bp_generic_fields(ctrl, &len, &type, &offset);
|
2013-12-18 00:09:08 +07:00
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
switch (note_type) {
|
|
|
|
case NT_ARM_HW_BREAK:
|
|
|
|
if ((type & HW_BREAKPOINT_X) != type)
|
2012-03-05 18:49:33 +07:00
|
|
|
return -EINVAL;
|
2013-12-18 00:09:08 +07:00
|
|
|
break;
|
|
|
|
case NT_ARM_HW_WATCH:
|
|
|
|
if ((type & HW_BREAKPOINT_RW) != type)
|
|
|
|
return -EINVAL;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
2012-03-05 18:49:33 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
attr->bp_len = len;
|
|
|
|
attr->bp_type = type;
|
2016-11-14 21:02:43 +07:00
|
|
|
attr->bp_addr += offset;
|
2012-03-05 18:49:33 +07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ptrace_hbp_get_resource_info(unsigned int note_type, u32 *info)
|
|
|
|
{
|
|
|
|
u8 num;
|
|
|
|
u32 reg = 0;
|
|
|
|
|
|
|
|
switch (note_type) {
|
|
|
|
case NT_ARM_HW_BREAK:
|
|
|
|
num = hw_breakpoint_slots(TYPE_INST);
|
|
|
|
break;
|
|
|
|
case NT_ARM_HW_WATCH:
|
|
|
|
num = hw_breakpoint_slots(TYPE_DATA);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
reg |= debug_monitors_arch();
|
|
|
|
reg <<= 8;
|
|
|
|
reg |= num;
|
|
|
|
|
|
|
|
*info = reg;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ptrace_hbp_get_ctrl(unsigned int note_type,
|
|
|
|
struct task_struct *tsk,
|
|
|
|
unsigned long idx,
|
|
|
|
u32 *ctrl)
|
|
|
|
{
|
|
|
|
struct perf_event *bp = ptrace_hbp_get_event(note_type, tsk, idx);
|
|
|
|
|
|
|
|
if (IS_ERR(bp))
|
|
|
|
return PTR_ERR(bp);
|
|
|
|
|
|
|
|
*ctrl = bp ? encode_ctrl_reg(counter_arch_bp(bp)->ctrl) : 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ptrace_hbp_get_addr(unsigned int note_type,
|
|
|
|
struct task_struct *tsk,
|
|
|
|
unsigned long idx,
|
|
|
|
u64 *addr)
|
|
|
|
{
|
|
|
|
struct perf_event *bp = ptrace_hbp_get_event(note_type, tsk, idx);
|
|
|
|
|
|
|
|
if (IS_ERR(bp))
|
|
|
|
return PTR_ERR(bp);
|
|
|
|
|
2016-11-14 21:02:43 +07:00
|
|
|
*addr = bp ? counter_arch_bp(bp)->address : 0;
|
2012-03-05 18:49:33 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct perf_event *ptrace_hbp_get_initialised_bp(unsigned int note_type,
|
|
|
|
struct task_struct *tsk,
|
|
|
|
unsigned long idx)
|
|
|
|
{
|
|
|
|
struct perf_event *bp = ptrace_hbp_get_event(note_type, tsk, idx);
|
|
|
|
|
|
|
|
if (!bp)
|
|
|
|
bp = ptrace_hbp_create(note_type, tsk, idx);
|
|
|
|
|
|
|
|
return bp;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ptrace_hbp_set_ctrl(unsigned int note_type,
|
|
|
|
struct task_struct *tsk,
|
|
|
|
unsigned long idx,
|
|
|
|
u32 uctrl)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
struct perf_event *bp;
|
|
|
|
struct perf_event_attr attr;
|
|
|
|
struct arch_hw_breakpoint_ctrl ctrl;
|
|
|
|
|
|
|
|
bp = ptrace_hbp_get_initialised_bp(note_type, tsk, idx);
|
|
|
|
if (IS_ERR(bp)) {
|
|
|
|
err = PTR_ERR(bp);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
attr = bp->attr;
|
|
|
|
decode_ctrl_reg(uctrl, &ctrl);
|
|
|
|
err = ptrace_hbp_fill_attr_ctrl(note_type, ctrl, &attr);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
return modify_user_hw_breakpoint(bp, &attr);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ptrace_hbp_set_addr(unsigned int note_type,
|
|
|
|
struct task_struct *tsk,
|
|
|
|
unsigned long idx,
|
|
|
|
u64 addr)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
struct perf_event *bp;
|
|
|
|
struct perf_event_attr attr;
|
|
|
|
|
|
|
|
bp = ptrace_hbp_get_initialised_bp(note_type, tsk, idx);
|
|
|
|
if (IS_ERR(bp)) {
|
|
|
|
err = PTR_ERR(bp);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
attr = bp->attr;
|
|
|
|
attr.bp_addr = addr;
|
|
|
|
err = modify_user_hw_breakpoint(bp, &attr);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define PTRACE_HBP_ADDR_SZ sizeof(u64)
|
|
|
|
#define PTRACE_HBP_CTRL_SZ sizeof(u32)
|
2012-10-11 18:10:57 +07:00
|
|
|
#define PTRACE_HBP_PAD_SZ sizeof(u32)
|
2012-03-05 18:49:33 +07:00
|
|
|
|
|
|
|
static int hw_break_get(struct task_struct *target,
|
|
|
|
const struct user_regset *regset,
|
|
|
|
unsigned int pos, unsigned int count,
|
|
|
|
void *kbuf, void __user *ubuf)
|
|
|
|
{
|
|
|
|
unsigned int note_type = regset->core_note_type;
|
2012-10-11 18:10:57 +07:00
|
|
|
int ret, idx = 0, offset, limit;
|
2012-03-05 18:49:33 +07:00
|
|
|
u32 info, ctrl;
|
|
|
|
u64 addr;
|
|
|
|
|
|
|
|
/* Resource info */
|
|
|
|
ret = ptrace_hbp_get_resource_info(note_type, &info);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2012-10-11 18:10:57 +07:00
|
|
|
ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, &info, 0,
|
|
|
|
sizeof(info));
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
/* Pad */
|
|
|
|
offset = offsetof(struct user_hwdebug_state, pad);
|
|
|
|
ret = user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf, offset,
|
|
|
|
offset + PTRACE_HBP_PAD_SZ);
|
2012-03-05 18:49:33 +07:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
/* (address, ctrl) registers */
|
2012-10-11 18:10:57 +07:00
|
|
|
offset = offsetof(struct user_hwdebug_state, dbg_regs);
|
2012-03-05 18:49:33 +07:00
|
|
|
limit = regset->n * regset->size;
|
|
|
|
while (count && offset < limit) {
|
|
|
|
ret = ptrace_hbp_get_addr(note_type, target, idx, &addr);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, &addr,
|
|
|
|
offset, offset + PTRACE_HBP_ADDR_SZ);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
offset += PTRACE_HBP_ADDR_SZ;
|
|
|
|
|
|
|
|
ret = ptrace_hbp_get_ctrl(note_type, target, idx, &ctrl);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, &ctrl,
|
|
|
|
offset, offset + PTRACE_HBP_CTRL_SZ);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
offset += PTRACE_HBP_CTRL_SZ;
|
2012-10-11 18:10:57 +07:00
|
|
|
|
|
|
|
ret = user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf,
|
|
|
|
offset,
|
|
|
|
offset + PTRACE_HBP_PAD_SZ);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
offset += PTRACE_HBP_PAD_SZ;
|
2012-03-05 18:49:33 +07:00
|
|
|
idx++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hw_break_set(struct task_struct *target,
|
|
|
|
const struct user_regset *regset,
|
|
|
|
unsigned int pos, unsigned int count,
|
|
|
|
const void *kbuf, const void __user *ubuf)
|
|
|
|
{
|
|
|
|
unsigned int note_type = regset->core_note_type;
|
2012-10-11 18:10:57 +07:00
|
|
|
int ret, idx = 0, offset, limit;
|
2012-03-05 18:49:33 +07:00
|
|
|
u32 ctrl;
|
|
|
|
u64 addr;
|
|
|
|
|
2012-10-11 18:10:57 +07:00
|
|
|
/* Resource info and pad */
|
|
|
|
offset = offsetof(struct user_hwdebug_state, dbg_regs);
|
|
|
|
ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, 0, offset);
|
2012-03-05 18:49:33 +07:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
/* (address, ctrl) registers */
|
|
|
|
limit = regset->n * regset->size;
|
|
|
|
while (count && offset < limit) {
|
2017-01-18 23:25:24 +07:00
|
|
|
if (count < PTRACE_HBP_ADDR_SZ)
|
|
|
|
return -EINVAL;
|
2012-03-05 18:49:33 +07:00
|
|
|
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &addr,
|
|
|
|
offset, offset + PTRACE_HBP_ADDR_SZ);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
ret = ptrace_hbp_set_addr(note_type, target, idx, addr);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
offset += PTRACE_HBP_ADDR_SZ;
|
|
|
|
|
2017-01-18 23:25:24 +07:00
|
|
|
if (!count)
|
|
|
|
break;
|
2012-03-05 18:49:33 +07:00
|
|
|
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &ctrl,
|
|
|
|
offset, offset + PTRACE_HBP_CTRL_SZ);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
ret = ptrace_hbp_set_ctrl(note_type, target, idx, ctrl);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
offset += PTRACE_HBP_CTRL_SZ;
|
2012-10-11 18:10:57 +07:00
|
|
|
|
|
|
|
ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf,
|
|
|
|
offset,
|
|
|
|
offset + PTRACE_HBP_PAD_SZ);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
offset += PTRACE_HBP_PAD_SZ;
|
2012-03-05 18:49:33 +07:00
|
|
|
idx++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif /* CONFIG_HAVE_HW_BREAKPOINT */
|
|
|
|
|
|
|
|
static int gpr_get(struct task_struct *target,
|
|
|
|
const struct user_regset *regset,
|
|
|
|
unsigned int pos, unsigned int count,
|
|
|
|
void *kbuf, void __user *ubuf)
|
|
|
|
{
|
|
|
|
struct user_pt_regs *uregs = &task_pt_regs(target)->user_regs;
|
|
|
|
return user_regset_copyout(&pos, &count, &kbuf, &ubuf, uregs, 0, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int gpr_set(struct task_struct *target, const struct user_regset *regset,
|
|
|
|
unsigned int pos, unsigned int count,
|
|
|
|
const void *kbuf, const void __user *ubuf)
|
|
|
|
{
|
|
|
|
int ret;
|
2017-01-18 23:25:20 +07:00
|
|
|
struct user_pt_regs newregs = task_pt_regs(target)->user_regs;
|
2012-03-05 18:49:33 +07:00
|
|
|
|
|
|
|
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &newregs, 0, -1);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2016-03-01 21:18:50 +07:00
|
|
|
if (!valid_user_regs(&newregs, target))
|
2012-03-05 18:49:33 +07:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
task_pt_regs(target)->user_regs = newregs;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* TODO: update fp accessors for lazy context switching (sync/flush hwstate)
|
|
|
|
*/
|
arm64/sve: ptrace and ELF coredump support
This patch defines and implements a new regset NT_ARM_SVE, which
describes a thread's SVE register state. This allows a debugger to
manipulate the SVE state, as well as being included in ELF
coredumps for post-mortem debugging.
Because the regset size and layout are dependent on the thread's
current vector length, it is not possible to define a C struct to
describe the regset contents as is done for existing regsets.
Instead, and for the same reasons, NT_ARM_SVE is based on the
freeform variable-layout approach used for the SVE signal frame.
Additionally, to reduce debug overhead when debugging threads that
might or might not have live SVE register state, NT_ARM_SVE may be
presented in one of two different formats: the old struct
user_fpsimd_state format is embedded for describing the state of a
thread with no live SVE state, whereas a new variable-layout
structure is embedded for describing live SVE state. This avoids a
debugger needing to poll NT_PRFPREG in addition to NT_ARM_SVE, and
allows existing userspace code to handle the non-SVE case without
too much modification.
For this to work, NT_ARM_SVE is defined with a fixed-format header
of type struct user_sve_header, which the recipient can use to
figure out the content, size and layout of the reset of the regset.
Accessor macros are defined to allow the vector-length-dependent
parts of the regset to be manipulated.
Signed-off-by: Alan Hayward <alan.hayward@arm.com>
Signed-off-by: Dave Martin <Dave.Martin@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Cc: Alex Bennée <alex.bennee@linaro.org>
Cc: Okamoto Takayuki <tokamoto@jp.fujitsu.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
2017-10-31 22:51:13 +07:00
|
|
|
static int __fpr_get(struct task_struct *target,
|
|
|
|
const struct user_regset *regset,
|
|
|
|
unsigned int pos, unsigned int count,
|
|
|
|
void *kbuf, void __user *ubuf, unsigned int start_pos)
|
2012-03-05 18:49:33 +07:00
|
|
|
{
|
|
|
|
struct user_fpsimd_state *uregs;
|
arm64/sve: ptrace and ELF coredump support
This patch defines and implements a new regset NT_ARM_SVE, which
describes a thread's SVE register state. This allows a debugger to
manipulate the SVE state, as well as being included in ELF
coredumps for post-mortem debugging.
Because the regset size and layout are dependent on the thread's
current vector length, it is not possible to define a C struct to
describe the regset contents as is done for existing regsets.
Instead, and for the same reasons, NT_ARM_SVE is based on the
freeform variable-layout approach used for the SVE signal frame.
Additionally, to reduce debug overhead when debugging threads that
might or might not have live SVE register state, NT_ARM_SVE may be
presented in one of two different formats: the old struct
user_fpsimd_state format is embedded for describing the state of a
thread with no live SVE state, whereas a new variable-layout
structure is embedded for describing live SVE state. This avoids a
debugger needing to poll NT_PRFPREG in addition to NT_ARM_SVE, and
allows existing userspace code to handle the non-SVE case without
too much modification.
For this to work, NT_ARM_SVE is defined with a fixed-format header
of type struct user_sve_header, which the recipient can use to
figure out the content, size and layout of the reset of the regset.
Accessor macros are defined to allow the vector-length-dependent
parts of the regset to be manipulated.
Signed-off-by: Alan Hayward <alan.hayward@arm.com>
Signed-off-by: Dave Martin <Dave.Martin@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Cc: Alex Bennée <alex.bennee@linaro.org>
Cc: Okamoto Takayuki <tokamoto@jp.fujitsu.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
2017-10-31 22:51:13 +07:00
|
|
|
|
|
|
|
sve_sync_to_fpsimd(target);
|
|
|
|
|
2018-03-28 16:50:49 +07:00
|
|
|
uregs = &target->thread.uw.fpsimd_state;
|
2017-06-21 22:00:43 +07:00
|
|
|
|
arm64/sve: ptrace and ELF coredump support
This patch defines and implements a new regset NT_ARM_SVE, which
describes a thread's SVE register state. This allows a debugger to
manipulate the SVE state, as well as being included in ELF
coredumps for post-mortem debugging.
Because the regset size and layout are dependent on the thread's
current vector length, it is not possible to define a C struct to
describe the regset contents as is done for existing regsets.
Instead, and for the same reasons, NT_ARM_SVE is based on the
freeform variable-layout approach used for the SVE signal frame.
Additionally, to reduce debug overhead when debugging threads that
might or might not have live SVE register state, NT_ARM_SVE may be
presented in one of two different formats: the old struct
user_fpsimd_state format is embedded for describing the state of a
thread with no live SVE state, whereas a new variable-layout
structure is embedded for describing live SVE state. This avoids a
debugger needing to poll NT_PRFPREG in addition to NT_ARM_SVE, and
allows existing userspace code to handle the non-SVE case without
too much modification.
For this to work, NT_ARM_SVE is defined with a fixed-format header
of type struct user_sve_header, which the recipient can use to
figure out the content, size and layout of the reset of the regset.
Accessor macros are defined to allow the vector-length-dependent
parts of the regset to be manipulated.
Signed-off-by: Alan Hayward <alan.hayward@arm.com>
Signed-off-by: Dave Martin <Dave.Martin@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Cc: Alex Bennée <alex.bennee@linaro.org>
Cc: Okamoto Takayuki <tokamoto@jp.fujitsu.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
2017-10-31 22:51:13 +07:00
|
|
|
return user_regset_copyout(&pos, &count, &kbuf, &ubuf, uregs,
|
|
|
|
start_pos, start_pos + sizeof(*uregs));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fpr_get(struct task_struct *target, const struct user_regset *regset,
|
|
|
|
unsigned int pos, unsigned int count,
|
|
|
|
void *kbuf, void __user *ubuf)
|
|
|
|
{
|
2017-06-21 22:00:43 +07:00
|
|
|
if (target == current)
|
|
|
|
fpsimd_preserve_current_state();
|
|
|
|
|
arm64/sve: ptrace and ELF coredump support
This patch defines and implements a new regset NT_ARM_SVE, which
describes a thread's SVE register state. This allows a debugger to
manipulate the SVE state, as well as being included in ELF
coredumps for post-mortem debugging.
Because the regset size and layout are dependent on the thread's
current vector length, it is not possible to define a C struct to
describe the regset contents as is done for existing regsets.
Instead, and for the same reasons, NT_ARM_SVE is based on the
freeform variable-layout approach used for the SVE signal frame.
Additionally, to reduce debug overhead when debugging threads that
might or might not have live SVE register state, NT_ARM_SVE may be
presented in one of two different formats: the old struct
user_fpsimd_state format is embedded for describing the state of a
thread with no live SVE state, whereas a new variable-layout
structure is embedded for describing live SVE state. This avoids a
debugger needing to poll NT_PRFPREG in addition to NT_ARM_SVE, and
allows existing userspace code to handle the non-SVE case without
too much modification.
For this to work, NT_ARM_SVE is defined with a fixed-format header
of type struct user_sve_header, which the recipient can use to
figure out the content, size and layout of the reset of the regset.
Accessor macros are defined to allow the vector-length-dependent
parts of the regset to be manipulated.
Signed-off-by: Alan Hayward <alan.hayward@arm.com>
Signed-off-by: Dave Martin <Dave.Martin@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Cc: Alex Bennée <alex.bennee@linaro.org>
Cc: Okamoto Takayuki <tokamoto@jp.fujitsu.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
2017-10-31 22:51:13 +07:00
|
|
|
return __fpr_get(target, regset, pos, count, kbuf, ubuf, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __fpr_set(struct task_struct *target,
|
|
|
|
const struct user_regset *regset,
|
|
|
|
unsigned int pos, unsigned int count,
|
|
|
|
const void *kbuf, const void __user *ubuf,
|
|
|
|
unsigned int start_pos)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
struct user_fpsimd_state newstate;
|
|
|
|
|
|
|
|
/*
|
2018-03-28 16:50:49 +07:00
|
|
|
* Ensure target->thread.uw.fpsimd_state is up to date, so that a
|
arm64/sve: ptrace and ELF coredump support
This patch defines and implements a new regset NT_ARM_SVE, which
describes a thread's SVE register state. This allows a debugger to
manipulate the SVE state, as well as being included in ELF
coredumps for post-mortem debugging.
Because the regset size and layout are dependent on the thread's
current vector length, it is not possible to define a C struct to
describe the regset contents as is done for existing regsets.
Instead, and for the same reasons, NT_ARM_SVE is based on the
freeform variable-layout approach used for the SVE signal frame.
Additionally, to reduce debug overhead when debugging threads that
might or might not have live SVE register state, NT_ARM_SVE may be
presented in one of two different formats: the old struct
user_fpsimd_state format is embedded for describing the state of a
thread with no live SVE state, whereas a new variable-layout
structure is embedded for describing live SVE state. This avoids a
debugger needing to poll NT_PRFPREG in addition to NT_ARM_SVE, and
allows existing userspace code to handle the non-SVE case without
too much modification.
For this to work, NT_ARM_SVE is defined with a fixed-format header
of type struct user_sve_header, which the recipient can use to
figure out the content, size and layout of the reset of the regset.
Accessor macros are defined to allow the vector-length-dependent
parts of the regset to be manipulated.
Signed-off-by: Alan Hayward <alan.hayward@arm.com>
Signed-off-by: Dave Martin <Dave.Martin@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Cc: Alex Bennée <alex.bennee@linaro.org>
Cc: Okamoto Takayuki <tokamoto@jp.fujitsu.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
2017-10-31 22:51:13 +07:00
|
|
|
* short copyin can't resurrect stale data.
|
|
|
|
*/
|
|
|
|
sve_sync_to_fpsimd(target);
|
|
|
|
|
2018-03-28 16:50:49 +07:00
|
|
|
newstate = target->thread.uw.fpsimd_state;
|
arm64/sve: ptrace and ELF coredump support
This patch defines and implements a new regset NT_ARM_SVE, which
describes a thread's SVE register state. This allows a debugger to
manipulate the SVE state, as well as being included in ELF
coredumps for post-mortem debugging.
Because the regset size and layout are dependent on the thread's
current vector length, it is not possible to define a C struct to
describe the regset contents as is done for existing regsets.
Instead, and for the same reasons, NT_ARM_SVE is based on the
freeform variable-layout approach used for the SVE signal frame.
Additionally, to reduce debug overhead when debugging threads that
might or might not have live SVE register state, NT_ARM_SVE may be
presented in one of two different formats: the old struct
user_fpsimd_state format is embedded for describing the state of a
thread with no live SVE state, whereas a new variable-layout
structure is embedded for describing live SVE state. This avoids a
debugger needing to poll NT_PRFPREG in addition to NT_ARM_SVE, and
allows existing userspace code to handle the non-SVE case without
too much modification.
For this to work, NT_ARM_SVE is defined with a fixed-format header
of type struct user_sve_header, which the recipient can use to
figure out the content, size and layout of the reset of the regset.
Accessor macros are defined to allow the vector-length-dependent
parts of the regset to be manipulated.
Signed-off-by: Alan Hayward <alan.hayward@arm.com>
Signed-off-by: Dave Martin <Dave.Martin@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Cc: Alex Bennée <alex.bennee@linaro.org>
Cc: Okamoto Takayuki <tokamoto@jp.fujitsu.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
2017-10-31 22:51:13 +07:00
|
|
|
|
|
|
|
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &newstate,
|
|
|
|
start_pos, start_pos + sizeof(newstate));
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2018-03-28 16:50:49 +07:00
|
|
|
target->thread.uw.fpsimd_state = newstate;
|
arm64/sve: ptrace and ELF coredump support
This patch defines and implements a new regset NT_ARM_SVE, which
describes a thread's SVE register state. This allows a debugger to
manipulate the SVE state, as well as being included in ELF
coredumps for post-mortem debugging.
Because the regset size and layout are dependent on the thread's
current vector length, it is not possible to define a C struct to
describe the regset contents as is done for existing regsets.
Instead, and for the same reasons, NT_ARM_SVE is based on the
freeform variable-layout approach used for the SVE signal frame.
Additionally, to reduce debug overhead when debugging threads that
might or might not have live SVE register state, NT_ARM_SVE may be
presented in one of two different formats: the old struct
user_fpsimd_state format is embedded for describing the state of a
thread with no live SVE state, whereas a new variable-layout
structure is embedded for describing live SVE state. This avoids a
debugger needing to poll NT_PRFPREG in addition to NT_ARM_SVE, and
allows existing userspace code to handle the non-SVE case without
too much modification.
For this to work, NT_ARM_SVE is defined with a fixed-format header
of type struct user_sve_header, which the recipient can use to
figure out the content, size and layout of the reset of the regset.
Accessor macros are defined to allow the vector-length-dependent
parts of the regset to be manipulated.
Signed-off-by: Alan Hayward <alan.hayward@arm.com>
Signed-off-by: Dave Martin <Dave.Martin@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Cc: Alex Bennée <alex.bennee@linaro.org>
Cc: Okamoto Takayuki <tokamoto@jp.fujitsu.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
2017-10-31 22:51:13 +07:00
|
|
|
|
|
|
|
return ret;
|
2012-03-05 18:49:33 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static int fpr_set(struct task_struct *target, const struct user_regset *regset,
|
|
|
|
unsigned int pos, unsigned int count,
|
|
|
|
const void *kbuf, const void __user *ubuf)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
arm64/sve: ptrace and ELF coredump support
This patch defines and implements a new regset NT_ARM_SVE, which
describes a thread's SVE register state. This allows a debugger to
manipulate the SVE state, as well as being included in ELF
coredumps for post-mortem debugging.
Because the regset size and layout are dependent on the thread's
current vector length, it is not possible to define a C struct to
describe the regset contents as is done for existing regsets.
Instead, and for the same reasons, NT_ARM_SVE is based on the
freeform variable-layout approach used for the SVE signal frame.
Additionally, to reduce debug overhead when debugging threads that
might or might not have live SVE register state, NT_ARM_SVE may be
presented in one of two different formats: the old struct
user_fpsimd_state format is embedded for describing the state of a
thread with no live SVE state, whereas a new variable-layout
structure is embedded for describing live SVE state. This avoids a
debugger needing to poll NT_PRFPREG in addition to NT_ARM_SVE, and
allows existing userspace code to handle the non-SVE case without
too much modification.
For this to work, NT_ARM_SVE is defined with a fixed-format header
of type struct user_sve_header, which the recipient can use to
figure out the content, size and layout of the reset of the regset.
Accessor macros are defined to allow the vector-length-dependent
parts of the regset to be manipulated.
Signed-off-by: Alan Hayward <alan.hayward@arm.com>
Signed-off-by: Dave Martin <Dave.Martin@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Cc: Alex Bennée <alex.bennee@linaro.org>
Cc: Okamoto Takayuki <tokamoto@jp.fujitsu.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
2017-10-31 22:51:13 +07:00
|
|
|
ret = __fpr_set(target, regset, pos, count, kbuf, ubuf, 0);
|
2012-03-05 18:49:33 +07:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
arm64/sve: ptrace and ELF coredump support
This patch defines and implements a new regset NT_ARM_SVE, which
describes a thread's SVE register state. This allows a debugger to
manipulate the SVE state, as well as being included in ELF
coredumps for post-mortem debugging.
Because the regset size and layout are dependent on the thread's
current vector length, it is not possible to define a C struct to
describe the regset contents as is done for existing regsets.
Instead, and for the same reasons, NT_ARM_SVE is based on the
freeform variable-layout approach used for the SVE signal frame.
Additionally, to reduce debug overhead when debugging threads that
might or might not have live SVE register state, NT_ARM_SVE may be
presented in one of two different formats: the old struct
user_fpsimd_state format is embedded for describing the state of a
thread with no live SVE state, whereas a new variable-layout
structure is embedded for describing live SVE state. This avoids a
debugger needing to poll NT_PRFPREG in addition to NT_ARM_SVE, and
allows existing userspace code to handle the non-SVE case without
too much modification.
For this to work, NT_ARM_SVE is defined with a fixed-format header
of type struct user_sve_header, which the recipient can use to
figure out the content, size and layout of the reset of the regset.
Accessor macros are defined to allow the vector-length-dependent
parts of the regset to be manipulated.
Signed-off-by: Alan Hayward <alan.hayward@arm.com>
Signed-off-by: Dave Martin <Dave.Martin@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Cc: Alex Bennée <alex.bennee@linaro.org>
Cc: Okamoto Takayuki <tokamoto@jp.fujitsu.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
2017-10-31 22:51:13 +07:00
|
|
|
sve_sync_from_fpsimd_zeropad(target);
|
2014-05-08 16:20:23 +07:00
|
|
|
fpsimd_flush_task_state(target);
|
arm64/sve: ptrace and ELF coredump support
This patch defines and implements a new regset NT_ARM_SVE, which
describes a thread's SVE register state. This allows a debugger to
manipulate the SVE state, as well as being included in ELF
coredumps for post-mortem debugging.
Because the regset size and layout are dependent on the thread's
current vector length, it is not possible to define a C struct to
describe the regset contents as is done for existing regsets.
Instead, and for the same reasons, NT_ARM_SVE is based on the
freeform variable-layout approach used for the SVE signal frame.
Additionally, to reduce debug overhead when debugging threads that
might or might not have live SVE register state, NT_ARM_SVE may be
presented in one of two different formats: the old struct
user_fpsimd_state format is embedded for describing the state of a
thread with no live SVE state, whereas a new variable-layout
structure is embedded for describing live SVE state. This avoids a
debugger needing to poll NT_PRFPREG in addition to NT_ARM_SVE, and
allows existing userspace code to handle the non-SVE case without
too much modification.
For this to work, NT_ARM_SVE is defined with a fixed-format header
of type struct user_sve_header, which the recipient can use to
figure out the content, size and layout of the reset of the regset.
Accessor macros are defined to allow the vector-length-dependent
parts of the regset to be manipulated.
Signed-off-by: Alan Hayward <alan.hayward@arm.com>
Signed-off-by: Dave Martin <Dave.Martin@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Cc: Alex Bennée <alex.bennee@linaro.org>
Cc: Okamoto Takayuki <tokamoto@jp.fujitsu.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
2017-10-31 22:51:13 +07:00
|
|
|
|
2012-03-05 18:49:33 +07:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int tls_get(struct task_struct *target, const struct user_regset *regset,
|
|
|
|
unsigned int pos, unsigned int count,
|
|
|
|
void *kbuf, void __user *ubuf)
|
|
|
|
{
|
2018-03-28 16:50:49 +07:00
|
|
|
unsigned long *tls = &target->thread.uw.tp_value;
|
2017-06-21 22:00:44 +07:00
|
|
|
|
|
|
|
if (target == current)
|
|
|
|
tls_preserve_current_state();
|
|
|
|
|
2012-03-05 18:49:33 +07:00
|
|
|
return user_regset_copyout(&pos, &count, &kbuf, &ubuf, tls, 0, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int tls_set(struct task_struct *target, const struct user_regset *regset,
|
|
|
|
unsigned int pos, unsigned int count,
|
|
|
|
const void *kbuf, const void __user *ubuf)
|
|
|
|
{
|
|
|
|
int ret;
|
2018-03-28 16:50:49 +07:00
|
|
|
unsigned long tls = target->thread.uw.tp_value;
|
2012-03-05 18:49:33 +07:00
|
|
|
|
|
|
|
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &tls, 0, -1);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2018-03-28 16:50:49 +07:00
|
|
|
target->thread.uw.tp_value = tls;
|
2012-03-05 18:49:33 +07:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-11-28 12:26:34 +07:00
|
|
|
static int system_call_get(struct task_struct *target,
|
|
|
|
const struct user_regset *regset,
|
|
|
|
unsigned int pos, unsigned int count,
|
|
|
|
void *kbuf, void __user *ubuf)
|
|
|
|
{
|
|
|
|
int syscallno = task_pt_regs(target)->syscallno;
|
|
|
|
|
|
|
|
return user_regset_copyout(&pos, &count, &kbuf, &ubuf,
|
|
|
|
&syscallno, 0, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int system_call_set(struct task_struct *target,
|
|
|
|
const struct user_regset *regset,
|
|
|
|
unsigned int pos, unsigned int count,
|
|
|
|
const void *kbuf, const void __user *ubuf)
|
|
|
|
{
|
2017-01-18 23:25:21 +07:00
|
|
|
int syscallno = task_pt_regs(target)->syscallno;
|
|
|
|
int ret;
|
2014-11-28 12:26:34 +07:00
|
|
|
|
|
|
|
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &syscallno, 0, -1);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
task_pt_regs(target)->syscallno = syscallno;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
arm64/sve: ptrace and ELF coredump support
This patch defines and implements a new regset NT_ARM_SVE, which
describes a thread's SVE register state. This allows a debugger to
manipulate the SVE state, as well as being included in ELF
coredumps for post-mortem debugging.
Because the regset size and layout are dependent on the thread's
current vector length, it is not possible to define a C struct to
describe the regset contents as is done for existing regsets.
Instead, and for the same reasons, NT_ARM_SVE is based on the
freeform variable-layout approach used for the SVE signal frame.
Additionally, to reduce debug overhead when debugging threads that
might or might not have live SVE register state, NT_ARM_SVE may be
presented in one of two different formats: the old struct
user_fpsimd_state format is embedded for describing the state of a
thread with no live SVE state, whereas a new variable-layout
structure is embedded for describing live SVE state. This avoids a
debugger needing to poll NT_PRFPREG in addition to NT_ARM_SVE, and
allows existing userspace code to handle the non-SVE case without
too much modification.
For this to work, NT_ARM_SVE is defined with a fixed-format header
of type struct user_sve_header, which the recipient can use to
figure out the content, size and layout of the reset of the regset.
Accessor macros are defined to allow the vector-length-dependent
parts of the regset to be manipulated.
Signed-off-by: Alan Hayward <alan.hayward@arm.com>
Signed-off-by: Dave Martin <Dave.Martin@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Cc: Alex Bennée <alex.bennee@linaro.org>
Cc: Okamoto Takayuki <tokamoto@jp.fujitsu.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
2017-10-31 22:51:13 +07:00
|
|
|
#ifdef CONFIG_ARM64_SVE
|
|
|
|
|
|
|
|
static void sve_init_header_from_task(struct user_sve_header *header,
|
|
|
|
struct task_struct *target)
|
|
|
|
{
|
|
|
|
unsigned int vq;
|
|
|
|
|
|
|
|
memset(header, 0, sizeof(*header));
|
|
|
|
|
|
|
|
header->flags = test_tsk_thread_flag(target, TIF_SVE) ?
|
|
|
|
SVE_PT_REGS_SVE : SVE_PT_REGS_FPSIMD;
|
|
|
|
if (test_tsk_thread_flag(target, TIF_SVE_VL_INHERIT))
|
|
|
|
header->flags |= SVE_PT_VL_INHERIT;
|
|
|
|
|
|
|
|
header->vl = target->thread.sve_vl;
|
|
|
|
vq = sve_vq_from_vl(header->vl);
|
|
|
|
|
|
|
|
header->max_vl = sve_max_vl;
|
|
|
|
header->size = SVE_PT_SIZE(vq, header->flags);
|
|
|
|
header->max_size = SVE_PT_SIZE(sve_vq_from_vl(header->max_vl),
|
|
|
|
SVE_PT_REGS_SVE);
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned int sve_size_from_header(struct user_sve_header const *header)
|
|
|
|
{
|
|
|
|
return ALIGN(header->size, SVE_VQ_BYTES);
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned int sve_get_size(struct task_struct *target,
|
|
|
|
const struct user_regset *regset)
|
|
|
|
{
|
|
|
|
struct user_sve_header header;
|
|
|
|
|
|
|
|
if (!system_supports_sve())
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
sve_init_header_from_task(&header, target);
|
|
|
|
return sve_size_from_header(&header);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sve_get(struct task_struct *target,
|
|
|
|
const struct user_regset *regset,
|
|
|
|
unsigned int pos, unsigned int count,
|
|
|
|
void *kbuf, void __user *ubuf)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
struct user_sve_header header;
|
|
|
|
unsigned int vq;
|
|
|
|
unsigned long start, end;
|
|
|
|
|
|
|
|
if (!system_supports_sve())
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* Header */
|
|
|
|
sve_init_header_from_task(&header, target);
|
|
|
|
vq = sve_vq_from_vl(header.vl);
|
|
|
|
|
|
|
|
ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, &header,
|
|
|
|
0, sizeof(header));
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
if (target == current)
|
|
|
|
fpsimd_preserve_current_state();
|
|
|
|
|
|
|
|
/* Registers: FPSIMD-only case */
|
|
|
|
|
|
|
|
BUILD_BUG_ON(SVE_PT_FPSIMD_OFFSET != sizeof(header));
|
|
|
|
if ((header.flags & SVE_PT_REGS_MASK) == SVE_PT_REGS_FPSIMD)
|
|
|
|
return __fpr_get(target, regset, pos, count, kbuf, ubuf,
|
|
|
|
SVE_PT_FPSIMD_OFFSET);
|
|
|
|
|
|
|
|
/* Otherwise: full SVE case */
|
|
|
|
|
|
|
|
BUILD_BUG_ON(SVE_PT_SVE_OFFSET != sizeof(header));
|
|
|
|
start = SVE_PT_SVE_OFFSET;
|
|
|
|
end = SVE_PT_SVE_FFR_OFFSET(vq) + SVE_PT_SVE_FFR_SIZE(vq);
|
|
|
|
ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf,
|
|
|
|
target->thread.sve_state,
|
|
|
|
start, end);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
start = end;
|
|
|
|
end = SVE_PT_SVE_FPSR_OFFSET(vq);
|
|
|
|
ret = user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf,
|
|
|
|
start, end);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copy fpsr, and fpcr which must follow contiguously in
|
|
|
|
* struct fpsimd_state:
|
|
|
|
*/
|
|
|
|
start = end;
|
|
|
|
end = SVE_PT_SVE_FPCR_OFFSET(vq) + SVE_PT_SVE_FPCR_SIZE;
|
|
|
|
ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf,
|
2018-03-28 16:50:49 +07:00
|
|
|
&target->thread.uw.fpsimd_state.fpsr,
|
arm64/sve: ptrace and ELF coredump support
This patch defines and implements a new regset NT_ARM_SVE, which
describes a thread's SVE register state. This allows a debugger to
manipulate the SVE state, as well as being included in ELF
coredumps for post-mortem debugging.
Because the regset size and layout are dependent on the thread's
current vector length, it is not possible to define a C struct to
describe the regset contents as is done for existing regsets.
Instead, and for the same reasons, NT_ARM_SVE is based on the
freeform variable-layout approach used for the SVE signal frame.
Additionally, to reduce debug overhead when debugging threads that
might or might not have live SVE register state, NT_ARM_SVE may be
presented in one of two different formats: the old struct
user_fpsimd_state format is embedded for describing the state of a
thread with no live SVE state, whereas a new variable-layout
structure is embedded for describing live SVE state. This avoids a
debugger needing to poll NT_PRFPREG in addition to NT_ARM_SVE, and
allows existing userspace code to handle the non-SVE case without
too much modification.
For this to work, NT_ARM_SVE is defined with a fixed-format header
of type struct user_sve_header, which the recipient can use to
figure out the content, size and layout of the reset of the regset.
Accessor macros are defined to allow the vector-length-dependent
parts of the regset to be manipulated.
Signed-off-by: Alan Hayward <alan.hayward@arm.com>
Signed-off-by: Dave Martin <Dave.Martin@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Cc: Alex Bennée <alex.bennee@linaro.org>
Cc: Okamoto Takayuki <tokamoto@jp.fujitsu.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
2017-10-31 22:51:13 +07:00
|
|
|
start, end);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
start = end;
|
|
|
|
end = sve_size_from_header(&header);
|
|
|
|
return user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf,
|
|
|
|
start, end);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sve_set(struct task_struct *target,
|
|
|
|
const struct user_regset *regset,
|
|
|
|
unsigned int pos, unsigned int count,
|
|
|
|
const void *kbuf, const void __user *ubuf)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
struct user_sve_header header;
|
|
|
|
unsigned int vq;
|
|
|
|
unsigned long start, end;
|
|
|
|
|
|
|
|
if (!system_supports_sve())
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* Header */
|
|
|
|
if (count < sizeof(header))
|
|
|
|
return -EINVAL;
|
|
|
|
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &header,
|
|
|
|
0, sizeof(header));
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Apart from PT_SVE_REGS_MASK, all PT_SVE_* flags are consumed by
|
|
|
|
* sve_set_vector_length(), which will also validate them for us:
|
|
|
|
*/
|
|
|
|
ret = sve_set_vector_length(target, header.vl,
|
|
|
|
((unsigned long)header.flags & ~SVE_PT_REGS_MASK) << 16);
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
/* Actual VL set may be less than the user asked for: */
|
|
|
|
vq = sve_vq_from_vl(target->thread.sve_vl);
|
|
|
|
|
|
|
|
/* Registers: FPSIMD-only case */
|
|
|
|
|
|
|
|
BUILD_BUG_ON(SVE_PT_FPSIMD_OFFSET != sizeof(header));
|
|
|
|
if ((header.flags & SVE_PT_REGS_MASK) == SVE_PT_REGS_FPSIMD) {
|
|
|
|
ret = __fpr_set(target, regset, pos, count, kbuf, ubuf,
|
|
|
|
SVE_PT_FPSIMD_OFFSET);
|
|
|
|
clear_tsk_thread_flag(target, TIF_SVE);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Otherwise: full SVE case */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If setting a different VL from the requested VL and there is
|
|
|
|
* register data, the data layout will be wrong: don't even
|
|
|
|
* try to set the registers in this case.
|
|
|
|
*/
|
|
|
|
if (count && vq != sve_vq_from_vl(header.vl)) {
|
|
|
|
ret = -EIO;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
sve_alloc(target);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Ensure target->thread.sve_state is up to date with target's
|
|
|
|
* FPSIMD regs, so that a short copyin leaves trailing registers
|
|
|
|
* unmodified.
|
|
|
|
*/
|
|
|
|
fpsimd_sync_to_sve(target);
|
|
|
|
set_tsk_thread_flag(target, TIF_SVE);
|
|
|
|
|
|
|
|
BUILD_BUG_ON(SVE_PT_SVE_OFFSET != sizeof(header));
|
|
|
|
start = SVE_PT_SVE_OFFSET;
|
|
|
|
end = SVE_PT_SVE_FFR_OFFSET(vq) + SVE_PT_SVE_FFR_SIZE(vq);
|
|
|
|
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
|
|
|
|
target->thread.sve_state,
|
|
|
|
start, end);
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
start = end;
|
|
|
|
end = SVE_PT_SVE_FPSR_OFFSET(vq);
|
|
|
|
ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf,
|
|
|
|
start, end);
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copy fpsr, and fpcr which must follow contiguously in
|
|
|
|
* struct fpsimd_state:
|
|
|
|
*/
|
|
|
|
start = end;
|
|
|
|
end = SVE_PT_SVE_FPCR_OFFSET(vq) + SVE_PT_SVE_FPCR_SIZE;
|
|
|
|
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
|
2018-03-28 16:50:49 +07:00
|
|
|
&target->thread.uw.fpsimd_state.fpsr,
|
arm64/sve: ptrace and ELF coredump support
This patch defines and implements a new regset NT_ARM_SVE, which
describes a thread's SVE register state. This allows a debugger to
manipulate the SVE state, as well as being included in ELF
coredumps for post-mortem debugging.
Because the regset size and layout are dependent on the thread's
current vector length, it is not possible to define a C struct to
describe the regset contents as is done for existing regsets.
Instead, and for the same reasons, NT_ARM_SVE is based on the
freeform variable-layout approach used for the SVE signal frame.
Additionally, to reduce debug overhead when debugging threads that
might or might not have live SVE register state, NT_ARM_SVE may be
presented in one of two different formats: the old struct
user_fpsimd_state format is embedded for describing the state of a
thread with no live SVE state, whereas a new variable-layout
structure is embedded for describing live SVE state. This avoids a
debugger needing to poll NT_PRFPREG in addition to NT_ARM_SVE, and
allows existing userspace code to handle the non-SVE case without
too much modification.
For this to work, NT_ARM_SVE is defined with a fixed-format header
of type struct user_sve_header, which the recipient can use to
figure out the content, size and layout of the reset of the regset.
Accessor macros are defined to allow the vector-length-dependent
parts of the regset to be manipulated.
Signed-off-by: Alan Hayward <alan.hayward@arm.com>
Signed-off-by: Dave Martin <Dave.Martin@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Cc: Alex Bennée <alex.bennee@linaro.org>
Cc: Okamoto Takayuki <tokamoto@jp.fujitsu.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
2017-10-31 22:51:13 +07:00
|
|
|
start, end);
|
|
|
|
|
|
|
|
out:
|
|
|
|
fpsimd_flush_task_state(target);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* CONFIG_ARM64_SVE */
|
|
|
|
|
2012-03-05 18:49:33 +07:00
|
|
|
enum aarch64_regset {
|
|
|
|
REGSET_GPR,
|
|
|
|
REGSET_FPR,
|
|
|
|
REGSET_TLS,
|
|
|
|
#ifdef CONFIG_HAVE_HW_BREAKPOINT
|
|
|
|
REGSET_HW_BREAK,
|
|
|
|
REGSET_HW_WATCH,
|
|
|
|
#endif
|
2014-11-28 12:26:34 +07:00
|
|
|
REGSET_SYSTEM_CALL,
|
arm64/sve: ptrace and ELF coredump support
This patch defines and implements a new regset NT_ARM_SVE, which
describes a thread's SVE register state. This allows a debugger to
manipulate the SVE state, as well as being included in ELF
coredumps for post-mortem debugging.
Because the regset size and layout are dependent on the thread's
current vector length, it is not possible to define a C struct to
describe the regset contents as is done for existing regsets.
Instead, and for the same reasons, NT_ARM_SVE is based on the
freeform variable-layout approach used for the SVE signal frame.
Additionally, to reduce debug overhead when debugging threads that
might or might not have live SVE register state, NT_ARM_SVE may be
presented in one of two different formats: the old struct
user_fpsimd_state format is embedded for describing the state of a
thread with no live SVE state, whereas a new variable-layout
structure is embedded for describing live SVE state. This avoids a
debugger needing to poll NT_PRFPREG in addition to NT_ARM_SVE, and
allows existing userspace code to handle the non-SVE case without
too much modification.
For this to work, NT_ARM_SVE is defined with a fixed-format header
of type struct user_sve_header, which the recipient can use to
figure out the content, size and layout of the reset of the regset.
Accessor macros are defined to allow the vector-length-dependent
parts of the regset to be manipulated.
Signed-off-by: Alan Hayward <alan.hayward@arm.com>
Signed-off-by: Dave Martin <Dave.Martin@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Cc: Alex Bennée <alex.bennee@linaro.org>
Cc: Okamoto Takayuki <tokamoto@jp.fujitsu.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
2017-10-31 22:51:13 +07:00
|
|
|
#ifdef CONFIG_ARM64_SVE
|
|
|
|
REGSET_SVE,
|
|
|
|
#endif
|
2012-03-05 18:49:33 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
static const struct user_regset aarch64_regsets[] = {
|
|
|
|
[REGSET_GPR] = {
|
|
|
|
.core_note_type = NT_PRSTATUS,
|
|
|
|
.n = sizeof(struct user_pt_regs) / sizeof(u64),
|
|
|
|
.size = sizeof(u64),
|
|
|
|
.align = sizeof(u64),
|
|
|
|
.get = gpr_get,
|
|
|
|
.set = gpr_set
|
|
|
|
},
|
|
|
|
[REGSET_FPR] = {
|
|
|
|
.core_note_type = NT_PRFPREG,
|
|
|
|
.n = sizeof(struct user_fpsimd_state) / sizeof(u32),
|
|
|
|
/*
|
|
|
|
* We pretend we have 32-bit registers because the fpsr and
|
|
|
|
* fpcr are 32-bits wide.
|
|
|
|
*/
|
|
|
|
.size = sizeof(u32),
|
|
|
|
.align = sizeof(u32),
|
|
|
|
.get = fpr_get,
|
|
|
|
.set = fpr_set
|
|
|
|
},
|
|
|
|
[REGSET_TLS] = {
|
|
|
|
.core_note_type = NT_ARM_TLS,
|
|
|
|
.n = 1,
|
|
|
|
.size = sizeof(void *),
|
|
|
|
.align = sizeof(void *),
|
|
|
|
.get = tls_get,
|
|
|
|
.set = tls_set,
|
|
|
|
},
|
|
|
|
#ifdef CONFIG_HAVE_HW_BREAKPOINT
|
|
|
|
[REGSET_HW_BREAK] = {
|
|
|
|
.core_note_type = NT_ARM_HW_BREAK,
|
|
|
|
.n = sizeof(struct user_hwdebug_state) / sizeof(u32),
|
|
|
|
.size = sizeof(u32),
|
|
|
|
.align = sizeof(u32),
|
|
|
|
.get = hw_break_get,
|
|
|
|
.set = hw_break_set,
|
|
|
|
},
|
|
|
|
[REGSET_HW_WATCH] = {
|
|
|
|
.core_note_type = NT_ARM_HW_WATCH,
|
|
|
|
.n = sizeof(struct user_hwdebug_state) / sizeof(u32),
|
|
|
|
.size = sizeof(u32),
|
|
|
|
.align = sizeof(u32),
|
|
|
|
.get = hw_break_get,
|
|
|
|
.set = hw_break_set,
|
|
|
|
},
|
|
|
|
#endif
|
2014-11-28 12:26:34 +07:00
|
|
|
[REGSET_SYSTEM_CALL] = {
|
|
|
|
.core_note_type = NT_ARM_SYSTEM_CALL,
|
|
|
|
.n = 1,
|
|
|
|
.size = sizeof(int),
|
|
|
|
.align = sizeof(int),
|
|
|
|
.get = system_call_get,
|
|
|
|
.set = system_call_set,
|
|
|
|
},
|
arm64/sve: ptrace and ELF coredump support
This patch defines and implements a new regset NT_ARM_SVE, which
describes a thread's SVE register state. This allows a debugger to
manipulate the SVE state, as well as being included in ELF
coredumps for post-mortem debugging.
Because the regset size and layout are dependent on the thread's
current vector length, it is not possible to define a C struct to
describe the regset contents as is done for existing regsets.
Instead, and for the same reasons, NT_ARM_SVE is based on the
freeform variable-layout approach used for the SVE signal frame.
Additionally, to reduce debug overhead when debugging threads that
might or might not have live SVE register state, NT_ARM_SVE may be
presented in one of two different formats: the old struct
user_fpsimd_state format is embedded for describing the state of a
thread with no live SVE state, whereas a new variable-layout
structure is embedded for describing live SVE state. This avoids a
debugger needing to poll NT_PRFPREG in addition to NT_ARM_SVE, and
allows existing userspace code to handle the non-SVE case without
too much modification.
For this to work, NT_ARM_SVE is defined with a fixed-format header
of type struct user_sve_header, which the recipient can use to
figure out the content, size and layout of the reset of the regset.
Accessor macros are defined to allow the vector-length-dependent
parts of the regset to be manipulated.
Signed-off-by: Alan Hayward <alan.hayward@arm.com>
Signed-off-by: Dave Martin <Dave.Martin@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Cc: Alex Bennée <alex.bennee@linaro.org>
Cc: Okamoto Takayuki <tokamoto@jp.fujitsu.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
2017-10-31 22:51:13 +07:00
|
|
|
#ifdef CONFIG_ARM64_SVE
|
|
|
|
[REGSET_SVE] = { /* Scalable Vector Extension */
|
|
|
|
.core_note_type = NT_ARM_SVE,
|
|
|
|
.n = DIV_ROUND_UP(SVE_PT_SIZE(SVE_VQ_MAX, SVE_PT_REGS_SVE),
|
|
|
|
SVE_VQ_BYTES),
|
|
|
|
.size = SVE_VQ_BYTES,
|
|
|
|
.align = SVE_VQ_BYTES,
|
|
|
|
.get = sve_get,
|
|
|
|
.set = sve_set,
|
|
|
|
.get_size = sve_get_size,
|
|
|
|
},
|
|
|
|
#endif
|
2012-03-05 18:49:33 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
static const struct user_regset_view user_aarch64_view = {
|
|
|
|
.name = "aarch64", .e_machine = EM_AARCH64,
|
|
|
|
.regsets = aarch64_regsets, .n = ARRAY_SIZE(aarch64_regsets)
|
|
|
|
};
|
|
|
|
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
|
|
enum compat_regset {
|
|
|
|
REGSET_COMPAT_GPR,
|
|
|
|
REGSET_COMPAT_VFP,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int compat_gpr_get(struct task_struct *target,
|
|
|
|
const struct user_regset *regset,
|
|
|
|
unsigned int pos, unsigned int count,
|
|
|
|
void *kbuf, void __user *ubuf)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
unsigned int i, start, num_regs;
|
|
|
|
|
|
|
|
/* Calculate the number of AArch32 registers contained in count */
|
|
|
|
num_regs = count / regset->size;
|
|
|
|
|
|
|
|
/* Convert pos into an register number */
|
|
|
|
start = pos / regset->size;
|
|
|
|
|
|
|
|
if (start + num_regs > regset->n)
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
for (i = 0; i < num_regs; ++i) {
|
|
|
|
unsigned int idx = start + i;
|
2013-11-28 19:07:22 +07:00
|
|
|
compat_ulong_t reg;
|
2012-03-05 18:49:33 +07:00
|
|
|
|
|
|
|
switch (idx) {
|
|
|
|
case 15:
|
2013-11-28 19:07:22 +07:00
|
|
|
reg = task_pt_regs(target)->pc;
|
2012-03-05 18:49:33 +07:00
|
|
|
break;
|
|
|
|
case 16:
|
2013-11-28 19:07:22 +07:00
|
|
|
reg = task_pt_regs(target)->pstate;
|
2012-03-05 18:49:33 +07:00
|
|
|
break;
|
|
|
|
case 17:
|
2013-11-28 19:07:22 +07:00
|
|
|
reg = task_pt_regs(target)->orig_x0;
|
2012-03-05 18:49:33 +07:00
|
|
|
break;
|
|
|
|
default:
|
2013-11-28 19:07:22 +07:00
|
|
|
reg = task_pt_regs(target)->regs[idx];
|
2012-03-05 18:49:33 +07:00
|
|
|
}
|
|
|
|
|
2014-06-04 01:21:30 +07:00
|
|
|
if (kbuf) {
|
|
|
|
memcpy(kbuf, ®, sizeof(reg));
|
|
|
|
kbuf += sizeof(reg);
|
|
|
|
} else {
|
|
|
|
ret = copy_to_user(ubuf, ®, sizeof(reg));
|
2014-08-22 20:20:24 +07:00
|
|
|
if (ret) {
|
|
|
|
ret = -EFAULT;
|
2014-06-04 01:21:30 +07:00
|
|
|
break;
|
2014-08-22 20:20:24 +07:00
|
|
|
}
|
2014-06-04 01:21:30 +07:00
|
|
|
|
|
|
|
ubuf += sizeof(reg);
|
|
|
|
}
|
2012-03-05 18:49:33 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int compat_gpr_set(struct task_struct *target,
|
|
|
|
const struct user_regset *regset,
|
|
|
|
unsigned int pos, unsigned int count,
|
|
|
|
const void *kbuf, const void __user *ubuf)
|
|
|
|
{
|
|
|
|
struct pt_regs newregs;
|
|
|
|
int ret = 0;
|
|
|
|
unsigned int i, start, num_regs;
|
|
|
|
|
|
|
|
/* Calculate the number of AArch32 registers contained in count */
|
|
|
|
num_regs = count / regset->size;
|
|
|
|
|
|
|
|
/* Convert pos into an register number */
|
|
|
|
start = pos / regset->size;
|
|
|
|
|
|
|
|
if (start + num_regs > regset->n)
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
newregs = *task_pt_regs(target);
|
|
|
|
|
|
|
|
for (i = 0; i < num_regs; ++i) {
|
|
|
|
unsigned int idx = start + i;
|
2013-11-28 19:07:22 +07:00
|
|
|
compat_ulong_t reg;
|
|
|
|
|
2014-06-04 01:21:30 +07:00
|
|
|
if (kbuf) {
|
|
|
|
memcpy(®, kbuf, sizeof(reg));
|
|
|
|
kbuf += sizeof(reg);
|
|
|
|
} else {
|
|
|
|
ret = copy_from_user(®, ubuf, sizeof(reg));
|
2014-08-22 20:20:24 +07:00
|
|
|
if (ret) {
|
|
|
|
ret = -EFAULT;
|
|
|
|
break;
|
|
|
|
}
|
2013-11-28 19:07:22 +07:00
|
|
|
|
2014-06-04 01:21:30 +07:00
|
|
|
ubuf += sizeof(reg);
|
|
|
|
}
|
2012-03-05 18:49:33 +07:00
|
|
|
|
|
|
|
switch (idx) {
|
|
|
|
case 15:
|
2013-11-28 19:07:22 +07:00
|
|
|
newregs.pc = reg;
|
2012-03-05 18:49:33 +07:00
|
|
|
break;
|
|
|
|
case 16:
|
2013-11-28 19:07:22 +07:00
|
|
|
newregs.pstate = reg;
|
2012-03-05 18:49:33 +07:00
|
|
|
break;
|
|
|
|
case 17:
|
2013-11-28 19:07:22 +07:00
|
|
|
newregs.orig_x0 = reg;
|
2012-03-05 18:49:33 +07:00
|
|
|
break;
|
|
|
|
default:
|
2013-11-28 19:07:22 +07:00
|
|
|
newregs.regs[idx] = reg;
|
2012-03-05 18:49:33 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-03-01 21:18:50 +07:00
|
|
|
if (valid_user_regs(&newregs.user_regs, target))
|
2012-03-05 18:49:33 +07:00
|
|
|
*task_pt_regs(target) = newregs;
|
|
|
|
else
|
|
|
|
ret = -EINVAL;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int compat_vfp_get(struct task_struct *target,
|
|
|
|
const struct user_regset *regset,
|
|
|
|
unsigned int pos, unsigned int count,
|
|
|
|
void *kbuf, void __user *ubuf)
|
|
|
|
{
|
|
|
|
struct user_fpsimd_state *uregs;
|
|
|
|
compat_ulong_t fpscr;
|
2017-06-21 22:00:42 +07:00
|
|
|
int ret, vregs_end_pos;
|
2012-03-05 18:49:33 +07:00
|
|
|
|
2018-03-28 16:50:49 +07:00
|
|
|
uregs = &target->thread.uw.fpsimd_state;
|
2012-03-05 18:49:33 +07:00
|
|
|
|
2017-06-21 22:00:43 +07:00
|
|
|
if (target == current)
|
|
|
|
fpsimd_preserve_current_state();
|
|
|
|
|
2012-03-05 18:49:33 +07:00
|
|
|
/*
|
|
|
|
* The VFP registers are packed into the fpsimd_state, so they all sit
|
|
|
|
* nicely together for us. We just need to create the fpscr separately.
|
|
|
|
*/
|
2017-06-21 22:00:42 +07:00
|
|
|
vregs_end_pos = VFP_STATE_SIZE - sizeof(compat_ulong_t);
|
|
|
|
ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, uregs,
|
|
|
|
0, vregs_end_pos);
|
2012-03-05 18:49:33 +07:00
|
|
|
|
|
|
|
if (count && !ret) {
|
|
|
|
fpscr = (uregs->fpsr & VFP_FPSCR_STAT_MASK) |
|
|
|
|
(uregs->fpcr & VFP_FPSCR_CTRL_MASK);
|
2017-06-21 22:00:42 +07:00
|
|
|
|
|
|
|
ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, &fpscr,
|
|
|
|
vregs_end_pos, VFP_STATE_SIZE);
|
2012-03-05 18:49:33 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int compat_vfp_set(struct task_struct *target,
|
|
|
|
const struct user_regset *regset,
|
|
|
|
unsigned int pos, unsigned int count,
|
|
|
|
const void *kbuf, const void __user *ubuf)
|
|
|
|
{
|
|
|
|
struct user_fpsimd_state *uregs;
|
|
|
|
compat_ulong_t fpscr;
|
2017-06-29 21:25:49 +07:00
|
|
|
int ret, vregs_end_pos;
|
2012-03-05 18:49:33 +07:00
|
|
|
|
2018-03-28 16:50:49 +07:00
|
|
|
uregs = &target->thread.uw.fpsimd_state;
|
2012-03-05 18:49:33 +07:00
|
|
|
|
2017-06-29 21:25:49 +07:00
|
|
|
vregs_end_pos = VFP_STATE_SIZE - sizeof(compat_ulong_t);
|
2012-03-05 18:49:33 +07:00
|
|
|
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, uregs, 0,
|
2017-06-29 21:25:49 +07:00
|
|
|
vregs_end_pos);
|
2012-03-05 18:49:33 +07:00
|
|
|
|
|
|
|
if (count && !ret) {
|
2017-06-29 21:25:49 +07:00
|
|
|
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &fpscr,
|
|
|
|
vregs_end_pos, VFP_STATE_SIZE);
|
2017-06-29 21:25:47 +07:00
|
|
|
if (!ret) {
|
|
|
|
uregs->fpsr = fpscr & VFP_FPSCR_STAT_MASK;
|
|
|
|
uregs->fpcr = fpscr & VFP_FPSCR_CTRL_MASK;
|
|
|
|
}
|
2012-03-05 18:49:33 +07:00
|
|
|
}
|
|
|
|
|
2014-05-08 16:20:23 +07:00
|
|
|
fpsimd_flush_task_state(target);
|
2012-03-05 18:49:33 +07:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-07-14 22:20:17 +07:00
|
|
|
static int compat_tls_get(struct task_struct *target,
|
|
|
|
const struct user_regset *regset, unsigned int pos,
|
|
|
|
unsigned int count, void *kbuf, void __user *ubuf)
|
|
|
|
{
|
2018-03-28 16:50:49 +07:00
|
|
|
compat_ulong_t tls = (compat_ulong_t)target->thread.uw.tp_value;
|
2015-07-14 22:20:17 +07:00
|
|
|
return user_regset_copyout(&pos, &count, &kbuf, &ubuf, &tls, 0, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int compat_tls_set(struct task_struct *target,
|
|
|
|
const struct user_regset *regset, unsigned int pos,
|
|
|
|
unsigned int count, const void *kbuf,
|
|
|
|
const void __user *ubuf)
|
|
|
|
{
|
|
|
|
int ret;
|
2018-03-28 16:50:49 +07:00
|
|
|
compat_ulong_t tls = target->thread.uw.tp_value;
|
2015-07-14 22:20:17 +07:00
|
|
|
|
|
|
|
ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, &tls, 0, -1);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2018-03-28 16:50:49 +07:00
|
|
|
target->thread.uw.tp_value = tls;
|
2015-07-14 22:20:17 +07:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-03-05 18:49:33 +07:00
|
|
|
static const struct user_regset aarch32_regsets[] = {
|
|
|
|
[REGSET_COMPAT_GPR] = {
|
|
|
|
.core_note_type = NT_PRSTATUS,
|
|
|
|
.n = COMPAT_ELF_NGREG,
|
|
|
|
.size = sizeof(compat_elf_greg_t),
|
|
|
|
.align = sizeof(compat_elf_greg_t),
|
|
|
|
.get = compat_gpr_get,
|
|
|
|
.set = compat_gpr_set
|
|
|
|
},
|
|
|
|
[REGSET_COMPAT_VFP] = {
|
|
|
|
.core_note_type = NT_ARM_VFP,
|
|
|
|
.n = VFP_STATE_SIZE / sizeof(compat_ulong_t),
|
|
|
|
.size = sizeof(compat_ulong_t),
|
|
|
|
.align = sizeof(compat_ulong_t),
|
|
|
|
.get = compat_vfp_get,
|
|
|
|
.set = compat_vfp_set
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct user_regset_view user_aarch32_view = {
|
|
|
|
.name = "aarch32", .e_machine = EM_ARM,
|
|
|
|
.regsets = aarch32_regsets, .n = ARRAY_SIZE(aarch32_regsets)
|
|
|
|
};
|
|
|
|
|
2015-07-14 22:20:17 +07:00
|
|
|
static const struct user_regset aarch32_ptrace_regsets[] = {
|
|
|
|
[REGSET_GPR] = {
|
|
|
|
.core_note_type = NT_PRSTATUS,
|
|
|
|
.n = COMPAT_ELF_NGREG,
|
|
|
|
.size = sizeof(compat_elf_greg_t),
|
|
|
|
.align = sizeof(compat_elf_greg_t),
|
|
|
|
.get = compat_gpr_get,
|
|
|
|
.set = compat_gpr_set
|
|
|
|
},
|
|
|
|
[REGSET_FPR] = {
|
|
|
|
.core_note_type = NT_ARM_VFP,
|
|
|
|
.n = VFP_STATE_SIZE / sizeof(compat_ulong_t),
|
|
|
|
.size = sizeof(compat_ulong_t),
|
|
|
|
.align = sizeof(compat_ulong_t),
|
|
|
|
.get = compat_vfp_get,
|
|
|
|
.set = compat_vfp_set
|
|
|
|
},
|
|
|
|
[REGSET_TLS] = {
|
|
|
|
.core_note_type = NT_ARM_TLS,
|
|
|
|
.n = 1,
|
|
|
|
.size = sizeof(compat_ulong_t),
|
|
|
|
.align = sizeof(compat_ulong_t),
|
|
|
|
.get = compat_tls_get,
|
|
|
|
.set = compat_tls_set,
|
|
|
|
},
|
|
|
|
#ifdef CONFIG_HAVE_HW_BREAKPOINT
|
|
|
|
[REGSET_HW_BREAK] = {
|
|
|
|
.core_note_type = NT_ARM_HW_BREAK,
|
|
|
|
.n = sizeof(struct user_hwdebug_state) / sizeof(u32),
|
|
|
|
.size = sizeof(u32),
|
|
|
|
.align = sizeof(u32),
|
|
|
|
.get = hw_break_get,
|
|
|
|
.set = hw_break_set,
|
|
|
|
},
|
|
|
|
[REGSET_HW_WATCH] = {
|
|
|
|
.core_note_type = NT_ARM_HW_WATCH,
|
|
|
|
.n = sizeof(struct user_hwdebug_state) / sizeof(u32),
|
|
|
|
.size = sizeof(u32),
|
|
|
|
.align = sizeof(u32),
|
|
|
|
.get = hw_break_get,
|
|
|
|
.set = hw_break_set,
|
|
|
|
},
|
|
|
|
#endif
|
|
|
|
[REGSET_SYSTEM_CALL] = {
|
|
|
|
.core_note_type = NT_ARM_SYSTEM_CALL,
|
|
|
|
.n = 1,
|
|
|
|
.size = sizeof(int),
|
|
|
|
.align = sizeof(int),
|
|
|
|
.get = system_call_get,
|
|
|
|
.set = system_call_set,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct user_regset_view user_aarch32_ptrace_view = {
|
|
|
|
.name = "aarch32", .e_machine = EM_ARM,
|
|
|
|
.regsets = aarch32_ptrace_regsets, .n = ARRAY_SIZE(aarch32_ptrace_regsets)
|
|
|
|
};
|
|
|
|
|
2012-03-05 18:49:33 +07:00
|
|
|
static int compat_ptrace_read_user(struct task_struct *tsk, compat_ulong_t off,
|
|
|
|
compat_ulong_t __user *ret)
|
|
|
|
{
|
|
|
|
compat_ulong_t tmp;
|
|
|
|
|
|
|
|
if (off & 3)
|
|
|
|
return -EIO;
|
|
|
|
|
2012-10-10 21:50:03 +07:00
|
|
|
if (off == COMPAT_PT_TEXT_ADDR)
|
2012-03-05 18:49:33 +07:00
|
|
|
tmp = tsk->mm->start_code;
|
2012-10-10 21:50:03 +07:00
|
|
|
else if (off == COMPAT_PT_DATA_ADDR)
|
2012-03-05 18:49:33 +07:00
|
|
|
tmp = tsk->mm->start_data;
|
2012-10-10 21:50:03 +07:00
|
|
|
else if (off == COMPAT_PT_TEXT_END_ADDR)
|
2012-03-05 18:49:33 +07:00
|
|
|
tmp = tsk->mm->end_code;
|
|
|
|
else if (off < sizeof(compat_elf_gregset_t))
|
|
|
|
return copy_regset_to_user(tsk, &user_aarch32_view,
|
|
|
|
REGSET_COMPAT_GPR, off,
|
|
|
|
sizeof(compat_ulong_t), ret);
|
|
|
|
else if (off >= COMPAT_USER_SZ)
|
|
|
|
return -EIO;
|
|
|
|
else
|
|
|
|
tmp = 0;
|
|
|
|
|
|
|
|
return put_user(tmp, ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int compat_ptrace_write_user(struct task_struct *tsk, compat_ulong_t off,
|
|
|
|
compat_ulong_t val)
|
|
|
|
{
|
|
|
|
int ret;
|
2014-06-02 17:47:23 +07:00
|
|
|
mm_segment_t old_fs = get_fs();
|
2012-03-05 18:49:33 +07:00
|
|
|
|
|
|
|
if (off & 3 || off >= COMPAT_USER_SZ)
|
|
|
|
return -EIO;
|
|
|
|
|
|
|
|
if (off >= sizeof(compat_elf_gregset_t))
|
|
|
|
return 0;
|
|
|
|
|
2014-06-02 17:47:23 +07:00
|
|
|
set_fs(KERNEL_DS);
|
2012-03-05 18:49:33 +07:00
|
|
|
ret = copy_regset_from_user(tsk, &user_aarch32_view,
|
|
|
|
REGSET_COMPAT_GPR, off,
|
|
|
|
sizeof(compat_ulong_t),
|
|
|
|
&val);
|
2014-06-02 17:47:23 +07:00
|
|
|
set_fs(old_fs);
|
|
|
|
|
2012-03-05 18:49:33 +07:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_HAVE_HW_BREAKPOINT
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert a virtual register number into an index for a thread_info
|
|
|
|
* breakpoint array. Breakpoints are identified using positive numbers
|
|
|
|
* whilst watchpoints are negative. The registers are laid out as pairs
|
|
|
|
* of (address, control), each pair mapping to a unique hw_breakpoint struct.
|
|
|
|
* Register 0 is reserved for describing resource information.
|
|
|
|
*/
|
|
|
|
static int compat_ptrace_hbp_num_to_idx(compat_long_t num)
|
|
|
|
{
|
|
|
|
return (abs(num) - 1) >> 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int compat_ptrace_hbp_get_resource_info(u32 *kdata)
|
|
|
|
{
|
|
|
|
u8 num_brps, num_wrps, debug_arch, wp_len;
|
|
|
|
u32 reg = 0;
|
|
|
|
|
|
|
|
num_brps = hw_breakpoint_slots(TYPE_INST);
|
|
|
|
num_wrps = hw_breakpoint_slots(TYPE_DATA);
|
|
|
|
|
|
|
|
debug_arch = debug_monitors_arch();
|
|
|
|
wp_len = 8;
|
|
|
|
reg |= debug_arch;
|
|
|
|
reg <<= 8;
|
|
|
|
reg |= wp_len;
|
|
|
|
reg <<= 8;
|
|
|
|
reg |= num_wrps;
|
|
|
|
reg <<= 8;
|
|
|
|
reg |= num_brps;
|
|
|
|
|
|
|
|
*kdata = reg;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int compat_ptrace_hbp_get(unsigned int note_type,
|
|
|
|
struct task_struct *tsk,
|
|
|
|
compat_long_t num,
|
|
|
|
u32 *kdata)
|
|
|
|
{
|
|
|
|
u64 addr = 0;
|
|
|
|
u32 ctrl = 0;
|
|
|
|
|
2018-02-22 16:54:55 +07:00
|
|
|
int err, idx = compat_ptrace_hbp_num_to_idx(num);
|
2012-03-05 18:49:33 +07:00
|
|
|
|
|
|
|
if (num & 1) {
|
|
|
|
err = ptrace_hbp_get_addr(note_type, tsk, idx, &addr);
|
|
|
|
*kdata = (u32)addr;
|
|
|
|
} else {
|
|
|
|
err = ptrace_hbp_get_ctrl(note_type, tsk, idx, &ctrl);
|
|
|
|
*kdata = ctrl;
|
|
|
|
}
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int compat_ptrace_hbp_set(unsigned int note_type,
|
|
|
|
struct task_struct *tsk,
|
|
|
|
compat_long_t num,
|
|
|
|
u32 *kdata)
|
|
|
|
{
|
|
|
|
u64 addr;
|
|
|
|
u32 ctrl;
|
|
|
|
|
|
|
|
int err, idx = compat_ptrace_hbp_num_to_idx(num);
|
|
|
|
|
|
|
|
if (num & 1) {
|
|
|
|
addr = *kdata;
|
|
|
|
err = ptrace_hbp_set_addr(note_type, tsk, idx, addr);
|
|
|
|
} else {
|
|
|
|
ctrl = *kdata;
|
|
|
|
err = ptrace_hbp_set_ctrl(note_type, tsk, idx, ctrl);
|
|
|
|
}
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int compat_ptrace_gethbpregs(struct task_struct *tsk, compat_long_t num,
|
|
|
|
compat_ulong_t __user *data)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
u32 kdata;
|
|
|
|
|
|
|
|
/* Watchpoint */
|
|
|
|
if (num < 0) {
|
|
|
|
ret = compat_ptrace_hbp_get(NT_ARM_HW_WATCH, tsk, num, &kdata);
|
|
|
|
/* Resource info */
|
|
|
|
} else if (num == 0) {
|
|
|
|
ret = compat_ptrace_hbp_get_resource_info(&kdata);
|
|
|
|
/* Breakpoint */
|
|
|
|
} else {
|
|
|
|
ret = compat_ptrace_hbp_get(NT_ARM_HW_BREAK, tsk, num, &kdata);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ret)
|
|
|
|
ret = put_user(kdata, data);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int compat_ptrace_sethbpregs(struct task_struct *tsk, compat_long_t num,
|
|
|
|
compat_ulong_t __user *data)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
u32 kdata = 0;
|
|
|
|
|
|
|
|
if (num == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
ret = get_user(kdata, data);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
if (num < 0)
|
|
|
|
ret = compat_ptrace_hbp_set(NT_ARM_HW_WATCH, tsk, num, &kdata);
|
|
|
|
else
|
|
|
|
ret = compat_ptrace_hbp_set(NT_ARM_HW_BREAK, tsk, num, &kdata);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
#endif /* CONFIG_HAVE_HW_BREAKPOINT */
|
|
|
|
|
|
|
|
long compat_arch_ptrace(struct task_struct *child, compat_long_t request,
|
|
|
|
compat_ulong_t caddr, compat_ulong_t cdata)
|
|
|
|
{
|
|
|
|
unsigned long addr = caddr;
|
|
|
|
unsigned long data = cdata;
|
|
|
|
void __user *datap = compat_ptr(data);
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
switch (request) {
|
|
|
|
case PTRACE_PEEKUSR:
|
|
|
|
ret = compat_ptrace_read_user(child, addr, datap);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PTRACE_POKEUSR:
|
|
|
|
ret = compat_ptrace_write_user(child, addr, data);
|
|
|
|
break;
|
|
|
|
|
2012-09-27 17:38:12 +07:00
|
|
|
case COMPAT_PTRACE_GETREGS:
|
2012-03-05 18:49:33 +07:00
|
|
|
ret = copy_regset_to_user(child,
|
|
|
|
&user_aarch32_view,
|
|
|
|
REGSET_COMPAT_GPR,
|
|
|
|
0, sizeof(compat_elf_gregset_t),
|
|
|
|
datap);
|
|
|
|
break;
|
|
|
|
|
2012-09-27 17:38:12 +07:00
|
|
|
case COMPAT_PTRACE_SETREGS:
|
2012-03-05 18:49:33 +07:00
|
|
|
ret = copy_regset_from_user(child,
|
|
|
|
&user_aarch32_view,
|
|
|
|
REGSET_COMPAT_GPR,
|
|
|
|
0, sizeof(compat_elf_gregset_t),
|
|
|
|
datap);
|
|
|
|
break;
|
|
|
|
|
2012-09-27 17:38:12 +07:00
|
|
|
case COMPAT_PTRACE_GET_THREAD_AREA:
|
2018-03-28 16:50:49 +07:00
|
|
|
ret = put_user((compat_ulong_t)child->thread.uw.tp_value,
|
2012-03-05 18:49:33 +07:00
|
|
|
(compat_ulong_t __user *)datap);
|
|
|
|
break;
|
|
|
|
|
2012-09-27 17:38:12 +07:00
|
|
|
case COMPAT_PTRACE_SET_SYSCALL:
|
2012-03-05 18:49:33 +07:00
|
|
|
task_pt_regs(child)->syscallno = data;
|
|
|
|
ret = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case COMPAT_PTRACE_GETVFPREGS:
|
|
|
|
ret = copy_regset_to_user(child,
|
|
|
|
&user_aarch32_view,
|
|
|
|
REGSET_COMPAT_VFP,
|
|
|
|
0, VFP_STATE_SIZE,
|
|
|
|
datap);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case COMPAT_PTRACE_SETVFPREGS:
|
|
|
|
ret = copy_regset_from_user(child,
|
|
|
|
&user_aarch32_view,
|
|
|
|
REGSET_COMPAT_VFP,
|
|
|
|
0, VFP_STATE_SIZE,
|
|
|
|
datap);
|
|
|
|
break;
|
|
|
|
|
|
|
|
#ifdef CONFIG_HAVE_HW_BREAKPOINT
|
2012-09-27 17:38:12 +07:00
|
|
|
case COMPAT_PTRACE_GETHBPREGS:
|
2012-03-05 18:49:33 +07:00
|
|
|
ret = compat_ptrace_gethbpregs(child, addr, datap);
|
|
|
|
break;
|
|
|
|
|
2012-09-27 17:38:12 +07:00
|
|
|
case COMPAT_PTRACE_SETHBPREGS:
|
2012-03-05 18:49:33 +07:00
|
|
|
ret = compat_ptrace_sethbpregs(child, addr, datap);
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
default:
|
|
|
|
ret = compat_ptrace_request(child, request, addr,
|
|
|
|
data);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
#endif /* CONFIG_COMPAT */
|
|
|
|
|
|
|
|
const struct user_regset_view *task_user_regset_view(struct task_struct *task)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_COMPAT
|
2015-07-14 22:20:17 +07:00
|
|
|
/*
|
|
|
|
* Core dumping of 32-bit tasks or compat ptrace requests must use the
|
|
|
|
* user_aarch32_view compatible with arm32. Native ptrace requests on
|
|
|
|
* 32-bit children use an extended user_aarch32_ptrace_view to allow
|
|
|
|
* access to the TLS register.
|
|
|
|
*/
|
|
|
|
if (is_compat_task())
|
2012-03-05 18:49:33 +07:00
|
|
|
return &user_aarch32_view;
|
2015-07-14 22:20:17 +07:00
|
|
|
else if (is_compat_thread(task_thread_info(task)))
|
|
|
|
return &user_aarch32_ptrace_view;
|
2012-03-05 18:49:33 +07:00
|
|
|
#endif
|
|
|
|
return &user_aarch64_view;
|
|
|
|
}
|
|
|
|
|
|
|
|
long arch_ptrace(struct task_struct *child, long request,
|
|
|
|
unsigned long addr, unsigned long data)
|
|
|
|
{
|
|
|
|
return ptrace_request(child, request, addr, data);
|
|
|
|
}
|
|
|
|
|
2014-04-30 16:51:30 +07:00
|
|
|
enum ptrace_syscall_dir {
|
|
|
|
PTRACE_SYSCALL_ENTER = 0,
|
|
|
|
PTRACE_SYSCALL_EXIT,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void tracehook_report_syscall(struct pt_regs *regs,
|
|
|
|
enum ptrace_syscall_dir dir)
|
2012-03-05 18:49:33 +07:00
|
|
|
{
|
2014-04-30 16:51:30 +07:00
|
|
|
int regno;
|
2012-03-05 18:49:33 +07:00
|
|
|
unsigned long saved_reg;
|
|
|
|
|
2014-04-30 16:51:30 +07:00
|
|
|
/*
|
|
|
|
* A scratch register (ip(r12) on AArch32, x7 on AArch64) is
|
|
|
|
* used to denote syscall entry/exit:
|
|
|
|
*/
|
|
|
|
regno = (is_compat_task() ? 12 : 7);
|
|
|
|
saved_reg = regs->regs[regno];
|
|
|
|
regs->regs[regno] = dir;
|
2012-03-05 18:49:33 +07:00
|
|
|
|
2014-04-30 16:51:30 +07:00
|
|
|
if (dir == PTRACE_SYSCALL_EXIT)
|
2012-03-05 18:49:33 +07:00
|
|
|
tracehook_report_syscall_exit(regs, 0);
|
|
|
|
else if (tracehook_report_syscall_entry(regs))
|
2017-08-01 21:35:54 +07:00
|
|
|
forget_syscall(regs);
|
2012-03-05 18:49:33 +07:00
|
|
|
|
2014-04-30 16:51:30 +07:00
|
|
|
regs->regs[regno] = saved_reg;
|
|
|
|
}
|
|
|
|
|
|
|
|
asmlinkage int syscall_trace_enter(struct pt_regs *regs)
|
|
|
|
{
|
|
|
|
if (test_thread_flag(TIF_SYSCALL_TRACE))
|
|
|
|
tracehook_report_syscall(regs, PTRACE_SYSCALL_ENTER);
|
2012-03-05 18:49:33 +07:00
|
|
|
|
2016-06-03 02:28:52 +07:00
|
|
|
/* Do the secure computing after ptrace; failures should be fast. */
|
|
|
|
if (secure_computing(NULL) == -1)
|
|
|
|
return -1;
|
|
|
|
|
2014-04-30 16:54:36 +07:00
|
|
|
if (test_thread_flag(TIF_SYSCALL_TRACEPOINT))
|
|
|
|
trace_sys_enter(regs, regs->syscallno);
|
|
|
|
|
2014-09-24 03:25:34 +07:00
|
|
|
audit_syscall_entry(regs->syscallno, regs->orig_x0, regs->regs[1],
|
|
|
|
regs->regs[2], regs->regs[3]);
|
2014-07-04 14:28:31 +07:00
|
|
|
|
2012-03-05 18:49:33 +07:00
|
|
|
return regs->syscallno;
|
|
|
|
}
|
2014-04-30 16:51:30 +07:00
|
|
|
|
|
|
|
asmlinkage void syscall_trace_exit(struct pt_regs *regs)
|
|
|
|
{
|
2014-07-04 14:28:31 +07:00
|
|
|
audit_syscall_exit(regs);
|
|
|
|
|
2014-04-30 16:54:36 +07:00
|
|
|
if (test_thread_flag(TIF_SYSCALL_TRACEPOINT))
|
|
|
|
trace_sys_exit(regs, regs_return_value(regs));
|
|
|
|
|
2014-04-30 16:51:30 +07:00
|
|
|
if (test_thread_flag(TIF_SYSCALL_TRACE))
|
|
|
|
tracehook_report_syscall(regs, PTRACE_SYSCALL_EXIT);
|
|
|
|
}
|
2016-03-01 21:18:50 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Bits which are always architecturally RES0 per ARM DDI 0487A.h
|
|
|
|
* Userspace cannot use these until they have an architectural meaning.
|
|
|
|
* We also reserve IL for the kernel; SS is handled dynamically.
|
|
|
|
*/
|
|
|
|
#define SPSR_EL1_AARCH64_RES0_BITS \
|
|
|
|
(GENMASK_ULL(63,32) | GENMASK_ULL(27, 22) | GENMASK_ULL(20, 10) | \
|
|
|
|
GENMASK_ULL(5, 5))
|
|
|
|
#define SPSR_EL1_AARCH32_RES0_BITS \
|
|
|
|
(GENMASK_ULL(63,32) | GENMASK_ULL(24, 22) | GENMASK_ULL(20,20))
|
|
|
|
|
|
|
|
static int valid_compat_regs(struct user_pt_regs *regs)
|
|
|
|
{
|
|
|
|
regs->pstate &= ~SPSR_EL1_AARCH32_RES0_BITS;
|
|
|
|
|
|
|
|
if (!system_supports_mixed_endian_el0()) {
|
|
|
|
if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN))
|
|
|
|
regs->pstate |= COMPAT_PSR_E_BIT;
|
|
|
|
else
|
|
|
|
regs->pstate &= ~COMPAT_PSR_E_BIT;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (user_mode(regs) && (regs->pstate & PSR_MODE32_BIT) &&
|
|
|
|
(regs->pstate & COMPAT_PSR_A_BIT) == 0 &&
|
|
|
|
(regs->pstate & COMPAT_PSR_I_BIT) == 0 &&
|
|
|
|
(regs->pstate & COMPAT_PSR_F_BIT) == 0) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Force PSR to a valid 32-bit EL0t, preserving the same bits as
|
|
|
|
* arch/arm.
|
|
|
|
*/
|
|
|
|
regs->pstate &= COMPAT_PSR_N_BIT | COMPAT_PSR_Z_BIT |
|
|
|
|
COMPAT_PSR_C_BIT | COMPAT_PSR_V_BIT |
|
|
|
|
COMPAT_PSR_Q_BIT | COMPAT_PSR_IT_MASK |
|
|
|
|
COMPAT_PSR_GE_MASK | COMPAT_PSR_E_BIT |
|
|
|
|
COMPAT_PSR_T_BIT;
|
|
|
|
regs->pstate |= PSR_MODE32_BIT;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int valid_native_regs(struct user_pt_regs *regs)
|
|
|
|
{
|
|
|
|
regs->pstate &= ~SPSR_EL1_AARCH64_RES0_BITS;
|
|
|
|
|
|
|
|
if (user_mode(regs) && !(regs->pstate & PSR_MODE32_BIT) &&
|
|
|
|
(regs->pstate & PSR_D_BIT) == 0 &&
|
|
|
|
(regs->pstate & PSR_A_BIT) == 0 &&
|
|
|
|
(regs->pstate & PSR_I_BIT) == 0 &&
|
|
|
|
(regs->pstate & PSR_F_BIT) == 0) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Force PSR to a valid 64-bit EL0t */
|
|
|
|
regs->pstate &= PSR_N_BIT | PSR_Z_BIT | PSR_C_BIT | PSR_V_BIT;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Are the current registers suitable for user mode? (used to maintain
|
|
|
|
* security in signal handlers)
|
|
|
|
*/
|
|
|
|
int valid_user_regs(struct user_pt_regs *regs, struct task_struct *task)
|
|
|
|
{
|
|
|
|
if (!test_tsk_thread_flag(task, TIF_SINGLESTEP))
|
|
|
|
regs->pstate &= ~DBG_SPSR_SS;
|
|
|
|
|
|
|
|
if (is_compat_thread(task_thread_info(task)))
|
|
|
|
return valid_compat_regs(regs);
|
|
|
|
else
|
|
|
|
return valid_native_regs(regs);
|
|
|
|
}
|