summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--target/hppa/translate.c85
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