summaryrefslogblamecommitdiffstats
path: root/src/core/arm/disassembler/arm_disasm.cpp
blob: d73495fe970d36b7d90fd1ffd5455ad10dbb4b5a (plain) (tree)
1
2
3
4
5
6
7
8

                                                 
                 
                        
 
                               
                                             
                                           



















                                   
                                     











                
            








            



             











            
          
          
          



          
          
          

            

             




            



             





           





              



            

             





              


            


















































                                       
                                               


                            
                                                                 
 
                                 

                        
                             
                          
                               















                    
                                                

                   
                                                         
                     
                                         



                                
                                       
                    
                         

                           
                    
                                        
                    
                         

                    
                                                     







                      
                                        








                                                



                      
                                            

                    
                                                
                    
                                                
                    
                                        
                    
                                        
                    
                                                





                                                       

                                        
                    
                                        

                                        




                                                
                    
                         
                    
                                        

                     
                                                












                                               



                      
                                                  
                
                           



                
                                                                    




                                         


                       







                                          
                                              

















                                                            
                                                       





                                                                        
                                                       






                                    
                                                                  
                                                                                                   












                                                                             
                                                          
                                                                                         



                                                     
                                                                  
                                                                                        
                                


                              
                                                                   
                                                                                             


                          
                                                              
                                                                                    
                                      

 
                                                                                      











                                                         
                                                                                   

 
                                                    


                                      
                                                                        

 
                                                      
 
                                      
                                                                 
                                                                             

 
                                                     



                                      
                                                                                  

 
                                                                         
 
                         






















                                              
                                                                     


















                               
                                                            
                                                                                      

 
                                                     






























                                            
                                                                     

                                                                 
                                                                              





                                                                                      
 
                                                                          

                                                                                      










                                                     
                                                                              
                                                                                  

                                  
                                                                                   
                                                                                  


                              
                                                                              

                                                                   







                              
                                                                          
                                                                                  

                              
                                                                               
                                                                                  



                          
                                                                          

                                                                         

 
                                                         


































                                                               
                                                                                                        
                    
                                                                             


                                                                                
                                                                       

                                                                      


                 
                                                                     

                                                                    
                                                                   

                                                              

 
                                                                    








                                              
                                                                         
                                                                      

 
                                                                    








                                              
                                                                 
                                                                         

 
                                                                      








                                              
                                                                 
                                                                             

 
                                                                    







                                              
                                                            
                                                                     

 
                                                     




                                      
                                                                                                   

 
                                                     






















                                                                              
                                                              
                                                                             



                            
                                                        
                                                                

 





                                                                                     























                                                                                                  
                                                     










                                        
                                                                            



                                   
                                                          
            
                                                                                
     

 





























                                                                                               




























                                                                                                         








                                                                                                   
                                                     



                                        
                                                                             

 
                                                                    






                                              
                                                                                                    

 























                                                                                                  
                                          


                                            
                                  
                 
                                  
                 
                                  
                 
                                  



                      
                                            
















                                                


                                                 

                                               
                                   




                                                               
                                    


         





                                                            
                                              
                           

 
                                            


                                        
                                 





                                            



                             















                           
                                            







                                             
 


                         
 
                

 
                                            
















                                             
 





























                                                                      




























                                                       
















                                                                   



                                 





                                       

                                 











                                       



                                 





                                       

                                 











                                       
                                             
































                                                                      
























                                                        
















                                                           
                                              




























                                                                      
                                             





















































                                                                  
 
// Copyright 2006 The Android Open Source Project

#include <string>
#include <unordered_set>

#include "common/string_util.h"
#include "core/arm/disassembler/arm_disasm.h"
#include "core/arm/skyeye_common/armsupp.h"

static const char *cond_names[] = {
    "eq",
    "ne",
    "cs",
    "cc",
    "mi",
    "pl",
    "vs",
    "vc",
    "hi",
    "ls",
    "ge",
    "lt",
    "gt",
    "le",
    "",
    "RESERVED"
};

static const char *opcode_names[] = {
    "invalid",
    "undefined",
    "adc",
    "add",
    "and",
    "b",
    "bl",
    "bic",
    "bkpt",
    "blx",
    "bx",
    "cdp",
    "clrex",
    "clz",
    "cmn",
    "cmp",
    "eor",
    "ldc",
    "ldm",
    "ldr",
    "ldrb",
    "ldrbt",
    "ldrex",
    "ldrexb",
    "ldrexd",
    "ldrexh",
    "ldrh",
    "ldrsb",
    "ldrsh",
    "ldrt",
    "mcr",
    "mla",
    "mov",
    "mrc",
    "mrs",
    "msr",
    "mul",
    "mvn",
    "nop",
    "orr",
    "pkh",
    "pld",
    "rsb",
    "rsc",
    "sbc",
    "sel",
    "sev",
    "smlal",
    "smull",
    "ssat",
    "ssat16",
    "stc",
    "stm",
    "str",
    "strb",
    "strbt",
    "strex",
    "strexb",
    "strexd",
    "strexh",
    "strh",
    "strt",
    "sub",
    "swi",
    "swp",
    "swpb",
    "sxtab",
    "sxtab16",
    "sxtah",
    "sxtb",
    "sxtb16",
    "sxth",
    "teq",
    "tst",
    "umlal",
    "umull",
    "usat",
    "usat16",
    "uxtab",
    "uxtab16",
    "uxtah",
    "uxtb",
    "uxtb16",
    "uxth",
    "wfe",
    "wfi",
    "yield",

    "undefined",
    "adc",
    "add",
    "and",
    "asr",
    "b",
    "bic",
    "bkpt",
    "bl",
    "blx",
    "bx",
    "cmn",
    "cmp",
    "eor",
    "ldmia",
    "ldr",
    "ldrb",
    "ldrh",
    "ldrsb",
    "ldrsh",
    "lsl",
    "lsr",
    "mov",
    "mul",
    "mvn",
    "neg",
    "orr",
    "pop",
    "push",
    "ror",
    "sbc",
    "stmia",
    "str",
    "strb",
    "strh",
    "sub",
    "swi",
    "tst",

    NULL
};

