| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* ppc-dis.c -- Disassemble PowerPC instructions |
| Copyright (C) 1994-2016 Free Software Foundation, Inc. |
| Written by Ian Lance Taylor, Cygnus Support |
| |
| This file is part of GDB, GAS, and the GNU binutils. |
| |
| */ |
| |
| #include <asm/cputable.h> |
| #include <asm/cpu_has_feature.h> |
| #include "nonstdio.h" |
| #include "ansidecl.h" |
| #include "ppc.h" |
| #include "dis-asm.h" |
| |
| /* This file provides several disassembler functions, all of which use |
| the disassembler interface defined in dis-asm.h. Several functions |
| are provided because this file handles disassembly for the PowerPC |
| in both big and little endian mode and also for the POWER (RS/6000) |
| chip. */ |
| |
| /* Extract the operand value from the PowerPC or POWER instruction. */ |
| |
| static long |
| operand_value_powerpc (const struct powerpc_operand *operand, |
| unsigned long insn, ppc_cpu_t dialect) |
| { |
| long value; |
| int invalid; |
| /* Extract the value from the instruction. */ |
| if (operand->extract) |
| value = (*operand->extract) (insn, dialect, &invalid); |
| else |
| { |
| if (operand->shift >= 0) |
| value = (insn >> operand->shift) & operand->bitm; |
| else |
| value = (insn << -operand->shift) & operand->bitm; |
| if ((operand->flags & PPC_OPERAND_SIGNED) != 0) |
| { |
| /* BITM is always some number of zeros followed by some |
| number of ones, followed by some number of zeros. */ |
| unsigned long top = operand->bitm; |
| /* top & -top gives the rightmost 1 bit, so this |
| fills in any trailing zeros. */ |
| top |= (top & -top) - 1; |
| top &= ~(top >> 1); |
| value = (value ^ top) - top; |
| } |
| } |
| |
| return value; |
| } |
| |
| /* Determine whether the optional operand(s) should be printed. */ |
| |
| static int |
| skip_optional_operands (const unsigned char *opindex, |
| unsigned long insn, ppc_cpu_t dialect) |
| { |
| const struct powerpc_operand *operand; |
| |
| for (; *opindex != 0; opindex++) |
| { |
| operand = &powerpc_operands[*opindex]; |
| if ((operand->flags & PPC_OPERAND_NEXT) != 0 |
| || ((operand->flags & PPC_OPERAND_OPTIONAL) != 0 |
| && operand_value_powerpc (operand, insn, dialect) != |
| ppc_optional_operand_value (operand))) |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| /* Find a match for INSN in the opcode table, given machine DIALECT. |
| A DIALECT of -1 is special, matching all machine opcode variations. */ |
| |
| static const struct powerpc_opcode * |
| lookup_powerpc (unsigned long insn, ppc_cpu_t dialect) |
| { |
| const struct powerpc_opcode *opcode; |
| const struct powerpc_opcode *opcode_end; |
| |
| opcode_end = powerpc_opcodes + powerpc_num_opcodes; |
| /* Find the first match in the opcode table for this major opcode. */ |
| for (opcode = powerpc_opcodes; opcode < opcode_end; ++opcode) |
| { |
| const unsigned char *opindex; |
| const struct powerpc_operand *operand; |
| int invalid; |
| |
| if ((insn & opcode->mask) != opcode->opcode |
| || (dialect != (ppc_cpu_t) -1 |
| && ((opcode->flags & dialect) == 0 |
| || (opcode->deprecated & dialect) != 0))) |
| continue; |
| |
| /* Check validity of operands. */ |
| invalid = 0; |
| for (opindex = opcode->operands; *opindex != 0; opindex++) |
| { |
| operand = powerpc_operands + *opindex; |
| if (operand->extract) |
| (*operand->extract) (insn, dialect, &invalid); |
| } |
| if (invalid) |
| continue; |
| |
| return opcode; |
| } |
| |
| return NULL; |
| } |
| |
| /* Print a PowerPC or POWER instruction. */ |
| |
| int print_insn_powerpc (unsigned long insn, unsigned long memaddr) |
| { |
| const struct powerpc_opcode *opcode; |
| bool insn_is_short; |
| ppc_cpu_t dialect; |
| |
| dialect = PPC_OPCODE_PPC | PPC_OPCODE_COMMON; |
| |
| if (IS_ENABLED(CONFIG_PPC64)) |
| dialect |= PPC_OPCODE_64 | PPC_OPCODE_POWER4 | PPC_OPCODE_CELL | |
| PPC_OPCODE_POWER5 | PPC_OPCODE_POWER6 | PPC_OPCODE_POWER7 | PPC_OPCODE_POWER8 | |
| PPC_OPCODE_POWER9; |
| |
| if (cpu_has_feature(CPU_FTR_TM)) |
| dialect |= PPC_OPCODE_HTM; |
| |
| if (cpu_has_feature(CPU_FTR_ALTIVEC)) |
| dialect |= PPC_OPCODE_ALTIVEC | PPC_OPCODE_ALTIVEC2; |
| |
| if (cpu_has_feature(CPU_FTR_VSX)) |
| dialect |= PPC_OPCODE_VSX | PPC_OPCODE_VSX3; |
| |
| /* Get the major opcode of the insn. */ |
| opcode = NULL; |
| insn_is_short = false; |
| |
| if (opcode == NULL) |
| opcode = lookup_powerpc (insn, dialect); |
| if (opcode == NULL && (dialect & PPC_OPCODE_ANY) != 0) |
| opcode = lookup_powerpc (insn, (ppc_cpu_t) -1); |
| |
| if (opcode != NULL) |
| { |
| const unsigned char *opindex; |
| const struct powerpc_operand *operand; |
| int need_comma; |
| int need_paren; |
| int skip_optional; |
| |
| if (opcode->operands[0] != 0) |
| printf("%-7s ", opcode->name); |
| else |
| printf("%s", opcode->name); |
| |
| if (insn_is_short) |
| /* The operands will be fetched out of the 16-bit instruction. */ |
| insn >>= 16; |
| |
| /* Now extract and print the operands. */ |
| need_comma = 0; |
| need_paren = 0; |
| skip_optional = -1; |
| for (opindex = opcode->operands; *opindex != 0; opindex++) |
| { |
| long value; |
| |
| operand = powerpc_operands + *opindex; |
| |
| /* Operands that are marked FAKE are simply ignored. We |
| already made sure that the extract function considered |
| the instruction to be valid. */ |
| if ((operand->flags & PPC_OPERAND_FAKE) != 0) |
| continue; |
| |
| /* If all of the optional operands have the value zero, |
| then don't print any of them. */ |
| if ((operand->flags & PPC_OPERAND_OPTIONAL) != 0) |
| { |
| if (skip_optional < 0) |
| skip_optional = skip_optional_operands (opindex, insn, |
| dialect); |
| if (skip_optional) |
| continue; |
| } |
| |
| value = operand_value_powerpc (operand, insn, dialect); |
| |
| if (need_comma) |
| { |
| printf(","); |
| need_comma = 0; |
| } |
| |
| /* Print the operand as directed by the flags. */ |
| if ((operand->flags & PPC_OPERAND_GPR) != 0 |
| || ((operand->flags & PPC_OPERAND_GPR_0) != 0 && value != 0)) |
| printf("r%ld", value); |
| else if ((operand->flags & PPC_OPERAND_FPR) != 0) |
| printf("f%ld", value); |
| else if ((operand->flags & PPC_OPERAND_VR) != 0) |
| printf("v%ld", value); |
| else if ((operand->flags & PPC_OPERAND_VSR) != 0) |
| printf("vs%ld", value); |
| else if ((operand->flags & PPC_OPERAND_RELATIVE) != 0) |
| print_address(memaddr + value); |
| else if ((operand->flags & PPC_OPERAND_ABSOLUTE) != 0) |
| print_address(value & 0xffffffff); |
| else if ((operand->flags & PPC_OPERAND_FSL) != 0) |
| printf("fsl%ld", value); |
| else if ((operand->flags & PPC_OPERAND_FCR) != 0) |
| printf("fcr%ld", value); |
| else if ((operand->flags & PPC_OPERAND_UDI) != 0) |
| printf("%ld", value); |
| else if ((operand->flags & PPC_OPERAND_CR_REG) != 0 |
| && (((dialect & PPC_OPCODE_PPC) != 0) |
| || ((dialect & PPC_OPCODE_VLE) != 0))) |
| printf("cr%ld", value); |
| else if (((operand->flags & PPC_OPERAND_CR_BIT) != 0) |
| && (((dialect & PPC_OPCODE_PPC) != 0) |
| || ((dialect & PPC_OPCODE_VLE) != 0))) |
| { |
| static const char *cbnames[4] = { "lt", "gt", "eq", "so" }; |
| int cr; |
| int cc; |
| |
| cr = value >> 2; |
| if (cr != 0) |
| printf("4*cr%d+", cr); |
| cc = value & 3; |
| printf("%s", cbnames[cc]); |
| } |
| else |
| printf("%d", (int) value); |
| |
| if (need_paren) |
| { |
| printf(")"); |
| need_paren = 0; |
| } |
| |
| if ((operand->flags & PPC_OPERAND_PARENS) == 0) |
| need_comma = 1; |
| else |
| { |
| printf("("); |
| need_paren = 1; |
| } |
| } |
| |
| /* We have found and printed an instruction. |
| If it was a short VLE instruction we have more to do. */ |
| if (insn_is_short) |
| { |
| memaddr += 2; |
| return 2; |
| } |
| else |
| /* Otherwise, return. */ |
| return 4; |
| } |
| |
| /* We could not find a match. */ |
| printf(".long 0x%lx", insn); |
| |
| return 4; |
| } |