xref: /linux/tools/objtool/arch/loongarch/decode.c (revision 7fc2cd2e4b398c57c9cf961cfea05eadbf34c05c)
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 const 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 
22 int arch_ftrace_match(const char *name)
23 {
24 	return !strcmp(name, "_mcount");
25 }
26 
27 unsigned long arch_jump_destination(struct instruction *insn)
28 {
29 	return insn->offset + (insn->immediate << 2);
30 }
31 
32 s64 arch_insn_adjusted_addend(struct instruction *insn, struct reloc *reloc)
33 {
34 	return reloc_addend(reloc);
35 }
36 
37 bool arch_pc_relative_reloc(struct reloc *reloc)
38 {
39 	return false;
40 }
41 
42 bool 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 
54 int 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 
73 static 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 
87 static 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 
108 static 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 
126 static 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 
194 static 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 
238 static 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 
293 static 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 
312 int 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 
365 const 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 
379 const 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 
393 void 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 
407 unsigned 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 
418 unsigned 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 
432 int 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 */
440