// Indexed by the shift type (bits 6-5)
static const char *shift_names[] = {
    "LSL",
    "LSR",
    "ASR",
    "ROR"
};

static const char* cond_to_str(uint32_t cond) {
    return cond_names[cond];
}

std::string ARM_Disasm::Disassemble(uint32_t addr, uint32_t insn)
{
    Opcode opcode = Decode(insn);
    switch (opcode) {
        case OP_INVALID:
            return "Invalid";
        case OP_UNDEFINED:
            return "Undefined";
        case OP_ADC:
        case OP_ADD:
        case OP_AND:
        case OP_BIC:
        case OP_CMN:
        case OP_CMP:
        case OP_EOR:
        case OP_MOV:
        case OP_MVN:
        case OP_ORR:
        case OP_RSB:
        case OP_RSC:
        case OP_SBC:
        case OP_SUB:
        case OP_TEQ:
        case OP_TST:
            return DisassembleALU(opcode, insn);
        case OP_B:
        case OP_BL:
            return DisassembleBranch(addr, opcode, insn);
        case OP_BKPT:
            return DisassembleBKPT(insn);
        case OP_BLX:
            // not supported yet
            break;
        case OP_BX:
            return DisassembleBX(insn);
        case OP_CDP:
            return "cdp";
        case OP_CLREX:
            return "clrex";
        case OP_CLZ:
            return DisassembleCLZ(insn);
        case OP_LDC:
            return "ldc";
        case OP_LDM:
        case OP_STM:
            return DisassembleMemblock(opcode, insn);
        case OP_LDR:
        case OP_LDRB:
        case OP_LDRBT:
        case OP_LDRT:
        case OP_STR:
        case OP_STRB:
        case OP_STRBT:
        case OP_STRT:
            return DisassembleMem(insn);
        case OP_LDREX:
        case OP_LDREXB:
        case OP_LDREXD:
        case OP_LDREXH:
        case OP_STREX:
        case OP_STREXB:
        case OP_STREXD:
        case OP_STREXH:
            return DisassembleREX(opcode, insn);
        case OP_LDRH:
        case OP_LDRSB:
        case OP_LDRSH:
        case OP_STRH:
            return DisassembleMemHalf(insn);
        case OP_MCR:
        case OP_MRC:
            return DisassembleMCR(opcode, insn);
        case OP_MLA:
            return DisassembleMLA(opcode, insn);
        case OP_MRS:
            return DisassembleMRS(insn);
        case OP_MSR:
            return DisassembleMSR(insn);
        case OP_MUL:
            return DisassembleMUL(opcode, insn);
        case OP_NOP:
        case OP_SEV:
        case OP_WFE:
        case OP_WFI:
        case OP_YIELD:
            return DisassembleNoOperands(opcode, insn);
        case OP_PKH:
            return DisassemblePKH(insn);
        case OP_PLD:
            return DisassemblePLD(insn);
        case OP_SEL:
            return DisassembleSEL(insn);
        case OP_SSAT:
        case OP_SSAT16:
        case OP_USAT:
        case OP_USAT16:
            return DisassembleSAT(opcode, insn);
        case OP_STC:
            return "stc";
        case OP_SWI:
            return DisassembleSWI(insn);
        case OP_SWP:
        case OP_SWPB:
            return DisassembleSWP(opcode, insn);
        case OP_SXTAB:
        case OP_SXTAB16:
        case OP_SXTAH:
        case OP_SXTB:
        case OP_SXTB16:
        case OP_SXTH:
        case OP_UXTAB:
        case OP_UXTAB16:
        case OP_UXTAH:
        case OP_UXTB:
        case OP_UXTB16:
        case OP_UXTH:
            return DisassembleXT(opcode, insn);
        case OP_UMLAL:
        case OP_UMULL:
        case OP_SMLAL:
        case OP_SMULL:
            return DisassembleUMLAL(opcode, insn);
        default:
            return "Error";
    }
    return NULL;
}

