LCOV - code coverage report
Current view: top level - flamenco/runtime/tests - fd_vm_test.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 246 276 89.1 %
Date: 2024-11-13 11:58:15 Functions: 5 6 83.3 %

          Line data    Source code
       1             : #include "fd_vm_test.h"
       2             : #include "../context/fd_exec_epoch_ctx.h"
       3             : #include "../context/fd_exec_slot_ctx.h"
       4             : #include "../context/fd_exec_txn_ctx.h"
       5             : #include "../../vm/test_vm_util.h"
       6             : #include "fd_exec_instr_test.h"
       7             : 
       8             : int
       9             : fd_vm_syscall_noop( void * _vm,
      10             :                     ulong arg0,
      11             :                     ulong arg1,
      12             :                     ulong arg2,
      13             :                     ulong arg3,
      14             :                     ulong arg4,
      15           0 :                     ulong* _ret){
      16             :   /* TODO: have input message determine CUs to deduct?
      17             :   fd_vm_t * vm = (fd_vm_t *) _vm;
      18             :   vm->cu = vm->cu - 5;
      19             :   */
      20             : 
      21           0 :   (void) _vm;
      22           0 :   (void) arg0;
      23           0 :   (void) arg1;
      24           0 :   (void) arg2;
      25           0 :   (void) arg3;
      26           0 :   (void) arg4;
      27           0 :   *_ret = 0;
      28           0 :   return 0;
      29           0 : }
      30             : 
      31             : ulong
      32             : fd_exec_vm_validate_test_run( fd_exec_instr_test_runner_t * runner,
      33             :                               void const *                  input_,
      34             :                               void **                       output_,
      35             :                               void *                        output_buf,
      36         858 :                               ulong                         output_bufsz ) {
      37         858 :   (void) runner;
      38         858 :   fd_exec_test_full_vm_context_t const * input  = fd_type_pun_const( input_ );
      39         858 :   fd_exec_test_validate_vm_effects_t **  output = fd_type_pun( output_ );
      40             : 
      41         858 :   if( FD_UNLIKELY( !input->has_vm_ctx ) ) {
      42           0 :     return 0UL;
      43           0 :   }
      44             : 
      45         858 :   fd_exec_instr_ctx_t * ctx = test_vm_minimal_exec_instr_ctx( fd_libc_alloc_virtual() );
      46             : 
      47         858 :   FD_TEST( output_bufsz >= sizeof(fd_exec_test_validate_vm_effects_t) );
      48             : 
      49             :   /* Capture outputs */
      50         858 :   FD_SCRATCH_ALLOC_INIT( l, output_buf );
      51             : 
      52         858 :   fd_exec_test_validate_vm_effects_t * effects =
      53         858 :     FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_validate_vm_effects_t),
      54         858 :                                 sizeof (fd_exec_test_validate_vm_effects_t) );
      55         858 :   FD_SCRATCH_ALLOC_FINI( l, 1UL );
      56             : 
      57         858 :   fd_valloc_t valloc = fd_scratch_virtual();
      58         858 :   do{
      59         858 :     fd_exec_test_vm_context_t const * vm_ctx = &input->vm_ctx;
      60             : 
      61             :     /* Follows prost/solfuzz-agave behavior for empty bytes field */
      62         858 :     uchar * rodata = NULL;
      63         858 :     ulong rodata_sz = 0UL;
      64         858 :     if( FD_LIKELY( vm_ctx->rodata ) ) {
      65         765 :       rodata = vm_ctx->rodata->bytes;
      66         765 :       rodata_sz = vm_ctx->rodata->size;
      67         765 :     }
      68             : 
      69         858 :     ulong * text = (ulong *) (rodata + vm_ctx->rodata_text_section_offset);
      70         858 :     ulong text_cnt = vm_ctx->rodata_text_section_length / 8UL;
      71             : 
      72         858 :     fd_vm_t * vm = fd_vm_join( fd_vm_new( fd_valloc_malloc( valloc, fd_vm_align(), fd_vm_footprint() ) ) );
      73         858 :     FD_TEST( vm );
      74             : 
      75         858 :     fd_vm_init(
      76         858 :       vm,
      77         858 :       ctx,
      78         858 :       0, /* heap_max */
      79         858 :       0, /* cu_avail */
      80         858 :       rodata,
      81         858 :       rodata_sz,
      82         858 :       text,
      83         858 :       text_cnt,
      84         858 :       vm_ctx->rodata_text_section_offset,
      85         858 :       vm_ctx->rodata_text_section_length,
      86         858 :       0, /* entry_pc, not used in validate at the moment */
      87         858 :       NULL, /* calldests */
      88         858 :       NULL, /* syscalls */
      89         858 :       NULL, /* trace */
      90         858 :       NULL, /* sha */
      91         858 :       NULL, /* mem regions */
      92         858 :       0,    /* mem regions count */
      93         858 :       NULL, /* mem regions accs */
      94         858 :       0,    /* is deprecated */
      95             :       FD_FEATURE_ACTIVE( ctx->slot_ctx, bpf_account_data_direct_mapping ) /* direct mapping */
      96         858 :     );
      97         858 :     effects->result = fd_vm_validate( vm );
      98             : 
      99         858 :     fd_valloc_free( valloc, fd_vm_delete( fd_vm_leave( vm ) ) );
     100             : 
     101         858 :   } while(0);
     102             : 
     103             : 
     104             :   /* Run vm validate and capture result */
     105             : 
     106         858 :   effects->success = (effects->result == FD_VM_SUCCESS);
     107         858 :   *output = effects;
     108             : 
     109         858 :   test_vm_exec_instr_ctx_delete( ctx );
     110         858 :   return sizeof (fd_exec_test_validate_vm_effects_t);
     111         858 : }
     112             : 
     113             : void
     114             : setup_vm_acc_region_metas( fd_vm_acc_region_meta_t * acc_regions_meta,
     115             :                            fd_vm_t *                 vm,
     116       11217 :                            fd_exec_instr_ctx_t *     instr_ctx ) {
     117             :   /* cur_region is used to figure out what acc region index the account
     118             :      corresponds to. */
     119       11217 :   uint cur_region = 0UL;
     120       12456 :   for( ulong i=0UL; i<instr_ctx->instr->acct_cnt; i++ ) {
     121        1239 :     cur_region++;
     122        1239 :     fd_borrowed_account_t const * acc = instr_ctx->instr->borrowed_accounts[i];
     123        1239 :     acc_regions_meta[i].region_idx          = cur_region;
     124        1239 :     acc_regions_meta[i].has_data_region     = acc->const_meta->dlen>0UL;
     125        1239 :     acc_regions_meta[i].has_resizing_region = !vm->is_deprecated;
     126        1239 :     if( acc->const_meta->dlen>0UL ) {
     127         501 :       cur_region++;
     128         501 :     }
     129        1239 :     if( vm->is_deprecated ) {
     130           6 :       cur_region--;
     131           6 :     }
     132        1239 :   }
     133       11217 : }
     134             : 
     135             : ulong
     136             : fd_exec_vm_interp_test_run( fd_exec_instr_test_runner_t *         runner,
     137             :                             fd_exec_test_syscall_context_t const *input,
     138             :                             fd_exec_test_syscall_effects_t      **output,
     139             :                             void *                                output_buf,
     140           6 :                             ulong                                 output_bufsz ) {
     141           6 :   fd_wksp_t  * wksp  = fd_wksp_attach( "wksp" );
     142           6 :   fd_alloc_t * alloc = fd_alloc_join( fd_alloc_new( fd_wksp_alloc_laddr( wksp, fd_alloc_align(), fd_alloc_footprint(), 2 ), 2 ), 0 );
     143             : 
     144             :   /* Create execution context */
     145           6 :   const fd_exec_test_instr_context_t * input_instr_ctx = &input->instr_ctx;
     146           6 :   fd_exec_instr_ctx_t instr_ctx[1];
     147           6 :   if( !fd_exec_test_instr_context_create( runner, instr_ctx, input_instr_ctx, alloc, true /* is_syscall avoids certain checks we don't want */ ) ) {
     148           0 :     fd_exec_test_instr_context_destroy( runner, instr_ctx, wksp, alloc );
     149           0 :     return 0UL;
     150           0 :   }
     151             : 
     152           6 :   if( !( input->has_vm_ctx && input->has_syscall_invocation ) ) {
     153           0 :     fd_exec_test_instr_context_destroy( runner, instr_ctx, wksp, alloc );
     154           0 :     return 0UL;
     155           0 :   }
     156             : 
     157           6 :   fd_valloc_t valloc = fd_scratch_virtual();
     158             : 
     159             :   /* Create effects */
     160           6 :   ulong output_end = (ulong) output_buf + output_bufsz;
     161           6 :   FD_SCRATCH_ALLOC_INIT( l, output_buf );
     162           6 :   fd_exec_test_syscall_effects_t * effects =
     163           6 :     FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_syscall_effects_t),
     164           6 :                                 sizeof (fd_exec_test_syscall_effects_t) );
     165           6 :   *effects = (fd_exec_test_syscall_effects_t) FD_EXEC_TEST_SYSCALL_EFFECTS_INIT_ZERO;
     166             : 
     167           6 :   if( FD_UNLIKELY( _l > output_end ) ) {
     168           0 :     fd_exec_test_instr_context_destroy( runner, instr_ctx, wksp, alloc );
     169           0 :     return 0UL;
     170           0 :   }
     171             : 
     172           6 : do{
     173             :   /* Setup regions */
     174           6 :   if ( !input->vm_ctx.rodata ) {
     175           0 :     break;
     176           0 :   }
     177           6 :   uchar * rodata = input->vm_ctx.rodata->bytes;
     178           6 :   ulong   rodata_sz = input->vm_ctx.rodata->size;
     179             : 
     180             :   /* Load input data regions */
     181           6 :   fd_vm_input_region_t * input_regions     = fd_valloc_malloc( valloc, alignof(fd_vm_input_region_t), sizeof(fd_vm_input_region_t) * input->vm_ctx.input_data_regions_count );
     182           6 :   uint                   input_regions_cnt = setup_vm_input_regions( input_regions, input->vm_ctx.input_data_regions, input->vm_ctx.input_data_regions_count, valloc );
     183             : 
     184           6 :   if (input->vm_ctx.heap_max > FD_VM_HEAP_DEFAULT) {
     185           0 :     break;
     186           0 :   }
     187             : 
     188             :   /* Setup calldests from call_whitelist */
     189           6 :   ulong * calldests = (ulong *) input->vm_ctx.call_whitelist->bytes;
     190             : 
     191             :   /* Setup syscalls. Have them all be no-ops */
     192           6 :   fd_sbpf_syscalls_t * syscalls = fd_sbpf_syscalls_new( fd_valloc_malloc( valloc, fd_sbpf_syscalls_align(), fd_sbpf_syscalls_footprint() ) );
     193           6 :   fd_vm_syscall_register_all( syscalls, 0 );
     194             : 
     195         774 :   for( ulong i=0; i< fd_sbpf_syscalls_slot_cnt(); i++ ){
     196         768 :     fd_sbpf_syscalls_t * syscall = fd_sbpf_syscalls_query( syscalls, syscalls[i].key, NULL );
     197         768 :     if ( !syscall ) {
     198         558 :       continue;
     199         558 :     }
     200         210 :     syscall->func = fd_vm_syscall_noop;
     201         210 :   }
     202             : 
     203             :   /* Setup trace */
     204           6 :   const uint DUMP_TRACE = 0; // Set to 1 to dump trace to stdout
     205           6 :   uint tracing_enabled = input->vm_ctx.tracing_enabled;
     206           6 :   fd_vm_trace_t * trace = NULL;
     207           6 :   ulong event_max = 1UL<<20;
     208           6 :   ulong event_data_max = 2048UL;
     209             : 
     210           6 :   if (!!tracing_enabled) {
     211           3 :     trace = fd_vm_trace_new( fd_valloc_malloc( valloc, fd_vm_trace_align(), fd_vm_trace_footprint( event_max, event_data_max ) ), event_max, event_data_max );
     212           3 :   }
     213             : 
     214             :   /* Setup vm */
     215           6 :   fd_vm_t * vm = fd_vm_join( fd_vm_new( fd_valloc_malloc( valloc, fd_vm_align(), fd_vm_footprint() ) ) );
     216           6 :   FD_TEST( vm );
     217             : 
     218             :   /* Override some execution state values from the interp fuzzer input
     219             :      This is so we can test if the interp (or vm setup) mutates any of
     220             :      these erroneously */
     221           6 :   vm->reg[0]  = input->vm_ctx.r0;
     222           6 :   vm->reg[1]  = input->vm_ctx.r1;
     223           6 :   vm->reg[2]  = input->vm_ctx.r2;
     224           6 :   vm->reg[3]  = input->vm_ctx.r3;
     225           6 :   vm->reg[4]  = input->vm_ctx.r4;
     226           6 :   vm->reg[5]  = input->vm_ctx.r5;
     227           6 :   vm->reg[6]  = input->vm_ctx.r6;
     228           6 :   vm->reg[7]  = input->vm_ctx.r7;
     229           6 :   vm->reg[8]  = input->vm_ctx.r8;
     230           6 :   vm->reg[9]  = input->vm_ctx.r9;
     231           6 :   vm->reg[10] = input->vm_ctx.r10;
     232           6 :   vm->reg[11] = input->vm_ctx.r11;
     233             : 
     234           6 :   fd_vm_init(
     235           6 :     vm,
     236           6 :     instr_ctx,
     237           6 :     FD_VM_HEAP_MAX,
     238           6 :     input->has_instr_ctx ? input->instr_ctx.cu_avail : 0,
     239           6 :     rodata,
     240           6 :     rodata_sz,
     241           6 :     (ulong *) rodata, /* text*, same as rodata */
     242           6 :     rodata_sz / 8, /* text_cnt */
     243           6 :     0, /* text_off */
     244           6 :     rodata_sz, /* text_sz */
     245           6 :     input->vm_ctx.entry_pc,
     246           6 :     calldests,
     247           6 :     syscalls,
     248           6 :     trace, /* trace */
     249           6 :     NULL, /* sha */
     250           6 :     input_regions,
     251           6 :     input_regions_cnt,
     252           6 :     NULL, /* vm_acc_region_meta*/
     253           6 :     0, /* is deprecated */
     254             :     FD_FEATURE_ACTIVE( instr_ctx->slot_ctx, bpf_account_data_direct_mapping ) /* direct mapping */
     255           6 :   );
     256             : 
     257             :   // Propagate the acc_regions_meta to the vm
     258           6 :   vm->acc_region_metas = fd_valloc_malloc( valloc, alignof(fd_vm_acc_region_meta_t), sizeof(fd_vm_acc_region_meta_t) * input->vm_ctx.input_data_regions_count );
     259           6 :   setup_vm_acc_region_metas( vm->acc_region_metas, vm, vm->instr_ctx );
     260             : 
     261             :   // Validate the vm
     262           6 :   if ( fd_vm_validate( vm ) != FD_VM_SUCCESS ) {
     263           0 :     effects->error = -1;
     264           0 :     break;
     265           0 :   }
     266             : 
     267           6 :   if( input->syscall_invocation.stack_prefix ) {
     268           6 :     uchar * stack    = input->syscall_invocation.stack_prefix->bytes;
     269           6 :     ulong   stack_sz = fd_ulong_min(input->syscall_invocation.stack_prefix->size, FD_VM_STACK_MAX);
     270           6 :     fd_memcpy( vm->stack, stack, stack_sz );
     271           6 :   }
     272             : 
     273           6 :   if( input->syscall_invocation.heap_prefix ) {
     274           3 :     uchar * heap    = input->syscall_invocation.heap_prefix->bytes;
     275           3 :     ulong   heap_sz = fd_ulong_min(input->syscall_invocation.heap_prefix->size, FD_VM_HEAP_MAX);
     276           3 :     fd_memcpy( vm->heap, heap, heap_sz );
     277           3 :   }
     278             : 
     279             :   /* Run vm */
     280           6 :   int exec_res = 0;
     281           6 :   if (!!tracing_enabled) {
     282           3 :     exec_res = fd_vm_exec_trace( vm );
     283           3 :     if( DUMP_TRACE ) fd_vm_trace_printf( trace, syscalls );
     284           3 :     fd_vm_trace_delete( fd_vm_trace_leave( trace ) );
     285           3 :   } else {
     286           3 :     exec_res = fd_vm_exec_notrace( vm );
     287           3 :   }
     288           6 :   effects->error = -1 * exec_res;
     289             : 
     290             :   /* Capture outputs */
     291           6 :   effects->cu_avail    = vm->cu;
     292           6 :   effects->frame_count = vm->frame_cnt;
     293           6 :   effects->r0          = exec_res ? 0 : vm->reg[0]; /* Only capture r0 if no error */
     294             : 
     295             :   /* skip logs since syscalls are stubbed */
     296             : 
     297             :   /* CU error is difficult to properly compare as there may have been
     298             :      valid writes to the memory regions prior to capturing the error. And
     299             :      the pc might be well past (by an arbitrary amount) the instruction
     300             :      where the CU error occurred. */
     301           6 :   if( exec_res == FD_VM_ERR_SIGCOST ) break;
     302             : 
     303           3 :   effects->pc = vm->pc;
     304             : 
     305           3 :   effects->heap       = FD_SCRATCH_ALLOC_APPEND(
     306           3 :     l, alignof(uchar), PB_BYTES_ARRAY_T_ALLOCSIZE( vm->heap_max ) );
     307           0 :   effects->heap->size = (uint)vm->heap_max;
     308           3 :   fd_memcpy( effects->heap->bytes, vm->heap, vm->heap_max );
     309             : 
     310           3 :   effects->stack       = FD_SCRATCH_ALLOC_APPEND(
     311           3 :     l, alignof(uchar), PB_BYTES_ARRAY_T_ALLOCSIZE( FD_VM_STACK_MAX ) );
     312           3 :   effects->stack->size = (uint)FD_VM_STACK_MAX;
     313           3 :   fd_memcpy( effects->stack->bytes, vm->stack, FD_VM_STACK_MAX );
     314             : 
     315           3 :   effects->rodata       = FD_SCRATCH_ALLOC_APPEND(
     316           3 :     l, alignof(uchar), PB_BYTES_ARRAY_T_ALLOCSIZE( rodata_sz ) );
     317           0 :   effects->rodata->size = (uint)rodata_sz;
     318           3 :   fd_memcpy( effects->rodata->bytes, rodata, rodata_sz );
     319             : 
     320             :   /* Capture input data regions */
     321           3 :   ulong tmp_end = FD_SCRATCH_ALLOC_FINI(l, 1UL);
     322           3 :   ulong input_data_regions_size = load_from_vm_input_regions( vm->input_mem_regions,
     323           3 :                                                               vm->input_mem_regions_cnt,
     324           3 :                                                               &effects->input_data_regions,
     325           3 :                                                               &effects->input_data_regions_count,
     326           3 :                                                               (void *) tmp_end,
     327           3 :                                                               fd_ulong_sat_sub( output_end, tmp_end) );
     328           3 :   FD_SCRATCH_ALLOC_APPEND( l, 1UL, input_data_regions_size );
     329             : 
     330             : 
     331           3 : } while(0);
     332             : 
     333           6 :   ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
     334           6 :   *output = effects;
     335           6 :   fd_exec_test_instr_context_destroy( runner, instr_ctx, wksp, alloc );
     336           6 :   return actual_end - (ulong)output_buf;
     337           6 : }
     338             : 
     339             : 
     340             : uint
     341             : setup_vm_input_regions( fd_vm_input_region_t *                   input,
     342             :                         fd_exec_test_input_data_region_t const * test_input,
     343             :                         ulong                                    test_input_count,
     344         720 :                         fd_valloc_t                              valloc ) {
     345         720 :   ulong offset = 0UL;
     346         720 :   uint input_idx = 0UL;
     347        2982 :   for( ulong i=0; i<test_input_count; i++ ) {
     348        2262 :     fd_exec_test_input_data_region_t const * region = &test_input[i];
     349        2262 :     pb_bytes_array_t * array = region->content;
     350        2262 :     if( !array ) {
     351          63 :       continue; /* skip empty regions https://github.com/anza-xyz/agave/blob/3072c1a72b2edbfa470ca869f1ea891dfb6517f2/programs/bpf_loader/src/serialization.rs#L136 */
     352          63 :     }
     353             : 
     354        2199 :     uchar * haddr = fd_valloc_malloc( valloc, 8UL, array->size );
     355        2199 :     fd_memcpy( haddr, array->bytes, array->size );
     356        2199 :     input[input_idx].vaddr_offset     = offset;
     357        2199 :     input[input_idx].haddr            = (ulong)haddr;
     358        2199 :     input[input_idx].region_sz        = array->size;
     359        2199 :     input[input_idx].is_writable      = region->is_writable;
     360             : 
     361        2199 :     input_idx++;
     362        2199 :     offset += array->size;
     363        2199 :   }
     364         720 :   return input_idx; /* return the number of populated regions */
     365         720 : }
     366             : 
     367             : 
     368             : ulong
     369             : load_from_vm_input_regions( fd_vm_input_region_t const *        input,
     370             :                             uint                                input_count,
     371             :                             fd_exec_test_input_data_region_t ** output,
     372             :                             pb_size_t *                         output_count,
     373             :                             void *                              output_buf,
     374       11214 :                             ulong                               output_bufsz ) {
     375             :   /* pre-flight checks on output buffer size*/
     376       11214 :   ulong input_regions_total_sz = 0;
     377       13410 :   for( ulong i=0; i<input_count; i++ ) {
     378        2196 :     input_regions_total_sz += input[i].region_sz;
     379        2196 :   }
     380             : 
     381       11214 :   if( FD_UNLIKELY(   input_regions_total_sz == 0
     382       11214 :                   || output_bufsz < input_regions_total_sz ) ) {
     383       10497 :     *output = NULL;
     384       10497 :     *output_count = 0;
     385       10497 :     return 0;
     386       10497 :   }
     387             : 
     388         717 :   FD_SCRATCH_ALLOC_INIT( l, output_buf );
     389         717 :   *output = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_input_data_region_t),
     390         717 :                                       input_count * sizeof (fd_exec_test_input_data_region_t) );
     391         717 :   FD_TEST( *output );
     392         717 :   *output_count = input_count;
     393             : 
     394        2913 :   for( ulong i=0; i<input_count; i++ ) {
     395        2196 :     fd_vm_input_region_t const * vm_region = &input[i];
     396        2196 :     fd_exec_test_input_data_region_t * out_region = &(*output)[i];
     397        2196 :     out_region->is_writable = vm_region->is_writable;
     398        2196 :     out_region->offset = vm_region->vaddr_offset;
     399             : 
     400        2196 :     out_region->content = FD_SCRATCH_ALLOC_APPEND( l, alignof(pb_bytes_array_t),
     401        2196 :                                                PB_BYTES_ARRAY_T_ALLOCSIZE(vm_region->region_sz) );
     402        2196 :     FD_TEST( out_region->content );
     403        2196 :     out_region->content->size = vm_region->region_sz;
     404        2196 :     fd_memcpy( out_region->content->bytes, (void *)vm_region->haddr, vm_region->region_sz );
     405        2196 :   }
     406             : 
     407         717 :   ulong end = FD_SCRATCH_ALLOC_FINI( l, 1UL );
     408         717 :   return end - (ulong)output_buf; /* return the number of bytes written */
     409         717 : }

Generated by: LCOV version 1.14