GNU Linux-libre 6.1.90-gnu
[releases.git] / arch / loongarch / kernel / unwind_prologue.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (C) 2022 Loongson Technology Corporation Limited
4  */
5 #include <linux/kallsyms.h>
6
7 #include <asm/inst.h>
8 #include <asm/ptrace.h>
9 #include <asm/unwind.h>
10
11 unsigned long unwind_get_return_address(struct unwind_state *state)
12 {
13
14         if (unwind_done(state))
15                 return 0;
16         else if (state->type)
17                 return state->pc;
18         else if (state->first)
19                 return state->pc;
20
21         return *(unsigned long *)(state->sp);
22
23 }
24 EXPORT_SYMBOL_GPL(unwind_get_return_address);
25
26 static bool unwind_by_guess(struct unwind_state *state)
27 {
28         struct stack_info *info = &state->stack_info;
29         unsigned long addr;
30
31         for (state->sp += sizeof(unsigned long);
32              state->sp < info->end;
33              state->sp += sizeof(unsigned long)) {
34                 addr = *(unsigned long *)(state->sp);
35                 if (__kernel_text_address(addr))
36                         return true;
37         }
38
39         return false;
40 }
41
42 static bool unwind_by_prologue(struct unwind_state *state)
43 {
44         struct stack_info *info = &state->stack_info;
45         union loongarch_instruction *ip, *ip_end;
46         long frame_ra = -1;
47         unsigned long frame_size = 0;
48         unsigned long size, offset, pc = state->pc;
49
50         if (state->sp >= info->end || state->sp < info->begin)
51                 return false;
52
53         if (!kallsyms_lookup_size_offset(pc, &size, &offset))
54                 return false;
55
56         ip = (union loongarch_instruction *)(pc - offset);
57         ip_end = (union loongarch_instruction *)pc;
58
59         while (ip < ip_end) {
60                 if (is_stack_alloc_ins(ip)) {
61                         frame_size = (1 << 12) - ip->reg2i12_format.immediate;
62                         ip++;
63                         break;
64                 }
65                 ip++;
66         }
67
68         if (!frame_size) {
69                 if (state->first)
70                         goto first;
71
72                 return false;
73         }
74
75         while (ip < ip_end) {
76                 if (is_ra_save_ins(ip)) {
77                         frame_ra = ip->reg2i12_format.immediate;
78                         break;
79                 }
80                 if (is_branch_ins(ip))
81                         break;
82                 ip++;
83         }
84
85         if (frame_ra < 0) {
86                 if (state->first) {
87                         state->sp = state->sp + frame_size;
88                         goto first;
89                 }
90                 return false;
91         }
92
93         if (state->first)
94                 state->first = false;
95
96         state->pc = *(unsigned long *)(state->sp + frame_ra);
97         state->sp = state->sp + frame_size;
98         return !!__kernel_text_address(state->pc);
99
100 first:
101         state->first = false;
102         if (state->pc == state->ra)
103                 return false;
104
105         state->pc = state->ra;
106
107         return !!__kernel_text_address(state->ra);
108 }
109
110 void unwind_start(struct unwind_state *state, struct task_struct *task,
111                     struct pt_regs *regs)
112 {
113         memset(state, 0, sizeof(*state));
114         state->type = UNWINDER_PROLOGUE;
115
116         if (regs) {
117                 state->sp = regs->regs[3];
118                 state->pc = regs->csr_era;
119                 state->ra = regs->regs[1];
120                 if (!__kernel_text_address(state->pc))
121                         state->type = UNWINDER_GUESS;
122         } else if (task && task != current) {
123                 state->sp = thread_saved_fp(task);
124                 state->pc = thread_saved_ra(task);
125                 state->ra = 0;
126         } else {
127                 state->sp = (unsigned long)__builtin_frame_address(0);
128                 state->pc = (unsigned long)__builtin_return_address(0);
129                 state->ra = 0;
130         }
131
132         state->task = task;
133         state->first = true;
134
135         get_stack_info(state->sp, state->task, &state->stack_info);
136
137         if (!unwind_done(state) && !__kernel_text_address(state->pc))
138                 unwind_next_frame(state);
139 }
140 EXPORT_SYMBOL_GPL(unwind_start);
141
142 bool unwind_next_frame(struct unwind_state *state)
143 {
144         struct stack_info *info = &state->stack_info;
145         struct pt_regs *regs;
146         unsigned long pc;
147
148         if (unwind_done(state))
149                 return false;
150
151         do {
152                 switch (state->type) {
153                 case UNWINDER_GUESS:
154                         state->first = false;
155                         if (unwind_by_guess(state))
156                                 return true;
157                         break;
158
159                 case UNWINDER_PROLOGUE:
160                         if (unwind_by_prologue(state))
161                                 return true;
162
163                         if (info->type == STACK_TYPE_IRQ &&
164                                 info->end == state->sp) {
165                                 regs = (struct pt_regs *)info->next_sp;
166                                 pc = regs->csr_era;
167
168                                 if (user_mode(regs) || !__kernel_text_address(pc))
169                                         return false;
170
171                                 state->pc = pc;
172                                 state->sp = regs->regs[3];
173                                 state->ra = regs->regs[1];
174                                 state->first = true;
175                                 get_stack_info(state->sp, state->task, info);
176
177                                 return true;
178                         }
179                 }
180
181                 state->sp = info->next_sp;
182
183         } while (!get_stack_info(state->sp, state->task, info));
184
185         return false;
186 }
187 EXPORT_SYMBOL_GPL(unwind_next_frame);