std::string ARM_Disasm::DisassembleALU(Opcode opcode, uint32_t insn)
{
    static const uint8_t kNoOperand1 = 1;
    static const uint8_t kNoDest = 2;
    static const uint8_t kNoSbit = 4;

    std::string rn_str;
    std::string rd_str;

    uint8_t flags = 0;
    uint8_t cond = (insn >> 28) & 0xf;
    uint8_t is_immed = (insn >> 25) & 0x1;
    uint8_t bit_s = (insn >> 20) & 1;
    uint8_t rn = (insn >> 16) & 0xf;
    uint8_t rd = (insn >> 12) & 0xf;
    uint8_t immed = insn & 0xff;

    const char* opname = opcode_names[opcode];
    switch (opcode) {
        case OP_CMN:
        case OP_CMP:
        case OP_TEQ:
        case OP_TST:
            flags = kNoDest | kNoSbit;
            break;
        case OP_MOV:
        case OP_MVN:
            flags = kNoOperand1;
            break;
        default:
            break;
    }

    // The "mov" instruction ignores the first operand (rn).
    rn_str[0] = 0;
    if ((flags & kNoOperand1) == 0) {
        rn_str = Common::StringFromFormat("r%d, ", rn);
    }

    // The following instructions do not write the result register (rd):
    // tst, teq, cmp, cmn.
    rd_str[0] = 0;
    if ((flags & kNoDest) == 0) {
        rd_str = Common::StringFromFormat("r%d, ", rd);
    }

    const char *sbit_str = "";
    if (bit_s && !(flags & kNoSbit))
        sbit_str = "s";

    if (is_immed) {
        return Common::StringFromFormat("%s%s%s\t%s%s#%u  ; 0x%x",
                opname, cond_to_str(cond), sbit_str, rd_str.c_str(), rn_str.c_str(), immed, immed);
    }

    uint8_t shift_is_reg = (insn >> 4) & 1;
    uint8_t rotate = (insn >> 8) & 0xf;
    uint8_t rm = insn & 0xf;
    uint8_t shift_type = (insn >> 5) & 0x3;
    uint8_t rs = (insn >> 8) & 0xf;
    uint8_t shift_amount = (insn >> 7) & 0x1f;
    uint32_t rotated_val = immed;
    uint8_t rotate2 = rotate << 1;
    rotated_val = (rotated_val >> rotate2) | (rotated_val << (32 - rotate2));

    if (!shift_is_reg && shift_type == 0 && shift_amount == 0) {
        return Common::StringFromFormat("%s%s%s\t%s%sr%d",
                opname, cond_to_str(cond), sbit_str, rd_str.c_str(), rn_str.c_str(), rm);
    }

    const char *shift_name = shift_names[shift_type];
    if (shift_is_reg) {
        return Common::StringFromFormat("%s%s%s\t%s%sr%d, %s r%d",
                opname, cond_to_str(cond), sbit_str, rd_str.c_str(), rn_str.c_str(), rm,
                shift_name, rs);
    }
    if (shift_amount == 0) {
        if (shift_type == 3) {
            return Common::StringFromFormat("%s%s%s\t%s%sr%d, RRX",
                    opname, cond_to_str(cond), sbit_str, rd_str.c_str(), rn_str.c_str(), rm);
        }
        shift_amount = 32;
    }
    return Common::StringFromFormat("%s%s%s\t%s%sr%d, %s #%u",
            opname, cond_to_str(cond), sbit_str, rd_str.c_str(), rn_str.c_str(), rm,
            shift_name, shift_amount);
}

std::string ARM_Disasm::DisassembleBranch(uint32_t addr, Opcode opcode, uint32_t insn)
{
    uint8_t cond = (insn >> 28) & 0xf;
    uint32_t offset = insn & 0xffffff;
    // Sign-extend the 24-bit offset
    if ((offset >> 23) & 1)
        offset |= 0xff000000;

    // Pre-compute the left-shift and the prefetch offset
    offset <<= 2;
    offset += 8;
    addr += offset;
    const char *opname = opcode_names[opcode];
    return Common::StringFromFormat("%s%s\t0x%x", opname, cond_to_str(cond), addr);
}

std::string ARM_Disasm::DisassembleBX(uint32_t insn)
{
    uint8_t cond = (insn >> 28) & 0xf;
    uint8_t rn = insn & 0xf;
    return Common::StringFromFormat("bx%s\tr%d", cond_to_str(cond), rn);
}

std::string ARM_Disasm::DisassembleBKPT(uint32_t insn)
{
    uint8_t cond = (insn >> 28) & 0xf;
    uint32_t immed = (((insn >> 8) & 0xfff) << 4) | (insn & 0xf);
    return Common::StringFromFormat("bkpt%s\t#%d", cond_to_str(cond), immed);
}

std::string ARM_Disasm::DisassembleCLZ(uint32_t insn)
{
    uint8_t cond = (insn >> 28) & 0xf;
    uint8_t rd = (insn >> 12) & 0xf;
    uint8_t rm = insn & 0xf;
    return Common::StringFromFormat("clz%s\tr%d, r%d", cond_to_str(cond), rd, rm);
}

std::string ARM_Disasm::DisassembleMemblock(Opcode opcode, uint32_t insn)
{
    std::string tmp_list;

    uint8_t cond = (insn >> 28) & 0xf;
    uint8_t write_back = (insn >> 21) & 0x1;
    uint8_t bit_s = (insn >> 22) & 0x1;
    uint8_t is_up = (insn >> 23) & 0x1;
    uint8_t is_pre = (insn >> 24) & 0x1;
    uint8_t rn = (insn >> 16) & 0xf;
    uint16_t reg_list = insn & 0xffff;

    const char *opname = opcode_names[opcode];

    const char *bang = "";
    if (write_back)
        bang = "!";

    const char *carret = "";
    if (bit_s)
        carret = "^";

    const char *comma = "";
    tmp_list[0] = 0;
    for (int ii = 0; ii < 16; ++ii) {
        if (reg_list & (1 << ii)) {
            tmp_list += Common::StringFromFormat("%sr%d", comma, ii);
            comma = ",";
        }
    }

    const char *addr_mode = "";
    if (is_pre) {
        if (is_up) {
            addr_mode = "ib";
        } else {
            addr_mode = "db";
        }
    } else {
        if (is_up) {
            addr_mode = "ia";
        } else {
            addr_mode = "da";
        }
    }

    return Common::StringFromFormat("%s%s%s\tr%d%s, {%s}%s",
            opname, cond_to_str(cond), addr_mode, rn, bang, tmp_list.c_str(), carret);
}

