diff options
author | Will Deacon <will.deacon@arm.com> | 2010-09-03 05:42:55 -0400 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2010-09-08 05:05:00 -0400 |
commit | 864232fa1a2f8dfe003438ef0851a56722740f3e (patch) | |
tree | 0f85f2b1b2f030e7c51f0c69b112dea746b3a861 /arch/arm | |
parent | f81ef4a920c8e1af75adf9f15042c2daa49d3cb3 (diff) |
ARM: 6357/1: hw-breakpoint: add new ptrace requests for hw-breakpoint interaction
For debuggers to take advantage of the hw-breakpoint framework in the kernel,
it is necessary to expose the API calls via a ptrace interface.
This patch exposes the hardware breakpoints framework as a collection of
virtual registers, accesible using PTRACE_SETHBPREGS and PTRACE_GETHBPREGS
requests. The breakpoints are stored in the debug_info struct of the running
thread.
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: S. Karthikeyan <informkarthik@gmail.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'arch/arm')
-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; |