/src/binutils-gdb/opcodes/loongarch-dis.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* LoongArch opcode support. |
2 | | Copyright (C) 2021-2023 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 | 58.0k | { |
40 | 58.0k | const struct loongarch_opcode *it; |
41 | 58.0k | struct loongarch_ase *ase; |
42 | 58.0k | size_t i; |
43 | 811k | for (ase = loongarch_ASEs; ase->enabled; ase++) |
44 | 777k | { |
45 | 777k | if (!*ase->enabled || (ase->include && !*ase->include) |
46 | 777k | || (ase->exclude && *ase->exclude)) |
47 | 0 | continue; |
48 | | |
49 | 777k | if (!ase->opc_htab_inited) |
50 | 36 | { |
51 | 3.49k | for (it = ase->opcodes; it->mask; it++) |
52 | 3.46k | if (!ase->opc_htab[LARCH_INSN_OPC (it->match)] |
53 | 3.46k | && it->macro == NULL |
54 | 3.46k | && (!(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 | 777k | it = ase->opc_htab[LARCH_INSN_OPC (insn)]; |
64 | 28.5M | for (; it->name; it++) |
65 | 27.8M | if ((insn & it->mask) == it->match && it->mask |
66 | 27.8M | && !(it->include && !*it->include) |
67 | 27.8M | && !(it->exclude && *it->exclude)) |
68 | 23.8k | return it; |
69 | 777k | } |
70 | 34.2k | return NULL; |
71 | 58.0k | } |
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_lp64_name; |
86 | 2 | loongarch_f_disname = loongarch_f_lp64_name; |
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 | loongarch_dis_show_aliases = false; |
99 | |
|
100 | 0 | if (strcmp (option, "numeric") == 0) |
101 | 0 | { |
102 | 0 | loongarch_r_disname = loongarch_r_normal_name; |
103 | 0 | loongarch_f_disname = loongarch_f_normal_name; |
104 | 0 | } |
105 | 0 | return -1; |
106 | 0 | } |
107 | | |
108 | | static int |
109 | | parse_loongarch_dis_options (const char *opts_in) |
110 | 2 | { |
111 | 2 | set_default_loongarch_dis_options (); |
112 | | |
113 | 2 | if (opts_in == NULL) |
114 | 2 | return 0; |
115 | | |
116 | 0 | char *opts, *opt, *opt_end; |
117 | 0 | opts = xmalloc (strlen (opts_in) + 1); |
118 | 0 | strcpy (opts, opts_in); |
119 | |
|
120 | 0 | for (opt = opt_end = opts; opt_end != NULL; opt = opt_end + 1) |
121 | 0 | { |
122 | 0 | if ((opt_end = strchr (opt, ',')) != NULL) |
123 | 0 | *opt_end = 0; |
124 | 0 | if (parse_loongarch_dis_option (opt) != 0) |
125 | 0 | return -1; |
126 | 0 | } |
127 | 0 | free (opts); |
128 | 0 | return 0; |
129 | 0 | } |
130 | | |
131 | | static int32_t |
132 | | dis_one_arg (char esc1, char esc2, const char *bit_field, |
133 | | const char *arg ATTRIBUTE_UNUSED, void *context) |
134 | 89.9k | { |
135 | 89.9k | static int need_comma = 0; |
136 | 89.9k | struct disassemble_info *info = context; |
137 | 89.9k | insn_t insn = *(insn_t *) info->private_data; |
138 | 89.9k | int32_t imm, u_imm; |
139 | 89.9k | enum disassembler_style style; |
140 | | |
141 | 89.9k | if (esc1) |
142 | 66.0k | { |
143 | 66.0k | if (need_comma) |
144 | 42.1k | info->fprintf_styled_func (info->stream, dis_style_text, ", "); |
145 | 66.0k | need_comma = 1; |
146 | 66.0k | imm = loongarch_decode_imm (bit_field, insn, 1); |
147 | 66.0k | u_imm = loongarch_decode_imm (bit_field, insn, 0); |
148 | 66.0k | } |
149 | | |
150 | 89.9k | switch (esc1) |
151 | 89.9k | { |
152 | 38.9k | case 'r': |
153 | 38.9k | info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_r_disname[u_imm]); |
154 | 38.9k | break; |
155 | 1.14k | case 'f': |
156 | 1.14k | switch (esc2) |
157 | 1.14k | { |
158 | 0 | case 'c': |
159 | 0 | info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_fc_disname[u_imm]); |
160 | 0 | break; |
161 | 1.14k | default: |
162 | 1.14k | info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_f_disname[u_imm]); |
163 | 1.14k | } |
164 | 1.14k | break; |
165 | 1.14k | case 'c': |
166 | 653 | switch (esc2) |
167 | 653 | { |
168 | 173 | case 'r': |
169 | 173 | info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_cr_disname[u_imm]); |
170 | 173 | break; |
171 | 480 | default: |
172 | 480 | info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_c_disname[u_imm]); |
173 | 653 | } |
174 | 653 | break; |
175 | 1.16k | case 'v': |
176 | 1.16k | info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_v_disname[u_imm]); |
177 | 1.16k | break; |
178 | 1.56k | case 'x': |
179 | 1.56k | info->fprintf_styled_func (info->stream, dis_style_register, "%s", loongarch_x_disname[u_imm]); |
180 | 1.56k | break; |
181 | 6.31k | case 'u': |
182 | 6.31k | style = esc2 == 'o' ? dis_style_address_offset : dis_style_immediate; |
183 | 6.31k | info->fprintf_styled_func (info->stream, style, "0x%x", u_imm); |
184 | 6.31k | break; |
185 | 16.3k | case 's': |
186 | 16.3k | switch (esc2) |
187 | 16.3k | { |
188 | 8.43k | case 'b': |
189 | 11.9k | case 'o': |
190 | | /* Both represent address offsets. */ |
191 | 11.9k | style = dis_style_address_offset; |
192 | 11.9k | break; |
193 | 4.32k | default: |
194 | 4.32k | style = dis_style_immediate; |
195 | 4.32k | break; |
196 | 16.3k | } |
197 | 16.3k | info->fprintf_styled_func (info->stream, style, "%d", imm); |
198 | 16.3k | switch (esc2) |
199 | 16.3k | { |
200 | 8.43k | case 'b': |
201 | 8.43k | info->insn_type = dis_branch; |
202 | 8.43k | info->target += imm; |
203 | 16.3k | } |
204 | 16.3k | break; |
205 | 23.8k | case '\0': |
206 | 23.8k | need_comma = 0; |
207 | 89.9k | } |
208 | 89.9k | return 0; |
209 | 89.9k | } |
210 | | |
211 | | static void |
212 | | disassemble_one (insn_t insn, struct disassemble_info *info) |
213 | 58.0k | { |
214 | 58.0k | const struct loongarch_opcode *opc = get_loongarch_opcode_by_binfmt (insn); |
215 | | |
216 | | #ifdef LOONGARCH_DEBUG |
217 | | char have_space[32] = { 0 }; |
218 | | insn_t t; |
219 | | int i; |
220 | | const char *t_f = opc ? opc->format : NULL; |
221 | | if (t_f) |
222 | | while (*t_f) |
223 | | { |
224 | | while (('a' <= t_f[0] && t_f[0] <= 'z') |
225 | | || ('A' <= t_f[0] && t_f[0] <= 'Z') |
226 | | || t_f[0] == ',') |
227 | | t_f++; |
228 | | while (1) |
229 | | { |
230 | | i = strtol (t_f, &t_f, 10); |
231 | | have_space[i] = 1; |
232 | | t_f++; /* ':' */ |
233 | | i += strtol (t_f, &t_f, 10); |
234 | | have_space[i] = 1; |
235 | | if (t_f[0] == '|') |
236 | | t_f++; |
237 | | else |
238 | | break; |
239 | | } |
240 | | if (t_f[0] == '<') |
241 | | t_f += 2; /* '<' '<' */ |
242 | | strtol (t_f, &t_f, 10); |
243 | | } |
244 | | |
245 | | have_space[28] = 1; |
246 | | have_space[0] = 0; |
247 | | t = ~((insn_t) -1 >> 1); |
248 | | for (i = 31; 0 <= i; i--) |
249 | | { |
250 | | if (t & insn) |
251 | | info->fprintf_styled_func (info->stream, dis_style_text, "1"); |
252 | | else |
253 | | info->fprintf_styled_func (info->stream, dis_style_text, "0"); |
254 | | if (have_space[i]) |
255 | | info->fprintf_styled_func (info->stream, dis_style_text, " "); |
256 | | t = t >> 1; |
257 | | } |
258 | | info->fprintf_styled_func (info->stream, dis_style_text, "\t"); |
259 | | #endif |
260 | | |
261 | 58.0k | if (!opc) |
262 | 34.2k | { |
263 | 34.2k | info->insn_type = dis_noninsn; |
264 | 34.2k | info->fprintf_styled_func (info->stream, dis_style_assembler_directive, ".word\t\t"); |
265 | 34.2k | info->fprintf_styled_func (info->stream, dis_style_immediate, "0x%08x", insn); |
266 | 34.2k | return; |
267 | 34.2k | } |
268 | | |
269 | 23.8k | info->insn_type = dis_nonbranch; |
270 | 23.8k | info->fprintf_styled_func (info->stream, dis_style_mnemonic, "%-12s", opc->name); |
271 | | |
272 | 23.8k | { |
273 | 23.8k | char *fake_args = xmalloc (strlen (opc->format) + 1); |
274 | 23.8k | const char *fake_arg_strs[MAX_ARG_NUM_PLUS_2]; |
275 | 23.8k | strcpy (fake_args, opc->format); |
276 | 23.8k | if (0 < loongarch_split_args_by_comma (fake_args, fake_arg_strs)) |
277 | 23.8k | info->fprintf_styled_func (info->stream, dis_style_text, "\t"); |
278 | 23.8k | info->private_data = &insn; |
279 | 23.8k | loongarch_foreach_args (opc->format, fake_arg_strs, dis_one_arg, info); |
280 | 23.8k | free (fake_args); |
281 | 23.8k | } |
282 | | |
283 | 23.8k | if (info->insn_type == dis_branch || info->insn_type == dis_condbranch) |
284 | 8.43k | { |
285 | 8.43k | info->fprintf_styled_func (info->stream, dis_style_comment_start, "\t# "); |
286 | 8.43k | info->print_address_func (info->target, info); |
287 | 8.43k | } |
288 | 23.8k | } |
289 | | |
290 | | int |
291 | | print_insn_loongarch (bfd_vma memaddr, struct disassemble_info *info) |
292 | 58.1k | { |
293 | 58.1k | insn_t insn; |
294 | 58.1k | int status; |
295 | | |
296 | 58.1k | static int not_init_yet = 1; |
297 | 58.1k | if (not_init_yet) |
298 | 2 | { |
299 | 2 | parse_loongarch_dis_options (info->disassembler_options); |
300 | 2 | not_init_yet = 0; |
301 | 2 | } |
302 | | |
303 | 58.1k | info->bytes_per_chunk = 4; |
304 | 58.1k | info->bytes_per_line = 4; |
305 | 58.1k | info->display_endian = BFD_ENDIAN_LITTLE; |
306 | 58.1k | info->insn_info_valid = 1; |
307 | 58.1k | info->target = memaddr; |
308 | | |
309 | 58.1k | if ((status = info->read_memory_func (memaddr, (bfd_byte *) &insn, |
310 | 58.1k | sizeof (insn), info)) != 0) |
311 | 118 | { |
312 | 118 | info->memory_error_func (status, memaddr, info); |
313 | 118 | return -1; /* loongarch_insn_length (0); */ |
314 | 118 | } |
315 | | |
316 | 58.0k | disassemble_one (insn, info); |
317 | | |
318 | 58.0k | return loongarch_insn_length (insn); |
319 | 58.1k | } |
320 | | |
321 | | void |
322 | | print_loongarch_disassembler_options (FILE *stream) |
323 | 0 | { |
324 | 0 | fprintf (stream, _("\n\ |
325 | 0 | The following LoongArch disassembler options are supported for use\n\ |
326 | 0 | with the -M switch (multiple options should be separated by commas):\n")); |
327 | |
|
328 | 0 | fprintf (stream, _("\n\ |
329 | 0 | no-aliases Use canonical instruction forms.\n")); |
330 | 0 | fprintf (stream, _("\n\ |
331 | 0 | numeric Print numeric register names, rather than ABI names.\n")); |
332 | 0 | fprintf (stream, _("\n")); |
333 | 0 | } |