Commit | Line | Data |
---|---|---|
1da177e4 LT |
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 | ||
fa1e1bdf | 94 | if (!user_mode_vm(regs)) { |
1da177e4 LT |
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 | } |