diff options
| -rw-r--r-- | arch/arm/include/asm/hw_breakpoint.h | 12 | ||||
| -rw-r--r-- | arch/arm/include/asm/processor.h | 4 | ||||
| -rw-r--r-- | arch/arm/include/asm/ptrace.h | 2 | ||||
| -rw-r--r-- | arch/arm/kernel/process.c | 5 | ||||
| -rw-r--r-- | arch/arm/kernel/ptrace.c | 239 |
5 files changed, 261 insertions, 1 deletions
diff --git a/arch/arm/include/asm/hw_breakpoint.h b/arch/arm/include/asm/hw_breakpoint.h index 33048c700caa..4d8ae9d67abe 100644 --- a/arch/arm/include/asm/hw_breakpoint.h +++ b/arch/arm/include/asm/hw_breakpoint.h | |||
| @@ -2,6 +2,11 @@ | |||
| 2 | #define _ARM_HW_BREAKPOINT_H | 2 | #define _ARM_HW_BREAKPOINT_H |
| 3 | 3 | ||
| 4 | #ifdef __KERNEL__ | 4 | #ifdef __KERNEL__ |
| 5 | |||
| 6 | struct task_struct; | ||
| 7 | |||
| 8 | #ifdef CONFIG_HAVE_HW_BREAKPOINT | ||
| 9 | |||
| 5 | struct arch_hw_breakpoint_ctrl { | 10 | struct arch_hw_breakpoint_ctrl { |
| 6 | u32 __reserved : 9, | 11 | u32 __reserved : 9, |
| 7 | mismatch : 1, | 12 | mismatch : 1, |
| @@ -102,7 +107,6 @@ static inline void decode_ctrl_reg(u32 reg, | |||
| 102 | struct notifier_block; | 107 | struct notifier_block; |
| 103 | struct perf_event; | 108 | struct perf_event; |
| 104 | struct pmu; | 109 | struct pmu; |
| 105 | struct task_struct; | ||
| 106 | 110 | ||
| 107 | extern struct pmu perf_ops_bp; | 111 | extern struct pmu perf_ops_bp; |
| 108 | extern int arch_bp_generic_fields(struct arch_hw_breakpoint_ctrl ctrl, | 112 | extern int arch_bp_generic_fields(struct arch_hw_breakpoint_ctrl ctrl, |
| @@ -111,13 +115,19 @@ extern int arch_check_bp_in_kernelspace(struct perf_event *bp); | |||
| 111 | extern int arch_validate_hwbkpt_settings(struct perf_event *bp); | 115 | extern int arch_validate_hwbkpt_settings(struct perf_event *bp); |
| 112 | extern int hw_breakpoint_exceptions_notify(struct notifier_block *unused, | 116 | extern int hw_breakpoint_exceptions_notify(struct notifier_block *unused, |
| 113 | unsigned long val, void *data); | 117 | unsigned long val, void *data); |
| 118 | |||
| 114 | extern u8 arch_get_debug_arch(void); | 119 | extern u8 arch_get_debug_arch(void); |
| 115 | extern u8 arch_get_max_wp_len(void); | 120 | extern u8 arch_get_max_wp_len(void); |
| 121 | extern void clear_ptrace_hw_breakpoint(struct task_struct *tsk); | ||
| 116 | 122 | ||
| 117 | int arch_install_hw_breakpoint(struct perf_event *bp); | 123 | int arch_install_hw_breakpoint(struct perf_event *bp); |
| 118 | void arch_uninstall_hw_breakpoint(struct perf_event *bp); | 124 | void arch_uninstall_hw_breakpoint(struct perf_event *bp); |
| 119 | void hw_breakpoint_pmu_read(struct perf_event *bp); | 125 | void hw_breakpoint_pmu_read(struct perf_event *bp); |
| 120 | int hw_breakpoint_slots(int type); | 126 | int hw_breakpoint_slots(int type); |
| 121 | 127 | ||
| 128 | #else | ||
| 129 | static inline void clear_ptrace_hw_breakpoint(struct task_struct *tsk) {} | ||
| 130 | |||
| 131 | #endif /* CONFIG_HAVE_HW_BREAKPOINT */ | ||
| 122 | #endif /* __KERNEL__ */ | 132 | #endif /* __KERNEL__ */ |
| 123 | #endif /* _ARM_HW_BREAKPOINT_H */ | 133 | #endif /* _ARM_HW_BREAKPOINT_H */ |
diff --git a/arch/arm/include/asm/processor.h b/arch/arm/include/asm/processor.h index 7bed3daf83b8..67357baaeeeb 100644 --- a/arch/arm/include/asm/processor.h +++ b/arch/arm/include/asm/processor.h | |||
| @@ -19,6 +19,7 @@ | |||
| 19 | 19 | ||
| 20 | #ifdef __KERNEL__ | 20 | #ifdef __KERNEL__ |
| 21 | 21 | ||
| 22 | #include <asm/hw_breakpoint.h> | ||
| 22 | #include <asm/ptrace.h> | 23 | #include <asm/ptrace.h> |
| 23 | #include <asm/types.h> | 24 | #include <asm/types.h> |
| 24 | 25 | ||
| @@ -41,6 +42,9 @@ struct debug_entry { | |||
| 41 | struct debug_info { | 42 | struct debug_info { |
| 42 | int nsaved; | 43 | int nsaved; |
| 43 | struct debug_entry bp[2]; | 44 | struct debug_entry bp[2]; |
| 45 | #ifdef CONFIG_HAVE_HW_BREAKPOINT | ||
| 46 | struct perf_event *hbp[ARM_MAX_HBP_SLOTS]; | ||
| 47 | #endif | ||
| 44 | }; | 48 | }; |
| 45 | 49 | ||
| 46 | struct thread_struct { | 50 | struct thread_struct { |
diff --git a/arch/arm/include/asm/ptrace.h b/arch/arm/include/asm/ptrace.h index 7ce15eb15f72..783d50f32618 100644 --- a/arch/arm/include/asm/ptrace.h +++ b/arch/arm/include/asm/ptrace.h | |||
| @@ -29,6 +29,8 @@ | |||
| 29 | #define PTRACE_SETCRUNCHREGS 26 | 29 | #define PTRACE_SETCRUNCHREGS 26 |
| 30 | #define PTRACE_GETVFPREGS 27 | 30 | #define PTRACE_GETVFPREGS 27 |
| 31 | #define PTRACE_SETVFPREGS 28 | 31 | #define PTRACE_SETVFPREGS 28 |
| 32 | #define PTRACE_GETHBPREGS 29 | ||
| 33 | #define PTRACE_SETHBPREGS 30 | ||
| 32 | 34 | ||
| 33 | /* | 35 | /* |
| 34 | * PSR bits | 36 | * PSR bits |
diff --git a/arch/arm/kernel/process.c b/arch/arm/kernel/process.c index 401e38be1f78..974af1c3eb1d 100644 --- a/arch/arm/kernel/process.c +++ b/arch/arm/kernel/process.c | |||
| @@ -29,6 +29,7 @@ | |||
| 29 | #include <linux/utsname.h> | 29 | #include <linux/utsname.h> |
| 30 | #include <linux/uaccess.h> | 30 | #include <linux/uaccess.h> |
| 31 | #include <linux/random.h> | 31 | #include <linux/random.h> |
| 32 | #include <linux/hw_breakpoint.h> | ||
| 32 | 33 | ||
| 33 | #include <asm/cacheflush.h> | 34 | #include <asm/cacheflush.h> |
| 34 | #include <asm/leds.h> | 35 | #include <asm/leds.h> |
| @@ -317,6 +318,8 @@ void flush_thread(void) | |||
| 317 | struct thread_info *thread = current_thread_info(); | 318 | struct thread_info *thread = current_thread_info(); |
| 318 | struct task_struct *tsk = current; | 319 | struct task_struct *tsk = current; |
| 319 | 320 | ||
| 321 | flush_ptrace_hw_breakpoint(tsk); | ||
| 322 | |||
| 320 | memset(thread->used_cp, 0, sizeof(thread->used_cp)); | 323 | memset(thread->used_cp, 0, sizeof(thread->used_cp)); |
| 321 | memset(&tsk->thread.debug, 0, sizeof(struct debug_info)); | 324 | memset(&tsk->thread.debug, 0, sizeof(struct debug_info)); |
| 322 | memset(&thread->fpstate, 0, sizeof(union fp_state)); | 325 | memset(&thread->fpstate, 0, sizeof(union fp_state)); |
| @@ -345,6 +348,8 @@ copy_thread(unsigned long clone_flags, unsigned long stack_start, | |||
| 345 | thread->cpu_context.sp = (unsigned long)childregs; | 348 | thread->cpu_context.sp = (unsigned long)childregs; |
| 346 | thread->cpu_context.pc = (unsigned long)ret_from_fork; | 349 | thread->cpu_context.pc = (unsigned long)ret_from_fork; |
| 347 | 350 | ||
| 351 | clear_ptrace_hw_breakpoint(p); | ||
| 352 | |||
| 348 | if (clone_flags & CLONE_SETTLS) | 353 | if (clone_flags & CLONE_SETTLS) |
| 349 | thread->tp_value = regs->ARM_r3; | 354 | thread->tp_value = regs->ARM_r3; |
| 350 | 355 | ||
diff --git a/arch/arm/kernel/ptrace.c b/arch/arm/kernel/ptrace.c index f99d489822d5..e0cb6370ed14 100644 --- a/arch/arm/kernel/ptrace.c +++ b/arch/arm/kernel/ptrace.c | |||
| @@ -19,6 +19,8 @@ | |||
| 19 | #include <linux/init.h> | 19 | #include <linux/init.h> |
| 20 | #include <linux/signal.h> | 20 | #include <linux/signal.h> |
| 21 | #include <linux/uaccess.h> | 21 | #include <linux/uaccess.h> |
| 22 | #include <linux/perf_event.h> | ||
| 23 | #include <linux/hw_breakpoint.h> | ||
| 22 | 24 | ||
| 23 | #include <asm/pgtable.h> | 25 | #include <asm/pgtable.h> |
| 24 | #include <asm/system.h> | 26 | #include <asm/system.h> |
| @@ -847,6 +849,232 @@ static int ptrace_setvfpregs(struct task_struct *tsk, void __user *data) | |||
| 847 | } | 849 | } |
| 848 | #endif | 850 | #endif |
| 849 | 851 | ||
| 852 | #ifdef CONFIG_HAVE_HW_BREAKPOINT | ||
| 853 | /* | ||
| 854 | * Convert a virtual register number into an index for a thread_info | ||
| 855 | * breakpoint array. Breakpoints are identified using positive numbers | ||
| 856 | * whilst watchpoints are negative. The registers are laid out as pairs | ||
| 857 | * of (address, control), each pair mapping to a unique hw_breakpoint struct. | ||
| 858 | * Register 0 is reserved for describing resource information. | ||
| 859 | */ | ||
| 860 | static int ptrace_hbp_num_to_idx(long num) | ||
| 861 | { | ||
| 862 | if (num < 0) | ||
| 863 | num = (ARM_MAX_BRP << 1) - num; | ||
| 864 | return (num - 1) >> 1; | ||
| 865 | } | ||
| 866 | |||
| 867 | /* | ||
| 868 | * Returns the virtual register number for the address of the | ||
| 869 | * breakpoint at index idx. | ||
| 870 | */ | ||
| 871 | static long ptrace_hbp_idx_to_num(int idx) | ||
| 872 | { | ||
| 873 | long mid = ARM_MAX_BRP << 1; | ||
| 874 | long num = (idx << 1) + 1; | ||
| 875 | return num > mid ? mid - num : num; | ||
| 876 | } | ||
| 877 | |||
| 878 | /* | ||
| 879 | * Handle hitting a HW-breakpoint. | ||
| 880 | */ | ||
| 881 | static void ptrace_hbptriggered(struct perf_event *bp, int unused, | ||
| 882 | struct perf_sample_data *data, | ||
| 883 | struct pt_regs *regs) | ||
| 884 | { | ||
| 885 | struct arch_hw_breakpoint *bkpt = counter_arch_bp(bp); | ||
| 886 | long num; | ||
| 887 | int i; | ||
| 888 | siginfo_t info; | ||
| 889 | |||
| 890 | for (i = 0; i < ARM_MAX_HBP_SLOTS; ++i) | ||
| 891 | if (current->thread.debug.hbp[i] == bp) | ||
| 892 | break; | ||
| 893 | |||
| 894 | num = (i == ARM_MAX_HBP_SLOTS) ? 0 : ptrace_hbp_idx_to_num(i); | ||
| 895 | |||
| 896 | info.si_signo = SIGTRAP; | ||
| 897 | info.si_errno = (int)num; | ||
| 898 | info.si_code = TRAP_HWBKPT; | ||
| 899 | info.si_addr = (void __user *)(bkpt->trigger); | ||
| 900 | |||
| 901 | force_sig_info(SIGTRAP, &info, current); | ||
| 902 | } | ||
| 903 | |||
| 904 | /* | ||
| 905 | * Set ptrace breakpoint pointers to zero for this task. | ||
| 906 | * This is required in order to prevent child processes from unregistering | ||
| 907 | * breakpoints held by their parent. | ||
| 908 | */ | ||
| 909 | void clear_ptrace_hw_breakpoint(struct task_struct *tsk) | ||
| 910 | { | ||
| 911 | memset(tsk->thread.debug.hbp, 0, sizeof(tsk->thread.debug.hbp)); | ||
| 912 | } | ||
| 913 | |||
| 914 | /* | ||
| 915 | * Unregister breakpoints from this task and reset the pointers in | ||
| 916 | * the thread_struct. | ||
| 917 | */ | ||
| 918 | void flush_ptrace_hw_breakpoint(struct task_struct *tsk) | ||
| 919 | { | ||
| 920 | int i; | ||
| 921 | struct thread_struct *t = &tsk->thread; | ||
| 922 | |||
| 923 | for (i = 0; i < ARM_MAX_HBP_SLOTS; i++) { | ||
| 924 | if (t->debug.hbp[i]) { | ||
| 925 | unregister_hw_breakpoint(t->debug.hbp[i]); | ||
| 926 | t->debug.hbp[i] = NULL; | ||
| 927 | } | ||
| 928 | } | ||
| 929 | } | ||
| 930 | |||
| 931 | static u32 ptrace_get_hbp_resource_info(void) | ||
| 932 | { | ||
| 933 | u8 num_brps, num_wrps, debug_arch, wp_len; | ||
| 934 | u32 reg = 0; | ||
| 935 | |||
| 936 | num_brps = hw_breakpoint_slots(TYPE_INST); | ||
| 937 | num_wrps = hw_breakpoint_slots(TYPE_DATA); | ||
| 938 | debug_arch = arch_get_debug_arch(); | ||
| 939 | wp_len = arch_get_max_wp_len(); | ||
| 940 | |||
| 941 | reg |= debug_arch; | ||
| 942 | reg <<= 8; | ||
| 943 | reg |= wp_len; | ||
| 944 | reg <<= 8; | ||
| 945 | reg |= num_wrps; | ||
| 946 | reg <<= 8; | ||
| 947 | reg |= num_brps; | ||
| 948 | |||
| 949 | return reg; | ||
| 950 | } | ||
| 951 | |||
| 952 | static struct perf_event *ptrace_hbp_create(struct task_struct *tsk, int type) | ||
| 953 | { | ||
| 954 | struct perf_event_attr attr; | ||
| 955 | |||
| 956 | ptrace_breakpoint_init(&attr); | ||
| 957 | |||
| 958 | /* Initialise fields to sane defaults. */ | ||
| 959 | attr.bp_addr = 0; | ||
| 960 | attr.bp_len = HW_BREAKPOINT_LEN_4; | ||
| 961 | attr.bp_type = type; | ||
| 962 | attr.disabled = 1; | ||
| 963 | |||
| 964 | return register_user_hw_breakpoint(&attr, ptrace_hbptriggered, tsk); | ||
| 965 | } | ||
| 966 | |||
| 967 | static int ptrace_gethbpregs(struct task_struct *tsk, long num, | ||
| 968 | unsigned long __user *data) | ||
| 969 | { | ||
| 970 | u32 reg; | ||
| 971 | int idx, ret = 0; | ||
| 972 | struct perf_event *bp; | ||
| 973 | struct arch_hw_breakpoint_ctrl arch_ctrl; | ||
| 974 | |||
| 975 | if (num == 0) { | ||
| 976 | reg = ptrace_get_hbp_resource_info(); | ||
| 977 | } else { | ||
| 978 | idx = ptrace_hbp_num_to_idx(num); | ||
| 979 | if (idx < 0 || idx >= ARM_MAX_HBP_SLOTS) { | ||
| 980 | ret = -EINVAL; | ||
| 981 | goto out; | ||
| 982 | } | ||
| 983 | |||
| 984 | bp = tsk->thread.debug.hbp[idx]; | ||
| 985 | if (!bp) { | ||
| 986 | reg = 0; | ||
| 987 | goto put; | ||
| 988 | } | ||
| 989 | |||
| 990 | arch_ctrl = counter_arch_bp(bp)->ctrl; | ||
| 991 | |||
| 992 | /* | ||
| 993 | * Fix up the len because we may have adjusted it | ||
| 994 | * to compensate for an unaligned address. | ||
| 995 | */ | ||
| 996 | while (!(arch_ctrl.len & 0x1)) | ||
| 997 | arch_ctrl.len >>= 1; | ||
| 998 | |||
| 999 | if (idx & 0x1) | ||
| 1000 | reg = encode_ctrl_reg(arch_ctrl); | ||
| 1001 | else | ||
| 1002 | reg = bp->attr.bp_addr; | ||
| 1003 | } | ||
| 1004 | |||
| 1005 | put: | ||
| 1006 | if (put_user(reg, data)) | ||
| 1007 | ret = -EFAULT; | ||
| 1008 | |||
| 1009 | out: | ||
| 1010 | return ret; | ||
| 1011 | } | ||
| 1012 | |||
| 1013 | static int ptrace_sethbpregs(struct task_struct *tsk, long num, | ||
| 1014 | unsigned long __user *data) | ||
| 1015 | { | ||
| 1016 | int idx, gen_len, gen_type, implied_type, ret = 0; | ||
| 1017 | u32 user_val; | ||
| 1018 | struct perf_event *bp; | ||
| 1019 | struct arch_hw_breakpoint_ctrl ctrl; | ||
| 1020 | struct perf_event_attr attr; | ||
| 1021 | |||
| 1022 | if (num == 0) | ||
| 1023 | goto out; | ||
| 1024 | else if (num < 0) | ||
| 1025 | implied_type = HW_BREAKPOINT_RW; | ||
| 1026 | else | ||
| 1027 | implied_type = HW_BREAKPOINT_X; | ||
| 1028 | |||
| 1029 | idx = ptrace_hbp_num_to_idx(num); | ||
| 1030 | if (idx < 0 || idx >= ARM_MAX_HBP_SLOTS) { | ||
| 1031 | ret = -EINVAL; | ||
| 1032 | goto out; | ||
| 1033 | } | ||
| 1034 | |||
| 1035 | if (get_user(user_val, data)) { | ||
| 1036 | ret = -EFAULT; | ||
| 1037 | goto out; | ||
| 1038 | } | ||
| 1039 | |||
| 1040 | bp = tsk->thread.debug.hbp[idx]; | ||
| 1041 | if (!bp) { | ||
| 1042 | bp = ptrace_hbp_create(tsk, implied_type); | ||
| 1043 | if (IS_ERR(bp)) { | ||
| 1044 | ret = PTR_ERR(bp); | ||
| 1045 | goto out; | ||
| 1046 | } | ||
| 1047 | tsk->thread.debug.hbp[idx] = bp; | ||
| 1048 | } | ||
| 1049 | |||
| 1050 | attr = bp->attr; | ||
| 1051 | |||
| 1052 | if (num & 0x1) { | ||
| 1053 | /* Address */ | ||
| 1054 | attr.bp_addr = user_val; | ||
| 1055 | } else { | ||
| 1056 | /* Control */ | ||
| 1057 | decode_ctrl_reg(user_val, &ctrl); | ||
| 1058 | ret = arch_bp_generic_fields(ctrl, &gen_len, &gen_type); | ||
| 1059 | if (ret) | ||
| 1060 | goto out; | ||
| 1061 | |||
| 1062 | if ((gen_type & implied_type) != gen_type) { | ||
| 1063 | ret = -EINVAL; | ||
| 1064 | goto out; | ||
| 1065 | } | ||
| 1066 | |||
| 1067 | attr.bp_len = gen_len; | ||
| 1068 | attr.bp_type = gen_type; | ||
| 1069 | attr.disabled = !ctrl.enabled; | ||
| 1070 | } | ||
| 1071 | |||
| 1072 | ret = modify_user_hw_breakpoint(bp, &attr); | ||
| 1073 | out: | ||
| 1074 | return ret; | ||
| 1075 | } | ||
| 1076 | #endif | ||
| 1077 | |||
| 850 | long arch_ptrace(struct task_struct *child, long request, long addr, long data) | 1078 | long arch_ptrace(struct task_struct *child, long request, long addr, long data) |
| 851 | { | 1079 | { |
| 852 | int ret; | 1080 | int ret; |
| @@ -916,6 +1144,17 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data) | |||
| 916 | break; | 1144 | break; |
| 917 | #endif | 1145 | #endif |
| 918 | 1146 | ||
| 1147 | #ifdef CONFIG_HAVE_HW_BREAKPOINT | ||
| 1148 | case PTRACE_GETHBPREGS: | ||
| 1149 | ret = ptrace_gethbpregs(child, addr, | ||
| 1150 | (unsigned long __user *)data); | ||
| 1151 | break; | ||
| 1152 | case PTRACE_SETHBPREGS: | ||
| 1153 | ret = ptrace_sethbpregs(child, addr, | ||
| 1154 | (unsigned long __user *)data); | ||
| 1155 | break; | ||
| 1156 | #endif | ||
| 1157 | |||
| 919 | default: | 1158 | default: |
| 920 | ret = ptrace_request(child, request, addr, data); | 1159 | ret = ptrace_request(child, request, addr, data); |
| 921 | break; | 1160 | break; |
