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