/src/binutils-gdb/opcodes/loongarch-dis.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* LoongArch opcode support. |
2 | | Copyright (C) 2021-2025 Free Software Foundation, Inc. |
3 | | Contributed by Loongson Ltd. |
4 | | |
5 | | This file is part of the GNU opcodes library. |
6 | | |
7 | | This library is free software; you can redistribute it and/or modify |
8 | | it under the terms of the GNU General Public License as published by |
9 | | the Free Software Foundation; either version 3, or (at your option) |
10 | | any later version. |
11 | | |
12 | | It is distributed in the hope that it will be useful, but WITHOUT |
13 | | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
14 | | or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public |
15 | | License for more details. |
16 | | |
17 | | You should have received a copy of the GNU General Public License |
18 | | along with this program; see the file COPYING3. If not, |
19 | | see <http://www.gnu.org/licenses/>. */ |
20 | | |
21 | | #include "sysdep.h" |
22 | | #include "disassemble.h" |
23 | | #include "opintl.h" |
24 | | #include "opcode/loongarch.h" |
25 | | #include "libiberty.h" |
26 | | #include <stdlib.h> |
27 | | |
28 | | static bool loongarch_dis_show_aliases = true; |
29 | | static const char *const *loongarch_r_disname = NULL; |
30 | | static const char *const *loongarch_f_disname = NULL; |
31 | | static const char *const *loongarch_fc_disname = NULL; |
32 | | static const char *const *loongarch_c_disname = NULL; |
33 | | static const char *const *loongarch_cr_disname = NULL; |
34 | | static const char *const *loongarch_v_disname = NULL; |
35 | | static const char *const *loongarch_x_disname = NULL; |
36 | | |
37 | | static const struct loongarch_opcode * |
38 | | get_loongarch_opcode_by_binfmt (insn_t insn) |
39 | 25.6k | { |
40 | 25.6k | const struct loongarch_opcode *it; |
41 | 25.6k | struct loongarch_ase *ase; |
42 | 25.6k | size_t i; |
43 | 354k | for (ase = loongarch_ASEs; ase->enabled; ase++) |
44 | 339k | { |
45 | 339k | if (!*ase->enabled || (ase->include && !*ase->include) |
46 | 339k | || (ase->exclude && *ase->exclude)) |
47 | 0 | continue; |
48 | | |
49 | 339k | if (!ase->opc_htab_inited) |
50 | 36 | { |
51 | 3.52k | for (it = ase->opcodes; it->mask; it++) |
52 | 3.49k | if (!ase->opc_htab[LARCH_INSN_OPC (it->match)] |
53 | 3.49k | && it->macro == NULL |
54 | 3.49k | && (!(it->pinfo & INSN_DIS_ALIAS) |
55 | 42 | || loongarch_dis_show_aliases)) |
56 | 42 | ase->opc_htab[LARCH_INSN_OPC (it->match)] = it; |
57 | 612 | for (i = 0; i < 16; i++) |
58 | 576 | if (!ase->opc_htab[i]) |
59 | 534 | ase->opc_htab[i] = it; |
60 | 36 | ase->opc_htab_inited = 1; |
61 | 36 | } |
62 | | |
63 | 339k | it = ase->opc_htab[LARCH_INSN_OPC (insn)]; |
64 | 13.7M | for (; it->name; it++) |
65 | 13.3M | if ((insn & it->mask) == it->match && it->mask |
66 | 13.3M | && !(it->include && !*it->include) |
67 | 13.3M | && !(it->exclude && *it->exclude)) |
68 | 11.1k | return it; |
69 | 339k | } |
70 | 14.4k | return NULL; |
71 | 25.6k | } |
72 | | |
73 | | static void |
74 | | set_default_loongarch_dis_options (void) |
75 | 2 | { |
76 | 2 | LARCH_opts.ase_ilp32 = 1; |
77 | 2 | LARCH_opts.ase_lp64 = 1; |
78 | 2 | LARCH_opts.ase_sf = 1; |
79 | 2 | LARCH_opts.ase_df = 1; |
80 | 2 | LARCH_opts.ase_lsx = 1; |
81 | 2 | LARCH_opts.ase_lasx = 1; |
82 | 2 | LARCH_opts.ase_lvz = 1; |
83 | 2 | LARCH_opts.ase_lbt = 1; |
84 | | |
85 | 2 | loongarch_r_disname = loongarch_r_alias; |
86 | 2 | loongarch_f_disname = loongarch_f_alias; |
87 | 2 | loongarch_fc_disname = loongarch_fc_normal_name; |
88 | 2 | loongarch_c_disname = loongarch_c_normal_name; |
89 | 2 | loongarch_cr_disname = loongarch_cr_normal_name; |
90 | 2 | loongarch_v_disname = loongarch_v_normal_name; |
91 | 2 | loongarch_x_disname = loongarch_x_normal_name; |
92 | 2 | } |
93 | | |
94 | | static int |
95 | | parse_loongarch_dis_option (const char *option) |
96 | 0 | { |
97 | 0 | if (strcmp (option, "no-aliases") == 0) |
98 | 0 | { |
99 | 0 | loongarch_dis_show_aliases = false; |
100 | 0 | return 0; |
101 | 0 | } |
102 | | |
103 | 0 | if (strcmp (option, "numeric") == 0) |
104 | 0 | { |
105 | 0 | loongarch_r_disname = loongarch_r_normal_name; |
106 | 0 | loongarch_f_disname = loongarch_f_normal_name; |
107 | 0 | return 0; |
108 | 0 | } |
109 | | |
110 | 0 | return -1; |
111 | 0 | } |
112 | | |
113 | | static int |
114 | | parse_loongarch_dis_options (const char *opts_in) |
115 | 2 | { |
116 | 2 | set_default_loongarch_dis_options (); |
117 | | |
118 | 2 | if (opts_in == NULL) |
119 | 2 | return 0; |
120 | | |
121 | 0 | char *opts, *opt, *opt_end; |
122 | 0 | opts = xmalloc (strlen (opts_in) + 1); |
123 | 0 | strcpy (opts, opts_in); |
124 | |
|
125 | 0 | for (opt = opt_end = opts; opt_end != NULL; opt = opt_end + 1) |
126 | 0 | { |
127 | 0 | if ((opt_end = strchr (opt, ',')) != NULL) |
128 | 0 | *opt_end = 0; |
129 | 0 | if (parse_loongarch_dis_option (opt) != 0) |
130 | 0 | return -1; |
131 | 0 | } |
132 | 0 | free (opts); |
133 | 0 | return 0; |
134 | 0 | } |
135 | | |
136 | | static int32_t |
137 | | dis_one_arg (char esc1, char esc2, const char *bit_field, |
138 | | const char *arg ATTRIBUTE_UNUSED, void *context) |
139 | 41.1k | { |
140 | 41.1k | static int need_comma = 0; |
141 | 41.1k | struct disassemble_info *info = context; |
142 | 41.1k | insn_t insn = *(insn_t *) info->private_data; |
143 | 41.1k | int32_t imm, u_imm; |
144 | 41.1k | enum disassembler_style style; |
145 | | |
146 | 41.1k | if (esc1) |
147 | 30.0k | { |
148 | 30.0k | if (need_comma) |
149 | 18.8k | info->fprintf_styled_func (info->stream, dis_style_text, ", "); |
150 | 30.0k | need_comma = 1; |
151 | 30.0k | imm = loongarch_decode_imm (bit_field, insn, 1); |
152 | 30.0k | u_imm = loongarch_decode_imm (bit_field, insn, 0); |
153 | 30.0k | } |
154 | | |
155 | 41.1k | switch (esc1) |
156 | 41.1k | { |
157 | 16.0k | case 'r': |
158 | 16.0k | info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_r_disname[u_imm]); |
159 | 16.0k | break; |
160 | 824 | case 'f': |
161 | 824 | switch (esc2) |
162 | 824 | { |
163 | 47 | case 'c': |
164 | 47 | info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_fc_disname[u_imm]); |
165 | 47 | break; |
166 | 777 | default: |
167 | 777 | info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_f_disname[u_imm]); |
168 | 824 | } |
169 | 824 | break; |
170 | 824 | case 'c': |
171 | 550 | switch (esc2) |
172 | 550 | { |
173 | 49 | case 'r': |
174 | 49 | info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_cr_disname[u_imm]); |
175 | 49 | break; |
176 | 501 | default: |
177 | 501 | info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_c_disname[u_imm]); |
178 | 550 | } |
179 | 550 | break; |
180 | 764 | case 'v': |
181 | 764 | info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_v_disname[u_imm]); |
182 | 764 | break; |
183 | 1.02k | case 'x': |
184 | 1.02k | info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_x_disname[u_imm]); |
185 | 1.02k | break; |
186 | 3.10k | case 'u': |
187 | 3.10k | style = esc2 == 'o' ? dis_style_address_offset : dis_style_immediate; |
188 | 3.10k | info->fprintf_styled_func (info->stream, style, "0x%x", u_imm); |
189 | 3.10k | break; |
190 | 7.71k | case 's': |
191 | 7.71k | switch (esc2) |
192 | 7.71k | { |
193 | 5.08k | case 'b': |
194 | 6.56k | case 'o': |
195 | | /* Both represent address offsets. */ |
196 | 6.56k | style = dis_style_address_offset; |
197 | 6.56k | break; |
198 | 1.15k | default: |
199 | 1.15k | style = dis_style_immediate; |
200 | 1.15k | break; |
201 | 7.71k | } |
202 | 7.71k | info->fprintf_styled_func (info->stream, style, "%d", imm); |
203 | 7.71k | switch (esc2) |
204 | 7.71k | { |
205 | 5.08k | case 'b': |
206 | 5.08k | info->insn_type = dis_branch; |
207 | 5.08k | info->target += imm; |
208 | 7.71k | } |
209 | 7.71k | break; |
210 | 11.1k | case '\0': |
211 | 11.1k | need_comma = 0; |
212 | 41.1k | } |
213 | 41.1k | return 0; |
214 | 41.1k | } |
215 | | |
216 | | static void |
217 | | disassemble_one (insn_t insn, struct disassemble_info *info) |
218 | 25.6k | { |
219 | 25.6k | const struct loongarch_opcode *opc = get_loongarch_opcode_by_binfmt (insn); |
220 | | |
221 | | #ifdef LOONGARCH_DEBUG |
222 | | char have_space[32] = { 0 }; |
223 | | insn_t t; |
224 | | int i; |
225 | | const char *t_f = opc ? opc->format : NULL; |
226 | | if (t_f) |
227 | | while (*t_f) |
228 | | { |
229 | | while (('a' <= t_f[0] && t_f[0] <= 'z') |
230 | | || ('A' <= t_f[0] && t_f[0] <= 'Z') |
231 | | || t_f[0] == ',') |
232 | | t_f++; |
233 | | while (1) |
234 | | { |
235 | | i = strtol (t_f, &t_f, 10); |
236 | | have_space[i] = 1; |
237 | | t_f++; /* ':' */ |
238 | | i += strtol (t_f, &t_f, 10); |
239 | | have_space[i] = 1; |
240 | | if (t_f[0] == '|') |
241 | | t_f++; |
242 | | else |
243 | | break; |
244 | | } |
245 | | if (t_f[0] == '<') |
246 | | t_f += 2; /* '<' '<' */ |
247 | | strtol (t_f, &t_f, 10); |
248 | | } |
249 | | |
250 | | have_space[28] = 1; |
251 | | have_space[0] = 0; |
252 | | t = ~((insn_t) -1 >> 1); |
253 | | for (i = 31; 0 <= i; i--) |
254 | | { |
255 | | if (t & insn) |
256 | | info->fprintf_styled_func (info->stream, dis_style_text, "1"); |
257 | | else |
258 | | info->fprintf_styled_func (info->stream, dis_style_text, "0"); |
259 | | if (have_space[i]) |
260 | | info->fprintf_styled_func (info->stream, dis_style_text, " "); |
261 | | t = t >> 1; |
262 | | } |
263 | | info->fprintf_styled_func (info->stream, dis_style_text, "\t"); |
264 | | #endif |
265 | | |
266 | 25.6k | if (!opc) |
267 | 14.4k | { |
268 | 14.4k | info->insn_type = dis_noninsn; |
269 | 14.4k | info->fprintf_styled_func (info->stream, dis_style_assembler_directive, ".word\t\t"); |
270 | 14.4k | info->fprintf_styled_func (info->stream, dis_style_immediate, "0x%08x", insn); |
271 | 14.4k | return; |
272 | 14.4k | } |
273 | | |
274 | 11.1k | info->insn_type = dis_nonbranch; |
275 | 11.1k | if (opc->format == NULL || opc->format[0] == '\0') |
276 | 36 | info->fprintf_styled_func (info->stream, dis_style_mnemonic, |
277 | 36 | "%s", opc->name); |
278 | 11.1k | else |
279 | 11.1k | info->fprintf_styled_func (info->stream, dis_style_mnemonic, |
280 | 11.1k | "%-12s", opc->name); |
281 | | |
282 | 11.1k | { |
283 | 11.1k | char *fake_args = xmalloc (strlen (opc->format) + 1); |
284 | 11.1k | const char *fake_arg_strs[MAX_ARG_NUM_PLUS_2]; |
285 | 11.1k | strcpy (fake_args, opc->format); |
286 | 11.1k | if (0 < loongarch_split_args_by_comma (fake_args, fake_arg_strs)) |
287 | 11.1k | info->fprintf_styled_func (info->stream, dis_style_text, "\t"); |
288 | 11.1k | info->private_data = &insn; |
289 | 11.1k | loongarch_foreach_args (opc->format, fake_arg_strs, dis_one_arg, info); |
290 | 11.1k | free (fake_args); |
291 | 11.1k | } |
292 | | |
293 | 11.1k | if (info->insn_type == dis_branch || info->insn_type == dis_condbranch) |
294 | 5.08k | { |
295 | 5.08k | info->fprintf_styled_func (info->stream, dis_style_comment_start, "\t# "); |
296 | 5.08k | info->print_address_func (info->target, info); |
297 | 5.08k | } |
298 | 11.1k | } |
299 | | |
300 | | int |
301 | | print_insn_loongarch (bfd_vma memaddr, struct disassemble_info *info) |
302 | 25.7k | { |
303 | 25.7k | insn_t insn; |
304 | 25.7k | int status; |
305 | | |
306 | 25.7k | static int not_init_yet = 1; |
307 | 25.7k | if (not_init_yet) |
308 | 2 | { |
309 | 2 | parse_loongarch_dis_options (info->disassembler_options); |
310 | 2 | not_init_yet = 0; |
311 | 2 | } |
312 | | |
313 | 25.7k | info->bytes_per_chunk = 4; |
314 | 25.7k | info->bytes_per_line = 4; |
315 | 25.7k | info->display_endian = BFD_ENDIAN_LITTLE; |
316 | 25.7k | info->insn_info_valid = 1; |
317 | 25.7k | info->target = memaddr; |
318 | | |
319 | 25.7k | if ((status = info->read_memory_func (memaddr, (bfd_byte *) &insn, |
320 | 25.7k | sizeof (insn), info)) != 0) |
321 | 121 | { |
322 | 121 | info->memory_error_func (status, memaddr, info); |
323 | 121 | return -1; /* loongarch_insn_length (0); */ |
324 | 121 | } |
325 | | |
326 | 25.6k | disassemble_one (insn, info); |
327 | | |
328 | 25.6k | return loongarch_insn_length (insn); |
329 | 25.7k | } |
330 | | |
331 | | void |
332 | | print_loongarch_disassembler_options (FILE *stream) |
333 | 0 | { |
334 | 0 | fprintf (stream, _("\n\ |
335 | 0 | The following LoongArch disassembler options are supported for use\n\ |
336 | 0 | with the -M switch (multiple options should be separated by commas):\n")); |
337 | |
|
338 | 0 | fprintf (stream, _("\n\ |
339 | 0 | no-aliases Use canonical instruction forms.\n")); |
340 | 0 | fprintf (stream, _("\n\ |
341 | 0 | numeric Print numeric register names, rather than ABI names.\n")); |
342 | 0 | fprintf (stream, _("\n")); |
343 | 0 | } |