~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

TOMOYO Linux Cross Reference
Linux/arch/loongarch/kernel/unwind_prologue.c

Version: ~ [ linux-6.11-rc3 ] ~ [ linux-6.10.4 ] ~ [ linux-6.9.12 ] ~ [ linux-6.8.12 ] ~ [ linux-6.7.12 ] ~ [ linux-6.6.45 ] ~ [ linux-6.5.13 ] ~ [ linux-6.4.16 ] ~ [ linux-6.3.13 ] ~ [ linux-6.2.16 ] ~ [ linux-6.1.104 ] ~ [ linux-6.0.19 ] ~ [ linux-5.19.17 ] ~ [ linux-5.18.19 ] ~ [ linux-5.17.15 ] ~ [ linux-5.16.20 ] ~ [ linux-5.15.164 ] ~ [ linux-5.14.21 ] ~ [ linux-5.13.19 ] ~ [ linux-5.12.19 ] ~ [ linux-5.11.22 ] ~ [ linux-5.10.223 ] ~ [ linux-5.9.16 ] ~ [ linux-5.8.18 ] ~ [ linux-5.7.19 ] ~ [ linux-5.6.19 ] ~ [ linux-5.5.19 ] ~ [ linux-5.4.281 ] ~ [ linux-5.3.18 ] ~ [ linux-5.2.21 ] ~ [ linux-5.1.21 ] ~ [ linux-5.0.21 ] ~ [ linux-4.20.17 ] ~ [ linux-4.19.319 ] ~ [ linux-4.18.20 ] ~ [ linux-4.17.19 ] ~ [ linux-4.16.18 ] ~ [ linux-4.15.18 ] ~ [ linux-4.14.336 ] ~ [ linux-4.13.16 ] ~ [ linux-4.12.14 ] ~ [ linux-4.11.12 ] ~ [ linux-4.10.17 ] ~ [ linux-4.9.337 ] ~ [ linux-4.4.302 ] ~ [ linux-3.10.108 ] ~ [ linux-2.6.32.71 ] ~ [ linux-2.6.0 ] ~ [ linux-2.4.37.11 ] ~ [ unix-v6-master ] ~ [ ccs-tools-1.8.9 ] ~ [ policy-sample ] ~
Architecture: ~ [ i386 ] ~ [ alpha ] ~ [ m68k ] ~ [ mips ] ~ [ ppc ] ~ [ sparc ] ~ [ sparc64 ] ~

  1 // SPDX-License-Identifier: GPL-2.0
  2 /*
  3  * Copyright (C) 2022 Loongson Technology Corporation Limited
  4  */
  5 #include <linux/cpumask.h>
  6 #include <linux/ftrace.h>
  7 #include <linux/kallsyms.h>
  8 
  9 #include <asm/inst.h>
 10 #include <asm/loongson.h>
 11 #include <asm/ptrace.h>
 12 #include <asm/setup.h>
 13 #include <asm/unwind.h>
 14 
 15 extern const int unwind_hint_ade;
 16 extern const int unwind_hint_ale;
 17 extern const int unwind_hint_bp;
 18 extern const int unwind_hint_fpe;
 19 extern const int unwind_hint_fpu;
 20 extern const int unwind_hint_lsx;
 21 extern const int unwind_hint_lasx;
 22 extern const int unwind_hint_lbt;
 23 extern const int unwind_hint_ri;
 24 extern const int unwind_hint_watch;
 25 extern unsigned long eentry;
 26 #ifdef CONFIG_NUMA
 27 extern unsigned long pcpu_handlers[NR_CPUS];
 28 #endif
 29 
 30 static inline bool scan_handlers(unsigned long entry_offset)
 31 {
 32         int idx, offset;
 33 
 34         if (entry_offset >= EXCCODE_INT_START * VECSIZE)
 35                 return false;
 36 
 37         idx = entry_offset / VECSIZE;
 38         offset = entry_offset % VECSIZE;
 39         switch (idx) {
 40         case EXCCODE_ADE:
 41                 return offset == unwind_hint_ade;
 42         case EXCCODE_ALE:
 43                 return offset == unwind_hint_ale;
 44         case EXCCODE_BP:
 45                 return offset == unwind_hint_bp;
 46         case EXCCODE_FPE:
 47                 return offset == unwind_hint_fpe;
 48         case EXCCODE_FPDIS:
 49                 return offset == unwind_hint_fpu;
 50         case EXCCODE_LSXDIS:
 51                 return offset == unwind_hint_lsx;
 52         case EXCCODE_LASXDIS:
 53                 return offset == unwind_hint_lasx;
 54         case EXCCODE_BTDIS:
 55                 return offset == unwind_hint_lbt;
 56         case EXCCODE_INE:
 57                 return offset == unwind_hint_ri;
 58         case EXCCODE_WATCH:
 59                 return offset == unwind_hint_watch;
 60         default:
 61                 return false;
 62         }
 63 }
 64 
 65 static inline bool fix_exception(unsigned long pc)
 66 {
 67 #ifdef CONFIG_NUMA
 68         int cpu;
 69 
 70         for_each_possible_cpu(cpu) {
 71                 if (!pcpu_handlers[cpu])
 72                         continue;
 73                 if (scan_handlers(pc - pcpu_handlers[cpu]))
 74                         return true;
 75         }
 76 #endif
 77         return scan_handlers(pc - eentry);
 78 }
 79 
 80 /*
 81  * As we meet ftrace_regs_entry, reset first flag like first doing
 82  * tracing. Prologue analysis will stop soon because PC is at entry.
 83  */
 84 static inline bool fix_ftrace(unsigned long pc)
 85 {
 86 #ifdef CONFIG_DYNAMIC_FTRACE
 87         return pc == (unsigned long)ftrace_call + LOONGARCH_INSN_SIZE;
 88 #else
 89         return false;
 90 #endif
 91 }
 92 
 93 static inline bool unwind_state_fixup(struct unwind_state *state)
 94 {
 95         if (!fix_exception(state->pc) && !fix_ftrace(state->pc))
 96                 return false;
 97 
 98         state->reset = true;
 99         return true;
100 }
101 
102 /*
103  * LoongArch function prologue is like follows,
104  *     [instructions not use stack var]
105  *     addi.d sp, sp, -imm
106  *     st.d   xx, sp, offset <- save callee saved regs and
107  *     st.d   yy, sp, offset    save ra if function is nest.
108  *     [others instructions]
109  */
110 static bool unwind_by_prologue(struct unwind_state *state)
111 {
112         long frame_ra = -1;
113         unsigned long frame_size = 0;
114         unsigned long size, offset, pc;
115         struct pt_regs *regs;
116         struct stack_info *info = &state->stack_info;
117         union loongarch_instruction *ip, *ip_end;
118 
119         if (state->sp >= info->end || state->sp < info->begin)
120                 return false;
121 
122         if (state->reset) {
123                 regs = (struct pt_regs *)state->sp;
124                 state->first = true;
125                 state->reset = false;
126                 state->pc = regs->csr_era;
127                 state->ra = regs->regs[1];
128                 state->sp = regs->regs[3];
129                 return true;
130         }
131 
132         /*
133          * When first is not set, the PC is a return address in the previous frame.
134          * We need to adjust its value in case overflow to the next symbol.
135          */
136         pc = state->pc - (state->first ? 0 : LOONGARCH_INSN_SIZE);
137         if (!kallsyms_lookup_size_offset(pc, &size, &offset))
138                 return false;
139 
140         ip = (union loongarch_instruction *)(pc - offset);
141         ip_end = (union loongarch_instruction *)pc;
142 
143         while (ip < ip_end) {
144                 if (is_stack_alloc_ins(ip)) {
145                         frame_size = (1 << 12) - ip->reg2i12_format.immediate;
146                         ip++;
147                         break;
148                 }
149                 ip++;
150         }
151 
152         /*
153          * Can't find stack alloc action, PC may be in a leaf function. Only the
154          * first being true is reasonable, otherwise indicate analysis is broken.
155          */
156         if (!frame_size) {
157                 if (state->first)
158                         goto first;
159 
160                 return false;
161         }
162 
163         while (ip < ip_end) {
164                 if (is_ra_save_ins(ip)) {
165                         frame_ra = ip->reg2i12_format.immediate;
166                         break;
167                 }
168                 if (is_branch_ins(ip))
169                         break;
170                 ip++;
171         }
172 
173         /* Can't find save $ra action, PC may be in a leaf function, too. */
174         if (frame_ra < 0) {
175                 if (state->first) {
176                         state->sp = state->sp + frame_size;
177                         goto first;
178                 }
179                 return false;
180         }
181 
182         state->pc = *(unsigned long *)(state->sp + frame_ra);
183         state->sp = state->sp + frame_size;
184         goto out;
185 
186 first:
187         state->pc = state->ra;
188 
189 out:
190         state->first = false;
191         return unwind_state_fixup(state) || __kernel_text_address(state->pc);
192 }
193 
194 static bool next_frame(struct unwind_state *state)
195 {
196         unsigned long pc;
197         struct pt_regs *regs;
198         struct stack_info *info = &state->stack_info;
199 
200         if (unwind_done(state))
201                 return false;
202 
203         do {
204                 if (unwind_by_prologue(state)) {
205                         state->pc = unwind_graph_addr(state, state->pc, state->sp);
206                         return true;
207                 }
208 
209                 if (info->type == STACK_TYPE_IRQ && info->end == state->sp) {
210                         regs = (struct pt_regs *)info->next_sp;
211                         pc = regs->csr_era;
212 
213                         if (user_mode(regs) || !__kernel_text_address(pc))
214                                 goto out;
215 
216                         state->first = true;
217                         state->pc = pc;
218                         state->ra = regs->regs[1];
219                         state->sp = regs->regs[3];
220                         get_stack_info(state->sp, state->task, info);
221 
222                         return true;
223                 }
224 
225                 state->sp = info->next_sp;
226 
227         } while (!get_stack_info(state->sp, state->task, info));
228 
229 out:
230         state->stack_info.type = STACK_TYPE_UNKNOWN;
231         return false;
232 }
233 
234 unsigned long unwind_get_return_address(struct unwind_state *state)
235 {
236         return __unwind_get_return_address(state);
237 }
238 EXPORT_SYMBOL_GPL(unwind_get_return_address);
239 
240 void unwind_start(struct unwind_state *state, struct task_struct *task,
241                     struct pt_regs *regs)
242 {
243         __unwind_start(state, task, regs);
244         state->type = UNWINDER_PROLOGUE;
245         state->first = true;
246 
247         /*
248          * The current PC is not kernel text address, we cannot find its
249          * relative symbol. Thus, prologue analysis will be broken. Luckily,
250          * we can use the default_next_frame().
251          */
252         if (!__kernel_text_address(state->pc)) {
253                 state->type = UNWINDER_GUESS;
254                 if (!unwind_done(state))
255                         unwind_next_frame(state);
256         }
257 }
258 EXPORT_SYMBOL_GPL(unwind_start);
259 
260 bool unwind_next_frame(struct unwind_state *state)
261 {
262         return state->type == UNWINDER_PROLOGUE ?
263                         next_frame(state) : default_next_frame(state);
264 }
265 EXPORT_SYMBOL_GPL(unwind_next_frame);
266 

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

kernel.org | git.kernel.org | LWN.net | Project Home | SVN repository | Mail admin

Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.

sflogo.php