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
unwind_get_return_address(struct unwind_state * state)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
unwind_by_guess(struct unwind_state * state)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
unwind_by_prologue(struct unwind_state * state)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
unwind_start(struct unwind_state * state,struct task_struct * task,struct pt_regs * regs)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
115 if (regs && __kernel_text_address(regs->csr_era)) {
116 state->pc = regs->csr_era;
117 state->sp = regs->regs[3];
118 state->ra = regs->regs[1];
119 state->type = UNWINDER_PROLOGUE;
120 }
121
122 state->task = task;
123 state->first = true;
124
125 get_stack_info(state->sp, state->task, &state->stack_info);
126
127 if (!unwind_done(state) && !__kernel_text_address(state->pc))
128 unwind_next_frame(state);
129 }
130 EXPORT_SYMBOL_GPL(unwind_start);
131
unwind_next_frame(struct unwind_state * state)132 bool unwind_next_frame(struct unwind_state *state)
133 {
134 struct stack_info *info = &state->stack_info;
135 struct pt_regs *regs;
136 unsigned long pc;
137
138 if (unwind_done(state))
139 return false;
140
141 do {
142 switch (state->type) {
143 case UNWINDER_GUESS:
144 state->first = false;
145 if (unwind_by_guess(state))
146 return true;
147 break;
148
149 case UNWINDER_PROLOGUE:
150 if (unwind_by_prologue(state))
151 return true;
152
153 if (info->type == STACK_TYPE_IRQ &&
154 info->end == state->sp) {
155 regs = (struct pt_regs *)info->next_sp;
156 pc = regs->csr_era;
157
158 if (user_mode(regs) || !__kernel_text_address(pc))
159 return false;
160
161 state->pc = pc;
162 state->sp = regs->regs[3];
163 state->ra = regs->regs[1];
164 state->first = true;
165 get_stack_info(state->sp, state->task, info);
166
167 return true;
168 }
169 }
170
171 state->sp = info->next_sp;
172
173 } while (!get_stack_info(state->sp, state->task, info));
174
175 return false;
176 }
177 EXPORT_SYMBOL_GPL(unwind_next_frame);
178