Skip to content

Commit

Permalink
MIPS: Ptrace support for HARDWARE_WATCHPOINTS
Browse files Browse the repository at this point in the history
This is the final part of the watch register patch.  Here we hook up
ptrace so that the user space debugger (gdb), can set and read the
registers.

Signed-off-by: David Daney <ddaney@avtrex.com>
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
  • Loading branch information
David Daney authored and Ralf Baechle committed Oct 11, 2008
1 parent 2c708cb commit 0926bf9
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 1 deletion.
46 changes: 46 additions & 0 deletions arch/mips/include/asm/ptrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,57 @@ struct pt_regs {
#define PTRACE_POKEDATA_3264 0xc3
#define PTRACE_GET_THREAD_AREA_3264 0xc4

/* Read and write watchpoint registers. */
enum pt_watch_style {
pt_watch_style_mips32,
pt_watch_style_mips64
};
struct mips32_watch_regs {
uint32_t watchlo[8];
/* Lower 16 bits of watchhi. */
uint16_t watchhi[8];
/* Valid mask and I R W bits.
* bit 0 -- 1 if W bit is usable.
* bit 1 -- 1 if R bit is usable.
* bit 2 -- 1 if I bit is usable.
* bits 3 - 11 -- Valid watchhi mask bits.
*/
uint16_t watch_masks[8];
/* The number of valid watch register pairs. */
uint32_t num_valid;
} __attribute__((aligned(8)));

struct mips64_watch_regs {
uint64_t watchlo[8];
uint16_t watchhi[8];
uint16_t watch_masks[8];
uint32_t num_valid;
} __attribute__((aligned(8)));

struct pt_watch_regs {
enum pt_watch_style style;
union {
struct mips32_watch_regs mips32;
struct mips32_watch_regs mips64;
};
};

#define PTRACE_GET_WATCH_REGS 0xd0
#define PTRACE_SET_WATCH_REGS 0xd1

#ifdef __KERNEL__

#include <linux/compiler.h>
#include <linux/linkage.h>
#include <asm/isadep.h>

struct task_struct;

extern int ptrace_get_watch_regs(struct task_struct *child,
struct pt_watch_regs __user *addr);
extern int ptrace_set_watch_regs(struct task_struct *child,
struct pt_watch_regs __user *addr);

/*
* Does the process account for user or for system time?
*/
Expand Down
100 changes: 99 additions & 1 deletion arch/mips/kernel/ptrace.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
*/
void ptrace_disable(struct task_struct *child)
{
/* Nothing to do.. */
/* Don't load the watchpoint registers for the ex-child. */
clear_tsk_thread_flag(child, TIF_LOAD_WATCH);
}

/*
Expand Down Expand Up @@ -167,6 +168,93 @@ int ptrace_setfpregs(struct task_struct *child, __u32 __user *data)
return 0;
}

int ptrace_get_watch_regs(struct task_struct *child,
struct pt_watch_regs __user *addr)
{
enum pt_watch_style style;
int i;

if (!cpu_has_watch || current_cpu_data.watch_reg_use_cnt == 0)
return -EIO;
if (!access_ok(VERIFY_WRITE, addr, sizeof(struct pt_watch_regs)))
return -EIO;

#ifdef CONFIG_32BIT
style = pt_watch_style_mips32;
#define WATCH_STYLE mips32
#else
style = pt_watch_style_mips64;
#define WATCH_STYLE mips64
#endif

__put_user(style, &addr->style);
__put_user(current_cpu_data.watch_reg_use_cnt,
&addr->WATCH_STYLE.num_valid);
for (i = 0; i < current_cpu_data.watch_reg_use_cnt; i++) {
__put_user(child->thread.watch.mips3264.watchlo[i],
&addr->WATCH_STYLE.watchlo[i]);
__put_user(child->thread.watch.mips3264.watchhi[i] & 0xfff,
&addr->WATCH_STYLE.watchhi[i]);
__put_user(current_cpu_data.watch_reg_masks[i],
&addr->WATCH_STYLE.watch_masks[i]);
}
for (; i < 8; i++) {
__put_user(0, &addr->WATCH_STYLE.watchlo[i]);
__put_user(0, &addr->WATCH_STYLE.watchhi[i]);
__put_user(0, &addr->WATCH_STYLE.watch_masks[i]);
}

return 0;
}

int ptrace_set_watch_regs(struct task_struct *child,
struct pt_watch_regs __user *addr)
{
int i;
int watch_active = 0;
unsigned long lt[NUM_WATCH_REGS];
u16 ht[NUM_WATCH_REGS];

if (!cpu_has_watch || current_cpu_data.watch_reg_use_cnt == 0)
return -EIO;
if (!access_ok(VERIFY_READ, addr, sizeof(struct pt_watch_regs)))
return -EIO;
/* Check the values. */
for (i = 0; i < current_cpu_data.watch_reg_use_cnt; i++) {
__get_user(lt[i], &addr->WATCH_STYLE.watchlo[i]);
#ifdef CONFIG_32BIT
if (lt[i] & __UA_LIMIT)
return -EINVAL;
#else
if (test_tsk_thread_flag(child, TIF_32BIT_ADDR)) {
if (lt[i] & 0xffffffff80000000UL)
return -EINVAL;
} else {
if (lt[i] & __UA_LIMIT)
return -EINVAL;
}
#endif
__get_user(ht[i], &addr->WATCH_STYLE.watchhi[i]);
if (ht[i] & ~0xff8)
return -EINVAL;
}
/* Install them. */
for (i = 0; i < current_cpu_data.watch_reg_use_cnt; i++) {
if (lt[i] & 7)
watch_active = 1;
child->thread.watch.mips3264.watchlo[i] = lt[i];
/* Set the G bit. */
child->thread.watch.mips3264.watchhi[i] = ht[i];
}

if (watch_active)
set_tsk_thread_flag(child, TIF_LOAD_WATCH);
else
clear_tsk_thread_flag(child, TIF_LOAD_WATCH);

return 0;
}

long arch_ptrace(struct task_struct *child, long request, long addr, long data)
{
int ret;
Expand Down Expand Up @@ -440,6 +528,16 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data)
(unsigned long __user *) data);
break;

case PTRACE_GET_WATCH_REGS:
ret = ptrace_get_watch_regs(child,
(struct pt_watch_regs __user *) addr);
break;

case PTRACE_SET_WATCH_REGS:
ret = ptrace_set_watch_regs(child,
(struct pt_watch_regs __user *) addr);
break;

default:
ret = ptrace_request(child, request, addr, data);
break;
Expand Down
10 changes: 10 additions & 0 deletions arch/mips/kernel/ptrace32.c
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,16 @@ long compat_arch_ptrace(struct task_struct *child, compat_long_t request,
(unsigned long __user *) (unsigned long) data);
break;

case PTRACE_GET_WATCH_REGS:
ret = ptrace_get_watch_regs(child,
(struct pt_watch_regs __user *) (unsigned long) addr);
break;

case PTRACE_SET_WATCH_REGS:
ret = ptrace_set_watch_regs(child,
(struct pt_watch_regs __user *) (unsigned long) addr);
break;

default:
ret = ptrace_request(child, request, addr, data);
break;
Expand Down

0 comments on commit 0926bf9

Please sign in to comment.