diff options
Diffstat (limited to 'arch/arm/kernel/ptrace.c')
| -rw-r--r-- | arch/arm/kernel/ptrace.c | 239 |
1 files changed, 239 insertions, 0 deletions
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; |
