| /* s390-dis.c -- Disassemble S390 instructions |
| Copyright (C) 2000-2024 Free Software Foundation, Inc. |
| Contributed by Martin Schwidefsky (schwidefsky@de.ibm.com). |
| |
| This file is part of the GNU opcodes library. |
| |
| This library is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3, or (at your option) |
| any later version. |
| |
| It is distributed in the hope that it will be useful, but WITHOUT |
| ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
| or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public |
| License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this file; see the file COPYING. If not, write to the |
| Free Software Foundation, 51 Franklin Street - Fifth Floor, Boston, |
| MA 02110-1301, USA. */ |
| |
| #include "sysdep.h" |
| #include <stdio.h> |
| #include "ansidecl.h" |
| #include "disassemble.h" |
| #include "opintl.h" |
| #include "opcode/s390.h" |
| #include "libiberty.h" |
| #include "dis-asm.h" |
| |
| static int opc_index[256]; |
| static int current_arch_mask = 0; |
| static int option_use_insn_len_bits_p = 0; |
| static int option_print_insn_desc = 0; |
| |
| typedef struct |
| { |
| const char *name; |
| const char *description; |
| } s390_options_t; |
| |
| static const s390_options_t options[] = |
| { |
| { "esa" , N_("Disassemble in ESA architecture mode") }, |
| /* TRANSLATORS: Please do not translate 'z/Architecture' as this is a technical name. */ |
| { "zarch", N_("Disassemble in z/Architecture mode") }, |
| { "insnlength", N_("Print unknown instructions according to " |
| "length from first two bits") }, |
| { "insndesc", N_("Print instruction description as comment") }, |
| }; |
| |
| /* Set up index table for first opcode byte. */ |
| |
| void |
| disassemble_init_s390 (struct disassemble_info *info) |
| { |
| int i; |
| const char *p; |
| |
| memset (opc_index, 0, sizeof (opc_index)); |
| |
| /* Reverse order, such that each opc_index ends up pointing to the |
| first matching entry instead of the last. */ |
| for (i = s390_num_opcodes; i--; ) |
| opc_index[s390_opcodes[i].opcode[0]] = i; |
| |
| current_arch_mask = 1 << S390_OPCODE_ZARCH; |
| option_use_insn_len_bits_p = 0; |
| option_print_insn_desc = 0; |
| |
| for (p = info->disassembler_options; p != NULL; ) |
| { |
| if (startswith (p, "esa")) |
| current_arch_mask = 1 << S390_OPCODE_ESA; |
| else if (startswith (p, "zarch")) |
| current_arch_mask = 1 << S390_OPCODE_ZARCH; |
| else if (startswith (p, "insnlength")) |
| option_use_insn_len_bits_p = 1; |
| else if (startswith (p, "insndesc")) |
| option_print_insn_desc = 1; |
| else |
| /* xgettext:c-format */ |
| opcodes_error_handler (_("unknown S/390 disassembler option: %s"), p); |
| |
| p = strchr (p, ','); |
| if (p != NULL) |
| p++; |
| } |
| } |
| |
| /* Derive the length of an instruction from its first byte. */ |
| |
| static inline int |
| s390_insn_length (const bfd_byte *buffer) |
| { |
| /* 00xxxxxx -> 2, 01xxxxxx/10xxxxxx -> 4, 11xxxxxx -> 6. */ |
| return ((buffer[0] >> 6) + 3) & ~1U; |
| } |
| |
| /* Match the instruction in BUFFER against the given OPCODE, excluding |
| the first byte. */ |
| |
| static inline int |
| s390_insn_matches_opcode (const bfd_byte *buffer, |
| const struct s390_opcode *opcode) |
| { |
| return (buffer[1] & opcode->mask[1]) == opcode->opcode[1] |
| && (buffer[2] & opcode->mask[2]) == opcode->opcode[2] |
| && (buffer[3] & opcode->mask[3]) == opcode->opcode[3] |
| && (buffer[4] & opcode->mask[4]) == opcode->opcode[4] |
| && (buffer[5] & opcode->mask[5]) == opcode->opcode[5]; |
| } |
| |
| union operand_value |
| { |
| int i; |
| unsigned int u; |
| }; |
| |
| /* Extracts an operand value from an instruction. */ |
| /* We do not perform the shift operation for larl-type address |
| operands here since that would lead to an overflow of the 32 bit |
| integer value. Instead the shift operation is done when printing |
| the operand. */ |
| |
| static inline union operand_value |
| s390_extract_operand (const bfd_byte *insn, |
| const struct s390_operand *operand) |
| { |
| union operand_value ret; |
| unsigned int val; |
| int bits; |
| const bfd_byte *orig_insn = insn; |
| |
| /* Extract fragments of the operand byte for byte. */ |
| insn += operand->shift / 8; |
| bits = (operand->shift & 7) + operand->bits; |
| val = 0; |
| do |
| { |
| val <<= 8; |
| val |= (unsigned int) *insn++; |
| bits -= 8; |
| } |
| while (bits > 0); |
| val >>= -bits; |
| val &= ((1U << (operand->bits - 1)) << 1) - 1; |
| |
| /* Check for special long displacement case. */ |
| if (operand->bits == 20 && operand->shift == 20) |
| val = (val & 0xff) << 12 | (val & 0xfff00) >> 8; |
| |
| /* Sign extend value if the operand is signed or pc relative. Avoid |
| integer overflows. */ |
| if (operand->flags & (S390_OPERAND_SIGNED | S390_OPERAND_PCREL)) |
| { |
| unsigned int m = 1U << (operand->bits - 1); |
| |
| if (val >= m) |
| ret.i = (int) (val - m) - 1 - (int) (m - 1U); |
| else |
| ret.i = (int) val; |
| } |
| else if (operand->flags & S390_OPERAND_LENGTH) |
| /* Length x in an instruction has real length x + 1. */ |
| ret.u = val + 1; |
| |
| else if (operand->flags & S390_OPERAND_VR) |
| { |
| /* Extract the extra bits for a vector register operand stored |
| in the RXB field. */ |
| unsigned vr = operand->shift == 32 ? 3 |
| : (unsigned) operand->shift / 4 - 2; |
| |
| ret.u = val | ((orig_insn[4] & (1 << (3 - vr))) << (vr + 1)); |
| } |
| else |
| ret.u = val; |
| |
| return ret; |
| } |
| |
| /* Print the S390 instruction in BUFFER, assuming that it matches the |
| given OPCODE. */ |
| |
| static void |
| s390_print_insn_with_opcode (bfd_vma memaddr, |
| struct disassemble_info *info, |
| const bfd_byte *buffer, |
| const struct s390_opcode *opcode) |
| { |
| const unsigned char *opindex; |
| char separator; |
| |
| /* Mnemonic. */ |
| info->fprintf_styled_func (info->stream, dis_style_mnemonic, |
| "%s", opcode->name); |
| |
| /* Operands. */ |
| separator = '\t'; |
| for (opindex = opcode->operands; *opindex != 0; opindex++) |
| { |
| const struct s390_operand *operand = s390_operands + *opindex; |
| union operand_value val = s390_extract_operand (buffer, operand); |
| unsigned long flags = operand->flags; |
| |
| /* Omit index register 0. */ |
| if ((flags & S390_OPERAND_INDEX) && val.u == 0) |
| continue; |
| /* Omit base register 0, if no or omitted index register 0. */ |
| if ((flags & S390_OPERAND_BASE) && val.u == 0 && separator == '(') |
| { |
| separator = ','; |
| continue; |
| } |
| |
| /* For instructions with a last optional operand don't print it |
| if zero. */ |
| if ((opcode->flags & (S390_INSTR_FLAG_OPTPARM | S390_INSTR_FLAG_OPTPARM2)) |
| && val.u == 0 |
| && opindex[1] == 0) |
| break; |
| |
| if ((opcode->flags & S390_INSTR_FLAG_OPTPARM2) |
| && val.u == 0 && opindex[1] != 0 && opindex[2] == 0) |
| { |
| union operand_value next_op_val = |
| s390_extract_operand (buffer, s390_operands + opindex[1]); |
| if (next_op_val.u == 0) |
| break; |
| } |
| |
| if (flags & S390_OPERAND_GPR) |
| { |
| info->fprintf_styled_func (info->stream, dis_style_text, |
| "%c", separator); |
| if ((flags & (S390_OPERAND_BASE | S390_OPERAND_INDEX)) |
| && val.u == 0) |
| info->fprintf_styled_func (info->stream, dis_style_register, |
| "%u", val.u); |
| else |
| info->fprintf_styled_func (info->stream, dis_style_register, |
| "%%r%u", val.u); |
| } |
| else if (flags & S390_OPERAND_FPR) |
| { |
| info->fprintf_styled_func (info->stream, dis_style_text, |
| "%c", separator); |
| info->fprintf_styled_func (info->stream, dis_style_register, |
| "%%f%u", val.u); |
| } |
| else if (flags & S390_OPERAND_VR) |
| { |
| info->fprintf_styled_func (info->stream, dis_style_text, |
| "%c", separator); |
| if ((flags & S390_OPERAND_INDEX) && val.u == 0) |
| info->fprintf_styled_func (info->stream, dis_style_register, |
| "%u", val.u); |
| else |
| info->fprintf_styled_func (info->stream, dis_style_register, |
| "%%v%i", val.u); |
| } |
| else if (flags & S390_OPERAND_AR) |
| { |
| info->fprintf_styled_func (info->stream, dis_style_text, |
| "%c", separator); |
| info->fprintf_styled_func (info->stream, dis_style_register, |
| "%%a%u", val.u); |
| } |
| else if (flags & S390_OPERAND_CR) |
| { |
| info->fprintf_styled_func (info->stream, dis_style_text, |
| "%c", separator); |
| info->fprintf_styled_func (info->stream, dis_style_register, |
| "%%c%u", val.u); |
| } |
| else if (flags & S390_OPERAND_PCREL) |
| { |
| bfd_vma target = memaddr + val.i + val.i; |
| |
| /* Provide info for jump visualization. May be evaluated by p_a_f(). */ |
| info->target = target; |
| |
| info->fprintf_styled_func (info->stream, dis_style_text, |
| "%c", separator); |
| info->print_address_func (target, info); |
| } |
| else if (flags & S390_OPERAND_SIGNED) |
| { |
| enum disassembler_style style; |
| |
| info->fprintf_styled_func (info->stream, dis_style_text, |
| "%c", separator); |
| style = ((flags & S390_OPERAND_DISP) |
| ? dis_style_address_offset : dis_style_immediate); |
| info->fprintf_styled_func (info->stream, style, "%i", val.i); |
| } |
| else |
| { |
| enum disassembler_style style; |
| |
| if (flags & S390_OPERAND_OR1) |
| val.u &= ~1; |
| if (flags & S390_OPERAND_OR2) |
| val.u &= ~2; |
| if (flags & S390_OPERAND_OR8) |
| val.u &= ~8; |
| |
| if ((opcode->flags & S390_INSTR_FLAG_OPTPARM) |
| && val.u == 0 |
| && opindex[1] == 0) |
| break; |
| info->fprintf_styled_func (info->stream, dis_style_text, |
| "%c", separator); |
| style = ((flags & S390_OPERAND_DISP) |
| ? dis_style_address_offset : dis_style_immediate); |
| info->fprintf_styled_func (info->stream, style, "%u", val.u); |
| } |
| |
| if (flags & S390_OPERAND_DISP) |
| separator = '('; |
| else if (flags & S390_OPERAND_BASE) |
| { |
| info->fprintf_styled_func (info->stream, dis_style_text, ")"); |
| separator = ','; |
| } |
| else |
| separator = ','; |
| } |
| |
| /* Optional: instruction name. */ |
| if (option_print_insn_desc && opcode->description |
| && opcode->description[0] != '\0') |
| info->fprintf_styled_func (info->stream, dis_style_comment_start, "\t# %s", |
| opcode->description); |
| } |
| |
| /* Check whether opcode A's mask is more specific than that of B. */ |
| |
| static int |
| opcode_mask_more_specific (const struct s390_opcode *a, |
| const struct s390_opcode *b) |
| { |
| return (((int) a->mask[0] + a->mask[1] + a->mask[2] |
| + a->mask[3] + a->mask[4] + a->mask[5]) |
| > ((int) b->mask[0] + b->mask[1] + b->mask[2] |
| + b->mask[3] + b->mask[4] + b->mask[5])); |
| } |
| |
| /* Print a S390 instruction. */ |
| |
| int |
| print_insn_s390 (bfd_vma memaddr, struct disassemble_info *info) |
| { |
| bfd_byte buffer[6]; |
| const struct s390_opcode *opcode = NULL; |
| unsigned int value; |
| int status, opsize, bufsize, bytes_to_dump, i; |
| |
| /* The output looks better if we put 6 bytes on a line. */ |
| info->bytes_per_line = 6; |
| |
| /* Set some defaults for the insn info. */ |
| info->insn_info_valid = 0; |
| info->branch_delay_insns = 0; |
| info->data_size = 0; |
| info->insn_type = dis_nonbranch; |
| info->target = 0; |
| info->target2 = 0; |
| |
| /* Every S390 instruction is max 6 bytes long. */ |
| memset (buffer, 0, 6); |
| status = info->read_memory_func (memaddr, buffer, 6, info); |
| if (status != 0) |
| { |
| for (bufsize = 0; bufsize < 6; bufsize++) |
| if (info->read_memory_func (memaddr, buffer, bufsize + 1, info) != 0) |
| break; |
| if (bufsize <= 0) |
| { |
| info->memory_error_func (status, memaddr, info); |
| return -1; |
| } |
| opsize = s390_insn_length (buffer); |
| status = opsize > bufsize; |
| } |
| else |
| { |
| bufsize = 6; |
| opsize = s390_insn_length (buffer); |
| } |
| |
| if (status == 0) |
| { |
| const struct s390_opcode *op; |
| |
| /* Find the "best match" in the opcode table. */ |
| for (op = s390_opcodes + opc_index[buffer[0]]; |
| op != s390_opcodes + s390_num_opcodes |
| && op->opcode[0] == buffer[0]; |
| op++) |
| { |
| if ((op->modes & current_arch_mask) |
| && s390_insn_matches_opcode (buffer, op) |
| && (opcode == NULL |
| || opcode_mask_more_specific (op, opcode))) |
| opcode = op; |
| } |
| |
| if (opcode != NULL) |
| { |
| /* Provide info for jump visualization. Must be done before print. */ |
| switch (opcode->flags & S390_INSTR_FLAG_CLASS_MASK) |
| { |
| case S390_INSTR_FLAGS_CLASS_JUMP: |
| info->insn_type = dis_branch; |
| break; |
| case S390_INSTR_FLAGS_CLASS_CONDJUMP: |
| info->insn_type = dis_condbranch; |
| break; |
| case S390_INSTR_FLAGS_CLASS_JUMPSR: |
| info->insn_type = dis_jsr; |
| break; |
| default: |
| info->insn_type = dis_nonbranch; |
| } |
| info->insn_info_valid = 1; |
| |
| /* The instruction is valid. Print it and return its size. */ |
| s390_print_insn_with_opcode (memaddr, info, buffer, opcode); |
| return opsize; |
| } |
| } |
| |
| /* For code sections it makes sense to skip unknown instructions |
| according to their length bits. */ |
| if (status == 0 |
| && option_use_insn_len_bits_p |
| && info->section != NULL |
| && (info->section->flags & SEC_CODE)) |
| bytes_to_dump = opsize; |
| else |
| /* By default unknown instructions are printed as .long's/.short' |
| depending on how many bytes are available. */ |
| bytes_to_dump = bufsize >= 4 ? 4 : bufsize; |
| |
| if (bytes_to_dump == 0) |
| return 0; |
| |
| info->insn_type = dis_noninsn; |
| info->insn_info_valid = 1; |
| |
| /* Fall back to hex print. */ |
| switch (bytes_to_dump) |
| { |
| case 4: |
| value = (unsigned int) buffer[0]; |
| value = (value << 8) + (unsigned int) buffer[1]; |
| value = (value << 8) + (unsigned int) buffer[2]; |
| value = (value << 8) + (unsigned int) buffer[3]; |
| info->fprintf_styled_func (info->stream, dis_style_assembler_directive, |
| ".long"); |
| info->fprintf_styled_func (info->stream, dis_style_text, |
| "\t"); |
| info->fprintf_styled_func (info->stream, dis_style_immediate, |
| "0x%08x", value); |
| return 4; |
| case 2: |
| value = (unsigned int) buffer[0]; |
| value = (value << 8) + (unsigned int) buffer[1]; |
| info->fprintf_styled_func (info->stream, dis_style_assembler_directive, |
| ".short"); |
| info->fprintf_styled_func (info->stream, dis_style_text, |
| "\t"); |
| info->fprintf_styled_func (info->stream, dis_style_immediate, |
| "0x%04x", value); |
| return 2; |
| default: |
| info->fprintf_styled_func (info->stream, dis_style_assembler_directive, |
| ".byte"); |
| info->fprintf_styled_func (info->stream, dis_style_text, |
| "\t"); |
| info->fprintf_styled_func (info->stream, dis_style_immediate, |
| "0x%02x", (unsigned int) buffer[0]); |
| for (i = 1; i < bytes_to_dump; i++) |
| info->fprintf_styled_func (info->stream, dis_style_immediate, |
| "0x%02x", (unsigned int) buffer[i]); |
| return bytes_to_dump; |
| } |
| return 0; |
| } |
| |
| const disasm_options_and_args_t * |
| disassembler_options_s390 (void) |
| { |
| static disasm_options_and_args_t *opts_and_args; |
| |
| if (opts_and_args == NULL) |
| { |
| size_t i, num_options = ARRAY_SIZE (options); |
| disasm_options_t *opts; |
| |
| opts_and_args = XNEW (disasm_options_and_args_t); |
| opts_and_args->args = NULL; |
| |
| opts = &opts_and_args->options; |
| opts->name = XNEWVEC (const char *, num_options + 1); |
| opts->description = XNEWVEC (const char *, num_options + 1); |
| opts->arg = NULL; |
| for (i = 0; i < num_options; i++) |
| { |
| opts->name[i] = options[i].name; |
| opts->description[i] = _(options[i].description); |
| } |
| /* The array we return must be NULL terminated. */ |
| opts->name[i] = NULL; |
| opts->description[i] = NULL; |
| } |
| |
| return opts_and_args; |
| } |
| |
| void |
| print_s390_disassembler_options (FILE *stream) |
| { |
| unsigned int i, max_len = 0; |
| fprintf (stream, _("\n\ |
| The following S/390 specific disassembler options are supported for use\n\ |
| with the -M switch (multiple options should be separated by commas):\n")); |
| |
| for (i = 0; i < ARRAY_SIZE (options); i++) |
| { |
| unsigned int len = strlen (options[i].name); |
| if (max_len < len) |
| max_len = len; |
| } |
| |
| for (i = 0, max_len++; i < ARRAY_SIZE (options); i++) |
| fprintf (stream, " %s%*c %s\n", |
| options[i].name, |
| (int)(max_len - strlen (options[i].name)), ' ', |
| _(options[i].description)); |
| } |