summaryrefslogtreecommitdiff
path: root/tests/tst-melf.c
diff options
context:
space:
mode:
authorkx <kx@radix-linux.su>2026-10-09 18:00:15 +0300
committerkx <kx@radix-linux.su>2026-10-09 18:00:15 +0300
commit065da3509019b15dd0e678072e6be629e5330bef (patch)
tree69c934c9e786ac048b4fbe8f4c4c5571661521f4 /tests/tst-melf.c
parentd130fe27fa753be02ed76fd9b277aa3c3d928eb9 (diff)
downloadlibmcpu-0.4.0.tar.xz
Version 0.4.00.4.0
Diffstat (limited to 'tests/tst-melf.c')
-rw-r--r--tests/tst-melf.c430
1 files changed, 430 insertions, 0 deletions
diff --git a/tests/tst-melf.c b/tests/tst-melf.c
new file mode 100644
index 0000000..c872857
--- /dev/null
+++ b/tests/tst-melf.c
@@ -0,0 +1,430 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libmcpu.h>
+#include <mcpu-elf.h>
+
+#include "mcpu-asm.h"
+#include "mcpu-object.h"
+#include "mcpu-link.h"
+#include "mcpu-exec.h"
+#include "mcpu-melf.h"
+
+#define CHECK(COND, MSG) \
+ do { if(!(COND)) { fprintf(stderr, "FAIL: %s\n", (MSG)); goto out; } } while(0)
+
+static int
+find_symbol( const cpu_asm_object *o, const char *name, size_t *idx )
+{
+ size_t i;
+ for( i = 0; i < o->symbol_count; ++i )
+ if( !strcmp( o->symbols[i].name, name ) )
+ {
+ if( idx ) * idx = i;
+ return( 0 );
+ }
+ return( -1 );
+}
+
+static int
+write_copy( const char *path, const unsigned char *p, size_t n )
+{
+ FILE *f = fopen( path, "wb" );
+ if( !f ) return( -1 );
+ if( n && fwrite( p, 1, n, f ) != n )
+ {
+ fclose( f );
+ return( -1 );
+ }
+ return( fclose( f ) ? -1 : 0 );
+}
+
+static int
+expect_load_fail( const char *path, const char *needle )
+{
+ cpu_exec_loaded ld;
+ char err[512];
+ char *av[] = {(char *)"prog"};
+
+ if( !cpu_exec_load( path, 1, av, &ld, err, sizeof( err ) ) )
+ {
+ fprintf( stderr, "malformed executable unexpectedly loaded: %s\n", path );
+ return( -1 );
+ }
+ if( needle && !strstr( err, needle ) )
+ {
+ fprintf( stderr, "load error mismatch: expected '%s', got '%s'\n", needle, err );
+ return( -1 );
+ }
+ return( 0 );
+}
+
+int
+main( void )
+{
+ static const char main_src[] =
+ ".extern func\n"
+ ".extern shared\n"
+ ".extern scratch\n"
+ ".text\n"
+ "_start_life:\n"
+ " LDI r64@0x00000, 40\n"
+ " CALL func\n" /* CODE64 */
+ " STORE shared, r64@0x00000\n" /* DATA64 */
+ " LOAD r64@0x00018, shared\n" /* DATA64 */
+ " LEA r64@0x00020, scratch\n" /* PTR64_CODE */
+ " LDI r32@0x00030, 0\n"
+ " EXIT r32@0x00030\n";
+
+ static const char lib_src[] =
+ ".global func, shared, scratch, shared_ptr, func_ptr\n"
+ ".data\n"
+ ".align 32\n"
+ "shared:\n"
+ " .integer 64, 0\n"
+ "shared_ptr:\n"
+ " .ptr scratch\n" /* PTR64_DATA */
+ "func_ptr:\n"
+ " .cptr func\n" /* CPTR64_DATA */
+ ".bss\n"
+ ".align 64\n"
+ "scratch:\n"
+ " .zero 128\n"
+ ".text\n"
+ "func:\n"
+ " LDI r64@0x00008, 2\n"
+ " IADD r64@0x00000, r64@0x00000, r64@0x00008\n"
+ " RET\n";
+
+ char dtemplate[] = "/tmp/libcpu-melf-XXXXXX";
+ char main_o[512], lib_o[512], exe[512], life_exe[512], bad_guard[512], bad_entry[512];
+ char err[512];
+ cpu_asm_object built[2], readback[2];
+ cpu_asm_image im;
+ cpu_exec_options opt;
+ cpu_exec_loaded ld;
+ unsigned char *file = 0;
+ size_t file_size = 0;
+ size_t ix, i;
+ __mpu_uint64_t p;
+ int data;
+ int rc = 1;
+
+ memset( built, 0, sizeof( built ) );
+ memset( readback, 0, sizeof( readback ) );
+ memset( &im, 0, sizeof( im ) );
+
+ CHECK( mkdtemp( dtemplate ) != NULL, "mkdtemp" );
+ snprintf( main_o, sizeof( main_o ), "%s/main.o", dtemplate );
+ snprintf( lib_o, sizeof( lib_o ), "%s/lib.o", dtemplate );
+ snprintf( exe, sizeof( exe ), "%s/a.melf", dtemplate );
+ snprintf( life_exe, sizeof( life_exe ), "%s/lifecycle.melf", dtemplate );
+ snprintf( bad_guard, sizeof( bad_guard ), "%s/bad-guard.melf", dtemplate );
+ snprintf( bad_entry, sizeof( bad_entry ), "%s/bad-entry.melf", dtemplate );
+
+ __cpu_init();
+
+ /*
+ * LibMPU uses MPU_BYTE_ORDER / MPU_WORD_ORDER as numeric tags (for
+ * example 1234), while the *_LITTLE_ENDIAN / *_BIG_ENDIAN macros are
+ * boolean selectors. MELF must key off the boolean selectors rather
+ * than compare the tag with 0/1.
+ */
+#if CPU_DATA_BYTE_ORDER_LITTLE_ENDIAN == 1
+ CHECK( cpu_melf_data_encoding() == MELF_DATA_LSB,
+ "MELF EI_DATA follows LibMPU little-endian selector");
+#elif CPU_DATA_BYTE_ORDER_BIG_ENDIAN == 1
+ CHECK( cpu_melf_data_encoding() == MELF_DATA_MSB,
+ "MELF EI_DATA follows LibMPU big-endian selector");
+#else
+# error Unsupported LibMPU byte order for MELF regression
+#endif
+ {
+ MElf_McpuTarget mt;
+ cpu_melf_fill_target( &mt );
+#if CPU_DATA_WORD_ORDER_LITTLE_ENDIAN == 1
+ CHECK( mt.mt_data_word_order == MELF_MCPU_WORD_ORDER_LSB,
+ "MELF target metadata follows LibMPU little word-order selector");
+#elif CPU_DATA_WORD_ORDER_BIG_ENDIAN == 1
+ CHECK( mt.mt_data_word_order == MELF_MCPU_WORD_ORDER_MSB,
+ "MELF target metadata follows LibMPU big word-order selector");
+#else
+# error Unsupported LibMPU word order for MELF regression
+#endif
+ }
+
+ CHECK( cpu_assemble_object( main_src, &built[0], err, sizeof( err ) ) == 0, err );
+ CHECK( cpu_assemble_object( lib_src, &built[1], err, sizeof( err ) ) == 0, err );
+ CHECK( built[1].has_bss && built[1].bss_size == 128 && built[1].bss_align == 64,
+ "assembler .bss/NOBITS metadata");
+ CHECK( built[0].reloc_count == 4 && built[1].reloc_count == 2,
+ "all expected relocations emitted");
+
+ /* Assembler-only lifecycle pseudo-label rules. */
+ {
+ cpu_asm_object q;
+ memset( &q, 0, sizeof( q ) );
+ CHECK( cpu_assemble_object(
+ ".init\n_start:\n END\n.text\n_start_life:\n END\n",
+ &q, err, sizeof( err )) != 0 && strstr( err, "_start_life" ),
+ "_start_life is rejected when .init exists");
+ cpu_asm_object_free( &q );
+ memset( &q, 0, sizeof( q ) );
+ CHECK( cpu_assemble_object(
+ ".text\n_start:\n_start_life:\n END\n",
+ &q, err, sizeof( err )) != 0 && strstr( err, "conflicts" ),
+ "_start_life conflicts with explicit _start/_life");
+ cpu_asm_object_free( &q );
+ memset( &q, 0, sizeof( q ) );
+ CHECK( cpu_assemble_object(
+ ".weak _start\n.text\n_start_life:\n END\n",
+ &q, err, sizeof( err )) != 0 && strstr( err, "cannot be weak" ),
+ "lifecycle symbols cannot be weak");
+ cpu_asm_object_free( &q );
+ }
+
+ /* Full .init/.text/.fini ET_EXEC lifecycle metadata and loader path. */
+ {
+ static const char life_src[] =
+ ".init\n"
+ "_start:\n"
+ " END\n"
+ ".text\n"
+ "_life:\n"
+ " LDI r32@0x00000, 0\n"
+ " EXIT r32@0x00000\n"
+ ".fini\n"
+ "_fini:\n"
+ " END\n";
+ cpu_asm_object q;
+ cpu_asm_image qi;
+ cpu_exec_loaded ql;
+ cpu_exec_options qo = {512, 0, 0, 0};
+ char *qv[] = {(char *)"lifecycle.melf"};
+ memset( &q, 0, sizeof( q ) );
+ memset( &qi, 0, sizeof( qi ) );
+ memset( &ql, 0, sizeof( ql ) );
+ CHECK( cpu_assemble_object( life_src, &q, err, sizeof( err ) ) == 0, err );
+ CHECK( cpu_link_objects( &q, 1, &qi, err, sizeof( err ) ) == 0, err );
+ CHECK( qi.has_start && qi.has_life && qi.has_init && qi.has_fini &&
+ qi.start_entry == qi.init_entry && qi.start_entry != qi.life_entry,
+ "linked INIT/LIFE/FINI lifecycle");
+ CHECK( cpu_exec_write( life_exe, &qi, &qo, err, sizeof( err ) ) == 0, err );
+ CHECK( cpu_exec_load( life_exe, 1, qv, &ql, err, sizeof( err ) ) == 0, err );
+ CHECK( ql.has_init && ql.has_fini && ql.start_entry == ql.init_entry &&
+ ql.start_entry != ql.life_entry,
+ "ET_EXEC INIT/LIFE/FINI loader metadata");
+ __cpu_free_context();
+ __cpu_init();
+ cpu_asm_image_free( &qi );
+ cpu_asm_object_free( &q );
+ }
+
+ CHECK( find_symbol( &built[0], "_start", &ix ) == 0,
+ "_start_life emits _start");
+ CHECK( built[0].symbols[ix].binding == CPU_OBJ_BIND_GLOBAL &&
+ built[0].symbols[ix].type == CPU_OBJ_TYPE_FUNC,
+ "_start ABI symbol attributes");
+ {
+ __mpu_uint64_t sv = built[0].symbols[ix].value;
+ CHECK( find_symbol( &built[0], "_life", &ix ) == 0,
+ "_start_life emits _life");
+ CHECK( built[0].symbols[ix].value == sv &&
+ built[0].symbols[ix].binding == CPU_OBJ_BIND_GLOBAL &&
+ built[0].symbols[ix].type == CPU_OBJ_TYPE_FUNC,
+ "_start/_life have same GLOBAL/FUNC address");
+ }
+
+ CHECK( cpu_object_write( main_o, &built[0], err, sizeof( err ) ) == 0, err );
+ CHECK( cpu_object_write( lib_o, &built[1], err, sizeof( err ) ) == 0, err );
+ CHECK( cpu_object_read( main_o, &readback[0], err, sizeof( err ) ) == 0, err );
+ CHECK( cpu_object_read( lib_o, &readback[1], err, sizeof( err ) ) == 0, err );
+ CHECK( readback[1].has_bss && readback[1].bss_size == 128 &&
+ readback[1].bss_align == 64, "ET_REL .bss round trip");
+ CHECK( readback[0].reloc_count == 4 && readback[1].reloc_count == 2,
+ "ET_REL RELA round trip");
+
+ /* Undefined lifecycle declarations retain STT_FUNC in ET_REL. */
+ {
+ cpu_asm_object q;
+ memset( &q, 0, sizeof( q ) );
+ CHECK( cpu_assemble_object(
+ ".extern _fini\n.text\n_start_life:\n END\n",
+ &q, err, sizeof(err)) == 0, err);
+ CHECK( find_symbol( &q, "_fini", &ix ) == 0 &&
+ q.symbols[ix].section == CPU_OBJ_SEC_UNDEF &&
+ q.symbols[ix].binding == CPU_OBJ_BIND_GLOBAL &&
+ q.symbols[ix].type == CPU_OBJ_TYPE_FUNC,
+ "undefined lifecycle symbol is GLOBAL/FUNC");
+ cpu_asm_object_free( &q );
+ }
+
+ /* WEAK resolution rules fixed by the symbol/RELA ABI. */
+ {
+ static const char weak_ptr_src[] =
+ ".weak missing\n"
+ ".data\nwp:\n .ptr missing\n"
+ ".text\n_start_life:\n END\n";
+ cpu_asm_object q;
+ cpu_asm_image qi;
+ memset( &q, 0, sizeof( q ) );
+ memset( &qi, 0, sizeof( qi ) );
+ CHECK( cpu_assemble_object( weak_ptr_src, &q, err, sizeof( err ) ) == 0, err );
+ CHECK( cpu_link_objects( &q, 1, &qi, err, sizeof( err ) ) == 0, err );
+ CHECK( qi.data_size >= CPU_DATA_NULL_GUARD_SIZE + 8 &&
+ __cpu_data_get_u64( qi.data + CPU_DATA_NULL_GUARD_SIZE ) == 0,
+ "unresolved weak pointer relocation becomes NULL");
+ cpu_asm_image_free( &qi );
+ cpu_asm_object_free( &q );
+ }
+ {
+ static const char weak_code_src[] =
+ ".weak missing\n"
+ ".text\n_start_life:\n CALL missing\n END\n";
+ cpu_asm_object q;
+ cpu_asm_image qi;
+ memset( &q, 0, sizeof( q ) );
+ memset( &qi, 0, sizeof( qi ) );
+ CHECK( cpu_assemble_object( weak_code_src, &q, err, sizeof( err ) ) == 0, err );
+ CHECK( cpu_link_objects( &q, 1, &qi, err, sizeof( err ) ) != 0 &&
+ strstr( err, "unresolved weak" ),
+ "unresolved weak CODE64 is a link error");
+ cpu_asm_image_free( &qi );
+ cpu_asm_object_free( &q );
+ }
+ {
+ static const char caller_src[] =
+ ".extern foo\n"
+ ".text\n_start_life:\n CALL foo\n END\n";
+ static const char weak_def_src[] =
+ ".weak foo\n.text\nfoo:\n NOP\n RET\n";
+ static const char strong_def_src[] =
+ ".global foo\n.text\nfoo:\n RET\n";
+ cpu_asm_object q[3];
+ cpu_asm_image qi;
+ __mpu_uint64_t expected_strong, encoded;
+ memset( q, 0, sizeof( q ) );
+ memset( &qi, 0, sizeof( qi ) );
+ CHECK( cpu_assemble_object( caller_src, &q[0], err, sizeof( err ) ) == 0, err );
+ CHECK( cpu_assemble_object( weak_def_src, &q[1], err, sizeof( err ) ) == 0, err );
+ CHECK( cpu_assemble_object( strong_def_src, &q[2], err, sizeof( err ) ) == 0, err );
+ expected_strong = q[0].text_size + q[1].text_size;
+ CHECK( cpu_link_objects( q, 3, &qi, err, sizeof( err ) ) == 0, err );
+ CHECK( q[0].reloc_count == 1 && q[0].relocs[0].type == CPU_RELOC_CODE64,
+ "strong/weak test CALL relocation");
+ encoded = __cpu_code_get_u64( qi.code + q[0].relocs[0].offset );
+ CHECK( encoded == expected_strong, "strong GLOBAL overrides earlier WEAK definition" );
+ cpu_asm_image_free( &qi );
+ for( i = 0; i < 3; ++i ) cpu_asm_object_free( &q[i] );
+ }
+
+ CHECK( cpu_link_objects( readback, 2, &im, err, sizeof( err ) ) == 0, err );
+ CHECK( im.has_start && im.has_life && !im.has_init &&
+ im.start_entry == im.life_entry, "linked no-INIT lifecycle");
+ CHECK( im.data_size == 56 && im.data_mem_size == 192,
+ "DATA filesz/memsz including .bss");
+ CHECK( im.bss_present && im.bss_offset == 64 && im.bss_size == 128,
+ "final .bss placement");
+ for( i = 0; i < CPU_DATA_NULL_GUARD_SIZE; ++i )
+ CHECK( im.data[i] == 0, "linker-owned DATA NULL guard" );
+
+ p = __cpu_data_get_u64( im.data + 40 );
+ CHECK( __cpu_ptr_segment( p ) == CPU_PTR_SEG_DATA &&
+ __cpu_ptr_offset( p ) == 64, "PTR64_DATA relocation to .bss");
+ p = __cpu_data_get_u64( im.data + 48 );
+ CHECK( __cpu_ptr_segment( p ) == CPU_PTR_SEG_PROGRAM,
+ "CPTR64_DATA relocation");
+
+ opt.stack_size = 512;
+ opt.have_heap = 1;
+ opt.heap_size = 128;
+ opt.heap_limit = 1024;
+ CHECK( cpu_exec_write( exe, &im, &opt, err, sizeof( err ) ) == 0, err );
+
+ {
+ char *av[] = {(char *)"a.melf", (char *)"alpha", (char *)"beta"};
+ CHECK( cpu_exec_load( exe, 3, av, &ld, err, sizeof( err ) ) == 0, err );
+ }
+ CHECK( __cpu_program_size() == im.code_size, "PROGRAM load size" );
+ CHECK( __cpu_data_size() == 192, "DATA p_memsz load size" );
+ CHECK( __cpu_stack_size() == 512, "STACK size comes from PT_MCPU_STACK" );
+ CHECK( __cpu_heap_size() == 128 && __cpu_heap_limit() == 1024,
+ "HEAP initial/maximum sizes");
+ CHECK( __cpu_sp == ld.startup_base && __cpu_fp == ld.stack_size,
+ "initial SP/FP startup state");
+ CHECK( __cpu_data_get_u32( __cpu_stack_ptr() + ld.startup_base +
+ MCPU_STARTUP_VERSION_OFF) == MCPU_STARTUP_VERSION_CURRENT,
+ "McpuStartup.version");
+ CHECK( __cpu_data_get_u64( __cpu_stack_ptr() + ld.startup_base +
+ MCPU_STARTUP_ARGC_OFF) == 3,
+ "McpuStartup.argc");
+ p = __cpu_data_get_u64( __cpu_stack_ptr() + ld.startup_base +
+ MCPU_STARTUP_ARGV_OFF);
+ CHECK( __cpu_ptr_segment( p ) == CPU_PTR_SEG_STACK,
+ "McpuStartup.argv segment");
+ CHECK( __cpu_data_get_u64( __cpu_stack_ptr() + __cpu_ptr_offset( p ) + 3 * 8 ) == 0,
+ "argv[argc] == NULL");
+ for( i = 64; i < 192; ++i )
+ CHECK( __cpu_data_ptr()[i] == 0, "NOBITS .bss loads as zero" );
+
+ /* Negative loader checks: it validates, never repairs. */
+ CHECK( cpu_melf_readall( exe, &file, &file_size, err, sizeof( err ) ) == 0, err );
+ data = file[MELF_EI_DATA];
+ {
+ __mpu_uint64_t phoff = cpu_melf_get64( file + MELF64_EHDR_E_PHOFF_OFF, data );
+ __mpu_uint16_t phnum = cpu_melf_get16( file + MELF64_EHDR_E_PHNUM_OFF, data );
+ __mpu_uint64_t dataoff = UINT64_MAX;
+ for( i = 0; i < phnum; ++i )
+ {
+ unsigned char *q = file + (size_t)phoff + i * MELF64_PHDR_SIZE;
+ if( cpu_melf_get32( q + MELF64_PHDR_P_TYPE_OFF, data ) == MELF_PT_LOAD &&
+ MELF_MCPU_PTR64_SEG( cpu_melf_get64( q + MELF64_PHDR_P_VADDR_OFF, data ) ) ==
+ MELF_MCPU_SEG_DATA)
+ {
+ dataoff = cpu_melf_get64( q + MELF64_PHDR_P_OFFSET_OFF, data );
+ break;
+ }
+ }
+ CHECK( dataoff != UINT64_MAX && dataoff < file_size, "find DATA PT_LOAD" );
+ file[(size_t)dataoff] = 1;
+ CHECK( write_copy( bad_guard, file, file_size ) == 0, "write bad guard file" );
+ file[(size_t)dataoff] = 0;
+ }
+ CHECK( expect_load_fail( bad_guard, "DATA NULL guard" ) == 0,
+ "loader rejects corrupted DATA guard");
+
+ cpu_melf_put64( file + MELF64_EHDR_E_ENTRY_OFF,
+ MELF_MCPU_PTR64( MELF_MCPU_SEG_PROGRAM, 1 ), data);
+ CHECK( write_copy( bad_entry, file, file_size ) == 0, "write bad entry file" );
+ CHECK( expect_load_fail( bad_entry, "without INIT" ) == 0,
+ "loader rejects inconsistent lifecycle entries");
+
+ rc = 0;
+out:
+ free( file );
+ cpu_asm_image_free( &im );
+ cpu_asm_object_free( &built[0] );
+ cpu_asm_object_free( &built[1] );
+ cpu_asm_object_free( &readback[0] );
+ cpu_asm_object_free( &readback[1] );
+ __cpu_free_context();
+ if( dtemplate[0] )
+ {
+ unlink( main_o );
+ unlink( lib_o );
+ unlink( exe );
+ unlink( life_exe );
+ unlink( bad_guard );
+ unlink( bad_entry );
+ rmdir( dtemplate );
+ }
+ if( !rc ) puts( "libcpu MELF ET_REL/ET_EXEC ABI regression: PASS" );
+ return( rc );
+}