]> rtime.felk.cvut.cz Git - sojka/nv-tegra/linux-3.10.git/blob - drivers/misc/tegra-profiler/backtrace.c
misc: tegra-profiler: support too deep stack level
[sojka/nv-tegra/linux-3.10.git] / drivers / misc / tegra-profiler / backtrace.c
1 /*
2  * drivers/misc/tegra-profiler/backtrace.c
3  *
4  * Copyright (c) 2014, NVIDIA CORPORATION.  All rights reserved.
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms and conditions of the GNU General Public License,
8  * version 2, as published by the Free Software Foundation.
9  *
10  * This program is distributed in the hope it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
13  * more details.
14  *
15  */
16
17 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
18
19 #include <linux/uaccess.h>
20 #include <linux/sched.h>
21 #include <linux/mm.h>
22
23 #include <linux/tegra_profiler.h>
24
25 #include "quadd.h"
26 #include "backtrace.h"
27 #include "eh_unwind.h"
28
29 #define QUADD_USER_SPACE_MIN_ADDR       0x8000
30
31 static inline int
32 is_thumb_mode(struct pt_regs *regs)
33 {
34 #ifdef CONFIG_ARM64
35         return compat_thumb_mode(regs);
36 #else
37         return thumb_mode(regs);
38 #endif
39 }
40
41 unsigned long
42 quadd_user_stack_pointer(struct pt_regs *regs)
43 {
44 #ifdef CONFIG_ARM64
45         if (compat_user_mode(regs))
46                 return regs->compat_sp;
47 #endif
48
49         return user_stack_pointer(regs);
50 }
51
52 static inline unsigned long
53 get_user_frame_pointer(struct pt_regs *regs)
54 {
55         unsigned long fp;
56
57 #ifdef CONFIG_ARM64
58         if (compat_user_mode(regs))
59                 fp = is_thumb_mode(regs) ?
60                         regs->compat_usr(7) : regs->compat_usr(11);
61         else
62                 fp = regs->regs[29];
63 #else
64         fp = is_thumb_mode(regs) ? regs->ARM_r7 : regs->ARM_fp;
65 #endif
66         return fp;
67 }
68
69 unsigned long
70 quadd_user_link_register(struct pt_regs *regs)
71 {
72 #ifdef CONFIG_ARM64
73         return compat_user_mode(regs) ?
74                 regs->compat_lr : regs->regs[30];
75 #else
76         return regs->ARM_lr;
77 #endif
78 }
79
80 int
81 quadd_callchain_store(struct quadd_callchain *cc,
82                       unsigned long ip)
83 {
84         if (!ip) {
85                 cc->unw_rc = QUADD_URC_PC_INCORRECT;
86                 return 0;
87         }
88
89         if (cc->nr >= QUADD_MAX_STACK_DEPTH) {
90                 cc->unw_rc = QUADD_URC_LEVEL_TOO_DEEP;
91                 return 0;
92         }
93
94         if (cc->cs_64)
95                 cc->ip_64[cc->nr++] = ip;
96         else
97                 cc->ip_32[cc->nr++] = ip;
98
99         return 1;
100 }
101
102 static unsigned long __user *
103 user_backtrace(unsigned long __user *tail,
104                struct quadd_callchain *cc,
105                struct vm_area_struct *stack_vma)
106 {
107         int nr_added;
108         unsigned long value, value_lr = 0, value_fp = 0;
109         unsigned long __user *fp_prev = NULL;
110
111         if (!is_vma_addr((unsigned long)tail, stack_vma, sizeof(*tail)))
112                 return NULL;
113
114         if (__copy_from_user_inatomic(&value, tail, sizeof(unsigned long))) {
115                 cc->unw_rc = QUADD_URC_EACCESS;
116                 return NULL;
117         }
118
119         if (is_vma_addr(value, stack_vma, sizeof(value))) {
120                 /* gcc thumb/clang frame */
121                 value_fp = value;
122
123                 if (!is_vma_addr((unsigned long)(tail + 1), stack_vma,
124                     sizeof(*tail)))
125                         return NULL;
126
127                 if (__copy_from_user_inatomic(&value_lr, tail + 1,
128                                               sizeof(value_lr))) {
129                         cc->unw_rc = QUADD_URC_EACCESS;
130                         return NULL;
131                 }
132         } else {
133                 /* gcc arm frame */
134                 if (__copy_from_user_inatomic(&value_fp, tail - 1,
135                                               sizeof(value_fp))) {
136                         cc->unw_rc = QUADD_URC_EACCESS;
137                         return NULL;
138                 }
139
140                 if (!is_vma_addr(value_fp, stack_vma, sizeof(value_fp)))
141                         return NULL;
142
143                 value_lr = value;
144         }
145
146         fp_prev = (unsigned long __user *)value_fp;
147
148         if (value_lr < QUADD_USER_SPACE_MIN_ADDR) {
149                 cc->unw_rc = QUADD_URC_PC_INCORRECT;
150                 return NULL;
151         }
152
153         nr_added = quadd_callchain_store(cc, value_lr);
154         if (nr_added == 0)
155                 return NULL;
156
157         if (fp_prev <= tail)
158                 return NULL;
159
160         return fp_prev;
161 }
162
163 static unsigned int
164 get_user_callchain_fp(struct pt_regs *regs,
165                       struct quadd_callchain *cc,
166                       struct task_struct *task)
167 {
168         unsigned long fp, sp, pc, reg;
169         struct vm_area_struct *vma, *vma_pc;
170         unsigned long __user *tail = NULL;
171         struct mm_struct *mm = task->mm;
172
173         cc->nr = 0;
174         cc->unw_rc = QUADD_URC_FP_INCORRECT;
175
176         if (!regs || !mm) {
177                 cc->unw_rc = QUADD_URC_FAILURE;
178                 return 0;
179         }
180
181         sp = quadd_user_stack_pointer(regs);
182         pc = instruction_pointer(regs);
183         fp = get_user_frame_pointer(regs);
184
185         if (fp == 0 || fp < sp || fp & 0x3)
186                 return 0;
187
188         vma = find_vma(mm, sp);
189         if (!vma) {
190                 cc->unw_rc = QUADD_URC_SP_INCORRECT;
191                 return 0;
192         }
193
194         if (!is_vma_addr(fp, vma, sizeof(fp)))
195                 return 0;
196
197         if (probe_kernel_address(fp, reg)) {
198                 pr_warn_once("frame error: sp/fp: %#lx/%#lx, pc/lr: %#lx/%#lx, vma: %#lx-%#lx\n",
199                              sp, fp, pc, quadd_user_link_register(regs),
200                              vma->vm_start, vma->vm_end);
201                 cc->unw_rc = QUADD_URC_EACCESS;
202                 return 0;
203         }
204
205         if (is_thumb_mode(regs)) {
206                 if (reg <= fp || !is_vma_addr(reg, vma, sizeof(reg)))
207                         return 0;
208         } else if (reg > fp && is_vma_addr(reg, vma, sizeof(reg))) {
209                 /* fp --> fp prev */
210                 unsigned long value;
211                 int read_lr = 0;
212
213                 if (is_vma_addr(fp + sizeof(unsigned long), vma, sizeof(fp))) {
214                         if (__copy_from_user_inatomic(
215                                         &value,
216                                         (unsigned long __user *)fp + 1,
217                                         sizeof(unsigned long))) {
218                                 cc->unw_rc = QUADD_URC_EACCESS;
219                                 return 0;
220                         }
221
222                         vma_pc = find_vma(mm, pc);
223                         read_lr = 1;
224                 }
225
226                 if (!read_lr || !is_vma_addr(value, vma_pc, sizeof(value))) {
227                         /* gcc: fp --> short frame tail (fp) */
228                         int nr_added;
229                         unsigned long lr = quadd_user_link_register(regs);
230
231                         if (lr < QUADD_USER_SPACE_MIN_ADDR) {
232                                 cc->unw_rc = QUADD_URC_PC_INCORRECT;
233                                 return 0;
234                         }
235
236                         nr_added = quadd_callchain_store(cc, lr);
237                         if (nr_added == 0)
238                                 return cc->nr;
239
240                         tail = (unsigned long __user *)reg;
241                 }
242         }
243
244         if (!tail)
245                 tail = (unsigned long __user *)fp;
246
247         while (tail && !((unsigned long)tail & 0x3))
248                 tail = user_backtrace(tail, cc, vma);
249
250         return cc->nr;
251 }
252
253 static unsigned int
254 __user_backtrace(struct quadd_callchain *cc, struct task_struct *task)
255 {
256         struct mm_struct *mm = task->mm;
257         struct vm_area_struct *vma;
258         unsigned long __user *tail;
259
260         cc->unw_rc = QUADD_URC_FP_INCORRECT;
261
262         if (!mm) {
263                 cc->unw_rc = QUADD_URC_FAILURE;
264                 return cc->nr;
265         }
266
267         vma = find_vma(mm, cc->curr_sp);
268         if (!vma) {
269                 cc->unw_rc = QUADD_URC_SP_INCORRECT;
270                 return cc->nr;
271         }
272
273         tail = (unsigned long __user *)cc->curr_fp;
274
275         while (tail && !((unsigned long)tail & 0x3))
276                 tail = user_backtrace(tail, cc, vma);
277
278         return cc->nr;
279 }
280
281 #ifdef CONFIG_ARM64
282 static u32 __user *
283 user_backtrace_compat(u32 __user *tail,
284                struct quadd_callchain *cc,
285                struct vm_area_struct *stack_vma)
286 {
287         int nr_added;
288         u32 value, value_lr = 0, value_fp = 0;
289         u32 __user *fp_prev = NULL;
290
291         if (!is_vma_addr((unsigned long)tail, stack_vma, sizeof(*tail)))
292                 return NULL;
293
294         if (__copy_from_user_inatomic(&value, tail, sizeof(value))) {
295                 cc->unw_rc = QUADD_URC_EACCESS;
296                 return NULL;
297         }
298
299         if (is_vma_addr(value, stack_vma, sizeof(value))) {
300                 /* gcc thumb/clang frame */
301                 value_fp = value;
302
303                 if (!is_vma_addr((unsigned long)(tail + 1), stack_vma,
304                     sizeof(*tail)))
305                         return NULL;
306
307                 if (__copy_from_user_inatomic(&value_lr, tail + 1,
308                                               sizeof(value_lr))) {
309                         cc->unw_rc = QUADD_URC_EACCESS;
310                         return NULL;
311                 }
312         } else {
313                 /* gcc arm frame */
314                 if (__copy_from_user_inatomic(&value_fp, tail - 1,
315                                               sizeof(value_fp))) {
316                         cc->unw_rc = QUADD_URC_EACCESS;
317                         return NULL;
318                 }
319
320                 if (!is_vma_addr(value_fp, stack_vma, sizeof(value_fp)))
321                         return NULL;
322
323                 value_lr = value;
324         }
325
326         fp_prev = (u32 __user *)(unsigned long)value_fp;
327
328         if (value_lr < QUADD_USER_SPACE_MIN_ADDR) {
329                 cc->unw_rc = QUADD_URC_PC_INCORRECT;
330                 return NULL;
331         }
332
333         nr_added = quadd_callchain_store(cc, value_lr);
334         if (nr_added == 0)
335                 return NULL;
336
337         if (fp_prev <= tail)
338                 return NULL;
339
340         return fp_prev;
341 }
342
343 static unsigned int
344 get_user_callchain_fp_compat(struct pt_regs *regs,
345                              struct quadd_callchain *cc,
346                              struct task_struct *task)
347 {
348         u32 fp, sp, pc, reg;
349         struct vm_area_struct *vma, *vma_pc;
350         u32 __user *tail = NULL;
351         struct mm_struct *mm = task->mm;
352
353         cc->nr = 0;
354         cc->unw_rc = QUADD_URC_FP_INCORRECT;
355
356         if (!regs || !mm) {
357                 cc->unw_rc = QUADD_URC_FAILURE;
358                 return 0;
359         }
360
361         sp = quadd_user_stack_pointer(regs);
362         pc = instruction_pointer(regs);
363         fp = get_user_frame_pointer(regs);
364
365         if (fp == 0 || fp < sp || fp & 0x3)
366                 return 0;
367
368         vma = find_vma(mm, sp);
369         if (!vma) {
370                 cc->unw_rc = QUADD_URC_SP_INCORRECT;
371                 return 0;
372         }
373
374         if (!is_vma_addr(fp, vma, sizeof(fp)))
375                 return 0;
376
377         if (probe_kernel_address((unsigned long)fp, reg)) {
378                 pr_warn_once("frame error: sp/fp: %#x/%#x, pc/lr: %#x/%#x, vma: %#lx-%#lx\n",
379                              sp, fp, pc, (u32)quadd_user_link_register(regs),
380                              vma->vm_start, vma->vm_end);
381                 cc->unw_rc = QUADD_URC_EACCESS;
382                 return 0;
383         }
384
385         if (is_thumb_mode(regs)) {
386                 if (reg <= fp || !is_vma_addr(reg, vma, sizeof(reg)))
387                         return 0;
388         } else if (reg > fp && is_vma_addr(reg, vma, sizeof(reg))) {
389                 /* fp --> fp prev */
390                 u32 value;
391                 int read_lr = 0;
392
393                 if (is_vma_addr(fp + sizeof(u32), vma, sizeof(fp))) {
394                         if (__copy_from_user_inatomic(
395                                         &value,
396                                         (u32 __user *)(fp + sizeof(u32)),
397                                         sizeof(value))) {
398                                 cc->unw_rc = QUADD_URC_EACCESS;
399                                 return 0;
400                         }
401
402                         vma_pc = find_vma(mm, pc);
403                         read_lr = 1;
404                 }
405
406                 if (!read_lr || !is_vma_addr(value, vma_pc, sizeof(value))) {
407                         /* gcc: fp --> short frame tail (fp) */
408                         int nr_added;
409                         u32 lr = quadd_user_link_register(regs);
410
411                         if (lr < QUADD_USER_SPACE_MIN_ADDR) {
412                                 cc->unw_rc = QUADD_URC_PC_INCORRECT;
413                                 return 0;
414                         }
415
416                         nr_added = quadd_callchain_store(cc, lr);
417                         if (nr_added == 0)
418                                 return cc->nr;
419
420                         tail = (u32 __user *)(unsigned long)reg;
421                 }
422         }
423
424         if (!tail)
425                 tail = (u32 __user *)(unsigned long)fp;
426
427         while (tail && !((unsigned long)tail & 0x3))
428                 tail = user_backtrace_compat(tail, cc, vma);
429
430         return cc->nr;
431 }
432
433 static unsigned int
434 __user_backtrace_compat(struct quadd_callchain *cc, struct task_struct *task)
435 {
436         struct mm_struct *mm = task->mm;
437         struct vm_area_struct *vma;
438         u32 __user *tail;
439
440         cc->unw_rc = QUADD_URC_FP_INCORRECT;
441
442         if (!mm) {
443                 cc->unw_rc = QUADD_URC_FAILURE;
444                 return cc->nr;
445         }
446
447         vma = find_vma(mm, cc->curr_sp);
448         if (!vma) {
449                 cc->unw_rc = QUADD_URC_SP_INCORRECT;
450                 return cc->nr;
451         }
452
453         tail = (u32 __user *)cc->curr_fp;
454
455         while (tail && !((unsigned long)tail & 0x3))
456                 tail = user_backtrace_compat(tail, cc, vma);
457
458         return cc->nr;
459 }
460
461 #endif  /* CONFIG_ARM64 */
462
463 static unsigned int
464 __get_user_callchain_fp(struct pt_regs *regs,
465                       struct quadd_callchain *cc,
466                       struct task_struct *task)
467 {
468         if (cc->nr > 0) {
469                 int nr, nr_prev = cc->nr;
470
471                 if (cc->unw_rc == QUADD_URC_LEVEL_TOO_DEEP)
472                         return nr_prev;
473
474 #ifdef CONFIG_ARM64
475                 if (compat_user_mode(regs))
476                         nr = __user_backtrace_compat(cc, task);
477                 else
478                         nr = __user_backtrace(cc, task);
479 #else
480                 nr = __user_backtrace(cc, task);
481 #endif
482                 if (nr != nr_prev)
483                         cc->unw_method = QUADD_UNW_METHOD_MIXED;
484
485                 return nr;
486         }
487
488         cc->unw_method = QUADD_UNW_METHOD_FP;
489
490 #ifdef CONFIG_ARM64
491         if (compat_user_mode(regs))
492                 return get_user_callchain_fp_compat(regs, cc, task);
493 #endif
494         return get_user_callchain_fp(regs, cc, task);
495 }
496
497 unsigned int
498 quadd_get_user_callchain(struct pt_regs *regs,
499                          struct quadd_callchain *cc,
500                          struct quadd_ctx *ctx,
501                          struct task_struct *task)
502 {
503         int unw_fp, unw_eht, unw_mix, nr = 0;
504         unsigned int extra;
505         struct quadd_parameters *param = &ctx->param;
506
507         cc->nr = 0;
508         cc->unw_method = QUADD_URC_FAILURE;
509
510         if (!regs)
511                 return 0;
512
513         cc->curr_sp = 0;
514         cc->curr_fp = 0;
515
516 #ifdef CONFIG_ARM64
517         cc->cs_64 = compat_user_mode(regs) ? 0 : 1;
518 #else
519         cc->cs_64 = 0;
520 #endif
521
522         extra = param->reserved[QUADD_PARAM_IDX_EXTRA];
523
524         unw_fp = extra & QUADD_PARAM_EXTRA_BT_FP;
525         unw_eht = extra & QUADD_PARAM_EXTRA_BT_UNWIND_TABLES;
526         unw_mix = extra & QUADD_PARAM_EXTRA_BT_MIXED;
527
528         cc->unw_rc = 0;
529
530         if (unw_eht)
531                 nr = quadd_get_user_callchain_ut(regs, cc, task);
532
533         if (unw_fp) {
534                 if (!nr || unw_mix)
535                         nr = __get_user_callchain_fp(regs, cc, task);
536         }
537
538         return nr;
539 }