xref: /linux/arch/arm/kernel/ftrace.c (revision 092e0e7e520a1fca03e13c9f2d157432a8657ff2)
1 /*
2  * Dynamic function tracing support.
3  *
4  * Copyright (C) 2008 Abhishek Sagar <sagar.abhishek@gmail.com>
5  * Copyright (C) 2010 Rabin Vincent <rabin@rab.in>
6  *
7  * For licencing details, see COPYING.
8  *
9  * Defines low-level handling of mcount calls when the kernel
10  * is compiled with the -pg flag. When using dynamic ftrace, the
11  * mcount call-sites get patched with NOP till they are enabled.
12  * All code mutation routines here are called under stop_machine().
13  */
14 
15 #include <linux/ftrace.h>
16 #include <linux/uaccess.h>
17 
18 #include <asm/cacheflush.h>
19 #include <asm/ftrace.h>
20 
21 #ifdef CONFIG_THUMB2_KERNEL
22 #define	NOP		0xeb04f85d	/* pop.w {lr} */
23 #else
24 #define	NOP		0xe8bd4000	/* pop {lr} */
25 #endif
26 
27 #ifdef CONFIG_OLD_MCOUNT
28 #define OLD_MCOUNT_ADDR	((unsigned long) mcount)
29 #define OLD_FTRACE_ADDR ((unsigned long) ftrace_caller_old)
30 
31 #define	OLD_NOP		0xe1a00000	/* mov r0, r0 */
32 
33 static unsigned long ftrace_nop_replace(struct dyn_ftrace *rec)
34 {
35 	return rec->arch.old_mcount ? OLD_NOP : NOP;
36 }
37 
38 static unsigned long adjust_address(struct dyn_ftrace *rec, unsigned long addr)
39 {
40 	if (!rec->arch.old_mcount)
41 		return addr;
42 
43 	if (addr == MCOUNT_ADDR)
44 		addr = OLD_MCOUNT_ADDR;
45 	else if (addr == FTRACE_ADDR)
46 		addr = OLD_FTRACE_ADDR;
47 
48 	return addr;
49 }
50 #else
51 static unsigned long ftrace_nop_replace(struct dyn_ftrace *rec)
52 {
53 	return NOP;
54 }
55 
56 static unsigned long adjust_address(struct dyn_ftrace *rec, unsigned long addr)
57 {
58 	return addr;
59 }
60 #endif
61 
62 /* construct a branch (BL) instruction to addr */
63 #ifdef CONFIG_THUMB2_KERNEL
64 static unsigned long ftrace_call_replace(unsigned long pc, unsigned long addr)
65 {
66 	unsigned long s, j1, j2, i1, i2, imm10, imm11;
67 	unsigned long first, second;
68 	long offset;
69 
70 	offset = (long)addr - (long)(pc + 4);
71 	if (offset < -16777216 || offset > 16777214) {
72 		WARN_ON_ONCE(1);
73 		return 0;
74 	}
75 
76 	s	= (offset >> 24) & 0x1;
77 	i1	= (offset >> 23) & 0x1;
78 	i2	= (offset >> 22) & 0x1;
79 	imm10	= (offset >> 12) & 0x3ff;
80 	imm11	= (offset >>  1) & 0x7ff;
81 
82 	j1 = (!i1) ^ s;
83 	j2 = (!i2) ^ s;
84 
85 	first = 0xf000 | (s << 10) | imm10;
86 	second = 0xd000 | (j1 << 13) | (j2 << 11) | imm11;
87 
88 	return (second << 16) | first;
89 }
90 #else
91 static unsigned long ftrace_call_replace(unsigned long pc, unsigned long addr)
92 {
93 	long offset;
94 
95 	offset = (long)addr - (long)(pc + 8);
96 	if (unlikely(offset < -33554432 || offset > 33554428)) {
97 		/* Can't generate branches that far (from ARM ARM). Ftrace
98 		 * doesn't generate branches outside of kernel text.
99 		 */
100 		WARN_ON_ONCE(1);
101 		return 0;
102 	}
103 
104 	offset = (offset >> 2) & 0x00ffffff;
105 
106 	return 0xeb000000 | offset;
107 }
108 #endif
109 
110 static int ftrace_modify_code(unsigned long pc, unsigned long old,
111 			      unsigned long new)
112 {
113 	unsigned long replaced;
114 
115 	if (probe_kernel_read(&replaced, (void *)pc, MCOUNT_INSN_SIZE))
116 		return -EFAULT;
117 
118 	if (replaced != old)
119 		return -EINVAL;
120 
121 	if (probe_kernel_write((void *)pc, &new, MCOUNT_INSN_SIZE))
122 		return -EPERM;
123 
124 	flush_icache_range(pc, pc + MCOUNT_INSN_SIZE);
125 
126 	return 0;
127 }
128 
129 int ftrace_update_ftrace_func(ftrace_func_t func)
130 {
131 	unsigned long pc, old;
132 	unsigned long new;
133 	int ret;
134 
135 	pc = (unsigned long)&ftrace_call;
136 	memcpy(&old, &ftrace_call, MCOUNT_INSN_SIZE);
137 	new = ftrace_call_replace(pc, (unsigned long)func);
138 
139 	ret = ftrace_modify_code(pc, old, new);
140 
141 #ifdef CONFIG_OLD_MCOUNT
142 	if (!ret) {
143 		pc = (unsigned long)&ftrace_call_old;
144 		memcpy(&old, &ftrace_call_old, MCOUNT_INSN_SIZE);
145 		new = ftrace_call_replace(pc, (unsigned long)func);
146 
147 		ret = ftrace_modify_code(pc, old, new);
148 	}
149 #endif
150 
151 	return ret;
152 }
153 
154 int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
155 {
156 	unsigned long new, old;
157 	unsigned long ip = rec->ip;
158 
159 	old = ftrace_nop_replace(rec);
160 	new = ftrace_call_replace(ip, adjust_address(rec, addr));
161 
162 	return ftrace_modify_code(rec->ip, old, new);
163 }
164 
165 int ftrace_make_nop(struct module *mod,
166 		    struct dyn_ftrace *rec, unsigned long addr)
167 {
168 	unsigned long ip = rec->ip;
169 	unsigned long old;
170 	unsigned long new;
171 	int ret;
172 
173 	old = ftrace_call_replace(ip, adjust_address(rec, addr));
174 	new = ftrace_nop_replace(rec);
175 	ret = ftrace_modify_code(ip, old, new);
176 
177 #ifdef CONFIG_OLD_MCOUNT
178 	if (ret == -EINVAL && addr == MCOUNT_ADDR) {
179 		rec->arch.old_mcount = true;
180 
181 		old = ftrace_call_replace(ip, adjust_address(rec, addr));
182 		new = ftrace_nop_replace(rec);
183 		ret = ftrace_modify_code(ip, old, new);
184 	}
185 #endif
186 
187 	return ret;
188 }
189 
190 int __init ftrace_dyn_arch_init(void *data)
191 {
192 	*(unsigned long *)data = 0;
193 
194 	return 0;
195 }
196