/* s390-dis.c -- Disassemble S390 instructions Copyright (C) 2000-2023 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 #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") }, { "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; if ((flags & S390_OPERAND_INDEX) && val.u == 0) continue; 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); 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); 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)); }