Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: GPL-2.0-or-later
2#include <string.h>
3#include <objtool/check.h>
4#include <objtool/disas.h>
5#include <objtool/warn.h>
6#include <asm/inst.h>
7#include <asm/orc_types.h>
8#include <linux/objtool_types.h>
9#include <arch/elf.h>
10
11const char *arch_reg_name[CFI_NUM_REGS] = {
12 "zero", "ra", "tp", "sp",
13 "a0", "a1", "a2", "a3",
14 "a4", "a5", "a6", "a7",
15 "t0", "t1", "t2", "t3",
16 "t4", "t5", "t6", "t7",
17 "t8", "u0", "fp", "s0",
18 "s1", "s2", "s3", "s4",
19 "s5", "s6", "s7", "s8"
20};
21
22int arch_ftrace_match(const char *name)
23{
24 return !strcmp(name, "_mcount");
25}
26
27unsigned long arch_jump_destination(struct instruction *insn)
28{
29 return insn->offset + (insn->immediate << 2);
30}
31
32s64 arch_insn_adjusted_addend(struct instruction *insn, struct reloc *reloc)
33{
34 return reloc_addend(reloc);
35}
36
37bool arch_pc_relative_reloc(struct reloc *reloc)
38{
39 return false;
40}
41
42bool arch_callee_saved_reg(unsigned char reg)
43{
44 switch (reg) {
45 case CFI_RA:
46 case CFI_FP:
47 case CFI_S0 ... CFI_S8:
48 return true;
49 default:
50 return false;
51 }
52}
53
54int arch_decode_hint_reg(u8 sp_reg, int *base)
55{
56 switch (sp_reg) {
57 case ORC_REG_UNDEFINED:
58 *base = CFI_UNDEFINED;
59 break;
60 case ORC_REG_SP:
61 *base = CFI_SP;
62 break;
63 case ORC_REG_FP:
64 *base = CFI_FP;
65 break;
66 default:
67 return -1;
68 }
69
70 return 0;
71}
72
73static bool is_loongarch(const struct elf *elf)
74{
75 if (elf->ehdr.e_machine == EM_LOONGARCH)
76 return true;
77
78 ERROR("unexpected ELF machine type %d", elf->ehdr.e_machine);
79 return false;
80}
81
82#define ADD_OP(op) \
83 if (!(op = calloc(1, sizeof(*op)))) \
84 return -1; \
85 else for (*ops_list = op, ops_list = &op->next; op; op = NULL)
86
87static bool decode_insn_reg0i26_fomat(union loongarch_instruction inst,
88 struct instruction *insn)
89{
90 switch (inst.reg0i26_format.opcode) {
91 case b_op:
92 insn->type = INSN_JUMP_UNCONDITIONAL;
93 insn->immediate = sign_extend64(inst.reg0i26_format.immediate_h << 16 |
94 inst.reg0i26_format.immediate_l, 25);
95 break;
96 case bl_op:
97 insn->type = INSN_CALL;
98 insn->immediate = sign_extend64(inst.reg0i26_format.immediate_h << 16 |
99 inst.reg0i26_format.immediate_l, 25);
100 break;
101 default:
102 return false;
103 }
104
105 return true;
106}
107
108static bool decode_insn_reg1i21_fomat(union loongarch_instruction inst,
109 struct instruction *insn)
110{
111 switch (inst.reg1i21_format.opcode) {
112 case beqz_op:
113 case bnez_op:
114 case bceqz_op:
115 insn->type = INSN_JUMP_CONDITIONAL;
116 insn->immediate = sign_extend64(inst.reg1i21_format.immediate_h << 16 |
117 inst.reg1i21_format.immediate_l, 20);
118 break;
119 default:
120 return false;
121 }
122
123 return true;
124}
125
126static bool decode_insn_reg2i12_fomat(union loongarch_instruction inst,
127 struct instruction *insn,
128 struct stack_op **ops_list,
129 struct stack_op *op)
130{
131 switch (inst.reg2i12_format.opcode) {
132 case addid_op:
133 if ((inst.reg2i12_format.rd == CFI_SP) || (inst.reg2i12_format.rj == CFI_SP)) {
134 /* addi.d sp,sp,si12 or addi.d fp,sp,si12 or addi.d sp,fp,si12 */
135 insn->immediate = sign_extend64(inst.reg2i12_format.immediate, 11);
136 ADD_OP(op) {
137 op->src.type = OP_SRC_ADD;
138 op->src.reg = inst.reg2i12_format.rj;
139 op->src.offset = insn->immediate;
140 op->dest.type = OP_DEST_REG;
141 op->dest.reg = inst.reg2i12_format.rd;
142 }
143 }
144 if ((inst.reg2i12_format.rd == CFI_SP) && (inst.reg2i12_format.rj == CFI_FP)) {
145 /* addi.d sp,fp,si12 */
146 struct symbol *func = find_func_containing(insn->sec, insn->offset);
147
148 if (!func)
149 return false;
150
151 func->frame_pointer = true;
152 }
153 break;
154 case ldd_op:
155 if (inst.reg2i12_format.rj == CFI_SP) {
156 /* ld.d rd,sp,si12 */
157 insn->immediate = sign_extend64(inst.reg2i12_format.immediate, 11);
158 ADD_OP(op) {
159 op->src.type = OP_SRC_REG_INDIRECT;
160 op->src.reg = CFI_SP;
161 op->src.offset = insn->immediate;
162 op->dest.type = OP_DEST_REG;
163 op->dest.reg = inst.reg2i12_format.rd;
164 }
165 }
166 break;
167 case std_op:
168 if (inst.reg2i12_format.rj == CFI_SP) {
169 /* st.d rd,sp,si12 */
170 insn->immediate = sign_extend64(inst.reg2i12_format.immediate, 11);
171 ADD_OP(op) {
172 op->src.type = OP_SRC_REG;
173 op->src.reg = inst.reg2i12_format.rd;
174 op->dest.type = OP_DEST_REG_INDIRECT;
175 op->dest.reg = CFI_SP;
176 op->dest.offset = insn->immediate;
177 }
178 }
179 break;
180 case andi_op:
181 if (inst.reg2i12_format.rd == 0 &&
182 inst.reg2i12_format.rj == 0 &&
183 inst.reg2i12_format.immediate == 0)
184 /* andi r0,r0,0 */
185 insn->type = INSN_NOP;
186 break;
187 default:
188 return false;
189 }
190
191 return true;
192}
193
194static bool decode_insn_reg2i14_fomat(union loongarch_instruction inst,
195 struct instruction *insn,
196 struct stack_op **ops_list,
197 struct stack_op *op)
198{
199 switch (inst.reg2i14_format.opcode) {
200 case ldptrd_op:
201 if (inst.reg2i14_format.rj == CFI_SP) {
202 /* ldptr.d rd,sp,si14 */
203 insn->immediate = sign_extend64(inst.reg2i14_format.immediate, 13);
204 ADD_OP(op) {
205 op->src.type = OP_SRC_REG_INDIRECT;
206 op->src.reg = CFI_SP;
207 op->src.offset = insn->immediate;
208 op->dest.type = OP_DEST_REG;
209 op->dest.reg = inst.reg2i14_format.rd;
210 }
211 }
212 break;
213 case stptrd_op:
214 if (inst.reg2i14_format.rj == CFI_SP) {
215 /* stptr.d ra,sp,0 */
216 if (inst.reg2i14_format.rd == LOONGARCH_GPR_RA &&
217 inst.reg2i14_format.immediate == 0)
218 break;
219
220 /* stptr.d rd,sp,si14 */
221 insn->immediate = sign_extend64(inst.reg2i14_format.immediate, 13);
222 ADD_OP(op) {
223 op->src.type = OP_SRC_REG;
224 op->src.reg = inst.reg2i14_format.rd;
225 op->dest.type = OP_DEST_REG_INDIRECT;
226 op->dest.reg = CFI_SP;
227 op->dest.offset = insn->immediate;
228 }
229 }
230 break;
231 default:
232 return false;
233 }
234
235 return true;
236}
237
238static bool decode_insn_reg2i16_fomat(union loongarch_instruction inst,
239 struct instruction *insn)
240{
241 switch (inst.reg2i16_format.opcode) {
242 case jirl_op:
243 if (inst.reg2i16_format.rd == 0 &&
244 inst.reg2i16_format.rj == CFI_RA &&
245 inst.reg2i16_format.immediate == 0) {
246 /* jirl r0,ra,0 */
247 insn->type = INSN_RETURN;
248 } else if (inst.reg2i16_format.rd == CFI_RA) {
249 /* jirl ra,rj,offs16 */
250 insn->type = INSN_CALL_DYNAMIC;
251 } else if (inst.reg2i16_format.rd == CFI_A0 &&
252 inst.reg2i16_format.immediate == 0) {
253 /*
254 * jirl a0,t0,0
255 * this is a special case in loongarch_suspend_enter,
256 * just treat it as a call instruction.
257 */
258 insn->type = INSN_CALL_DYNAMIC;
259 } else if (inst.reg2i16_format.rd == 0 &&
260 inst.reg2i16_format.immediate == 0) {
261 /* jirl r0,rj,0 */
262 insn->type = INSN_JUMP_DYNAMIC;
263 } else if (inst.reg2i16_format.rd == 0 &&
264 inst.reg2i16_format.immediate != 0) {
265 /*
266 * jirl r0,t0,12
267 * this is a rare case in JUMP_VIRT_ADDR,
268 * just ignore it due to it is harmless for tracing.
269 */
270 break;
271 } else {
272 /* jirl rd,rj,offs16 */
273 insn->type = INSN_JUMP_UNCONDITIONAL;
274 insn->immediate = sign_extend64(inst.reg2i16_format.immediate, 15);
275 }
276 break;
277 case beq_op:
278 case bne_op:
279 case blt_op:
280 case bge_op:
281 case bltu_op:
282 case bgeu_op:
283 insn->type = INSN_JUMP_CONDITIONAL;
284 insn->immediate = sign_extend64(inst.reg2i16_format.immediate, 15);
285 break;
286 default:
287 return false;
288 }
289
290 return true;
291}
292
293static bool decode_insn_reg3_fomat(union loongarch_instruction inst,
294 struct instruction *insn)
295{
296 switch (inst.reg3_format.opcode) {
297 case amswapw_op:
298 if (inst.reg3_format.rd == LOONGARCH_GPR_ZERO &&
299 inst.reg3_format.rk == LOONGARCH_GPR_RA &&
300 inst.reg3_format.rj == LOONGARCH_GPR_ZERO) {
301 /* amswap.w $zero, $ra, $zero */
302 insn->type = INSN_BUG;
303 }
304 break;
305 default:
306 return false;
307 }
308
309 return true;
310}
311
312int arch_decode_instruction(struct objtool_file *file, const struct section *sec,
313 unsigned long offset, unsigned int maxlen,
314 struct instruction *insn)
315{
316 struct stack_op **ops_list = &insn->stack_ops;
317 const struct elf *elf = file->elf;
318 struct stack_op *op = NULL;
319 union loongarch_instruction inst;
320
321 if (!is_loongarch(elf))
322 return -1;
323
324 if (maxlen < LOONGARCH_INSN_SIZE)
325 return 0;
326
327 insn->len = LOONGARCH_INSN_SIZE;
328 insn->type = INSN_OTHER;
329 insn->immediate = 0;
330
331 inst = *(union loongarch_instruction *)(sec->data->d_buf + offset);
332
333 if (decode_insn_reg0i26_fomat(inst, insn))
334 return 0;
335 if (decode_insn_reg1i21_fomat(inst, insn))
336 return 0;
337 if (decode_insn_reg2i12_fomat(inst, insn, ops_list, op))
338 return 0;
339 if (decode_insn_reg2i14_fomat(inst, insn, ops_list, op))
340 return 0;
341 if (decode_insn_reg2i16_fomat(inst, insn))
342 return 0;
343 if (decode_insn_reg3_fomat(inst, insn))
344 return 0;
345
346 if (inst.word == 0) {
347 /* andi $zero, $zero, 0x0 */
348 insn->type = INSN_NOP;
349 } else if (inst.reg0i15_format.opcode == break_op &&
350 inst.reg0i15_format.immediate == 0x0) {
351 /* break 0x0 */
352 insn->type = INSN_TRAP;
353 } else if (inst.reg0i15_format.opcode == break_op &&
354 inst.reg0i15_format.immediate == 0x1) {
355 /* break 0x1 */
356 insn->type = INSN_BUG;
357 } else if (inst.reg2_format.opcode == ertn_op) {
358 /* ertn */
359 insn->type = INSN_RETURN;
360 }
361
362 return 0;
363}
364
365const char *arch_nop_insn(int len)
366{
367 static u32 nop;
368
369 if (len != LOONGARCH_INSN_SIZE) {
370 ERROR("invalid NOP size: %d\n", len);
371 return NULL;
372 }
373
374 nop = LOONGARCH_INSN_NOP;
375
376 return (const char *)&nop;
377}
378
379const char *arch_ret_insn(int len)
380{
381 static u32 ret;
382
383 if (len != LOONGARCH_INSN_SIZE) {
384 ERROR("invalid RET size: %d\n", len);
385 return NULL;
386 }
387
388 emit_jirl((union loongarch_instruction *)&ret, LOONGARCH_GPR_RA, LOONGARCH_GPR_ZERO, 0);
389
390 return (const char *)&ret;
391}
392
393void arch_initial_func_cfi_state(struct cfi_init_state *state)
394{
395 int i;
396
397 for (i = 0; i < CFI_NUM_REGS; i++) {
398 state->regs[i].base = CFI_UNDEFINED;
399 state->regs[i].offset = 0;
400 }
401
402 /* initial CFA (call frame address) */
403 state->cfa.base = CFI_SP;
404 state->cfa.offset = 0;
405}
406
407unsigned int arch_reloc_size(struct reloc *reloc)
408{
409 switch (reloc_type(reloc)) {
410 case R_LARCH_32:
411 case R_LARCH_32_PCREL:
412 return 4;
413 default:
414 return 8;
415 }
416}
417
418unsigned long arch_jump_table_sym_offset(struct reloc *reloc, struct reloc *table)
419{
420 switch (reloc_type(reloc)) {
421 case R_LARCH_32_PCREL:
422 case R_LARCH_64_PCREL:
423 return reloc->sym->offset + reloc_addend(reloc) -
424 (reloc_offset(reloc) - reloc_offset(table));
425 default:
426 return reloc->sym->offset + reloc_addend(reloc);
427 }
428}
429
430#ifdef DISAS
431
432int arch_disas_info_init(struct disassemble_info *dinfo)
433{
434 return disas_info_init(dinfo, bfd_arch_loongarch,
435 bfd_mach_loongarch32, bfd_mach_loongarch64,
436 NULL);
437}
438
439#endif /* DISAS */