Repository: capstone-engine/capstone (C)

Issue:
AArch64 ror instruction shift encoded differently than other shifts
### Work environment


| Questions                                | Answers
|------------------------------------------|--------------------
| Capstone module affected                 | AArch64
| Source of Capstone                       | `git clone`
| Version/git commit                       | https://github.com/capstone-engine/capstone/commit/8ac2843b9bc91e29c0287d27f9c49cff2ad44776 (HEAD, tag: 6.0.0-Alpha3)

The way some `aarch64` bit shift instructions expose operand data changed between Capstone V5 and V6.0.0alpha3. The  `asr`, `lsl`, `lsr` now expose the shift as a modifier to the second operand (previously it was a separate operand). This type of representation is not reflected in the `ror` shift instruction

```
# lsl
cstool -d aarch64 "c4 75 1e 53"

 0  c4 75 1e 53  lsl    w4, w14, #2
        ID: 1253 (ubfm)
        Is alias: 1764 (lsl) with ALIAS operand set
        op_count: 2
                operands[0].type: REG = w4
                operands[0].access: WRITE
                operands[1].type: REG = w14
                operands[1].access: READ
                        Shift: type = 1, value = 2
        Registers read: w14
        Registers modified: w4

# lsr
cstool -d aarch64 "c4 7d 02 53"
 0  c4 7d 02 53  lsr    w4, w14, #2
        ID: 1253 (ubfm)
        Is alias: 1579 (lsr) with ALIAS operand set
        op_count: 2
                operands[0].type: REG = w4
                operands[0].access: WRITE
                operands[1].type: REG = w14
                operands[1].access: READ
                        Shift: type = 3, value = 2
        Registers read: w14
        Registers modified: w4

# asr
cstool -d aarch64 "c4 7d 02 13"
 0  c4 7d 02 13  asr    w4, w14, #2
        ID: 917 (sbfm)
        Is alias: 1575 (asr) with ALIAS operand set
        op_count: 2
                operands[0].type: REG = w4
                operands[0].access: WRITE
                operands[1].type: REG = w14
                operands[1].access: READ
                        Shift: type = 4, value = 2
        Registers read: w14
        Registers modified: w4

# ror
cstool -d aarch64 "c4 09 8e 13" 
 0  c4 09 8e 13  ror    w4, w14, #2
        ID: 317 (extr)
        Is alias: 1574 (ror) with ALIAS operand set
        op_count: 3
                operands[0].type: REG = w4
                operands[0].access: WRITE
                operands[1].type: REG = w14
                operands[1].access: READ
                operands[2].type: IMM = 0x2
                operands[2].access: READ
        Registers read: w14
        Registers modified: w4
```

### Expected behavior

`ror` shift data should be represented the same way that other similar shift instructions - not as a separate operand.

### Actual behavior

### Steps to reproduce the behavior
```
cstool -d aarch64 "c4 09 8e 13"
```





Discussion: I should note this is only relevant when the shift is a immediate value - when it's a register, the shift for `lsl`,`lsr`, `asr` is a separate operand. I'm not sure what the expected behavior should be (always have the shift as a immediate/register operand or not) but it would be great for it to be consistent across the shift instructions.
Thanks for the catch!

> I'm not sure what the expected behavior should be (always have the shift as a immediate/register operand or not) but it would be great for it to be consistent across the shift instructions.

I think it should be as for `ARM`, so we have at least some consistency between them. So the register version should also save the reg in the `shift.value` field. And to check it the `aarch64_shifter` enum is extended with `_REG` versions.



Fix (patch):
diff --git a/arch/AArch64/AArch64Mapping.c b/arch/AArch64/AArch64Mapping.c
index d2799eb3b0..de9512e551 100644
--- a/arch/AArch64/AArch64Mapping.c
+++ b/arch/AArch64/AArch64Mapping.c
@@ -369,12 +369,52 @@ static void AArch64_check_updates_flags(MCInst *MI)
 #endif // CAPSTONE_DIET
 }
 
