diff options
authorPeter Maydell <peter.maydell@linaro.org>2016-10-04 13:28:10 +0100
committerPeter Maydell <peter.maydell@linaro.org>2016-10-04 13:28:10 +0100
commit9b6a3ea7a699594162ed3d11e4e04b98568dc5c0 (patch)
parent173ff58580b383a7841b18fddb293038c9d40d1c (diff)
target-arm: Correctly handle 'sub pc, pc, 1' for ARMv6pull-target-arm-20161004
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.axon@gmail.com> Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Message-id: 1474380941-4730-1-git-send-email-peter.maydell@linaro.org
1 files changed, 6 insertions, 1 deletions
diff --git a/target-arm/translate.c b/target-arm/translate.c
index 693d4bc6a2..8df24bf35a 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);