std::string ARM_Disasm::DisassembleMem(uint32_t insn)
{
    uint8_t cond = (insn >> 28) & 0xf;
    uint8_t is_reg = (insn >> 25) & 0x1;
    uint8_t is_load = (insn >> 20) & 0x1;
    uint8_t write_back = (insn >> 21) & 0x1;
    uint8_t is_byte = (insn >> 22) & 0x1;
    uint8_t is_up = (insn >> 23) & 0x1;
    uint8_t is_pre = (insn >> 24) & 0x1;
    uint8_t rn = (insn >> 16) & 0xf;
    uint8_t rd = (insn >> 12) & 0xf;
    uint16_t offset = insn & 0xfff;

    const char *opname = "ldr";
    if (!is_load)
        opname = "str";

    const char *bang = "";
    if (write_back)
        bang = "!";

    const char *minus = "";
    if (is_up == 0)
        minus = "-";

    const char *byte = "";
    if (is_byte)
        byte = "b";

    if (is_reg == 0) {
        if (is_pre) {
            if (offset == 0) {
                return Common::StringFromFormat("%s%s%s\tr%d, [r%d]",
                        opname, cond_to_str(cond), byte, rd, rn);
            } else {
                return Common::StringFromFormat("%s%s%s\tr%d, [r%d, #%s%u]%s",
                        opname, cond_to_str(cond), byte, rd, rn, minus, offset, bang);
            }
        } else {
            const char *transfer = "";
            if (write_back)
                transfer = "t";

            return Common::StringFromFormat("%s%s%s%s\tr%d, [r%d], #%s%u",
                    opname, cond_to_str(cond), byte, transfer, rd, rn, minus, offset);
        }
    }

    uint8_t rm = insn & 0xf;
    uint8_t shift_type = (insn >> 5) & 0x3;
    uint8_t shift_amount = (insn >> 7) & 0x1f;

    const char *shift_name = shift_names[shift_type];

    if (is_pre) {
        if (shift_amount == 0) {
            if (shift_type == 0) {
                return Common::StringFromFormat("%s%s%s\tr%d, [r%d, %sr%d]%s",
                        opname, cond_to_str(cond), byte, rd, rn, minus, rm, bang);
            }
            if (shift_type == 3) {
                return Common::StringFromFormat("%s%s%s\tr%d, [r%d, %sr%d, RRX]%s",
                        opname, cond_to_str(cond), byte, rd, rn, minus, rm, bang);
            }
            shift_amount = 32;
        }
        return Common::StringFromFormat("%s%s%s\tr%d, [r%d, %sr%d, %s #%u]%s",
                opname, cond_to_str(cond), byte, rd, rn, minus, rm,
                shift_name, shift_amount, bang);
    }

    const char *transfer = "";
    if (write_back)
        transfer = "t";

    if (shift_amount == 0) {
        if (shift_type == 0) {
            return Common::StringFromFormat("%s%s%s%s\tr%d, [r%d], %sr%d",
                    opname, cond_to_str(cond), byte, transfer, rd, rn, minus, rm);
        }
        if (shift_type == 3) {
            return Common::StringFromFormat("%s%s%s%s\tr%d, [r%d], %sr%d, RRX",
                    opname, cond_to_str(cond), byte, transfer, rd, rn, minus, rm);
        }
        shift_amount = 32;
    }

    return Common::StringFromFormat("%s%s%s%s\tr%d, [r%d], %sr%d, %s #%u",
            opname, cond_to_str(cond), byte, transfer, rd, rn, minus, rm,
            shift_name, shift_amount);
}

std::string ARM_Disasm::DisassembleMemHalf(uint32_t insn)
{
    uint8_t cond = (insn >> 28) & 0xf;
    uint8_t is_load = (insn >> 20) & 0x1;
    uint8_t write_back = (insn >> 21) & 0x1;
    uint8_t is_immed = (insn >> 22) & 0x1;
    uint8_t is_up = (insn >> 23) & 0x1;
    uint8_t is_pre = (insn >> 24) & 0x1;
    uint8_t rn = (insn >> 16) & 0xf;
    uint8_t rd = (insn >> 12) & 0xf;
    uint8_t bits_65 = (insn >> 5) & 0x3;
    uint8_t rm = insn & 0xf;
    uint8_t offset = (((insn >> 8) & 0xf) << 4) | (insn & 0xf);

    const char *opname = "ldr";
    if (is_load == 0)
        opname = "str";

    const char *width = "";
    if (bits_65 == 1)
        width = "h";
    else if (bits_65 == 2)
        width = "sb";
    else
        width = "sh";

    const char *bang = "";
    if (write_back)
        bang = "!";
    const char *minus = "";
    if (is_up == 0)
        minus = "-";

    if (is_immed) {
        if (is_pre) {
            if (offset == 0) {
                return Common::StringFromFormat("%s%sh\tr%d, [r%d]", opname, cond_to_str(cond), rd, rn);
            } else {
                return Common::StringFromFormat("%s%sh\tr%d, [r%d, #%s%u]%s",
                        opname, cond_to_str(cond), rd, rn, minus, offset, bang);
            }
        } else {
            return Common::StringFromFormat("%s%sh\tr%d, [r%d], #%s%u",
                    opname, cond_to_str(cond), rd, rn, minus, offset);
        }
    }

    if (is_pre) {
        return Common::StringFromFormat("%s%sh\tr%d, [r%d, %sr%d]%s",
                opname, cond_to_str(cond), rd, rn, minus, rm, bang);
    } else {
        return Common::StringFromFormat("%s%sh\tr%d, [r%d], %sr%d",
                opname, cond_to_str(cond), rd, rn, minus, rm);
    }
}

std::string ARM_Disasm::DisassembleMCR(Opcode opcode, uint32_t insn)
{
    uint8_t cond = (insn >> 28) & 0xf;
    uint8_t crn = (insn >> 16) & 0xf;
    uint8_t crd = (insn >> 12) & 0xf;
    uint8_t cpnum = (insn >> 8) & 0xf;
    uint8_t opcode2 = (insn >> 5) & 0x7;
    uint8_t crm = insn & 0xf;

    const char *opname = opcode_names[opcode];
    return Common::StringFromFormat("%s%s\t%d, 0, r%d, cr%d, cr%d, {%d}",
            opname, cond_to_str(cond), cpnum, crd, crn, crm, opcode2);
}

