mirror of
https://gitee.com/open-loongarch/linux-5.10.git
synced 2026-10-27 13:37:12 +08:00
427 lines
10 KiB
C
427 lines
10 KiB
C
/*
|
|
* Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com>
|
|
* Copyright (C) 2020 Loongson Technology Corporation Limited
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#define unlikely(cond) (cond)
|
|
#include <asm/inst.h>
|
|
#include <asm/orc_types.h>
|
|
|
|
#include "../../check.h"
|
|
#include "../../elf.h"
|
|
#include "../../arch.h"
|
|
#include "../../warn.h"
|
|
|
|
#ifndef LOONGARCH_INSN_SIZE
|
|
#define LOONGARCH_INSN_SIZE sizeof(union loongarch_instruction)
|
|
#endif
|
|
|
|
#ifndef EM_LOONGARCH
|
|
#define EM_LOONGARCH 258
|
|
#endif
|
|
|
|
#define to_cfi_reg(reg) (reg)
|
|
|
|
#define signex(x, symbol_idx) \
|
|
({ \
|
|
unsigned long ___u64; \
|
|
___u64 = ((x) & (1UL << symbol_idx)) ? \
|
|
~((1UL << (symbol_idx + 1)) - 1) | (x) : \
|
|
((1UL << (symbol_idx + 1)) - 1) & (x); \
|
|
___u64; \
|
|
})
|
|
|
|
const unsigned int code_nop = 0x03400000;
|
|
|
|
static int is_loongarch(const struct elf *elf)
|
|
{
|
|
if (elf->ehdr.e_machine == EM_LOONGARCH)
|
|
return 1;
|
|
|
|
WARN("unexpected ELF machine type %x\n", elf->ehdr.e_machine);
|
|
return 0;
|
|
}
|
|
|
|
bool arch_callee_saved_reg(unsigned char reg)
|
|
{
|
|
switch (reg) {
|
|
case CFI_S0 ... CFI_S8:
|
|
case CFI_FP:
|
|
case CFI_RA:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
unsigned long arch_dest_reloc_offset(int addend)
|
|
{
|
|
return addend;
|
|
}
|
|
|
|
unsigned long arch_jump_destination(struct instruction *insn)
|
|
{
|
|
return insn->offset + insn->immediate * 4;
|
|
}
|
|
|
|
#define ADD_OP(op) \
|
|
if (!(op = calloc(1, sizeof(*op)))) \
|
|
return -1; \
|
|
else for (list_add_tail(&op->list, ops_list); op; op = NULL)
|
|
|
|
int arch_decode_instruction(const struct elf *elf, const struct section *sec,
|
|
unsigned long offset, unsigned int maxlen,
|
|
unsigned int *len, enum insn_type *type,
|
|
unsigned long *immediate,
|
|
struct list_head *ops_list)
|
|
{
|
|
union loongarch_instruction code;
|
|
struct stack_op *op = NULL;
|
|
|
|
if (!is_loongarch(elf))
|
|
return -1;
|
|
|
|
if (maxlen < LOONGARCH_INSN_SIZE)
|
|
return 0;
|
|
|
|
*len = LOONGARCH_INSN_SIZE;
|
|
*type = INSN_OTHER;
|
|
*immediate = 0;
|
|
|
|
code = *(union loongarch_instruction *)(sec->data->d_buf + offset);
|
|
|
|
/* For some where we .fill 0 and we cannot execute it. */
|
|
if (code.word == 0)
|
|
*type = INSN_NOP;
|
|
|
|
switch (code.reg2i12_format.opcode) {
|
|
case addid_op:
|
|
if ((code.reg2i12_format.rj == CFI_SP) || (code.reg2i12_format.rd == CFI_SP)) {
|
|
/* addi.d reg1,reg2,imm */
|
|
*immediate = signex(code.reg2i12_format.simmediate, 11);
|
|
ADD_OP(op) {
|
|
op->src.type = OP_SRC_ADD;
|
|
op->src.reg = to_cfi_reg(code.reg2i12_format.rj);
|
|
op->src.offset = *immediate;
|
|
op->dest.type = OP_DEST_REG;
|
|
op->dest.reg = to_cfi_reg(code.reg2i12_format.rd);
|
|
}
|
|
}
|
|
break;
|
|
case std_op:
|
|
if ((code.reg2i12_format.rj == CFI_SP)) {
|
|
/* st.d reg,sp,imm */
|
|
*immediate = signex(code.reg2i12_format.simmediate, 11);
|
|
ADD_OP(op) {
|
|
op->src.type = OP_SRC_REG;
|
|
op->src.reg = to_cfi_reg(code.reg2i12_format.rd);
|
|
op->dest.type = OP_DEST_REG_INDIRECT;
|
|
op->dest.reg = CFI_SP;
|
|
op->dest.offset = *immediate;
|
|
}
|
|
}
|
|
break;
|
|
case ldd_op:
|
|
if ((code.reg2i12_format.rj == CFI_SP)) {
|
|
/* ld.d reg,sp,imm */
|
|
*immediate = signex(code.reg2i12_format.simmediate, 11);
|
|
ADD_OP(op) {
|
|
op->src.type = OP_SRC_REG_INDIRECT;
|
|
op->src.reg = CFI_SP;
|
|
op->src.offset = *immediate;
|
|
op->dest.type = OP_DEST_REG;
|
|
op->dest.reg = to_cfi_reg(code.reg2i12_format.rd);
|
|
}
|
|
}
|
|
break;
|
|
case andi_op:
|
|
if (code.reg2i12_format.simmediate == 0 &&
|
|
code.reg2i12_format.rj == 0 &&
|
|
code.reg2i12_format.rd == 0)
|
|
/* nop */
|
|
*type = INSN_NOP;
|
|
break;
|
|
default:
|
|
switch (code.reg2i16_format.opcode) {
|
|
case jirl_op:
|
|
if (code.reg2i16_format.simmediate) {
|
|
WARN("unexpected insn type 0x%lx\n", offset);
|
|
return -1;
|
|
}
|
|
if (code.reg2i16_format.rj == CFI_RA &&
|
|
code.reg2i16_format.rd == 0)
|
|
/* jr ra */
|
|
*type = INSN_RETURN;
|
|
else if (code.reg2i16_format.rd == CFI_RA)
|
|
/* jalr reg */
|
|
*type = INSN_CALL_DYNAMIC;
|
|
else if (code.reg2i16_format.rd == 0)
|
|
/* jr reg */
|
|
*type = INSN_JUMP_DYNAMIC;
|
|
break;
|
|
case beq_op:
|
|
case bne_op:
|
|
case blt_op:
|
|
case bge_op:
|
|
case bltu_op:
|
|
case bgeu_op:
|
|
*immediate = signex(code.reg2i16_format.simmediate, 15);
|
|
*type = INSN_JUMP_CONDITIONAL;
|
|
break;
|
|
case beqz_op:
|
|
case bnez_op:
|
|
*immediate = signex(code.reg1i21_format.simmediate_h << 16 |
|
|
code.reg1i21_format.simmediate_l, 20);
|
|
*type = INSN_JUMP_CONDITIONAL;
|
|
break;
|
|
case bl_op:
|
|
*type = INSN_CALL;
|
|
break;
|
|
case b_op:
|
|
*type = INSN_JUMP_UNCONDITIONAL;
|
|
break;
|
|
default:
|
|
if (code.reg2i14_format.opcode == stptrd_op &&
|
|
code.reg2i14_format.rj == CFI_SP) {
|
|
/* stptr.d reg,sp,imm */
|
|
*immediate = signex(code.reg2i14_format.simmediate, 13);
|
|
ADD_OP(op) {
|
|
op->src.type = OP_SRC_REG;
|
|
op->src.reg = to_cfi_reg(code.reg2i14_format.rd);
|
|
op->dest.type = OP_DEST_REG_INDIRECT;
|
|
op->dest.reg = CFI_SP;
|
|
op->dest.offset = *immediate;
|
|
}
|
|
} else if (code.reg2i14_format.opcode == ldptrd_op &&
|
|
code.reg2i14_format.rj == CFI_SP) {
|
|
/* ldptr.d reg,sp,imm */
|
|
*immediate = signex(code.reg2i14_format.simmediate, 13);
|
|
ADD_OP(op) {
|
|
op->src.type = OP_SRC_REG_INDIRECT;
|
|
op->src.reg = CFI_SP;
|
|
op->src.offset = *immediate;
|
|
op->dest.type = OP_DEST_REG;
|
|
op->dest.reg = to_cfi_reg(code.reg2i14_format.rd);
|
|
}
|
|
} else if (code.reg0i15_format.opcode == break_op) {
|
|
/* break */
|
|
*type = INSN_BUG;
|
|
} else if (code.reg2_format.opcode == ertn_op) {
|
|
/* ertn */
|
|
*type = INSN_RETURN;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void arch_initial_func_cfi_state(struct cfi_init_state *state)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < CFI_NUM_REGS; i++) {
|
|
state->regs[i].base = CFI_UNDEFINED;
|
|
state->regs[i].offset = 0;
|
|
}
|
|
|
|
/* initial CFA (call frame address) */
|
|
state->cfa.base = CFI_SP;
|
|
state->cfa.offset = 0;
|
|
}
|
|
|
|
int arch_decode_hint_reg(struct instruction *insn, u8 sp_reg)
|
|
{
|
|
struct cfi_reg *cfa = &insn->cfi.cfa;
|
|
|
|
switch (sp_reg) {
|
|
case ORC_REG_UNDEFINED:
|
|
cfa->base = CFI_UNDEFINED;
|
|
break;
|
|
case ORC_REG_SP:
|
|
cfa->base = CFI_SP;
|
|
break;
|
|
case ORC_REG_FP:
|
|
cfa->base = CFI_FP;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool arch_has_valid_stack_frame(struct insn_state *state)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
static int update_cfi_state_regs(struct instruction *insn,
|
|
struct cfi_state *cfi,
|
|
struct stack_op *op)
|
|
{
|
|
struct cfi_reg *cfa = &cfi->cfa;
|
|
|
|
if (cfa->base != CFI_SP && cfa->base != CFI_SP_INDIRECT)
|
|
return 0;
|
|
|
|
/* addi.d sp, sp, imm */
|
|
if (op->dest.type == OP_DEST_REG && op->src.type == OP_SRC_ADD &&
|
|
op->dest.reg == CFI_SP && op->src.reg == CFI_SP)
|
|
cfa->offset -= op->src.offset;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int update_cfi_state(struct instruction *insn, struct cfi_state *cfi,
|
|
struct stack_op *op)
|
|
{
|
|
struct cfi_reg *cfa = &cfi->cfa;
|
|
struct cfi_reg *regs = cfi->regs;
|
|
|
|
if (cfa->base == CFI_UNDEFINED) {
|
|
if (insn->func) {
|
|
WARN_FUNC("undefined stack state", insn->sec, insn->offset);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (cfi->type == ORC_TYPE_REGS)
|
|
return update_cfi_state_regs(insn, cfi, op);
|
|
|
|
switch (op->dest.type) {
|
|
case OP_DEST_REG:
|
|
switch (op->src.type) {
|
|
case OP_SRC_ADD:
|
|
if (op->dest.reg == CFI_SP && op->src.reg == CFI_SP) {
|
|
/* addi.d sp, sp, imm */
|
|
cfi->stack_size -= op->src.offset;
|
|
if (cfa->base == CFI_SP)
|
|
cfa->offset -= op->src.offset;
|
|
} else if (op->dest.reg == CFI_FP && op->src.reg == CFI_SP) {
|
|
/* addi.d fp, sp, imm */
|
|
if (cfa->base == CFI_SP && cfa->offset == op->src.offset) {
|
|
cfa->base = CFI_FP;
|
|
cfa->offset = 0;
|
|
}
|
|
} else if (op->dest.reg == CFI_SP && op->src.reg == CFI_FP) {
|
|
/* addi.d sp, fp, imm */
|
|
if (cfa->base == CFI_FP && cfa->offset == 0) {
|
|
cfa->base = CFI_SP;
|
|
cfa->offset = -op->src.offset;
|
|
}
|
|
}
|
|
break;
|
|
case OP_SRC_REG_INDIRECT:
|
|
/* ld.d _reg, sp, imm */
|
|
if (op->src.reg == CFI_SP &&
|
|
op->src.offset == (regs[op->dest.reg].offset + cfi->stack_size)) {
|
|
restore_reg(cfi, op->dest.reg);
|
|
/* Gcc may not restore sp, we adjust it directly. */
|
|
if (cfa->base == CFI_FP && cfa->offset == 0) {
|
|
cfa->base = CFI_SP;
|
|
cfa->offset = cfi->stack_size;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case OP_DEST_REG_INDIRECT:
|
|
if (op->src.type == OP_SRC_REG) {
|
|
/* st.d _reg, sp, imm */
|
|
save_reg(cfi, op->src.reg, CFI_CFA, op->dest.offset - cfi->stack_size);
|
|
}
|
|
break;
|
|
default:
|
|
WARN_FUNC("unknown stack-related instruction", insn->sec, insn->offset);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int arch_handle_insn_ops(struct instruction *insn, struct insn_state *state)
|
|
{
|
|
struct stack_op *op;
|
|
|
|
list_for_each_entry(op, &insn->stack_ops, list) {
|
|
int res;
|
|
|
|
res = update_cfi_state(insn, &state->cfi, op);
|
|
if (res)
|
|
return res;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifndef R_LARCH_MARK_LA
|
|
#define R_LARCH_MARK_LA 20
|
|
#endif
|
|
|
|
void arch_try_find_call(struct list_head *p_orbit_list, struct objtool_file *file,
|
|
struct symbol *func, struct instruction *insn)
|
|
{
|
|
int count = 0, reg;
|
|
struct instruction *orbit;
|
|
struct reloc *reloc;
|
|
union loongarch_instruction code;
|
|
|
|
if (list_empty(p_orbit_list)) {
|
|
WARN_FUNC("BUG: why do I have no insn track?", insn->sec, insn->offset);
|
|
return;
|
|
}
|
|
|
|
if (func_last_orbit(p_orbit_list) != insn) {
|
|
WARN_FUNC("BUG: insn is not expected.", insn->sec, insn->offset);
|
|
return;
|
|
}
|
|
|
|
code = *(union loongarch_instruction *)(insn->sec->data->d_buf + insn->offset);
|
|
if (code.reg2i16_format.opcode != jirl_op || code.reg2i16_format.rd != CFI_RA) {
|
|
WARN_FUNC("BUG: first insn track is not expected.", insn->sec, insn->offset);
|
|
return;
|
|
}
|
|
|
|
reg = code.reg2i16_format.rj;
|
|
list_for_each_entry(orbit, p_orbit_list, orbit_node) {
|
|
count++;
|
|
/* jirl, la.abs (== lu12i.w, ori, lu32i.d, lu52i.d) */
|
|
if (count == 5)
|
|
break;
|
|
}
|
|
|
|
if (count != 5)
|
|
return;
|
|
|
|
code = *(union loongarch_instruction *)(orbit->sec->data->d_buf + orbit->offset);
|
|
if (code.reg1i20_format.opcode != lu12iw_op || code.reg1i20_format.rd != reg)
|
|
return;
|
|
|
|
reloc = find_reloc_by_dest(file->elf, orbit->sec, orbit->offset);
|
|
if (!reloc)
|
|
return;
|
|
|
|
if (reloc->type != R_LARCH_MARK_LA)
|
|
return;
|
|
|
|
insn->type = INSN_CALL;
|
|
insn->call_dest = reloc->sym;
|
|
}
|
|
|
|
const char *arch_nop_insn(int len)
|
|
{
|
|
if (len != LOONGARCH_INSN_SIZE)
|
|
WARN("invalid NOP size: %d\n", len);
|
|
|
|
return (const char *)&code_nop;
|
|
}
|