/* LoongArch opcode support. Copyright (C) 2021-2023 Free Software Foundation, Inc. Contributed by Loongson Ltd. 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 program; see the file COPYING3. If not, see . */ #include "sysdep.h" #include "disassemble.h" #include "opintl.h" #include "opcode/loongarch.h" #include "libiberty.h" #include static const struct loongarch_opcode * get_loongarch_opcode_by_binfmt (insn_t insn) { const struct loongarch_opcode *it; struct loongarch_ase *ase; size_t i; for (ase = loongarch_ASEs; ase->enabled; ase++) { if (!*ase->enabled || (ase->include && !*ase->include) || (ase->exclude && *ase->exclude)) continue; if (!ase->opc_htab_inited) { for (it = ase->opcodes; it->mask; it++) if (!ase->opc_htab[LARCH_INSN_OPC (it->match)] && it->macro == NULL) ase->opc_htab[LARCH_INSN_OPC (it->match)] = it; for (i = 0; i < 16; i++) if (!ase->opc_htab[i]) ase->opc_htab[i] = it; ase->opc_htab_inited = 1; } it = ase->opc_htab[LARCH_INSN_OPC (insn)]; for (; it->name; it++) if ((insn & it->mask) == it->match && it->mask && !(it->include && !*it->include) && !(it->exclude && *it->exclude)) return it; } return NULL; } static const char *const *loongarch_r_disname = NULL; static const char *const *loongarch_f_disname = NULL; static const char *const *loongarch_c_disname = NULL; static const char *const *loongarch_cr_disname = NULL; static const char *const *loongarch_v_disname = NULL; static const char *const *loongarch_x_disname = NULL; static void set_default_loongarch_dis_options (void) { LARCH_opts.ase_ilp32 = 1; LARCH_opts.ase_lp64 = 1; LARCH_opts.ase_sf = 1; LARCH_opts.ase_df = 1; LARCH_opts.ase_lsx = 1; LARCH_opts.ase_lasx = 1; loongarch_r_disname = loongarch_r_lp64_name; loongarch_f_disname = loongarch_f_lp64_name; loongarch_c_disname = loongarch_c_normal_name; loongarch_cr_disname = loongarch_cr_normal_name; loongarch_v_disname = loongarch_v_normal_name; loongarch_x_disname = loongarch_x_normal_name; } static int parse_loongarch_dis_option (const char *option) { if (strcmp (option, "numeric") == 0) { loongarch_r_disname = loongarch_r_normal_name; loongarch_f_disname = loongarch_f_normal_name; } return -1; } static int parse_loongarch_dis_options (const char *opts_in) { set_default_loongarch_dis_options (); if (opts_in == NULL) return 0; char *opts, *opt, *opt_end; opts = xmalloc (strlen (opts_in) + 1); strcpy (opts, opts_in); for (opt = opt_end = opts; opt_end != NULL; opt = opt_end + 1) { if ((opt_end = strchr (opt, ',')) != NULL) *opt_end = 0; if (parse_loongarch_dis_option (opt) != 0) return -1; } free (opts); return 0; } static int32_t dis_one_arg (char esc1, char esc2, const char *bit_field, const char *arg ATTRIBUTE_UNUSED, void *context) { static int need_comma = 0; struct disassemble_info *info = context; insn_t insn = *(insn_t *) info->private_data; int32_t imm, u_imm; if (esc1) { if (need_comma) info->fprintf_func (info->stream, ", "); need_comma = 1; imm = loongarch_decode_imm (bit_field, insn, 1); u_imm = loongarch_decode_imm (bit_field, insn, 0); } switch (esc1) { case 'r': info->fprintf_func (info->stream, "%s", loongarch_r_disname[u_imm]); break; case 'f': info->fprintf_func (info->stream, "%s", loongarch_f_disname[u_imm]); break; case 'c': switch (esc2) { case 'r': info->fprintf_func (info->stream, "%s", loongarch_cr_disname[u_imm]); break; default: info->fprintf_func (info->stream, "%s", loongarch_c_disname[u_imm]); } break; case 'v': info->fprintf_func (info->stream, "%s", loongarch_v_disname[u_imm]); break; case 'x': info->fprintf_func (info->stream, "%s", loongarch_x_disname[u_imm]); break; case 'u': info->fprintf_func (info->stream, "0x%x", u_imm); break; case 's': if (imm == 0) info->fprintf_func (info->stream, "%d", imm); else info->fprintf_func (info->stream, "%d(0x%x)", imm, u_imm); switch (esc2) { case 'b': info->insn_type = dis_branch; info->target += imm; } break; case '\0': need_comma = 0; } return 0; } static void disassemble_one (insn_t insn, struct disassemble_info *info) { const struct loongarch_opcode *opc = get_loongarch_opcode_by_binfmt (insn); #ifdef LOONGARCH_DEBUG char have_space[32] = { 0 }; insn_t t; int i; const char *t_f = opc ? opc->format : NULL; if (t_f) while (*t_f) { while (('a' <= t_f[0] && t_f[0] <= 'z') || ('A' <= t_f[0] && t_f[0] <= 'Z') || t_f[0] == ',') t_f++; while (1) { i = strtol (t_f, &t_f, 10); have_space[i] = 1; t_f++; /* ':' */ i += strtol (t_f, &t_f, 10); have_space[i] = 1; if (t_f[0] == '|') t_f++; else break; } if (t_f[0] == '<') t_f += 2; /* '<' '<' */ strtol (t_f, &t_f, 10); } have_space[28] = 1; have_space[0] = 0; t = ~((insn_t) -1 >> 1); for (i = 31; 0 <= i; i--) { if (t & insn) info->fprintf_func (info->stream, "1"); else info->fprintf_func (info->stream, "0"); if (have_space[i]) info->fprintf_func (info->stream, " "); t = t >> 1; } info->fprintf_func (info->stream, "\t"); #endif if (!opc) { info->insn_type = dis_noninsn; info->fprintf_func (info->stream, "0x%08x", insn); return; } info->insn_type = dis_nonbranch; info->fprintf_func (info->stream, "%-12s", opc->name); { char *fake_args = xmalloc (strlen (opc->format) + 1); const char *fake_arg_strs[MAX_ARG_NUM_PLUS_2]; strcpy (fake_args, opc->format); if (0 < loongarch_split_args_by_comma (fake_args, fake_arg_strs)) info->fprintf_func (info->stream, "\t"); info->private_data = &insn; loongarch_foreach_args (opc->format, fake_arg_strs, dis_one_arg, info); free (fake_args); } if (info->insn_type == dis_branch || info->insn_type == dis_condbranch /* Someother if we have extra info to print. */) info->fprintf_func (info->stream, "\t#"); if (info->insn_type == dis_branch || info->insn_type == dis_condbranch) { info->fprintf_func (info->stream, " "); info->print_address_func (info->target, info); } } int print_insn_loongarch (bfd_vma memaddr, struct disassemble_info *info) { insn_t insn; int status; static int not_init_yet = 1; if (not_init_yet) { parse_loongarch_dis_options (info->disassembler_options); not_init_yet = 0; } info->bytes_per_chunk = 4; info->bytes_per_line = 4; info->display_endian = BFD_ENDIAN_LITTLE; info->insn_info_valid = 1; info->target = memaddr; if ((status = info->read_memory_func (memaddr, (bfd_byte *) &insn, sizeof (insn), info)) != 0) { info->memory_error_func (status, memaddr, info); return -1; /* loongarch_insn_length (0); */ } disassemble_one (insn, info); return loongarch_insn_length (insn); } void print_loongarch_disassembler_options (FILE *stream) { fprintf (stream, _("\n\ The following LoongArch disassembler options are supported for use\n\ with the -M switch (multiple options should be separated by commas):\n")); fprintf (stream, _("\n\ numeric Print numeric register names, rather than ABI names.\n")); fprintf (stream, _("\n")); } int loongarch_parse_dis_options (const char *opts_in) { return parse_loongarch_dis_options (opts_in); } static void my_print_address_func (bfd_vma addr, struct disassemble_info *dinfo) { dinfo->fprintf_func (dinfo->stream, "0x%llx", (long long) addr); } void loongarch_disassemble_one (int64_t pc, insn_t insn, int (*fprintf_func) (void *stream, const char *format, ...), void *stream) { static struct disassemble_info my_disinfo = { .print_address_func = my_print_address_func, }; static int not_init_yet = 1; if (not_init_yet) { loongarch_parse_dis_options (NULL); not_init_yet = 0; } my_disinfo.fprintf_func = fprintf_func; my_disinfo.stream = stream; my_disinfo.target = pc; disassemble_one (insn, &my_disinfo); }