std::string ARM_Disasm::DisassembleMLA(Opcode opcode, uint32_t insn)
{
    uint8_t cond = (insn >> 28) & 0xf;
    uint8_t rd = (insn >> 16) & 0xf;
    uint8_t rn = (insn >> 12) & 0xf;
    uint8_t rs = (insn >> 8) & 0xf;
    uint8_t rm = insn & 0xf;
    uint8_t bit_s = (insn >> 20) & 1;

    const char *opname = opcode_names[opcode];
    return Common::StringFromFormat("%s%s%s\tr%d, r%d, r%d, r%d",
            opname, cond_to_str(cond), bit_s ? "s" : "", rd, rm, rs, rn);
}

std::string ARM_Disasm::DisassembleUMLAL(Opcode opcode, uint32_t insn)
{
    uint8_t cond = (insn >> 28) & 0xf;
    uint8_t rdhi = (insn >> 16) & 0xf;
    uint8_t rdlo = (insn >> 12) & 0xf;
    uint8_t rs = (insn >> 8) & 0xf;
    uint8_t rm = insn & 0xf;
    uint8_t bit_s = (insn >> 20) & 1;

    const char *opname = opcode_names[opcode];
    return Common::StringFromFormat("%s%s%s\tr%d, r%d, r%d, r%d",
            opname, cond_to_str(cond), bit_s ? "s" : "", rdlo, rdhi, rm, rs);
}

std::string ARM_Disasm::DisassembleMUL(Opcode opcode, uint32_t insn)
{
    uint8_t cond = (insn >> 28) & 0xf;
    uint8_t rd = (insn >> 16) & 0xf;
    uint8_t rs = (insn >> 8) & 0xf;
    uint8_t rm = insn & 0xf;
    uint8_t bit_s = (insn >> 20) & 1;

    const char *opname = opcode_names[opcode];
    return Common::StringFromFormat("%s%s%s\tr%d, r%d, r%d",
            opname, cond_to_str(cond), bit_s ? "s" : "", rd, rm, rs);
}

std::string ARM_Disasm::DisassembleMRS(uint32_t insn)
{
    uint8_t cond = (insn >> 28) & 0xf;
    uint8_t rd = (insn >> 12) & 0xf;
    uint8_t ps = (insn >> 22) & 1;

    return Common::StringFromFormat("mrs%s\tr%d, %s", cond_to_str(cond), rd, ps ? "spsr" : "cpsr");
}

std::string ARM_Disasm::DisassembleMSR(uint32_t insn)
{
    char flags[8];
    int flag_index = 0;
    uint8_t cond = (insn >> 28) & 0xf;
    uint8_t is_immed = (insn >> 25) & 0x1;
    uint8_t pd = (insn >> 22) & 1;
    uint8_t mask = (insn >> 16) & 0xf;

    if (mask & 1)
        flags[flag_index++] = 'c';
    if (mask & 2)
        flags[flag_index++] = 'x';
    if (mask & 4)
        flags[flag_index++] = 's';
    if (mask & 8)
        flags[flag_index++] = 'f';
    flags[flag_index] = 0;

    if (is_immed) {
        uint32_t immed = insn & 0xff;
        uint8_t rotate = (insn >> 8) & 0xf;
        uint8_t rotate2 = rotate << 1;
        uint32_t rotated_val = (immed >> rotate2) | (immed << (32 - rotate2));
        return Common::StringFromFormat("msr%s\t%s_%s, #0x%x",
                cond_to_str(cond), pd ? "spsr" : "cpsr", flags, rotated_val);
    }

    uint8_t rm = insn & 0xf;

    return Common::StringFromFormat("msr%s\t%s_%s, r%d",
            cond_to_str(cond), pd ? "spsr" : "cpsr", flags, rm);
}

std::string ARM_Disasm::DisassembleNoOperands(Opcode opcode, uint32_t insn)
{
    uint32_t cond = BITS(insn, 28, 31);
    return Common::StringFromFormat("%s%s", opcode_names[opcode], cond_to_str(cond));
}

std::string ARM_Disasm::DisassemblePKH(uint32_t insn)
{
    uint32_t cond = BITS(insn, 28, 31);
    uint32_t rn = BITS(insn, 16, 19);
    uint32_t rd = BITS(insn, 12, 15);
    uint32_t imm5 = BITS(insn, 7, 11);
    uint32_t tb = BIT(insn, 6);
    uint32_t rm = BITS(insn, 0, 3);

    std::string suffix = tb ? "tb" : "bt";
    std::string shift = "";

    if (tb && imm5 == 0)
        imm5 = 32;

    if (imm5 > 0) {
        shift = tb ? ", ASR" : ", LSL";
        shift += " #" + std::to_string(imm5);
    }

    return Common::StringFromFormat("pkh%s%s\tr%u, r%u, r%u%s", suffix.c_str(), cond_to_str(cond),
                                    rd, rn, rm, shift.c_str());
}

std::string ARM_Disasm::DisassemblePLD(uint32_t insn)
{
    uint8_t is_reg = (insn >> 25) & 0x1;
    uint8_t is_up = (insn >> 23) & 0x1;
    uint8_t rn = (insn >> 16) & 0xf;

    const char *minus = "";
    if (is_up == 0)
        minus = "-";

    if (is_reg) {
        uint8_t rm = insn & 0xf;
        return Common::StringFromFormat("pld\t[r%d, %sr%d]", rn, minus, rm);
    }

    uint16_t offset = insn & 0xfff;
    if (offset == 0) {
        return Common::StringFromFormat("pld\t[r%d]", rn);
    } else {
        return Common::StringFromFormat("pld\t[r%d, #%s%u]", rn, minus, offset);
    }
}

