From 52a0fb08201a8c9d07e712411afd61f3e3ece4b8 Mon Sep 17 00:00:00 2001 From: Luke Craig Date: Tue, 9 Jun 2026 20:50:56 -0400 Subject: [PATCH] target/mips: don't pass cpu_gpr[0] to TCG ops for $zero writes The Penguin guest-hypercall hook replaced two upstream guards that treated writes to $zero as NOPs: - gen_cond_move(): the 'if (rd == 0) return;' NOP guard was narrowed to only the movz $0,$0,$0 hypercall trigger, so movz/movn $0, rs, rt now falls through to tcg_gen_movcond_tl(..., cpu_gpr[0], ...). - gen_cp0()/OPC_MFC0: the 'if (rt == 0) return;' NOP guard was removed, so mfc0 $0, rd now falls through to gen_mfc0(ctx, cpu_gpr[0], ...). On MIPS cpu_gpr[0] is NULL ($zero is special-cased and never allocated as a TCG global), so both paths hand a NULL TCGv to the code generator. That does not fault at translation time; it yields a garbage temp index and silently corrupts unrelated TCG temps / guest registers, producing intermittent wild control-flow transfers in the guest (observed as random SIGSEGVs in large dynamically-linked programs such as CPython, triggered by the kernel's privileged 'mfc0 $0' CP0 hazard reads). Restore the NOP guards after the hypercall check. RISC-V and LoongArch route their hypercall results through dest_gpr()/gen_set_gpr() helpers that already handle the zero register, so only MIPS (which indexes cpu_gpr[] directly) is affected. --- target/mips/tcg/translate.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/target/mips/tcg/translate.c b/target/mips/tcg/translate.c index 6e5ebcd667..56e6f3cb80 100644 --- a/target/mips/tcg/translate.c +++ b/target/mips/tcg/translate.c @@ -2730,6 +2730,16 @@ static void gen_cond_move(DisasContext *ctx, uint32_t opc, return; } + if (rd == 0) { + /* + * A conditional move into $zero has no architectural effect. cpu_gpr[0] + * is NULL on MIPS ($zero is special-cased, never a TCG global), so the + * movcond below would hand a NULL TCGv to the code generator. Treat it + * as a NOP, as upstream did before the Penguin hypercall hook was added. + */ + return; + } + t0 = tcg_temp_new(); gen_load_gpr(t0, rt); t1 = tcg_constant_tl(0); @@ -8521,6 +8531,14 @@ static void gen_cp0(CPUMIPSState *env, DisasContext *ctx, uint32_t opc, check_cp0_enabled(ctx); switch (opc) { case OPC_MFC0: + if (rt == 0) { + /* + * mfc0 into $zero discards its result. cpu_gpr[0] is NULL on MIPS, + * so gen_mfc0() would write through a NULL TCGv; treat it as a NOP + * as upstream did before the Penguin hypercall hook was added. + */ + return; + } gen_mfc0(ctx, cpu_gpr[rt], rd, ctx->opcode & 0x7); opn = "mfc0"; break;