From bb97240df62bd46302f10993cb7ab8d9c9b037d1 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Mon, 8 Oct 2018 14:17:19 -0400 Subject: [PATCH] target/arm: Add v8M stack checks for LDRD/STRD (imm) Add the v8M stack checks for: * LDRD (immediate) * STRD (immediate) Loads and stores are more complicated than ADD/SUB/MOV, because we must ensure that memory accesses below the stack limit are not performed, so we can't simply do the check when we actually update SP. For these instructions, if the stack limit check triggers we must not: * perform any memory access below the SP limit * update PC, SP or the load/store base register but it is IMPDEF whether we: * perform any accesses above or equal to the SP limit * update destination registers for loads For QEMU we choose to always check the limit before doing any other part of the load or store, so we won't update any registers or perform any memory accesses. It is UNKNOWN whether the limit check triggers for a load or store where the initial SP value is below the limit and one of the stores would be below the limit, but the writeback moves SP to above the limit. For QEMU we choose to trigger the check in this situation. Note that limit checks happen only for loads and stores which update SP via writeback; they do not happen for loads and stores which simply use SP as a base register. Backports commit 910d7692e5b60f2c2d08cc3d6d36076e85b6a69d from qemu --- qemu/target/arm/translate.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/qemu/target/arm/translate.c b/qemu/target/arm/translate.c index 52a2bda8..1a5b618f 100644 --- a/qemu/target/arm/translate.c +++ b/qemu/target/arm/translate.c @@ -10466,6 +10466,8 @@ static void disas_thumb2_insn(DisasContext *s, uint32_t insn) * 0b1111_1001_x11x_xxxx_xxxx_xxxx_xxxx_xxxx * - load/store dual (pre-indexed) */ + bool wback = extract32(insn, 21, 1); + if (rn == 15) { if (insn & (1 << 21)) { /* UNPREDICTABLE */ @@ -10477,8 +10479,29 @@ static void disas_thumb2_insn(DisasContext *s, uint32_t insn) addr = load_reg(s, rn); } offset = (insn & 0xff) * 4; - if ((insn & (1 << 23)) == 0) + if ((insn & (1 << 23)) == 0) { offset = 0-offset; + } + + if (s->v8m_stackcheck && rn == 13 && wback) { + /* + * Here 'addr' is the current SP; if offset is +ve we're + * moving SP up, else down. It is UNKNOWN whether the limit + * check triggers when SP starts below the limit and ends + * up above it; check whichever of the current and final + * SP is lower, so QEMU will trigger in that situation. + */ + if ((int32_t)offset < 0) { + TCGv_i32 newsp = tcg_temp_new_i32(tcg_ctx); + + tcg_gen_addi_i32(tcg_ctx, newsp, addr, offset); + gen_helper_v8m_stackcheck(tcg_ctx, tcg_ctx->cpu_env, newsp); + tcg_temp_free_i32(tcg_ctx, newsp); + } else { + gen_helper_v8m_stackcheck(tcg_ctx, tcg_ctx->cpu_env, addr); + } + } + if (insn & (1 << 24)) { tcg_gen_addi_i32(tcg_ctx, addr, addr, offset); offset = 0;