diff options
Diffstat (limited to 'arch/i386/oprofile/backtrace.c')
-rw-r--r-- | arch/i386/oprofile/backtrace.c | 111 |
1 files changed, 111 insertions, 0 deletions
diff --git a/arch/i386/oprofile/backtrace.c b/arch/i386/oprofile/backtrace.c new file mode 100644 index 000000000000..52d72e074f7f --- /dev/null +++ b/arch/i386/oprofile/backtrace.c | |||
@@ -0,0 +1,111 @@ | |||
1 | /** | ||
2 | * @file backtrace.c | ||
3 | * | ||
4 | * @remark Copyright 2002 OProfile authors | ||
5 | * @remark Read the file COPYING | ||
6 | * | ||
7 | * @author John Levon | ||
8 | * @author David Smith | ||
9 | */ | ||
10 | |||
11 | #include <linux/oprofile.h> | ||
12 | #include <linux/sched.h> | ||
13 | #include <linux/mm.h> | ||
14 | #include <asm/ptrace.h> | ||
15 | |||
16 | struct frame_head { | ||
17 | struct frame_head * ebp; | ||
18 | unsigned long ret; | ||
19 | } __attribute__((packed)); | ||
20 | |||
21 | static struct frame_head * | ||
22 | dump_backtrace(struct frame_head * head) | ||
23 | { | ||
24 | oprofile_add_trace(head->ret); | ||
25 | |||
26 | /* frame pointers should strictly progress back up the stack | ||
27 | * (towards higher addresses) */ | ||
28 | if (head >= head->ebp) | ||
29 | return NULL; | ||
30 | |||
31 | return head->ebp; | ||
32 | } | ||
33 | |||
34 | /* check that the page(s) containing the frame head are present */ | ||
35 | static int pages_present(struct frame_head * head) | ||
36 | { | ||
37 | struct mm_struct * mm = current->mm; | ||
38 | |||
39 | /* FIXME: only necessary once per page */ | ||
40 | if (!check_user_page_readable(mm, (unsigned long)head)) | ||
41 | return 0; | ||
42 | |||
43 | return check_user_page_readable(mm, (unsigned long)(head + 1)); | ||
44 | } | ||
45 | |||
46 | /* | ||
47 | * | | /\ Higher addresses | ||
48 | * | | | ||
49 | * --------------- stack base (address of current_thread_info) | ||
50 | * | thread info | | ||
51 | * . . | ||
52 | * | stack | | ||
53 | * --------------- saved regs->ebp value if valid (frame_head address) | ||
54 | * . . | ||
55 | * --------------- struct pt_regs stored on stack (struct pt_regs *) | ||
56 | * | | | ||
57 | * . . | ||
58 | * | | | ||
59 | * --------------- %esp | ||
60 | * | | | ||
61 | * | | \/ Lower addresses | ||
62 | * | ||
63 | * Thus, &pt_regs <-> stack base restricts the valid(ish) ebp values | ||
64 | */ | ||
65 | #ifdef CONFIG_FRAME_POINTER | ||
66 | static int valid_kernel_stack(struct frame_head * head, struct pt_regs * regs) | ||
67 | { | ||
68 | unsigned long headaddr = (unsigned long)head; | ||
69 | unsigned long stack = (unsigned long)regs; | ||
70 | unsigned long stack_base = (stack & ~(THREAD_SIZE - 1)) + THREAD_SIZE; | ||
71 | |||
72 | return headaddr > stack && headaddr < stack_base; | ||
73 | } | ||
74 | #else | ||
75 | /* without fp, it's just junk */ | ||
76 | static int valid_kernel_stack(struct frame_head * head, struct pt_regs * regs) | ||
77 | { | ||
78 | return 0; | ||
79 | } | ||
80 | #endif | ||
81 | |||
82 | |||
83 | void | ||
84 | x86_backtrace(struct pt_regs * const regs, unsigned int depth) | ||
85 | { | ||
86 | struct frame_head *head; | ||
87 | |||
88 | #ifdef CONFIG_X86_64 | ||
89 | head = (struct frame_head *)regs->rbp; | ||
90 | #else | ||
91 | head = (struct frame_head *)regs->ebp; | ||
92 | #endif | ||
93 | |||
94 | if (!user_mode(regs)) { | ||
95 | while (depth-- && valid_kernel_stack(head, regs)) | ||
96 | head = dump_backtrace(head); | ||
97 | return; | ||
98 | } | ||
99 | |||
100 | #ifdef CONFIG_SMP | ||
101 | if (!spin_trylock(¤t->mm->page_table_lock)) | ||
102 | return; | ||
103 | #endif | ||
104 | |||
105 | while (depth-- && head && pages_present(head)) | ||
106 | head = dump_backtrace(head); | ||
107 | |||
108 | #ifdef CONFIG_SMP | ||
109 | spin_unlock(¤t->mm->page_table_lock); | ||
110 | #endif | ||
111 | } | ||