std::string ARM_Disasm::DisassembleREX(Opcode opcode, uint32_t insn) {
    uint32_t rn = BITS(insn, 16, 19);
    uint32_t rd = BITS(insn, 12, 15);
    uint32_t rt = BITS(insn, 0, 3);
    uint32_t cond = BITS(insn, 28, 31);

    switch (opcode) {
        case OP_STREX:
        case OP_STREXB:
        case OP_STREXH:
            return Common::StringFromFormat("%s%s\tr%d, r%d, [r%d]", opcode_names[opcode],
                                            cond_to_str(cond), rd, rt, rn);
        case OP_STREXD:
            return Common::StringFromFormat("%s%s\tr%d, r%d, r%d, [r%d]", opcode_names[opcode],
                                            cond_to_str(cond), rd, rt, rt + 1, rn);

        // for LDREX instructions, rd corresponds to Rt from reference manual
        case OP_LDREX:
        case OP_LDREXB:
        case OP_LDREXH:
            return Common::StringFromFormat("%s%s\tr%d, [r%d]", opcode_names[opcode],
                                            cond_to_str(cond), rd, rn);
        case OP_LDREXD:
            return Common::StringFromFormat("%s%s\tr%d, r%d, [r%d]", opcode_names[opcode],
                                            cond_to_str(cond), rd, rd + 1, rn);
        default:
            return opcode_names[OP_UNDEFINED];
    }
}

std::string ARM_Disasm::DisassembleSAT(Opcode opcode, uint32_t insn) {
    uint32_t cond = BITS(insn, 28, 31);
    uint32_t sat_imm = BITS(insn, 16, 20);
    uint32_t rd = BITS(insn, 12, 15);
    uint32_t imm5 = BITS(insn, 7, 11);
    uint32_t sh = BIT(insn, 6);
    uint32_t rn = BITS(insn, 0, 3);

    std::string shift_part = "";
    bool opcode_has_shift = (opcode == OP_SSAT) || (opcode == OP_USAT);
    if (opcode_has_shift && !(sh == 0 && imm5 == 0)) {
        if (sh == 0)
            shift_part += ", LSL #";
        else
            shift_part += ", ASR #";

        if (imm5 == 0)
            imm5 = 32;
        shift_part += std::to_string(imm5);
    }

    if (opcode == OP_SSAT || opcode == OP_SSAT16)
        sat_imm++;

    return Common::StringFromFormat("%s%s\tr%u, #%u, r%u%s", opcode_names[opcode], cond_to_str(cond), rd,
                                    sat_imm, rn, shift_part.c_str());
}

std::string ARM_Disasm::DisassembleSEL(uint32_t insn) {
    uint32_t cond = BITS(insn, 28, 31);
    uint32_t rn = BITS(insn, 16, 19);
    uint32_t rd = BITS(insn, 12, 15);
    uint32_t rm = BITS(insn, 0, 3);

    return Common::StringFromFormat("%s%s\tr%u, r%u, r%u", opcode_names[OP_SEL], cond_to_str(cond),
                                    rd, rn, rm);
}

std::string ARM_Disasm::DisassembleSWI(uint32_t insn)
{
    uint8_t cond = (insn >> 28) & 0xf;
    uint32_t sysnum = insn & 0x00ffffff;

    return Common::StringFromFormat("swi%s 0x%x", cond_to_str(cond), sysnum);
}

std::string ARM_Disasm::DisassembleSWP(Opcode opcode, uint32_t insn)
{
    uint8_t cond = (insn >> 28) & 0xf;
    uint8_t rn = (insn >> 16) & 0xf;
    uint8_t rd = (insn >> 12) & 0xf;
    uint8_t rm = insn & 0xf;

    const char *opname = opcode_names[opcode];
    return Common::StringFromFormat("%s%s\tr%d, r%d, [r%d]", opname, cond_to_str(cond), rd, rm, rn);
}

std::string ARM_Disasm::DisassembleXT(Opcode opcode, uint32_t insn)
{
    uint32_t cond = BITS(insn, 28, 31);
    uint32_t rn = BITS(insn, 16, 19);
    uint32_t rd = BITS(insn, 12, 15);
    uint32_t rotate = BITS(insn, 10, 11);
    uint32_t rm = BITS(insn, 0, 3);

    std::string rn_part = "";
    static std::unordered_set<Opcode, std::hash<int>> extend_with_add = {
        OP_SXTAB, OP_SXTAB16, OP_SXTAH,
        OP_UXTAB, OP_UXTAB16, OP_UXTAH
    };
    if (extend_with_add.find(opcode) != extend_with_add.end())
        rn_part = ", r" + std::to_string(rn);

    std::string rotate_part = "";
    if (rotate != 0)
        rotate_part = ", ROR #" + std::to_string(rotate << 3);

    return Common::StringFromFormat("%s%s\tr%u%s, r%u%s", opcode_names[opcode], cond_to_str(cond),
                                    rd, rn_part.c_str(), rm, rotate_part.c_str());
}

Opcode ARM_Disasm::Decode(uint32_t insn) {
    uint32_t bits27_26 = (insn >> 26) & 0x3;
    switch (bits27_26) {
        case 0x0:
            return Decode00(insn);
        case 0x1:
            return Decode01(insn);
        case 0x2:
            return Decode10(insn);
        case 0x3:
            return Decode11(insn);
    }
    return OP_INVALID;
}

