Line data Source code
1 : #include "fd_vm_private.h"
2 :
3 : /* fd_vm_disasm_printf appends to the *_len string in the max byte
4 : buffer buf the printf of the remaining args. On input, assumes *_len
5 : is strlen(buf) and *_len is in [0,max). On output, even on error
6 : cases, the leading string in buf will be unchanged, *len will be
7 : strlen(buf) and *len will be [*len_as_it_was_on_input,max).
8 :
9 : Returns:
10 :
11 : FD_VM_SUCCESS - success. buf and *len updated.
12 :
13 : FD_VM_ERR_FULL - not enough room in buf to hold result. As many
14 : bytes as possible were written to buf and *len==max-1 on return.
15 :
16 : FD_VM_ERR_IO - printf format parse error. buf and *len unchanged but
17 : trailing bytes of buf might have been clobbered. */
18 :
19 : /* FIXME: REWORK API TO USE FD_CSTR_PRINTF HERE? (OR CONSIDER ADDING
20 : FD_VM_DISASM_PRINTF AS A FD_CSTR API) */
21 :
22 : #include <stdio.h>
23 : #include <stdarg.h>
24 :
25 : static int
26 : fd_vm_disasm_printf( char * buf,
27 : ulong max,
28 : ulong * _len,
29 : char const * fmt, ... ) __attribute__((format(printf,4,5)));
30 :
31 : static int
32 : fd_vm_disasm_printf( char * buf,
33 : ulong max,
34 : ulong * _len,
35 28108308 : char const * fmt, ... ) {
36 28108308 : ulong len = *_len; /* In [0,max) */
37 28108308 : ulong rem = max - len; /* In (0,max] */
38 :
39 28108308 : va_list ap;
40 28108308 : va_start( ap, fmt );
41 28108308 : int ret = vsnprintf( buf + len, rem, fmt, ap );
42 28108308 : va_end( ap );
43 :
44 28108308 : if( FD_UNLIKELY( ret<0 ) ) { /* Parse error */
45 0 : buf[len] = '\0'; /* Guarantee '\0' termination */
46 0 : return FD_VM_ERR_IO;
47 0 : }
48 :
49 28108308 : ulong append_len = (ulong)ret; /* Guaranteed safe */
50 :
51 28108308 : if( FD_UNLIKELY( append_len>=rem ) ) { /* Truncated output */
52 0 : buf[max-1UL] = '\0'; /* Guarantee '\0' termination */
53 0 : *_len = max-1UL;
54 0 : return FD_VM_ERR_FULL;
55 0 : }
56 :
57 28108308 : *_len = len + append_len;
58 28108308 : return FD_VM_SUCCESS;
59 28108308 : }
60 :
61 : /* OUT_PRINTF is a convenience macro to do boilerplate error trapping
62 : on fd_vm_disasm_printf. */
63 :
64 28108308 : #define OUT_PRINTF( ... ) do { \
65 28108308 : int _err = fd_vm_disasm_printf( out, out_max, _out_len, __VA_ARGS__ ); \
66 28108308 : if( FD_UNLIKELY( _err ) ) return _err; \
67 28108308 : } while(0)
68 :
69 : /* fd_vm_disasm_instr_* are pretty printers for single word instructions.
70 : They do not validate their input arguments. Return out, out_max,
71 : _out_len and return error code have the same interpretation as their
72 : public facing wrappers. */
73 :
74 : static int
75 : fd_vm_disasm_instr_alu( fd_sbpf_instr_t instr,
76 : char const * suffix,
77 : char * out,
78 : ulong out_max,
79 7491759 : ulong * _out_len ) {
80 :
81 7491759 : char * op_name;
82 7491759 : switch( instr.opcode.normal.op_mode ) {
83 469560 : case FD_SBPF_OPCODE_ALU_OP_MODE_ADD: op_name = "add"; break;
84 466773 : case FD_SBPF_OPCODE_ALU_OP_MODE_SUB: op_name = "sub"; break;
85 467028 : case FD_SBPF_OPCODE_ALU_OP_MODE_MUL: op_name = "mul"; break;
86 468531 : case FD_SBPF_OPCODE_ALU_OP_MODE_DIV: op_name = "div"; break;
87 468021 : case FD_SBPF_OPCODE_ALU_OP_MODE_OR: op_name = "or"; break;
88 467013 : case FD_SBPF_OPCODE_ALU_OP_MODE_AND: op_name = "and"; break;
89 469251 : case FD_SBPF_OPCODE_ALU_OP_MODE_LSH: op_name = "lsh"; break;
90 467364 : case FD_SBPF_OPCODE_ALU_OP_MODE_RSH: op_name = "rsh"; break;
91 467880 : case FD_SBPF_OPCODE_ALU_OP_MODE_NEG: op_name = "neg"; break;
92 469461 : case FD_SBPF_OPCODE_ALU_OP_MODE_MOD: op_name = "mod"; break;
93 468939 : case FD_SBPF_OPCODE_ALU_OP_MODE_XOR: op_name = "xor"; break;
94 468300 : case FD_SBPF_OPCODE_ALU_OP_MODE_MOV: op_name = "mov"; break;
95 468492 : case FD_SBPF_OPCODE_ALU_OP_MODE_ARSH: op_name = "arsh"; break;
96 468588 : case FD_SBPF_OPCODE_ALU_OP_MODE_END: op_name = "end"; break;
97 936558 : default: return FD_VM_ERR_INVAL;
98 7491759 : }
99 :
100 6555201 : if( FD_UNLIKELY( instr.opcode.normal.op_mode==FD_SBPF_OPCODE_ALU_OP_MODE_NEG ) ) {
101 467880 : OUT_PRINTF( "%s%s r%d", op_name, suffix, instr.dst_reg );
102 467880 : return FD_VM_SUCCESS;
103 467880 : }
104 :
105 6087321 : switch( instr.opcode.normal.op_src ) {
106 3043260 : case FD_SBPF_OPCODE_SOURCE_MODE_IMM:
107 3043260 : OUT_PRINTF( "%s%s r%d, %d", op_name, suffix, instr.dst_reg, (int)instr.imm );
108 3043260 : return FD_VM_SUCCESS;
109 3044061 : case FD_SBPF_OPCODE_SOURCE_MODE_REG:
110 3044061 : OUT_PRINTF( "%s%s r%d, r%d", op_name, suffix, instr.dst_reg, instr.src_reg );
111 3044061 : return FD_VM_SUCCESS;
112 0 : default: break;
113 6087321 : }
114 :
115 0 : return FD_VM_ERR_INVAL;
116 6087321 : }
117 :
118 : static int
119 : fd_vm_disasm_instr_jmp( fd_sbpf_instr_t instr,
120 : ulong pc,
121 : char const * suffix,
122 : fd_sbpf_syscalls_t const * syscalls,
123 : char * out,
124 : ulong out_max,
125 7502163 : ulong * _out_len ) {
126 :
127 7502163 : char * op_name;
128 7502163 : switch( instr.opcode.normal.op_mode ) {
129 469752 : case FD_SBPF_OPCODE_JMP_OP_MODE_JA: op_name = "ja"; break;
130 466641 : case FD_SBPF_OPCODE_JMP_OP_MODE_JEQ: op_name = "jeq"; break;
131 471039 : case FD_SBPF_OPCODE_JMP_OP_MODE_JGT: op_name = "jgt"; break;
132 467022 : case FD_SBPF_OPCODE_JMP_OP_MODE_JGE: op_name = "jge"; break;
133 470964 : case FD_SBPF_OPCODE_JMP_OP_MODE_JSET: op_name = "jset"; break;
134 466776 : case FD_SBPF_OPCODE_JMP_OP_MODE_JNE: op_name = "jne"; break;
135 467802 : case FD_SBPF_OPCODE_JMP_OP_MODE_JSGT: op_name = "jsgt"; break;
136 469074 : case FD_SBPF_OPCODE_JMP_OP_MODE_JSGE: op_name = "jsge"; break;
137 469824 : case FD_SBPF_OPCODE_JMP_OP_MODE_CALL: op_name = "call"; break;
138 469584 : case FD_SBPF_OPCODE_JMP_OP_MODE_EXIT: op_name = "exit"; break;
139 470478 : case FD_SBPF_OPCODE_JMP_OP_MODE_JLT: op_name = "jlt"; break;
140 467139 : case FD_SBPF_OPCODE_JMP_OP_MODE_JLE: op_name = "jle"; break;
141 467706 : case FD_SBPF_OPCODE_JMP_OP_MODE_JSLT: op_name = "jslt"; break;
142 468159 : case FD_SBPF_OPCODE_JMP_OP_MODE_JSLE: op_name = "jsle"; break;
143 940203 : default: return FD_VM_ERR_INVAL;
144 7502163 : }
145 :
146 6561960 : if( FD_UNLIKELY( instr.opcode.normal.op_mode==FD_SBPF_OPCODE_JMP_OP_MODE_CALL ) ) {
147 469824 : switch ( instr.opcode.normal.op_src ) {
148 234804 : case FD_SBPF_OPCODE_SOURCE_MODE_IMM: {
149 234804 : fd_sbpf_syscalls_t const * syscall = syscalls ? fd_sbpf_syscalls_query_const( syscalls, instr.imm, NULL ) : NULL;
150 234804 : if( syscall ) { /* FIXME: THESE CODE PATHS CURRENTLY NOT EXERCISED BY UNIT TEST */
151 0 : char const * name = syscall->name;
152 0 : if( name ) OUT_PRINTF( "syscall%s %s", suffix, name );
153 0 : else OUT_PRINTF( "syscall%s 0x%08x", suffix, instr.imm );
154 234804 : } else {
155 234804 : uint pc = fd_pchash_inverse( instr.imm ); /* FIXME: is pchash in the right place? */
156 234804 : if( pc<(10<<17) ) OUT_PRINTF( "%s%s function_%u", op_name, suffix, pc ); /* FIXME: hardcoded constant */
157 88047 : else OUT_PRINTF( "%s%s function_%#x", op_name, suffix, instr.imm );
158 234804 : }
159 234804 : return FD_VM_SUCCESS;
160 234804 : }
161 235020 : case FD_SBPF_OPCODE_SOURCE_MODE_REG:
162 235020 : OUT_PRINTF( "%sx%s r%u", op_name, suffix, instr.imm );
163 235020 : return FD_VM_SUCCESS;
164 0 : default: break;
165 469824 : }
166 0 : return FD_VM_ERR_INVAL;
167 469824 : }
168 :
169 6092136 : if( FD_UNLIKELY( instr.opcode.normal.op_mode==FD_SBPF_OPCODE_JMP_OP_MODE_EXIT ) ) {
170 469584 : OUT_PRINTF( "%s%s", op_name, suffix );
171 469584 : return FD_VM_SUCCESS;
172 469584 : }
173 :
174 5622552 : if( FD_UNLIKELY( instr.opcode.normal.op_mode==FD_SBPF_OPCODE_JMP_OP_MODE_JA ) ) {
175 469752 : OUT_PRINTF( "%s%s lbb_%ld", op_name, suffix, (long)pc+(long)instr.offset+1L );
176 469752 : return FD_VM_SUCCESS;
177 469752 : }
178 :
179 5152800 : switch( instr.opcode.normal.op_src ) {
180 2577315 : case FD_SBPF_OPCODE_SOURCE_MODE_IMM:
181 2577315 : OUT_PRINTF( "%s%s r%d, %d, lbb_%ld", op_name, suffix, instr.dst_reg, (int)instr.imm, (long)pc+(long)instr.offset+1L );
182 2577315 : return FD_VM_SUCCESS;
183 2575485 : case FD_SBPF_OPCODE_SOURCE_MODE_REG:
184 2575485 : OUT_PRINTF( "%s%s r%d, r%d, lbb_%ld", op_name, suffix, instr.dst_reg, instr.src_reg, (long)pc+(long)instr.offset+1L );
185 2575485 : return FD_VM_SUCCESS;
186 0 : break;
187 0 : default: break;
188 5152800 : }
189 :
190 0 : return FD_VM_ERR_INVAL;
191 5152800 : }
192 :
193 : static int
194 : fd_vm_disasm_instr_ldx( fd_sbpf_instr_t instr,
195 : char * out,
196 : ulong out_max,
197 3749811 : ulong * _out_len ) {
198 :
199 3749811 : char * op_name;
200 3749811 : switch( instr.opcode.mem.op_size ) {
201 937140 : case FD_SBPF_OPCODE_SIZE_MODE_WORD: op_name = "ldxw"; break;
202 935406 : case FD_SBPF_OPCODE_SIZE_MODE_HALF: op_name = "ldxh"; break;
203 938931 : case FD_SBPF_OPCODE_SIZE_MODE_BYTE: op_name = "ldxb"; break;
204 938334 : case FD_SBPF_OPCODE_SIZE_MODE_DOUB: op_name = "ldxdw"; break;
205 0 : default: return FD_VM_ERR_INVAL;
206 3749811 : }
207 :
208 3749811 : if( instr.offset<0 ) OUT_PRINTF( "%s r%d, [r%d-0x%x]", op_name, instr.dst_reg, instr.src_reg, (ushort)-instr.offset );
209 1876233 : else OUT_PRINTF( "%s r%d, [r%d+0x%x]", op_name, instr.dst_reg, instr.src_reg, (ushort) instr.offset );
210 3749811 : return FD_VM_SUCCESS;
211 3749811 : }
212 :
213 : static int
214 : fd_vm_disasm_instr_stx( fd_sbpf_instr_t instr,
215 : char * out,
216 : ulong out_max,
217 3752208 : ulong * _out_len ) {
218 :
219 3752208 : char * op_name;
220 3752208 : switch( instr.opcode.mem.op_size ) {
221 939231 : case FD_SBPF_OPCODE_SIZE_MODE_WORD: op_name = "stxw"; break;
222 937776 : case FD_SBPF_OPCODE_SIZE_MODE_HALF: op_name = "stxh"; break;
223 938136 : case FD_SBPF_OPCODE_SIZE_MODE_BYTE: op_name = "stxb"; break;
224 937065 : case FD_SBPF_OPCODE_SIZE_MODE_DOUB: op_name = "stxdw"; break;
225 0 : default: return FD_VM_ERR_INVAL;
226 3752208 : }
227 :
228 3752208 : if( instr.offset<0 ) OUT_PRINTF( "%s [r%d-0x%x], r%d", op_name, instr.dst_reg, (ushort)-instr.offset, instr.src_reg );
229 1872705 : else OUT_PRINTF( "%s [r%d+0x%x], r%d", op_name, instr.dst_reg, (ushort) instr.offset, instr.src_reg );
230 3752208 : return FD_VM_SUCCESS;
231 3752208 : }
232 :
233 : int
234 : fd_vm_disasm_instr( ulong const * text,
235 : ulong text_cnt,
236 : ulong pc,
237 : fd_sbpf_syscalls_t const * syscalls,
238 : char * out,
239 : ulong out_max,
240 30000033 : ulong * _out_len ) {
241 :
242 30000033 : if( FD_UNLIKELY( (!text) | (!text_cnt) | (!out) | (!out_max) | (!_out_len) ) ) return FD_VM_ERR_INVAL;
243 30000018 : if( FD_UNLIKELY( (*_out_len)>=out_max ) ) return FD_VM_ERR_INVAL;
244 :
245 30000015 : fd_sbpf_instr_t i0 = fd_sbpf_instr( text[0] );
246 :
247 30000015 : switch( i0.opcode.any.op_class ) {
248 :
249 3753156 : case FD_SBPF_OPCODE_CLASS_LD: {
250 3753156 : if( FD_UNLIKELY( text_cnt<2UL ) ) return FD_VM_ERR_INVAL;
251 3738210 : fd_sbpf_instr_t i1 = fd_sbpf_instr( text[1] );
252 : /* FIXME: VALIDATE I1 IS PROPER */
253 3738210 : OUT_PRINTF( "lddw r%d, 0x%lx", i0.dst_reg, (ulong)((ulong)i0.imm | (ulong)((ulong)i1.imm << 32UL)) );
254 3738210 : return FD_VM_SUCCESS;
255 3738210 : }
256 :
257 3750918 : case FD_SBPF_OPCODE_CLASS_ST: { /* FIXME: FIGURE OUT WHAT'S UP HERE */
258 3750918 : OUT_PRINTF( "FIXME: %016lx (ST)", text[0] );
259 3750918 : return FD_VM_SUCCESS;
260 3750918 : }
261 :
262 3749811 : case FD_SBPF_OPCODE_CLASS_LDX: return fd_vm_disasm_instr_ldx( i0, out, out_max, _out_len);
263 3752208 : case FD_SBPF_OPCODE_CLASS_STX: return fd_vm_disasm_instr_stx( i0, out, out_max, _out_len );
264 3747018 : case FD_SBPF_OPCODE_CLASS_ALU: return fd_vm_disasm_instr_alu( i0, "", out, out_max, _out_len );
265 3751272 : case FD_SBPF_OPCODE_CLASS_JMP: return fd_vm_disasm_instr_jmp( i0, pc, "", syscalls, out, out_max, _out_len );
266 3750891 : case FD_SBPF_OPCODE_CLASS_JMP32: return fd_vm_disasm_instr_jmp( i0, pc, "32", syscalls, out, out_max, _out_len );
267 3744741 : case FD_SBPF_OPCODE_CLASS_ALU64: return fd_vm_disasm_instr_alu( i0, "64", out, out_max, _out_len );
268 0 : default: break;
269 30000015 : }
270 0 : return FD_VM_ERR_INVAL;
271 30000015 : }
272 :
273 : int
274 : fd_vm_disasm_program( ulong const * text,
275 : ulong text_cnt,
276 : fd_sbpf_syscalls_t const * syscalls,
277 : char * out,
278 : ulong out_max,
279 15 : ulong * _out_len ) {
280 :
281 15 : if( FD_UNLIKELY( ((!text) & (!!text_cnt)) | (!out) | (!out_max) | (!_out_len) ) ) return FD_VM_ERR_INVAL;
282 3 : if( FD_UNLIKELY( (*_out_len)>=out_max ) ) return FD_VM_ERR_INVAL;
283 :
284 : /* Construct the mapping of pc to labels and functions. FIXME: This
285 : is currently not an algo efficient implementation. Note: if the
286 : same instruction is the targeted by multiple calls / exits / jmps,
287 : it will appear multiple times in the label_pc and/or func_pc
288 : arrays. But that's okay because use the target instruction as the
289 : label and function name. */
290 :
291 0 : ulong func_pc [ 65536 ]; ulong func_cnt = 0UL;
292 0 : ulong label_pc[ 65536 ]; ulong label_cnt = 0UL;
293 :
294 0 : for( ulong i=0UL; i<text_cnt; i++ ) {
295 0 : fd_sbpf_instr_t instr = fd_sbpf_instr( text[i] );
296 0 : if ( instr.opcode.raw==FD_SBPF_OP_CALL_IMM ) func_cnt++;
297 0 : else if( instr.opcode.raw==FD_SBPF_OP_EXIT ) func_cnt++;
298 0 : else if( instr.opcode.raw==FD_SBPF_OP_CALL_REG ) continue;
299 0 : else if( ( (instr.opcode.any.op_class==FD_SBPF_OPCODE_CLASS_JMP ) |
300 0 : (instr.opcode.any.op_class==FD_SBPF_OPCODE_CLASS_JMP32) ) ) label_cnt++;
301 0 : }
302 :
303 0 : if( FD_UNLIKELY( (func_cnt>65536UL) | (label_cnt>65536UL) ) ) return FD_VM_ERR_UNSUP;
304 :
305 0 : func_cnt = 0UL;
306 0 : label_cnt = 0UL;
307 :
308 0 : for( ulong i=0UL; i<text_cnt; i++ ) {
309 0 : fd_sbpf_instr_t instr = fd_sbpf_instr( text[i] );
310 0 : if ( instr.opcode.raw==FD_SBPF_OP_CALL_IMM ) func_pc[ func_cnt++ ] = i + instr.imm + 1UL; /* FIXME: what if out of bounds? */
311 0 : else if( instr.opcode.raw==FD_SBPF_OP_EXIT ) func_pc[ func_cnt++ ] = i + instr.imm + 1UL; /* FIXME: what if out of bounds? */
312 0 : else if( instr.opcode.raw==FD_SBPF_OP_CALL_REG ) continue;
313 0 : else if( ( (instr.opcode.any.op_class==FD_SBPF_OPCODE_CLASS_JMP ) |
314 0 : (instr.opcode.any.op_class==FD_SBPF_OPCODE_CLASS_JMP32) ) )
315 0 : label_pc[ label_cnt++ ] = (ulong)((long)i + (long)instr.offset + 1L); /* FIXME: casting and what if out of bounds? */
316 0 : }
317 :
318 : /* Output the program */
319 :
320 0 : OUT_PRINTF( "function_0:\n" );
321 :
322 0 : for( ulong i=0UL; i<text_cnt; i++ ) {
323 :
324 : /* Print functions / labels */
325 : /* FIXME: What if there is a func_pc and a label_pc that target
326 : for the same instruction? It is possible given the above logic.
327 : Probably should print both. */
328 : /* FIXME: Algo efficiency! */
329 :
330 0 : int found = 0;
331 0 : for( ulong j=0UL; j<label_cnt; j++ ) if( label_pc[j]==i ) { found = 1; OUT_PRINTF( "lbb_%lu:\n", i ); break; }
332 0 : if( !found ) for( ulong j=0UL; j<func_cnt; j++ ) if( func_pc[j]==i ) { OUT_PRINTF( "\nfunction_%lu:\n", i ); break; }
333 :
334 : /* Print instruction */
335 :
336 : /* FIXME: WHAT ABOUT LABELS IN THE MIDDLE OF MULTIWORD INSTRUCTIONS!
337 : AND NOT JUST FOR DISASSEMBLY ... POTENTIAL CONSENSUS FAILURE
338 : MECHANISM! */
339 :
340 0 : fd_sbpf_instr_t instr = fd_sbpf_instr( text[i] );
341 0 : ulong extra_cnt = fd_ulong_if( instr.opcode.any.op_class==FD_SBPF_OPCODE_CLASS_LD, 1UL, 0UL );
342 0 : if( FD_UNLIKELY( (i+extra_cnt)>=text_cnt ) ) return FD_VM_ERR_INVAL; /* Truncated multiword instruction at end of text */
343 :
344 0 : OUT_PRINTF( " " );
345 0 : int err = fd_vm_disasm_instr( text+i, text_cnt-i, i, syscalls, out, out_max, _out_len );
346 0 : if( FD_UNLIKELY( err ) ) return err;
347 0 : OUT_PRINTF( "\n" );
348 :
349 0 : i += extra_cnt;
350 :
351 : /* Print any trailing function */
352 : /* FIXME: this is probably not necessary if the function/label print
353 : above just prints both, unless trying print a function label that
354 : happens to immediately after the end of a program. */
355 : /* FIXME: Algo efficiency? */
356 :
357 0 : if( FD_UNLIKELY( (instr.opcode.raw==FD_SBPF_OP_JA) & ((i+1UL)<text_cnt) ) ) {
358 0 : found = 0;
359 0 : for( ulong j=0UL; j<label_cnt; j++ ) if( label_pc[j]==(i+1UL) ) { found = 1; break; }
360 0 : if( !found ) OUT_PRINTF( "\nfunction_%lu:\n", i+1UL );
361 0 : }
362 0 : }
363 :
364 0 : return FD_VM_SUCCESS;
365 0 : }
366 :
367 : #undef OUT_PRINTF
|