Commit | Line | Data |
---|---|---|
40a8b421 DP |
1 | /* |
2 | * SH specific backtracing code for oprofile | |
3 | * | |
4 | * Copyright 2007 STMicroelectronics Ltd. | |
5 | * | |
6 | * Author: Dave Peverley <dpeverley@mpc-data.co.uk> | |
7 | * | |
8 | * Based on ARM oprofile backtrace code by Richard Purdie and in turn, i386 | |
9 | * oprofile backtrace code by John Levon, David Smith | |
10 | * | |
11 | * This program is free software; you can redistribute it and/or modify | |
12 | * it under the terms of the GNU General Public License version 2 as | |
13 | * published by the Free Software Foundation. | |
14 | * | |
15 | */ | |
16 | #include <linux/oprofile.h> | |
17 | #include <linux/sched.h> | |
18 | #include <linux/kallsyms.h> | |
19 | #include <linux/mm.h> | |
20 | #include <asm/ptrace.h> | |
21 | #include <asm/uaccess.h> | |
22 | #include <asm/sections.h> | |
23 | ||
24 | /* Limit to stop backtracing too far. */ | |
25 | static int backtrace_limit = 20; | |
26 | ||
27 | static unsigned long * | |
28 | user_backtrace(unsigned long *stackaddr, struct pt_regs *regs) | |
29 | { | |
30 | unsigned long buf_stack; | |
31 | ||
32 | /* Also check accessibility of address */ | |
33 | if (!access_ok(VERIFY_READ, stackaddr, sizeof(unsigned long))) | |
34 | return NULL; | |
35 | ||
36 | if (__copy_from_user_inatomic(&buf_stack, stackaddr, sizeof(unsigned long))) | |
37 | return NULL; | |
38 | ||
39 | /* Quick paranoia check */ | |
40 | if (buf_stack & 3) | |
41 | return NULL; | |
42 | ||
43 | oprofile_add_trace(buf_stack); | |
44 | ||
45 | stackaddr++; | |
46 | ||
47 | return stackaddr; | |
48 | } | |
49 | ||
50 | /* | |
51 | * | | /\ Higher addresses | |
52 | * | | | |
53 | * --------------- stack base (address of current_thread_info) | |
54 | * | thread info | | |
55 | * . . | |
56 | * | stack | | |
57 | * --------------- saved regs->regs[15] value if valid | |
58 | * . . | |
59 | * --------------- struct pt_regs stored on stack (struct pt_regs *) | |
60 | * | | | |
61 | * . . | |
62 | * | | | |
63 | * --------------- ??? | |
64 | * | | | |
65 | * | | \/ Lower addresses | |
66 | * | |
67 | * Thus, &pt_regs <-> stack base restricts the valid(ish) fp values | |
68 | */ | |
69 | static int valid_kernel_stack(unsigned long *stackaddr, struct pt_regs *regs) | |
70 | { | |
71 | unsigned long stack = (unsigned long)regs; | |
72 | unsigned long stack_base = (stack & ~(THREAD_SIZE - 1)) + THREAD_SIZE; | |
73 | ||
74 | return ((unsigned long)stackaddr > stack) && ((unsigned long)stackaddr < stack_base); | |
75 | } | |
76 | ||
77 | static unsigned long * | |
78 | kernel_backtrace(unsigned long *stackaddr, struct pt_regs *regs) | |
79 | { | |
80 | unsigned long addr; | |
81 | ||
82 | /* | |
83 | * If not a valid kernel address, keep going till we find one | |
84 | * or the SP stops being a valid address. | |
85 | */ | |
86 | do { | |
87 | addr = *stackaddr++; | |
59de580a | 88 | oprofile_add_trace(addr); |
40a8b421 DP |
89 | } while (valid_kernel_stack(stackaddr, regs)); |
90 | ||
91 | return stackaddr; | |
92 | } | |
93 | ||
94 | void sh_backtrace(struct pt_regs * const regs, unsigned int depth) | |
95 | { | |
96 | unsigned long *stackaddr; | |
97 | ||
98 | /* | |
99 | * Paranoia - clip max depth as we could get lost in the weeds. | |
100 | */ | |
101 | if (depth > backtrace_limit) | |
102 | depth = backtrace_limit; | |
103 | ||
104 | stackaddr = (unsigned long *)regs->regs[15]; | |
105 | if (!user_mode(regs)) { | |
106 | while (depth-- && valid_kernel_stack(stackaddr, regs)) | |
107 | stackaddr = kernel_backtrace(stackaddr, regs); | |
108 | ||
109 | return; | |
110 | } | |
111 | ||
112 | while (depth-- && (stackaddr != NULL)) | |
113 | stackaddr = user_backtrace(stackaddr, regs); | |
114 | } |