Files
filza27.2/XPF/external/ChOma/src/arm64.c
T

1009 lines
29 KiB
C

#include "arm64.h"
#include "Util.h"
#include <stdio.h>
#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;
}