aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/kernel/ftrace.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/kernel/ftrace.c')
-rw-r--r--arch/arm/kernel/ftrace.c100
1 files changed, 28 insertions, 72 deletions
diff --git a/arch/arm/kernel/ftrace.c b/arch/arm/kernel/ftrace.c
index c0062ad1e847..df0bf0c8cb79 100644
--- a/arch/arm/kernel/ftrace.c
+++ b/arch/arm/kernel/ftrace.c
@@ -16,10 +16,13 @@
16#include <linux/uaccess.h> 16#include <linux/uaccess.h>
17 17
18#include <asm/cacheflush.h> 18#include <asm/cacheflush.h>
19#include <asm/opcodes.h>
19#include <asm/ftrace.h> 20#include <asm/ftrace.h>
20 21
22#include "insn.h"
23
21#ifdef CONFIG_THUMB2_KERNEL 24#ifdef CONFIG_THUMB2_KERNEL
22#define NOP 0xeb04f85d /* pop.w {lr} */ 25#define NOP 0xf85deb04 /* pop.w {lr} */
23#else 26#else
24#define NOP 0xe8bd4000 /* pop {lr} */ 27#define NOP 0xe8bd4000 /* pop {lr} */
25#endif 28#endif
@@ -60,76 +63,31 @@ static unsigned long adjust_address(struct dyn_ftrace *rec, unsigned long addr)
60} 63}
61#endif 64#endif
62 65
63#ifdef CONFIG_THUMB2_KERNEL
64static unsigned long ftrace_gen_branch(unsigned long pc, unsigned long addr,
65 bool link)
66{
67 unsigned long s, j1, j2, i1, i2, imm10, imm11;
68 unsigned long first, second;
69 long offset;
70
71 offset = (long)addr - (long)(pc + 4);
72 if (offset < -16777216 || offset > 16777214) {
73 WARN_ON_ONCE(1);
74 return 0;
75 }
76
77 s = (offset >> 24) & 0x1;
78 i1 = (offset >> 23) & 0x1;
79 i2 = (offset >> 22) & 0x1;
80 imm10 = (offset >> 12) & 0x3ff;
81 imm11 = (offset >> 1) & 0x7ff;
82
83 j1 = (!i1) ^ s;
84 j2 = (!i2) ^ s;
85
86 first = 0xf000 | (s << 10) | imm10;
87 second = 0x9000 | (j1 << 13) | (j2 << 11) | imm11;
88 if (link)
89 second |= 1 << 14;
90
91 return (second << 16) | first;
92}
93#else
94static unsigned long ftrace_gen_branch(unsigned long pc, unsigned long addr,
95 bool link)
96{
97 unsigned long opcode = 0xea000000;
98 long offset;
99
100 if (link)
101 opcode |= 1 << 24;
102
103 offset = (long)addr - (long)(pc + 8);
104 if (unlikely(offset < -33554432 || offset > 33554428)) {
105 /* Can't generate branches that far (from ARM ARM). Ftrace
106 * doesn't generate branches outside of kernel text.
107 */
108 WARN_ON_ONCE(1);
109 return 0;
110 }
111
112 offset = (offset >> 2) & 0x00ffffff;
113
114 return opcode | offset;
115}
116#endif
117
118static unsigned long ftrace_call_replace(unsigned long pc, unsigned long addr) 66static unsigned long ftrace_call_replace(unsigned long pc, unsigned long addr)
119{ 67{
120 return ftrace_gen_branch(pc, addr, true); 68 return arm_gen_branch_link(pc, addr);
121} 69}
122 70
123static int ftrace_modify_code(unsigned long pc, unsigned long old, 71static int ftrace_modify_code(unsigned long pc, unsigned long old,
124 unsigned long new) 72 unsigned long new, bool validate)
125{ 73{
126 unsigned long replaced; 74 unsigned long replaced;
127 75
128 if (probe_kernel_read(&replaced, (void *)pc, MCOUNT_INSN_SIZE)) 76 if (IS_ENABLED(CONFIG_THUMB2_KERNEL)) {
129 return -EFAULT; 77 old = __opcode_to_mem_thumb32(old);
78 new = __opcode_to_mem_thumb32(new);
79 } else {
80 old = __opcode_to_mem_arm(old);
81 new = __opcode_to_mem_arm(new);
82 }
130 83
131 if (replaced != old) 84 if (validate) {
132 return -EINVAL; 85 if (probe_kernel_read(&replaced, (void *)pc, MCOUNT_INSN_SIZE))
86 return -EFAULT;
87
88 if (replaced != old)
89 return -EINVAL;
90 }
133 91
134 if (probe_kernel_write((void *)pc, &new, MCOUNT_INSN_SIZE)) 92 if (probe_kernel_write((void *)pc, &new, MCOUNT_INSN_SIZE))
135 return -EPERM; 93 return -EPERM;
@@ -141,23 +99,21 @@ static int ftrace_modify_code(unsigned long pc, unsigned long old,
141 99
142int ftrace_update_ftrace_func(ftrace_func_t func) 100int ftrace_update_ftrace_func(ftrace_func_t func)
143{ 101{
144 unsigned long pc, old; 102 unsigned long pc;
145 unsigned long new; 103 unsigned long new;
146 int ret; 104 int ret;
147 105
148 pc = (unsigned long)&ftrace_call; 106 pc = (unsigned long)&ftrace_call;
149 memcpy(&old, &ftrace_call, MCOUNT_INSN_SIZE);
150 new = ftrace_call_replace(pc, (unsigned long)func); 107 new = ftrace_call_replace(pc, (unsigned long)func);
151 108
152 ret = ftrace_modify_code(pc, old, new); 109 ret = ftrace_modify_code(pc, 0, new, false);
153 110
154#ifdef CONFIG_OLD_MCOUNT 111#ifdef CONFIG_OLD_MCOUNT
155 if (!ret) { 112 if (!ret) {
156 pc = (unsigned long)&ftrace_call_old; 113 pc = (unsigned long)&ftrace_call_old;
157 memcpy(&old, &ftrace_call_old, MCOUNT_INSN_SIZE);
158 new = ftrace_call_replace(pc, (unsigned long)func); 114 new = ftrace_call_replace(pc, (unsigned long)func);
159 115
160 ret = ftrace_modify_code(pc, old, new); 116 ret = ftrace_modify_code(pc, 0, new, false);
161 } 117 }
162#endif 118#endif
163 119
@@ -172,7 +128,7 @@ int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
172 old = ftrace_nop_replace(rec); 128 old = ftrace_nop_replace(rec);
173 new = ftrace_call_replace(ip, adjust_address(rec, addr)); 129 new = ftrace_call_replace(ip, adjust_address(rec, addr));
174 130
175 return ftrace_modify_code(rec->ip, old, new); 131 return ftrace_modify_code(rec->ip, old, new, true);
176} 132}
177 133
178int ftrace_make_nop(struct module *mod, 134int ftrace_make_nop(struct module *mod,
@@ -185,7 +141,7 @@ int ftrace_make_nop(struct module *mod,
185 141
186 old = ftrace_call_replace(ip, adjust_address(rec, addr)); 142 old = ftrace_call_replace(ip, adjust_address(rec, addr));
187 new = ftrace_nop_replace(rec); 143 new = ftrace_nop_replace(rec);
188 ret = ftrace_modify_code(ip, old, new); 144 ret = ftrace_modify_code(ip, old, new, true);
189 145
190#ifdef CONFIG_OLD_MCOUNT 146#ifdef CONFIG_OLD_MCOUNT
191 if (ret == -EINVAL && addr == MCOUNT_ADDR) { 147 if (ret == -EINVAL && addr == MCOUNT_ADDR) {
@@ -193,7 +149,7 @@ int ftrace_make_nop(struct module *mod,
193 149
194 old = ftrace_call_replace(ip, adjust_address(rec, addr)); 150 old = ftrace_call_replace(ip, adjust_address(rec, addr));
195 new = ftrace_nop_replace(rec); 151 new = ftrace_nop_replace(rec);
196 ret = ftrace_modify_code(ip, old, new); 152 ret = ftrace_modify_code(ip, old, new, true);
197 } 153 }
198#endif 154#endif
199 155
@@ -249,12 +205,12 @@ static int __ftrace_modify_caller(unsigned long *callsite,
249{ 205{
250 unsigned long caller_fn = (unsigned long) func; 206 unsigned long caller_fn = (unsigned long) func;
251 unsigned long pc = (unsigned long) callsite; 207 unsigned long pc = (unsigned long) callsite;
252 unsigned long branch = ftrace_gen_branch(pc, caller_fn, false); 208 unsigned long branch = arm_gen_branch(pc, caller_fn);
253 unsigned long nop = 0xe1a00000; /* mov r0, r0 */ 209 unsigned long nop = 0xe1a00000; /* mov r0, r0 */
254 unsigned long old = enable ? nop : branch; 210 unsigned long old = enable ? nop : branch;
255 unsigned long new = enable ? branch : nop; 211 unsigned long new = enable ? branch : nop;
256 212
257 return ftrace_modify_code(pc, old, new); 213 return ftrace_modify_code(pc, old, new, true);
258} 214}
259 215
260static int ftrace_modify_graph_caller(bool enable) 216static int ftrace_modify_graph_caller(bool enable)