xref: /openbmc/qemu/disas/capstone.c (revision f343346b)
1 /*
2  * Interface to the capstone disassembler.
3  * SPDX-License-Identifier: GPL-2.0-or-later
4  */
5 
6 #include "qemu/osdep.h"
7 #include "qemu/bswap.h"
8 #include "disas/dis-asm.h"
9 #include "disas/capstone.h"
10 
11 
12 /*
13  * Temporary storage for the capstone library.  This will be alloced via
14  * malloc with a size private to the library; thus there's no reason not
15  * to share this across calls and across host vs target disassembly.
16  */
17 static __thread cs_insn *cap_insn;
18 
19 /*
20  * Initialize the Capstone library.
21  *
22  * ??? It would be nice to cache this.  We would need one handle for the
23  * host and one for the target.  For most targets we can reset specific
24  * parameters via cs_option(CS_OPT_MODE, new_mode), but we cannot change
25  * CS_ARCH_* in this way.  Thus we would need to be able to close and
26  * re-open the target handle with a different arch for the target in order
27  * to handle AArch64 vs AArch32 mode switching.
28  */
29 static cs_err cap_disas_start(disassemble_info *info, csh *handle)
30 {
31     cs_mode cap_mode = info->cap_mode;
32     cs_err err;
33 
34     cap_mode += (info->endian == BFD_ENDIAN_BIG ? CS_MODE_BIG_ENDIAN
35                  : CS_MODE_LITTLE_ENDIAN);
36 
37     err = cs_open(info->cap_arch, cap_mode, handle);
38     if (err != CS_ERR_OK) {
39         return err;
40     }
41 
42     /* "Disassemble" unknown insns as ".byte W,X,Y,Z".  */
43     cs_option(*handle, CS_OPT_SKIPDATA, CS_OPT_ON);
44 
45     if (info->cap_arch == CS_ARCH_X86) {
46         /*
47          * We don't care about errors (if for some reason the library
48          * is compiled without AT&T syntax); the user will just have
49          * to deal with the Intel syntax.
50          */
51         cs_option(*handle, CS_OPT_SYNTAX, CS_OPT_SYNTAX_ATT);
52     }
53 
54     /* Allocate temp space for cs_disasm_iter.  */
55     if (cap_insn == NULL) {
56         cap_insn = cs_malloc(*handle);
57         if (cap_insn == NULL) {
58             cs_close(handle);
59             return CS_ERR_MEM;
60         }
61     }
62     return CS_ERR_OK;
63 }
64 
65 static void cap_dump_insn_units(disassemble_info *info, cs_insn *insn,
66                                 int i, int n)
67 {
68     fprintf_function print = info->fprintf_func;
69     FILE *stream = info->stream;
70 
71     switch (info->cap_insn_unit) {
72     case 4:
73         if (info->endian == BFD_ENDIAN_BIG) {
74             for (; i < n; i += 4) {
75                 print(stream, " %08x", ldl_be_p(insn->bytes + i));
76 
77             }
78         } else {
79             for (; i < n; i += 4) {
80                 print(stream, " %08x", ldl_le_p(insn->bytes + i));
81             }
82         }
83         break;
84 
85     case 2:
86         if (info->endian == BFD_ENDIAN_BIG) {
87             for (; i < n; i += 2) {
88                 print(stream, " %04x", lduw_be_p(insn->bytes + i));
89             }
90         } else {
91             for (; i < n; i += 2) {
92                 print(stream, " %04x", lduw_le_p(insn->bytes + i));
93             }
94         }
95         break;
96 
97     default:
98         for (; i < n; i++) {
99             print(stream, " %02x", insn->bytes[i]);
100         }
101         break;
102     }
103 }
104 
105 static void cap_dump_insn(disassemble_info *info, cs_insn *insn)
106 {
107     fprintf_function print = info->fprintf_func;
108     FILE *stream = info->stream;
109     int i, n, split;
110 
111     print(stream, "0x%08" PRIx64 ": ", insn->address);
112 
113     n = insn->size;
114     split = info->cap_insn_split;
115 
116     /* Dump the first SPLIT bytes of the instruction.  */
117     cap_dump_insn_units(info, insn, 0, MIN(n, split));
118 
119     /* Add padding up to SPLIT so that mnemonics line up.  */
120     if (n < split) {
121         int width = (split - n) / info->cap_insn_unit;
122         width *= (2 * info->cap_insn_unit + 1);
123         print(stream, "%*s", width, "");
124     }
125 
126     /* Print the actual instruction.  */
127     print(stream, "  %-8s %s\n", insn->mnemonic, insn->op_str);
128 
129     /* Dump any remaining part of the insn on subsequent lines.  */
130     for (i = split; i < n; i += split) {
131         print(stream, "0x%08" PRIx64 ": ", insn->address + i);
132         cap_dump_insn_units(info, insn, i, MIN(n, i + split));
133         print(stream, "\n");
134     }
135 }
136 
137 /* Disassemble SIZE bytes at PC for the target.  */
138 bool cap_disas_target(disassemble_info *info, uint64_t pc, size_t size)
139 {
140     uint8_t cap_buf[1024];
141     csh handle;
142     cs_insn *insn;
143     size_t csize = 0;
144 
145     if (cap_disas_start(info, &handle) != CS_ERR_OK) {
146         return false;
147     }
148     insn = cap_insn;
149 
150     while (1) {
151         size_t tsize = MIN(sizeof(cap_buf) - csize, size);
152         const uint8_t *cbuf = cap_buf;
153 
154         info->read_memory_func(pc + csize, cap_buf + csize, tsize, info);
155         csize += tsize;
156         size -= tsize;
157 
158         while (cs_disasm_iter(handle, &cbuf, &csize, &pc, insn)) {
159             cap_dump_insn(info, insn);
160         }
161 
162         /* If the target memory is not consumed, go back for more... */
163         if (size != 0) {
164             /*
165              * ... taking care to move any remaining fractional insn
166              * to the beginning of the buffer.
167              */
168             if (csize != 0) {
169                 memmove(cap_buf, cbuf, csize);
170             }
171             continue;
172         }
173 
174         /*
175          * Since the target memory is consumed, we should not have
176          * a remaining fractional insn.
177          */
178         if (csize != 0) {
179             info->fprintf_func(info->stream,
180                 "Disassembler disagrees with translator "
181                 "over instruction decoding\n"
182                 "Please report this to qemu-devel@nongnu.org\n");
183         }
184         break;
185     }
186 
187     cs_close(&handle);
188     return true;
189 }
190 
191 /* Disassemble SIZE bytes at CODE for the host.  */
192 bool cap_disas_host(disassemble_info *info, void *code, size_t size)
193 {
194     csh handle;
195     const uint8_t *cbuf;
196     cs_insn *insn;
197     uint64_t pc;
198 
199     if (cap_disas_start(info, &handle) != CS_ERR_OK) {
200         return false;
201     }
202     insn = cap_insn;
203 
204     cbuf = code;
205     pc = (uintptr_t)code;
206 
207     while (cs_disasm_iter(handle, &cbuf, &size, &pc, insn)) {
208         cap_dump_insn(info, insn);
209     }
210     if (size != 0) {
211         info->fprintf_func(info->stream,
212             "Disassembler disagrees with TCG over instruction encoding\n"
213             "Please report this to qemu-devel@nongnu.org\n");
214     }
215 
216     cs_close(&handle);
217     return true;
218 }
219 
220 /* Disassemble COUNT insns at PC for the target.  */
221 bool cap_disas_monitor(disassemble_info *info, uint64_t pc, int count)
222 {
223     uint8_t cap_buf[32];
224     csh handle;
225     cs_insn *insn;
226     size_t csize = 0;
227 
228     if (cap_disas_start(info, &handle) != CS_ERR_OK) {
229         return false;
230     }
231     insn = cap_insn;
232 
233     while (1) {
234         /*
235          * We want to read memory for one insn, but generically we do not
236          * know how much memory that is.  We have a small buffer which is
237          * known to be sufficient for all supported targets.  Try to not
238          * read beyond the page, Just In Case.  For even more simplicity,
239          * ignore the actual target page size and use a 1k boundary.  If
240          * that turns out to be insufficient, we'll come back around the
241          * loop and read more.
242          */
243         uint64_t epc = QEMU_ALIGN_UP(pc + csize + 1, 1024);
244         size_t tsize = MIN(sizeof(cap_buf) - csize, epc - pc);
245         const uint8_t *cbuf = cap_buf;
246 
247         /* Make certain that we can make progress.  */
248         assert(tsize != 0);
249         info->read_memory_func(pc, cap_buf + csize, tsize, info);
250         csize += tsize;
251 
252         if (cs_disasm_iter(handle, &cbuf, &csize, &pc, insn)) {
253             cap_dump_insn(info, insn);
254             if (--count <= 0) {
255                 break;
256             }
257         }
258         memmove(cap_buf, cbuf, csize);
259     }
260 
261     cs_close(&handle);
262     return true;
263 }
264 
265 /* Disassemble a single instruction directly into plugin output */
266 bool cap_disas_plugin(disassemble_info *info, uint64_t pc, size_t size)
267 {
268     uint8_t cap_buf[32];
269     const uint8_t *cbuf = cap_buf;
270     csh handle;
271 
272     if (cap_disas_start(info, &handle) != CS_ERR_OK) {
273         return false;
274     }
275 
276     assert(size < sizeof(cap_buf));
277     info->read_memory_func(pc, cap_buf, size, info);
278 
279     if (cs_disasm_iter(handle, &cbuf, &size, &pc, cap_insn)) {
280         info->fprintf_func(info->stream, "%s %s",
281                            cap_insn->mnemonic, cap_insn->op_str);
282     }
283 
284     cs_close(&handle);
285     return true;
286 }
287