Opcode ARM_Disasm::Decode00(uint32_t insn) {
    uint8_t bit25 = (insn >> 25) & 0x1;
    uint8_t bit4 = (insn >> 4) & 0x1;
    if (bit25 == 0 && bit4 == 1) {
        if ((insn & 0x0ffffff0) == 0x012fff10) {
            // Bx instruction
            return OP_BX;
        }
        if ((insn & 0x0ff000f0) == 0x01600010) {
            // Clz instruction
            return OP_CLZ;
        }
        if ((insn & 0xfff000f0) == 0xe1200070) {
            // Bkpt instruction
            return OP_BKPT;
        }
        uint32_t bits7_4 = (insn >> 4) & 0xf;
        if (bits7_4 == 0x9) {
            uint32_t bit24 = BIT(insn, 24);
            if (bit24) {
                return DecodeSyncPrimitive(insn);
            }
            // One of the multiply instructions
            return DecodeMUL(insn);
        }

        uint8_t bit7 = (insn >> 7) & 0x1;
        if (bit7 == 1) {
            // One of the load/store halfword/byte instructions
            return DecodeLDRH(insn);
        }
    }

    uint32_t op1 = BITS(insn, 20, 24);
    if (bit25 && (op1 == 0x12 || op1 == 0x16)) {
        // One of the MSR (immediate) and hints instructions
        return DecodeMSRImmAndHints(insn);
    }

    // One of the data processing instructions
    return DecodeALU(insn);
}

Opcode ARM_Disasm::Decode01(uint32_t insn) {
    uint8_t is_reg = (insn >> 25) & 0x1;
    uint8_t bit4 = (insn >> 4) & 0x1;
    if (is_reg == 1 && bit4 == 1)
        return DecodeMedia(insn);
    uint8_t is_load = (insn >> 20) & 0x1;
    uint8_t is_byte = (insn >> 22) & 0x1;
    if ((insn & 0xfd70f000) == 0xf550f000) {
        // Pre-load
        return OP_PLD;
    }
    if (insn == 0xf57ff01f) {
        // Clear-Exclusive
        return OP_CLREX;
    }
    if (is_load) {
        if (is_byte) {
            // Load byte
            return OP_LDRB;
        }
        // Load word
        return OP_LDR;
    }
    if (is_byte) {
        // Store byte
        return OP_STRB;
    }
    // Store word
    return OP_STR;
}

Opcode ARM_Disasm::Decode10(uint32_t insn) {
    uint8_t bit25 = (insn >> 25) & 0x1;
    if (bit25 == 0) {
        // LDM/STM
        uint8_t is_load = (insn >> 20) & 0x1;
        if (is_load)
            return OP_LDM;
        return OP_STM;
    }

    // Branch with link
    if ((insn >> 24) & 1)
        return OP_BL;

    return OP_B;
}

Opcode ARM_Disasm::Decode11(uint32_t insn) {
    uint8_t bit25 = (insn >> 25) & 0x1;
    if (bit25 == 0) {
        // LDC, SDC
        uint8_t is_load = (insn >> 20) & 0x1;
        if (is_load) {
            // LDC
            return OP_LDC;
        }
        // STC
        return OP_STC;
    }

    uint8_t bit24 = (insn >> 24) & 0x1;
    if (bit24 == 0x1) {
        // SWI
        return OP_SWI;
    }

    uint8_t bit4 = (insn >> 4) & 0x1;
    uint8_t cpnum = (insn >> 8) & 0xf;

    if (cpnum == 15) {
        // Special case for coprocessor 15
        uint8_t opcode = (insn >> 21) & 0x7;
        if (bit4 == 0 || opcode != 0) {
            // This is an unexpected bit pattern.  Create an undefined
            // instruction in case this is ever executed.
            return OP_UNDEFINED;
        }

        // MRC, MCR
        uint8_t is_mrc = (insn >> 20) & 0x1;
        if (is_mrc)
            return OP_MRC;
        return OP_MCR;
    }

    if (bit4 == 0) {
        // CDP
        return OP_CDP;
    }
    // MRC, MCR
    uint8_t is_mrc = (insn >> 20) & 0x1;
    if (is_mrc)
        return OP_MRC;
    return OP_MCR;
}

Opcode ARM_Disasm::DecodeSyncPrimitive(uint32_t insn) {
    uint32_t op = BITS(insn, 20, 23);
    uint32_t bit22 = BIT(insn, 22);
    switch (op) {
        case 0x0:
            if (bit22)
                return OP_SWPB;
            return OP_SWP;
        case 0x8:
            return OP_STREX;
        case 0x9:
            return OP_LDREX;
        case 0xA:
            return OP_STREXD;
        case 0xB:
            return OP_LDREXD;
        case 0xC:
            return OP_STREXB;
        case 0xD:
            return OP_LDREXB;
        case 0xE:
            return OP_STREXH;
        case 0xF:
            return OP_LDREXH;
        default:
            return OP_UNDEFINED;
    }
}

Opcode ARM_Disasm::DecodePackingSaturationReversal(uint32_t insn) {
    uint32_t op1 = BITS(insn, 20, 22);
    uint32_t a = BITS(insn, 16, 19);
    uint32_t op2 = BITS(insn, 5, 7);

    switch (op1) {
        case 0x0:
            if (BIT(op2, 0) == 0)
                return OP_PKH;
            if (op2 == 0x3 && a != 0xf)
                return OP_SXTAB16;
            if (op2 == 0x3 && a == 0xf)
                return OP_SXTB16;
            if (op2 == 0x5)
                return OP_SEL;
            break;
        case 0x2:
            if (BIT(op2, 0) == 0)
                return OP_SSAT;
            if (op2 == 0x1)
                return OP_SSAT16;
            if (op2 == 0x3 && a != 0xf)
                return OP_SXTAB;
            if (op2 == 0x3 && a == 0xf)
                return OP_SXTB;
            break;
        case 0x3:
            if (BIT(op2, 0) == 0)
                return OP_SSAT;
            if (op2 == 0x3 && a != 0xf)
                return OP_SXTAH;
            if (op2 == 0x3 && a == 0xf)
                return OP_SXTH;
            break;
        case 0x4:
            if (op2 == 0x3 && a != 0xf)
                return OP_UXTAB16;
            if (op2 == 0x3 && a == 0xf)
                return OP_UXTB16;
            break;
        case 0x6:
            if (BIT(op2, 0) == 0)
                return OP_USAT;
            if (op2 == 0x1)
                return OP_USAT16;
            if (op2 == 0x3 && a != 0xf)
                return OP_UXTAB;
            if (op2 == 0x3 && a == 0xf)
                return OP_UXTB;
            break;
        case 0x7:
            if (BIT(op2, 0) == 0)
                return OP_USAT;
            if (op2 == 0x3 && a != 0xf)
                return OP_UXTAH;
            if (op2 == 0x3 && a == 0xf)
                return OP_UXTH;
            break;
        default:
            break;
    }

    return OP_UNDEFINED;
}

