target-arm: Ignore attempts to set invalid modes in CPSR

Ignore attempts to set the CPSR mode field to an invalid value.
This is UNPREDICTABLE, but we should not cpu_abort() for things
a malicious guest (or a confused user on the gdbstub interface)
can provoke.

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
diff --git a/target-arm/helper.c b/target-arm/helper.c
index 5b994d5..261d547 100644
--- a/target-arm/helper.c
+++ b/target-arm/helper.c
@@ -463,6 +463,26 @@
     g_free(env);
 }
 
+static int bad_mode_switch(CPUState *env, int mode)
+{
+    /* Return true if it is not valid for us to switch to
+     * this CPU mode (ie all the UNPREDICTABLE cases in
+     * the ARM ARM CPSRWriteByInstr pseudocode).
+     */
+    switch (mode) {
+    case ARM_CPU_MODE_USR:
+    case ARM_CPU_MODE_SYS:
+    case ARM_CPU_MODE_SVC:
+    case ARM_CPU_MODE_ABT:
+    case ARM_CPU_MODE_UND:
+    case ARM_CPU_MODE_IRQ:
+    case ARM_CPU_MODE_FIQ:
+        return 0;
+    default:
+        return 1;
+    }
+}
+
 uint32_t cpsr_read(CPUARMState *env)
 {
     int ZF;
@@ -499,7 +519,15 @@
     }
 
     if ((env->uncached_cpsr ^ val) & mask & CPSR_M) {
-        switch_mode(env, val & CPSR_M);
+        if (bad_mode_switch(env, val & CPSR_M)) {
+            /* Attempt to switch to an invalid mode: this is UNPREDICTABLE.
+             * We choose to ignore the attempt and leave the CPSR M field
+             * untouched.
+             */
+            mask &= ~CPSR_M;
+        } else {
+            switch_mode(env, val & CPSR_M);
+        }
     }
     mask &= ~CACHED_CPSR_BITS;
     env->uncached_cpsr = (env->uncached_cpsr & ~mask) | (val & mask);