In the ARM v6 architecture, 'sub pc, pc, 1' is not an interworking
branch, so the computed new value is written to r15 as a normal
value. The architecture says that in this case, bits [1:0] of
the value written must be ignored if we are in ARM mode (or
bit [0] ignored if in Thumb mode); this is a change from the
ARMv4/v5 specification that behaviour is UNPREDICTABLE.
Use the correct mask on the PC value when doing a non-interworking
store to PC.

A popular library used on RaspberryPi uses this instruction
as part of a trick to determine whether it is running on
ARMv6 or ARMv7, and we were mishandling the sequence.

Fixes bug: https://bugs.launchpad.net/bugs/1625295

Reported-by: <stu.a...@gmail.com>
Signed-off-by: Peter Maydell <peter.mayd...@linaro.org>
---
 target-arm/translate.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/target-arm/translate.c b/target-arm/translate.c
index 693d4bc..8df24bf 100644
--- a/target-arm/translate.c
+++ b/target-arm/translate.c
@@ -180,7 +180,12 @@ static inline TCGv_i32 load_reg(DisasContext *s, int reg)
 static void store_reg(DisasContext *s, int reg, TCGv_i32 var)
 {
     if (reg == 15) {
-        tcg_gen_andi_i32(var, var, ~1);
+        /* In Thumb mode, we must ignore bit 0.
+         * In ARM mode, for ARMv4 and ARMv5, it is UNPREDICTABLE if bits [1:0]
+         * are not 0b00, but for ARMv6 and above, we must ignore bits [1:0].
+         * We choose to ignore [1:0] in ARM mode for all architecture versions.
+         */
+        tcg_gen_andi_i32(var, var, s->thumb ? ~1 : ~3);
         s->is_jmp = DISAS_JUMP;
     }
     tcg_gen_mov_i32(cpu_R[reg], var);
-- 
2.7.4


Reply via email to