diff options
-rw-r--r-- | target/hppa/translate.c | 85 |
1 files changed, 81 insertions, 4 deletions
diff --git a/target/hppa/translate.c b/target/hppa/translate.c index 1d0976f756..14fe4bbd2c 100644 --- a/target/hppa/translate.c +++ b/target/hppa/translate.c @@ -1123,6 +1123,64 @@ static ExitStatus do_ibranch(DisasContext *ctx, TCGv dest, return NO_EXIT; } +/* On Linux, page zero is normally marked execute only + gateway. + Therefore normal read or write is supposed to fail, but specific + offsets have kernel code mapped to raise permissions to implement + system calls. Handling this via an explicit check here, rather + in than the "be disp(sr2,r0)" instruction that probably sent us + here, is the easiest way to handle the branch delay slot on the + aforementioned BE. */ +static ExitStatus do_page_zero(DisasContext *ctx) +{ + /* If by some means we get here with PSW[N]=1, that implies that + the B,GATE instruction would be skipped, and we'd fault on the + next insn within the privilaged page. */ + switch (ctx->null_cond.c) { + case TCG_COND_NEVER: + break; + case TCG_COND_ALWAYS: + tcg_gen_movi_tl(cpu_psw_n, 0); + goto do_sigill; + default: + /* Since this is always the first (and only) insn within the + TB, we should know the state of PSW[N] from TB->FLAGS. */ + g_assert_not_reached(); + } + + /* Check that we didn't arrive here via some means that allowed + non-sequential instruction execution. Normally the PSW[B] bit + detects this by disallowing the B,GATE instruction to execute + under such conditions. */ + if (ctx->iaoq_b != ctx->iaoq_f + 4) { + goto do_sigill; + } + + switch (ctx->iaoq_f) { + case 0x00: /* Null pointer call */ + gen_excp_1(EXCP_SIGSEGV); + return EXIT_NORETURN; + + case 0xb0: /* LWS */ + gen_excp_1(EXCP_SYSCALL_LWS); + return EXIT_NORETURN; + + case 0xe0: /* SET_THREAD_POINTER */ + tcg_gen_mov_tl(cpu_cr27, cpu_gr[26]); + tcg_gen_mov_tl(cpu_iaoq_f, cpu_gr[31]); + tcg_gen_addi_tl(cpu_iaoq_b, cpu_iaoq_f, 4); + return EXIT_IAQ_N_UPDATED; + + case 0x100: /* SYSCALL */ + gen_excp_1(EXCP_SYSCALL); + return EXIT_NORETURN; + + default: + do_sigill: + gen_excp_1(EXCP_SIGILL); + return EXIT_NORETURN; + } +} + static ExitStatus trans_nop(DisasContext *ctx, uint32_t insn, const DisasInsn *di) { @@ -1884,7 +1942,10 @@ void gen_intermediate_code(CPUHPPAState *env, struct TranslationBlock *tb) gen_io_start(); } - { + if (ctx.iaoq_f < TARGET_PAGE_SIZE) { + ret = do_page_zero(&ctx); + assert(ret != NO_EXIT); + } else { /* Always fetch the insn, even if nullified, so that we check the page permissions for execute. */ uint32_t insn = cpu_ldl_code(env, ctx.iaoq_f); @@ -1986,9 +2047,25 @@ void gen_intermediate_code(CPUHPPAState *env, struct TranslationBlock *tb) if (qemu_loglevel_mask(CPU_LOG_TB_IN_ASM) && qemu_log_in_addr_range(tb->pc)) { qemu_log_lock(); - qemu_log("IN: %s\n", lookup_symbol(tb->pc)); - log_target_disas(cs, tb->pc, tb->size, 1); - qemu_log("\n"); + switch (tb->pc) { + case 0x00: + qemu_log("IN:\n0x00000000: (null)\n\n"); + break; + case 0xb0: + qemu_log("IN:\n0x000000b0: light-weight-syscall\n\n"); + break; + case 0xe0: + qemu_log("IN:\n0x000000e0: set-thread-pointer-syscall\n\n"); + break; + case 0x100: + qemu_log("IN:\n0x00000100: syscall\n\n"); + break; + default: + qemu_log("IN: %s\n", lookup_symbol(tb->pc)); + log_target_disas(cs, tb->pc, tb->size, 1); + qemu_log("\n"); + break; + } qemu_log_unlock(); } #endif |