Opcode ARM_Disasm::DecodeMUL(uint32_t insn) {
    uint8_t bit24 = (insn >> 24) & 0x1;
    if (bit24 != 0) {
        // This is an unexpected bit pattern.  Create an undefined
        // instruction in case this is ever executed.
        return OP_UNDEFINED;
    }
    uint8_t bit23 = (insn >> 23) & 0x1;
    uint8_t bit22_U = (insn >> 22) & 0x1;
    uint8_t bit21_A = (insn >> 21) & 0x1;
    if (bit23 == 0) {
        // 32-bit multiply
        if (bit22_U != 0) {
            // This is an unexpected bit pattern.  Create an undefined
            // instruction in case this is ever executed.
            return OP_UNDEFINED;
        }
        if (bit21_A == 0)
            return OP_MUL;
        return OP_MLA;
    }
    // 64-bit multiply
    if (bit22_U == 0) {
        // Unsigned multiply long
        if (bit21_A == 0)
            return OP_UMULL;
        return OP_UMLAL;
    }
    // Signed multiply long
    if (bit21_A == 0)
        return OP_SMULL;
    return OP_SMLAL;
}

Opcode ARM_Disasm::DecodeMSRImmAndHints(uint32_t insn) {
    uint32_t op = BIT(insn, 22);
    uint32_t op1 = BITS(insn, 16, 19);
    uint32_t op2 = BITS(insn, 0, 7);

    if (op == 0 && op1 == 0) {
        switch (op2) {
            case 0x0:
                return OP_NOP;
            case 0x1:
                return OP_YIELD;
            case 0x2:
                return OP_WFE;
            case 0x3:
                return OP_WFI;
            case 0x4:
                return OP_SEV;
            default:
                return OP_UNDEFINED;
        }
    }

    return OP_MSR;
}

Opcode ARM_Disasm::DecodeMedia(uint32_t insn) {
    uint32_t op1 = BITS(insn, 20, 24);
    uint32_t rd = BITS(insn, 12, 15);
    uint32_t op2 = BITS(insn, 5, 7);
    uint32_t rn = BITS(insn, 0, 3);

    switch (BITS(op1, 3, 4)) {
        case 0x1:
            // Packing, unpacking, saturation, and reversal
            return DecodePackingSaturationReversal(insn);
        default:
            break;
    }

    return OP_UNDEFINED;
}

Opcode ARM_Disasm::DecodeLDRH(uint32_t insn) {
    uint8_t is_load = (insn >> 20) & 0x1;
    uint8_t bits_65 = (insn >> 5) & 0x3;
    if (is_load) {
        if (bits_65 == 0x1) {
            // Load unsigned halfword
            return OP_LDRH;
        } else if (bits_65 == 0x2) {
            // Load signed byte
            return OP_LDRSB;
        }
        // Signed halfword
        if (bits_65 != 0x3) {
            // This is an unexpected bit pattern.  Create an undefined
            // instruction in case this is ever executed.
            return OP_UNDEFINED;
        }
        // Load signed halfword
        return OP_LDRSH;
    }
    // Store halfword
    if (bits_65 != 0x1) {
        // This is an unexpected bit pattern.  Create an undefined
        // instruction in case this is ever executed.
        return OP_UNDEFINED;
    }
    // Store halfword
    return OP_STRH;
}

Opcode ARM_Disasm::DecodeALU(uint32_t insn) {
    uint8_t is_immed = (insn >> 25) & 0x1;
    uint8_t opcode = (insn >> 21) & 0xf;
    uint8_t bit_s = (insn >> 20) & 1;
    uint8_t shift_is_reg = (insn >> 4) & 1;
    uint8_t bit7 = (insn >> 7) & 1;
    if (!is_immed && shift_is_reg && (bit7 != 0)) {
        // This is an unexpected bit pattern.  Create an undefined
        // instruction in case this is ever executed.
        return OP_UNDEFINED;
    }
    switch (opcode) {
        case 0x0:
            return OP_AND;
        case 0x1:
            return OP_EOR;
        case 0x2:
            return OP_SUB;
        case 0x3:
            return OP_RSB;
        case 0x4:
            return OP_ADD;
        case 0x5:
            return OP_ADC;
        case 0x6:
            return OP_SBC;
        case 0x7:
            return OP_RSC;
        case 0x8:
            if (bit_s)
                return OP_TST;
            return OP_MRS;
        case 0x9:
            if (bit_s)
                return OP_TEQ;
            return OP_MSR;
        case 0xa:
            if (bit_s)
                return OP_CMP;
            return OP_MRS;
        case 0xb:
            if (bit_s)
                return OP_CMN;
            return OP_MSR;
        case 0xc:
            return OP_ORR;
        case 0xd:
            return OP_MOV;
        case 0xe:
            return OP_BIC;
        case 0xf:
            return OP_MVN;
    }
    // Unreachable
    return OP_INVALID;
}