diff options
Diffstat (limited to 'arch/x86/kernel/unwind_frame.c')
-rw-r--r-- | arch/x86/kernel/unwind_frame.c | 49 |
1 files changed, 40 insertions, 9 deletions
diff --git a/arch/x86/kernel/unwind_frame.c b/arch/x86/kernel/unwind_frame.c index 82c6d7f1fd73..b9389d72b2f7 100644 --- a/arch/x86/kernel/unwind_frame.c +++ b/arch/x86/kernel/unwind_frame.c | |||
@@ -104,6 +104,11 @@ static inline unsigned long *last_frame(struct unwind_state *state) | |||
104 | return (unsigned long *)task_pt_regs(state->task) - 2; | 104 | return (unsigned long *)task_pt_regs(state->task) - 2; |
105 | } | 105 | } |
106 | 106 | ||
107 | static bool is_last_frame(struct unwind_state *state) | ||
108 | { | ||
109 | return state->bp == last_frame(state); | ||
110 | } | ||
111 | |||
107 | #ifdef CONFIG_X86_32 | 112 | #ifdef CONFIG_X86_32 |
108 | #define GCC_REALIGN_WORDS 3 | 113 | #define GCC_REALIGN_WORDS 3 |
109 | #else | 114 | #else |
@@ -115,16 +120,15 @@ static inline unsigned long *last_aligned_frame(struct unwind_state *state) | |||
115 | return last_frame(state) - GCC_REALIGN_WORDS; | 120 | return last_frame(state) - GCC_REALIGN_WORDS; |
116 | } | 121 | } |
117 | 122 | ||
118 | static bool is_last_task_frame(struct unwind_state *state) | 123 | static bool is_last_aligned_frame(struct unwind_state *state) |
119 | { | 124 | { |
120 | unsigned long *last_bp = last_frame(state); | 125 | unsigned long *last_bp = last_frame(state); |
121 | unsigned long *aligned_bp = last_aligned_frame(state); | 126 | unsigned long *aligned_bp = last_aligned_frame(state); |
122 | 127 | ||
123 | /* | 128 | /* |
124 | * We have to check for the last task frame at two different locations | 129 | * GCC can occasionally decide to realign the stack pointer and change |
125 | * because gcc can occasionally decide to realign the stack pointer and | 130 | * the offset of the stack frame in the prologue of a function called |
126 | * change the offset of the stack frame in the prologue of a function | 131 | * by head/entry code. Examples: |
127 | * called by head/entry code. Examples: | ||
128 | * | 132 | * |
129 | * <start_secondary>: | 133 | * <start_secondary>: |
130 | * push %edi | 134 | * push %edi |
@@ -141,11 +145,38 @@ static bool is_last_task_frame(struct unwind_state *state) | |||
141 | * push %rbp | 145 | * push %rbp |
142 | * mov %rsp,%rbp | 146 | * mov %rsp,%rbp |
143 | * | 147 | * |
144 | * Note that after aligning the stack, it pushes a duplicate copy of | 148 | * After aligning the stack, it pushes a duplicate copy of the return |
145 | * the return address before pushing the frame pointer. | 149 | * address before pushing the frame pointer. |
150 | */ | ||
151 | return (state->bp == aligned_bp && *(aligned_bp + 1) == *(last_bp + 1)); | ||
152 | } | ||
153 | |||
154 | static bool is_last_ftrace_frame(struct unwind_state *state) | ||
155 | { | ||
156 | unsigned long *last_bp = last_frame(state); | ||
157 | unsigned long *last_ftrace_bp = last_bp - 3; | ||
158 | |||
159 | /* | ||
160 | * When unwinding from an ftrace handler of a function called by entry | ||
161 | * code, the stack layout of the last frame is: | ||
162 | * | ||
163 | * bp | ||
164 | * parent ret addr | ||
165 | * bp | ||
166 | * function ret addr | ||
167 | * parent ret addr | ||
168 | * pt_regs | ||
169 | * ----------------- | ||
146 | */ | 170 | */ |
147 | return (state->bp == last_bp || | 171 | return (state->bp == last_ftrace_bp && |
148 | (state->bp == aligned_bp && *(aligned_bp+1) == *(last_bp+1))); | 172 | *state->bp == *(state->bp + 2) && |
173 | *(state->bp + 1) == *(state->bp + 4)); | ||
174 | } | ||
175 | |||
176 | static bool is_last_task_frame(struct unwind_state *state) | ||
177 | { | ||
178 | return is_last_frame(state) || is_last_aligned_frame(state) || | ||
179 | is_last_ftrace_frame(state); | ||
149 | } | 180 | } |
150 | 181 | ||
151 | /* | 182 | /* |