+static aarch64_shifter id_to_shifter(unsigned Opcode) {
+	switch (Opcode) {
+	default:
+		return AARCH64_SFT_INVALID;
+	case AArch64_RORVXr:
+	case AArch64_RORVWr:
+		return AARCH64_SFT_ROR_REG;
+	case AArch64_LSRVXr:
+	case AArch64_LSRVWr:
+		return AARCH64_SFT_LSR_REG;
+	case AArch64_LSLVXr:
+	case AArch64_LSLVWr:
+		return AARCH64_SFT_LSL_REG;
+	case AArch64_ASRVXr:
+	case AArch64_ASRVWr:
+		return AARCH64_SFT_ASR_REG;
+	}
+}
+
 static void add_non_alias_details(MCInst *MI)
 {
 	unsigned Opcode = MCInst_getOpcode(MI);
 	switch (Opcode) {
 	default:
 		break;
+	case AArch64_RORVXr:
+	case AArch64_RORVWr:
+	case AArch64_LSRVXr:
+	case AArch64_LSRVWr:
+	case AArch64_LSLVXr:
+	case AArch64_LSLVWr:
+	case AArch64_ASRVXr:
+	case AArch64_ASRVWr:
+		if (AArch64_get_detail(MI)->op_count != 3) {
+			return;
+		}
+		CS_ASSERT_RET(AArch64_get_detail_op(MI, -1)->type == AARCH64_OP_REG);
+
+		// The shift by register instructions don't set the shift value properly.
+		// Correct it here.
+		uint64_t shift = AArch64_get_detail_op(MI, -1)->reg;
+		cs_aarch64_op *op1 = AArch64_get_detail_op(MI, -2);
+		op1->shift.type = id_to_shifter(Opcode);
+		op1->shift.value = shift;
+		AArch64_dec_op_count(MI);
+		break;
 	case AArch64_FCMPDri:
 	case AArch64_FCMPEDri:
 	case AArch64_FCMPEHri:
@@ -573,6 +613,19 @@ static void AArch64_add_not_defined_ops(MCInst *MI, const SStream *OS)
 	switch (MI->flat_insn->alias_id) {
 	default:
 		return;
+	case AARCH64_INS_ALIAS_ROR:
+		if (AArch64_get_detail(MI)->op_count != 3) {
+			return;
+		}
+		// The ROR alias doesn't set the shift value properly.
+		// Correct it here.
+		bool reg_shift = AArch64_get_detail_op(MI, -1)->type == AARCH64_OP_REG;
+		uint64_t shift = reg_shift ? AArch64_get_detail_op(MI, -1)->reg : AArch64_get_detail_op(MI, -1)->imm;
+		cs_aarch64_op *op1 = AArch64_get_detail_op(MI, -2);
+		op1->shift.type = reg_shift ? AARCH64_SFT_ROR_REG : AARCH64_SFT_ROR;
+		op1->shift.value = shift;
+		AArch64_dec_op_count(MI);
+		break;
 	case AARCH64_INS_ALIAS_FMOV:
 		if (AArch64_get_detail_op(MI, -1)->type == AARCH64_OP_FP) {
 			break;
@@ -1042,6 +1095,12 @@ void AArch64_reg_access(const cs_insn *insn, cs_regs regs_read,
 		default:
 			break;
 		}
+		if (op->shift.type >= AARCH64_SFT_LSL_REG) {
+			if (!arr_exist(regs_read, read_count, op->shift.value)) {
+				regs_read[read_count] = (uint16_t)op->shift.value;
+				read_count++;
+			}
+		}
 	}
 
 	*regs_read_count = read_count;
diff --git a/bindings/python/capstone/aarch64_const.py b/bindings/python/capstone/aarch64_const.py
index d5860aa2ac..4f32f288d3 100644
--- a/bindings/python/capstone/aarch64_const.py
+++ b/bindings/python/capstone/aarch64_const.py
@@ -7,6 +7,11 @@
 AARCH64_SFT_LSR = 3
 AARCH64_SFT_ASR = 4
 AARCH64_SFT_ROR = 5
+AARCH64_SFT_LSL_REG = 6
+AARCH64_SFT_MSL_REG = 7
+AARCH64_SFT_LSR_REG = 8
+AARCH64_SFT_ASR_REG = 9
+AARCH64_SFT_ROR_REG = 10
 
 AARCH64_EXT_INVALID = 0
 AARCH64_EXT_UXTB = 1
diff --git a/include/capstone/aarch64.h b/include/capstone/aarch64.h
index 2905da8336..c26e74db81 100644
--- a/include/capstone/aarch64.h
+++ b/include/capstone/aarch64.h
@@ -18,6 +18,8 @@ extern "C" {
 #endif
 
 /// AArch64 shift type
+/// Those values do not correspond to the bit values encoded
+/// in the instruction.
 typedef enum aarch64_shifter {
   AARCH64_SFT_INVALID = 0,
   AARCH64_SFT_LSL = 1,
@@ -25,6 +27,11 @@ typedef enum aarch64_shifter {
   AARCH64_SFT_LSR = 3,
   AARCH64_SFT_ASR = 4,
   AARCH64_SFT_ROR = 5,
+  AARCH64_SFT_LSL_REG = 6,
+  AARCH64_SFT_MSL_REG = 7,
+  AARCH64_SFT_LSR_REG = 8,
+  AARCH64_SFT_ASR_REG = 9,
+  AARCH64_SFT_ROR_REG = 10,
 } aarch64_shifter;
 
 /// AArch64 extender type
@@ -2813,7 +2820,10 @@ typedef struct cs_aarch64_op {
   AArch64Layout_VectorLayout vas;    ///< Vector Arrangement Specifier
   struct {
     aarch64_shifter type; ///< shifter type of this operand
-    unsigned int value; ///< shifter value of this operand
+    /// Shift value of this operand.
+    /// If the type indicates a shift with a register this value should be
+    /// interpreted as aarch64_reg.
+    unsigned int value;
   } shift;
   aarch64_extender ext; ///< extender type of this operand
   aarch64_op_type type; ///< operand type
diff --git a/include/capstone/arm64.h b/include/capstone/arm64.h
index 36b37930d8..231009ce70 100644
--- a/include/capstone/arm64.h
+++ b/include/capstone/arm64.h
@@ -25,6 +25,11 @@ typedef enum {
   ARM64_SFT_LSR = AARCH64_SFT_LSR,
   ARM64_SFT_ASR = AARCH64_SFT_ASR,
   ARM64_SFT_ROR = AARCH64_SFT_ROR,
+  ARM64_SFT_LSL_REG = AARCH64_SFT_LSL_REG,
+  ARM64_SFT_MSL_REG = AARCH64_SFT_MSL_REG,
+  ARM64_SFT_LSR_REG = AARCH64_SFT_LSR_REG,
+  ARM64_SFT_ASR_REG = AARCH64_SFT_ASR_REG,
+  ARM64_SFT_ROR_REG = AARCH64_SFT_ROR_REG,
 } arm64_shifter;
 
 typedef enum {