diff options
author | Frederic Weisbecker <fweisbec@gmail.com> | 2008-11-11 01:03:45 -0500 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2008-11-11 04:29:11 -0500 |
commit | caf4b323b02a16c92fba449952ac6515ddc76d7a (patch) | |
tree | 6fc234d00faca192248344996a168afb253e5f40 /arch | |
parent | d844222a54c33a960755b44b934cd1b01b05dceb (diff) |
tracing, x86: add low level support for ftrace return tracing
Impact: add infrastructure for function-return tracing
Add low level support for ftrace return tracing.
This plug-in stores return addresses on the thread_info structure of
the current task.
The index of the current return address is initialized when the task
is the first one (init) and when a process forks (the child). It is
not needed when a task does a sys_execve because after this syscall,
it still needs to return on the kernel functions it called.
Note that the code of return_to_handler has been suggested by Steven
Rostedt as almost all of the ideas of improvements in this V3.
For purpose of security, arch/x86/kernel/process_32.c is not traced
because __switch_to() changes the current task during its execution.
That could cause inconsistency in the stored return address of this
function even if I didn't have any crash after testing with tracing on
this function enabled.
Signed-off-by: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Steven Rostedt <rostedt@goodmis.org>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Diffstat (limited to 'arch')
-rw-r--r-- | arch/x86/Kconfig | 1 | ||||
-rw-r--r-- | arch/x86/include/asm/ftrace.h | 26 | ||||
-rw-r--r-- | arch/x86/include/asm/thread_info.h | 24 | ||||
-rw-r--r-- | arch/x86/kernel/Makefile | 6 | ||||
-rw-r--r-- | arch/x86/kernel/entry_32.S | 33 | ||||
-rw-r--r-- | arch/x86/kernel/ftrace.c | 181 |
6 files changed, 264 insertions, 7 deletions
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 27b8a3a39911..ca91e50bdb10 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig | |||
@@ -11,6 +11,7 @@ config 64BIT | |||
11 | 11 | ||
12 | config X86_32 | 12 | config X86_32 |
13 | def_bool !64BIT | 13 | def_bool !64BIT |
14 | select HAVE_FUNCTION_RET_TRACER | ||
14 | 15 | ||
15 | config X86_64 | 16 | config X86_64 |
16 | def_bool 64BIT | 17 | def_bool 64BIT |
diff --git a/arch/x86/include/asm/ftrace.h b/arch/x86/include/asm/ftrace.h index f8173ed1c970..9b6a1fa19e70 100644 --- a/arch/x86/include/asm/ftrace.h +++ b/arch/x86/include/asm/ftrace.h | |||
@@ -20,4 +20,30 @@ static inline unsigned long ftrace_call_adjust(unsigned long addr) | |||
20 | #endif /* __ASSEMBLY__ */ | 20 | #endif /* __ASSEMBLY__ */ |
21 | #endif /* CONFIG_FUNCTION_TRACER */ | 21 | #endif /* CONFIG_FUNCTION_TRACER */ |
22 | 22 | ||
23 | #ifdef CONFIG_FUNCTION_RET_TRACER | ||
24 | #define FTRACE_RET_STACK_SIZE 20 | ||
25 | |||
26 | #ifndef __ASSEMBLY__ | ||
27 | |||
28 | /* | ||
29 | * Stack of return addresses for functions | ||
30 | * of a thread. | ||
31 | * Used in struct thread_info | ||
32 | */ | ||
33 | struct ftrace_ret_stack { | ||
34 | unsigned long ret; | ||
35 | unsigned long func; | ||
36 | unsigned long long calltime; | ||
37 | }; | ||
38 | |||
39 | /* | ||
40 | * Primary handler of a function return. | ||
41 | * It relays on ftrace_return_to_handler. | ||
42 | * Defined in entry32.S | ||
43 | */ | ||
44 | extern void return_to_handler(void); | ||
45 | |||
46 | #endif /* __ASSEMBLY__ */ | ||
47 | #endif /* CONFIG_FUNCTION_RET_TRACER */ | ||
48 | |||
23 | #endif /* _ASM_X86_FTRACE_H */ | 49 | #endif /* _ASM_X86_FTRACE_H */ |
diff --git a/arch/x86/include/asm/thread_info.h b/arch/x86/include/asm/thread_info.h index e44d379faad2..a71158369fd4 100644 --- a/arch/x86/include/asm/thread_info.h +++ b/arch/x86/include/asm/thread_info.h | |||
@@ -20,6 +20,7 @@ | |||
20 | struct task_struct; | 20 | struct task_struct; |
21 | struct exec_domain; | 21 | struct exec_domain; |
22 | #include <asm/processor.h> | 22 | #include <asm/processor.h> |
23 | #include <asm/ftrace.h> | ||
23 | 24 | ||
24 | struct thread_info { | 25 | struct thread_info { |
25 | struct task_struct *task; /* main task structure */ | 26 | struct task_struct *task; /* main task structure */ |
@@ -38,8 +39,30 @@ struct thread_info { | |||
38 | */ | 39 | */ |
39 | __u8 supervisor_stack[0]; | 40 | __u8 supervisor_stack[0]; |
40 | #endif | 41 | #endif |
42 | |||
43 | #ifdef CONFIG_FUNCTION_RET_TRACER | ||
44 | /* Index of current stored adress in ret_stack */ | ||
45 | int curr_ret_stack; | ||
46 | /* Stack of return addresses for return function tracing */ | ||
47 | struct ftrace_ret_stack ret_stack[FTRACE_RET_STACK_SIZE]; | ||
48 | #endif | ||
41 | }; | 49 | }; |
42 | 50 | ||
51 | #ifdef CONFIG_FUNCTION_RET_TRACER | ||
52 | #define INIT_THREAD_INFO(tsk) \ | ||
53 | { \ | ||
54 | .task = &tsk, \ | ||
55 | .exec_domain = &default_exec_domain, \ | ||
56 | .flags = 0, \ | ||
57 | .cpu = 0, \ | ||
58 | .preempt_count = 1, \ | ||
59 | .addr_limit = KERNEL_DS, \ | ||
60 | .restart_block = { \ | ||
61 | .fn = do_no_restart_syscall, \ | ||
62 | }, \ | ||
63 | .curr_ret_stack = -1,\ | ||
64 | } | ||
65 | #else | ||
43 | #define INIT_THREAD_INFO(tsk) \ | 66 | #define INIT_THREAD_INFO(tsk) \ |
44 | { \ | 67 | { \ |
45 | .task = &tsk, \ | 68 | .task = &tsk, \ |
@@ -52,6 +75,7 @@ struct thread_info { | |||
52 | .fn = do_no_restart_syscall, \ | 75 | .fn = do_no_restart_syscall, \ |
53 | }, \ | 76 | }, \ |
54 | } | 77 | } |
78 | #endif | ||
55 | 79 | ||
56 | #define init_thread_info (init_thread_union.thread_info) | 80 | #define init_thread_info (init_thread_union.thread_info) |
57 | #define init_stack (init_thread_union.stack) | 81 | #define init_stack (init_thread_union.stack) |
diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile index e489ff9cb3e2..1d8ed95da846 100644 --- a/arch/x86/kernel/Makefile +++ b/arch/x86/kernel/Makefile | |||
@@ -14,6 +14,11 @@ CFLAGS_REMOVE_paravirt-spinlocks.o = -pg | |||
14 | CFLAGS_REMOVE_ftrace.o = -pg | 14 | CFLAGS_REMOVE_ftrace.o = -pg |
15 | endif | 15 | endif |
16 | 16 | ||
17 | ifdef CONFIG_FUNCTION_RET_TRACER | ||
18 | # Don't trace __switch_to() but let it for function tracer | ||
19 | CFLAGS_REMOVE_process_32.o = -pg | ||
20 | endif | ||
21 | |||
17 | # | 22 | # |
18 | # vsyscalls (which work on the user stack) should have | 23 | # vsyscalls (which work on the user stack) should have |
19 | # no stack-protector checks: | 24 | # no stack-protector checks: |
@@ -65,6 +70,7 @@ obj-$(CONFIG_X86_LOCAL_APIC) += apic.o nmi.o | |||
65 | obj-$(CONFIG_X86_IO_APIC) += io_apic.o | 70 | obj-$(CONFIG_X86_IO_APIC) += io_apic.o |
66 | obj-$(CONFIG_X86_REBOOTFIXUPS) += reboot_fixups_32.o | 71 | obj-$(CONFIG_X86_REBOOTFIXUPS) += reboot_fixups_32.o |
67 | obj-$(CONFIG_DYNAMIC_FTRACE) += ftrace.o | 72 | obj-$(CONFIG_DYNAMIC_FTRACE) += ftrace.o |
73 | obj-$(CONFIG_FUNCTION_RET_TRACER) += ftrace.o | ||
68 | obj-$(CONFIG_KEXEC) += machine_kexec_$(BITS).o | 74 | obj-$(CONFIG_KEXEC) += machine_kexec_$(BITS).o |
69 | obj-$(CONFIG_KEXEC) += relocate_kernel_$(BITS).o crash.o | 75 | obj-$(CONFIG_KEXEC) += relocate_kernel_$(BITS).o crash.o |
70 | obj-$(CONFIG_CRASH_DUMP) += crash_dump_$(BITS).o | 76 | obj-$(CONFIG_CRASH_DUMP) += crash_dump_$(BITS).o |
diff --git a/arch/x86/kernel/entry_32.S b/arch/x86/kernel/entry_32.S index 9134de814c97..9a0ac85946db 100644 --- a/arch/x86/kernel/entry_32.S +++ b/arch/x86/kernel/entry_32.S | |||
@@ -1188,6 +1188,10 @@ ENTRY(mcount) | |||
1188 | 1188 | ||
1189 | cmpl $ftrace_stub, ftrace_trace_function | 1189 | cmpl $ftrace_stub, ftrace_trace_function |
1190 | jnz trace | 1190 | jnz trace |
1191 | #ifdef CONFIG_FUNCTION_RET_TRACER | ||
1192 | cmpl $ftrace_stub, ftrace_function_return | ||
1193 | jnz trace_return | ||
1194 | #endif | ||
1191 | .globl ftrace_stub | 1195 | .globl ftrace_stub |
1192 | ftrace_stub: | 1196 | ftrace_stub: |
1193 | ret | 1197 | ret |
@@ -1206,8 +1210,37 @@ trace: | |||
1206 | popl %edx | 1210 | popl %edx |
1207 | popl %ecx | 1211 | popl %ecx |
1208 | popl %eax | 1212 | popl %eax |
1213 | jmp ftrace_stub | ||
1209 | 1214 | ||
1215 | #ifdef CONFIG_FUNCTION_RET_TRACER | ||
1216 | trace_return: | ||
1217 | pushl %eax | ||
1218 | pushl %ecx | ||
1219 | pushl %edx | ||
1220 | movl 0xc(%esp), %eax | ||
1221 | pushl %eax | ||
1222 | lea 0x4(%ebp), %eax | ||
1223 | pushl %eax | ||
1224 | call prepare_ftrace_return | ||
1225 | addl $8, %esp | ||
1226 | popl %edx | ||
1227 | popl %ecx | ||
1228 | popl %eax | ||
1210 | jmp ftrace_stub | 1229 | jmp ftrace_stub |
1230 | |||
1231 | .globl return_to_handler | ||
1232 | return_to_handler: | ||
1233 | pushl $0 | ||
1234 | pushl %eax | ||
1235 | pushl %ecx | ||
1236 | pushl %edx | ||
1237 | call ftrace_return_to_handler | ||
1238 | movl %eax, 0xc(%esp) | ||
1239 | popl %edx | ||
1240 | popl %ecx | ||
1241 | popl %eax | ||
1242 | ret | ||
1243 | #endif /* CONFIG_FUNCTION_RET_TRACER */ | ||
1211 | END(mcount) | 1244 | END(mcount) |
1212 | #endif /* CONFIG_DYNAMIC_FTRACE */ | 1245 | #endif /* CONFIG_DYNAMIC_FTRACE */ |
1213 | #endif /* CONFIG_FUNCTION_TRACER */ | 1246 | #endif /* CONFIG_FUNCTION_TRACER */ |
diff --git a/arch/x86/kernel/ftrace.c b/arch/x86/kernel/ftrace.c index 69149337f2fe..d68033bba223 100644 --- a/arch/x86/kernel/ftrace.c +++ b/arch/x86/kernel/ftrace.c | |||
@@ -18,10 +18,173 @@ | |||
18 | #include <linux/list.h> | 18 | #include <linux/list.h> |
19 | 19 | ||
20 | #include <asm/ftrace.h> | 20 | #include <asm/ftrace.h> |
21 | #include <linux/ftrace.h> | ||
21 | #include <asm/nops.h> | 22 | #include <asm/nops.h> |
23 | #include <asm/nmi.h> | ||
22 | 24 | ||
23 | 25 | ||
24 | static unsigned char ftrace_nop[MCOUNT_INSN_SIZE]; | 26 | |
27 | #ifdef CONFIG_FUNCTION_RET_TRACER | ||
28 | |||
29 | /* | ||
30 | * These functions are picked from those used on | ||
31 | * this page for dynamic ftrace. They have been | ||
32 | * simplified to ignore all traces in NMI context. | ||
33 | */ | ||
34 | static atomic_t in_nmi; | ||
35 | |||
36 | void ftrace_nmi_enter(void) | ||
37 | { | ||
38 | atomic_inc(&in_nmi); | ||
39 | } | ||
40 | |||
41 | void ftrace_nmi_exit(void) | ||
42 | { | ||
43 | atomic_dec(&in_nmi); | ||
44 | } | ||
45 | |||
46 | /* | ||
47 | * Synchronize accesses to return adresses stack with | ||
48 | * interrupts. | ||
49 | */ | ||
50 | static raw_spinlock_t ret_stack_lock; | ||
51 | |||
52 | /* Add a function return address to the trace stack on thread info.*/ | ||
53 | static int push_return_trace(unsigned long ret, unsigned long long time, | ||
54 | unsigned long func) | ||
55 | { | ||
56 | int index; | ||
57 | struct thread_info *ti; | ||
58 | unsigned long flags; | ||
59 | int err = 0; | ||
60 | |||
61 | raw_local_irq_save(flags); | ||
62 | __raw_spin_lock(&ret_stack_lock); | ||
63 | |||
64 | ti = current_thread_info(); | ||
65 | /* The return trace stack is full */ | ||
66 | if (ti->curr_ret_stack == FTRACE_RET_STACK_SIZE - 1) { | ||
67 | err = -EBUSY; | ||
68 | goto out; | ||
69 | } | ||
70 | |||
71 | index = ++ti->curr_ret_stack; | ||
72 | ti->ret_stack[index].ret = ret; | ||
73 | ti->ret_stack[index].func = func; | ||
74 | ti->ret_stack[index].calltime = time; | ||
75 | |||
76 | out: | ||
77 | __raw_spin_unlock(&ret_stack_lock); | ||
78 | raw_local_irq_restore(flags); | ||
79 | return err; | ||
80 | } | ||
81 | |||
82 | /* Retrieve a function return address to the trace stack on thread info.*/ | ||
83 | static void pop_return_trace(unsigned long *ret, unsigned long long *time, | ||
84 | unsigned long *func) | ||
85 | { | ||
86 | struct thread_info *ti; | ||
87 | int index; | ||
88 | unsigned long flags; | ||
89 | |||
90 | raw_local_irq_save(flags); | ||
91 | __raw_spin_lock(&ret_stack_lock); | ||
92 | |||
93 | ti = current_thread_info(); | ||
94 | index = ti->curr_ret_stack; | ||
95 | *ret = ti->ret_stack[index].ret; | ||
96 | *func = ti->ret_stack[index].func; | ||
97 | *time = ti->ret_stack[index].calltime; | ||
98 | ti->curr_ret_stack--; | ||
99 | |||
100 | __raw_spin_unlock(&ret_stack_lock); | ||
101 | raw_local_irq_restore(flags); | ||
102 | } | ||
103 | |||
104 | /* | ||
105 | * Send the trace to the ring-buffer. | ||
106 | * @return the original return address. | ||
107 | */ | ||
108 | unsigned long ftrace_return_to_handler(void) | ||
109 | { | ||
110 | struct ftrace_retfunc trace; | ||
111 | pop_return_trace(&trace.ret, &trace.calltime, &trace.func); | ||
112 | trace.rettime = cpu_clock(raw_smp_processor_id()); | ||
113 | ftrace_function_return(&trace); | ||
114 | |||
115 | return trace.ret; | ||
116 | } | ||
117 | |||
118 | /* | ||
119 | * Hook the return address and push it in the stack of return addrs | ||
120 | * in current thread info. | ||
121 | */ | ||
122 | asmlinkage | ||
123 | void prepare_ftrace_return(unsigned long *parent, unsigned long self_addr) | ||
124 | { | ||
125 | unsigned long old; | ||
126 | unsigned long long calltime; | ||
127 | int faulted; | ||
128 | unsigned long return_hooker = (unsigned long) | ||
129 | &return_to_handler; | ||
130 | |||
131 | /* Nmi's are currently unsupported */ | ||
132 | if (atomic_read(&in_nmi)) | ||
133 | return; | ||
134 | |||
135 | /* | ||
136 | * Protect against fault, even if it shouldn't | ||
137 | * happen. This tool is too much intrusive to | ||
138 | * ignore such a protection. | ||
139 | */ | ||
140 | asm volatile( | ||
141 | "1: movl (%[parent_old]), %[old]\n" | ||
142 | "2: movl %[return_hooker], (%[parent_replaced])\n" | ||
143 | " movl $0, %[faulted]\n" | ||
144 | |||
145 | ".section .fixup, \"ax\"\n" | ||
146 | "3: movl $1, %[faulted]\n" | ||
147 | ".previous\n" | ||
148 | |||
149 | ".section __ex_table, \"a\"\n" | ||
150 | " .long 1b, 3b\n" | ||
151 | " .long 2b, 3b\n" | ||
152 | ".previous\n" | ||
153 | |||
154 | : [parent_replaced] "=rm" (parent), [old] "=r" (old), | ||
155 | [faulted] "=r" (faulted) | ||
156 | : [parent_old] "0" (parent), [return_hooker] "r" (return_hooker) | ||
157 | : "memory" | ||
158 | ); | ||
159 | |||
160 | if (WARN_ON(faulted)) { | ||
161 | unregister_ftrace_return(); | ||
162 | return; | ||
163 | } | ||
164 | |||
165 | if (WARN_ON(!__kernel_text_address(old))) { | ||
166 | unregister_ftrace_return(); | ||
167 | *parent = old; | ||
168 | return; | ||
169 | } | ||
170 | |||
171 | calltime = cpu_clock(raw_smp_processor_id()); | ||
172 | |||
173 | if (push_return_trace(old, calltime, self_addr) == -EBUSY) | ||
174 | *parent = old; | ||
175 | } | ||
176 | |||
177 | static int __init init_ftrace_function_return(void) | ||
178 | { | ||
179 | ret_stack_lock = (raw_spinlock_t)__RAW_SPIN_LOCK_UNLOCKED; | ||
180 | return 0; | ||
181 | } | ||
182 | device_initcall(init_ftrace_function_return); | ||
183 | |||
184 | |||
185 | #endif | ||
186 | |||
187 | #ifdef CONFIG_DYNAMIC_FTRACE | ||
25 | 188 | ||
26 | union ftrace_code_union { | 189 | union ftrace_code_union { |
27 | char code[MCOUNT_INSN_SIZE]; | 190 | char code[MCOUNT_INSN_SIZE]; |
@@ -31,17 +194,11 @@ union ftrace_code_union { | |||
31 | } __attribute__((packed)); | 194 | } __attribute__((packed)); |
32 | }; | 195 | }; |
33 | 196 | ||
34 | |||
35 | static int ftrace_calc_offset(long ip, long addr) | 197 | static int ftrace_calc_offset(long ip, long addr) |
36 | { | 198 | { |
37 | return (int)(addr - ip); | 199 | return (int)(addr - ip); |
38 | } | 200 | } |
39 | 201 | ||
40 | unsigned char *ftrace_nop_replace(void) | ||
41 | { | ||
42 | return ftrace_nop; | ||
43 | } | ||
44 | |||
45 | unsigned char *ftrace_call_replace(unsigned long ip, unsigned long addr) | 202 | unsigned char *ftrace_call_replace(unsigned long ip, unsigned long addr) |
46 | { | 203 | { |
47 | static union ftrace_code_union calc; | 204 | static union ftrace_code_union calc; |
@@ -183,6 +340,15 @@ do_ftrace_mod_code(unsigned long ip, void *new_code) | |||
183 | } | 340 | } |
184 | 341 | ||
185 | 342 | ||
343 | |||
344 | |||
345 | static unsigned char ftrace_nop[MCOUNT_INSN_SIZE]; | ||
346 | |||
347 | unsigned char *ftrace_nop_replace(void) | ||
348 | { | ||
349 | return ftrace_nop; | ||
350 | } | ||
351 | |||
186 | int | 352 | int |
187 | ftrace_modify_code(unsigned long ip, unsigned char *old_code, | 353 | ftrace_modify_code(unsigned long ip, unsigned char *old_code, |
188 | unsigned char *new_code) | 354 | unsigned char *new_code) |
@@ -292,3 +458,4 @@ int __init ftrace_dyn_arch_init(void *data) | |||
292 | 458 | ||
293 | return 0; | 459 | return 0; |
294 | } | 460 | } |
461 | #endif | ||