@@ -4,6 +4,8 @@
#include <stdlib.h>
#include <linux/bitops.h>
+#include <linux/objtool.h>
+
#include <asm/inst.h>
#include <asm/orc_types.h>
@@ -274,3 +276,77 @@ void arch_initial_func_cfi_state(struct cfi_init_state *state)
state->cfa.base = CFI_SP;
state->cfa.offset = 0;
}
+
+
+int arch_update_cfi_state(struct instruction *insn,
+ struct instruction *next_insn,
+ struct cfi_state *cfi, struct stack_op *op)
+{
+ struct cfi_reg *cfa = &cfi->cfa;
+ struct cfi_reg *regs = cfi->regs;
+
+ /* stack operations don't make sense with an undefined CFA */
+ if (cfa->base == CFI_UNDEFINED) {
+ if (insn_func(insn)) {
+ WARN_FUNC("undefined stack state", insn->sec, insn->offset);
+ return -1;
+ }
+ return 0;
+ }
+
+ if (cfi->type == UNWIND_HINT_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 */
+ if (op->dest.offset)
+ 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;
+}
@@ -2230,7 +2230,7 @@ static bool has_valid_stack_frame(struct insn_state *state)
return false;
}
-static int update_cfi_state_regs(struct instruction *insn,
+int update_cfi_state_regs(struct instruction *insn,
struct cfi_state *cfi,
struct stack_op *op)
{
@@ -2255,7 +2255,7 @@ static int update_cfi_state_regs(struct instruction *insn,
return 0;
}
-static void save_reg(struct cfi_state *cfi, unsigned char reg, int base, int offset)
+void save_reg(struct cfi_state *cfi, unsigned char reg, int base, int offset)
{
if (arch_callee_saved_reg(reg) &&
cfi->regs[reg].base == CFI_UNDEFINED) {
@@ -2264,7 +2264,7 @@ static void save_reg(struct cfi_state *cfi, unsigned char reg, int base, int off
}
}
-static void restore_reg(struct cfi_state *cfi, unsigned char reg)
+void restore_reg(struct cfi_state *cfi, unsigned char reg)
{
cfi->regs[reg].base = initial_func_cfi.regs[reg].base;
cfi->regs[reg].offset = initial_func_cfi.regs[reg].offset;
@@ -2323,7 +2323,7 @@ static void restore_reg(struct cfi_state *cfi, unsigned char reg)
* 41 5d pop %r13
* c3 retq
*/
-static int update_cfi_state(struct instruction *insn,
+int __weak arch_update_cfi_state(struct instruction *insn,
struct instruction *next_insn,
struct cfi_state *cfi, struct stack_op *op)
{
@@ -2795,7 +2795,7 @@ static int handle_insn_ops(struct instruction *insn,
for (op = insn->stack_ops; op; op = op->next) {
- if (update_cfi_state(insn, next_insn, &state->cfi, op))
+ if (arch_update_cfi_state(insn, next_insn, &state->cfi, op))
return 1;
if (!insn->alt_group)
@@ -95,4 +95,7 @@ int arch_rewrite_retpolines(struct objtool_file *file);
bool arch_pc_relative_reloc(struct reloc *reloc);
+int __weak arch_update_cfi_state(struct instruction *insn,
+ struct instruction *next_insn,
+ struct cfi_state *cfi, struct stack_op *op);
#endif /* _ARCH_H */
@@ -33,4 +33,9 @@ struct alt_group {
extern unsigned long nr_insns;
+int update_cfi_state_regs(struct instruction *insn,
+ struct cfi_state *cfi,
+ struct stack_op *op);
+void save_reg(struct cfi_state *cfi, unsigned char reg, int base, int offset);
+void restore_reg(struct cfi_state *cfi, unsigned char reg);
#endif /* _CHECK_H */