#include "arm64.h" #include "Util.h" #include #define ADRP_PAGE_SIZE 0x1000 #define ADRP_PAGE_MASK 0x0fff uint8_t arm64_reg_type_get_width(arm64_register_type type) { switch (type) { case ARM64_REG_TYPE_X: return 8; case ARM64_REG_TYPE_W: return 4; case ARM64_REG_TYPE_Q: return 16; case ARM64_REG_TYPE_D: return 8; case ARM64_REG_TYPE_S: return 4; case ARM64_REG_TYPE_H: return 2; case ARM64_REG_TYPE_B: return 1; } } const char *arm64_reg_type_get_string(arm64_register_type type) { switch (type) { case ARM64_REG_TYPE_X: return "x"; case ARM64_REG_TYPE_W: return "w"; case ARM64_REG_TYPE_Q: return "q"; case ARM64_REG_TYPE_D: return "d"; case ARM64_REG_TYPE_S: return "s"; case ARM64_REG_TYPE_H: return "h"; case ARM64_REG_TYPE_B: return "b"; } return ""; } const char *arm64_reg_get_type_string(arm64_register reg) { return arm64_reg_type_get_string(ARM64_REG_GET_TYPE(reg)); } int arm64_gen_b_l(optional_bool optIsBl, optional_uint64_t optOrigin, optional_uint64_t optTarget, uint32_t *bytesOut, uint32_t *maskOut) { bool isBl = false; if (OPT_BOOL_IS_SET(optIsBl)) { isBl = OPT_BOOL_GET_VAL(optIsBl); } uint64_t origin = 0; uint64_t target = 0; if (OPT_UINT64_IS_SET(optOrigin) && OPT_UINT64_IS_SET(optTarget)) { *maskOut = 0xffffffff; origin = OPT_UINT64_GET_VAL(optOrigin); target = OPT_UINT64_GET_VAL(optTarget); } else { if (!OPT_BOOL_IS_SET(optIsBl)) { *maskOut = 0x7c000000; } else { *maskOut = 0xfc000000; } } int64_t offset = ((int64_t)target - (int64_t)origin) / 4; *bytesOut = (isBl ? 0x94000000 : 0x14000000) | (uint32_t)(offset & 0x3ffffff); return 0; } int arm64_dec_b_l(uint32_t inst, uint64_t origin, uint64_t *targetOut, bool *isBlOut) { if ((inst & 0xfc000000) == 0x14000000) { if (isBlOut) *isBlOut = false; } else if ((inst & 0xfc000000) == 0x94000000) { if (isBlOut) *isBlOut = true; } else { return -1; } int64_t offset = sxt64((inst & 0x3ffffff), 26); if (targetOut) *targetOut = origin + (offset * 4); return 0; } int arm64_gen_b_c_cond(optional_bool optIsBc, optional_uint64_t optOrigin, optional_uint64_t optTarget, arm64_cond optCond, uint32_t *bytesOut, uint32_t *maskOut) { uint32_t inst = 0x54000000; uint32_t mask = 0xff000000; if (OPT_BOOL_IS_SET(optIsBc)) { mask |= (1 << 4); inst |= (OPT_BOOL_GET_VAL(optIsBc) << 4); } if (OPT_UINT64_IS_SET(optOrigin) && OPT_UINT64_IS_SET(optTarget)) { uint64_t origin = OPT_UINT64_GET_VAL(optOrigin); uint64_t target = OPT_UINT64_GET_VAL(optTarget); int64_t offset = (int64_t)target - (int64_t)origin; if (offset < -0x100000 || offset > 0x100000) return -1; // +/-1MB max inst |= ((offset / 4) << 5); mask |= (0x7ffff << 5); } if (ARM64_COND_IS_SET(optCond)) { mask |= 0xf; inst |= ARM64_COND_GET_VAL(optCond); } if (bytesOut) *bytesOut = inst; if (maskOut) *maskOut = mask; return 0; } int arm64_dec_b_c_cond(uint32_t inst, uint64_t origin, uint64_t *targetOut, arm64_cond *condOut, bool *isBcOut) { if ((inst & 0xff000000) != 0x54000000) return -1; if (targetOut) { int64_t offset = sxt64(((inst >> 5) & 0x7ffff), 19); *targetOut = origin + (offset * 4); } if (condOut) { *condOut = ARM64_COND(inst & 0xf); } if (isBcOut) { *isBcOut = inst & (1 << 4); } return 0; } int arm64_gen_adr_p(optional_bool optIsAdrp, optional_uint64_t optOrigin, optional_uint64_t optTarget, arm64_register reg, uint32_t *bytesOut, uint32_t *maskOut) { if (ARM64_REG_IS_ANY_VECTOR(reg)) return -1; bool isAdrp = false; uint32_t inst = (1 << 28); uint32_t mask = 0x1f000000; if (OPT_BOOL_IS_SET(optIsAdrp)) { mask |= (1 << 31); isAdrp = OPT_BOOL_GET_VAL(optIsAdrp); if (isAdrp) { inst |= (1 << 31); } } if (OPT_UINT64_IS_SET(optOrigin) && OPT_UINT64_IS_SET(optTarget)) { mask |= 0x60ffffe0; uint64_t origin = OPT_UINT64_GET_VAL(optOrigin); uint64_t target = OPT_UINT64_GET_VAL(optTarget); if (isAdrp) { origin &= ~ADRP_PAGE_MASK; target &= ~ADRP_PAGE_MASK; } int64_t offset = ((int64_t)target - (int64_t)origin); if (isAdrp) { offset = (offset >> 12); } // +- 1MB if (offset > 0x100000 || offset < -0x100000) { // Offset too big return -1; } uint64_t unsignedOffset = offset & 0x1FFFFF; inst |= ((unsignedOffset & 0x3) << 29); inst |= ((unsignedOffset & ~0x3) << 3); } if (!ARM64_REG_IS_ANY(reg)) { if (ARM64_REG_IS_W(reg)) return -1; inst |= ARM64_REG_GET_NUM(reg);; mask |= 0x1F; } if (bytesOut) *bytesOut = inst; if (maskOut) *maskOut = mask; return 0; } int arm64_dec_adr_p(uint32_t inst, uint64_t origin, uint64_t *targetOut, arm64_register *registerOut, bool *isAdrpOut) { bool isAdrp = false; if ((inst & 0x9f000000) == 0x90000000) { isAdrp = true; } else if ((inst & 0x9f000000) == 0x10000000) { isAdrp = false; } else { return -1; } if (targetOut) { uint64_t offset = ((inst >> 29) & 0x3) | ((inst >> 3) & 0x1ffffc); int64_t signedOffset = 0; if (isAdrp) { offset = (offset << 12); origin &= ~ADRP_PAGE_MASK; signedOffset = sxt64(offset, 33); } else { signedOffset = sxt64(offset, 21); } *targetOut = origin + signedOffset; } if (registerOut) *registerOut = ARM64_REG_X(inst & 0x1f); if (isAdrpOut) *isAdrpOut = isAdrp; return 0; } int arm64_gen_mov_imm(char type, arm64_register destinationReg, optional_uint64_t optImm, optional_uint64_t optShift, uint32_t *bytesOut, uint32_t *maskOut) { if (ARM64_REG_IS_ANY_VECTOR(destinationReg)) return -1; uint32_t inst = 0x12800000; uint32_t mask = 0x7f800000; // Note this mask includes the type as it's not optional switch (type) { case 'k': { inst |= (1 << 30) | (1 << 29); break; } case 'n': { break; } case 'z': { inst |= (1 << 30); break; } default: { return -1; } } if (!ARM64_REG_IS_ANY(destinationReg)) { mask |= (1 << 31); if (ARM64_REG_IS_W(destinationReg)) { if (OPT_UINT64_IS_SET(optShift)) { uint64_t shift = OPT_UINT64_GET_VAL(optShift); if (shift != 0 && shift != 16) return -1; } } else { inst |= (1 << 31); if (OPT_UINT64_IS_SET(optShift)) { uint64_t shift = OPT_UINT64_GET_VAL(optShift); if (shift != 0 && shift != 16 && shift != 32 && shift != 48) return -1; } } mask |= 0x1f; inst |= ARM64_REG_GET_NUM(destinationReg); } if (OPT_UINT64_IS_SET(optImm)) { uint64_t imm = OPT_UINT64_GET_VAL(optImm); if (imm > UINT16_MAX) return -1; mask |= 0x1fffe0; inst |= ((uint32_t)imm << 5); } if (OPT_UINT64_IS_SET(optShift)) { uint64_t shift = OPT_UINT64_GET_VAL(optShift); inst |= ((shift / 16) & 0b11) << 21; mask |= (0b11 << 21); } if (bytesOut) *bytesOut = inst; if (maskOut) *maskOut = mask; return 0; } int arm64_dec_mov_imm(uint32_t inst, arm64_register *destinationRegOut, uint64_t *immOut, uint64_t *shiftOut, char *typeOut) { if ((inst & 0x1f800000) != 0x12800000) return -1; char type = 0; uint8_t opc = ((inst >> 29) & 0b11); switch (opc) { case 0b11: { type = 'k'; break; } case 0b00: { type = 'n'; break; } case 0b10: { type = 'z'; break; } default: { return -1; } } if (destinationRegOut) { bool is64 = inst & (1 << 31); *destinationRegOut = ARM64_REG(is64 ? ARM64_REG_TYPE_X : ARM64_REG_TYPE_W, inst & 0x1f); } if (immOut) { *immOut = (inst >> 5) & 0xffff; } if (shiftOut) { *shiftOut = ((inst >> 21) & 0b11) * 16; } if (typeOut) { *typeOut = type; } return 0; } int arm64_gen_mov_reg(arm64_register destinationReg, arm64_register sourceReg, uint32_t *bytesOut, uint32_t *maskOut) { uint32_t inst = 0x2a0003e0; uint32_t mask = 0x7fe0ffe0; if (!ARM64_REG_IS_ANY(destinationReg) && !ARM64_REG_IS_ANY(sourceReg)) { if (ARM64_REG_IS_W(destinationReg) != ARM64_REG_IS_W(sourceReg)) return -1; } if (!ARM64_REG_IS_ANY(destinationReg)) { mask |= (1 << 31); inst |= (ARM64_REG_IS_X(destinationReg) << 31); inst |= (ARM64_REG_GET_NUM(destinationReg)); mask |= 0x1f; } if (!ARM64_REG_IS_ANY(sourceReg)) { mask |= (1 << 31); inst |= (ARM64_REG_IS_X(sourceReg) << 31); inst |= (ARM64_REG_GET_NUM(sourceReg) << 16); mask |= (0x1f << 16); } if (bytesOut) *bytesOut = inst; if (maskOut) *maskOut = mask; return 0; } int arm64_dec_mov_reg(uint32_t inst, arm64_register *destinationRegOut, arm64_register *sourceRegOut) { if ((inst & 0x7fe0ffe0) != 0x2a0003e0) return -1; bool is64 = inst & (1 << 31); if (destinationRegOut) *destinationRegOut = ARM64_REG(is64 ? ARM64_REG_TYPE_X : ARM64_REG_TYPE_W, inst & 0x1f); if (sourceRegOut) *sourceRegOut = ARM64_REG(is64 ? ARM64_REG_TYPE_X : ARM64_REG_TYPE_W, (inst >> 16) & 0x1f); return 0; } int arm64_gen_add_imm(arm64_register destinationReg, arm64_register sourceReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut) { if (ARM64_REG_IS_ANY_VECTOR(destinationReg)) return -1; if (ARM64_REG_IS_ANY_VECTOR(sourceReg)) return -1; if (!ARM64_REG_IS_ANY(destinationReg) && !ARM64_REG_IS_ANY(sourceReg)) { // if both regs are set and have a mismatching width, abort if (ARM64_REG_IS_W(destinationReg) != ARM64_REG_IS_W(sourceReg)) return -1; } uint32_t inst = 0x11000000; uint32_t mask = 0x7f800000; // if one is set and 32 bit, include 32 bit in mask and set it in inst if (!ARM64_REG_IS_ANY(destinationReg)) { mask |= (1 << 31); inst |= ((uint32_t)(ARM64_REG_IS_X(destinationReg)) << 31); } else if (!ARM64_REG_IS_ANY(sourceReg)) { mask |= (1 << 31); inst |= ((uint32_t)(ARM64_REG_IS_X(sourceReg)) << 31); } if (!ARM64_REG_IS_ANY(destinationReg)) { mask |= 0x1F; inst |= (uint32_t)(ARM64_REG_GET_NUM(destinationReg)); } if (!ARM64_REG_IS_ANY(sourceReg)) { mask |= (0x1F << 5); inst |= ((uint32_t)(ARM64_REG_GET_NUM(destinationReg)) << 5); } if (OPT_UINT64_IS_SET(optImm)) { uint64_t imm = OPT_UINT64_GET_VAL(optImm); if (imm & ~0xFFF) return -1; mask |= (0xFFF << 10); inst |= (imm << 10); } if (bytesOut) *bytesOut = inst; if (maskOut) *maskOut = mask; return 0; } int arm64_dec_add_imm(uint32_t inst, arm64_register *destinationRegOut, arm64_register *sourceRegOut, uint16_t *immOut) { if ((inst & 0x7f800000) != 0x11000000) return -1; bool is64 = (inst & 0x80000000); bool shift = (inst & 0x400000); if (destinationRegOut) { *destinationRegOut = ARM64_REG(is64 ? ARM64_REG_TYPE_X : ARM64_REG_TYPE_W, inst & 0x1F); } if (sourceRegOut) { *sourceRegOut = ARM64_REG(is64 ? ARM64_REG_TYPE_X : ARM64_REG_TYPE_W, (inst >> 5) & 0x1F); } if (immOut) { uint16_t imm = ((inst >> 10) & 0xFFF); if (shift) { imm = (imm << 12); } *immOut = imm; } return 0; } int arm64_gen_sub_imm(arm64_register destinationReg, arm64_register sourceReg, optional_uint64_t optImm, optional_bool optS, uint32_t *bytesOut, uint32_t *maskOut) { if (ARM64_REG_IS_ANY_VECTOR(destinationReg)) return -1; if (ARM64_REG_IS_ANY_VECTOR(sourceReg)) return -1; if (!ARM64_REG_IS_ANY(destinationReg) && !ARM64_REG_IS_ANY(sourceReg)) { // if both regs are set and have a mismatching width, abort if (ARM64_REG_IS_W(destinationReg) != ARM64_REG_IS_W(sourceReg)) return -1; } uint32_t inst = 0x51000000; uint32_t mask = 0x7f800000; if(OPT_BOOL_IS_SET(optS)) { bool s = OPT_BOOL_GET_VAL(optS); if(s) { inst = 0x71000000; mask |= (1 << 29); inst |= (1 << 29); } } // if one is set and 32 bit, include 32 bit in mask and set it in inst if (!ARM64_REG_IS_ANY(destinationReg)) { mask |= (1 << 31); inst |= ((uint32_t)(ARM64_REG_IS_X(destinationReg)) << 31); } else if (!ARM64_REG_IS_ANY(sourceReg)) { mask |= (1 << 31); inst |= ((uint32_t)(ARM64_REG_IS_X(sourceReg)) << 31); } if (!ARM64_REG_IS_ANY(destinationReg)) { mask |= 0x1F; inst |= (uint32_t)(ARM64_REG_GET_NUM(destinationReg)); } if (!ARM64_REG_IS_ANY(sourceReg)) { mask |= (0x1F << 5); inst |= ((uint32_t)(ARM64_REG_GET_NUM(destinationReg)) << 5); } if (OPT_UINT64_IS_SET(optImm)) { uint64_t imm = OPT_UINT64_GET_VAL(optImm); if (imm & ~0xFFF) return -1; mask |= (0xFFF << 10); inst |= (imm << 10); } if (bytesOut) *bytesOut = inst; if (maskOut) *maskOut = mask; return 0; } int arm64_dec_sub_imm(uint32_t inst, arm64_register *destinationRegOut, arm64_register *sourceRegOut, uint16_t *immOut, bool *sOut) { if ((inst & 0x7f800000) != 0x51000000) { if ((inst & 0x7f800000) != 0x71000000) { return -1; } } bool is64 = (inst & 0x80000000); bool isS = (inst & 0x20000000); bool shift = (inst & 0x400000); if (destinationRegOut) { *destinationRegOut = ARM64_REG(is64 ? ARM64_REG_TYPE_X : ARM64_REG_TYPE_W, inst & 0x1F); } if (sourceRegOut) { *sourceRegOut = ARM64_REG(is64 ? ARM64_REG_TYPE_X : ARM64_REG_TYPE_W, (inst >> 5) & 0x1F); } if (immOut) { uint16_t imm = ((inst >> 10) & 0xFFF); if (shift) { imm = (imm << 12); } *immOut = imm; } if (sOut) { *sOut = isS; } return 0; } uint8_t arm64_str_ldr_type_width(arm64_register_type registerType, char ldrStrType) { switch (ldrStrType) { case 'b': return 1; case 'h': return 2; default: return arm64_reg_type_get_width(registerType); } } static int _arm64_gen_str_ldr_imm(uint32_t inst, uint32_t mask, char type, arm64_ldr_str_type instType, arm64_register sourceDestinationReg, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut) { if (ARM64_REG_IS_ANY_VECTOR(addrReg)) return -1; if (!ARM64_REG_IS_ANY(sourceDestinationReg)) { if (ARM64_REG_IS_ANY_VECTOR(sourceDestinationReg)) { mask |= (1 << 26); inst |= (1 << 26); } else { mask |= (1 << 23) | (1 << 26); //inst |= (0 << 23) | (0 << 26); inst |= ARM64_REG_GET_NUM(sourceDestinationReg); mask |= 0x1f; bool isVector = ARM64_REG_IS_VECTOR(sourceDestinationReg); if (isVector && type != 0) return -1; mask |= (1 << 23) | (1 << 26) | (0b11 << 30); inst |= (isVector << 26); uint8_t size = 0b00; arm64_register_type regType = ARM64_REG_GET_TYPE(sourceDestinationReg); if (isVector) { switch (regType) { case ARM64_REG_TYPE_Q: size = 0b00; inst |= (1 << 23); break; case ARM64_REG_TYPE_D: size = 0b11; break; case ARM64_REG_TYPE_S: size = 0b10; break; case ARM64_REG_TYPE_H: size = 0b01; break; case ARM64_REG_TYPE_B: size = 0b00; break; default: break; } } else { if (regType == ARM64_REG_TYPE_X) { size = 0b11; } else if (regType == ARM64_REG_TYPE_W) { size = 0b10; } } inst |= (size << 30); mask |= (0b11 << 30); } } if (type != 0) { mask |= (1 << 23) | (1 << 26); //inst |= (0 << 23) | (0 << 26); uint8_t size = 0b00; if (type == 'h') { size = 0b01; } else if (type == 'b') { size = 0b00; } inst &= ~(0b11 << 30); inst |= (size << 30); mask |= (0b11 << 30); } if (instType != LDR_STR_TYPE_ANY) { mask |= (1 << 24); if (instType == LDR_STR_TYPE_PRE_INDEX || instType == LDR_STR_TYPE_POST_INDEX) { // Bit 10: Mask out LDUR / STUR // Bit 11: post index or not? mask |= (1 << 10) | (1 << 11); inst |= (1 << 10) | ((instType == LDR_STR_TYPE_PRE_INDEX) << 11); } else if(instType == LDR_STR_TYPE_UNSIGNED) { inst |= (1 << 24); } } if (!ARM64_REG_IS_ANY(addrReg)) { if (!ARM64_REG_IS_X(addrReg)) return -1; inst |= ((uint32_t)(ARM64_REG_GET_NUM(addrReg)) << 5); mask |= (0x1f << 5); } if (OPT_UINT64_IS_SET(optImm)) { uint64_t imm = OPT_UINT64_GET_VAL(optImm) / arm64_str_ldr_type_width(ARM64_REG_GET_TYPE(sourceDestinationReg), type); if (imm & ~0xfff) return -1; inst |= (imm << 10); mask |= (0xfff << 10); } if (bytesOut) *bytesOut = inst; if (maskOut) *maskOut = mask; return 0; } static int _arm64_dec_str_ldr_imm(uint32_t inst, arm64_register *sourceDestinationRegOut, arm64_register *addrRegOut, uint64_t *immOut, char *typeOut, arm64_ldr_str_type *instTypeOut) { bool isVector = inst & (1 << 26); bool isUnsigned = inst & (1 << 24); if (!isUnsigned) { bool isUnscaled = !(inst & (1 << 10)); if (isUnscaled) return -1; } uint8_t size = (inst >> 30); char instructionType = 0; arm64_register_type registerType = 0; if (isVector) { switch (size) { case 0b00: if (inst & (1 << 23)) { registerType = ARM64_REG_TYPE_Q; } else { registerType = ARM64_REG_TYPE_B; } break; case 0b01: registerType = ARM64_REG_TYPE_H; break; case 0b10: registerType = ARM64_REG_TYPE_S; break; case 0b11: registerType = ARM64_REG_TYPE_D; } } else { switch (size) { case 0b00: registerType = ARM64_REG_TYPE_W; instructionType = 'b'; break; case 0b01: registerType = ARM64_REG_TYPE_W; instructionType = 'h'; break; case 0b10: registerType = ARM64_REG_TYPE_W; break; case 0b11: registerType = ARM64_REG_TYPE_X; break; } } if (sourceDestinationRegOut) { *sourceDestinationRegOut = ARM64_REG(registerType, (inst & 0x1f)); } if (typeOut) { *typeOut = instructionType; } if (addrRegOut) { *addrRegOut = ARM64_REG_X((inst >> 5) & 0x1f); } if (immOut) { if (isUnsigned) { uint64_t imm = ((inst >> 10) & 0xfff) * arm64_str_ldr_type_width(registerType, instructionType); *immOut = imm; } else { uint64_t imm = ((inst >> 12) & 0x1ff); *immOut = sxt64(imm, 9); } } if (instTypeOut) { if (isUnsigned) { *instTypeOut = LDR_STR_TYPE_UNSIGNED; } else { bool isPreIndex = inst & (1 << 11); *instTypeOut = isPreIndex ? LDR_STR_TYPE_PRE_INDEX : LDR_STR_TYPE_POST_INDEX; } } return 0; } int arm64_gen_ldr_imm(char type, arm64_ldr_str_type instType, arm64_register destinationReg, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut) { return _arm64_gen_str_ldr_imm(0x38400000, 0x3a400000, type, instType, destinationReg, addrReg, optImm, bytesOut, maskOut); } int arm64_dec_ldr_imm(uint32_t inst, arm64_register *destinationRegOut, arm64_register *addrRegOut, uint64_t *immOut, char *typeOut, arm64_ldr_str_type *instTypeOut) { if ((inst & 0x3a400000) != 0x38400000) return -1; return _arm64_dec_str_ldr_imm(inst, destinationRegOut, addrRegOut, immOut, typeOut, instTypeOut); } int arm64_gen_ldrs_imm(char type, arm64_ldr_str_type instType, arm64_register destinationReg, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut) { return _arm64_gen_str_ldr_imm(0x38800000, 0x3ac00000, type, instType, destinationReg, addrReg, optImm, bytesOut, maskOut); } int arm64_dec_ldrs_imm(uint32_t inst, arm64_register *destinationRegOut, arm64_register *addrRegOut, uint64_t *immOut, char *typeOut, arm64_ldr_str_type *instTypeOut) { if ((inst & 0x3ac00000) != 0x38800000) return -1; return _arm64_dec_str_ldr_imm(inst, destinationRegOut, addrRegOut, immOut, typeOut, instTypeOut); } int arm64_gen_str_imm(char type, arm64_ldr_str_type instType, arm64_register sourceReg, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut) { return _arm64_gen_str_ldr_imm(0x38000000, 0x3a400000, type, instType, sourceReg, addrReg, optImm, bytesOut, maskOut); } int arm64_dec_str_imm(uint32_t inst, arm64_register *sourceRegOut, arm64_register *addrRegOut, uint64_t *immOut, char *typeOut, arm64_ldr_str_type *instTypeOut) { if ((inst & 0x3a400000) != 0x38000000) return -1; return _arm64_dec_str_ldr_imm(inst, sourceRegOut, addrRegOut, immOut, typeOut, instTypeOut); } int arm64_gen_ldr_lit(arm64_register destinationReg, optional_uint64_t optOrigin, optional_uint64_t optTarget, uint32_t *bytesOut, uint32_t *maskOut) { if (ARM64_REG_IS_ANY_VECTOR(destinationReg)) return -1; uint32_t inst = 0x18000000; uint32_t mask = 0xbf000000; if (!ARM64_REG_IS_ANY(destinationReg)) { mask |= (1 << 30); inst |= (ARM64_REG_IS_X(destinationReg) << 30); mask |= 0x1f; inst |= ARM64_REG_GET_NUM(destinationReg); } if (OPT_UINT64_IS_SET(optOrigin) && OPT_UINT64_IS_SET(optTarget)) { uint64_t origin = OPT_UINT64_GET_VAL(optOrigin); uint64_t target = OPT_UINT64_GET_VAL(optTarget); mask |= (0x7ffff << 5); inst |= (((target - origin) / 4) & 0x7ffff) << 5; } if (bytesOut) *bytesOut = inst; if (maskOut) *maskOut = mask; return 0; } int arm64_dec_ldr_lit(uint32_t inst, uint64_t origin, uint64_t *targetOut, arm64_register *destinationReg) { if ((inst & 0xbf000000) != 0x18000000) return -1; if (destinationReg) { *destinationReg = ARM64_REG((inst & (1 << 30)) ? ARM64_REG_TYPE_X : ARM64_REG_TYPE_W, inst & 0x1f); } if (targetOut) { uint64_t imm = (inst >> 5) & 0x7ffff; *targetOut = origin + (sxt64(imm, 19) * 4); } return 0; } int arm64_gen_ldp(arm64_ldp_stp_type instType, arm64_register destinationReg1, arm64_register destinationReg2, arm64_register addrReg, optional_uint64_t optImm, uint32_t *bytesOut, uint32_t *maskOut) { uint32_t inst = 0x28400000; uint32_t mask = 0x7e400000; if (!ARM64_REG_IS_ANY(destinationReg1) && !ARM64_REG_IS_ANY(destinationReg2)) { if (ARM64_REG_IS_X(destinationReg1) != ARM64_REG_IS_X(destinationReg2)) { return -1; } } bool is64 = false; if (!ARM64_REG_IS_ANY(destinationReg1) || !ARM64_REG_IS_ANY(destinationReg2)) { if (!ARM64_REG_IS_ANY(destinationReg1)) { is64 = ARM64_REG_IS_X(destinationReg1); } else { is64 = ARM64_REG_IS_X(destinationReg2); } mask |= (1 << 31); inst |= (is64 << 31); } if (ARM64_REG_IS_ANY(destinationReg1) && ARM64_REG_IS_ANY(destinationReg2) && OPT_UINT64_IS_SET(optImm)) { return -1; } if (instType != ARM64_LDP_STP_TYPE_ANY) { mask |= (0b11 << 23); switch (instType) { case ARM64_LDP_STP_TYPE_POST_INDEX: inst |= (0b01 << 23); break; case ARM64_LDP_STP_TYPE_PRE_INDEX: inst |= (0b11 << 23); break; case ARM64_LDP_STP_TYPE_SIGNED: inst |= (0b10 << 23); break; default: break; } } if (!ARM64_REG_IS_ANY(destinationReg1)) { mask |= 0x1f; inst |= ARM64_REG_GET_NUM(destinationReg1); } if (!ARM64_REG_IS_ANY(destinationReg2)) { mask |= 0x1f << 10; inst |= ARM64_REG_GET_NUM(destinationReg2) << 10; } if (!ARM64_REG_IS_ANY(addrReg)) { mask |= 0x1f << 5; inst |= ARM64_REG_GET_NUM(addrReg) << 5; } if (OPT_UINT64_IS_SET(optImm)) { mask |= 0b1111111 << 15; if (is64) { inst |= OPT_UINT64_GET_VAL(optImm) / 8; } else { inst |= OPT_UINT64_GET_VAL(optImm) / 4; } } if (bytesOut) *bytesOut = inst; if (maskOut) *maskOut = mask; return 0; } int arm64_gen_cb_n_z(optional_bool isCbnz, arm64_register reg, optional_uint64_t optTarget, uint32_t *bytesOut, uint32_t *maskOut) { if (ARM64_REG_IS_ANY_VECTOR(reg)) return -1; uint32_t inst = 0x34000000; uint32_t mask = 0x7e000000; if (OPT_BOOL_IS_SET(isCbnz)) { mask |= (1 << 24); if (OPT_BOOL_GET_VAL(isCbnz)) { inst |= (1 << 24); } } if (!ARM64_REG_IS_ANY(reg)) { mask |= 0x1f | (1 << 31); inst |= (ARM64_REG_IS_X(reg) << 31); inst |= ARM64_REG_GET_NUM(reg); } if (OPT_UINT64_IS_SET(optTarget)) { uint64_t target = OPT_UINT64_GET_VAL(optTarget) / 4; if (target & ~0x7ffff) { return -1; } mask |= (0x7ffff << 5); inst |= (target << 5); } if (bytesOut) *bytesOut = inst; if (maskOut) *maskOut = mask; return 0; } int arm64_dec_cb_n_z(uint32_t inst, uint64_t origin, bool *isCbnzOut, arm64_register *regOut, uint64_t *targetOut) { if ((inst & 0x7e000000) != 0x34000000) return -1; if (isCbnzOut) { *isCbnzOut = ((inst >> 24) & 0x1); } if (regOut) { bool is64 = ((inst >> 31) & 0x1); uint16_t num = inst & 0x1f; *regOut = ARM64_REG(is64 ? ARM64_REG_TYPE_X : ARM64_REG_TYPE_W, num); } if (targetOut) { *targetOut = origin + sxt64(((inst >> 5) & 0x7ffff) * 4, 19); } return 0; } int arm64_gen_tb_n_z(optional_bool isTbnz, arm64_register reg, optional_uint64_t optTarget, optional_uint64_t optBit, uint32_t *bytesOut, uint32_t *maskOut) { if (ARM64_REG_IS_ANY_VECTOR(reg)) return -1; uint32_t inst = 0x36000000; uint32_t mask = 0x7e000000; if (OPT_BOOL_IS_SET(isTbnz)) { mask |= (1 << 25); inst |= (OPT_BOOL_GET_VAL(isTbnz)) << 25; } if (!ARM64_REG_IS_ANY(reg)) { mask |= 0x1f | (1 << 31); inst |= (ARM64_REG_IS_X(reg) << 31); inst |= ARM64_REG_GET_NUM(reg); } if (OPT_UINT64_IS_SET(optTarget)) { uint64_t target = OPT_UINT64_GET_VAL(optTarget) / 4; if (target & ~0x3fff) { return -1; } mask |= (0x3fff << 5); inst |= (target << 5); } if (OPT_UINT64_IS_SET(optBit)) { uint64_t bit = OPT_UINT64_GET_VAL(optBit); if (bit & ~0x1f) return -1; bool bitIs64 = (bit & (1 << 5)); if (!ARM64_REG_IS_ANY(reg)) { if (ARM64_REG_IS_X(reg) != bitIs64) return -1; } mask |= (1 << 31) | (0xf << 19); inst |= (bitIs64 << 31) | (bit & 0xf) << 5; } if (bytesOut) *bytesOut = inst; if (maskOut) *maskOut = mask; return 0; } int arm64_dec_tb_n_z(uint32_t inst, uint64_t origin, bool *isTbnzOut, arm64_register *regOut, uint64_t *targetOut, uint64_t *bitOut) { if ((inst & 0x7e000000) != 0x36000000) return -1; bool is64 = inst & (1 << 31); if (isTbnzOut) { *isTbnzOut = (inst >> 25); } if (regOut) { *regOut = ARM64_REG(is64 ? ARM64_REG_TYPE_X : ARM64_REG_TYPE_W, inst & 0x1f); } if (targetOut) { *targetOut = origin + sxt64(((inst >> 5) & 0x3fff) * 4, 14); } if (bitOut) { *bitOut = (is64 << 5) | ((inst >> 19) & 0xf); } return 0; }