summaryrefslogtreecommitdiff
path: root/tests/tst-syscall.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/tst-syscall.c')
-rw-r--r--tests/tst-syscall.c229
1 files changed, 229 insertions, 0 deletions
diff --git a/tests/tst-syscall.c b/tests/tst-syscall.c
new file mode 100644
index 0000000..92c3fd3
--- /dev/null
+++ b/tests/tst-syscall.c
@@ -0,0 +1,229 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <libmcpu.h>
+
+#include "mcpu-asm.h"
+
+
+#define TEST_NR_brk 10
+
+#define CHECK(cond,msg) \
+ do { \
+ if( !(cond) ) \
+ { \
+ fprintf( stderr, "FAIL: %s\n", (msg) ); \
+ return( 1 ); \
+ } \
+ } while( 0 )
+
+
+static __mpu_uint64_t
+reg64( __mpu_uint32_t address )
+{
+ return( __cpu_data_get_u64( __cpu_reg_ptr( 64, address ) ) );
+}
+
+
+static void
+put32( __mpu_uint32_t address, __mpu_uint32_t value )
+{
+ __cpu_data_put_u32( __cpu_reg_ptr( 32, address ), value );
+}
+
+
+static void
+put64( __mpu_uint32_t address, __mpu_uint64_t value )
+{
+ __cpu_data_put_u64( __cpu_reg_ptr( 64, address ), value );
+}
+
+
+static int
+run_syscall( __mpu_uint32_t nargs, __mpu_uint32_t sysno,
+ __mpu_uint64_t arg0, __mpu_uint64_t *ret )
+{
+ __mpu_uint64_t keep1 = __mpu_UINT64_C( 0x1111111111111111 );
+ __mpu_uint64_t keep2 = __mpu_UINT64_C( 0x2222222222222222 );
+ __mpu_uint64_t keep3 = __mpu_UINT64_C( 0x3333333333333333 );
+ __mpu_uint64_t keep4 = __mpu_UINT64_C( 0x4444444444444444 );
+ __mpu_uint64_t keep5 = __mpu_UINT64_C( 0x5555555555555555 );
+ int rc;
+
+ put32( CPU_SYSCALL_NARGS_ADDR, nargs );
+ put32( CPU_SYSCALL_SYSNO_ADDR, sysno );
+ put64( CPU_SYSCALL_ARG0_ADDR, arg0 );
+ put64( CPU_SYSCALL_ARG1_ADDR, keep1 );
+ put64( CPU_SYSCALL_ARG2_ADDR, keep2 );
+ put64( CPU_SYSCALL_ARG3_ADDR, keep3 );
+ put64( CPU_SYSCALL_ARG4_ADDR, keep4 );
+ put64( CPU_SYSCALL_ARG5_ADDR, keep5 );
+
+ __cpu_pc = 0;
+ rc = __cpu_run();
+ if( rc != CPU_EXEC_SUCCESS ) return( rc );
+
+ *ret = reg64( CPU_SYSCALL_RET_ADDR );
+
+ if( reg64( CPU_SYSCALL_ARG0_ADDR ) != arg0 ||
+ reg64( CPU_SYSCALL_ARG1_ADDR ) != keep1 ||
+ reg64( CPU_SYSCALL_ARG2_ADDR ) != keep2 ||
+ reg64( CPU_SYSCALL_ARG3_ADDR ) != keep3 ||
+ reg64( CPU_SYSCALL_ARG4_ADDR ) != keep4 ||
+ reg64( CPU_SYSCALL_ARG5_ADDR ) != keep5 )
+ return( -1000 );
+
+ return( 0 );
+}
+
+
+static void
+regop( cpu_operand *o, unsigned int bits, __mpu_uint32_t address )
+{
+ memset( o, 0, sizeof( *o ) );
+ o->type = CPU_OPERAND_REGISTER;
+ o->value.reg.bits = bits;
+ o->value.reg.address = address;
+}
+
+
+int
+main( void )
+{
+ static const char source[] =
+ ".text\n"
+ "SYSCALL\n"
+ "HALT\n";
+ cpu_asm_image image;
+ cpu_instruction store;
+ char err[256];
+ __mpu_uint64_t ptr, ret;
+ __mpu_int64_t sret;
+ size_t i;
+
+ memset( &image, 0, sizeof( image ) );
+
+ __cpu_init();
+
+ CHECK( CPU_SYSCALL_NARGS_ADDR == CPU_SYSCALL_RET_ADDR,
+ "NARGS overlaps low-address half of RET storage" );
+ CHECK( CPU_SYSCALL_SYSNO_ADDR == CPU_SYSCALL_RET_ADDR + 4,
+ "SYSNO overlaps second half of RET storage" );
+ CHECK( CPU_SYSCALL_ARG0_ADDR + 8 == CPU_SYSCALL_RET_ADDR,
+ "ARG0 is immediately below RET" );
+
+ CHECK( cpu_assemble_text( source, &image, err, sizeof( err ) ) == 0, err );
+ CHECK( image.code_size == 4, "SYSCALL/HALT code size" );
+ CHECK( image.code[0] == 0x00 && image.code[1] == 0x04,
+ "SYSCALL opcode encoding" );
+ CHECK( __cpu_program_set( image.code, image.code_size ) == 0,
+ "program load" );
+ CHECK( __cpu_heap_alloc( 64, 8192 ) == 0, "heap allocation" );
+
+ CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_HEAP, CPU_HEAP_BEGIN ) == 0,
+ "make initial HEAP pointer" );
+ put64( 0x00000, ptr );
+ put64( 0x00008, __mpu_UINT64_C( 0xa5a5a5a5a5a5a5a5 ) );
+ memset( &store, 0, sizeof( store ) );
+ store.opcode = CPU_OP_STOREP;
+ regop( &store.operand[0], 64, 0x00000 );
+ regop( &store.operand[1], 64, 0x00008 );
+ CHECK( __cpu_exec( &store ) == CPU_EXEC_MEMORY_FAULT,
+ "initial break protects HEAP above _end" );
+
+ CHECK( run_syscall( 1, TEST_NR_brk, CPU_NULL_PTR, &ret ) == 0,
+ "SYS_brk query" );
+ CHECK( __cpu_ptr_segment( ret ) == CPU_PTR_SEG_HEAP &&
+ __cpu_ptr_offset( ret ) == CPU_HEAP_BEGIN,
+ "initial SYS_brk is HEAP:16" );
+
+ CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_HEAP, CPU_HEAP_BEGIN + 8 ) == 0,
+ "make grown break" );
+ CHECK( run_syscall( 1, TEST_NR_brk, ptr, &ret ) == 0,
+ "SYS_brk grow inside initial heap size" );
+ CHECK( ret == ptr, "SYS_brk grow return" );
+ CHECK( __cpu_exec( &store ) == CPU_EXEC_SUCCESS,
+ "HEAP becomes writable below new break" );
+
+ memset( __cpu_heap_ptr() + CPU_HEAP_BEGIN, 0x5a, 8 );
+ CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_HEAP, CPU_HEAP_BEGIN ) == 0,
+ "make shrink break" );
+ CHECK( run_syscall( 1, TEST_NR_brk, ptr, &ret ) == 0,
+ "SYS_brk shrink to _end" );
+ CHECK( ret == ptr, "SYS_brk shrink return" );
+ for( i = 0; i < 8; ++i )
+ CHECK( __cpu_heap_ptr()[CPU_HEAP_BEGIN + i] == 0,
+ "shrink zeroes released HEAP" );
+ CHECK( __cpu_heap_size() == 64,
+ "shrink does not reduce logical backing size" );
+ CHECK( __cpu_exec( &store ) == CPU_EXEC_MEMORY_FAULT,
+ "shrunk HEAP is inaccessible above _end" );
+
+ CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_HEAP, CPU_HEAP_BEGIN + 8 ) == 0,
+ "make regrown break" );
+ CHECK( run_syscall( 1, TEST_NR_brk, ptr, &ret ) == 0,
+ "SYS_brk regrow inside retained backing" );
+ CHECK( ret == ptr, "SYS_brk regrow return" );
+ for( i = 0; i < 8; ++i )
+ CHECK( __cpu_heap_ptr()[CPU_HEAP_BEGIN + i] == 0,
+ "regrown released HEAP remains zero" );
+
+ CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_HEAP, 1000 ) == 0,
+ "make break inside host capacity" );
+ CHECK( run_syscall( 1, TEST_NR_brk, ptr, &ret ) == 0,
+ "SYS_brk extends logical heap" );
+ CHECK( ret == ptr && __cpu_heap_size() == 1000,
+ "logical heap extension" );
+ __cpu_heap_ptr()[900] = 0x6b;
+
+ CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_HEAP, 5000 ) == 0,
+ "make break requiring backing growth" );
+ CHECK( run_syscall( 1, TEST_NR_brk, ptr, &ret ) == 0,
+ "SYS_brk grows host backing" );
+ CHECK( ret == ptr && __cpu_heap_size() == 5000,
+ "host-backed heap extension" );
+ CHECK( __cpu_heap_ptr()[900] == 0x6b,
+ "heap relocation preserves old contents" );
+ CHECK( __cpu_heap_ptr()[4999] == 0,
+ "newly allocated heap memory is zero" );
+
+ CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_HEAP, 8193 ) == 0,
+ "make break above immutable limit" );
+ CHECK( run_syscall( 1, TEST_NR_brk, ptr, &ret ) == 0,
+ "SYS_brk above limit returns normally" );
+ CHECK( __cpu_ptr_segment( ret ) == CPU_PTR_SEG_HEAP &&
+ __cpu_ptr_offset( ret ) == 5000 && __cpu_heap_size() == 5000,
+ "heap limit rejection preserves current break" );
+
+ CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_DATA, 32 ) == 0,
+ "make non-HEAP pointer" );
+ CHECK( run_syscall( 1, TEST_NR_brk, ptr, &ret ) == 0,
+ "SYS_brk rejects non-HEAP pointer" );
+ CHECK( __cpu_ptr_offset( ret ) == 5000,
+ "invalid SYS_brk returns current break" );
+
+ CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_HEAP, 6000 ) == 0,
+ "make break for bad NARGS" );
+ CHECK( run_syscall( 2, TEST_NR_brk, ptr, &ret ) == 0,
+ "SYS_brk bad NARGS" );
+ CHECK( __cpu_ptr_offset( ret ) == 5000,
+ "bad SYS_brk NARGS returns current break" );
+
+ CHECK( run_syscall( 0, 9, 0, &ret ) == 0,
+ "unimplemented syscall execution" );
+ memcpy( &sret, &ret, sizeof( sret ) );
+ CHECK( sret == -(__mpu_int64_t)ENOSYS,
+ "unimplemented syscall returns -ENOSYS" );
+
+ cpu_asm_image_free( &image );
+ __cpu_free_context();
+
+ puts( "libmcpu SYSCALL/SYS_brk regression: PASS" );
+ return( 0 );
+
+} /* End of main() */