aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSandeepa Prabhu <sandeepa.s.prabhu@gmail.com>2016-07-08 12:35:51 -0400
committerCatalin Marinas <catalin.marinas@arm.com>2016-07-19 10:03:21 -0400
commit39a67d49ba353630d144a8eb775500c041c89e7a (patch)
tree8a15a87c630a2710e2acdbcd0a0fc627999276f8
parent888b3c8720e0a4033db09ba2364afde6a4763638 (diff)
arm64: kprobes instruction simulation support
Kprobes needs simulation of instructions that cannot be stepped from a different memory location, e.g.: those instructions that uses PC-relative addressing. In simulation, the behaviour of the instruction is implemented using a copy of pt_regs. The following instruction categories are simulated: - All branching instructions(conditional, register, and immediate) - Literal access instructions(load-literal, adr/adrp) Conditional execution is limited to branching instructions in ARM v8. If conditions at PSTATE do not match the condition fields of opcode, the instruction is effectively NOP. Thanks to Will Cohen for assorted suggested changes. Signed-off-by: Sandeepa Prabhu <sandeepa.s.prabhu@gmail.com> Signed-off-by: William Cohen <wcohen@redhat.com> Signed-off-by: David A. Long <dave.long@linaro.org> Acked-by: Masami Hiramatsu <mhiramat@kernel.org> [catalin.marinas@arm.com: removed linux/module.h include] Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
-rw-r--r--arch/arm64/include/asm/probes.h5
-rw-r--r--arch/arm64/kernel/insn.c1
-rw-r--r--arch/arm64/kernel/probes/Makefile3
-rw-r--r--arch/arm64/kernel/probes/decode-insn.c33
-rw-r--r--arch/arm64/kernel/probes/decode-insn.h1
-rw-r--r--arch/arm64/kernel/probes/kprobes.c53
-rw-r--r--arch/arm64/kernel/probes/simulate-insn.c217
-rw-r--r--arch/arm64/kernel/probes/simulate-insn.h28
8 files changed, 326 insertions, 15 deletions
diff --git a/arch/arm64/include/asm/probes.h b/arch/arm64/include/asm/probes.h
index 1e8a21a96002..5af574d632fa 100644
--- a/arch/arm64/include/asm/probes.h
+++ b/arch/arm64/include/asm/probes.h
@@ -15,17 +15,18 @@
15#ifndef _ARM_PROBES_H 15#ifndef _ARM_PROBES_H
16#define _ARM_PROBES_H 16#define _ARM_PROBES_H
17 17
18#include <asm/opcodes.h>
19
18struct kprobe; 20struct kprobe;
19struct arch_specific_insn; 21struct arch_specific_insn;
20 22
21typedef u32 kprobe_opcode_t; 23typedef u32 kprobe_opcode_t;
22typedef unsigned long (kprobes_pstate_check_t)(unsigned long);
23typedef void (kprobes_handler_t) (u32 opcode, long addr, struct pt_regs *); 24typedef void (kprobes_handler_t) (u32 opcode, long addr, struct pt_regs *);
24 25
25/* architecture specific copy of original instruction */ 26/* architecture specific copy of original instruction */
26struct arch_specific_insn { 27struct arch_specific_insn {
27 kprobe_opcode_t *insn; 28 kprobe_opcode_t *insn;
28 kprobes_pstate_check_t *pstate_cc; 29 pstate_check_t *pstate_cc;
29 kprobes_handler_t *handler; 30 kprobes_handler_t *handler;
30 /* restore address after step xol */ 31 /* restore address after step xol */
31 unsigned long restore; 32 unsigned long restore;
diff --git a/arch/arm64/kernel/insn.c b/arch/arm64/kernel/insn.c
index 5cb2f3db3da5..63f9432d05e8 100644
--- a/arch/arm64/kernel/insn.c
+++ b/arch/arm64/kernel/insn.c
@@ -30,6 +30,7 @@
30#include <asm/cacheflush.h> 30#include <asm/cacheflush.h>
31#include <asm/debug-monitors.h> 31#include <asm/debug-monitors.h>
32#include <asm/fixmap.h> 32#include <asm/fixmap.h>
33#include <asm/opcodes.h>
33#include <asm/insn.h> 34#include <asm/insn.h>
34 35
35#define AARCH64_INSN_SF_BIT BIT(31) 36#define AARCH64_INSN_SF_BIT BIT(31)
diff --git a/arch/arm64/kernel/probes/Makefile b/arch/arm64/kernel/probes/Makefile
index bc159bf56992..e184d00ebf01 100644
--- a/arch/arm64/kernel/probes/Makefile
+++ b/arch/arm64/kernel/probes/Makefile
@@ -1 +1,2 @@
1obj-$(CONFIG_KPROBES) += kprobes.o decode-insn.o 1obj-$(CONFIG_KPROBES) += kprobes.o decode-insn.o \
2 simulate-insn.o
diff --git a/arch/arm64/kernel/probes/decode-insn.c b/arch/arm64/kernel/probes/decode-insn.c
index 95c0c5281e7b..37e47a9d617e 100644
--- a/arch/arm64/kernel/probes/decode-insn.c
+++ b/arch/arm64/kernel/probes/decode-insn.c
@@ -21,6 +21,7 @@
21#include <asm/sections.h> 21#include <asm/sections.h>
22 22
23#include "decode-insn.h" 23#include "decode-insn.h"
24#include "simulate-insn.h"
24 25
25static bool __kprobes aarch64_insn_is_steppable(u32 insn) 26static bool __kprobes aarch64_insn_is_steppable(u32 insn)
26{ 27{
@@ -74,6 +75,7 @@ static bool __kprobes aarch64_insn_is_steppable(u32 insn)
74/* Return: 75/* Return:
75 * INSN_REJECTED If instruction is one not allowed to kprobe, 76 * INSN_REJECTED If instruction is one not allowed to kprobe,
76 * INSN_GOOD If instruction is supported and uses instruction slot, 77 * INSN_GOOD If instruction is supported and uses instruction slot,
78 * INSN_GOOD_NO_SLOT If instruction is supported but doesn't use its slot.
77 */ 79 */
78static enum kprobe_insn __kprobes 80static enum kprobe_insn __kprobes
79arm_probe_decode_insn(kprobe_opcode_t insn, struct arch_specific_insn *asi) 81arm_probe_decode_insn(kprobe_opcode_t insn, struct arch_specific_insn *asi)
@@ -84,8 +86,37 @@ arm_probe_decode_insn(kprobe_opcode_t insn, struct arch_specific_insn *asi)
84 */ 86 */
85 if (aarch64_insn_is_steppable(insn)) 87 if (aarch64_insn_is_steppable(insn))
86 return INSN_GOOD; 88 return INSN_GOOD;
87 else 89
90 if (aarch64_insn_is_bcond(insn)) {
91 asi->handler = simulate_b_cond;
92 } else if (aarch64_insn_is_cbz(insn) ||
93 aarch64_insn_is_cbnz(insn)) {
94 asi->handler = simulate_cbz_cbnz;
95 } else if (aarch64_insn_is_tbz(insn) ||
96 aarch64_insn_is_tbnz(insn)) {
97 asi->handler = simulate_tbz_tbnz;
98 } else if (aarch64_insn_is_adr_adrp(insn)) {
99 asi->handler = simulate_adr_adrp;
100 } else if (aarch64_insn_is_b(insn) ||
101 aarch64_insn_is_bl(insn)) {
102 asi->handler = simulate_b_bl;
103 } else if (aarch64_insn_is_br(insn) ||
104 aarch64_insn_is_blr(insn) ||
105 aarch64_insn_is_ret(insn)) {
106 asi->handler = simulate_br_blr_ret;
107 } else if (aarch64_insn_is_ldr_lit(insn)) {
108 asi->handler = simulate_ldr_literal;
109 } else if (aarch64_insn_is_ldrsw_lit(insn)) {
110 asi->handler = simulate_ldrsw_literal;
111 } else {
112 /*
113 * Instruction cannot be stepped out-of-line and we don't
114 * (yet) simulate it.
115 */
88 return INSN_REJECTED; 116 return INSN_REJECTED;
117 }
118
119 return INSN_GOOD_NO_SLOT;
89} 120}
90 121
91static bool __kprobes 122static bool __kprobes
diff --git a/arch/arm64/kernel/probes/decode-insn.h b/arch/arm64/kernel/probes/decode-insn.h
index ad5ba9cc3d45..d438289646a6 100644
--- a/arch/arm64/kernel/probes/decode-insn.h
+++ b/arch/arm64/kernel/probes/decode-insn.h
@@ -25,6 +25,7 @@
25 25
26enum kprobe_insn { 26enum kprobe_insn {
27 INSN_REJECTED, 27 INSN_REJECTED,
28 INSN_GOOD_NO_SLOT,
28 INSN_GOOD, 29 INSN_GOOD,
29}; 30};
30 31
diff --git a/arch/arm64/kernel/probes/kprobes.c b/arch/arm64/kernel/probes/kprobes.c
index 0fe2b6578163..63eb0a14d8e9 100644
--- a/arch/arm64/kernel/probes/kprobes.c
+++ b/arch/arm64/kernel/probes/kprobes.c
@@ -45,6 +45,9 @@ void jprobe_return_break(void);
45DEFINE_PER_CPU(struct kprobe *, current_kprobe) = NULL; 45DEFINE_PER_CPU(struct kprobe *, current_kprobe) = NULL;
46DEFINE_PER_CPU(struct kprobe_ctlblk, kprobe_ctlblk); 46DEFINE_PER_CPU(struct kprobe_ctlblk, kprobe_ctlblk);
47 47
48static void __kprobes
49post_kprobe_handler(struct kprobe_ctlblk *, struct pt_regs *);
50
48static void __kprobes arch_prepare_ss_slot(struct kprobe *p) 51static void __kprobes arch_prepare_ss_slot(struct kprobe *p)
49{ 52{
50 /* prepare insn slot */ 53 /* prepare insn slot */
@@ -61,6 +64,23 @@ static void __kprobes arch_prepare_ss_slot(struct kprobe *p)
61 sizeof(kprobe_opcode_t); 64 sizeof(kprobe_opcode_t);
62} 65}
63 66
67static void __kprobes arch_prepare_simulate(struct kprobe *p)
68{
69 /* This instructions is not executed xol. No need to adjust the PC */
70 p->ainsn.restore = 0;
71}
72
73static void __kprobes arch_simulate_insn(struct kprobe *p, struct pt_regs *regs)
74{
75 struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
76
77 if (p->ainsn.handler)
78 p->ainsn.handler((u32)p->opcode, (long)p->addr, regs);
79
80 /* single step simulated, now go for post processing */
81 post_kprobe_handler(kcb, regs);
82}
83
64int __kprobes arch_prepare_kprobe(struct kprobe *p) 84int __kprobes arch_prepare_kprobe(struct kprobe *p)
65{ 85{
66 unsigned long probe_addr = (unsigned long)p->addr; 86 unsigned long probe_addr = (unsigned long)p->addr;
@@ -84,6 +104,10 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p)
84 case INSN_REJECTED: /* insn not supported */ 104 case INSN_REJECTED: /* insn not supported */
85 return -EINVAL; 105 return -EINVAL;
86 106
107 case INSN_GOOD_NO_SLOT: /* insn need simulation */
108 p->ainsn.insn = NULL;
109 break;
110
87 case INSN_GOOD: /* instruction uses slot */ 111 case INSN_GOOD: /* instruction uses slot */
88 p->ainsn.insn = get_insn_slot(); 112 p->ainsn.insn = get_insn_slot();
89 if (!p->ainsn.insn) 113 if (!p->ainsn.insn)
@@ -92,7 +116,10 @@ int __kprobes arch_prepare_kprobe(struct kprobe *p)
92 }; 116 };
93 117
94 /* prepare the instruction */ 118 /* prepare the instruction */
95 arch_prepare_ss_slot(p); 119 if (p->ainsn.insn)
120 arch_prepare_ss_slot(p);
121 else
122 arch_prepare_simulate(p);
96 123
97 return 0; 124 return 0;
98} 125}
@@ -218,20 +245,24 @@ static void __kprobes setup_singlestep(struct kprobe *p,
218 kcb->kprobe_status = KPROBE_HIT_SS; 245 kcb->kprobe_status = KPROBE_HIT_SS;
219 } 246 }
220 247
221 BUG_ON(!p->ainsn.insn);
222 248
223 /* prepare for single stepping */ 249 if (p->ainsn.insn) {
224 slot = (unsigned long)p->ainsn.insn; 250 /* prepare for single stepping */
251 slot = (unsigned long)p->ainsn.insn;
225 252
226 set_ss_context(kcb, slot); /* mark pending ss */ 253 set_ss_context(kcb, slot); /* mark pending ss */
227 254
228 if (kcb->kprobe_status == KPROBE_REENTER) 255 if (kcb->kprobe_status == KPROBE_REENTER)
229 spsr_set_debug_flag(regs, 0); 256 spsr_set_debug_flag(regs, 0);
230 257
231 /* IRQs and single stepping do not mix well. */ 258 /* IRQs and single stepping do not mix well. */
232 kprobes_save_local_irqflag(kcb, regs); 259 kprobes_save_local_irqflag(kcb, regs);
233 kernel_enable_single_step(regs); 260 kernel_enable_single_step(regs);
234 instruction_pointer_set(regs, slot); 261 instruction_pointer_set(regs, slot);
262 } else {
263 /* insn simulation */
264 arch_simulate_insn(p, regs);
265 }
235} 266}
236 267
237static int __kprobes reenter_kprobe(struct kprobe *p, 268static int __kprobes reenter_kprobe(struct kprobe *p,
diff --git a/arch/arm64/kernel/probes/simulate-insn.c b/arch/arm64/kernel/probes/simulate-insn.c
new file mode 100644
index 000000000000..8977ce9d009d
--- /dev/null
+++ b/arch/arm64/kernel/probes/simulate-insn.c
@@ -0,0 +1,217 @@
1/*
2 * arch/arm64/kernel/probes/simulate-insn.c
3 *
4 * Copyright (C) 2013 Linaro Limited.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 */
15
16#include <linux/kernel.h>
17#include <linux/kprobes.h>
18
19#include "simulate-insn.h"
20
21#define sign_extend(x, signbit) \
22 ((x) | (0 - ((x) & (1 << (signbit)))))
23
24#define bbl_displacement(insn) \
25 sign_extend(((insn) & 0x3ffffff) << 2, 27)
26
27#define bcond_displacement(insn) \
28 sign_extend(((insn >> 5) & 0x7ffff) << 2, 20)
29
30#define cbz_displacement(insn) \
31 sign_extend(((insn >> 5) & 0x7ffff) << 2, 20)
32
33#define tbz_displacement(insn) \
34 sign_extend(((insn >> 5) & 0x3fff) << 2, 15)
35
36#define ldr_displacement(insn) \
37 sign_extend(((insn >> 5) & 0x7ffff) << 2, 20)
38
39static inline void set_x_reg(struct pt_regs *regs, int reg, u64 val)
40{
41 if (reg < 31)
42 regs->regs[reg] = val;
43}
44
45static inline void set_w_reg(struct pt_regs *regs, int reg, u64 val)
46{
47 if (reg < 31)
48 regs->regs[reg] = lower_32_bits(val);
49}
50
51static inline u64 get_x_reg(struct pt_regs *regs, int reg)
52{
53 if (reg < 31)
54 return regs->regs[reg];
55 else
56 return 0;
57}
58
59static inline u32 get_w_reg(struct pt_regs *regs, int reg)
60{
61 if (reg < 31)
62 return lower_32_bits(regs->regs[reg]);
63 else
64 return 0;
65}
66
67static bool __kprobes check_cbz(u32 opcode, struct pt_regs *regs)
68{
69 int xn = opcode & 0x1f;
70
71 return (opcode & (1 << 31)) ?
72 (get_x_reg(regs, xn) == 0) : (get_w_reg(regs, xn) == 0);
73}
74
75static bool __kprobes check_cbnz(u32 opcode, struct pt_regs *regs)
76{
77 int xn = opcode & 0x1f;
78
79 return (opcode & (1 << 31)) ?
80 (get_x_reg(regs, xn) != 0) : (get_w_reg(regs, xn) != 0);
81}
82
83static bool __kprobes check_tbz(u32 opcode, struct pt_regs *regs)
84{
85 int xn = opcode & 0x1f;
86 int bit_pos = ((opcode & (1 << 31)) >> 26) | ((opcode >> 19) & 0x1f);
87
88 return ((get_x_reg(regs, xn) >> bit_pos) & 0x1) == 0;
89}
90
91static bool __kprobes check_tbnz(u32 opcode, struct pt_regs *regs)
92{
93 int xn = opcode & 0x1f;
94 int bit_pos = ((opcode & (1 << 31)) >> 26) | ((opcode >> 19) & 0x1f);
95
96 return ((get_x_reg(regs, xn) >> bit_pos) & 0x1) != 0;
97}
98
99/*
100 * instruction simulation functions
101 */
102void __kprobes
103simulate_adr_adrp(u32 opcode, long addr, struct pt_regs *regs)
104{
105 long imm, xn, val;
106
107 xn = opcode & 0x1f;
108 imm = ((opcode >> 3) & 0x1ffffc) | ((opcode >> 29) & 0x3);
109 imm = sign_extend(imm, 20);
110 if (opcode & 0x80000000)
111 val = (imm<<12) + (addr & 0xfffffffffffff000);
112 else
113 val = imm + addr;
114
115 set_x_reg(regs, xn, val);
116
117 instruction_pointer_set(regs, instruction_pointer(regs) + 4);
118}
119
120void __kprobes
121simulate_b_bl(u32 opcode, long addr, struct pt_regs *regs)
122{
123 int disp = bbl_displacement(opcode);
124
125 /* Link register is x30 */
126 if (opcode & (1 << 31))
127 set_x_reg(regs, 30, addr + 4);
128
129 instruction_pointer_set(regs, addr + disp);
130}
131
132void __kprobes
133simulate_b_cond(u32 opcode, long addr, struct pt_regs *regs)
134{
135 int disp = 4;
136
137 if (aarch32_opcode_cond_checks[opcode & 0xf](regs->pstate & 0xffffffff))
138 disp = bcond_displacement(opcode);
139
140 instruction_pointer_set(regs, addr + disp);
141}
142
143void __kprobes
144simulate_br_blr_ret(u32 opcode, long addr, struct pt_regs *regs)
145{
146 int xn = (opcode >> 5) & 0x1f;
147
148 /* update pc first in case we're doing a "blr lr" */
149 instruction_pointer_set(regs, get_x_reg(regs, xn));
150
151 /* Link register is x30 */
152 if (((opcode >> 21) & 0x3) == 1)
153 set_x_reg(regs, 30, addr + 4);
154}
155
156void __kprobes
157simulate_cbz_cbnz(u32 opcode, long addr, struct pt_regs *regs)
158{
159 int disp = 4;
160
161 if (opcode & (1 << 24)) {
162 if (check_cbnz(opcode, regs))
163 disp = cbz_displacement(opcode);
164 } else {
165 if (check_cbz(opcode, regs))
166 disp = cbz_displacement(opcode);
167 }
168 instruction_pointer_set(regs, addr + disp);
169}
170
171void __kprobes
172simulate_tbz_tbnz(u32 opcode, long addr, struct pt_regs *regs)
173{
174 int disp = 4;
175
176 if (opcode & (1 << 24)) {
177 if (check_tbnz(opcode, regs))
178 disp = tbz_displacement(opcode);
179 } else {
180 if (check_tbz(opcode, regs))
181 disp = tbz_displacement(opcode);
182 }
183 instruction_pointer_set(regs, addr + disp);
184}
185
186void __kprobes
187simulate_ldr_literal(u32 opcode, long addr, struct pt_regs *regs)
188{
189 u64 *load_addr;
190 int xn = opcode & 0x1f;
191 int disp;
192
193 disp = ldr_displacement(opcode);
194 load_addr = (u64 *) (addr + disp);
195
196 if (opcode & (1 << 30)) /* x0-x30 */
197 set_x_reg(regs, xn, *load_addr);
198 else /* w0-w30 */
199 set_w_reg(regs, xn, *load_addr);
200
201 instruction_pointer_set(regs, instruction_pointer(regs) + 4);
202}
203
204void __kprobes
205simulate_ldrsw_literal(u32 opcode, long addr, struct pt_regs *regs)
206{
207 s32 *load_addr;
208 int xn = opcode & 0x1f;
209 int disp;
210
211 disp = ldr_displacement(opcode);
212 load_addr = (s32 *) (addr + disp);
213
214 set_x_reg(regs, xn, *load_addr);
215
216 instruction_pointer_set(regs, instruction_pointer(regs) + 4);
217}
diff --git a/arch/arm64/kernel/probes/simulate-insn.h b/arch/arm64/kernel/probes/simulate-insn.h
new file mode 100644
index 000000000000..050bde683c2d
--- /dev/null
+++ b/arch/arm64/kernel/probes/simulate-insn.h
@@ -0,0 +1,28 @@
1/*
2 * arch/arm64/kernel/probes/simulate-insn.h
3 *
4 * Copyright (C) 2013 Linaro Limited
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 */
15
16#ifndef _ARM_KERNEL_KPROBES_SIMULATE_INSN_H
17#define _ARM_KERNEL_KPROBES_SIMULATE_INSN_H
18
19void simulate_adr_adrp(u32 opcode, long addr, struct pt_regs *regs);
20void simulate_b_bl(u32 opcode, long addr, struct pt_regs *regs);
21void simulate_b_cond(u32 opcode, long addr, struct pt_regs *regs);
22void simulate_br_blr_ret(u32 opcode, long addr, struct pt_regs *regs);
23void simulate_cbz_cbnz(u32 opcode, long addr, struct pt_regs *regs);
24void simulate_tbz_tbnz(u32 opcode, long addr, struct pt_regs *regs);
25void simulate_ldr_literal(u32 opcode, long addr, struct pt_regs *regs);
26void simulate_ldrsw_literal(u32 opcode, long addr, struct pt_regs *regs);
27
28#endif /* _ARM_KERNEL_KPROBES_SIMULATE_INSN_H */