diff options
Diffstat (limited to 'tests')
91 files changed, 11172 insertions, 0 deletions
diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000..ba58979 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1,156 @@ + +AUTOMAKE_OPTIONS = subdir-objects + +AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/utils $(LIBMPU_CPPFLAGS) $(LIBMPUIO_CPPFLAGS) +AM_CFLAGS = $(LIBMPU_CFLAGS) $(LIBMPUIO_CFLAGS) +AM_LDFLAGS = $(LIBMPU_LDFLAGS) $(LIBMPUIO_LDFLAGS) +test_programs = tst-errno-abi tst-errno-map tst-context tst-registers tst-overlap tst-instruction tst-bytecode tst-syscall tst-dup tst-assembler tst-control tst-stack tst-call tst-pointer tst-pointer-arithmetic tst-aggregate tst-frame tst-block-copy tst-memory tst-indirect-control tst-psw-abi tst-psw-write tst-byteorder tst-pointer-policy tst-bswap tst-mpu-coverage tst-immediate-exactness tst-conversion-flags tst-asm-directives tst-model-segments tst-lifecycle tst-relocatable tst-melf tst-dynamic +check_PROGRAMS = tst-tool-config tst-strip-fixture $(test_programs) +TESTS = $(test_programs) tst-runner.sh tst-stack-runner.sh tst-call-runner.sh tst-frame-runner.sh tst-block-copy-runner.sh tst-indirect-runner.sh tst-psw-abi-runner.sh tst-bswap-runner.sh tst-mpu-ops-runner.sh tst-immediate-exactness-runner.sh tst-lifecycle-runner.sh tst-relocatable-runner.sh tst-dynamic-runner.sh tst-line-runner.sh tst-run-memory-options.sh tst-stage-actions-runner.sh tst-readelf-runner.sh tst-tool-config-runner.sh tst-tool-inspection-runner.sh tst-library-search-runner.sh tst-link-needed-runner.sh tst-needed-runtime-runner.sh tst-symbol-scope-runner.sh tst-env-preload-runner.sh tst-abi3-final-runner.sh tst-ar-runner.sh tst-static-archive-link-runner.sh tst-partial-link-runner.sh tst-static-option-runner.sh tst-nm-runner.sh tst-objdump-runner.sh tst-strip-runner.sh tst-objcopy-runner.sh tst-hll-crt-runner.sh tst-verbose-runner.sh tst-doc-version-policy.sh +TESTS_ENVIRONMENT = PACKAGE_VERSION="$(PACKAGE_VERSION)" MCPU_LD_PRELOAD= CPU_AS="$(abs_top_builddir)/utils/mcpu-as" CPU_RUN="$(abs_top_builddir)/utils/mcpu-run" CPU_LD="$(abs_top_builddir)/utils/mcpu-ld" CPU_READELF="$(abs_top_builddir)/utils/mcpu-readelf" CPU_AR="$(abs_top_builddir)/utils/mcpu-ar" CPU_NM="$(abs_top_builddir)/utils/mcpu-nm" CPU_OBJDUMP="$(abs_top_builddir)/utils/mcpu-objdump" CPU_STRIP="$(abs_top_builddir)/utils/mcpu-strip" CPU_OBJCOPY="$(abs_top_builddir)/utils/mcpu-objcopy" STRIP_FIXTURE="$(abs_top_builddir)/tests/tst-strip-fixture" TOOL_CONFIG_TEST="$(abs_top_builddir)/tests/tst-tool-config" TEST_SRCDIR="$(abs_top_srcdir)/tests" MCPU_TEST_ROOT="$(abs_top_builddir)/sysroot" MCPU_HLL_FIXTURE_SRCDIR="$(abs_top_srcdir)/tests/fixtures/hll" +tst_errno_abi_SOURCES = tst-errno-abi.c +tst_errno_abi_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/sysroot/include +tst_errno_abi_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +tst_errno_map_SOURCES = tst-errno-map.c +tst_errno_map_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/cpu +tst_errno_map_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +tst_context_SOURCES = tst-context.c +tst_context_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) +tst_registers_SOURCES = tst-registers.c +tst_registers_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) +tst_overlap_SOURCES = tst-overlap.c +tst_overlap_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) +tst_instruction_SOURCES = tst-instruction.c +tst_instruction_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) +tst_bytecode_SOURCES = tst-bytecode.c +tst_bytecode_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +tst_syscall_SOURCES = tst-syscall.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_syscall_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) +tst_dup_SOURCES = tst-dup.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_dup_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/sysroot/include +tst_dup_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) +tst_assembler_SOURCES = tst-assembler.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_assembler_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +tst_tool_config_SOURCES = tst-tool-config.c $(top_srcdir)/utils/mcpu-tool-options.c $(top_srcdir)/utils/mcpu-tool-options.h $(top_srcdir)/utils/mcpu-library-search.c $(top_srcdir)/utils/mcpu-library-search.h $(top_srcdir)/utils/mcpu-runtime.c $(top_srcdir)/utils/mcpu-runtime.h $(top_srcdir)/utils/mcpu-config.c $(top_srcdir)/utils/mcpu-config.h +tst_tool_config_LDADD = $(top_builddir)/utils/libmcpu-version.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +tst_strip_fixture_SOURCES = tst-strip-fixture.c $(top_srcdir)/utils/mcpu-melf.c $(top_srcdir)/utils/mcpu-melf.h +tst_strip_fixture_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +EXTRA_DIST = \ + fixtures/radd-pi.s \ + fixtures/stack.s \ + fixtures/call.s \ + fixtures/frame-pointer.s \ + fixtures/block-copy.s \ + fixtures/indirect-control.s \ + fixtures/psw-call-abi.s \ + fixtures/byte-swap.s \ + fixtures/mpu-ops.s \ + fixtures/model-lifecycle.s \ + fixtures/life-print.s \ + fixtures/argc-argv.s \ + fixtures/shared-libs/dynamic-main.s \ + fixtures/shared-libs/dynamic-liba.s \ + fixtures/shared-libs/dynamic-libb.s \ + fixtures/hll/program-main.S \ + fixtures/hll/model-pendulum-life.S \ + fixtures/hll/libc-stub.S \ + tst-runner.sh tst-stack-runner.sh tst-call-runner.sh tst-frame-runner.sh tst-block-copy-runner.sh tst-indirect-runner.sh tst-psw-abi-runner.sh tst-bswap-runner.sh tst-mpu-ops-runner.sh tst-immediate-exactness-runner.sh tst-lifecycle-runner.sh tst-relocatable-runner.sh tst-dynamic-runner.sh tst-line-runner.sh tst-run-memory-options.sh tst-stage-actions-runner.sh tst-readelf-runner.sh tst-tool-config-runner.sh tst-tool-inspection-runner.sh tst-library-search-runner.sh tst-link-needed-runner.sh tst-needed-runtime-runner.sh tst-symbol-scope-runner.sh tst-env-preload-runner.sh tst-abi3-final-runner.sh tst-ar-runner.sh tst-static-archive-link-runner.sh tst-partial-link-runner.sh tst-static-option-runner.sh tst-nm-runner.sh tst-objdump-runner.sh tst-strip-runner.sh tst-objcopy-runner.sh tst-hll-crt-runner.sh tst-verbose-runner.sh tst-doc-version-policy.sh + +tst_control_SOURCES = tst-control.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_control_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_stack_SOURCES = tst-stack.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_stack_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_call_SOURCES = tst-call.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_call_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_pointer_SOURCES = tst-pointer.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_pointer_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_pointer_arithmetic_SOURCES = tst-pointer-arithmetic.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_pointer_arithmetic_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_aggregate_SOURCES = tst-aggregate.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_aggregate_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_frame_SOURCES = tst-frame.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_frame_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_block_copy_SOURCES = tst-block-copy.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_block_copy_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_memory_SOURCES = tst-memory.c +tst_memory_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_indirect_control_SOURCES = tst-indirect-control.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_indirect_control_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_psw_abi_SOURCES = tst-psw-abi.c +tst_psw_abi_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_psw_write_SOURCES = tst-psw-write.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_psw_write_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_byteorder_SOURCES = tst-byteorder.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_byteorder_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_pointer_policy_SOURCES = tst-pointer-policy.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_pointer_policy_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +tst_bswap_SOURCES = tst-bswap.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_bswap_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_mpu_coverage_SOURCES = tst-mpu-coverage.c +tst_mpu_coverage_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_immediate_exactness_SOURCES = tst-immediate-exactness.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_immediate_exactness_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_conversion_flags_SOURCES = tst-conversion-flags.c +tst_conversion_flags_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_asm_directives_SOURCES = tst-asm-directives.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_asm_directives_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_model_segments_SOURCES = tst-model-segments.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_model_segments_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + +tst_lifecycle_SOURCES = tst-lifecycle.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h +tst_lifecycle_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_relocatable_SOURCES = tst-relocatable.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h $(top_srcdir)/utils/mcpu-link.c $(top_srcdir)/utils/mcpu-link.h $(top_srcdir)/utils/mcpu-melf.c $(top_srcdir)/utils/mcpu-melf.h +tst_relocatable_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_melf_SOURCES = tst-melf.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h $(top_srcdir)/utils/mcpu-object.c $(top_srcdir)/utils/mcpu-object.h $(top_srcdir)/utils/mcpu-link.c $(top_srcdir)/utils/mcpu-link.h $(top_srcdir)/utils/mcpu-exec.c $(top_srcdir)/utils/mcpu-exec.h $(top_srcdir)/utils/mcpu-dyn.c $(top_srcdir)/utils/mcpu-dyn.h $(top_srcdir)/utils/mcpu-melf.c $(top_srcdir)/utils/mcpu-melf.h +tst_melf_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) + + +tst_dynamic_SOURCES = tst-dynamic.c $(top_srcdir)/utils/mcpu-asm.c $(top_srcdir)/utils/mcpu-asm.h $(top_srcdir)/utils/mcpu-link.c $(top_srcdir)/utils/mcpu-link.h $(top_srcdir)/utils/mcpu-dyn.c $(top_srcdir)/utils/mcpu-dyn.h $(top_srcdir)/utils/mcpu-exec.c $(top_srcdir)/utils/mcpu-exec.h $(top_srcdir)/utils/mcpu-melf.c $(top_srcdir)/utils/mcpu-melf.h +tst_dynamic_LDADD = $(top_builddir)/cpu/libmcpu.la $(LIBMPUIO_LIBS) $(LIBMPU_LIBS) diff --git a/tests/fixtures/argc-argv.s b/tests/fixtures/argc-argv.s new file mode 100644 index 0000000..4c1d2ac --- /dev/null +++ b/tests/fixtures/argc-argv.s @@ -0,0 +1,44 @@ +; Minimal argc/argv and explicit "--" separator example. +; +; Assemble/link: +; ../utils/mcpu-as -o argc-argv.o argc-argv.s +; ../utils/mcpu-ld -o argc-argv --stack-size=4K argc-argv.o +; +; Run: +; ../utils/mcpu-run \ +; --on-step=print-int-regs:r64@0x00000,r8@0x00020,r8@0x00021 \ +; -- \ +; argc-argv --help -x +; +; Guest argv is: +; argv[0] = "argc-argv" +; argv[1] = "--help" +; argv[2] = "-x" +; +; The example prints argc, argv[1][2] ('h') and argv[2][1] ('x') as integers. +; Expected output: +; 3 104 120 + +.text +_start_life: +loop: + ; Persistent McpuStartup at SP: + ; SP+8 = argc + ; SP+16 = argv (PTR64 to argv[0]) + LDSP r64@0x00000, 8 + LDSP r64@0x00008, 16 + + ; argv[1] + PADDI r64@0x00010, r64@0x00008, 8 + LOADP r64@0x00018, r64@0x00010 + PADDI r64@0x00018, r64@0x00018, 2 + LOADP r8@0x00020, r64@0x00018 + + ; argv[2] + PADDI r64@0x00010, r64@0x00008, 16 + LOADP r64@0x00018, r64@0x00010 + PADDI r64@0x00018, r64@0x00018, 1 + LOADP r8@0x00021, r64@0x00018 + + END + JMP loop diff --git a/tests/fixtures/block-copy.s b/tests/fixtures/block-copy.s new file mode 100644 index 0000000..81422e2 --- /dev/null +++ b/tests/fixtures/block-copy.s @@ -0,0 +1,46 @@ +; Build/run from examples/: +; ../utils/mcpu-as -o block-copy.o block-copy.s +; ../utils/mcpu-ld --stack-size=512 -o block-copy block-copy.o +; ../utils/mcpu-run --int-reg=r64@0x00028 block-copy +; +; Expected output: 22 +; +; LibCPU block-copy example. +; +; 1. Copy a 32-byte object from DATA to a local STACK object with BMOVI. +; 2. Use BMOV with a runtime byte count to perform an overlapping move +; inside that STACK object. BMOV has memmove semantics. +; 3. Read the moved value back through LOADP. Expected result: 22. + +.data +.align 8 +source_object: + .integer 64, 11 + .integer 64, 22 + .integer 64, 33 + .integer 64, 44 + +.text +_start_life: + ; Source PTR64 -> DATA object. + LEA r64@0x00000, source_object + + ; Reserve 32 bytes for a local object and obtain its STACK PTR64. + ADJSP -32 + LEASP r64@0x00008, 0 + + ; Fixed-size structure copy: DATA -> STACK. + BMOVI r64@0x00008, r64@0x00000, 32 + + ; Overlapping move inside the local object: + ; bytes [0..15] -> [8..23]. memmove semantics are required. + PADDI r64@0x00010, r64@0x00008, 8 + LDI r64@0x00018, 16 + BMOV r64@0x00010, r64@0x00008, r64@0x00018 + + ; The third 64-bit slot now contains the old second slot: 22. + PADDI r64@0x00020, r64@0x00008, 16 + LOADP r64@0x00028, r64@0x00020 + + ADJSP 32 + HALT diff --git a/tests/fixtures/byte-swap.s b/tests/fixtures/byte-swap.s new file mode 100644 index 0000000..7f666d0 --- /dev/null +++ b/tests/fixtures/byte-swap.s @@ -0,0 +1,15 @@ +; Byte-swap example. +; +; Build/run from examples/: +; ../utils/mcpu-as -o byte-swap.o byte-swap.s +; ../utils/mcpu-ld --stack-size=512 -o byte-swap byte-swap.o +; ../utils/mcpu-run --int-reg=r64@0x00010 byte-swap +; +; Expected output: 42 +; +.text +_start_life: + LDI r64@0x00000, 42 + BSWAP r64@0x00008, r64@0x00000 + BSWAP r64@0x00010, r64@0x00008 + HALT diff --git a/tests/fixtures/call.s b/tests/fixtures/call.s new file mode 100644 index 0000000..bfc9965 --- /dev/null +++ b/tests/fixtures/call.s @@ -0,0 +1,39 @@ +; Build/run from examples/: +; ../utils/mcpu-as -o call.o call.s +; ../utils/mcpu-ld --stack-size=128 -o call call.o +; ../utils/mcpu-run --int-reg=r64@0x00010 call +; +; Expected output: 33 +; +; LibCPU CALL ABI example. +; +; add2(a,b): this two-r64 argument block is exactly 16 bytes, so the +; traditional right-to-left PUSH sequence also satisfies the formal ABI. +; CALL then pushes +; the 64-bit return PC, therefore on callee entry: +; SP+0 = return address +; SP+8 = first argument a +; SP+16 = second argument b +; +; Before CALL, SP is 16-byte aligned. The r64 result is returned in the canonical ABI return view +; r64@0x7fff8. The caller removes its own arguments with ADJSP. + +.text +_start_life: + + LDI r64@0x00000, 11 + LDI r64@0x00008, 22 + + PUSH r64@0x00008 ; second/rightmost argument b + PUSH r64@0x00000 ; first/leftmost argument a + CALL add2 + ADJSP 16 ; caller cleanup + + MOV r64@0x00010, r64@0x7fff8 + HALT + +add2: + LDSP r64@0x00100, 8 ; a + LDSP r64@0x00108, 16 ; b + IADD r64@0x7fff8, r64@0x00100, r64@0x00108 + RET diff --git a/tests/fixtures/frame-pointer.s b/tests/fixtures/frame-pointer.s new file mode 100644 index 0000000..6c6934b --- /dev/null +++ b/tests/fixtures/frame-pointer.s @@ -0,0 +1,63 @@ +; Build/run from examples/: +; ../utils/mcpu-as -o frame-pointer.o frame-pointer.s +; ../utils/mcpu-ld --stack-size=512 -o frame-pointer frame-pointer.o +; ../utils/mcpu-run --int-reg=r64@0x00008 frame-pointer +; +; Expected output: 42 +; +; LibCPU optional frame-pointer and dynamic STACK allocation example. +; +; demo(5) builds a fixed frame, creates a 24-byte dynamic object below it, +; accesses a fixed local through FP while SP is moving, and returns 42 in +; the canonical r64 return register. + +.text +_start_life: + LDI r64@0x00000, 5 + ADJSP -16 ; 16-byte ABI argument block + STSP 0, r64@0x00000 + CALL demo + ADJSP 16 + MOV r64@0x00008, r64@0x7fff8 + HALT + +demo: + PUSHFP + SETFP + ADJSP -16 + + ; First explicit argument. With FP in use the layout is: + ; FP+0 saved caller FP + ; FP+8 return PC + ; FP+16 first explicit argument + LDFP r64@0x00100, 16 + + ; Fixed local at FP-8. + STFP -8, r64@0x00100 + LEAFP r64@0x00108, -8 + + ; Save the current stack top as a dynamic-scope mark. + LEASP r64@0x00110, 0 + + ; Dynamically reserve 24 bytes: new_SP = old_SP - 24. + LDI r64@0x00118, 24 + PSUB r64@0x00120, r64@0x00110, r64@0x00118 + SETSP r64@0x00120 + + ; Put 37 in the dynamic object at current SP. + LDI r64@0x00128, 37 + LEASP r64@0x00130, 0 + STOREP r64@0x00130, r64@0x00128 + + ; FP-relative access is stable although SP moved dynamically. + LDFP r64@0x00138, -8 + LOADP r64@0x00140, r64@0x00130 + IADD r64@0x7fff8, r64@0x00138, r64@0x00140 + + ; Restore the nested dynamic scope explicitly. + SETSP r64@0x00110 + + ; Destroy the complete frame below FP, restore caller FP and return. + RESTSP + POPFP + RET diff --git a/tests/fixtures/hll/libc-stub.S b/tests/fixtures/hll/libc-stub.S new file mode 100644 index 0000000..d4fd878 --- /dev/null +++ b/tests/fixtures/hll/libc-stub.S @@ -0,0 +1,42 @@ +; Test/development MCPU Libc emulator. +; +; This source is NEVER installed. The register marker is intentionally a +; testing convention only; it is not part of the CRT <-> MCPU Libc ABI. + +.global __mcpu_libc_init +.global __mcpu_init_array +.global __mcpu_fini_array +.global __mcpu_libc_fini + +.text +__mcpu_libc_init: + CLR r64@0x70008 + ICMP r64@0x70000, r64@0x70008 + JNZ .L_stub_bad + LDI r64@0x70000, 1 + RET + +__mcpu_init_array: + LDI r64@0x70008, 1 + ICMP r64@0x70000, r64@0x70008 + JNZ .L_stub_bad + LDI r64@0x70000, 2 + RET + +__mcpu_fini_array: + LDI r64@0x70008, 4 + ICMP r64@0x70000, r64@0x70008 + JNZ .L_stub_bad + LDI r64@0x70000, 5 + RET + +__mcpu_libc_fini: + LDI r64@0x70008, 5 + ICMP r64@0x70000, r64@0x70008 + JNZ .L_stub_bad + LDI r64@0x70000, 6 + RET + +.L_stub_bad: + LDI r64@0x70000, 999 + RET diff --git a/tests/fixtures/hll/model-pendulum-life.S b/tests/fixtures/hll/model-pendulum-life.S new file mode 100644 index 0000000..5ad6ddb --- /dev/null +++ b/tests/fixtures/hll/model-pendulum-life.S @@ -0,0 +1,133 @@ +; Assembler emulator of future mcpu-cc MODEL output. +; +; The lifecycle shape is a compact pendulum-style MODEL regression fixture. +; INIT snapshots CONTROL +; into DATA, LIFE advances one model step and publishes one TRACE frame, and +; fini() marks graceful model finalization. +; +; This reduced fixture tests the HLL MODEL/CRT protocol, not RK4 numerics. + +.global init +.global life +.global fini + +.control +.align 8 +control_n: + .integer 64, 3 +control_error_step: + .integer 64, 0 + +.data +.align 8 +model_n: + .integer 64, 0 +model_error_step: + .integer 64, 0 +model_step: + .integer 64, 0 +model_fini_marker: + .integer 64, 0 + +.trace +.align 8 +trace_step: + .integer 64, 0 + +.text +; int32 init(int64 argc, PTR64 argv) +init: + LDI r64@0x70200, 2 + ICMP r64@0x70000, r64@0x70200 + JNZ .L_init_bad_order + + ; The test invokes the MODEL without extra arguments. + LDSP r64@0x70208, 8 + LDI r64@0x70210, 1 + ICMP r64@0x70208, r64@0x70210 + JNZ .L_init_bad_args + + ; Negative CONTROL.n deliberately requests an init failure. + LEA r64@0x70218, control_n + LOADP r64@0x70220, r64@0x70218 + CLR r64@0x70228 + ICMP r64@0x70220, r64@0x70228 + JS .L_init_fail + + STORE model_n, r64@0x70220 + LEA r64@0x70218, control_error_step + LOADP r64@0x70230, r64@0x70218 + STORE model_error_step, r64@0x70230 + + CLR r64@0x70238 + STORE model_step, r64@0x70238 + STORE model_fini_marker, r64@0x70238 + + LDI r64@0x70000, 3 + CLR r32@0x7fffc + RET + +.L_init_fail: + LDI r64@0x70000, 3 + LDI r32@0x7fffc, 17 + RET + +.L_init_bad_order: + LDI r64@0x70000, 910 + LDI r32@0x7fffc, 18 + RET + +.L_init_bad_args: + LDI r64@0x70000, 911 + LDI r32@0x7fffc, 19 + RET + +; int32 life(void) +life: + LOAD r64@0x70300, model_step + LDI r64@0x70308, 1 + IADD r64@0x70300, r64@0x70300, r64@0x70308 + STORE model_step, r64@0x70300 + + ; Optional positive LIFE error requested by CONTROL.error_step. + LOAD r64@0x70310, model_error_step + CLR r64@0x70318 + ICMP r64@0x70310, r64@0x70318 + JZ .L_no_life_error + ICMP r64@0x70300, r64@0x70310 + JNZ .L_no_life_error + LDI r32@0x7fffc, 9 + RET + +.L_no_life_error: + ; Pendulum-style sequential stop: step == n + 1 means natural end. + LOAD r64@0x70320, model_n + IADD r64@0x70328, r64@0x70320, r64@0x70308 + ICMP r64@0x70300, r64@0x70328 + JZ .L_life_end + + LEA r64@0x70330, trace_step + STOREP r64@0x70330, r64@0x70300 + + ; LIFE_CONTINUE == INT32_MIN. + LDI r32@0x7fffc, -2147483648 + RET + +.L_life_end: + CLR r32@0x7fffc + RET + +fini: + LDI r64@0x70200, 3 + ICMP r64@0x70000, r64@0x70200 + JNZ .L_model_fini_bad + + LDI r64@0x70000, 4 + LDI r64@0x70208, 1 + STORE model_fini_marker, r64@0x70208 + MOV r64@0x70010, r64@0x70208 + RET + +.L_model_fini_bad: + LDI r64@0x70000, 912 + RET diff --git a/tests/fixtures/hll/program-main.S b/tests/fixtures/hll/program-main.S new file mode 100644 index 0000000..3161343 --- /dev/null +++ b/tests/fixtures/hll/program-main.S @@ -0,0 +1,48 @@ +; Assembler emulator of future mcpu-cc PROGRAM output. +; +; It intentionally defines only main() and fini(); lifecycle symbols belong +; to CRT and must be inserted automatically by mcpu-ld. + +.global main +.global fini + +.text +main: + ; Startup hooks must already have completed. + LDI r64@0x70100, 2 + ICMP r64@0x70000, r64@0x70100 + JNZ .L_main_bad + + ; argc must include argv[0] plus the two test arguments. + LDSP r64@0x70108, 8 + LDI r64@0x70110, 3 + ICMP r64@0x70108, r64@0x70110 + JNZ .L_main_bad + + ; argv[argc] is the required NULL terminator. + LDSP r64@0x70118, 16 + PADDI r64@0x70120, r64@0x70118, 24 + LOADP r64@0x70128, r64@0x70120 + CLR r64@0x70130 + ICMP r64@0x70128, r64@0x70130 + JNZ .L_main_bad + + LDI r64@0x70000, 3 + LDI r32@0x7fffc, 42 + RET + +.L_main_bad: + LDI r64@0x70000, 900 + LDI r32@0x7fffc, 43 + RET + +fini: + LDI r64@0x70100, 3 + ICMP r64@0x70000, r64@0x70100 + JNZ .L_fini_bad + LDI r64@0x70000, 4 + RET + +.L_fini_bad: + LDI r64@0x70000, 901 + RET diff --git a/tests/fixtures/indirect-control.s b/tests/fixtures/indirect-control.s new file mode 100644 index 0000000..1e9d0e3 --- /dev/null +++ b/tests/fixtures/indirect-control.s @@ -0,0 +1,39 @@ +; Indirect CALLP/JMPP control-flow example. +; +; Build/run from examples/: +; ../utils/mcpu-as -o indirect-control.o indirect-control.s +; ../utils/mcpu-ld --stack-size=512 -o indirect-control indirect-control.o +; ../utils/mcpu-run --int-reg=r64@0x00020 indirect-control +; +; Expected output: 42 +; +.data + +.align 8 +dispatch: + .cptr indirect_function + .cptr done + +.text +_start_life: + + ; Load a PROGRAM PTR64 from DATA and call through it. + LEA r64@0x00000, dispatch + LOADP r64@0x00008, r64@0x00000 + CALLP r64@0x00008 + + ; Select the second entry and jump through it. + PADDI r64@0x00000, r64@0x00000, 8 + LOADP r64@0x00010, r64@0x00000 + JMPP r64@0x00010 + + ; JMPP must skip this path. + LDI r64@0x00020, 99 + HALT + +indirect_function: + LDI r64@0x00020, 42 + RET + +done: + HALT diff --git a/tests/fixtures/life-print.s b/tests/fixtures/life-print.s new file mode 100644 index 0000000..a1de41f --- /dev/null +++ b/tests/fixtures/life-print.s @@ -0,0 +1,30 @@ +; Minimal LIFE-stage action example. +; +; Assemble/link: +; ../utils/mcpu-as -o life-print.o life-print.s +; ../utils/mcpu-ld -o life-print --stack-size=1K life-print.o +; +; Run three LIFE cycles and print two registers on one line after each END: +; ../utils/mcpu-run \ +; --steps=3 \ +; --on-step=print-int-regs:r64@0x00000,r64@0x00008 \ +; -- \ +; life-print +; +; Expected output: +; 1 10 +; 2 20 +; 3 30 + +.text +_start_life: + LDI r64@0x00000, 1 + LDI r64@0x00008, 10 + LDI r64@0x00010, 1 + LDI r64@0x00018, 10 + +loop: + END + IADD r64@0x00000, r64@0x00000, r64@0x00010 + IADD r64@0x00008, r64@0x00008, r64@0x00018 + JMP loop diff --git a/tests/fixtures/model-lifecycle.s b/tests/fixtures/model-lifecycle.s new file mode 100644 index 0000000..4c190d5 --- /dev/null +++ b/tests/fixtures/model-lifecycle.s @@ -0,0 +1,53 @@ +; Build from examples/: +; ../utils/mcpu-as -o model-lifecycle.o model-lifecycle.s +; ../utils/mcpu-ld --stack-size=512 -o model-lifecycle model-lifecycle.o +; +; Run one INIT-only pass or one LIFE step: +; ../utils/mcpu-run --init-only --int-reg=r64@0x00000 model-lifecycle +; ../utils/mcpu-run --steps=1 --int-reg=r64@0x00000 model-lifecycle +; +; Expected outputs: 40 and 42 respectively. +; +; LibCPU lifecycle/model-segment example. +; cpu-run --steps=1 --int-reg=r64@0x00000 model-lifecycle -> 42 +; cpu-run --init-only --int-reg=r64@0x00000 model-lifecycle -> 40 + +.control +model_step: + .real 64, 1.0 + +.input +external_delta: + .integer 64, 2 + +.output +current_state: + .integer 64, 0 + +.trace +telemetry_state: + .integer 64, 0 + +.init +_start: + LDI r64@0x00000, 40 + END + +.text +_life: +loop: + LEA r64@0x00008, external_delta + LOADP r64@0x00010, r64@0x00008 + IADD r64@0x00000, r64@0x00000, r64@0x00010 + + LEA r64@0x00018, current_state + STOREP r64@0x00018, r64@0x00000 + LEA r64@0x00020, telemetry_state + STOREP r64@0x00020, r64@0x00000 + + END + JMP loop + +.fini +_fini: + END diff --git a/tests/fixtures/mpu-ops.s b/tests/fixtures/mpu-ops.s new file mode 100644 index 0000000..49d4f97 --- /dev/null +++ b/tests/fixtures/mpu-ops.s @@ -0,0 +1,25 @@ +; LibMPU-backed arithmetic/conversion operation example. +; +; Build/run from examples/: +; ../utils/mcpu-as -o mpu-ops.o mpu-ops.s +; ../utils/mcpu-ld --stack-size=512 -o mpu-ops mpu-ops.o +; ../utils/mcpu-run --int-reg=r64@0x00020 mpu-ops +; +; Expected output: 42 +; +.text +_start_life: + + LDI r64@0x00000, 6 + LDI r64@0x00008, 7 + IMUL r128@0x00010, r64@0x00000, r64@0x00008 + ICPY r64@0x00020, r128@0x00010 + + RCONST r64@0x00028, ONE + CRTOC c128@0x00030, r64@0x00028 + CISEQ r64@0x00040, c128@0x00030, c128@0x00030 + IINC r64@0x00048, r64@0x00040 + IDEC r64@0x00050, r64@0x00048 + ISUB r64@0x00020, r64@0x00020, r64@0x00050 + IADD r64@0x00020, r64@0x00020, r64@0x00040 + HALT diff --git a/tests/fixtures/psw-call-abi.s b/tests/fixtures/psw-call-abi.s new file mode 100644 index 0000000..3dccab3 --- /dev/null +++ b/tests/fixtures/psw-call-abi.s @@ -0,0 +1,36 @@ +; PSW volatility across the CALL ABI example. +; +; Build/run from examples/: +; ../utils/mcpu-as -o psw-call-abi.o psw-call-abi.s +; ../utils/mcpu-ld --stack-size=512 -o psw-call-abi psw-call-abi.o +; ../utils/mcpu-run --int-reg=r64@0x00010 psw-call-abi +; +; Expected output: 42 +; +.text +_start_life: + + ; Establish ZF=1 before the call: -1 + 1 -> 0 in signed r8. + LDI r8@0x00000, -1 + LDI r8@0x00001, 1 + IADD r8@0x00002, r8@0x00000, r8@0x00001 + + ; CALL itself preserves PSW, but the callee is free to clobber + ; PSW[15:0]. This function deliberately clears ZF with 1 + 1. + CALL clobber_status + + ; The old ZF is not part of the calling convention. Observe the + ; callee-produced ZF=0 and report 42. + JNZ status_is_volatile + LDI r64@0x00010, 99 + HALT + +clobber_status: + LDI r8@0x00003, 1 + LDI r8@0x00004, 1 + IADD r8@0x00005, r8@0x00003, r8@0x00004 + RET + +status_is_volatile: + LDI r64@0x00010, 42 + HALT diff --git a/tests/fixtures/radd-pi.s b/tests/fixtures/radd-pi.s new file mode 100644 index 0000000..2921d91 --- /dev/null +++ b/tests/fixtures/radd-pi.s @@ -0,0 +1,24 @@ +; High-precision real arithmetic example. +; +; Build/run from examples/: +; ../utils/mcpu-as -o radd-pi.o radd-pi.s +; ../utils/mcpu-ld --stack-size=512 -o radd-pi radd-pi.o +; ../utils/mcpu-run --digits=60 --real-reg=r4096@0x00400 radd-pi +; +; Expected output starts with: +; 5.14159265358979323846264338327950288419716939937510582097 +; +.data + +.align 16 +result: + .real 4096, 0.0 + +.text +_start_life: + + LDR r4096@0x00000, 2.0 + RCONST r4096@0x00200, PI + RADD r4096@0x00400, r4096@0x00000, r4096@0x00200 + STORE result, r4096@0x00400 + HALT diff --git a/tests/fixtures/shared-libs/dynamic-liba.s b/tests/fixtures/shared-libs/dynamic-liba.s new file mode 100644 index 0000000..f35cfe2 --- /dev/null +++ b/tests/fixtures/shared-libs/dynamic-liba.s @@ -0,0 +1,18 @@ +; Build/run from examples/shared-libs/: +; make +; make run +; +; make run prints, in order: +; 43 +; 43 +; 42 +; 42 +; 41 +; +; Shared-library implementation of foo: adds 2. +.global foo +.text +foo: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET diff --git a/tests/fixtures/shared-libs/dynamic-libb.s b/tests/fixtures/shared-libs/dynamic-libb.s new file mode 100644 index 0000000..348b358 --- /dev/null +++ b/tests/fixtures/shared-libs/dynamic-libb.s @@ -0,0 +1,18 @@ +; Build/run from examples/shared-libs/: +; make +; make run +; +; make run prints, in order: +; 43 +; 43 +; 42 +; 42 +; 41 +; +; Shared-library implementation of foo: adds 3. +.global foo +.text +foo: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET diff --git a/tests/fixtures/shared-libs/dynamic-main.s b/tests/fixtures/shared-libs/dynamic-main.s new file mode 100644 index 0000000..f296429 --- /dev/null +++ b/tests/fixtures/shared-libs/dynamic-main.s @@ -0,0 +1,35 @@ +; Build/run from examples/shared-libs/: +; make +; make run +; +; make run prints, in order: +; 43 +; 43 +; 42 +; 42 +; 41 +; +; ABI-v2 startup-dynamic executable example. +; main-program itself defines foo(), but cpu-ld --dynamic retains CALL foo as +; a preemptible runtime relocation. cpu-run --ldpreload can therefore replace +; it before _start executes. +; +; Without preload: foo adds 1 -> result 41. +; With dynamic-liba.so: foo adds 2 -> result 42. +; With dynamic-liba.so:dynamic-libb.so: rightmost strong GLOBAL wins, so +; dynamic-libb.so foo adds 3 -> result 43. +.global _life +.global foo + +.text +_start_life: + LDI r64@0x00000, 40 + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 + +foo: + LDI r64@0x00008, 1 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET diff --git a/tests/fixtures/stack.s b/tests/fixtures/stack.s new file mode 100644 index 0000000..9b45785 --- /dev/null +++ b/tests/fixtures/stack.s @@ -0,0 +1,32 @@ +; STACK instruction example. +; +; Build/run from examples/: +; ../utils/mcpu-as -o stack.o stack.s +; ../utils/mcpu-ld --stack-size=128 -o stack stack.o +; ../utils/mcpu-run --int-reg=r64@0x00028 stack +; +; Expected output: 22 +; +.text +_start_life: + LDI r64@0x00000, 11 + LDI r64@0x00008, 22 + + ; STACK grows downward. cpu-run initializes SP to stack_size. + PUSH r64@0x00000 + PUSH r64@0x00008 + + ; Current stack top contains 22, SP+8 contains 11. + LDSP r64@0x00010, 8 + STSP 0, r64@0x00000 + + POP r64@0x00018 + POP r64@0x00020 + + ; Reserve a 16-byte local frame without automatic padding. + ADJSP -16 + STSP 0, r64@0x00008 + LDSP r64@0x00028, 0 + ADJSP 16 + + HALT diff --git a/tests/tst-abi3-final-runner.sh b/tests/tst-abi3-final-runner.sh new file mode 100755 index 0000000..7744775 --- /dev/null +++ b/tests/tst-abi3-final-runner.sh @@ -0,0 +1,78 @@ +#!/bin/sh +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${CPU_READELF:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-abi3-final-$$ +mkdir -p "$TMP/lib" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +cat > "$TMP/main.s" <<'EOS' +.global _life +.extern foo +.text +_start_life: + LDI r64@0x00000, 40 + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/a.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +cat > "$TMP/override.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +"$CPU_AS" -o "$TMP/main.o" "$TMP/main.s" +"$CPU_AS" -o "$TMP/a.o" "$TMP/a.s" +"$CPU_AS" -o "$TMP/override.o" "$TMP/override.s" + +# Conventional development link -> SONAME -> physical file layout. +"$CPU_LD" --shared --soname=liba.so.1 -o "$TMP/lib/liba.so.1.2.3" "$TMP/a.o" +ln -s liba.so.1.2.3 "$TMP/lib/liba.so.1" +ln -s liba.so.1 "$TMP/lib/liba.so" +"$CPU_LD" --shared --soname=liboverride.so.1 -o "$TMP/lib/liboverride.so.1" "$TMP/override.o" +ln -s liboverride.so.1 "$TMP/lib/liboverride.so" + +"$CPU_LD" --dynamic --stack-size=4096 --no-heap -L"$TMP/lib" -la \ + -o "$TMP/main" "$TMP/main.o" + +"$CPU_READELF" -h -d "$TMP/lib/liba.so.1.2.3" > "$TMP/lib.txt" +grep -q 'ABI Version: 3' "$TMP/lib.txt" +grep -q 'DYN (Shared object file)' "$TMP/lib.txt" +grep -q 'SONAME' "$TMP/lib.txt" +grep -q 'Library soname: \[liba.so.1\]' "$TMP/lib.txt" + +"$CPU_READELF" -h -d "$TMP/main" > "$TMP/main.txt" +grep -q 'ABI Version: 3' "$TMP/main.txt" +grep -q 'EXEC (Executable file)' "$TMP/main.txt" +grep -q 'NEEDED' "$TMP/main.txt" +grep -q 'Shared library: \[liba.so.1\]' "$TMP/main.txt" +if grep -q 'liba.so.1.2.3' "$TMP/main.txt"; then + echo "physical shared-library filename leaked into DT_NEEDED" >&2 + exit 1 +fi + +# Frozen normal dependency path: automatic SONAME lookup and execution. +[ "`MCPU_LD_PRELOAD= "$CPU_RUN" --library-path="$TMP/lib" \ + --int-reg=r64@0x00018 "$TMP/main"`" = 42 ] + +# Frozen explicit-preload interposition: explicit preload outranks normal graph. +[ "`MCPU_LD_PRELOAD=liboverride.so "$CPU_RUN" --library-path="$TMP/lib" \ + --int-reg=r64@0x00018 "$TMP/main"`" = 43 ] + +exit 0 diff --git a/tests/tst-aggregate.c b/tests/tst-aggregate.c new file mode 100644 index 0000000..2311d0f --- /dev/null +++ b/tests/tst-aggregate.c @@ -0,0 +1,68 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static const char src[]= +".text\n" +" ADJSP -16\n" +" LEASP r64@0x00000, 0\n" +" LDI r64@0x00100, 11\n" +" LDI r64@0x00108, 22\n" +" ADJSP -32\n" +" STSP 0, r64@0x00000\n" +" STSP 8, r128@0x00100\n" +" CALL copy_pair\n" +" ADJSP 32\n" +" LDSP r128@0x00200, 0\n" +" IADD r64@0x00210, r64@0x00200, r64@0x00208\n" +" LEASP r64@0x00008, 0\n" +" ADJSP -16\n" +" STSP 0, r64@0x00008\n" +" CALL first_field\n" +" ADJSP 16\n" +" ADJSP 16\n" +" HALT\n" +"copy_pair:\n" +" LDSP r64@0x00300, 8\n" +" LDSP r128@0x00310, 16\n" +" STOREP r64@0x00300, r128@0x00310\n" +" RET\n" +"first_field:\n" +" LDSP r64@0x00400, 8\n" +" LOADP r64@0x7fff8, r64@0x00400\n" +" RET\n"; + +static int +eq64( unsigned address, const char *text ) +{ + mpu_int64_t x; + iatoi( ( mpu_int * ) x, (__mpu_char8_t *)text, NB_I64 ); + return( !memcmp( __cpu_reg_ptr( 64, address ), x, NB_I64 ) ); +} + +int +main( void ) +{ + cpu_asm_image im; + char err[256]; + + __cpu_init(); + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + CHECK( __cpu_stack_alloc( 512 ) == 0, "stack alloc" ); + CHECK( __cpu_run() == CPU_EXEC_SUCCESS, "aggregate ABI program run" ); + + CHECK( __cpu_sp == 512, "caller released local object and all arguments" ); + CHECK( eq64( 0x00200, "11" ), "aggregate return first field" ); + CHECK( eq64( 0x00208, "22" ), "aggregate return second field" ); + CHECK( eq64( 0x00210, "33" ), "aggregate return object preserved" ); + CHECK( eq64( 0x7fff8, "11" ), "pointer argument and scalar return" ); + + cpu_asm_image_free( &im ); + __cpu_free_context(); + puts( "libcpu aggregate CALL ABI regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-ar-runner.sh b/tests/tst-ar-runner.sh new file mode 100755 index 0000000..ffc939a --- /dev/null +++ b/tests/tst-ar-runner.sh @@ -0,0 +1,122 @@ +#!/bin/sh +set -eu + +: "${PACKAGE_VERSION:?}" + +: "${CPU_AS:?}" "${CPU_AR:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-ar-$$ +mkdir -p "$TMP" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +cat > "$TMP/a.s" <<'EOS' +.global foo +.text +foo: + RET +EOS + +cat > "$TMP/very-long-member-name-for-mcpu.s" <<'EOS' +.global bar +.text +bar: + RET +EOS + +cat > "$TMP/c.s" <<'EOS' +.global baz +.text +baz: + RET +EOS + +"$CPU_AS" -o "$TMP/a.o" "$TMP/a.s" +"$CPU_AS" -o "$TMP/very-long-member-name-for-mcpu.o" \ + "$TMP/very-long-member-name-for-mcpu.s" +"$CPU_AS" -o "$TMP/c.o" "$TMP/c.s" + +# rcs creates a deterministic UNIX archive, preserves command-line member +# order, supports a long member name, and writes a real ar symbol index. +"$CPU_AR" rcs "$TMP/libx.a" "$TMP/a.o" \ + "$TMP/very-long-member-name-for-mcpu.o" +"$CPU_AR" t "$TMP/libx.a" > "$TMP/list" +cat > "$TMP/list.expected" <<'EOS' +a.o +very-long-member-name-for-mcpu.o +EOS +cmp "$TMP/list.expected" "$TMP/list" + +first_member=`dd if="$TMP/libx.a" bs=1 skip=8 count=1 2>/dev/null` +[ "$first_member" = / ] + +# p returns the exact member bytes. +"$CPU_AR" p "$TMP/libx.a" a.o > "$TMP/printed.o" +cmp "$TMP/a.o" "$TMP/printed.o" + +# x restores exact member bytes, including BSD extended-name members. +mkdir "$TMP/extract" +( + cd "$TMP/extract" + "$CPU_AR" x ../libx.a +) +cmp "$TMP/a.o" "$TMP/extract/a.o" +cmp "$TMP/very-long-member-name-for-mcpu.o" \ + "$TMP/extract/very-long-member-name-for-mcpu.o" + +# Archive output is deterministic across independent creations. +"$CPU_AR" rcs "$TMP/libx2.a" "$TMP/a.o" \ + "$TMP/very-long-member-name-for-mcpu.o" +cmp "$TMP/libx.a" "$TMP/libx2.a" + +# q always appends. Existing symbol-index state remains present. +"$CPU_AR" q "$TMP/libx.a" "$TMP/c.o" +"$CPU_AR" t "$TMP/libx.a" > "$TMP/list" +cat > "$TMP/list.expected" <<'EOS' +a.o +very-long-member-name-for-mcpu.o +c.o +EOS +cmp "$TMP/list.expected" "$TMP/list" +first_member=`dd if="$TMP/libx.a" bs=1 skip=8 count=1 2>/dev/null` +[ "$first_member" = / ] + +# r replaces a member in place rather than moving it to the end. +cat > "$TMP/c.s" <<'EOS' +.global qux +.text +qux: + RET +EOS +"$CPU_AS" -o "$TMP/c.o" "$TMP/c.s" +"$CPU_AR" r "$TMP/libx.a" "$TMP/c.o" +"$CPU_AR" t "$TMP/libx.a" > "$TMP/list" +cmp "$TMP/list.expected" "$TMP/list" +"$CPU_AR" p "$TMP/libx.a" c.o > "$TMP/printed-c.o" +cmp "$TMP/c.o" "$TMP/printed-c.o" + +# d removes named members and s rebuilds the symbol index explicitly. +"$CPU_AR" d "$TMP/libx.a" a.o +"$CPU_AR" s "$TMP/libx.a" +"$CPU_AR" t "$TMP/libx.a" > "$TMP/list" +cat > "$TMP/list.expected" <<'EOS' +very-long-member-name-for-mcpu.o +c.o +EOS +cmp "$TMP/list.expected" "$TMP/list" +first_member=`dd if="$TMP/libx.a" bs=1 skip=8 count=1 2>/dev/null` +[ "$first_member" = / ] + +# A static MCPU archive may contain only valid current MELF ET_REL objects. +printf 'not MELF\n' > "$TMP/bad.o" +if "$CPU_AR" r "$TMP/bad.a" "$TMP/bad.o" >/dev/null 2>"$TMP/bad.err"; then + echo "mcpu-ar unexpectedly accepted a non-MELF member" >&2 + exit 1 +fi +grep -q 'not a MELF file' "$TMP/bad.err" +test ! -e "$TMP/bad.a" + +"$CPU_AR" --version | grep -Fq "$PACKAGE_VERSION" +"$CPU_AR" --help | grep -q 'rcs' +"$CPU_AR" --help | grep -q 'symbol index' + +exit 0 diff --git a/tests/tst-asm-directives.c b/tests/tst-asm-directives.c new file mode 100644 index 0000000..e86f46e --- /dev/null +++ b/tests/tst-asm-directives.c @@ -0,0 +1,84 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do { if(!(c)) { fprintf(stderr,"FAIL: %s\n",m); return 1; } } while(0) + +static const char src[] = +".equ REG0, REGBASE+0\n" +".equ REGBASE, 0x03000\n" +".equ STEP, 4*512\n" +".data\n" +"ucs:\n" +" .ucs2 \"Андрей\"\n" +"utf:\n" +" .utf8 \"Андрей\"\n" +"raw:\n" +" .byte 1, 2+3, STEP/512, 0xff\n" +"ints:\n" +" .integer 16, 0x0410, 0x043d, 0\n" +"ints8:\n" +" .int 8, 65, 66, 0\n" +" .align 4*4\n" +"vals:\n" +" .integer 64, 11, 42\n" +".text\n" +"start:\n" +" LDI r64@REG0, 40+2\n" +" LOAD r64@(REG0+8), vals+8\n" +" HALT\n"; + +int +main( void ) +{ + cpu_asm_image im; + char err[256]; + int rc, i; + static const __mpu_uint16_t ucs_expect[] = + { + 0x0410, 0x043d, 0x0434, 0x0440, 0x0435, 0x0439, 0 + }; + static const unsigned char utf_expect[] = + { + 0xd0, 0x90, 0xd0, 0xbd, 0xd0, 0xb4, 0xd1, 0x80, 0xd0, 0xb5, 0xd0, 0xb9, 0 + }; + static const unsigned char raw_expect[] = + { + 1, 5, 4, 0xff + }; + static const char bad_ucs[] = ".data\n.ucs2 \"\\U0001F600\"\n.text\nHALT\n"; + static const char bad_byte[] = ".data\n.byte 256\n.text\nHALT\n"; + static const char bad_cycle[] = ".equ A, B+1\n.equ B, A+1\n.text\nHALT\n"; + + __cpu_init(); + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( im.data_size == 80, "DATA size including NULL guard, strings, raw bytes, integer lists and alignment" ); + for( i = 0; i < 7; i++ ) CHECK( __cpu_data_get_u16( im.data + 16 + i * 2 ) == ucs_expect[i], ".ucs2 code unit" ); + CHECK( !memcmp( im.data + 30, utf_expect, sizeof( utf_expect ) ), ".utf8 exact UTF-8 bytes plus NUL" ); + CHECK( !memcmp( im.data + 43, raw_expect, sizeof( raw_expect ) ), ".byte expressions" ); + CHECK( __cpu_data_get_u16( im.data + 47 ) == 0x0410, ".integer first list element" ); + CHECK( __cpu_data_get_u16( im.data + 49 ) == 0x043d, ".integer second list element" ); + CHECK( __cpu_data_get_u16( im.data + 51 ) == 0, ".integer terminator" ); + CHECK( im.data[53] == 65 && im.data[54] == 66 && im.data[55] == 0, ".int alias and list" ); + for( i = 56; i < 64; i++ ) CHECK( im.data[i] == 0, ".align expression padding" ); + CHECK( __cpu_data_get_u64( im.data + 64 ) == 11, "first 64-bit integer list element" ); + CHECK( __cpu_data_get_u64( im.data + 72 ) == 42, "second 64-bit integer list element" ); + + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + CHECK( __cpu_data_set( im.data, im.data_size ) == 0, "data set" ); + rc = __cpu_run(); + CHECK( rc == 0, "assembled expression program run" ); + CHECK( __cpu_data_get_u64( __cpu_reg_ptr( 64, 0x03000 ) ) == 42, "symbolic register address via forward .equ" ); + CHECK( __cpu_data_get_u64( __cpu_reg_ptr( 64, 0x03008 ) ) == 42, "DATA label+offset expression" ); + cpu_asm_image_free( &im ); + + CHECK( cpu_assemble_text( bad_ucs, &im, err, sizeof( err ) ) != 0, "strict UCS-2 rejects non-BMP character" ); + CHECK( strstr( err, "strict UCS-2" ) != 0, "strict UCS-2 diagnostic" ); + CHECK( cpu_assemble_text( bad_byte, &im, err, sizeof( err ) ) != 0, ".byte rejects >8-bit value" ); + CHECK( cpu_assemble_text( bad_cycle, &im, err, sizeof( err ) ) != 0, "cyclic .equ rejected" ); + + __cpu_free_context(); + puts( "libcpu assembler directives/expressions regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-assembler.c b/tests/tst-assembler.c new file mode 100644 index 0000000..9b31edc --- /dev/null +++ b/tests/tst-assembler.c @@ -0,0 +1,60 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) +static const char src[]= +".data\n" +"big:\n" +" .integer 128, 123456789012345678901234567890\n" +"rv:\n" +" .real 128, 1.25\n" +"cv:\n" +" .complex 128, 1.5, -2.5\n" +".text\n" +" LDI r128@0x00000, -17\n" +" LDR r128@0x00010, -17.0\n" +" LDC c128@0x00020, -17.0r0+32.0j0\n" +" RCONST r128@0x00030, PI\n" +" IADD r128@0x00040, r128@0x00000, r128@0x00000\n" +" RADD r128@0x00050, r128@0x00010, r128@0x00010\n" +" CADD c128@0x00060, c128@0x00020, c128@0x00020\n" +" LOAD r128@0x00070, big\n" +" HALT\n"; +int +main( void ) +{ + cpu_asm_image im; + char err[256]; + mpu_int128_t ie; + mpu_real128_t re, rr; + mpu_wide_real_t cr, ci, er, ei; + int rc; + __cpu_init(); + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( im.code_size > 0, "code produced" ); + CHECK( im.data_size == 64, "DATA NULL guard plus directives size" ); + CHECK( !memcmp( im.data, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", CPU_DATA_NULL_GUARD_SIZE ), "DATA NULL guard bytes" ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + CHECK( __cpu_data_set( im.data, im.data_size ) == 0, "data set" ); + rc = __cpu_run(); + CHECK( rc == 0, "assembled program run" ); + iatoi( ( mpu_int * ) ie, "-17", NB_I128 ); + CHECK( !memcmp( __cpu_reg_ptr( 128, 0 ), ie, NB_I128 ), "LDI" ); + ascii_to_real( ( mpu_real * ) re, "-17.0", NB_R128 ); + CHECK( r_cmp( ( mpu_real * ) __cpu_reg_ptr( 128, 16 ), ( mpu_real * ) re, NB_R128 ) == 0, "LDR" ); + _m_PI( ( mpu_real * ) rr, NB_R128 ); + CHECK( r_cmp( ( mpu_real * ) __cpu_reg_ptr( 128, 48 ), ( mpu_real * ) rr, NB_R128 ) == 0, "RCONST PI" ); + c_real_part( ( mpu_real * ) cr, ( mpu_complex * ) __cpu_reg_ptr( 128, 32 ), NB_R64 ); + c_imaginary( ( mpu_real * ) ci, ( mpu_complex * ) __cpu_reg_ptr( 128, 32 ), NB_R64 ); + ascii_to_real( ( mpu_real * ) er, "-17.0", NB_R64 ); + ascii_to_real( ( mpu_real * ) ei, "32.0", NB_R64 ); + CHECK( r_cmp( ( mpu_real * ) cr, ( mpu_real * ) er, NB_R64 ) == 0, "LDC real component" ); + CHECK( r_cmp( ( mpu_real * ) ci, ( mpu_real * ) ei, NB_R64 ) == 0, "LDC imag component" ); + iatoi( ( mpu_int * ) ie, "123456789012345678901234567890", NB_I128 ); + CHECK( !memcmp( __cpu_reg_ptr( 128, 112 ), ie, NB_I128 ), "LOAD DATA integer" ); + cpu_asm_image_free( &im ); + __cpu_free_context(); + puts( "libcpu test assembler regression: PASS" ); + return( 0 ); + } diff --git a/tests/tst-block-copy-runner.sh b/tests/tst-block-copy-runner.sh new file mode 100755 index 0000000..c8d02c9 --- /dev/null +++ b/tests/tst-block-copy-runner.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +base="${TMPDIR:-/tmp}/libcpu-block-copy-$$" +trap 'rm -f "$base.s" "$base.o" "$base.melf"' EXIT HUP INT TERM +"$CPU_AS" -o "$base.o" "$TEST_SRCDIR/fixtures/block-copy.s" >/dev/null +"$CPU_LD" --stack-size=512 -o "$base.melf" "$base.o" >/dev/null +out=`"$CPU_RUN" --int-reg=r64@0x00028 "$base.melf"` +test "x$out" = "x22" diff --git a/tests/tst-block-copy.c b/tests/tst-block-copy.c new file mode 100644 index 0000000..463633b --- /dev/null +++ b/tests/tst-block-copy.c @@ -0,0 +1,171 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static void +regop( cpu_operand *o, unsigned address ) +{ + memset( o, 0, sizeof( *o ) ); + o->type = CPU_OPERAND_REGISTER; + o->value.reg.bits = 64; + o->value.reg.address = address; +} + +static void +set_reg_u64( unsigned address, __mpu_uint64_t v ) +{ + memcpy( __cpu_reg_ptr( 64, address ), &v, sizeof( v ) ); +} + +static __mpu_uint64_t +get_reg_u64( unsigned address ) +{ + __mpu_uint64_t v = 0; + memcpy( &v, __cpu_reg_ptr( 64, address ), sizeof( v ) ); + return( v ); +} + +int +main( void ) +{ + cpu_asm_image im; + cpu_instruction i; + __mpu_uint64_t p; + __mpu_uint32_t psw; + __mpu_UNIT_t before[32]; + unsigned k; + char err[256]; + static const char src[]= + ".data\n" + ".align 8\n" + "v:\n" + " .integer 64, 1\n" + " .integer 64, 2\n" + " .integer 64, 3\n" + " .integer 64, 4\n" + ".text\n" + " LEA r64@0x00000, v\n" + " ADJSP -32\n" + " LEASP r64@0x00008, 0\n" + " BMOVI r64@0x00008, r64@0x00000, 32\n" + " LDI r64@0x00010, 16\n" + " BMOV r64@0x00008, r64@0x00000, r64@0x00010\n" + " HALT\n"; + + __cpu_init(); + CHECK( __cpu_stack_alloc( 128 ) == 0, "stack alloc" ); + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + CHECK( __cpu_data_set( im.data, im.data_size ) == 0, "data set" ); + + /* Decoder knows both block-copy signatures. */ + __cpu_pc = 0; + CHECK( __cpu_decode( &i ) == CPU_EXEC_SUCCESS, "decode LEA" ); + __cpu_pc+=i.length; + CHECK( __cpu_decode( &i ) == CPU_EXEC_SUCCESS, "decode ADJSP" ); + __cpu_pc+=i.length; + CHECK( __cpu_decode( &i ) == CPU_EXEC_SUCCESS, "decode LEASP" ); + __cpu_pc+=i.length; + CHECK( __cpu_decode( &i ) == CPU_EXEC_SUCCESS, "decode BMOVI" ); + CHECK( i.opcode == CPU_OP_BMOVI, "BMOVI opcode" ); + CHECK( i.length == CPU_OPCODE_SIZE + 2 * CPU_REG24_SIZE + CPU_IMM64_SIZE, "BMOVI length" ); + CHECK( i.operand[2].type == CPU_OPERAND_IMM64 && i.operand[2].value.imm64 == 32, "BMOVI count" ); + + __cpu_pc = 0; + CHECK( __cpu_run() == CPU_EXEC_SUCCESS, "assembled block-copy program" ); + CHECK( memcmp( __cpu_stack_ptr() + 96, __cpu_data_ptr() + CPU_DATA_NULL_GUARD_SIZE, 16 ) == 0, "BMOV runtime count DATA->STACK" ); + + /* Direct executor: overlapping copy must have memmove semantics. */ + for( k = 0; k < 32; k++ ) __cpu_stack_ptr()[64 + k] = (__mpu_UNIT_t)k; + memcpy( before, __cpu_stack_ptr() + 64, sizeof( before ) ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_STACK, 68 ) == 0, "make overlap destination" ); + set_reg_u64( 0x00100, p ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_STACK, 64 ) == 0, "make overlap source" ); + set_reg_u64( 0x00108, p ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_BMOVI; + regop( &i.operand[0], 0x00100 ); + regop( &i.operand[1], 0x00108 ); + i.operand[2].type = CPU_OPERAND_IMM64; + i.operand[2].value.imm64 = 16; + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "BMOVI overlap" ); + CHECK( memcmp( __cpu_stack_ptr() + 68, before, 16 ) == 0, "BMOVI uses memmove semantics" ); + + /* Register-sized form uses an unsigned r64 byte count. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, CPU_DATA_NULL_GUARD_SIZE ) == 0, "make DATA source" ); + set_reg_u64( 0x00108, p ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_STACK, 32 ) == 0, "make STACK destination" ); + set_reg_u64( 0x00100, p ); + set_reg_u64( 0x00110, 24 ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_BMOV; + regop( &i.operand[0], 0x00100 ); + regop( &i.operand[1], 0x00108 ); + regop( &i.operand[2], 0x00110 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "BMOV register count" ); + CHECK( memcmp( __cpu_stack_ptr() + 32, __cpu_data_ptr() + CPU_DATA_NULL_GUARD_SIZE, 24 ) == 0, "BMOV copied runtime byte count" ); + + /* Copying from PROGRAM is permitted. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_PROGRAM, 0 ) == 0, "make PROGRAM source" ); + set_reg_u64( 0x00108, p ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, CPU_DATA_NULL_GUARD_SIZE ) == 0, "make DATA destination" ); + set_reg_u64( 0x00100, p ); + set_reg_u64( 0x00110, 2 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "BMOV PROGRAM->DATA" ); + CHECK( __cpu_data_ptr()[CPU_DATA_NULL_GUARD_SIZE] == __cpu_program_ptr()[0] && __cpu_data_ptr()[CPU_DATA_NULL_GUARD_SIZE + 1] == __cpu_program_ptr()[1], "PROGRAM source bytes copied" ); + + /* PROGRAM is read-only as a destination; destination memory is unchanged. */ + memcpy( before, __cpu_program_ptr(), 16 ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_PROGRAM, 0 ) == 0, "make PROGRAM destination" ); + set_reg_u64( 0x00100, p ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_STACK, 32 ) == 0, "make STACK source" ); + set_reg_u64( 0x00108, p ); + set_reg_u64( 0x00110, 8 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_MEMORY_FAULT, "BMOV rejects PROGRAM destination" ); + CHECK( memcmp( before, __cpu_program_ptr(), 16 ) == 0, "PROGRAM unchanged on destination fault" ); + + /* Out-of-range source is detected before any destination bytes change. */ + memset( __cpu_stack_ptr() + 16, 0xa5, 16 ); + memcpy( before, __cpu_stack_ptr() + 16, 16 ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_STACK, 16 ) == 0, "make atomic destination" ); + set_reg_u64( 0x00100, p ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, __cpu_data_size() - 4 ) == 0, "make short source range" ); + set_reg_u64( 0x00108, p ); + set_reg_u64( 0x00110, 8 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_MEMORY_FAULT, "BMOV source range fault" ); + CHECK( memcmp( before, __cpu_stack_ptr() + 16, 16 ) == 0, "destination unchanged on source fault" ); + + /* Reserved segment fails as a pointer fault. */ + CHECK( __cpu_ptr_make( &p, 0x0f, 0 ) == 0, "make reserved pointer" ); + set_reg_u64( 0x00108, p ); + set_reg_u64( 0x00110, 1 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "BMOV reserved source segment" ); + + /* PSW is not modified. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, CPU_DATA_NULL_GUARD_SIZE ) == 0, "make PSW source" ); + set_reg_u64( 0x00108, p ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_STACK, 0 ) == 0, "make PSW destination" ); + set_reg_u64( 0x00100, p ); + set_reg_u64( 0x00110, 1 ); + __cpu_set_psw( PSW_CF_MASK | PSW_IF_MASK ); + psw = __cpu_get_psw(); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "BMOV PSW test" ); + CHECK( __cpu_get_psw() == psw, "BMOV preserves PSW" ); + + /* Zero length is a valid no-op at one-past offsets of allocated segments. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, __cpu_data_size() ) == 0, "make DATA one-past" ); + set_reg_u64( 0x00108, p ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_STACK, __cpu_stack_size() ) == 0, "make STACK one-past" ); + set_reg_u64( 0x00100, p ); + set_reg_u64( 0x00110, 0 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "BMOV zero byte no-op" ); + CHECK( get_reg_u64( 0x00110 ) == 0, "zero count register retained" ); + + cpu_asm_image_free( &im ); + __cpu_free_context(); + puts( "libcpu block-copy regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-bswap-runner.sh b/tests/tst-bswap-runner.sh new file mode 100755 index 0000000..aec1668 --- /dev/null +++ b/tests/tst-bswap-runner.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +base="${TMPDIR:-/tmp}/libcpu-bswap-$$" +trap 'rm -f "$base.s" "$base.o" "$base.melf"' EXIT HUP INT TERM +"$CPU_AS" -o "$base.o" "$TEST_SRCDIR/fixtures/byte-swap.s" >/dev/null +"$CPU_LD" --stack-size=512 -o "$base.melf" "$base.o" >/dev/null +out=`"$CPU_RUN" --int-reg=r64@0x00010 "$base.melf"` +test "x$out" = "x42" diff --git a/tests/tst-bswap.c b/tests/tst-bswap.c new file mode 100644 index 0000000..aff9cbd --- /dev/null +++ b/tests/tst-bswap.c @@ -0,0 +1,105 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static void +regop( cpu_operand *o, unsigned bits, unsigned address ) +{ + memset( o, 0, sizeof( *o ) ); + o->type = CPU_OPERAND_REGISTER; + o->value.reg.bits = bits; + o->value.reg.address = address; +} + +static int +do_bswap( unsigned bits, unsigned d, unsigned s ) +{ + cpu_instruction i; + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_BSWAP; + regop( &i.operand[0], bits, d ); + regop( &i.operand[1], bits, s ); + return( __cpu_exec( &i ) ); +} + +int +main( void ) +{ + cpu_asm_image im; + char err[256]; + __mpu_UNIT_t *src, *dst; + unsigned i; + __mpu_uint32_t psw; + const char *asrc= + ".text\n" + "LDI r64@0x00000, 42\n" + "BSWAP r64@0x00008, r64@0x00000\n" + "BSWAP r64@0x00010, r64@0x00008\n" + "HALT\n"; + + __cpu_init(); + CHECK( cpu_assemble_text( asrc, &im, err, sizeof( err ) ) == 0, err ); + CHECK( im.code_size == 31, "BSWAP machine size is OPCODE16 + 2*REG24" ); + CHECK( __cpu_code_get_u16( im.code + 13 ) == CPU_OP_BSWAP, "first BSWAP opcode encoded" ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "set BSWAP program" ); + CHECK( __cpu_run() == CPU_EXEC_SUCCESS, "run BSWAP program" ); + CHECK( __cpu_data_get_u64( __cpu_reg_ptr( 64, 0x10 ) ) == 42, "double BSWAP round trip" ); + cpu_asm_image_free( &im ); + + /* r8 is a defined identity operation. */ + src = __cpu_reg_ptr( 8, 0x100 ); + dst = __cpu_reg_ptr( 8, 0x101 ); + *src = 0xa5; + *dst = 0; + CHECK( do_bswap( 8, 0x101, 0x100 ) == CPU_EXEC_SUCCESS, "BSWAP r8" ); + CHECK( *dst == 0xa5, "BSWAP r8 identity" ); + + /* Arbitrary wide physical registers are reversed byte for byte. */ + src = __cpu_reg_ptr( 256, 0x200 ); + dst = __cpu_reg_ptr( 256, 0x220 ); + for( i = 0; i < 32; i++ ) src[i] = (__mpu_UNIT_t)i; + memset( dst, 0, 32 ); + psw = PSW_CF_MASK | PSW_IF_MASK | PSW_TF_MASK; + __cpu_set_psw( psw ); + CHECK( do_bswap( 256, 0x220, 0x200 ) == CPU_EXEC_SUCCESS, "BSWAP r256" ); + for( i = 0; i < 32; i++ ) CHECK( dst[i] == (__mpu_UNIT_t)( 31 - i ), "BSWAP r256 byte reversal" ); + CHECK( __cpu_get_psw() == psw, "BSWAP preserves PSW" ); + + /* In-place BSWAP uses source-snapshot semantics. */ + src = __cpu_reg_ptr( 64, 0x300 ); + for( i = 0; i < 8; i++ ) src[i] = (__mpu_UNIT_t)( 0x10 + i ); + CHECK( do_bswap( 64, 0x300, 0x300 ) == CPU_EXEC_SUCCESS, "in-place BSWAP" ); + for( i = 0; i < 8; i++ ) CHECK( src[i] == (__mpu_UNIT_t)( 0x17 - i ), "in-place BSWAP snapshot result" ); + + /* Maximum physical width is supported. */ + src = __cpu_reg_ptr( 65536, 0x0000 ); + dst = __cpu_reg_ptr( 65536, 0x2000 ); + for( i = 0; i < 8192; i++ ) src[i] = (__mpu_UNIT_t)( i & 0xffU ); + CHECK( do_bswap( 65536, 0x2000, 0x0000 ) == CPU_EXEC_SUCCESS, "BSWAP r65536" ); + CHECK( dst[0] == src[8191] && dst[8191] == src[0], "BSWAP r65536 endpoints" ); + + /* Width mismatch is rejected before modifying destination. */ + { + cpu_instruction x; + __mpu_UNIT_t before[8]; + memset( x.operand, 0, sizeof( x.operand ) ); + x.opcode = CPU_OP_BSWAP; + regop( &x.operand[0], 64, 0x400 ); + regop( &x.operand[1], 32, 0x408 ); + memset( __cpu_reg_ptr( 64, 0x400 ), 0x5a, 8 ); + memcpy( before, __cpu_reg_ptr( 64, 0x400 ), 8 ); + CHECK( __cpu_exec( &x ) == CPU_EXEC_WIDTH_FAULT, "BSWAP width mismatch" ); + CHECK( memcmp( before, __cpu_reg_ptr( 64, 0x400 ), 8 ) == 0, "BSWAP width fault atomic" ); + } + + /* Assembler syntax intentionally uses raw r<bits> views, not c<bits>. */ + CHECK( cpu_assemble_text( ".text\nBSWAP c64@0x0, c64@0x8\nHALT\n", &im, err, sizeof( err ) ) != 0, + "assembler rejects complex spelling for raw BSWAP"); + + __cpu_free_context(); + puts( "libcpu BSWAP regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-bytecode.c b/tests/tst-bytecode.c new file mode 100644 index 0000000..c4f885e --- /dev/null +++ b/tests/tst-bytecode.c @@ -0,0 +1,56 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) +int +main( void ) +{ + cpu_register r = + { + 128, 0 + }; + __mpu_UNIT_t reg[3], code[18]; + mpu_int128_t exp; + cpu_instruction insn; + int rc; + CHECK( __cpu_encode_reg24( reg, &r ) == 0, "encode REG24" ); + code[0] = 0x01; + code[1] = 0x10; + memcpy( code + 2, reg, 3 ); + /* LDI */ + code[5] = 0xff; + code[6] = 0xff; + code[7] = 0xff; + code[8] = 0xff; + code[9] = 0xff; + code[10] = 0xff; + code[11] = 0xff; + code[12] = 0xef; + /* -17 */ + code[13] = 0x00; + code[14] = 0x01; + /* HALT */ + CHECK( __cpu_decode_at( code, 15, 0, &insn ) == CPU_EXEC_SUCCESS, + "decode-at LDI" ); + CHECK( insn.opcode == CPU_OP_LDI && insn.length == 13, + "decode-at LDI fields" ); + CHECK( __cpu_decode_at( code, 15, 13, &insn ) == CPU_EXEC_SUCCESS, + "decode-at HALT" ); + CHECK( insn.opcode == CPU_OP_HALT && insn.length == 2, + "decode-at HALT fields" ); + CHECK( __cpu_decode_at( code, 14, 13, &insn ) == CPU_EXEC_DECODE_FAULT, + "decode-at bounds" ); + __cpu_init(); + CHECK( __cpu_program_set( code, 15 ) == 0, "program mmap/load" ); + CHECK( __cpu_program_ptr() != 0, "program pointer" ); + CHECK( __cpu_program_size() == 15, "program size" ); + rc = __cpu_run(); + CHECK( rc == 0, "run" ); + CHECK( __cpu_pc == 15, "PC advanced" ); + CHECK( __cpu_gth() != 0, "HF set" ); + iatoi( ( mpu_int * ) exp, "-17", NB_I128 ); + CHECK( !memcmp( __cpu_reg_ptr( 128, 0 ), exp, NB_I128 ), "LDI int64 -> int128" ); + __cpu_free_context(); + puts( "libcpu bytecode/program-memory regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-byteorder.c b/tests/tst-byteorder.c new file mode 100644 index 0000000..6346c9e --- /dev/null +++ b/tests/tst-byteorder.c @@ -0,0 +1,170 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static int +raw_u64_matches_target( const __mpu_UNIT_t *p, __mpu_uint64_t v ) +{ + __mpu_UNIT_t x[8]; + __cpu_data_put_u64( x, v ); + return( memcmp( p, x, 8 ) == 0 ); +} + +int +main( void ) +{ + static const __mpu_UNIT_t canonical64[8] = + { + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88 + }; + static const char src[]= + ".data\n" + ".align 8\n" + "ival:\n" + " .integer 64, 1234605616436508552\n" + ".align 8\n" + "rval:\n" + " .real 64, 1.5\n" + ".align 16\n" + "zval:\n" + " .complex 128, 1.5, -2.0\n" + ".align 8\n" + "target_slot:\n" + " .cptr done\n" + ".align 8\n" + "copy:\n" + " .integer 64, 0\n" + ".text\n" + " LDI r64@0x00000, 1234605616436508552\n" + " LOAD r64@0x00008, ival\n" + " LEA r64@0x00010, ival\n" + " LEA r64@0x00018, copy\n" + " BMOVI r64@0x00018, r64@0x00010, 8\n" + " LOAD r64@0x00020, copy\n" + " CALL leaf\n" + " JMPP r64@0x00028\n" + "leaf:\n" + " RET\n" + "done:\n" + " HALT\n"; + static const char call_src[]= + ".text\n" + " CALL leaf\n" + " HALT\n" + "leaf:\n" + " RET\n"; + cpu_asm_image im, callim, alias; + __mpu_UNIT_t buf[8], real_expected[8], complex_expected[16]; + mpu_wide_real_t rr, ri; + mpu_wide_complex_t zz; + __mpu_uint64_t v = __mpu_UINT64_C( 0x1122334455667788 ), ptr, ret; + char err[256]; + int rc; + + __cpu_init(); + + /* The two byte-order domains are deliberately different concepts. */ + memset( buf, 0, sizeof( buf ) ); + __cpu_code_put_u64( buf, v ); + CHECK( !memcmp( buf, canonical64, 8 ), "CODE u64 is canonical MSB-first" ); + CHECK( __cpu_code_get_u64( buf ) == v, "CODE u64 round trip" ); + CHECK( CPU_CODE_BYTE_ORDER_MSB_FIRST == 1, "CODE byte-order ABI marker" ); + + memset( buf, 0, sizeof( buf ) ); + __cpu_data_put_u64( buf, v ); + CHECK( __cpu_data_get_u64( buf ) == v, "DATA/object u64 round trip" ); +#if CPU_DATA_BYTE_ORDER_LITTLE_ENDIAN == 1 + { + static const __mpu_UNIT_t le[8] = + { + 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11 + }; + CHECK( !memcmp( buf, le, 8 ), "target data u64 little-endian bytes" ); + } +#elif CPU_DATA_BYTE_ORDER_BIG_ENDIAN == 1 + CHECK( !memcmp( buf, canonical64, 8 ), "target data u64 big-endian bytes" ); +#else +# error Unsupported LibMPU byte order for LibCPU byte-order regression +#endif + + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + + /* First instruction is LDI: OPCODE16 + REG24 + canonical IMM64. */ + CHECK( im.code_size > 13, "assembled bytecode exists" ); + CHECK( im.code[0] == 0x01 && im.code[1] == 0x10, "LDI opcode canonical bytes" ); + CHECK( !memcmp( im.code + 5, canonical64, 8 ), "LDI IMM64 is canonical MSB-first" ); + + /* DATA directives create target object representation, not bytecode order. */ + CHECK( im.data_size >= 64, "assembled DATA image size" ); + CHECK( __cpu_data_get_u64( im.data + 16 ) == v, ".integer64 target data object" ); + CHECK( raw_u64_matches_target( im.data + 16, v ), ".integer64 raw target bytes" ); + + memset( rr, 0, sizeof( rr ) ); + CHECK( ascii_to_real( ( mpu_real * ) rr, "1.5", NB_R64 ) >= 0, "build expected real64" ); + memcpy( real_expected, rr, 8 ); + CHECK( !memcmp( im.data + 24, real_expected, 8 ), ".real64 follows LibMPU object representation" ); + + memset( rr, 0, sizeof( rr ) ); + memset( ri, 0, sizeof( ri ) ); + memset( zz, 0, sizeof( zz ) ); + CHECK( ascii_to_real( ( mpu_real * ) rr, "1.5", NB_R64 ) >= 0, "build complex real component" ); + CHECK( ascii_to_real( ( mpu_real * ) ri, "-2.0", NB_R64 ) >= 0, "build complex imaginary component" ); + c_gen_complex( ( mpu_complex * ) zz, ( mpu_real * ) rr, ( mpu_real * ) ri, NB_R64 ); + memcpy( complex_expected, zz, 16 ); + CHECK( !memcmp( im.data + 32, complex_expected, 16 ), ".complex128 follows LibMPU object representation" ); + + ptr = __cpu_data_get_u64( im.data + 48 ); + CHECK( __cpu_ptr_segment( ptr ) == CPU_PTR_SEG_PROGRAM, ".cptr stores PROGRAM PTR64 data object" ); + CHECK( __cpu_ptr_offset( ptr ) < im.code_size, ".cptr PROGRAM offset in range" ); + + /* Compatibility spelling remains accepted, but .cptr is canonical. */ + CHECK( cpu_assemble_text( ".data\n .codeptr x\n.text\nx:\n HALT\n", &alias, err, sizeof( err ) ) == 0, "legacy .codeptr alias accepted" ); + cpu_asm_image_free( &alias ); + + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + CHECK( __cpu_data_set( im.data, im.data_size ) == 0, "data set" ); + CHECK( __cpu_stack_alloc( 512 ) == 0, "stack alloc" ); + + /* Load source and BMOV destination preserve the complete object bytes. */ + rc = __cpu_step(); + CHECK( rc == CPU_EXEC_SUCCESS, "step LDI" ); + CHECK( __cpu_data_get_u64( __cpu_reg_ptr( 64, 0x00000 ) ) == v, "LDI decoded canonical bits into target register object" ); + rc = __cpu_step(); + CHECK( rc == CPU_EXEC_SUCCESS, "step LOAD source" ); + CHECK( !memcmp( __cpu_reg_ptr( 64, 0x00008 ), im.data + 16, 8 ), "LOAD preserves data object bytes" ); + rc = __cpu_step(); + CHECK( rc == CPU_EXEC_SUCCESS, "step LEA source" ); + rc = __cpu_step(); + CHECK( rc == CPU_EXEC_SUCCESS, "step LEA destination" ); + rc = __cpu_step(); + CHECK( rc == CPU_EXEC_SUCCESS, "step BMOVI" ); + rc = __cpu_step(); + CHECK( rc == CPU_EXEC_SUCCESS, "step LOAD copied object" ); + CHECK( !memcmp( __cpu_reg_ptr( 64, 0x00008 ), __cpu_reg_ptr( 64, 0x00020 ), 8 ), "LOAD-BMOV-LOAD is byte preserving" ); + CHECK( !memcmp( __cpu_data_ptr() + 56, im.data + 16, 8 ), "BMOV copied exact target object bytes" ); + + cpu_asm_image_free( &im ); + __cpu_program_free(); + __cpu_data_free(); + __cpu_stack_free(); + + /* CALL return PC is a STACK data object, not a canonical CODE64 field. */ + CHECK( cpu_assemble_text( call_src, &callim, err, sizeof( err ) ) == 0, err ); + CHECK( __cpu_program_set( callim.code, callim.code_size ) == 0, "call program set" ); + CHECK( __cpu_stack_alloc( 64 ) == 0, "call stack alloc" ); + __cpu_pc = 0; + rc = __cpu_step(); + CHECK( rc == CPU_EXEC_SUCCESS, "CALL step" ); + CHECK( __cpu_sp == 56, "CALL pushed 8-byte return object" ); + ret = __cpu_data_get_u64( __cpu_stack_ptr() + 56 ); + CHECK( ret == 10, "CALL return PC target data representation decodes to next PC" ); + CHECK( raw_u64_matches_target( __cpu_stack_ptr() + 56, 10 ), "CALL return PC raw stack bytes use target data order" ); + cpu_asm_image_free( &callim ); + + __cpu_free_context(); + puts( "libcpu byte-order/object-representation regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-call-runner.sh b/tests/tst-call-runner.sh new file mode 100755 index 0000000..e0e6994 --- /dev/null +++ b/tests/tst-call-runner.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +base="${TMPDIR:-/tmp}/libcpu-call-$$" +trap 'rm -f "$base.s" "$base.o" "$base.melf"' EXIT HUP INT TERM +"$CPU_AS" -o "$base.o" "$TEST_SRCDIR/fixtures/call.s" >/dev/null +"$CPU_LD" --stack-size=128 -o "$base.melf" "$base.o" >/dev/null +out=`"$CPU_RUN" --int-reg=r64@0x00010 "$base.melf"` +test "x$out" = "x33" diff --git a/tests/tst-call.c b/tests/tst-call.c new file mode 100644 index 0000000..7363c83 --- /dev/null +++ b/tests/tst-call.c @@ -0,0 +1,100 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static const char leaf_src[]= +".text\n" +" CALL leaf\n" +" HALT\n" +"leaf:\n" +" RET\n"; + +static const char add_src[]= +".text\n" +" LDI r64@0x00000, 11\n" +" LDI r64@0x00008, 22\n" +" PUSH r64@0x00008\n" +" PUSH r64@0x00000\n" +" CALL add2\n" +" ADJSP 16\n" +" MOV r64@0x00010, r64@0x7fff8\n" +" HALT\n" +"add2:\n" +" LDSP r64@0x00100, 8\n" +" LDSP r64@0x00108, 16\n" +" IADD r64@0x7fff8, r64@0x00100, r64@0x00108\n" +" RET\n"; + +int +main( void ) +{ + cpu_asm_image im; + cpu_register rr; + mpu_int64_t thirty_three; + __mpu_uint64_t saved_pc; + __mpu_uint32_t psw; + char err[256]; + unsigned int widths[] = + { + 8, 16, 32, 64, 4096, 65536 + }; + size_t k; + + __cpu_init(); + + CHECK( CPU_RETURN_AREA_BEGIN == 0x0007e000U, "return area begin" ); + CHECK( CPU_RETURN_AREA_END == 0x00080000U, "return area end" ); + CHECK( CPU_RETURN_AREA_SIZE == 0x00002000U, "return area size" ); + for( k = 0; k < sizeof( widths ) / sizeof( widths[0] ); k++ ) + { + __mpu_size_t n = __cpu_reg_size( widths[k] ); + CHECK( __cpu_return_reg( &rr, widths[k] ) == 0, "canonical return register" ); + CHECK( rr.bits == widths[k], "return register width" ); + CHECK( rr.address == CPU_RETURN_AREA_END - (unsigned)n, "return register address formula" ); + } + CHECK( __cpu_return_reg( &rr, 7 ) != 0, "reject invalid return width" ); + + CHECK( cpu_assemble_text( leaf_src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "leaf program set" ); + CHECK( __cpu_stack_alloc( 64 ) == 0, "leaf stack alloc" ); + __cpu_pc = 0; + __cpu_set_psw( PSW_CF_MASK | PSW_TF_MASK ); + psw = __cpu_get_psw(); + + CHECK( __cpu_step() == CPU_EXEC_SUCCESS, "CALL step" ); + CHECK( __cpu_pc == 12, "CALL target" ); + CHECK( __cpu_sp == 56, "CALL pushed 8-byte return address" ); + memcpy( &saved_pc, __cpu_stack_ptr() + __cpu_sp, 8 ); + CHECK( saved_pc == 10, "CALL stored next PC" ); + CHECK( __cpu_get_psw() == psw, "CALL preserves PSW" ); + + CHECK( __cpu_step() == CPU_EXEC_SUCCESS, "RET step" ); + CHECK( __cpu_pc == 10, "RET restored PC" ); + CHECK( __cpu_sp == 64, "RET popped return address" ); + CHECK( __cpu_get_psw() == psw, "RET preserves PSW" ); + cpu_asm_image_free( &im ); + + __cpu_program_free(); + __cpu_stack_free(); + memset( __cpu_registers, 0, CPU_REGISTERS_MEM_SIZE_IN_UNITS ); + __cpu_clear_psw(); + + CHECK( cpu_assemble_text( add_src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "add program set" ); + CHECK( __cpu_stack_alloc( 128 ) == 0, "add stack alloc" ); + __cpu_pc = 0; + CHECK( __cpu_run() == CPU_EXEC_SUCCESS, "CALL ABI run" ); + CHECK( __cpu_sp == 128, "caller cleanup restored empty SP" ); + + iatoi( ( mpu_int * ) thirty_three, "33", NB_I64 ); + CHECK( !memcmp( __cpu_reg_ptr( 64, 0x7fff8 ), thirty_three, NB_I64 ), "return value area result" ); + CHECK( !memcmp( __cpu_reg_ptr( 64, 0x00010 ), thirty_three, NB_I64 ), "caller copied return value" ); + + cpu_asm_image_free( &im ); + __cpu_free_context(); + puts( "libcpu CALL ABI regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-context.c b/tests/tst-context.c new file mode 100644 index 0000000..6384d75 --- /dev/null +++ b/tests/tst-context.c @@ -0,0 +1,121 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> + +#include <libmcpu.h> + + +static int failures = 0; + +#define CHECK(cond,msg) \ + do { \ + if( !(cond) ) \ + { \ + fprintf( stderr, "FAIL: %s\n", (msg) ); \ + ++failures; \ + } \ + } while( 0 ) + + +int +main( void ) +{ + __mpu_uint32_t psw; + + __cpu_init(); + + CHECK( __cpu_pc == 0, "PC after __cpu_init()" ); + CHECK( __cpu_sp == 0, "SP after __cpu_init()" ); + CHECK( __cpu_fp == 0, "FP after __cpu_init()" ); + CHECK( __cpu_get_psw() == 0, "PSW after __cpu_init()" ); + + /* Verify access to the complete 512 KiB register file. */ + __cpu_registers[0] = 0x5a; + __cpu_registers[CPU_REGISTERS_MEM_SIZE_IN_UNITS - 1] = 0xa5; + CHECK( __cpu_registers[0] == 0x5a, "register file first byte" ); + CHECK( __cpu_registers[CPU_REGISTERS_MEM_SIZE_IN_UNITS - 1] == 0xa5, + "register file last byte" ); + + /* Low PSW bits are maintained by LibMPU. */ + __mpu_stc(); + __mpu_stz(); + __mpu_stinx(); + + psw = __cpu_get_psw(); + CHECK( (psw & PSW_CF_MASK) != 0, "PSW reads LibMPU CF" ); + CHECK( (psw & PSW_ZF_MASK) != 0, "PSW reads LibMPU ZF" ); + CHECK( (psw & PSW_INXF_MASK) != 0, "PSW reads LibMPU INXF" ); + + /* High PSW bits belong to LibCPU. */ + __cpu_sth(); + __cpu_stt(); + __cpu_sti(); + CHECK( __cpu_stmode( 2 ) == 0, "set CPU MODE" ); + + psw = __cpu_get_psw(); + CHECK( (psw & PSW_HF_MASK) != 0, "PSW HF" ); + CHECK( (psw & PSW_TF_MASK) != 0, "PSW TF" ); + CHECK( (psw & PSW_IF_MASK) != 0, "PSW IF" ); + CHECK( __cpu_gtmode() == 2, "PSW MODE" ); + + /* CPU flag operations do not alter the LibMPU half of PSW. */ + __cpu_clh(); + __cpu_cmt(); + __cpu_cmi(); + + psw = __cpu_get_psw(); + CHECK( (psw & PSW_HF_MASK) == 0, "clear HF" ); + CHECK( (psw & PSW_TF_MASK) == 0, "complement TF" ); + CHECK( (psw & PSW_IF_MASK) == 0, "complement IF" ); + CHECK( (psw & PSW_CF_MASK) != 0, "CF preserved by CPU flags" ); + CHECK( (psw & PSW_ZF_MASK) != 0, "ZF preserved by CPU flags" ); + CHECK( (psw & PSW_INXF_MASK) != 0, "INXF preserved by CPU flags" ); + + /* Store/restore the complete logical PSW. */ + __cpu_set_psw( PSW_AF_MASK | PSW_OF_MASK | PSW_DOMF_MASK | + PSW_HF_MASK | PSW_IF_MASK | + ((__mpu_uint32_t)3 << PSW_MODE_SHIFT) ); + + psw = __cpu_get_psw(); + CHECK( psw == (PSW_AF_MASK | PSW_OF_MASK | PSW_DOMF_MASK | + PSW_HF_MASK | PSW_IF_MASK | + ((__mpu_uint32_t)3 << PSW_MODE_SHIFT)), + "complete PSW store/restore" ); + CHECK( __mpu_gta() == 1, "LibMPU AF after __cpu_set_psw()" ); + CHECK( __mpu_gto() == 1, "LibMPU OF after __cpu_set_psw()" ); + CHECK( __mpu_gtdom() == 1, "LibMPU DOMF after __cpu_set_psw()" ); + CHECK( __mpu_gtc() == 0, "LibMPU CF cleared by __cpu_set_psw()" ); + CHECK( __cpu_gth() == 1, "LibCPU HF after __cpu_set_psw()" ); + CHECK( __cpu_gti() == 1, "LibCPU IF after __cpu_set_psw()" ); + CHECK( __cpu_gtmode() == 3, "LibCPU MODE after __cpu_set_psw()" ); + + __cpu_clear_psw(); + CHECK( __cpu_get_psw() == 0, "clear complete PSW" ); + + __cpu_pc = 0x123456789abcdef0ULL; + __cpu_sp = 0x0fedcba987654321ULL; + __cpu_fp = 0x0123456789abcdefULL; + CHECK( __cpu_pc == 0x123456789abcdef0ULL, "PC access" ); + CHECK( __cpu_sp == 0x0fedcba987654321ULL, "SP access" ); + CHECK( __cpu_fp == 0x0123456789abcdefULL, "FP access" ); + + __cpu_free_context(); + CHECK( __cpu_pc == 0, "PC after __cpu_free_context()" ); + CHECK( __cpu_sp == 0, "SP after __cpu_free_context()" ); + CHECK( __cpu_fp == 0, "FP after __cpu_free_context()" ); + CHECK( __cpu_get_psw() == 0, "PSW after __cpu_free_context()" ); + + if( failures != 0 ) + { + fprintf( stderr, "libcpu context regression: %d failure(s)\n", failures ); + return( 1 ); + } + + printf( "libcpu context/PSW regression: PASS\n" ); + printf( "register file: %u bytes\n", CPU_REGISTERS_MEM_SIZE_IN_UNITS ); + printf( "PSW: LibMPU bits 0..15, LibCPU bits 16..20\n" ); + + return( 0 ); +} diff --git a/tests/tst-control.c b/tests/tst-control.c new file mode 100644 index 0000000..7a3300f --- /dev/null +++ b/tests/tst-control.c @@ -0,0 +1,80 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + + +static const char alias_src[]= +".text\n" +" JZ end\n" +" JNZ end\n" +" JC end\n" +" JNC end\n" +" JO end\n" +" JNO end\n" +" JS end\n" +" JNS end\n" +"end:\n" +" HALT\n"; + +static const char src[]= +".text\n" +" JMP start\n" +"dead:\n" +" HALT\n" +"start:\n" +" LDI r64@0x00000, 3\n" +" LDI r64@0x00008, 1\n" +" CLR r64@0x00010\n" +"loop:\n" +" ISUB r64@0x00000, r64@0x00000, r64@0x00008\n" +" ICMP r64@0x00000, r64@0x00010\n" +" JNZ loop\n" +" JSET ZF, done\n" +" LDI r64@0x00018, 77\n" +"done:\n" +" LDI r64@0x00018, 42\n" +" HALT\n"; + +int +main( void ) +{ + cpu_asm_image im, aliases; + cpu_instruction insn; + mpu_int64_t zero, forty_two; + char err[256]; + int rc; + + __cpu_init(); + CHECK( cpu_assemble_text( alias_src, &aliases, err, sizeof( err ) ) == 0, err ); + CHECK( aliases.code_size == 90, "all eight jump aliases assembled" ); + cpu_asm_image_free( &aliases ); + + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( im.code_size > 0, "control-flow code produced" ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + + __cpu_pc = 0; + CHECK( __cpu_decode( &insn ) == 0, "decode JMP" ); + CHECK( insn.opcode == CPU_OP_JMP, "first opcode JMP" ); + CHECK( insn.length == 10, "JMP length" ); + CHECK( insn.operand[0].type == CPU_OPERAND_CODE64, "JMP CODE64 operand" ); + CHECK( insn.operand[0].value.code64 < im.code_size, "JMP target in PROGRAM" ); + + rc = __cpu_run(); + CHECK( rc == CPU_EXEC_SUCCESS, "control-flow run" ); + CHECK( __cpu_pc == im.code_size, "PC after final HALT" ); + CHECK( __cpu_gth() != 0, "HALT flag set" ); + + iatoi( ( mpu_int * ) zero, "0", NB_I64 ); + iatoi( ( mpu_int * ) forty_two, "42", NB_I64 ); + CHECK( !memcmp( __cpu_reg_ptr( 64, 0 ), zero, NB_I64 ), "backward JNZ loop reached zero" ); + CHECK( !memcmp( __cpu_reg_ptr( 64, 24 ), forty_two, NB_I64 ), "forward JSET reached done label" ); + + cpu_asm_image_free( &im ); + __cpu_free_context(); + puts( "libcpu control-flow regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-conversion-flags.c b/tests/tst-conversion-flags.c new file mode 100644 index 0000000..5244c1a --- /dev/null +++ b/tests/tst-conversion-flags.c @@ -0,0 +1,204 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> + +static int failures; +#define CHECK(c,m) do { if(!(c)){ fprintf(stderr,"FAIL: %s\n",(m)); ++failures; } } while(0) + +static cpu_operand +R( unsigned bits, __mpu_uint32_t addr ) +{ + cpu_operand o; + memset( &o, 0, sizeof( o ) ); + o.type = CPU_OPERAND_REGISTER; + o.value.reg.bits = bits; + o.value.reg.address = addr; + return( o ); +} + +static cpu_operand +IMM( __mpu_uint64_t v ) +{ + cpu_operand o; + memset( &o, 0, sizeof( o ) ); + o.type = CPU_OPERAND_IMM64; + o.value.imm64 = v; + return( o ); +} + +static void +I( cpu_instruction *i, cpu_opcode op ) +{ + memset( i, 0, sizeof( *i ) ); + i->opcode = op; +} + +static __mpu_UNIT_t * +P( unsigned bits, __mpu_uint32_t addr ) +{ + return( __cpu_reg_ptr( bits, addr ) ); +} + +static __mpu_uint32_t +seed_psw( void ) +{ + __mpu_uint32_t p= + PSW_AF_MASK | PSW_OF_MASK | PSW_PF_MASK | PSW_RF_MASK | + PSW_DOMF_MASK | PSW_OVFF_MASK | PSW_TLSF_MASK | PSW_INDF_MASK | + PSW_TF_MASK | PSW_IF_MASK | + ( (__mpu_uint32_t)2U << PSW_MODE_SHIFT ); + __cpu_set_psw( p ); + return( p ); +} + +static void +preserved( const char *name ) +{ + __mpu_uint32_t expect = seed_psw(); + (void)name; + CHECK( __cpu_get_psw() == expect, "PSW seed" ); +} + +static void +check_after( const char *name, __mpu_uint32_t expect ) +{ + if( __cpu_get_psw() != expect ) + { + fprintf( stderr, "FAIL: %s changed PSW: got 0x%08x expected 0x%08x\n", + name, (unsigned)__cpu_get_psw(), (unsigned)expect); + ++failures; + } +} + +int +main( void ) +{ + cpu_instruction x; + __mpu_uint32_t psw; + __cpu_init(); + + /* LDI: runtime icvt is an implementation detail and must be flag-neutral. */ + I( &x, CPU_OP_LDI ); + x.operand[0] = R( 128, 0x000 ); + x.operand[1] = IMM( 42 ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "LDI exec" ); + check_after( "LDI", psw ); + + /* LDR: runtime real64 -> destination conversion is also flag-neutral. */ + { + __mpu_real64_t v = 2.0; + __mpu_uint64_t bits; + memcpy( &bits, &v, 8 ); + I( &x, CPU_OP_LDR ); + x.operand[0] = R( 128, 0x020 ); + x.operand[1] = IMM( bits ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "LDR exec" ); + check_after( "LDR", psw ); + } + + /* LDC: two real32 components are widened internally, but PSW is preserved. */ + { + __mpu_real32_t vr = 1.0f, vi = 2.0f; + __mpu_uint32_t br, bi; + __mpu_uint64_t payload; + memcpy( &br, &vr, 4 ); + memcpy( &bi, &vi, 4 ); + payload = ( (__mpu_uint64_t)br << 32 ) | bi; + I( &x, CPU_OP_LDC ); + x.operand[0] = R( 256, 0x040 ); + x.operand[1] = IMM( payload ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "LDC exec" ); + check_after( "LDC", psw ); + } + + /* Explicit integer conversion. */ + *P( 8, 0x080 ) = 0xff; + I( &x, CPU_OP_ICVT ); + x.operand[0] = R( 16, 0x082 ); + x.operand[1] = R( 8, 0x080 ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "ICVT exec" ); + check_after( "ICVT", psw ); + + /* Explicit Real conversion. */ + CHECK( ascii_to_real( ( mpu_real * ) P( 64, 0x090 ), (__mpu_char8_t *)"3.5", NB_R64 ) >= 0, "real source" ); + I( &x, CPU_OP_RCVT ); + x.operand[0] = R( 128, 0x0a0 ); + x.operand[1] = R( 64, 0x090 ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "RCVT exec" ); + check_after( "RCVT", psw ); + + /* Integer <-> Real conversions. */ + __cpu_data_put_u64( P( 64, 0x0c0 ), 42 ); + I( &x, CPU_OP_ITOR ); + x.operand[0] = R( 64, 0x0c8 ); + x.operand[1] = R( 64, 0x0c0 ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "ITOR exec" ); + check_after( "ITOR", psw ); + + __cpu_data_put_u64( P( 64, 0x0d0 ), 42 ); + I( &x, CPU_OP_UITOR ); + x.operand[0] = R( 128, 0x0e0 ); + x.operand[1] = R( 64, 0x0d0 ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "UITOR exec" ); + check_after( "UITOR", psw ); + + CHECK( ascii_to_real( ( mpu_real * ) P( 64, 0x100 ), (__mpu_char8_t *)"42.5", NB_R64 ) >= 0, "RTOI source" ); + I( &x, CPU_OP_RTOI ); + x.operand[0] = R( 64, 0x108 ); + x.operand[1] = R( 64, 0x110 ); + x.operand[2] = R( 64, 0x100 ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "RTOI exec" ); + check_after( "RTOI", psw ); + + CHECK( ascii_to_real( ( mpu_real * ) P( 64, 0x118 ), (__mpu_char8_t *)"42.5", NB_R64 ) >= 0, "RTOUI source" ); + I( &x, CPU_OP_RTOUI ); + x.operand[0] = R( 64, 0x120 ); + x.operand[1] = R( 64, 0x128 ); + x.operand[2] = R( 64, 0x118 ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "RTOUI exec" ); + check_after( "RTOUI", psw ); + + /* Complex conversion and Real -> Complex construction. */ + { + mpu_real64_t rr, ri; + CHECK( ascii_to_real( ( mpu_real * ) rr, (__mpu_char8_t *)"3.0", NB_R64 ) >= 0, "complex real" ); + CHECK( ascii_to_real( ( mpu_real * ) ri, (__mpu_char8_t *)"4.0", NB_R64 ) >= 0, "complex imag" ); + c_gen_complex( ( mpu_complex * ) P( 128, 0x140 ), ( mpu_real * ) rr, ( mpu_real * ) ri, NB_R64 ); + } + I( &x, CPU_OP_CCVT ); + x.operand[0] = R( 256, 0x160 ); + x.operand[1] = R( 128, 0x140 ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "CCVT exec" ); + check_after( "CCVT", psw ); + + CHECK( ascii_to_real( ( mpu_real * ) P( 64, 0x190 ), (__mpu_char8_t *)"5.0", NB_R64 ) >= 0, "CRTOC source" ); + I( &x, CPU_OP_CRTOC ); + x.operand[0] = R( 128, 0x1a0 ); + x.operand[1] = R( 64, 0x190 ); + psw = seed_psw(); + CHECK( __cpu_exec( &x ) == 0, "CRTOC exec" ); + check_after( "CRTOC", psw ); + + __cpu_free_context(); + if( failures ) + { + fprintf( stderr, "libcpu conversion flag regression: %d failure(s)\n", failures ); + return( 1 ); + } + puts( "libcpu conversion flag preservation regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-doc-version-policy.sh b/tests/tst-doc-version-policy.sh new file mode 100755 index 0000000..cd50995 --- /dev/null +++ b/tests/tst-doc-version-policy.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +set -e + +: "${TEST_SRCDIR:?}" + +TOP_SRCDIR=`cd "${TEST_SRCDIR}/.." && pwd` +README="${TOP_SRCDIR}/README.md" +MAN_DIR="${TOP_SRCDIR}/man" + +fail() +{ + echo "tst-doc-version-policy: $*" >&2 + exit 1 +} + +# README is a current-interface document, not a release changelog. Keep +# package release numbers in NEWS/ChangeLog and obtain the installed version +# from the tools themselves. +if grep -E 'LibMCPU [0-9]+\.[0-9]+\.[0-9]+|libmcpu-[0-9]+\.[0-9]+\.[0-9]+' \ + "${README}" >/dev/null ; then + fail "README.md contains a release-bound LibMCPU version string" +fi + +# Manual pages describe the interface contract. Their .TH source field is +# deliberately version-independent so a release bump cannot leave stale +# numbers behind. +while IFS= read -r page ; do + header=`sed -n '1p' "${page}"` + case "${header}" in + *'"LibMCPU"'*) + ;; + *) + fail "${page}: .TH source field is not version-independent" + ;; + esac + +done < <(find "${MAN_DIR}" -type f \( -name '*.1' -o -name '*.7' \) | sort) + +if grep -ER 'LibMCPU [0-9]+\.[0-9]+\.[0-9]+' "${MAN_DIR}" >/dev/null ; then + fail "manual page contains a release-bound LibMCPU version string" +fi + +exit 0 diff --git a/tests/tst-dup.c b/tests/tst-dup.c new file mode 100644 index 0000000..3941378 --- /dev/null +++ b/tests/tst-dup.c @@ -0,0 +1,187 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> + +#include <libmcpu.h> +#include <sys/asm/errno.h> +#include <sys/asm/fcntl.h> +#include <sys/asm/syscall.h> + +#include "mcpu-asm.h" + + +#define CHECK(cond,msg) \ + do { \ + if( !(cond) ) \ + { \ + fprintf( stderr, "FAIL: %s\n", (msg) ); \ + return( 1 ); \ + } \ + } while( 0 ) + + +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 __mpu_uint64_t +reg64( __mpu_uint32_t address ) +{ + return( __cpu_data_get_u64( __cpu_reg_ptr( 64, address ) ) ); +} + + +static int +run_syscall( __mpu_uint32_t nargs, __mpu_uint32_t sysno, + __mpu_uint64_t arg0, __mpu_uint64_t arg1, + __mpu_uint64_t arg2, __mpu_uint64_t *ret ) +{ + 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, arg1 ); + put64( CPU_SYSCALL_ARG2_ADDR, arg2 ); + + __cpu_pc = 0; + rc = __cpu_run(); + if( rc != CPU_EXEC_SUCCESS ) return( rc ); + + *ret = reg64( CPU_SYSCALL_RET_ADDR ); + return( 0 ); + +} /* End of run_syscall() */ + + +static __mpu_int64_t +signed_result( __mpu_uint64_t value ) +{ + __mpu_int64_t ret; + + memcpy( &ret, &value, sizeof( ret ) ); + return( ret ); + +} /* End of signed_result() */ + + +int +main( void ) +{ + static const char source[] = + ".text\n" + "SYSCALL\n" + "HALT\n"; + cpu_asm_image image; + char err[256]; + __mpu_uint64_t ret; + + memset( &image, 0, sizeof( image ) ); + + CHECK( __NR_dup == 0x126, "public __NR_dup ABI number" ); + CHECK( __NR_dup2 == 0x127, "public __NR_dup2 ABI number" ); + + __cpu_init(); + + CHECK( cpu_assemble_text( source, &image, err, sizeof( err ) ) == 0, err ); + CHECK( __cpu_program_set( image.code, image.code_size ) == 0, + "program load" ); + + /* dup() returns the lowest free guest descriptor. */ + CHECK( run_syscall( 1, __NR_dup, 1, 0, 0, &ret ) == 0, "dup stdout" ); + CHECK( ret == 3, "dup returns lowest free guest fd 3" ); + + /* Descriptor flags are not copied by dup(). */ + CHECK( run_syscall( 3, __NR_fcntl, 1, F_SETFD, FD_CLOEXEC, &ret ) == 0, + "set FD_CLOEXEC on source" ); + CHECK( ret == 0, "F_SETFD source result" ); + CHECK( run_syscall( 3, __NR_fcntl, 3, F_GETFD, 0, &ret ) == 0, + "F_GETFD duplicated descriptor" ); + CHECK( ret == 0, "dup clears descriptor flags" ); + + /* dup2() installs exactly the requested guest descriptor. */ + CHECK( run_syscall( 2, __NR_dup2, 1, 10, 0, &ret ) == 0, + "dup2 stdout to guest fd 10" ); + CHECK( ret == 10, "dup2 returns requested guest fd" ); + + CHECK( run_syscall( 3, __NR_fcntl, 10, F_SETFD, FD_CLOEXEC, &ret ) == 0, + "set FD_CLOEXEC on dup2 target" ); + CHECK( ret == 0, "set target flags" ); + + /* Replacing an occupied target clears descriptor flags. */ + CHECK( run_syscall( 2, __NR_dup2, 3, 10, 0, &ret ) == 0, + "dup2 replaces occupied target" ); + CHECK( ret == 10, "dup2 replacement return" ); + CHECK( run_syscall( 3, __NR_fcntl, 10, F_GETFD, 0, &ret ) == 0, + "F_GETFD replaced target" ); + CHECK( ret == 0, "dup2 replacement clears descriptor flags" ); + + /* oldfd == newfd is a no-op and preserves descriptor flags. */ + CHECK( run_syscall( 3, __NR_fcntl, 1, F_GETFD, 0, &ret ) == 0, + "F_GETFD source before dup2 self" ); + CHECK( ret == FD_CLOEXEC, "source flag before dup2 self" ); + CHECK( run_syscall( 2, __NR_dup2, 1, 1, 0, &ret ) == 0, + "dup2 oldfd equals newfd" ); + CHECK( ret == 1, "dup2 self return" ); + CHECK( run_syscall( 3, __NR_fcntl, 1, F_GETFD, 0, &ret ) == 0, + "F_GETFD source after dup2 self" ); + CHECK( ret == FD_CLOEXEC, "dup2 self preserves descriptor flags" ); + + /* Closing a borrowed standard guest fd does not close HOST stdio, and dup() + may subsequently reuse that lowest free guest slot. */ + CHECK( run_syscall( 1, __NR_close, 0, 0, 0, &ret ) == 0, + "close borrowed guest stdin" ); + CHECK( ret == 0, "close borrowed guest stdin result" ); + CHECK( run_syscall( 1, __NR_dup, 1, 0, 0, &ret ) == 0, + "dup reuses closed guest fd 0" ); + CHECK( ret == 0, "dup returns lowest free guest fd 0" ); + + /* Invalid source and target descriptors use the stable MCPU EBADF value. */ + CHECK( run_syscall( 1, __NR_dup, 255, 0, 0, &ret ) == 0, + "dup invalid source" ); + CHECK( signed_result( ret ) == -(__mpu_int64_t)EBADF, + "dup invalid source returns -EBADF" ); + + CHECK( run_syscall( 2, __NR_dup2, 1, 256, 0, &ret ) == 0, + "dup2 invalid target" ); + CHECK( signed_result( ret ) == -(__mpu_int64_t)EBADF, + "dup2 invalid target returns -EBADF" ); + + CHECK( run_syscall( 3, __NR_fcntl, 10, F_SETFD, FD_CLOEXEC, &ret ) == 0, + "set target flag before invalid-source dup2" ); + CHECK( run_syscall( 2, __NR_dup2, 255, 10, 0, &ret ) == 0, + "dup2 invalid source with occupied target" ); + CHECK( signed_result( ret ) == -(__mpu_int64_t)EBADF, + "dup2 invalid source returns -EBADF" ); + CHECK( run_syscall( 3, __NR_fcntl, 10, F_GETFD, 0, &ret ) == 0, + "target survives invalid-source dup2" ); + CHECK( ret == FD_CLOEXEC, + "invalid-source dup2 leaves target unchanged" ); + + CHECK( run_syscall( 1, __NR_close, 0, 0, 0, &ret ) == 0, + "close reused guest fd 0" ); + CHECK( run_syscall( 1, __NR_close, 3, 0, 0, &ret ) == 0, + "close dup guest fd 3" ); + CHECK( run_syscall( 1, __NR_close, 10, 0, 0, &ret ) == 0, + "close dup2 guest fd 10" ); + + cpu_asm_image_free( &image ); + __cpu_free_context(); + + puts( "libmcpu dup/dup2 regression: PASS" ); + return( 0 ); + +} /* End of main() */ diff --git a/tests/tst-dynamic-runner.sh b/tests/tst-dynamic-runner.sh new file mode 100755 index 0000000..2ebc76f --- /dev/null +++ b/tests/tst-dynamic-runner.sh @@ -0,0 +1,96 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +TMP=${TMPDIR:-/tmp}/libcpu-dynamic-$$ +mkdir -p "$TMP" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM +EXAMPLES=`cd "$TEST_SRCDIR/fixtures/shared-libs" && pwd` +"$CPU_AS" -o "$TMP/main.o" "$EXAMPLES/dynamic-main.s" +"$CPU_AS" -o "$TMP/a.o" "$EXAMPLES/dynamic-liba.s" +"$CPU_AS" -o "$TMP/b.o" "$EXAMPLES/dynamic-libb.s" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap -o "$TMP/main-program" "$TMP/main.o" +"$CPU_LD" --shared --soname=liba.so.1 -o "$TMP/liba.so" "$TMP/a.o" +"$CPU_LD" --shared --soname=libb.so.1 -o "$TMP/libb.so" "$TMP/b.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/main-program"`" = 41 ] +[ "`"$CPU_RUN" --int-reg=r64@0x00018 --ldpreload="$TMP/liba.so" "$TMP/main-program"`" = 42 ] +[ "`"$CPU_RUN" --int-reg=r64@0x00018 --ldpreload="$TMP/liba.so:$TMP/libb.so" "$TMP/main-program"`" = 43 ] +[ "`"$CPU_RUN" --int-reg=r64@0x00018 --ldpreload="$TMP/libb.so:$TMP/liba.so" "$TMP/main-program"`" = 42 ] + +# LOCAL references inside ET_DYN are private to that module, but their +# section-relative values still need startup rebasing by the module base. +# local_helper is deliberately not exported. Without LOCAL ET_DYN runtime +# relocation the CALL below contains the shared-object-relative offset and +# jumps to the wrong process PROGRAM address after the library is placed +# behind the executable. +cat > "$TMP/local-main.s" <<'EOS' +.global _life +.extern local_api +.text +_start_life: + LDI r64@0x00000, 40 + CALL local_api + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +cat > "$TMP/local-lib.s" <<'EOS' +.global local_api +.text +local_api: + CALL local_helper + RET +local_helper: + LDI r64@0x00008, 7 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +"$CPU_AS" -o "$TMP/local-main.o" "$TMP/local-main.s" +"$CPU_AS" -o "$TMP/local-lib.o" "$TMP/local-lib.s" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap -o "$TMP/local-program" "$TMP/local-main.o" +"$CPU_LD" --shared --soname=local.so.1 -o "$TMP/local.so" "$TMP/local-lib.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 --ldpreload="$TMP/local.so" "$TMP/local-program"`" = 47 ] + +# ABI-v1 static executable must reject preload rather than silently relink. +"$CPU_LD" --stack-size=4096 --no-heap -o "$TMP/static-program" "$TMP/main.o" +if "$CPU_RUN" --ldpreload="$TMP/liba.so" "$TMP/static-program" >/dev/null 2>&1; then + echo "ABI-v1 executable unexpectedly accepted --ldpreload" >&2 + exit 1 +fi + +# An ABI-v3 executable with an unresolved import but no DT_NEEDED provider +# remains a startup load error. Recursive loading cannot invent dependencies. +cat > "$TMP/missing.s" <<'EOS' +.global _life +.extern missing_function +.text +_start_life: + CALL missing_function + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +"$CPU_AS" -o "$TMP/missing.o" "$TMP/missing.s" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap -o "$TMP/missing-program" "$TMP/missing.o" +if "$CPU_RUN" "$TMP/missing-program" >/dev/null 2>&1; then + echo "unresolved dynamic symbol unexpectedly reached execution" >&2 + exit 1 +fi + +# Shared objects own no process STACK/HEAP policy. +if "$CPU_LD" --shared --soname=bad.so.1 --stack-size=4096 -o "$TMP/bad.so" "$TMP/a.o" >/dev/null 2>&1; then + echo "cpu-ld --shared unexpectedly accepted --stack-size" >&2 + exit 1 +fi +if "$CPU_LD" --shared -o "$TMP/no-soname.so" "$TMP/a.o" >/dev/null 2>&1; then + echo "ABI-v3 shared object unexpectedly accepted without SONAME" >&2 + exit 1 +fi +if "$CPU_LD" --shared --soname=path/bad.so.1 -o "$TMP/bad-soname.so" "$TMP/a.o" >/dev/null 2>&1; then + echo "ABI-v3 shared object unexpectedly accepted pathname SONAME" >&2 + exit 1 +fi +if "$CPU_LD" --dynamic --soname=main.so.1 --stack-size=4096 --no-heap \ + -o "$TMP/bad-main" "$TMP/main.o" >/dev/null 2>&1; then + echo "ABI-v3 executable unexpectedly accepted SONAME" >&2 + exit 1 +fi +exit 0 diff --git a/tests/tst-dynamic.c b/tests/tst-dynamic.c new file mode 100644 index 0000000..a70d9e1 --- /dev/null +++ b/tests/tst-dynamic.c @@ -0,0 +1,407 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <libmcpu.h> + +#include "mcpu-asm.h" +#include "mcpu-link.h" +#include "mcpu-dyn.h" + +static int +add_symbol( cpu_asm_object *o, const char *name, unsigned sec, unsigned bind, unsigned type, __mpu_uint64_t value ) +{ + cpu_asm_symbol *q = (cpu_asm_symbol *)realloc( o->symbols, ( o->symbol_count + 1 ) * sizeof( *q ) ); + if( !q ) return( -1 ); + o->symbols = q; + memset( &o->symbols[o->symbol_count], 0, sizeof( *q ) ); + o->symbols[o->symbol_count].name = strdup( name ); + if( !o->symbols[o->symbol_count].name ) return( -1 ); + o->symbols[o->symbol_count].section = sec; + o->symbols[o->symbol_count].binding = bind; + o->symbols[o->symbol_count].type = type; + o->symbols[o->symbol_count].value = value; + ++o->symbol_count; + return( 0 ); +} + +static int +add_reloc( cpu_asm_object *o, unsigned sec, unsigned type, size_t sym, __mpu_uint64_t off ) +{ + cpu_asm_reloc *q = (cpu_asm_reloc *)realloc( o->relocs, ( o->reloc_count + 1 ) * sizeof( *q ) ); + if( !q ) return( -1 ); + o->relocs = q; + o->relocs[o->reloc_count] = ( cpu_asm_reloc ) + { + sec, type, sym, off, 0 + }; + ++o->reloc_count; + return( 0 ); +} + +static void +init_text( cpu_asm_object *o, size_t n ) +{ + memset( o, 0, sizeof( *o ) ); + o->text = (__mpu_UNIT_t *)calloc( n, 1 ); + o->text_size = n; + o->text_align = 1; + o->has_text = 1; +} + +static int +tempname( char *buf, size_t n, const char *tag ) +{ + int fd; + if( snprintf( buf, n, "/tmp/libcpu-%s-XXXXXX", tag ) < 0 ) return( -1 ); + fd = mkstemp( buf ); + if( fd < 0 ) return( -1 ); + close( fd ); + return( 0 ); +} + +int +main( void ) +{ + cpu_asm_object mo = + { + 0 + } + , ao = + { + 0 + } + , bo = + { + 0 + } + , wo = + { + 0 + } + , co = + { + 0 + }; + cpu_asm_image mi = + { + 0 + } + , ai = + { + 0 + } + , bi = + { + 0 + } + , wi = + { + 0 + } + , ci = + { + 0 + } + , si = + { + 0 + }; + cpu_exec_options opt = + { + 256, 0, 0, 0 + }; + cpu_exec_loaded ld; + const char *needed_a[] = + { + "liba.so.1" + }; + cpu_dyn_metadata v3_main = + { + 0, 0, 0 + }; + cpu_dyn_metadata v3_liba = + { + "liba.so.1", 0, 0 + }; + cpu_dyn_metadata v3_needed = + { + 0, needed_a, 1 + }; + char me[128], ae[128], be[128], we[128], ce[128]; + char v3me[128], v3ae[128], v3ne[128], pre[520], err[512]; + char *av[] = + { + "main-program" + }; + int rc = 1; + __mpu_init(); + init_text( &mo, 24 ); + init_text( &ao, 16 ); + init_text( &bo, 16 ); + init_text( &wo, 16 ); + init_text( &co, 16 ); + ao.data = (__mpu_UNIT_t *)calloc( 16, 1 ); + ao.data_size = 16; + ao.data_align = 8; + ao.has_data = 1; + if( !mo.text || !ao.text || !ao.data || !bo.text || !wo.text || !co.text ) goto out; + if( add_symbol( &mo, "_start", CPU_OBJ_SEC_TEXT, CPU_OBJ_BIND_GLOBAL, CPU_OBJ_TYPE_FUNC, 0 ) || + add_symbol( &mo, "_life", CPU_OBJ_SEC_TEXT, CPU_OBJ_BIND_GLOBAL, CPU_OBJ_TYPE_FUNC, 0 ) || + add_symbol( &mo, "foo", CPU_OBJ_SEC_TEXT, CPU_OBJ_BIND_GLOBAL, CPU_OBJ_TYPE_FUNC, 16 ) || + add_symbol( &mo, "shared_data", CPU_OBJ_SEC_UNDEF, CPU_OBJ_BIND_WEAK, CPU_OBJ_TYPE_OBJECT, 0 ) || + add_reloc( &mo, CPU_OBJ_SEC_TEXT, CPU_RELOC_PTR64_CODE, 3, 0 ) || + add_reloc( &mo, CPU_OBJ_SEC_TEXT, CPU_RELOC_CODE64, 2, 8 ) || + add_symbol( &ao, "foo", CPU_OBJ_SEC_TEXT, CPU_OBJ_BIND_GLOBAL, CPU_OBJ_TYPE_FUNC, 4 ) || + add_symbol( &ao, "shared_data", CPU_OBJ_SEC_DATA, CPU_OBJ_BIND_GLOBAL, CPU_OBJ_TYPE_OBJECT, 0 ) || + add_symbol( &ao, "local_code", CPU_OBJ_SEC_TEXT, CPU_OBJ_BIND_LOCAL, CPU_OBJ_TYPE_FUNC, 12 ) || + add_reloc( &ao, CPU_OBJ_SEC_TEXT, CPU_RELOC_CODE64, 2, 0 ) || + add_symbol( &ao, "local_data", CPU_OBJ_SEC_DATA, CPU_OBJ_BIND_LOCAL, CPU_OBJ_TYPE_OBJECT, 8 ) || + add_reloc( &ao, CPU_OBJ_SEC_DATA, CPU_RELOC_PTR64_DATA, 3, 0 ) || + add_symbol( &bo, "foo", CPU_OBJ_SEC_TEXT, CPU_OBJ_BIND_GLOBAL, CPU_OBJ_TYPE_FUNC, 6 ) || + add_symbol( &bo, "helper", CPU_OBJ_SEC_TEXT, CPU_OBJ_BIND_GLOBAL, CPU_OBJ_TYPE_FUNC, 10 ) || + add_symbol( &wo, "foo", CPU_OBJ_SEC_TEXT, CPU_OBJ_BIND_WEAK, CPU_OBJ_TYPE_FUNC, 8 ) || + add_symbol( &co, "helper", CPU_OBJ_SEC_UNDEF, CPU_OBJ_BIND_GLOBAL, CPU_OBJ_TYPE_FUNC, 0 ) || + add_reloc( &co, CPU_OBJ_SEC_TEXT, CPU_RELOC_CODE64, 0, 0 )) goto out; + if( cpu_link_objects( &mo, 1, &si, err, sizeof( err ) ) ) + { + fprintf( stderr, "static compatibility link: %s\n", err ); + goto out; + } + if( si.dyn_symbol_count || si.dyn_reloc_count || __cpu_code_get_u64( si.code ) != 0 || __cpu_code_get_u64( si.code + 8 ) != 16 ) + { + fprintf( stderr, "ABI-v1 static link compatibility mismatch\n" ); + goto out; + } + if( cpu_link_objects_mode( &mo, 1, &mi, CPU_LINK_DYNAMIC_EXEC, err, sizeof( err ) ) ) + { + fprintf( stderr, "main link: %s\n", err ); + goto out; + } + if( cpu_link_objects_mode( &ao, 1, &ai, CPU_LINK_SHARED, err, sizeof( err ) ) ) + { + fprintf( stderr, "a link: %s\n", err ); + goto out; + } + if( cpu_link_objects_mode( &bo, 1, &bi, CPU_LINK_SHARED, err, sizeof( err ) ) ) + { + fprintf( stderr, "b link: %s\n", err ); + goto out; + } + if( cpu_link_objects_mode( &wo, 1, &wi, CPU_LINK_SHARED, err, sizeof( err ) ) ) + { + fprintf( stderr, "weak link: %s\n", err ); + goto out; + } + if( cpu_link_objects_mode( &co, 1, &ci, CPU_LINK_SHARED, err, sizeof( err ) ) ) + { + fprintf( stderr, "cross link: %s\n", err ); + goto out; + } + if( mi.dyn_reloc_count != 2 || mi.dyn_symbol_count != 2 ) + { + fprintf( stderr, "dynamic metadata count mismatch\n" ); + goto out; + } + if( ai.dyn_reloc_count != 2 || ai.dyn_symbol_count != 4 ) + { + fprintf( stderr, "LOCAL ET_DYN metadata count mismatch\n" ); + goto out; + } + if( tempname( me, sizeof( me ), "main" ) || + tempname( ae, sizeof( ae ), "a" ) || + tempname( be, sizeof( be ), "b" ) || + tempname( we, sizeof( we ), "weak" ) || + tempname( ce, sizeof( ce ), "cross" ) || + tempname( v3me, sizeof( v3me ), "v3-main" ) || + tempname( v3ae, sizeof( v3ae ), "v3-a" ) || + tempname( v3ne, sizeof( v3ne ), "v3-needed" ) ) goto out; + if( cpu_dyn_write( me, &mi, &opt, 0, err, sizeof( err ) ) || + cpu_dyn_write( ae, &ai, 0, 1, err, sizeof( err ) ) || + cpu_dyn_write( be, &bi, 0, 1, err, sizeof( err ) ) || + cpu_dyn_write( we, &wi, 0, 1, err, sizeof( err ) ) || + cpu_dyn_write( ce, &ci, 0, 1, err, sizeof( err ) ) ) + { + fprintf( stderr, "write ABI-v2: %s\n", err ); + goto files; + } + if( cpu_dyn_write_abi3(v3me, &mi, &opt, 0, &v3_main, + err, sizeof( err )) || + cpu_dyn_write_abi3(v3ae, &ai, 0, 1, &v3_liba, + err, sizeof( err )) || + cpu_dyn_write_abi3(v3ne, &mi, &opt, 0, &v3_needed, + err, sizeof( err )) ) + { + fprintf( stderr, "write ABI-v3: %s\n", err ); + goto files; + } + __cpu_init(); + if( cpu_dyn_load( me, NULL, 1, av, &ld, err, sizeof( err ) ) ) + { + fprintf( stderr, "load main: %s\n", err ); + __cpu_free_context(); + goto files; + } + if( __cpu_code_get_u64( __cpu_program_ptr() + 8 ) != 16 || __cpu_code_get_u64( __cpu_program_ptr() ) != 0 ) + { + fprintf( stderr, "main/weak definition mismatch\n" ); + __cpu_free_context(); + goto files; + } + __cpu_free_context(); + + snprintf( pre, sizeof( pre ), "%s:%s", ae, be ); + __cpu_init(); + if( cpu_dyn_load( me, pre, 1, av, &ld, err, sizeof( err ) ) ) + { + fprintf( stderr, "load: %s\n", err ); + __cpu_free_context(); + goto files; + } + /* main PROGRAM=24, a=16, b=16; later strong b wins: 24+16+6 = 46. */ + if( __cpu_code_get_u64( __cpu_program_ptr() + 8 ) != 46 ) + { + fprintf( stderr, "interposition mismatch: got %llu\n", (unsigned long long)__cpu_code_get_u64( __cpu_program_ptr() + 8 ) ); + __cpu_free_context(); + goto files; + } + /* main has no DATA; loader creates the single process guard and places + liba DATA at offset 16. ET_DYN itself contains no per-library guard. */ + if( __cpu_code_get_u64( __cpu_program_ptr() ) != 16 ) + { + fprintf( stderr, "shared DATA base/guard mismatch\n" ); + __cpu_free_context(); + goto files; + } + /* LOCAL ET_DYN references are not interposable, but they still require + startup rebasing by the final module PROGRAM/DATA base. */ + if( __cpu_code_get_u64( __cpu_program_ptr() + 24 ) != 36 ) + { + fprintf( stderr, "LOCAL ET_DYN CODE64 rebase mismatch\n" ); + __cpu_free_context(); + goto files; + } + { + __mpu_uint64_t p = 0; + if( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, 24 ) || __cpu_data_get_u64( __cpu_data_ptr() + 16 ) != p ) + { + fprintf( stderr, "LOCAL ET_DYN PTR64_DATA rebase mismatch\n" ); + __cpu_free_context(); + goto files; + } + } + __cpu_free_context(); + + /* A shared object can import from another explicitly preloaded object + without DT_NEEDED. cross is placed at 24, b at 40, helper at 40+10. */ + snprintf( pre, sizeof( pre ), "%s:%s", ce, be ); + __cpu_init(); + if( cpu_dyn_load( me, pre, 1, av, &ld, err, sizeof( err ) ) ) + { + fprintf( stderr, "cross-library load: %s\n", err ); + __cpu_free_context(); + goto files; + } + if( __cpu_code_get_u64( __cpu_program_ptr() + 24 ) != 50 ) + { + fprintf( stderr, "cross-library explicit-preload resolution mismatch\n" ); + __cpu_free_context(); + goto files; + } + __cpu_free_context(); + + /* A later WEAK never preempts an earlier strong GLOBAL, including main. */ + __cpu_init(); + if( cpu_dyn_load( me, we, 1, av, &ld, err, sizeof( err ) ) ) + { + fprintf( stderr, "weak load: %s\n", err ); + __cpu_free_context(); + goto files; + } + if( __cpu_code_get_u64( __cpu_program_ptr() + 8 ) != 16 ) + { + fprintf( stderr, "weak incorrectly preempted main GLOBAL\n" ); + __cpu_free_context(); + goto files; + } + __cpu_free_context(); + + /* Strong library a beats later weak: a base=24, foo=4 => 28. */ + snprintf( pre, sizeof( pre ), "%s:%s", ae, we ); + __cpu_init(); + if( cpu_dyn_load( me, pre, 1, av, &ld, err, sizeof( err ) ) ) + { + fprintf( stderr, "strong/weak load: %s\n", err ); + __cpu_free_context(); + goto files; + } + if( __cpu_code_get_u64( __cpu_program_ptr() + 8 ) != 28 ) + { + fprintf( stderr, "strong/weak binding mismatch\n" ); + __cpu_free_context(); + goto files; + } + __cpu_free_context(); + + /* ABI-v3 without NEEDED keeps the established startup-relocation + semantics and can still use the explicit preload compatibility path. */ + __cpu_init(); + if( cpu_dyn_load( v3me, v3ae, 1, av, &ld, err, sizeof( err ) ) ) + { + fprintf( stderr, "ABI-v3 explicit-preload load: %s\n", err ); + __cpu_free_context(); + goto files; + } + if( __cpu_code_get_u64( __cpu_program_ptr() + 8 ) != 28 ) + { + fprintf( stderr, "ABI-v3 explicit-preload binding mismatch\n" ); + __cpu_free_context(); + goto files; + } + __cpu_free_context(); + + /* The low-level loader still refuses to ignore unresolved DT_NEEDED. + mcpu-run first resolves the graph and then uses cpu_dyn_load_resolved(). */ + __cpu_init(); + if( cpu_dyn_load( v3ne, NULL, 1, av, &ld, err, sizeof( err ) ) == 0 ) + { + fprintf( stderr, "low-level ABI-v3 DT_NEEDED unexpectedly ignored\n" ); + __cpu_free_context(); + goto files; + } + if( strstr( err, "DT_NEEDED loading is not implemented yet" ) == NULL ) + { + fprintf( stderr, "unexpected ABI-v3 DT_NEEDED diagnostic: %s\n", err ); + __cpu_free_context(); + goto files; + } + __cpu_free_context(); + + /* A static ABI-v1 executable must not silently accept preload; this is + checked by cpu_exec_load_preload runner tests. Here verify no missing + explicit library dependency machinery is needed: the list alone resolves. */ + printf( "libcpu ABI-v2 compatibility / ABI-v3 metadata regression: PASS\n" ); + rc = 0; +files: unlink( me ); +unlink( ae ); +unlink( be ); +unlink( we ); +unlink( ce ); +unlink( v3me ); +unlink( v3ae ); +unlink( v3ne ); +out: cpu_asm_image_free( &si ); +cpu_asm_image_free( &mi ); +cpu_asm_image_free( &ai ); +cpu_asm_image_free( &bi ); +cpu_asm_image_free( &wi ); +cpu_asm_image_free( &ci ); +cpu_asm_object_free( &mo ); +cpu_asm_object_free( &ao ); +cpu_asm_object_free( &bo ); +cpu_asm_object_free( &wo ); +cpu_asm_object_free( &co ); +__mpu_free_context(); +return( rc ); +} diff --git a/tests/tst-env-preload-runner.sh b/tests/tst-env-preload-runner.sh new file mode 100755 index 0000000..5f49e9b --- /dev/null +++ b/tests/tst-env-preload-runner.sh @@ -0,0 +1,124 @@ +#!/bin/sh +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-env-preload-$$ +mkdir -p "$TMP/lib" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +cat > "$TMP/main.s" <<'EOS' +.global _life +.global foo +.text +_start_life: + LDI r64@0x00000, 40 + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +foo: + LDI r64@0x00008, 1 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +cat > "$TMP/a.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +cat > "$TMP/b.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +"$CPU_AS" -o "$TMP/main.o" "$TMP/main.s" +"$CPU_AS" -o "$TMP/a.o" "$TMP/a.s" +"$CPU_AS" -o "$TMP/b.o" "$TMP/b.s" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap -o "$TMP/main" "$TMP/main.o" +"$CPU_LD" --shared --soname=liba.so.1 -o "$TMP/lib/liba.so.1" "$TMP/a.o" +"$CPU_LD" --shared --soname=libb.so.1 -o "$TMP/lib/libb.so.1" "$TMP/b.o" +ln -s liba.so.1 "$TMP/lib/liba.so" +ln -s libb.so.1 "$TMP/lib/libb.so" + +# Empty environment is the normal no-preload baseline. +[ "`MCPU_LD_PRELOAD= "$CPU_RUN" --library-path="$TMP/lib" \ + --int-reg=r64@0x00018 "$TMP/main"`" = 41 ] + +# Environment-only preload uses the same resolver and rightmost-wins ordering. +[ "`MCPU_LD_PRELOAD=liba.so "$CPU_RUN" --library-path="$TMP/lib" \ + --int-reg=r64@0x00018 "$TMP/main"`" = 42 ] +[ "`MCPU_LD_PRELOAD=liba.so:libb.so "$CPU_RUN" --library-path="$TMP/lib" \ + --int-reg=r64@0x00018 "$TMP/main"`" = 43 ] +[ "`MCPU_LD_PRELOAD=libb.so:liba.so "$CPU_RUN" --library-path="$TMP/lib" \ + --int-reg=r64@0x00018 "$TMP/main"`" = 42 ] + +# Command-line preload entries are appended after environment entries, so the +# established rightmost-higher-priority rule makes command line stronger. +[ "`MCPU_LD_PRELOAD=liba.so "$CPU_RUN" --library-path="$TMP/lib" \ + --ldpreload=libb.so --int-reg=r64@0x00018 "$TMP/main"`" = 43 ] +[ "`MCPU_LD_PRELOAD=libb.so "$CPU_RUN" --library-path="$TMP/lib" \ + --ldpreload=liba.so --int-reg=r64@0x00018 "$TMP/main"`" = 42 ] +[ "`MCPU_LD_PRELOAD=liba.so:libb.so "$CPU_RUN" --library-path="$TMP/lib" \ + --ldpreload=libb.so:liba.so --int-reg=r64@0x00018 "$TMP/main"`" = 42 ] + +# Environment preload is equivalent to --ldpreload for ABI compatibility: an +# ABI-v1 static executable must reject it just as it rejects the CLI option. +"$CPU_LD" --stack-size=4096 --no-heap -o "$TMP/static" "$TMP/main.o" +if MCPU_LD_PRELOAD="$TMP/lib/liba.so.1" "$CPU_RUN" "$TMP/static" \ + >/dev/null 2>"$TMP/static.err"; then + echo "ABI-v1 executable unexpectedly accepted MCPU_LD_PRELOAD" >&2 + exit 1 +fi +grep -q 'dynamic modules require' "$TMP/static.err" + +# The environment preload is independent of configuration suppression. An +# explicit pathname therefore still works under --no-config. +[ "`MCPU_LD_PRELOAD="$TMP/lib/liba.so.1" "$CPU_RUN" --no-config \ + --int-reg=r64@0x00018 "$TMP/main"`" = 42 ] + +# Environment preload remains an explicit override above a normal dependency. +cat > "$TMP/import.s" <<'EOS' +.global _life +.extern foo +.text +_start_life: + LDI r64@0x00000, 40 + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +"$CPU_AS" -o "$TMP/import.o" "$TMP/import.s" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap -L"$TMP/lib" -la \ + -o "$TMP/import" "$TMP/import.o" +[ "`MCPU_LD_PRELOAD=libb.so "$CPU_RUN" --library-path="$TMP/lib" \ + --int-reg=r64@0x00018 "$TMP/import"`" = 43 ] + +# Invalid environment lists and missing names are hard errors. +if MCPU_LD_PRELOAD=liba.so::libb.so "$CPU_RUN" --library-path="$TMP/lib" \ + "$TMP/main" >/dev/null 2>"$TMP/bad-list.err"; then + echo "malformed MCPU_LD_PRELOAD unexpectedly accepted" >&2 + exit 1 +fi +grep -q 'MCPU_LD_PRELOAD' "$TMP/bad-list.err" + +if MCPU_LD_PRELOAD=does-not-exist.so "$CPU_RUN" --library-path="$TMP/lib" \ + "$TMP/main" >/dev/null 2>"$TMP/missing.err"; then + echo "missing MCPU_LD_PRELOAD library unexpectedly accepted" >&2 + exit 1 +fi +grep -q 'MCPU_LD_PRELOAD' "$TMP/missing.err" + +MCPU_LD_PRELOAD= "$CPU_RUN" --help 2>&1 | grep -q 'MCPU_LD_PRELOAD' + +exit 0 diff --git a/tests/tst-errno-abi.c b/tests/tst-errno-abi.c new file mode 100644 index 0000000..1d7dcd5 --- /dev/null +++ b/tests/tst-errno-abi.c @@ -0,0 +1,217 @@ +#include <stdio.h> + +#include <libmpu.h> + +/* Capture the LibMPU values before the public assembler header is included. */ +enum +{ + LIBMPU_EUNKNOWN = __EUNKNOWN__, + LIBMPU_I_ESIZE = __I_ESIZE__, + LIBMPU_I_ESHIFT = __I_ESHIFT__, + LIBMPU_I_ENUMBER = __I_ENUMBER__, + LIBMPU_I_ERADIX = __I_ERADIX__, + LIBMPU_I_MAX_ERRNO = __I_MAX_ERRNO, + LIBMPU_R_ESIZE = __R_ESIZE__, + LIBMPU_R_ETRUNC = __R_ETRUNC__, + LIBMPU_R_MAX_ERRNO = __R_MAX_ERRNO, + LIBMPU_C_EDIVZEROBYZERO = __C_EDIVZEROBYZERO__, + LIBMPU_C_ELOGOFZERO = __C_ELOGOFZERO__, + LIBMPU_C_EPOWOFZERO = __C_EPOWOFZERO__, + LIBMPU_C_MAX_ERRNO = __C_MAX_ERRNO, + LIBMPU_DOMAIN = __DOMAIN__, + LIBMPU_SING = __SING__, + LIBMPU_OVERFLOW = __OVERFLOW__, + LIBMPU_UNDERFLOW = __UNDERFLOW__, + LIBMPU_TLOSS = __TLOSS__, + LIBMPU_PLOSS = __PLOSS__, + LIBMPU_INVALID = __INVALID__, + LIBMPU_INEXACT = __INEXACT__, + LIBMPU_M_MAX_ERRNO = __M_MAX_ERRNO +}; + +#include "../cpu/cpu-errno.h" +#include <sys/asm/errno.h> +#include <sys/asm/mpu-errno.h> + + +#define CHECK_SYSTEM_ERRNO(name) \ + do { \ + if( (name) != (__MCPU_ ## name) ) \ + { \ + fprintf( stderr, #name " ABI mismatch: public=%d internal=%d\n", \ + (int)(name), (int)(__MCPU_ ## name) ); \ + return( 1 ); \ + } \ + } while( 0 ) + + +int +main( void ) +{ + CHECK_SYSTEM_ERRNO( EPERM ); + CHECK_SYSTEM_ERRNO( ENOENT ); + CHECK_SYSTEM_ERRNO( ESRCH ); + CHECK_SYSTEM_ERRNO( EINTR ); + CHECK_SYSTEM_ERRNO( EIO ); + CHECK_SYSTEM_ERRNO( ENXIO ); + CHECK_SYSTEM_ERRNO( E2BIG ); + CHECK_SYSTEM_ERRNO( ENOEXEC ); + CHECK_SYSTEM_ERRNO( EBADF ); + CHECK_SYSTEM_ERRNO( ECHILD ); + CHECK_SYSTEM_ERRNO( EAGAIN ); + CHECK_SYSTEM_ERRNO( ENOMEM ); + CHECK_SYSTEM_ERRNO( EACCES ); + CHECK_SYSTEM_ERRNO( EFAULT ); + CHECK_SYSTEM_ERRNO( ENOTBLK ); + CHECK_SYSTEM_ERRNO( EBUSY ); + CHECK_SYSTEM_ERRNO( EEXIST ); + CHECK_SYSTEM_ERRNO( EXDEV ); + CHECK_SYSTEM_ERRNO( ENODEV ); + CHECK_SYSTEM_ERRNO( ENOTDIR ); + CHECK_SYSTEM_ERRNO( EISDIR ); + CHECK_SYSTEM_ERRNO( EINVAL ); + CHECK_SYSTEM_ERRNO( ENFILE ); + CHECK_SYSTEM_ERRNO( EMFILE ); + CHECK_SYSTEM_ERRNO( ENOTTY ); + CHECK_SYSTEM_ERRNO( ETXTBSY ); + CHECK_SYSTEM_ERRNO( EFBIG ); + CHECK_SYSTEM_ERRNO( ENOSPC ); + CHECK_SYSTEM_ERRNO( ESPIPE ); + CHECK_SYSTEM_ERRNO( EROFS ); + CHECK_SYSTEM_ERRNO( EMLINK ); + CHECK_SYSTEM_ERRNO( EPIPE ); + CHECK_SYSTEM_ERRNO( EDOM ); + CHECK_SYSTEM_ERRNO( ERANGE ); + CHECK_SYSTEM_ERRNO( EDEADLK ); + CHECK_SYSTEM_ERRNO( ENAMETOOLONG ); + CHECK_SYSTEM_ERRNO( ENOLCK ); + CHECK_SYSTEM_ERRNO( ENOSYS ); + CHECK_SYSTEM_ERRNO( ENOTEMPTY ); + CHECK_SYSTEM_ERRNO( ELOOP ); + CHECK_SYSTEM_ERRNO( ENOMSG ); + CHECK_SYSTEM_ERRNO( EIDRM ); + CHECK_SYSTEM_ERRNO( ECHRNG ); + CHECK_SYSTEM_ERRNO( EL2NSYNC ); + CHECK_SYSTEM_ERRNO( EL3HLT ); + CHECK_SYSTEM_ERRNO( EL3RST ); + CHECK_SYSTEM_ERRNO( ELNRNG ); + CHECK_SYSTEM_ERRNO( EUNATCH ); + CHECK_SYSTEM_ERRNO( ENOCSI ); + CHECK_SYSTEM_ERRNO( EL2HLT ); + CHECK_SYSTEM_ERRNO( EBADE ); + CHECK_SYSTEM_ERRNO( EBADR ); + CHECK_SYSTEM_ERRNO( EXFULL ); + CHECK_SYSTEM_ERRNO( ENOANO ); + CHECK_SYSTEM_ERRNO( EBADRQC ); + CHECK_SYSTEM_ERRNO( EBADSLT ); + CHECK_SYSTEM_ERRNO( EBFONT ); + CHECK_SYSTEM_ERRNO( ENOSTR ); + CHECK_SYSTEM_ERRNO( ENODATA ); + CHECK_SYSTEM_ERRNO( ETIME ); + CHECK_SYSTEM_ERRNO( ENOSR ); + CHECK_SYSTEM_ERRNO( ENONET ); + CHECK_SYSTEM_ERRNO( ENOPKG ); + CHECK_SYSTEM_ERRNO( EREMOTE ); + CHECK_SYSTEM_ERRNO( ENOLINK ); + CHECK_SYSTEM_ERRNO( EADV ); + CHECK_SYSTEM_ERRNO( ESRMNT ); + CHECK_SYSTEM_ERRNO( ECOMM ); + CHECK_SYSTEM_ERRNO( EPROTO ); + CHECK_SYSTEM_ERRNO( EMULTIHOP ); + CHECK_SYSTEM_ERRNO( EDOTDOT ); + CHECK_SYSTEM_ERRNO( EBADMSG ); + CHECK_SYSTEM_ERRNO( EOVERFLOW ); + CHECK_SYSTEM_ERRNO( ENOTUNIQ ); + CHECK_SYSTEM_ERRNO( EBADFD ); + CHECK_SYSTEM_ERRNO( EREMCHG ); + CHECK_SYSTEM_ERRNO( ELIBACC ); + CHECK_SYSTEM_ERRNO( ELIBBAD ); + CHECK_SYSTEM_ERRNO( ELIBSCN ); + CHECK_SYSTEM_ERRNO( ELIBMAX ); + CHECK_SYSTEM_ERRNO( ELIBEXEC ); + CHECK_SYSTEM_ERRNO( EILSEQ ); + CHECK_SYSTEM_ERRNO( ERESTART ); + CHECK_SYSTEM_ERRNO( ESTRPIPE ); + CHECK_SYSTEM_ERRNO( EUSERS ); + CHECK_SYSTEM_ERRNO( ENOTSOCK ); + CHECK_SYSTEM_ERRNO( EDESTADDRREQ ); + CHECK_SYSTEM_ERRNO( EMSGSIZE ); + CHECK_SYSTEM_ERRNO( EPROTOTYPE ); + CHECK_SYSTEM_ERRNO( ENOPROTOOPT ); + CHECK_SYSTEM_ERRNO( EPROTONOSUPPORT ); + CHECK_SYSTEM_ERRNO( ESOCKTNOSUPPORT ); + CHECK_SYSTEM_ERRNO( EOPNOTSUPP ); + CHECK_SYSTEM_ERRNO( EPFNOSUPPORT ); + CHECK_SYSTEM_ERRNO( EAFNOSUPPORT ); + CHECK_SYSTEM_ERRNO( EADDRINUSE ); + CHECK_SYSTEM_ERRNO( EADDRNOTAVAIL ); + CHECK_SYSTEM_ERRNO( ENETDOWN ); + CHECK_SYSTEM_ERRNO( ENETUNREACH ); + CHECK_SYSTEM_ERRNO( ENETRESET ); + CHECK_SYSTEM_ERRNO( ECONNABORTED ); + CHECK_SYSTEM_ERRNO( ECONNRESET ); + CHECK_SYSTEM_ERRNO( ENOBUFS ); + CHECK_SYSTEM_ERRNO( EISCONN ); + CHECK_SYSTEM_ERRNO( ENOTCONN ); + CHECK_SYSTEM_ERRNO( ESHUTDOWN ); + CHECK_SYSTEM_ERRNO( ETOOMANYREFS ); + CHECK_SYSTEM_ERRNO( ETIMEDOUT ); + CHECK_SYSTEM_ERRNO( ECONNREFUSED ); + CHECK_SYSTEM_ERRNO( EHOSTDOWN ); + CHECK_SYSTEM_ERRNO( EHOSTUNREACH ); + CHECK_SYSTEM_ERRNO( EALREADY ); + CHECK_SYSTEM_ERRNO( EINPROGRESS ); + CHECK_SYSTEM_ERRNO( ESTALE ); + CHECK_SYSTEM_ERRNO( EUCLEAN ); + CHECK_SYSTEM_ERRNO( ENOTNAM ); + CHECK_SYSTEM_ERRNO( ENAVAIL ); + CHECK_SYSTEM_ERRNO( EISNAM ); + CHECK_SYSTEM_ERRNO( EREMOTEIO ); + CHECK_SYSTEM_ERRNO( EDQUOT ); + CHECK_SYSTEM_ERRNO( ENOMEDIUM ); + CHECK_SYSTEM_ERRNO( EMEDIUMTYPE ); + CHECK_SYSTEM_ERRNO( ECANCELED ); + CHECK_SYSTEM_ERRNO( ENOKEY ); + CHECK_SYSTEM_ERRNO( EKEYEXPIRED ); + CHECK_SYSTEM_ERRNO( EKEYREVOKED ); + CHECK_SYSTEM_ERRNO( EKEYREJECTED ); + CHECK_SYSTEM_ERRNO( EOWNERDEAD ); + CHECK_SYSTEM_ERRNO( ENOTRECOVERABLE ); + CHECK_SYSTEM_ERRNO( ERFKILL ); + CHECK_SYSTEM_ERRNO( EHWPOISON ); + CHECK_SYSTEM_ERRNO( EFTYPE ); + CHECK_SYSTEM_ERRNO( EUNKNOWN ); + + if( EWOULDBLOCK != EAGAIN || __MCPU_EWOULDBLOCK != __MCPU_EAGAIN ) + return( 1 ); + if( EDEADLOCK != EDEADLK || __MCPU_EDEADLOCK != __MCPU_EDEADLK ) + return( 1 ); + if( ENOTSUP != EOPNOTSUPP || __MCPU_ENOTSUP != __MCPU_EOPNOTSUPP ) + return( 1 ); + + if( __EUNKNOWN__ != LIBMPU_EUNKNOWN ) return( 1 ); + if( __I_ESIZE__ != LIBMPU_I_ESIZE || + __I_ESHIFT__ != LIBMPU_I_ESHIFT || + __I_ENUMBER__ != LIBMPU_I_ENUMBER || + __I_ERADIX__ != LIBMPU_I_ERADIX || + __I_MAX_ERRNO != LIBMPU_I_MAX_ERRNO ) + return( 1 ); + if( __R_ESIZE__ != LIBMPU_R_ESIZE || + __R_ETRUNC__ != LIBMPU_R_ETRUNC || + __R_MAX_ERRNO != LIBMPU_R_MAX_ERRNO ) + return( 1 ); + if( __C_EDIVZEROBYZERO__ != LIBMPU_C_EDIVZEROBYZERO || + __C_ELOGOFZERO__ != LIBMPU_C_ELOGOFZERO || + __C_EPOWOFZERO__ != LIBMPU_C_EPOWOFZERO || + __C_MAX_ERRNO != LIBMPU_C_MAX_ERRNO ) + return( 1 ); + if( __DOMAIN__ != LIBMPU_DOMAIN || __SING__ != LIBMPU_SING || + __OVERFLOW__ != LIBMPU_OVERFLOW || + __UNDERFLOW__ != LIBMPU_UNDERFLOW || __TLOSS__ != LIBMPU_TLOSS || + __PLOSS__ != LIBMPU_PLOSS || __INVALID__ != LIBMPU_INVALID || + __INEXACT__ != LIBMPU_INEXACT || __M_MAX_ERRNO != LIBMPU_M_MAX_ERRNO ) + return( 1 ); + + return( 0 ); + +} /* End of main() */ diff --git a/tests/tst-errno-map.c b/tests/tst-errno-map.c new file mode 100644 index 0000000..c8ab42d --- /dev/null +++ b/tests/tst-errno-map.c @@ -0,0 +1,439 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <stdio.h> + +#include <libmcpu.h> +#include "../cpu/cpu-sysdeps.h" + + +#define CHECK_HOST_ERRNO(host_name, mcpu_name) \ + do { \ + int mapped = __cpu_errno_from_host( (host_name) ); \ + if( mapped != (__MCPU_ ## mcpu_name) ) \ + { \ + fprintf( stderr, #host_name " mapping mismatch: got=%d expected=%d\n", \ + mapped, (int)(__MCPU_ ## mcpu_name) ); \ + return( 1 ); \ + } \ + } while( 0 ) + + +int +main( void ) +{ + if( __cpu_errno_from_host( 0 ) != 0 ) return( 1 ); + if( __cpu_errno_from_host( -1 ) != __MCPU_EUNKNOWN ) return( 1 ); + if( __cpu_errno_from_host( 0x7fffffff ) != __MCPU_EUNKNOWN ) return( 1 ); + +#ifdef EPERM + CHECK_HOST_ERRNO( EPERM, EPERM ); +#endif +#ifdef ENOENT + CHECK_HOST_ERRNO( ENOENT, ENOENT ); +#endif +#ifdef ESRCH + CHECK_HOST_ERRNO( ESRCH, ESRCH ); +#endif +#ifdef EINTR + CHECK_HOST_ERRNO( EINTR, EINTR ); +#endif +#ifdef EIO + CHECK_HOST_ERRNO( EIO, EIO ); +#endif +#ifdef ENXIO + CHECK_HOST_ERRNO( ENXIO, ENXIO ); +#endif +#ifdef E2BIG + CHECK_HOST_ERRNO( E2BIG, E2BIG ); +#endif +#ifdef ENOEXEC + CHECK_HOST_ERRNO( ENOEXEC, ENOEXEC ); +#endif +#ifdef EBADF + CHECK_HOST_ERRNO( EBADF, EBADF ); +#endif +#ifdef ECHILD + CHECK_HOST_ERRNO( ECHILD, ECHILD ); +#endif +#ifdef EAGAIN + CHECK_HOST_ERRNO( EAGAIN, EAGAIN ); +#endif +#ifdef ENOMEM + CHECK_HOST_ERRNO( ENOMEM, ENOMEM ); +#endif +#ifdef EACCES + CHECK_HOST_ERRNO( EACCES, EACCES ); +#endif +#ifdef EFAULT + CHECK_HOST_ERRNO( EFAULT, EFAULT ); +#endif +#ifdef ENOTBLK + CHECK_HOST_ERRNO( ENOTBLK, ENOTBLK ); +#endif +#ifdef EBUSY + CHECK_HOST_ERRNO( EBUSY, EBUSY ); +#endif +#ifdef EEXIST + CHECK_HOST_ERRNO( EEXIST, EEXIST ); +#endif +#ifdef EXDEV + CHECK_HOST_ERRNO( EXDEV, EXDEV ); +#endif +#ifdef ENODEV + CHECK_HOST_ERRNO( ENODEV, ENODEV ); +#endif +#ifdef ENOTDIR + CHECK_HOST_ERRNO( ENOTDIR, ENOTDIR ); +#endif +#ifdef EISDIR + CHECK_HOST_ERRNO( EISDIR, EISDIR ); +#endif +#ifdef EINVAL + CHECK_HOST_ERRNO( EINVAL, EINVAL ); +#endif +#ifdef ENFILE + CHECK_HOST_ERRNO( ENFILE, ENFILE ); +#endif +#ifdef EMFILE + CHECK_HOST_ERRNO( EMFILE, EMFILE ); +#endif +#ifdef ENOTTY + CHECK_HOST_ERRNO( ENOTTY, ENOTTY ); +#endif +#ifdef ETXTBSY + CHECK_HOST_ERRNO( ETXTBSY, ETXTBSY ); +#endif +#ifdef EFBIG + CHECK_HOST_ERRNO( EFBIG, EFBIG ); +#endif +#ifdef ENOSPC + CHECK_HOST_ERRNO( ENOSPC, ENOSPC ); +#endif +#ifdef ESPIPE + CHECK_HOST_ERRNO( ESPIPE, ESPIPE ); +#endif +#ifdef EROFS + CHECK_HOST_ERRNO( EROFS, EROFS ); +#endif +#ifdef EMLINK + CHECK_HOST_ERRNO( EMLINK, EMLINK ); +#endif +#ifdef EPIPE + CHECK_HOST_ERRNO( EPIPE, EPIPE ); +#endif +#ifdef EDOM + CHECK_HOST_ERRNO( EDOM, EDOM ); +#endif +#ifdef ERANGE + CHECK_HOST_ERRNO( ERANGE, ERANGE ); +#endif +#ifdef EDEADLK + CHECK_HOST_ERRNO( EDEADLK, EDEADLK ); +#endif +#ifdef ENAMETOOLONG + CHECK_HOST_ERRNO( ENAMETOOLONG, ENAMETOOLONG ); +#endif +#ifdef ENOLCK + CHECK_HOST_ERRNO( ENOLCK, ENOLCK ); +#endif +#ifdef ENOSYS + CHECK_HOST_ERRNO( ENOSYS, ENOSYS ); +#endif +#ifdef ENOTEMPTY + CHECK_HOST_ERRNO( ENOTEMPTY, ENOTEMPTY ); +#endif +#ifdef ELOOP + CHECK_HOST_ERRNO( ELOOP, ELOOP ); +#endif +#ifdef ENOMSG + CHECK_HOST_ERRNO( ENOMSG, ENOMSG ); +#endif +#ifdef EIDRM + CHECK_HOST_ERRNO( EIDRM, EIDRM ); +#endif +#ifdef ECHRNG + CHECK_HOST_ERRNO( ECHRNG, ECHRNG ); +#endif +#ifdef EL2NSYNC + CHECK_HOST_ERRNO( EL2NSYNC, EL2NSYNC ); +#endif +#ifdef EL3HLT + CHECK_HOST_ERRNO( EL3HLT, EL3HLT ); +#endif +#ifdef EL3RST + CHECK_HOST_ERRNO( EL3RST, EL3RST ); +#endif +#ifdef ELNRNG + CHECK_HOST_ERRNO( ELNRNG, ELNRNG ); +#endif +#ifdef EUNATCH + CHECK_HOST_ERRNO( EUNATCH, EUNATCH ); +#endif +#ifdef ENOCSI + CHECK_HOST_ERRNO( ENOCSI, ENOCSI ); +#endif +#ifdef EL2HLT + CHECK_HOST_ERRNO( EL2HLT, EL2HLT ); +#endif +#ifdef EBADE + CHECK_HOST_ERRNO( EBADE, EBADE ); +#endif +#ifdef EBADR + CHECK_HOST_ERRNO( EBADR, EBADR ); +#endif +#ifdef EXFULL + CHECK_HOST_ERRNO( EXFULL, EXFULL ); +#endif +#ifdef ENOANO + CHECK_HOST_ERRNO( ENOANO, ENOANO ); +#endif +#ifdef EBADRQC + CHECK_HOST_ERRNO( EBADRQC, EBADRQC ); +#endif +#ifdef EBADSLT + CHECK_HOST_ERRNO( EBADSLT, EBADSLT ); +#endif +#ifdef EBFONT + CHECK_HOST_ERRNO( EBFONT, EBFONT ); +#endif +#ifdef ENOSTR + CHECK_HOST_ERRNO( ENOSTR, ENOSTR ); +#endif +#ifdef ENODATA + CHECK_HOST_ERRNO( ENODATA, ENODATA ); +#endif +#ifdef ETIME + CHECK_HOST_ERRNO( ETIME, ETIME ); +#endif +#ifdef ENOSR + CHECK_HOST_ERRNO( ENOSR, ENOSR ); +#endif +#ifdef ENONET + CHECK_HOST_ERRNO( ENONET, ENONET ); +#endif +#ifdef ENOPKG + CHECK_HOST_ERRNO( ENOPKG, ENOPKG ); +#endif +#ifdef EREMOTE + CHECK_HOST_ERRNO( EREMOTE, EREMOTE ); +#endif +#ifdef ENOLINK + CHECK_HOST_ERRNO( ENOLINK, ENOLINK ); +#endif +#ifdef EADV + CHECK_HOST_ERRNO( EADV, EADV ); +#endif +#ifdef ESRMNT + CHECK_HOST_ERRNO( ESRMNT, ESRMNT ); +#endif +#ifdef ECOMM + CHECK_HOST_ERRNO( ECOMM, ECOMM ); +#endif +#ifdef EPROTO + CHECK_HOST_ERRNO( EPROTO, EPROTO ); +#endif +#ifdef EMULTIHOP + CHECK_HOST_ERRNO( EMULTIHOP, EMULTIHOP ); +#endif +#ifdef EDOTDOT + CHECK_HOST_ERRNO( EDOTDOT, EDOTDOT ); +#endif +#ifdef EBADMSG + CHECK_HOST_ERRNO( EBADMSG, EBADMSG ); +#endif +#ifdef EOVERFLOW + CHECK_HOST_ERRNO( EOVERFLOW, EOVERFLOW ); +#endif +#ifdef ENOTUNIQ + CHECK_HOST_ERRNO( ENOTUNIQ, ENOTUNIQ ); +#endif +#ifdef EBADFD + CHECK_HOST_ERRNO( EBADFD, EBADFD ); +#endif +#ifdef EREMCHG + CHECK_HOST_ERRNO( EREMCHG, EREMCHG ); +#endif +#ifdef ELIBACC + CHECK_HOST_ERRNO( ELIBACC, ELIBACC ); +#endif +#ifdef ELIBBAD + CHECK_HOST_ERRNO( ELIBBAD, ELIBBAD ); +#endif +#ifdef ELIBSCN + CHECK_HOST_ERRNO( ELIBSCN, ELIBSCN ); +#endif +#ifdef ELIBMAX + CHECK_HOST_ERRNO( ELIBMAX, ELIBMAX ); +#endif +#ifdef ELIBEXEC + CHECK_HOST_ERRNO( ELIBEXEC, ELIBEXEC ); +#endif +#ifdef EILSEQ + CHECK_HOST_ERRNO( EILSEQ, EILSEQ ); +#endif +#ifdef ERESTART + CHECK_HOST_ERRNO( ERESTART, ERESTART ); +#endif +#ifdef ESTRPIPE + CHECK_HOST_ERRNO( ESTRPIPE, ESTRPIPE ); +#endif +#ifdef EUSERS + CHECK_HOST_ERRNO( EUSERS, EUSERS ); +#endif +#ifdef ENOTSOCK + CHECK_HOST_ERRNO( ENOTSOCK, ENOTSOCK ); +#endif +#ifdef EDESTADDRREQ + CHECK_HOST_ERRNO( EDESTADDRREQ, EDESTADDRREQ ); +#endif +#ifdef EMSGSIZE + CHECK_HOST_ERRNO( EMSGSIZE, EMSGSIZE ); +#endif +#ifdef EPROTOTYPE + CHECK_HOST_ERRNO( EPROTOTYPE, EPROTOTYPE ); +#endif +#ifdef ENOPROTOOPT + CHECK_HOST_ERRNO( ENOPROTOOPT, ENOPROTOOPT ); +#endif +#ifdef EPROTONOSUPPORT + CHECK_HOST_ERRNO( EPROTONOSUPPORT, EPROTONOSUPPORT ); +#endif +#ifdef ESOCKTNOSUPPORT + CHECK_HOST_ERRNO( ESOCKTNOSUPPORT, ESOCKTNOSUPPORT ); +#endif +#ifdef EOPNOTSUPP + CHECK_HOST_ERRNO( EOPNOTSUPP, EOPNOTSUPP ); +#endif +#ifdef EPFNOSUPPORT + CHECK_HOST_ERRNO( EPFNOSUPPORT, EPFNOSUPPORT ); +#endif +#ifdef EAFNOSUPPORT + CHECK_HOST_ERRNO( EAFNOSUPPORT, EAFNOSUPPORT ); +#endif +#ifdef EADDRINUSE + CHECK_HOST_ERRNO( EADDRINUSE, EADDRINUSE ); +#endif +#ifdef EADDRNOTAVAIL + CHECK_HOST_ERRNO( EADDRNOTAVAIL, EADDRNOTAVAIL ); +#endif +#ifdef ENETDOWN + CHECK_HOST_ERRNO( ENETDOWN, ENETDOWN ); +#endif +#ifdef ENETUNREACH + CHECK_HOST_ERRNO( ENETUNREACH, ENETUNREACH ); +#endif +#ifdef ENETRESET + CHECK_HOST_ERRNO( ENETRESET, ENETRESET ); +#endif +#ifdef ECONNABORTED + CHECK_HOST_ERRNO( ECONNABORTED, ECONNABORTED ); +#endif +#ifdef ECONNRESET + CHECK_HOST_ERRNO( ECONNRESET, ECONNRESET ); +#endif +#ifdef ENOBUFS + CHECK_HOST_ERRNO( ENOBUFS, ENOBUFS ); +#endif +#ifdef EISCONN + CHECK_HOST_ERRNO( EISCONN, EISCONN ); +#endif +#ifdef ENOTCONN + CHECK_HOST_ERRNO( ENOTCONN, ENOTCONN ); +#endif +#ifdef ESHUTDOWN + CHECK_HOST_ERRNO( ESHUTDOWN, ESHUTDOWN ); +#endif +#ifdef ETOOMANYREFS + CHECK_HOST_ERRNO( ETOOMANYREFS, ETOOMANYREFS ); +#endif +#ifdef ETIMEDOUT + CHECK_HOST_ERRNO( ETIMEDOUT, ETIMEDOUT ); +#endif +#ifdef ECONNREFUSED + CHECK_HOST_ERRNO( ECONNREFUSED, ECONNREFUSED ); +#endif +#ifdef EHOSTDOWN + CHECK_HOST_ERRNO( EHOSTDOWN, EHOSTDOWN ); +#endif +#ifdef EHOSTUNREACH + CHECK_HOST_ERRNO( EHOSTUNREACH, EHOSTUNREACH ); +#endif +#ifdef EALREADY + CHECK_HOST_ERRNO( EALREADY, EALREADY ); +#endif +#ifdef EINPROGRESS + CHECK_HOST_ERRNO( EINPROGRESS, EINPROGRESS ); +#endif +#ifdef ESTALE + CHECK_HOST_ERRNO( ESTALE, ESTALE ); +#endif +#ifdef EUCLEAN + CHECK_HOST_ERRNO( EUCLEAN, EUCLEAN ); +#endif +#ifdef ENOTNAM + CHECK_HOST_ERRNO( ENOTNAM, ENOTNAM ); +#endif +#ifdef ENAVAIL + CHECK_HOST_ERRNO( ENAVAIL, ENAVAIL ); +#endif +#ifdef EISNAM + CHECK_HOST_ERRNO( EISNAM, EISNAM ); +#endif +#ifdef EREMOTEIO + CHECK_HOST_ERRNO( EREMOTEIO, EREMOTEIO ); +#endif +#ifdef EDQUOT + CHECK_HOST_ERRNO( EDQUOT, EDQUOT ); +#endif +#ifdef ENOMEDIUM + CHECK_HOST_ERRNO( ENOMEDIUM, ENOMEDIUM ); +#endif +#ifdef EMEDIUMTYPE + CHECK_HOST_ERRNO( EMEDIUMTYPE, EMEDIUMTYPE ); +#endif +#ifdef ECANCELED + CHECK_HOST_ERRNO( ECANCELED, ECANCELED ); +#endif +#ifdef ENOKEY + CHECK_HOST_ERRNO( ENOKEY, ENOKEY ); +#endif +#ifdef EKEYEXPIRED + CHECK_HOST_ERRNO( EKEYEXPIRED, EKEYEXPIRED ); +#endif +#ifdef EKEYREVOKED + CHECK_HOST_ERRNO( EKEYREVOKED, EKEYREVOKED ); +#endif +#ifdef EKEYREJECTED + CHECK_HOST_ERRNO( EKEYREJECTED, EKEYREJECTED ); +#endif +#ifdef EOWNERDEAD + CHECK_HOST_ERRNO( EOWNERDEAD, EOWNERDEAD ); +#endif +#ifdef ENOTRECOVERABLE + CHECK_HOST_ERRNO( ENOTRECOVERABLE, ENOTRECOVERABLE ); +#endif +#ifdef ERFKILL + CHECK_HOST_ERRNO( ERFKILL, ERFKILL ); +#endif +#ifdef EHWPOISON + CHECK_HOST_ERRNO( EHWPOISON, EHWPOISON ); +#endif +#ifdef EFTYPE + CHECK_HOST_ERRNO( EFTYPE, EFTYPE ); +#endif +#ifdef EWOULDBLOCK + CHECK_HOST_ERRNO( EWOULDBLOCK, EWOULDBLOCK ); +#endif +#ifdef EDEADLOCK + CHECK_HOST_ERRNO( EDEADLOCK, EDEADLOCK ); +#endif +#ifdef ENOTSUP + CHECK_HOST_ERRNO( ENOTSUP, ENOTSUP ); +#endif + + return( 0 ); + +} /* End of main() */ diff --git a/tests/tst-frame-runner.sh b/tests/tst-frame-runner.sh new file mode 100755 index 0000000..932f15e --- /dev/null +++ b/tests/tst-frame-runner.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +base="${TMPDIR:-/tmp}/libcpu-frame-$$" +trap 'rm -f "$base.s" "$base.o" "$base.melf"' EXIT HUP INT TERM +"$CPU_AS" -o "$base.o" "$TEST_SRCDIR/fixtures/frame-pointer.s" >/dev/null +"$CPU_LD" --stack-size=512 -o "$base.melf" "$base.o" >/dev/null +out=`"$CPU_RUN" --int-reg=r64@0x00008 "$base.melf"` +test "x$out" = "x42" diff --git a/tests/tst-frame.c b/tests/tst-frame.c new file mode 100644 index 0000000..b94b51e --- /dev/null +++ b/tests/tst-frame.c @@ -0,0 +1,178 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static const char src[]= +".text\n" +" LDI r64@0x00000, 5\n" +" ADJSP -16\n" +" STSP 0, r64@0x00000\n" +" CALL demo\n" +" ADJSP 16\n" +" MOV r64@0x00008, r64@0x7fff8\n" +" HALT\n" +"demo:\n" +" PUSHFP\n" +" SETFP\n" +" ADJSP -16\n" +" LDFP r64@0x00100, 16\n" +" STFP -8, r64@0x00100\n" +" LEAFP r64@0x00108, -8\n" +" LEASP r64@0x00110, 0\n" +" LDI r64@0x00118, 24\n" +" PSUB r64@0x00120, r64@0x00110, r64@0x00118\n" +" SETSP r64@0x00120\n" +" LDI r64@0x00128, 37\n" +" LEASP r64@0x00130, 0\n" +" STOREP r64@0x00130, r64@0x00128\n" +" LDFP r64@0x00138, -8\n" +" LOADP r64@0x00140, r64@0x00130\n" +" IADD r64@0x7fff8, r64@0x00138, r64@0x00140\n" +" SETSP r64@0x00110\n" +" RESTSP\n" +" POPFP\n" +" RET\n"; + +static __mpu_uint64_t +reg_u64( unsigned address ) +{ + __mpu_uint64_t v = 0; + memcpy( &v, __cpu_reg_ptr( 64, address ), sizeof( v ) ); + return( v ); +} + +static __mpu_int64_t +reg_i64( unsigned address ) +{ + __mpu_int64_t v = 0; + memcpy( &v, __cpu_reg_ptr( 64, address ), sizeof( v ) ); + return( v ); +} + +static void +set_reg_u64( unsigned address, __mpu_uint64_t v ) +{ + memcpy( __cpu_reg_ptr( 64, address ), &v, sizeof( v ) ); +} + +static void +regop( cpu_operand *o, unsigned address ) +{ + memset( o, 0, sizeof( *o ) ); + o->type = CPU_OPERAND_REGISTER; + o->value.reg.bits = 64; + o->value.reg.address = address; +} + +int +main( void ) +{ + cpu_asm_image im; + cpu_instruction i; + __mpu_uint64_t ptr, saved_sp, saved_fp, badfp; + __mpu_uint32_t psw; + char err[256]; + + __cpu_init(); + CHECK( __cpu_stack_alloc( 256 ) == 0, "stack alloc" ); + CHECK( __cpu_sp == 256, "empty stack SP" ); + CHECK( __cpu_fp == 256, "empty stack FP" ); + + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + __cpu_pc = 0; + __cpu_set_psw( PSW_CF_MASK | PSW_IF_MASK ); + psw = __cpu_get_psw(); + CHECK( __cpu_run() == CPU_EXEC_SUCCESS, "frame/dynamic stack program run" ); + CHECK( reg_i64( 0x00008 ) == 42, "frame example result" ); + CHECK( reg_i64( 0x7fff8 ) == 42, "canonical return value" ); + CHECK( __cpu_sp == 256, "caller cleanup restored SP" ); + CHECK( __cpu_fp == 256, "callee restored caller FP" ); + cpu_asm_image_free( &im ); + + /* LEAFP is based on FP, not on the moving SP. */ + __cpu_sp = 160; + __cpu_fp = 200; + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_LEAFP; + regop( &i.operand[0], 0x00200 ); + i.operand[1].type = CPU_OPERAND_OFF64; + i.operand[1].value.off64 = -24; + __cpu_set_psw( PSW_CF_MASK | PSW_IF_MASK ); + psw = __cpu_get_psw(); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "LEAFP direct execution" ); + CHECK( __cpu_get_psw() == psw, "LEAFP preserves PSW" ); + ptr = reg_u64( 0x00200 ); + CHECK( __cpu_ptr_segment( ptr ) == CPU_PTR_SEG_STACK, "LEAFP STACK segment" ); + CHECK( __cpu_ptr_offset( ptr ) == 176, "LEAFP uses FP base" ); + CHECK( __cpu_sp == 160, "LEAFP leaves SP unchanged" ); + CHECK( __cpu_fp == 200, "LEAFP leaves FP unchanged" ); + + /* SETSP accepts only a STACK PTR64 and is atomic on faults. */ + CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_STACK, 144 ) == 0, "make SETSP STACK ptr" ); + set_reg_u64( 0x00208, ptr ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_SETSP; + regop( &i.operand[0], 0x00208 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "SETSP valid STACK ptr" ); + CHECK( __cpu_sp == 144, "SETSP installs offset" ); + CHECK( __cpu_fp == 200, "SETSP preserves FP" ); + + saved_sp = __cpu_sp; + CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_DATA, 10 ) == 0, "make DATA ptr" ); + set_reg_u64( 0x00208, ptr ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "SETSP rejects DATA ptr" ); + CHECK( __cpu_sp == saved_sp, "SETSP pointer fault atomic" ); + + CHECK( __cpu_ptr_make( &ptr, CPU_PTR_SEG_STACK, 300 ) == 0, "make out-of-range STACK ptr" ); + set_reg_u64( 0x00208, ptr ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_STACK_FAULT, "SETSP checks allocated stack size" ); + CHECK( __cpu_sp == saved_sp, "SETSP stack fault atomic" ); + + /* PUSHFP/POPFP preserve exact previous FP and validate a restored FP. */ + __cpu_sp = 128; + __cpu_fp = 216; + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_PUSHFP; + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "PUSHFP" ); + CHECK( __cpu_sp == 120, "PUSHFP moves SP by eight bytes" ); + memcpy( &saved_fp, __cpu_stack_ptr() + 120, 8 ); + CHECK( saved_fp == 216, "PUSHFP stored FP" ); + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_SETFP; + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "SETFP" ); + CHECK( __cpu_fp == 120, "SETFP copies SP" ); + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_RESTSP; + __cpu_sp = 80; + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "RESTSP" ); + CHECK( __cpu_sp == 120, "RESTSP copies FP" ); + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_POPFP; + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "POPFP" ); + CHECK( __cpu_fp == 216, "POPFP restores caller FP" ); + CHECK( __cpu_sp == 128, "POPFP advances SP by eight bytes" ); + CHECK( __cpu_get_psw() == psw, "FP/SP management preserves PSW" ); + + /* Invalid saved FP must not partially change FP or SP. */ + __cpu_sp = 64; + __cpu_fp = 100; + badfp = 257; + memcpy( __cpu_stack_ptr() + 64, &badfp, 8 ); + saved_sp = __cpu_sp; + saved_fp = __cpu_fp; + CHECK( __cpu_exec( &i ) == CPU_EXEC_STACK_FAULT, "POPFP rejects invalid saved FP" ); + CHECK( __cpu_sp == saved_sp, "POPFP fault preserves SP" ); + CHECK( __cpu_fp == saved_fp, "POPFP fault preserves FP" ); + + __cpu_free_context(); + CHECK( __cpu_fp == 0, "FP cleared by free context" ); + puts( "libcpu frame-pointer/dynamic STACK regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-hll-crt-runner.sh b/tests/tst-hll-crt-runner.sh new file mode 100755 index 0000000..ca03641 --- /dev/null +++ b/tests/tst-hll-crt-runner.sh @@ -0,0 +1,338 @@ +#!/bin/sh + +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${CPU_READELF:?}" +: "${MCPU_TEST_ROOT:?}" "${MCPU_HLL_FIXTURE_SRCDIR:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-hll-crt-$$ +mkdir -p "$TMP" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +as() +{ + "$CPU_AS" -o "$2" "$1" >/dev/null +} + +must_fail_link() +{ + if "$CPU_LD" "$@" >/dev/null 2>&1; then + echo "mcpu-ld unexpectedly accepted invalid HLL input: $*" >&2 + exit 1 + fi +} + +# --------------------------------------------------------------------------- +# Regression fixtures for compiler-normalized HLL objects and libc hooks. +# --------------------------------------------------------------------------- +as "$MCPU_HLL_FIXTURE_SRCDIR/program-main.S" "$TMP/program.o" +as "$MCPU_HLL_FIXTURE_SRCDIR/model-pendulum-life.S" "$TMP/model.o" +as "$MCPU_HLL_FIXTURE_SRCDIR/libc-stub.S" "$TMP/libc-stub.o" + +# The future compiler output owns HLL functions, never MELF lifecycle symbols. +"$CPU_READELF" -s "$TMP/program.o" > "$TMP/program.syms" +grep -q ' main$' "$TMP/program.syms" +grep -q ' fini$' "$TMP/program.syms" +if grep -Eq ' (_start|_life|_fini)$' "$TMP/program.syms"; then + echo "PROGRAM compiler-output emulator defines lifecycle symbols" >&2 + exit 1 +fi + +"$CPU_READELF" -s "$TMP/model.o" > "$TMP/model.syms" +grep -q ' init$' "$TMP/model.syms" +grep -q ' life$' "$TMP/model.syms" +grep -q ' fini$' "$TMP/model.syms" +if grep -Eq ' (_start|_life|_fini)$' "$TMP/model.syms"; then + echo "MODEL compiler-output emulator defines lifecycle symbols" >&2 + exit 1 +fi + +# --------------------------------------------------------------------------- +# PROGRAM: automatic classification, CRT insertion, argc/argv bridge and fini. +# --------------------------------------------------------------------------- +"$CPU_LD" --sys-root="$MCPU_TEST_ROOT" --stack-size=4K --no-heap \ + -o "$TMP/program" "$TMP/program.o" "$TMP/libc-stub.o" >/dev/null + +"$CPU_READELF" -h "$TMP/program" > "$TMP/program.hdr" +entry=`sed -n 's/^ Entry point address:[[:space:]]*//p' "$TMP/program.hdr"` +init=`sed -n 's/^ INIT entry address:[[:space:]]*//p' "$TMP/program.hdr"` +life=`sed -n 's/^ LIFE entry address:[[:space:]]*//p' "$TMP/program.hdr"` +fini=`sed -n 's/^ FINI entry address:[[:space:]]*//p' "$TMP/program.hdr"` +test "x$entry" = "x$life" +test "x$init" = "x0x0000000000000000" +test "x$fini" != "x0x0000000000000000" + +set +e +out=`"$CPU_RUN" --after-run=print-int-regs:r64@0x70000 \ + -- "$TMP/program" alpha beta` +status=$? +set -e +test "$status" -eq 42 +test "x$out" = "x6" + +# ABI-v3 PROGRAM uses the same HLL interface with the runtime hooks supplied +# by a normal shared MCPU Libc dependency. The four hook names must therefore +# be exported from ET_DYN, while explicit preloads still may not interpose them. +"$CPU_LD" --shared --soname=libhllc.so.1 \ + -o "$TMP/libhllc.so.1" "$TMP/libc-stub.o" >/dev/null +ln -s libhllc.so.1 "$TMP/libhllc.so" +"$CPU_READELF" -s "$TMP/libhllc.so.1" > "$TMP/libhllc.syms" +grep -q ' __mcpu_libc_init$' "$TMP/libhllc.syms" +grep -q ' __mcpu_init_array$' "$TMP/libhllc.syms" +grep -q ' __mcpu_fini_array$' "$TMP/libhllc.syms" +grep -q ' __mcpu_libc_fini$' "$TMP/libhllc.syms" + +"$CPU_LD" --dynamic --sys-root="$MCPU_TEST_ROOT" \ + --stack-size=4K --no-heap -L"$TMP" -lhllc \ + -o "$TMP/program-dyn" "$TMP/program.o" >/dev/null +set +e +out=`MCPU_LD_PRELOAD= "$CPU_RUN" --library-path="$TMP" \ + --after-run=print-int-regs:r64@0x70000 \ + -- "$TMP/program-dyn" alpha beta` +status=$? +set -e +test "$status" -eq 42 +test "x$out" = "x6" + +# HLL complete-program functions remain bound to the main executable. Runtime +# hooks come from the normal libc dependency. A preload may export all of the +# same names but cannot interpose either class of protected HLL symbol. +cat > "$TMP/override.s" <<'EOS' +.global main +.global fini +.global __mcpu_libc_init +.global __mcpu_init_array +.global __mcpu_fini_array +.global __mcpu_libc_fini +.text +main: + LDI r64@0x70000, 700 + LDI r32@0x7fffc, 70 + RET +fini: + LDI r64@0x70000, 701 + RET +__mcpu_libc_init: + LDI r64@0x70000, 702 + RET +__mcpu_init_array: + LDI r64@0x70000, 703 + RET +__mcpu_fini_array: + LDI r64@0x70000, 704 + RET +__mcpu_libc_fini: + LDI r64@0x70000, 705 + RET +EOS +as "$TMP/override.s" "$TMP/override.o" +"$CPU_LD" --shared --soname=libhll-override.so.1 \ + -o "$TMP/libhll-override.so" "$TMP/override.o" >/dev/null +set +e +out=`MCPU_LD_PRELOAD= "$CPU_RUN" --library-path="$TMP" \ + --ldpreload="$TMP/libhll-override.so" \ + --after-run=print-int-regs:r64@0x70000 \ + -- "$TMP/program-dyn" alpha beta` +status=$? +set -e +test "$status" -eq 42 +test "x$out" = "x6" + +# --------------------------------------------------------------------------- +# MODEL: compact pendulum-shaped compiler-output regression fixture. +# --------------------------------------------------------------------------- +"$CPU_LD" --sys-root="$MCPU_TEST_ROOT" --stack-size=4K --no-heap \ + -o "$TMP/model" "$TMP/model.o" "$TMP/libc-stub.o" >/dev/null + +"$CPU_READELF" -h "$TMP/model" > "$TMP/model.hdr" +entry=`sed -n 's/^ Entry point address:[[:space:]]*//p' "$TMP/model.hdr"` +init=`sed -n 's/^ INIT entry address:[[:space:]]*//p' "$TMP/model.hdr"` +life=`sed -n 's/^ LIFE entry address:[[:space:]]*//p' "$TMP/model.hdr"` +fini=`sed -n 's/^ FINI entry address:[[:space:]]*//p' "$TMP/model.hdr"` +test "x$entry" = "x$init" +test "x$life" != "x$entry" +test "x$fini" != "x0x0000000000000000" + +# Natural MODEL run: three LIFE_CONTINUE boundaries, then LIFE_END. +set +e +out=`"$CPU_RUN" --steps=10 \ + --on-step=print-int-trace:64@0 \ + --after-run=print-int-regs:r64@0x70000,r64@0x70010 \ + -- "$TMP/model"` +status=$? +set -e +test "$status" -eq 0 +expected='1 +2 +3 +6 1' +test "x$out" = "x$expected" + +# init() failure is already RUNNING: normal HLL finalization is mandatory. +set +e +out=`"$CPU_RUN" --before-run=set-int-control:64@0=-1 \ + --after-run=print-int-regs:r64@0x70000,r64@0x70010 \ + -- "$TMP/model"` +status=$? +set -e +test "$status" -eq 17 +test "x$out" = "x6 1" + +# Positive life() status is a MODEL error and performs normal finalization. +set +e +out=`"$CPU_RUN" --steps=10 \ + --before-run=set-int-control:64@8=2 \ + --on-step=print-int-trace:64@0 \ + --after-run=print-int-regs:r64@0x70000,r64@0x70010 \ + -- "$TMP/model"` +status=$? +set -e +test "$status" -eq 9 +expected='1 +6 1' +test "x$out" = "x$expected" + +# Diagnostic --steps stop does not synthesize FINI. +out=`"$CPU_RUN" --steps=1 \ + --on-step=print-int-trace:64@0 \ + --after-run=print-int-regs:r64@0x70000,r64@0x70010 \ + -- "$TMP/model"` +expected='1 +3 0' +test "x$out" = "x$expected" + +# --------------------------------------------------------------------------- +# PRE_RUNTIME failure: exercise the real common CRT startup/finalization code. +# A test-only PROGRAM adapter corrupts McpuStartup.version before entering the +# real __mcpu_crt_startup. The normal user object and libc stub are unchanged. +# --------------------------------------------------------------------------- +mkdir -p "$TMP/bad-root/lib/mcpu" +cp "$MCPU_TEST_ROOT/lib/mcpu/crti.o" "$TMP/bad-root/lib/mcpu/" +cp "$MCPU_TEST_ROOT/lib/mcpu/crtn.o" "$TMP/bad-root/lib/mcpu/" +cp "$MCPU_TEST_ROOT/lib/mcpu/crt1.o" "$TMP/bad-root/lib/mcpu/" +cat > "$TMP/bad-crt0.s" <<'EOS' +.global __mcpu_crt_startup +.global main +.text +__mcpu_crt_hll_program: +_start_life: + LDI r32@0x7fe00, 99 + STSP 0, r32@0x7fe00 + CALL __mcpu_crt_startup + LDI r32@0x7fe08, 88 + EXIT r32@0x7fe08 +EOS +as "$TMP/bad-crt0.s" "$TMP/bad-root/lib/mcpu/crt0.o" +"$CPU_LD" --sys-root="$TMP/bad-root" --stack-size=4K --no-heap \ + -o "$TMP/bad-startup" "$TMP/program.o" "$TMP/libc-stub.o" >/dev/null +set +e +out=`"$CPU_RUN" --after-run=print-int-regs:r64@0x70000 \ + -- "$TMP/bad-startup" alpha beta` +status=$? +set -e +test "$status" -ne 0 +test "x$out" = "x0" + +# --------------------------------------------------------------------------- +# HLL complete-program classification errors. +# --------------------------------------------------------------------------- +cat > "$TMP/main-only.s" <<'EOS' +.global main +.text +main: + CLR r32@0x7fffc + RET +EOS +as "$TMP/main-only.s" "$TMP/main-only.o" +must_fail_link --sys-root="$MCPU_TEST_ROOT" --stack-size=4K --no-heap \ + -o "$TMP/bad-main-only" "$TMP/main-only.o" "$TMP/libc-stub.o" + +cat > "$TMP/model-init.s" <<'EOS' +.global init +.global fini +.text +init: + CLR r32@0x7fffc + RET +fini: + RET +EOS +cat > "$TMP/model-life.s" <<'EOS' +.global life +.text +life: + CLR r32@0x7fffc + RET +EOS +as "$TMP/model-init.s" "$TMP/model-init.o" +as "$TMP/model-life.s" "$TMP/model-life.o" +must_fail_link --sys-root="$MCPU_TEST_ROOT" --stack-size=4K --no-heap \ + -o "$TMP/bad-split-model" "$TMP/model-init.o" "$TMP/model-life.o" \ + "$TMP/libc-stub.o" + +cat > "$TMP/mixed.s" <<'EOS' +.global main +.global fini +.text +_start_life: +main: + CLR r32@0x7fffc + RET +fini: + RET +EOS +as "$TMP/mixed.s" "$TMP/mixed.o" +must_fail_link --sys-root="$MCPU_TEST_ROOT" --stack-size=4K --no-heap \ + -o "$TMP/bad-mixed" "$TMP/mixed.o" "$TMP/libc-stub.o" + +cat > "$TMP/reserved.s" <<'EOS' +.global __mcpu_crt_user_collision +.text +__mcpu_crt_user_collision: + RET +EOS +as "$TMP/reserved.s" "$TMP/reserved.o" +must_fail_link --sys-root="$MCPU_TEST_ROOT" --stack-size=4K --no-heap \ + -o "$TMP/bad-reserved" "$TMP/reserved.o" + +# --------------------------------------------------------------------------- +# Frozen ET_DYN ownership rule: model/lifecycle sections belong to ET_EXEC. +# --------------------------------------------------------------------------- +for sec in init fini control input output trace; do + case "$sec" in + init|fini) + cat > "$TMP/shared-$sec.s" <<EOS +.$sec +shared_${sec}_object: + RET +EOS + ;; + *) + cat > "$TMP/shared-$sec.s" <<EOS +.$sec +shared_${sec}_object: + .integer 64, 0 +EOS + ;; + esac + as "$TMP/shared-$sec.s" "$TMP/shared-$sec.o" + if "$CPU_LD" --shared --soname="libbad-$sec.so.1" \ + -o "$TMP/libbad-$sec.so" "$TMP/shared-$sec.o" >/dev/null 2>&1; then + echo "ET_DYN unexpectedly accepted .$sec" >&2 + exit 1 + fi +done + +cat > "$TMP/shared-lifecycle.s" <<'EOS' +.text +_start_life: + END +EOS +as "$TMP/shared-lifecycle.s" "$TMP/shared-lifecycle.o" +if "$CPU_LD" --shared --soname=libbad-lifecycle.so.1 \ + -o "$TMP/libbad-lifecycle.so" "$TMP/shared-lifecycle.o" >/dev/null 2>&1; then + echo "ET_DYN unexpectedly accepted lifecycle symbols" >&2 + exit 1 +fi + +exit 0 diff --git a/tests/tst-immediate-exactness-runner.sh b/tests/tst-immediate-exactness-runner.sh new file mode 100755 index 0000000..3fc3a42 --- /dev/null +++ b/tests/tst-immediate-exactness-runner.sh @@ -0,0 +1,65 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" +TMP="${TMPDIR:-/tmp}/libcpu-imm-$$" +trap 'rm -f "$TMP".s "$TMP".o "$TMP".err "$TMP".out' EXIT HUP INT TERM +cat > "$TMP.s" <<'EOS' +.text +LDR r4096@0x00000, 3.14243987129849274029174912694696912849214e-286 +HALT +EOS +if "$CPU_AS" -o "$TMP.o" "$TMP.s" >"$TMP.out" 2>"$TMP.err"; then + echo "cpu-as unexpectedly accepted inexact wide LDR" >&2 + exit 1 +fi +grep -F "$TMP.s:2: error:" "$TMP.err" >/dev/null +grep -F 'LDR immediate is not exactly representable as real64;' "$TMP.err" >/dev/null +grep -F 'use .real <bits>, value in DATA and LOAD' "$TMP.err" >/dev/null + +cat > "$TMP.s" <<'EOS' +.text +LDR r32@0x00000, 0.1 +HALT +EOS +if "$CPU_AS" -o "$TMP.o" "$TMP.s" >"$TMP.out" 2>"$TMP.err"; then + echo "cpu-as unexpectedly accepted inexact real32 LDR" >&2 + exit 1 +fi +grep -F 'LDR immediate is not exactly representable as real32;' "$TMP.err" >/dev/null + +cat > "$TMP.s" <<'EOS' +.text +LDC c128@0x00000, 0.1r0+32.0j0 +HALT +EOS +if "$CPU_AS" -o "$TMP.o" "$TMP.s" >"$TMP.out" 2>"$TMP.err"; then + echo "cpu-as unexpectedly accepted inexact complex immediate" >&2 + exit 1 +fi +grep -F 'LDC immediate component is not exactly representable as real32;' "$TMP.err" >/dev/null +grep -F 'use .complex <bits>, real, imag in DATA and LOAD' "$TMP.err" >/dev/null + +cat > "$TMP.s" <<'EOS' +.text +LDI r8@0x00000, 255 +HALT +EOS +if "$CPU_AS" -o "$TMP.o" "$TMP.s" >"$TMP.out" 2>"$TMP.err"; then + echo "cpu-as unexpectedly accepted lossy narrow integer immediate" >&2 + exit 1 +fi +grep -F 'LDI immediate is not exactly representable as integer8;' "$TMP.err" >/dev/null +grep -F 'use .integer <bits>, value in DATA and LOAD' "$TMP.err" >/dev/null + +cat > "$TMP.s" <<'EOS' +.text +LDI r128@0x00000, 9223372036854775808 +HALT +EOS +if "$CPU_AS" -o "$TMP.o" "$TMP.s" >"$TMP.out" 2>"$TMP.err"; then + echo "cpu-as unexpectedly accepted integer wider than IMM64" >&2 + exit 1 +fi +grep -F 'LDI immediate is not exactly representable as integer64;' "$TMP.err" >/dev/null + +echo 'libcpu immediate exactness runner regression: PASS' diff --git a/tests/tst-immediate-exactness.c b/tests/tst-immediate-exactness.c new file mode 100644 index 0000000..ed054e7 --- /dev/null +++ b/tests/tst-immediate-exactness.c @@ -0,0 +1,100 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do { if(!(c)) { fprintf(stderr,"FAIL: %s\n",m); return 1; } } while(0) + +static int +rejected( const char *src, const char *needle ) +{ + cpu_asm_image im; + char err[512]; + int rc = cpu_assemble_text( src, &im, err, sizeof( err ) ); + if( rc == 0 ) + { + cpu_asm_image_free( &im ); + return( 0 ); + } + return( strstr( err, needle ) != 0 ); +} + +int +main( void ) +{ + cpu_asm_image im; + char err[512]; + static const char *prec = "3.14243987129849274029174912694696912849214e-286"; + char src[4096]; + + __cpu_init(); + + CHECK( cpu_assemble_text( ".text\nLDR r32@0x00000, -17.0\nHALT\n", &im, err, sizeof( err ) ) == 0, + "exact real32 LDR accepted"); + cpu_asm_image_free( &im ); + + CHECK( cpu_assemble_text( ".text\nLDR r4096@0x00000, 2.0\nHALT\n", &im, err, sizeof( err ) ) == 0, + "exact real64 immediate widened to real4096 accepted"); + cpu_asm_image_free( &im ); + + CHECK( rejected( ".text\nLDR r32@0x00000, 0.1\nHALT\n", + "LDR immediate is not exactly representable as real32;"), + "inexact real32 immediate rejected"); + + CHECK( rejected( ".text\nLDR r64@0x00000, 0.1\nHALT\n", + "LDR immediate is not exactly representable as real64;"), + "inexact real64 immediate rejected"); + + snprintf( src, sizeof( src ), ".text\nLDR r64@0x00000, %s\nHALT\n", prec ); + CHECK( rejected( src, "LDR immediate is not exactly representable as real64;" ), + "inexact real64 immediate rejected"); + + snprintf( src, sizeof( src ), ".text\nLDR r4096@0x00000, %s\nHALT\n", prec ); + CHECK( rejected( src, "LDR immediate is not exactly representable as real64;" ), + "wide LDR cannot hide binary64 precision loss"); + + CHECK( cpu_assemble_text( ".text\nLDC c64@0x00000, -17.0r0+32.0j0\nHALT\n", &im, err, sizeof( err ) ) == 0, + "exact complex64 immediate accepted"); + cpu_asm_image_free( &im ); + + CHECK( rejected( ".text\nLDC c128@0x00000, 0.1r0+32.0j0\nHALT\n", + "LDC immediate component is not exactly representable as real32;"), + "inexact complex immediate component rejected"); + + CHECK( rejected( ".text\nLDC c32@0x00000, 1.0r0+2.0j0\nHALT\n", + "bad LDC destination"), + "complex destination below 64 bits rejected"); + + CHECK( rejected( ".text\nLDI r128@0x00000, 9223372036854775808\nHALT\n", + "LDI immediate is not exactly representable as integer64;"), + "integer wider than signed IMM64 rejected"); + + CHECK( rejected( ".text\nLDI r8@0x00000, 255\nHALT\n", + "LDI immediate is not exactly representable as integer8;"), + "narrow integer immediate that changes value rejected"); + CHECK( cpu_assemble_text( ".text\nLDI r8@0x00000, -1\nHALT\n", &im, err, sizeof( err ) ) == 0, + "exact signed integer8 immediate accepted"); + cpu_asm_image_free( &im ); + + snprintf( src, sizeof( src ), + ".data\n" + ".align 16\n" + "wide_real:\n" + " .real 4096, %s\n" + "wide_int:\n" + " .integer 128, 9223372036854775808\n" + "wide_complex:\n" + " .complex 4096, %s, -%s\n" + ".text\n" + " LOAD r4096@0x00000, wide_real\n" + " LOAD r128@0x00200, wide_int\n" + " LOAD c4096@0x00400, wide_complex\n" + " HALT\n", prec, prec, prec); + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, + "DATA fallback with .real/.integer/.complex and LOAD accepted"); + cpu_asm_image_free( &im ); + + __cpu_free_context(); + puts( "libcpu immediate exactness regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-indirect-control.c b/tests/tst-indirect-control.c new file mode 100644 index 0000000..acb0a7b --- /dev/null +++ b/tests/tst-indirect-control.c @@ -0,0 +1,184 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static const char decode_src[]= +".text\n" +" JMPP r64@0x00000\n" +" CALLP r64@0x00008\n" +" HALT\n"; + +static const char run_src[]= +".data\n" +".align 8\n" +"table:\n" +" .cptr leaf\n" +" .cptr done\n" +".text\n" +" LEA r64@0x00000, table\n" +" LOADP r64@0x00008, r64@0x00000\n" +" CALLP r64@0x00008\n" +" PADDI r64@0x00000, r64@0x00000, 8\n" +" LOADP r64@0x00010, r64@0x00000\n" +" JMPP r64@0x00010\n" +" LDI r64@0x00020, 99\n" +" HALT\n" +"leaf:\n" +" LDI r64@0x00020, 42\n" +" RET\n" +"done:\n" +" HALT\n"; + +static void +put_u64_reg( unsigned addr, __mpu_uint64_t v ) +{ + __cpu_data_put_u64( __cpu_reg_ptr( 64, addr ), v ); +} + +int +main( void ) +{ + cpu_asm_image decim, im, bad; + cpu_instruction insn, execi; + cpu_register r64 = + { + 64, 0x00100 + } + , r32 = + { + 32, 0x00100 + }; + __mpu_uint64_t p0, p1, pvalid, pbad, ret, saved_sp, saved_pc; + __mpu_uint32_t psw; + mpu_int64_t forty_two; + unsigned char before[16], after[16]; + char err[256]; + int rc; + + __cpu_init(); + + CHECK( cpu_assemble_text( decode_src, &decim, err, sizeof( err ) ) == 0, err ); + CHECK( decim.code_size == 12, "JMPP/CALLP bytecode lengths" ); + CHECK( __cpu_program_set( decim.code, decim.code_size ) == 0, "decode program set" ); + __cpu_pc = 0; + CHECK( __cpu_decode( &insn ) == CPU_EXEC_SUCCESS, "decode JMPP" ); + CHECK( insn.opcode == CPU_OP_JMPP, "JMPP opcode" ); + CHECK( insn.length == CPU_OPCODE_SIZE + CPU_REG24_SIZE, "JMPP length 5" ); + CHECK( insn.operand[0].type == CPU_OPERAND_REGISTER, "JMPP register operand" ); + CHECK( insn.operand[0].value.reg.bits == 64, "JMPP r64 width" ); + __cpu_pc = 5; + CHECK( __cpu_decode( &insn ) == CPU_EXEC_SUCCESS, "decode CALLP" ); + CHECK( insn.opcode == CPU_OP_CALLP, "CALLP opcode" ); + CHECK( insn.length == CPU_OPCODE_SIZE + CPU_REG24_SIZE, "CALLP length 5" ); + cpu_asm_image_free( &decim ); + + __cpu_program_free(); + CHECK( cpu_assemble_text( run_src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( im.data_size == CPU_DATA_NULL_GUARD_SIZE + 16, "two .cptr entries in DATA" ); + p0 = __cpu_data_get_u64( im.data + CPU_DATA_NULL_GUARD_SIZE ); + p1 = __cpu_data_get_u64( im.data + CPU_DATA_NULL_GUARD_SIZE + 8 ); + CHECK( __cpu_ptr_segment( p0 ) == CPU_PTR_SEG_PROGRAM, "first .cptr PROGRAM segment" ); + CHECK( __cpu_ptr_segment( p1 ) == CPU_PTR_SEG_PROGRAM, "second .cptr PROGRAM segment" ); + CHECK( __cpu_ptr_offset( p0 ) < im.code_size, "first .cptr target in PROGRAM" ); + CHECK( __cpu_ptr_offset( p1 ) < im.code_size, "second .cptr target in PROGRAM" ); + CHECK( p0 != p1, ".cptr labels resolve to distinct targets" ); + + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "indirect program set" ); + CHECK( __cpu_data_set( im.data, im.data_size ) == 0, "indirect data set" ); + CHECK( __cpu_stack_alloc( 512 ) == 0, "indirect stack alloc" ); + __cpu_pc = 0; + CHECK( __cpu_run() == CPU_EXEC_SUCCESS, "indirect CALLP/JMPP run" ); + iatoi( ( mpu_int * ) forty_two, "42", NB_I64 ); + CHECK( !memcmp( __cpu_reg_ptr( 64, 0x00020 ), forty_two, NB_I64 ), "CALLP returned and JMPP reached done" ); + CHECK( __cpu_sp == 512, "CALLP/RET restored SP" ); + + /* Direct executor tests: JMPP must only install a validated PROGRAM PTR64. */ + memset( &execi, 0, sizeof( execi ) ); + execi.opcode = CPU_OP_JMPP; + execi.operand[0].type = CPU_OPERAND_REGISTER; + execi.operand[0].value.reg = r64; + pvalid = p0; + put_u64_reg( 0x00100, pvalid ); + __cpu_pc = 7; + __cpu_sp = 512; + __cpu_set_psw( PSW_CF_MASK | PSW_TF_MASK ); + psw = __cpu_get_psw(); + CHECK( __cpu_exec( &execi ) == CPU_EXEC_BRANCH, "JMPP executor success" ); + CHECK( __cpu_pc == __cpu_ptr_offset( pvalid ), "JMPP installed target PC" ); + CHECK( __cpu_sp == 512, "JMPP does not change SP" ); + CHECK( __cpu_get_psw() == psw, "JMPP preserves PSW" ); + + CHECK( __cpu_ptr_make( &pbad, CPU_PTR_SEG_DATA, CPU_DATA_NULL_GUARD_SIZE ) == 0, "make DATA pointer" ); + put_u64_reg( 0x00100, pbad ); + __cpu_pc = 9; + saved_pc = __cpu_pc; + saved_sp = __cpu_sp; + CHECK( __cpu_exec( &execi ) == CPU_EXEC_POINTER_FAULT, "JMPP rejects non-PROGRAM PTR64" ); + CHECK( __cpu_pc == saved_pc && __cpu_sp == saved_sp, "JMPP pointer fault is atomic" ); + + CHECK( __cpu_ptr_make( &pbad, CPU_PTR_SEG_PROGRAM, im.code_size ) == 0, "make out-of-range PROGRAM pointer" ); + put_u64_reg( 0x00100, pbad ); + __cpu_pc = 11; + saved_pc = __cpu_pc; + CHECK( __cpu_exec( &execi ) == CPU_EXEC_POINTER_FAULT, "JMPP rejects PROGRAM end pointer" ); + CHECK( __cpu_pc == saved_pc, "JMPP range fault preserves PC" ); + + put_u64_reg( 0x00100, CPU_NULL_PTR ); + CHECK( __cpu_exec( &execi ) == CPU_EXEC_POINTER_FAULT, "JMPP rejects NULL PTR64" ); + + execi.operand[0].value.reg = r32; + CHECK( __cpu_exec( &execi ) == CPU_EXEC_WIDTH_FAULT, "JMPP requires physical r64" ); + + /* CALLP validates pointer, return PC and stack before architectural mutation. */ + memset( &execi, 0, sizeof( execi ) ); + execi.opcode = CPU_OP_CALLP; + execi.operand[0].type = CPU_OPERAND_REGISTER; + execi.operand[0].value.reg = r64; + put_u64_reg( 0x00100, pvalid ); + __cpu_pc = 10; + __cpu_sp = 512; + memset( __cpu_stack_ptr() + 496, 0xa5, 16 ); + __cpu_set_psw( PSW_CF_MASK | PSW_TF_MASK ); + psw = __cpu_get_psw(); + CHECK( __cpu_exec( &execi ) == CPU_EXEC_BRANCH, "CALLP executor success" ); + CHECK( __cpu_pc == __cpu_ptr_offset( pvalid ), "CALLP installed target PC" ); + CHECK( __cpu_sp == 504, "CALLP pushed 8-byte return PC" ); + ret = __cpu_data_get_u64( __cpu_stack_ptr() + 504 ); + CHECK( ret == 15, "CALLP return PC is next 5-byte instruction" ); + CHECK( __cpu_get_psw() == psw, "CALLP preserves PSW" ); + + CHECK( __cpu_ptr_make( &pbad, CPU_PTR_SEG_STACK, 0 ) == 0, "make STACK pointer" ); + put_u64_reg( 0x00100, pbad ); + __cpu_pc = 20; + __cpu_sp = 512; + saved_pc = __cpu_pc; + saved_sp = __cpu_sp; + memcpy( before, __cpu_stack_ptr() + 496, 16 ); + CHECK( __cpu_exec( &execi ) == CPU_EXEC_POINTER_FAULT, "CALLP rejects non-PROGRAM PTR64" ); + memcpy( after, __cpu_stack_ptr() + 496, 16 ); + CHECK( __cpu_pc == saved_pc && __cpu_sp == saved_sp && !memcmp( before, after, 16 ), "CALLP pointer fault is atomic" ); + + put_u64_reg( 0x00100, pvalid ); + __cpu_pc = 20; + __cpu_sp = 4; + saved_pc = __cpu_pc; + saved_sp = __cpu_sp; + CHECK( __cpu_exec( &execi ) == CPU_EXEC_STACK_FAULT, "CALLP checks return-address stack space" ); + CHECK( __cpu_pc == saved_pc && __cpu_sp == saved_sp, "CALLP stack fault is atomic" ); + + execi.operand[0].value.reg = r32; + __cpu_sp = 512; + CHECK( __cpu_exec( &execi ) == CPU_EXEC_WIDTH_FAULT, "CALLP requires physical r64" ); + + CHECK( cpu_assemble_text( ".text\n JMPP r32@0x00000\n", &bad, err, sizeof( err ) ) != 0, "assembler rejects JMPP r32" ); + CHECK( cpu_assemble_text( ".text\n CALLP c64@0x00000\n", &bad, err, sizeof( err ) ) != 0, "assembler rejects CALLP c64 spelling" ); + CHECK( cpu_assemble_text( ".data\n .cptr nowhere\n.text\n HALT\n", &bad, err, sizeof( err ) ) != 0, "assembler rejects undefined .cptr label" ); + + cpu_asm_image_free( &im ); + __cpu_free_context(); + puts( "libcpu indirect control-flow regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-indirect-runner.sh b/tests/tst-indirect-runner.sh new file mode 100755 index 0000000..dba3b94 --- /dev/null +++ b/tests/tst-indirect-runner.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +base="${TMPDIR:-/tmp}/libcpu-indirect-$$" +trap 'rm -f "$base.s" "$base.o" "$base.melf"' EXIT HUP INT TERM +"$CPU_AS" -o "$base.o" "$TEST_SRCDIR/fixtures/indirect-control.s" >/dev/null +"$CPU_LD" --stack-size=512 -o "$base.melf" "$base.o" >/dev/null +out=`"$CPU_RUN" --int-reg=r64@0x00020 "$base.melf"` +test "x$out" = "x42" diff --git a/tests/tst-instruction.c b/tests/tst-instruction.c new file mode 100644 index 0000000..052fb9c --- /dev/null +++ b/tests/tst-instruction.c @@ -0,0 +1,237 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> + +#include <libmcpu.h> + + +static int failures = 0; + +#define CHECK(cond,msg) \ + do { \ + if( !(cond) ) \ + { \ + fprintf( stderr, "FAIL: %s\n", (msg) ); \ + ++failures; \ + } \ + } while( 0 ) + + +static cpu_operand +reg_operand( unsigned int bits, __mpu_uint32_t address ) +{ + cpu_operand operand; + + memset( &operand, 0, sizeof(operand) ); + operand.type = CPU_OPERAND_REGISTER; + operand.value.reg.bits = bits; + operand.value.reg.address = address; + + return( operand ); +} + + +static void +clear_instruction( cpu_instruction *instruction, cpu_opcode opcode ) +{ + memset( instruction, 0, sizeof(*instruction) ); + instruction->opcode = opcode; +} + + +int +main( void ) +{ + cpu_instruction instruction; + __mpu_uint32_t psw; + + __cpu_init(); + + /* NOP must not change the logical PSW. */ + __cpu_set_psw( PSW_CF_MASK | PSW_INXF_MASK | PSW_TF_MASK | PSW_IF_MASK ); + psw = __cpu_get_psw(); + + clear_instruction( &instruction, CPU_OP_NOP ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "NOP return code" ); + CHECK( __cpu_get_psw() == psw, "NOP preserves PSW" ); + + /* MOV is a raw physical-register copy and accepts overlap. */ + { + __mpu_UNIT_t *src = __cpu_reg_ptr( 128, 0x00100 ); + __mpu_UNIT_t *dst = __cpu_reg_ptr( 128, 0x00110 ); + unsigned int i; + + CHECK( src != 0 && dst != 0, "MOV register pointers" ); + + for( i = 0; i < 16; ++i ) src[i] = (__mpu_UNIT_t)( 0x20 + i ); + memset( dst, 0, 16 ); + + clear_instruction( &instruction, CPU_OP_MOV ); + instruction.operand[0] = reg_operand( 128, 0x00110 ); + instruction.operand[1] = reg_operand( 128, 0x00100 ); + + psw = __cpu_get_psw(); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "MOV return code" ); + CHECK( memcmp( dst, src, 16 ) == 0, "MOV register contents" ); + CHECK( __cpu_get_psw() == psw, "MOV preserves PSW" ); + + /* Overlap must have memmove semantics. */ + for( i = 0; i < 32; ++i ) __cpu_registers[0x00200 + i] = (__mpu_UNIT_t)i; + + clear_instruction( &instruction, CPU_OP_MOV ); + instruction.operand[0] = reg_operand( 128, 0x00210 ); + instruction.operand[1] = reg_operand( 128, 0x00200 ); + + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, + "overlapping MOV return code" ); + for( i = 0; i < 16; ++i ) + CHECK( __cpu_registers[0x00210 + i] == (__mpu_UNIT_t)i, + "overlapping MOV contents" ); + } + + /* CLR clears physical storage and leaves PSW unchanged. */ + { + __mpu_UNIT_t *dst = __cpu_reg_ptr( 256, 0x00300 ); + unsigned int i; + + CHECK( dst != 0, "CLR register pointer" ); + memset( dst, 0xa5, 32 ); + + clear_instruction( &instruction, CPU_OP_CLR ); + instruction.operand[0] = reg_operand( 256, 0x00300 ); + + psw = __cpu_get_psw(); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "CLR return code" ); + for( i = 0; i < 32; ++i ) CHECK( dst[i] == 0, "CLR register contents" ); + CHECK( __cpu_get_psw() == psw, "CLR preserves PSW" ); + } + + /* IADD delegates arithmetic and integer FLAGS to LibMPU. */ + { + __mpu_UNIT_t *dst = __cpu_reg_ptr( 8, 0x00400 ); + __mpu_UNIT_t *a = __cpu_reg_ptr( 8, 0x00401 ); + __mpu_UNIT_t *b = __cpu_reg_ptr( 8, 0x00402 ); + + *a = 0xff; + *b = 0x01; + *dst = 0x5a; + + __cpu_stt(); + __cpu_sti(); + CHECK( __cpu_stmode( 2 ) == 0, "set CPU status before IADD" ); + + clear_instruction( &instruction, CPU_OP_IADD ); + instruction.operand[0] = reg_operand( 8, 0x00400 ); + instruction.operand[1] = reg_operand( 8, 0x00401 ); + instruction.operand[2] = reg_operand( 8, 0x00402 ); + + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "IADD return code" ); + CHECK( *dst == 0x00, "IADD result" ); + CHECK( __mpu_gtc() == 1, "IADD carry flag" ); + CHECK( __mpu_gtz() == 1, "IADD zero flag" ); + CHECK( __cpu_gtt() == 1, "IADD preserves TF" ); + CHECK( __cpu_gti() == 1, "IADD preserves IF" ); + CHECK( __cpu_gtmode() == 2, "IADD preserves MODE" ); + } + + /* IADC consumes LibMPU carry from the preceding operation. */ + { + __mpu_UNIT_t *dst = __cpu_reg_ptr( 8, 0x00403 ); + __mpu_UNIT_t *a = __cpu_reg_ptr( 8, 0x00404 ); + __mpu_UNIT_t *b = __cpu_reg_ptr( 8, 0x00405 ); + + *a = 1; + *b = 2; + *dst = 0; + __mpu_stc(); + + clear_instruction( &instruction, CPU_OP_IADC ); + instruction.operand[0] = reg_operand( 8, 0x00403 ); + instruction.operand[1] = reg_operand( 8, 0x00404 ); + instruction.operand[2] = reg_operand( 8, 0x00405 ); + + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "IADC return code" ); + CHECK( *dst == 4, "IADC result with carry" ); + } + + /* ISUB and ISBB use the same three-register representation. */ + { + __mpu_UNIT_t *dst = __cpu_reg_ptr( 8, 0x00406 ); + __mpu_UNIT_t *a = __cpu_reg_ptr( 8, 0x00407 ); + __mpu_UNIT_t *b = __cpu_reg_ptr( 8, 0x00408 ); + + *a = 9; + *b = 3; + + clear_instruction( &instruction, CPU_OP_ISUB ); + instruction.operand[0] = reg_operand( 8, 0x00406 ); + instruction.operand[1] = reg_operand( 8, 0x00407 ); + instruction.operand[2] = reg_operand( 8, 0x00408 ); + + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "ISUB return code" ); + CHECK( *dst == 6, "ISUB result" ); + + *a = 9; + *b = 3; + __mpu_stc(); + + clear_instruction( &instruction, CPU_OP_ISBB ); + instruction.operand[0] = reg_operand( 8, 0x00406 ); + instruction.operand[1] = reg_operand( 8, 0x00407 ); + instruction.operand[2] = reg_operand( 8, 0x00408 ); + + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "ISBB return code" ); + CHECK( *dst == 5, "ISBB result with borrow" ); + } + + /* Operand validation belongs to the common executor. */ + clear_instruction( &instruction, CPU_OP_MOV ); + instruction.operand[0] = reg_operand( 16, 0x00500 ); + instruction.operand[1] = reg_operand( 32, 0x00504 ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_WIDTH_FAULT, + "MOV width mismatch" ); + + clear_instruction( &instruction, CPU_OP_IADD ); + instruction.operand[0] = reg_operand( 32, 0x00501 ); /* unaligned */ + instruction.operand[1] = reg_operand( 32, 0x00504 ); + instruction.operand[2] = reg_operand( 32, 0x00508 ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_REGISTER_FAULT, + "invalid register operand" ); + + clear_instruction( &instruction, CPU_OP_CLR ); + instruction.operand[0].type = CPU_OPERAND_IMMEDIATE; + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_OPERAND_FAULT, + "unsupported operand type" ); + + clear_instruction( &instruction, (cpu_opcode)0x7fff ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_OPCODE_FAULT, + "unknown opcode" ); + + CHECK( __cpu_exec( (const cpu_instruction *)0 ) == CPU_EXEC_INSTRUCTION_FAULT, + "NULL instruction" ); + + /* HALT sets only the CPU-owned HF part of PSW. */ + __cpu_set_psw( PSW_CF_MASK | PSW_INXF_MASK | PSW_IF_MASK ); + clear_instruction( &instruction, CPU_OP_HALT ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "HALT return code" ); + psw = __cpu_get_psw(); + CHECK( (psw & PSW_HF_MASK) != 0, "HALT sets HF" ); + CHECK( (psw & PSW_CF_MASK) != 0, "HALT preserves CF" ); + CHECK( (psw & PSW_INXF_MASK) != 0, "HALT preserves INXF" ); + CHECK( (psw & PSW_IF_MASK) != 0, "HALT preserves IF" ); + + if( failures != 0 ) + { + fprintf( stderr, "libcpu instruction regression: %d failure(s)\n", failures ); + return( 1 ); + } + + printf( "libcpu 0.0.3 decoded instruction/executor regression: PASS\n" ); + printf( "opcodes: NOP HALT MOV CLR IADD IADC ISUB ISBB\n" ); + printf( "binary encoding and addressing modes are not defined yet\n" ); + + return( 0 ); +} diff --git a/tests/tst-library-search-runner.sh b/tests/tst-library-search-runner.sh new file mode 100755 index 0000000..6c5ee73 --- /dev/null +++ b/tests/tst-library-search-runner.sh @@ -0,0 +1,118 @@ +#!/bin/sh +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-library-search-$$ +HOME="$TMP/home" +export HOME +mkdir -p "$HOME/.mcpu/etc" "$TMP/root/lib" "$TMP/first" "$TMP/second" "$TMP/user/lib" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +EXAMPLES=`cd "$TEST_SRCDIR/fixtures/shared-libs" && pwd` + +"$CPU_AS" -o "$TMP/main.o" "$EXAMPLES/dynamic-main.s" +"$CPU_AS" -o "$TMP/a.o" "$EXAMPLES/dynamic-liba.s" +"$CPU_AS" -o "$TMP/b.o" "$EXAMPLES/dynamic-libb.s" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -o "$TMP/main-program" "$TMP/main.o" +"$CPU_LD" --shared --soname=liba.so.1 -o "$TMP/liba.so" "$TMP/a.o" +"$CPU_LD" --shared --soname=libb.so.1 -o "$TMP/libb.so" "$TMP/b.o" + +cp "$TMP/liba.so" "$TMP/root/lib/liba.so" +cp "$TMP/libb.so" "$TMP/root/lib/libb.so" +cp "$TMP/liba.so" "$TMP/root/lib/libpick.so" +cp "$TMP/libb.so" "$TMP/first/libpick.so" +cp "$TMP/liba.so" "$TMP/first/libfirst.so" +cp "$TMP/liba.so" "$TMP/first/liborder.so" +cp "$TMP/libb.so" "$TMP/second/libpick.so" +cp "$TMP/libb.so" "$TMP/second/libsecond.so" +cp "$TMP/libb.so" "$TMP/second/liborder.so" + +# A bare preload name is resolved through <sys-root>/lib. +[ "`"$CPU_RUN" --sys-root="$TMP/root" --int-reg=r64@0x00018 \ + --ldpreload=liba.so "$TMP/main-program"`" = 42 ] + +# Explicit --library-path directories have priority over the system tree. +[ "`"$CPU_RUN" --sys-root="$TMP/root" --library-path="$TMP/first" \ + --int-reg=r64@0x00018 --ldpreload=libpick.so "$TMP/main-program"`" = 43 ] + +# Colon-list order is the actual search order. +[ "`"$CPU_RUN" --sys-root="$TMP/root" \ + --library-path="$TMP/first:$TMP/second" --int-reg=r64@0x00018 \ + --ldpreload=liborder.so "$TMP/main-program"`" = 42 ] +[ "`"$CPU_RUN" --sys-root="$TMP/root" \ + --library-path="$TMP/second:$TMP/first" --int-reg=r64@0x00018 \ + --ldpreload=liborder.so "$TMP/main-program"`" = 43 ] + +# Repeated --library-path occurrences append in command-line order. +cat > "$TMP/run-search.expected" <<EOT +libraries: + $TMP/first + $TMP/second + $TMP/root/lib +EOT +"$CPU_RUN" --sys-root="$TMP/root" --library-path="$TMP/first" \ + --library-path "$TMP/second" --print-search-dirs > "$TMP/run-search.txt" +cmp "$TMP/run-search.expected" "$TMP/run-search.txt" + +# mcpu-ld uses the same common search state through -L. +cat > "$TMP/ld-search.expected" <<EOT +libraries: + $TMP/first + $TMP/second + $TMP/root/lib +EOT +"$CPU_LD" --sys-root="$TMP/root" -L"$TMP/first" -L "$TMP/second" \ + --print-search-dirs > "$TMP/ld-search.txt" +cmp "$TMP/ld-search.expected" "$TMP/ld-search.txt" + +# Explicit pathnames keep the historical behavior and bypass name lookup. +[ "`"$CPU_RUN" --sys-root="$TMP/root" --library-path="$TMP/first" \ + --int-reg=r64@0x00018 --ldpreload="$TMP/root/lib/liba.so" \ + "$TMP/main-program"`" = 42 ] + +# Resolving a list does not change the established rightmost preload priority. +[ "`"$CPU_RUN" --sys-root="$TMP/root" --int-reg=r64@0x00018 \ + --ldpreload=liba.so:libb.so "$TMP/main-program"`" = 43 ] +[ "`"$CPU_RUN" --sys-root="$TMP/root" --int-reg=r64@0x00018 \ + --ldpreload=libb.so:liba.so "$TMP/main-program"`" = 42 ] + +# The configured system path participates in real name lookup. +cp "$TMP/liba.so" "$TMP/user/lib/libuser.so" +cat > "$HOME/.mcpu/etc/mcpu-ld.conf" <<EOC +MCPU_LD_SYSTEM_LIBRARY_PATH = $TMP/user/lib; +EOC +[ "`"$CPU_RUN" --int-reg=r64@0x00018 --ldpreload=libuser.so \ + "$TMP/main-program"`" = 42 ] + +# An explicitly empty system path has no fallback, while an explicit path works. +printf '%s\n' 'MCPU_LD_SYSTEM_LIBRARY_PATH = ;' \ + > "$HOME/.mcpu/etc/mcpu-ld.conf" +if "$CPU_RUN" --ldpreload=liba.so "$TMP/main-program" >/dev/null 2>&1; then + echo "bare preload unexpectedly resolved with empty system path" >&2 + exit 1 +fi +[ "`"$CPU_RUN" --library-path="$TMP/root/lib" --int-reg=r64@0x00018 \ + --ldpreload=liba.so "$TMP/main-program"`" = 42 ] + +# Missing names and empty path-list elements are errors. +if "$CPU_RUN" --sys-root="$TMP/root" --ldpreload=does-not-exist.so \ + "$TMP/main-program" >/dev/null 2>&1; then + echo "missing preload name unexpectedly resolved" >&2 + exit 1 +fi +if "$CPU_RUN" --sys-root="$TMP/root" --library-path="$TMP/first::$TMP/second" \ + --print-search-dirs >/dev/null 2>&1; then + echo "empty --library-path element unexpectedly accepted" >&2 + exit 1 +fi + +# 0.2.15 exposes -l for real link-time dependency resolution. Search-dir +# diagnostics remain independent of whether a later link uses library operands. +"$CPU_LD" --help 2>&1 | grep -q -- '-l <name>' + +"$CPU_RUN" --help 2>&1 | grep -q -- '--library-path' +"$CPU_LD" --help 2>&1 | grep -q -- '-L <dir>' + +exit 0 diff --git a/tests/tst-lifecycle-runner.sh b/tests/tst-lifecycle-runner.sh new file mode 100755 index 0000000..c25a29a --- /dev/null +++ b/tests/tst-lifecycle-runner.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +base="${TMPDIR:-/tmp}/libcpu-lifecycle-$$" +trap 'rm -f "$base.o" "$base.melf"' EXIT HUP INT TERM +"$CPU_AS" -o "$base.o" "$TEST_SRCDIR/fixtures/model-lifecycle.s" >/dev/null +"$CPU_LD" --stack-size=512 -o "$base.melf" "$base.o" >/dev/null +out=`"$CPU_RUN" --init-only --int-reg=r64@0x00000 "$base.melf"` +test "x$out" = "x40" +out=`"$CPU_RUN" --steps=1 --int-reg=r64@0x00000 "$base.melf"` +test "x$out" = "x42" diff --git a/tests/tst-lifecycle.c b/tests/tst-lifecycle.c new file mode 100644 index 0000000..6e2a7d6 --- /dev/null +++ b/tests/tst-lifecycle.c @@ -0,0 +1,63 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) +static const char src[]= +".init\n" +"_start:\n" +" LDI r64@0x00000, 40\n" +" END\n" +".text\n" +"_life:\n" +" IINC r64@0x00000, r64@0x00000\n" +" END\n" +" IINC r64@0x00000, r64@0x00000\n" +" LDI r32@0x00020, 7\n" +" EXIT r32@0x00020\n" +".fini\n" +"_fini:\n" +" LDI r64@0x00008, 99\n" +" END\n"; +static __mpu_uint64_t +get64( unsigned a ) +{ + return( __cpu_data_get_u64( __cpu_reg_ptr( 64, a ) ) ); +} + +int +main( void ) +{ + cpu_asm_image im; + char err[256]; + int rc; + __cpu_init(); + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( im.has_init && im.has_life && im.has_fini, "three lifecycle entries present" ); + CHECK( im.init_entry < im.life_entry && im.life_entry < im.fini_entry, "entry order in PROGRAM" ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + CHECK( __cpu_data_set( im.data, im.data_size ) == 0, "data set" ); + CHECK( __cpu_stack_alloc( 256 ) == 0, "stack alloc" ); + __cpu_pc = im.init_entry; + rc = __cpu_run(); + CHECK( rc == CPU_EXEC_END, ".init END returned" ); + CHECK( __cpu_get_stop_reason() == CPU_STOP_END, "INIT stop reason END" ); + CHECK( get64( 0 ) == 40, "init result" ); + __cpu_pc = im.life_entry; + rc = __cpu_run(); + CHECK( rc == CPU_EXEC_END, "first LIFE END returned" ); + CHECK( get64( 0 ) == 41, "first life step" ); + rc = __cpu_run(); + CHECK( rc == CPU_EXEC_EXIT, "resume reaches EXIT" ); + CHECK( __cpu_get_stop_reason() == CPU_STOP_EXIT, "EXIT stop reason" ); + CHECK( __cpu_get_exit_status() == 7, "EXIT status" ); + CHECK( get64( 0 ) == 42, "resume PC is after END" ); + __cpu_pc = im.fini_entry; + rc = __cpu_run(); + CHECK( rc == CPU_EXEC_END, ".fini END returned" ); + CHECK( get64( 8 ) == 99, "fini result" ); + cpu_asm_image_free( &im ); + __cpu_free_context(); + puts( "libcpu lifecycle ABI regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-line-runner.sh b/tests/tst-line-runner.sh new file mode 100755 index 0000000..ed62877 --- /dev/null +++ b/tests/tst-line-runner.sh @@ -0,0 +1,174 @@ +#!/bin/sh +set -eu + +: "${CPU_AS:?CPU_AS is not set}" + +tmp="${TMPDIR:-/tmp}/libcpu-line-$$" +trap 'rm -rf "$tmp"' EXIT HUP INT TERM +mkdir -p "$tmp" + +# --help remains a compact public interface. Preprocessor source-control +# details belong to the documentation, not to the command-line synopsis. +"$CPU_AS" --help >"$tmp/help.txt" 2>&1 +grep -q -- '--help' "$tmp/help.txt" +grep -q -- '-o <file>' "$tmp/help.txt" +if grep -q -- '#line' "$tmp/help.txt"; then + echo "mcpu-as --help unexpectedly documents obsolete #line" >&2 + exit 1 +fi + +# The assembler accepts the usual UNIX-style option ordering. -c is no +# longer part of the interface because mcpu-as always produces ET_REL. +cat >"$tmp/cli.s" <<'SRC' +.text +.global helper +helper: + RET +SRC +"$CPU_AS" -o "$tmp/cli-before.o" "$tmp/cli.s" >/dev/null +"$CPU_AS" "$tmp/cli.s" -o "$tmp/cli-after.o" >/dev/null +test -s "$tmp/cli-before.o" +test -s "$tmp/cli-after.o" +cmp "$tmp/cli-before.o" "$tmp/cli-after.o" +if "$CPU_AS" -c "$tmp/cli.s" -o "$tmp/cli-old.o" >"$tmp/out.txt" 2>"$tmp/err.txt"; then + echo "mcpu-as unexpectedly accepted obsolete -c" >&2 + exit 1 +fi +grep -Fq 'mcpu-as: unknown option -c' "$tmp/err.txt" + +# Native diagnostics count every ordinary physical source line, including a +# zero-length line. +cat >"$tmp/physical.s" <<'SRC' +.text + +BAD_OPCODE +SRC +if "$CPU_AS" -o "$tmp/physical.o" "$tmp/physical.s" >"$tmp/out.txt" 2>"$tmp/err.txt"; then + echo "mcpu-as unexpectedly accepted invalid physical-line source" >&2 + exit 1 +fi +grep -Fq "$tmp/physical.s:3: error:" "$tmp/err.txt" + +# Preprocessor line markers set the logical number and optional filename of the +# source line immediately following the marker. Trailing preprocessor flags +# are deliberately ignored by mcpu-as. +cat >"$tmp/marker.S" <<'SRC' +# 7 "included source.s" 1 3 4 anything-after-the-filename-is-ignored +.text +THIS_IS_NOT_AN_INSTRUCTION +SRC +if "$CPU_AS" -o "$tmp/marker.o" "$tmp/marker.S" >"$tmp/out.txt" 2>"$tmp/err.txt"; then + echo "mcpu-as unexpectedly accepted invalid marker source" >&2 + exit 1 +fi +grep -Fq 'included source.s:8: error:' "$tmp/err.txt" + +# The logical counter continues across empty ordinary lines. +cat >"$tmp/blank-lines.S" <<'SRC' +# 4 "dyn.s" + +.text +helper: +BAD_OPCODE +SRC +if "$CPU_AS" -o "$tmp/blank-lines.o" "$tmp/blank-lines.S" >"$tmp/out.txt" 2>"$tmp/err.txt"; then + echo "mcpu-as unexpectedly accepted invalid blank-line source" >&2 + exit 1 +fi +grep -Fq 'dyn.s:7: error:' "$tmp/err.txt" + +# Omitting the filename retains the previous logical filename. Leading zeroes +# remain decimal: 0200 means line 200, not octal 128. +cat >"$tmp/preserve.s" <<'SRC' +# 100 "first-name.s" +# 0200 +.text +BAD_OPCODE +SRC +if "$CPU_AS" -o "$tmp/preserve.o" "$tmp/preserve.s" >"$tmp/out.txt" 2>"$tmp/err.txt"; then + echo "mcpu-as unexpectedly accepted invalid preserve test" >&2 + exit 1 +fi +grep -Fq 'first-name.s:201: error:' "$tmp/err.txt" + +# Zero is accepted. Here .text is logical line 0 and the bad opcode is line 1. +cat >"$tmp/zero.s" <<'SRC' +# 0 "zero.s" +.text +BAD_OPCODE +SRC +if "$CPU_AS" -o "$tmp/zero.o" "$tmp/zero.s" >"$tmp/out.txt" 2>"$tmp/err.txt"; then + echo "mcpu-as unexpectedly accepted invalid zero-line test" >&2 + exit 1 +fi +grep -Fq 'zero.s:1: error:' "$tmp/err.txt" + +# The accepted decimal marker range remains 0..2147483647. +cat >"$tmp/range.s" <<'SRC' +# 2147483648 "too-large.s" +.text +SRC +if "$CPU_AS" -o "$tmp/range.o" "$tmp/range.s" >"$tmp/out.txt" 2>"$tmp/err.txt"; then + echo "mcpu-as unexpectedly accepted an out-of-range line marker" >&2 + exit 1 +fi +grep -Fq 'range.s:1: error: preprocessor line marker value is outside 0..2147483647' "$tmp/err.txt" + +# #pragma and #lang are passed through by preprocessing stages for other +# consumers. mcpu-as ignores them and they do not advance the logical counter. +cat >"$tmp/meta.s" <<'SRC' +# 100 "meta.s" +#pragma once +#lang "mcpu" +BAD_OPCODE +SRC +if "$CPU_AS" -o "$tmp/meta.o" "$tmp/meta.s" >"$tmp/out.txt" 2>"$tmp/err.txt"; then + echo "mcpu-as unexpectedly accepted invalid pragma/lang source" >&2 + exit 1 +fi +grep -Fq 'meta.s:100: error:' "$tmp/err.txt" + +# Other '#' lines are discarded as assembler syntax but still count as normal +# logical source lines. +cat >"$tmp/hash.s" <<'SRC' +# 50 "hash.s" +#define SOMETHING 1 +#unknown whatever +BAD_OPCODE +SRC +if "$CPU_AS" -o "$tmp/hash.o" "$tmp/hash.s" >"$tmp/out.txt" 2>"$tmp/err.txt"; then + echo "mcpu-as unexpectedly accepted invalid generic-hash source" >&2 + exit 1 +fi +grep -Fq 'hash.s:52: error:' "$tmp/err.txt" + +# Historical #line is no longer a source-location directive. It is simply an +# unrecognized '#' line: ignored as syntax, counted as a normal source line, +# and it must not replace the diagnostic filename or line counter. +cat >"$tmp/obsolete-line.s" <<'SRC' +#line 700 "obsolete.s" +.text +BAD_OPCODE +SRC +if "$CPU_AS" -o "$tmp/obsolete-line.o" "$tmp/obsolete-line.s" >"$tmp/out.txt" 2>"$tmp/err.txt"; then + echo "mcpu-as unexpectedly accepted invalid obsolete-#line source" >&2 + exit 1 +fi +grep -Fq "$tmp/obsolete-line.s:3: error:" "$tmp/err.txt" +if grep -Fq 'obsolete.s:' "$tmp/err.txt"; then + echo "obsolete #line unexpectedly changed source coordinates" >&2 + exit 1 +fi + +# A normal preprocessor marker is accepted independently of the input suffix. +cat >"$tmp/good.s" <<'SRC' +# 42 "generated/original source.s" 1 +.text +.global helper +helper: + RET +SRC +"$CPU_AS" -o "$tmp/good.o" "$tmp/good.s" >/dev/null +test -s "$tmp/good.o" + +echo "libmcpu mcpu-as preprocessor source-control regression: PASS" diff --git a/tests/tst-link-needed-runner.sh b/tests/tst-link-needed-runner.sh new file mode 100755 index 0000000..8f33986 --- /dev/null +++ b/tests/tst-link-needed-runner.sh @@ -0,0 +1,151 @@ +#!/bin/sh +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${CPU_READELF:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-link-needed-$$ +HOME="$TMP/home" +export HOME +mkdir -p "$HOME/.mcpu/etc" "$TMP/root/lib" "$TMP/first" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +cat > "$TMP/main.s" <<'EOS' +.global _life +.extern foo +.text +_start_life: + LDI r64@0x00000, 40 + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/good.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +cat > "$TMP/alt.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +cat > "$TMP/bad.s" <<'EOS' +.global other +.text +other: + RET +EOS + +"$CPU_AS" -o "$TMP/main.o" "$TMP/main.s" +"$CPU_AS" -o "$TMP/good.o" "$TMP/good.s" +"$CPU_AS" -o "$TMP/alt.o" "$TMP/alt.s" +"$CPU_AS" -o "$TMP/bad.o" "$TMP/bad.s" + +# The development link name is libfoo.so, but the ABI identity recorded in +# DT_NEEDED comes from the validated library SONAME. +"$CPU_LD" --shared --soname=libfoo.so.1 \ + -o "$TMP/root/lib/libfoo.so.1.2.3" "$TMP/good.o" +ln -s libfoo.so.1.2.3 "$TMP/root/lib/libfoo.so.1" +ln -s libfoo.so.1 "$TMP/root/lib/libfoo.so" + +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/root/lib" -lfoo -o "$TMP/main" "$TMP/main.o" +"$CPU_READELF" -d "$TMP/main" > "$TMP/main.dynamic" +grep -q 'NEEDED.*\[libfoo.so.1\]' "$TMP/main.dynamic" +[ "`grep -c 'NEEDED' "$TMP/main.dynamic"`" -eq 1 ] +if grep -q 'libfoo.so.1.2.3' "$TMP/main.dynamic"; then + echo "DT_NEEDED unexpectedly contains physical library filename" >&2 + exit 1 +fi + +# The separated -l NAME spelling is equivalent. +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L "$TMP/root/lib" -l foo -o "$TMP/main-separated" "$TMP/main.o" +"$CPU_READELF" -d "$TMP/main-separated" | grep -q 'NEEDED.*\[libfoo.so.1\]' + +# Repeated requests resolving to the same SONAME produce one dependency. +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/root/lib" -lfoo -lfoo -o "$TMP/main-duplicate" "$TMP/main.o" +"$CPU_READELF" -d "$TMP/main-duplicate" > "$TMP/main-duplicate.dynamic" +[ "`grep -c 'NEEDED' "$TMP/main-duplicate.dynamic"`" -eq 1 ] + +# -L order is real link-name search order. The selected object's SONAME, +# not the requested link name, is recorded. +"$CPU_LD" --shared --soname=libfoo-alt.so.7 \ + -o "$TMP/first/libfoo.so" "$TMP/alt.o" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/first" -L"$TMP/root/lib" -lfoo \ + -o "$TMP/main-first" "$TMP/main.o" +"$CPU_READELF" -d "$TMP/main-first" | grep -q 'NEEDED.*\[libfoo-alt.so.7\]' + +# The configured system library tree is also used by -l. +cat > "$HOME/.mcpu/etc/mcpu-ld.conf" <<EOC +MCPU_LD_SYSTEM_LIBRARY_PATH = $TMP/root/lib; +EOC +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -lfoo -o "$TMP/main-config" "$TMP/main.o" +"$CPU_READELF" -d "$TMP/main-config" | grep -q 'NEEDED.*\[libfoo.so.1\]' + +# A shared object may itself record a direct ABI-v3 dependency. +"$CPU_LD" --shared --soname=libchild.so.1 \ + -lfoo -o "$TMP/libchild.so.1.0.0" "$TMP/bad.o" +"$CPU_READELF" -d "$TMP/libchild.so.1.0.0" > "$TMP/child.dynamic" +grep -q 'SONAME.*\[libchild.so.1\]' "$TMP/child.dynamic" +grep -q 'NEEDED.*\[libfoo.so.1\]' "$TMP/child.dynamic" + +# A named dependency must exist and must be a valid ABI-v3 ET_DYN object. +if "$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/root/lib" -lmissing -o "$TMP/missing" "$TMP/main.o" \ + >/dev/null 2>&1; then + echo "missing -l dependency unexpectedly linked" >&2 + exit 1 +fi + +cp "$TMP/main" "$TMP/root/lib/libexec.so" +if "$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/root/lib" -lexec -o "$TMP/from-exec" "$TMP/main.o" \ + >/dev/null 2>&1; then + echo "ET_EXEC unexpectedly accepted as -l provider" >&2 + exit 1 +fi + +# Existing file, valid shared object, wrong exports: link must fail before +# producing an executable. +"$CPU_LD" --shared --soname=libbad.so.1 \ + -o "$TMP/root/lib/libbad.so" "$TMP/bad.o" +rm -f "$TMP/no-provider" +if "$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/root/lib" -lbad -o "$TMP/no-provider" "$TMP/main.o" \ + >"$TMP/no-provider.out" 2>"$TMP/no-provider.err"; then + echo "library without required export unexpectedly linked" >&2 + exit 1 +fi +grep -q 'undefined symbol not provided by linked libraries: foo' \ + "$TMP/no-provider.err" +[ ! -e "$TMP/no-provider" ] + +# ABI-v1 -l searches only for a static archive. This directory contains +# only libfoo.so, so the static link must fail instead of using ET_DYN. +if "$CPU_LD" -L"$TMP/root/lib" -lfoo -o "$TMP/static" "$TMP/main.o" \ + >/dev/null 2>&1; then + echo "ABI-v1 unexpectedly selected a shared -l provider" >&2 + exit 1 +fi + +# 0.2.16 resolves the recorded SONAME at runtime through the same system +# library search policy. No explicit --ldpreload is required. +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/main"`" = 42 ] + +"$CPU_LD" --help 2>&1 | grep -q -- '-l <name>' + +exit 0 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 ); +} diff --git a/tests/tst-memory.c b/tests/tst-memory.c new file mode 100644 index 0000000..ee31477 --- /dev/null +++ b/tests/tst-memory.c @@ -0,0 +1,85 @@ +#include <stdio.h> +#include <signal.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <libmcpu.h> + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static int +page_aligned( const void *p, size_t page ) +{ + return( p != 0 && ( (size_t)p % page ) == 0 ); +} + +int +main( void ) +{ + long ps; + size_t page; + pid_t pid; + int status; + __mpu_UNIT_t code[3] = + { + 0x00, 0x00, 0x00 + }; + + __cpu_init(); + ps = sysconf( _SC_PAGESIZE ); + CHECK( ps > 0, "host page size" ); + page = (size_t)ps; + + CHECK( CPU_ABI_MAX_ALIGN == 16U, "ABI maximum natural alignment" ); + CHECK( CPU_ABI_STACK_ALIGN == 16U, "ABI stack alignment" ); + CHECK( CPU_ABI_ARGUMENT_BLOCK_ALIGN == 16U, "ABI argument block alignment" ); + CHECK( CPU_DATA_NULL_GUARD_SIZE == 16U, "DATA NULL guard size" ); + CHECK( CPU_NULL_PTR == __mpu_UINT64_C( 0 ), "NULL PTR64 encoding" ); + + CHECK( __cpu_data_alloc( 1 ) == 0, "allocate one-byte DATA" ); + CHECK( __cpu_data_size() == 1, "DATA architectural size is exact" ); + CHECK( page_aligned( __cpu_data_ptr(), page ), "DATA host base is page aligned" ); + CHECK( __cpu_data_ptr()[0] == 0, "anonymous DATA mapping is zero-filled" ); + __cpu_data_ptr()[0] = 0x5a; + CHECK( __cpu_data_ptr()[0] == 0x5a, "DATA mapping is writable" ); + + CHECK( __cpu_stack_alloc( 1 ) == 0, "allocate one-byte STACK" ); + CHECK( __cpu_stack_size() == 1, "STACK architectural size is exact" ); + CHECK( page_aligned( __cpu_stack_ptr(), page ), "STACK host base is page aligned" ); + CHECK( __cpu_stack_ptr()[0] == 0, "anonymous STACK mapping is zero-filled" ); + CHECK( __cpu_sp == 1 && __cpu_fp == 1, "empty STACK uses exact architectural size" ); + + CHECK( __cpu_program_alloc( sizeof( code ) ) == 0, "allocate PROGRAM for loading" ); + CHECK( __cpu_program_size() == sizeof( code ), "PROGRAM architectural size is exact" ); + CHECK( page_aligned( __cpu_program_ptr(), page ), "PROGRAM host base is page aligned" ); + memcpy( __cpu_program_ptr(), code, sizeof( code ) ); + __cpu_program_ptr()[0] = 0x12; + /* PROGRAM is writable during image loading. */ + CHECK( __cpu_program_ptr()[0] == 0x12, "PROGRAM writable before protect" ); + CHECK( __cpu_program_protect() == 0, "protect PROGRAM read-only" ); + + /* Verify host protection without crashing the test process itself. */ + pid = fork(); + CHECK( pid >= 0, "fork for PROGRAM protection test" ); + if( pid == 0 ) + { + volatile __mpu_UNIT_t *p = __cpu_program_ptr(); + p[0] = 0x34; + _exit( 0 ); + } + CHECK( waitpid( pid, &status, 0 ) == pid, "wait for protection child" ); + CHECK( WIFSIGNALED( status ), "PROGRAM host write is trapped" ); + CHECK( WTERMSIG( status ) == SIGSEGV || WTERMSIG( status ) == SIGBUS, + "PROGRAM write fault signal"); + + /* Reallocation must unmap the protected image and create a fresh RW mapping. */ + CHECK( __cpu_program_alloc( 2 ) == 0, "reallocate PROGRAM after protection" ); + CHECK( __cpu_program_size() == 2, "reallocated PROGRAM exact size" ); + __cpu_program_ptr()[0] = 0x56; + CHECK( __cpu_program_ptr()[0] == 0x56, "new PROGRAM mapping writable for loading" ); + + __cpu_free_context(); + puts( "libcpu anonymous-mmap memory regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-model-segments.c b/tests/tst-model-segments.c new file mode 100644 index 0000000..686bee9 --- /dev/null +++ b/tests/tst-model-segments.c @@ -0,0 +1,108 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static const char src[]= +".control\n" +"ctl:\n" +" .integer 64, 10\n" +".input\n" +"stimulus:\n" +" .integer 64, 32\n" +".output\n" +"answer:\n" +" .integer 64, 0\n" +".trace\n" +"sample:\n" +" .integer 64, 0\n" +".text\n" +"_life:\n" +" LEA r64@0x00000, ctl\n" +" LEA r64@0x00008, stimulus\n" +" LEA r64@0x00010, answer\n" +" LEA r64@0x00018, sample\n" +" LOADP r64@0x00020, r64@0x00000\n" +" LOADP r64@0x00028, r64@0x00008\n" +" IADD r64@0x00030, r64@0x00020, r64@0x00028\n" +" STOREP r64@0x00010, r64@0x00030\n" +" STOREP r64@0x00018, r64@0x00030\n" +" HALT\n"; + +static __mpu_uint64_t +r64( unsigned a ) +{ + return( __cpu_data_get_u64( __cpu_reg_ptr( 64, a ) ) ); +} + +static void +regop( cpu_operand *o, unsigned bits, unsigned addr ) +{ + memset( o, 0, sizeof( *o ) ); + o->type = CPU_OPERAND_REGISTER; + o->value.reg.bits = bits; + o->value.reg.address = addr; +} + +int +main( void ) +{ + cpu_asm_image im; + cpu_instruction x; + char err[256]; + __mpu_uint64_t p; + int rc; + __cpu_init(); + CHECK( CPU_PTR_SEG_CONTROL == 3, "CONTROL selector" ); + CHECK( CPU_PTR_SEG_INPUT == 4, "INPUT selector" ); + CHECK( CPU_PTR_SEG_OUTPUT == 5, "OUTPUT selector" ); + CHECK( CPU_PTR_SEG_TRACE == 6, "TRACE selector" ); + CHECK( CPU_PTR_SEG_HEAP == 7, "HEAP selector" ); + CHECK( CPU_PTR_SEG_FIRST_RESERVED == 8, "reserved selectors begin at 8" ); + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( im.control_size == 8 && im.input_size == 8 && im.output_size == 8 && im.trace_size == 8, "model segment image sizes" ); + CHECK( im.has_life && im.life_entry == 0, "_life entry metadata" ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + CHECK( __cpu_data_set( im.data, im.data_size ) == 0, "data set" ); + CHECK( __cpu_control_set( im.control, im.control_size ) == 0, "control set" ); + CHECK( __cpu_input_set( im.input, im.input_size ) == 0, "input set" ); + CHECK( __cpu_output_set( im.output, im.output_size ) == 0, "output set" ); + CHECK( __cpu_trace_set( im.trace, im.trace_size ) == 0, "trace set" ); + CHECK( __cpu_stack_alloc( 256 ) == 0, "stack alloc" ); + CHECK( __cpu_heap_alloc( 128, 1024 ) == 0, "heap alloc" ); + __cpu_pc = im.life_entry; + CHECK( __cpu_run() == CPU_EXEC_SUCCESS, "segment program run" ); + CHECK( __cpu_ptr_segment( r64( 0x00 ) ) == CPU_PTR_SEG_CONTROL, "LEA CONTROL" ); + CHECK( __cpu_ptr_segment( r64( 0x08 ) ) == CPU_PTR_SEG_INPUT, "LEA INPUT" ); + CHECK( __cpu_ptr_segment( r64( 0x10 ) ) == CPU_PTR_SEG_OUTPUT, "LEA OUTPUT" ); + CHECK( __cpu_ptr_segment( r64( 0x18 ) ) == CPU_PTR_SEG_TRACE, "LEA TRACE" ); + CHECK( __cpu_data_get_u64( __cpu_output_ptr() ) == 42, "OUTPUT guest write" ); + CHECK( __cpu_data_get_u64( __cpu_trace_ptr() ) == 42, "TRACE guest write" ); + + memset( &x, 0, sizeof( x ) ); + x.opcode = CPU_OP_STOREP; + regop( &x.operand[0], 64, 0x00 ); + regop( &x.operand[1], 64, 0x30 ); + CHECK( __cpu_exec( &x ) == CPU_EXEC_MEMORY_FAULT, "CONTROL is guest read-only" ); + regop( &x.operand[0], 64, 0x08 ); + CHECK( __cpu_exec( &x ) == CPU_EXEC_MEMORY_FAULT, "INPUT is guest read-only" ); + + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_HEAP, CPU_HEAP_BEGIN ) == 0, "make HEAP pointer" ); + __cpu_data_put_u64( __cpu_reg_ptr( 64, 0x38 ), p ); + regop( &x.operand[0], 64, 0x38 ); + CHECK( __cpu_exec( &x ) == CPU_EXEC_MEMORY_FAULT, "HEAP is protected above initial _end break" ); + CHECK( __cpu_heap_size() == 128 && __cpu_heap_limit() == 1024, "HEAP size/limit ABI" ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_HEAP, 0 ) == 0, "make HEAP guard pointer" ); + __cpu_data_put_u64( __cpu_reg_ptr( 64, 0x38 ), p ); + CHECK( __cpu_exec( &x ) == CPU_EXEC_MEMORY_FAULT, "HEAP low guard rejects access" ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_FIRST_RESERVED, 0 ) == 0, "make reserved pointer" ); + __cpu_data_put_u64( __cpu_reg_ptr( 64, 0x38 ), p ); + rc = __cpu_exec( &x ); + CHECK( rc == CPU_EXEC_POINTER_FAULT, "reserved selector still faults" ); + + cpu_asm_image_free( &im ); + __cpu_free_context(); + puts( "libcpu model-segment/HEAP regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-mpu-coverage.c b/tests/tst-mpu-coverage.c new file mode 100644 index 0000000..0c8e3a3 --- /dev/null +++ b/tests/tst-mpu-coverage.c @@ -0,0 +1,580 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> + +static int failures; +#define CHECK(c,m) do { if(!(c)){ fprintf(stderr,"FAIL: %s\n",(m)); ++failures; } } while(0) + +static cpu_operand +R( unsigned bits, __mpu_uint32_t addr ) +{ + cpu_operand o; + memset( &o, 0, sizeof( o ) ); + o.type = CPU_OPERAND_REGISTER; + o.value.reg.bits = bits; + o.value.reg.address = addr; + return( o ); +} + +static void +I( cpu_instruction *i, cpu_opcode op ) +{ + memset( i, 0, sizeof( *i ) ); + i->opcode = op; +} + +static __mpu_UNIT_t * +P( unsigned bits, __mpu_uint32_t addr ) +{ + return( __cpu_reg_ptr( bits, addr ) ); +} + +static void +put64( unsigned addr, __mpu_uint64_t v ) +{ + __cpu_data_put_u64( P( 64, addr ), v ); +} + +static __mpu_uint64_t +get64( unsigned addr ) +{ + return( __cpu_data_get_u64( P( 64, addr ) ) ); +} + +static __mpu_int64_t +geti64( unsigned addr ) +{ + __mpu_uint64_t u = get64( addr ); + __mpu_int64_t v; + memcpy( &v, &u, 8 ); + return( v ); +} + +static void +setreal( unsigned bits, unsigned addr, const char *s ) +{ + int nb = (int)( bits / 8U ); + CHECK( ascii_to_real( ( mpu_real * ) P( bits, addr ), (__mpu_char8_t *)s, nb ) >= 0, "ascii_to_real" ); +} + +int +main( void ) +{ + cpu_instruction x; + __cpu_init(); + + /* Integer shifts/rotates: compare CPU output with the matching LibMPU API. */ + { + struct S + { + cpu_opcode op; + void( *fn )( mpu_int *, mpu_int *, int ); + const char *n; + } + v[] = + { + {CPU_OP_ISHL,ishl,"ISHL"},{CPU_OP_ISHR,ishr,"ISHR"},{CPU_OP_ISAL,isal,"ISAL"},{CPU_OP_ISAR,isar,"ISAR"}, + {CPU_OP_IROL,irol,"IROL"},{CPU_OP_IROR,iror,"IROR"},{CPU_OP_IRCL,ircl,"IRCL"},{CPU_OP_IRCR,ircr,"IRCR"} + }; + unsigned k; + mpu_int8_t ref, src; + for( k = 0; k < sizeof( v ) / sizeof( v[0] ); k++ ) + { + src[0] = 0x81; + ref[0] = 0; + if( v[k].op == CPU_OP_IRCL || v[k].op == CPU_OP_IRCR ) __mpu_stc(); + else __mpu_clc(); + v[k].fn( ( mpu_int * ) ref, ( mpu_int * ) src, 1 ); + *P( 8, 0x100 ) = 0x81; + *P( 8, 0x101 ) = 0; + if( v[k].op == CPU_OP_IRCL || v[k].op == CPU_OP_IRCR ) __mpu_stc(); + else __mpu_clc(); + I( &x, v[k].op ); + x.operand[0] = R( 8, 0x101 ); + x.operand[1] = R( 8, 0x100 ); + CHECK( __cpu_exec( &x ) == 0, v[k].n ); + CHECK( *P( 8, 0x101 ) == ref[0], v[k].n ); + } + } + { + struct N + { + cpu_opcode op; + void( *fn )( mpu_int *, mpu_int *, unsigned int, int ); + const char *n; + } + v[] = + { + {CPU_OP_ISHLN,ishln,"ISHLN"},{CPU_OP_ISHRN,ishrn,"ISHRN"},{CPU_OP_ISALN,isaln,"ISALN"},{CPU_OP_ISARN,isarn,"ISARN"}, + {CPU_OP_IROLN,iroln,"IROLN"},{CPU_OP_IRORN,irorn,"IRORN"},{CPU_OP_IRCLN,ircln,"IRCLN"},{CPU_OP_IRCRN,ircrn,"IRCRN"} + }; + unsigned k; + mpu_int16_t ref, src; + for( k = 0; k < sizeof( v ) / sizeof( v[0] ); k++ ) + { + __cpu_data_put_u16( src, 0x8123 ); + if( v[k].op == CPU_OP_IRCLN || v[k].op == CPU_OP_IRCRN ) __mpu_stc(); + else __mpu_clc(); + v[k].fn( ( mpu_int * ) ref, ( mpu_int * ) src, 3, 2 ); + __cpu_data_put_u16( P( 16, 0x120 ), 0x8123 ); + put64( 0x128, 3 ); + if( v[k].op == CPU_OP_IRCLN || v[k].op == CPU_OP_IRCRN ) __mpu_stc(); + else __mpu_clc(); + I( &x, v[k].op ); + x.operand[0] = R( 16, 0x122 ); + x.operand[1] = R( 16, 0x120 ); + x.operand[2] = R( 64, 0x128 ); + CHECK( __cpu_exec( &x ) == 0, v[k].n ); + CHECK( memcmp( P( 16, 0x122 ), ref, 2 ) == 0, v[k].n ); + } + } + + /* INC/DEC/XCHG and width-changing integer copy/conversion. */ + *P( 8, 0x140 ) = 41; + I( &x, CPU_OP_IINC ); + x.operand[0] = R( 8, 0x141 ); + x.operand[1] = R( 8, 0x140 ); + CHECK( __cpu_exec( &x ) == 0, "IINC" ); + CHECK( *P( 8, 0x141 ) == 42, "IINC result" ); + I( &x, CPU_OP_IDEC ); + x.operand[0] = R( 8, 0x142 ); + x.operand[1] = R( 8, 0x141 ); + CHECK( __cpu_exec( &x ) == 0, "IDEC" ); + CHECK( *P( 8, 0x142 ) == 41, "IDEC result" ); + *P( 8, 0x143 ) = 0x12; + *P( 8, 0x144 ) = 0x34; + I( &x, CPU_OP_IXCHG ); + x.operand[0] = R( 8, 0x143 ); + x.operand[1] = R( 8, 0x144 ); + CHECK( __cpu_exec( &x ) == 0, "IXCHG" ); + CHECK( *P( 8, 0x143 ) == 0x34 && *P( 8, 0x144 ) == 0x12, "IXCHG result" ); + *P( 8, 0x145 ) = 0xff; + I( &x, CPU_OP_ICPY ); + x.operand[0] = R( 16, 0x146 ); + x.operand[1] = R( 8, 0x145 ); + CHECK( __cpu_exec( &x ) == 0, "ICPY" ); + CHECK( __cpu_data_get_u16( P( 16, 0x146 ) ) == 255, "ICPY zero extension" ); + I( &x, CPU_OP_ICVT ); + x.operand[0] = R( 16, 0x148 ); + x.operand[1] = R( 8, 0x145 ); + CHECK( __cpu_exec( &x ) == 0, "ICVT" ); + CHECK( __cpu_data_get_u16( P( 16, 0x148 ) ) == 0xffff, "ICVT sign extension" ); + + /* Full products and quotient+remainder forms. */ + __cpu_data_put_u16( P( 16, 0x160 ), 300 ); + __cpu_data_put_u16( P( 16, 0x162 ), 200 ); + I( &x, CPU_OP_IMUL ); + x.operand[0] = R( 32, 0x164 ); + x.operand[1] = R( 16, 0x160 ); + x.operand[2] = R( 16, 0x162 ); + CHECK( __cpu_exec( &x ) == 0, "IMUL" ); + CHECK( __cpu_data_get_u32( P( 32, 0x164 ) ) == 60000, "IMUL result" ); + __cpu_data_put_u16( P( 16, 0x168 ), (__mpu_uint16_t)-3 ); + __cpu_data_put_u16( P( 16, 0x16a ), 7 ); + I( &x, CPU_OP_ISMUL ); + x.operand[0] = R( 32, 0x16c ); + x.operand[1] = R( 16, 0x168 ); + x.operand[2] = R( 16, 0x16a ); + CHECK( __cpu_exec( &x ) == 0, "ISMUL" ); + CHECK( (__mpu_int32_t)__cpu_data_get_u32( P( 32, 0x16c ) ) == -21, "ISMUL result" ); + __cpu_data_put_u16( P( 16, 0x170 ), 100 ); + __cpu_data_put_u16( P( 16, 0x172 ), 7 ); + I( &x, CPU_OP_IDIV ); + x.operand[0] = R( 16, 0x174 ); + x.operand[1] = R( 16, 0x176 ); + x.operand[2] = R( 16, 0x170 ); + x.operand[3] = R( 16, 0x172 ); + CHECK( __cpu_exec( &x ) == 0, "IDIV" ); + CHECK( __cpu_data_get_u16( P( 16, 0x174 ) ) == 14 && __cpu_data_get_u16( P( 16, 0x176 ) ) == 2, "IDIV result" ); + { + mpu_int16_t qref, rref, nref, dref; + __cpu_data_put_u16( nref, (__mpu_uint16_t)-100 ); + __cpu_data_put_u16( dref, 7 ); + isdiv( ( mpu_int * ) qref, ( mpu_int * ) rref, ( mpu_int * ) nref, ( mpu_int * ) dref, 2 ); + memcpy( P( 16, 0x17a ), nref, 2 ); + memcpy( P( 16, 0x17c ), dref, 2 ); + I( &x, CPU_OP_ISDIV ); + x.operand[0] = R( 16, 0x17e ); + x.operand[1] = R( 16, 0x180 ); + x.operand[2] = R( 16, 0x17a ); + x.operand[3] = R( 16, 0x17c ); + CHECK( __cpu_exec( &x ) == 0, "ISDIV" ); + CHECK( memcmp( P( 16, 0x17e ), qref, 2 ) == 0 && memcmp( P( 16, 0x180 ), rref, 2 ) == 0, "ISDIV result" ); + } + *P( 8, 0x188 ) = 0x80; + I( &x, CPU_OP_IBITSLEN ); + x.operand[0] = R( 64, 0x190 ); + x.operand[1] = R( 8, 0x188 ); + CHECK( __cpu_exec( &x ) == 0, "IBITSLEN" ); + CHECK( get64( 0x190 ) == 8, "IBITSLEN result" ); + + /* Real conversion/utility coverage. */ + setreal( 64, 0x200, "-3.75" ); + I( &x, CPU_OP_RABS ); + x.operand[0] = R( 64, 0x208 ); + x.operand[1] = R( 64, 0x200 ); + CHECK( __cpu_exec( &x ) == 0, "RABS" ); + { + mpu_real64_t ref; + memcpy( ref, P( 64, 0x200 ), 8 ); + r_abs( ( mpu_real * ) ref, 8 ); + CHECK( memcmp( P( 64, 0x208 ), ref, 8 ) == 0, "RABS result" ); + } + I( &x, CPU_OP_RNEG ); + x.operand[0] = R( 64, 0x210 ); + x.operand[1] = R( 64, 0x200 ); + CHECK( __cpu_exec( &x ) == 0, "RNEG" ); + { + mpu_real64_t ref; + memcpy( ref, P( 64, 0x200 ), 8 ); + r_neg( ( mpu_real * ) ref, 8 ); + CHECK( memcmp( P( 64, 0x210 ), ref, 8 ) == 0, "RNEG result" ); + } + setreal( 64, 0x218, "2.0" ); + setreal( 64, 0x220, "3.0" ); + I( &x, CPU_OP_RCMP ); + x.operand[0] = R( 64, 0x228 ); + x.operand[1] = R( 64, 0x218 ); + x.operand[2] = R( 64, 0x220 ); + CHECK( __cpu_exec( &x ) == 0, "RCMP" ); + CHECK( geti64( 0x228 ) == -1, "RCMP result" ); + I( &x, CPU_OP_RCPY ); + x.operand[0] = R( 64, 0x230 ); + x.operand[1] = R( 64, 0x218 ); + CHECK( __cpu_exec( &x ) == 0, "RCPY" ); + CHECK( memcmp( P( 64, 0x230 ), P( 64, 0x218 ), 8 ) == 0, "RCPY result" ); + I( &x, CPU_OP_RCVT ); + x.operand[0] = R( 128, 0x240 ); + x.operand[1] = R( 64, 0x218 ); + CHECK( __cpu_exec( &x ) == 0, "RCVT" ); + { + mpu_real128_t ref; + r_cvt( ( mpu_real * ) ref, ( mpu_real * ) P( 64, 0x218 ), 16, 8 ); + CHECK( memcmp( P( 128, 0x240 ), ref, 16 ) == 0, "RCVT result" ); + } + + /* floor/ceil/round, remain and the previously missing transcendental family. */ + { + struct U + { + cpu_opcode op; + void( *fn )( mpu_real *, mpu_real *, int ); + const char *n; + const char *src; + } + u[] = + { + {CPU_OP_RFLOOR,r_floor,"RFLOOR","3.75"},{CPU_OP_RCEIL,r_ceil,"RCEIL","3.25"},{CPU_OP_RROUND,r_round,"RROUND","3.60"}, + {CPU_OP_RLOG1P,r_log1p,"RLOG1P","0.25"},{CPU_OP_REXPM1,r_expm1,"REXPM1","0.25"}, + {CPU_OP_RASINH,r_asinh,"RASINH","0.25"},{CPU_OP_RACOSH,r_acosh,"RACOSH","2.0"},{CPU_OP_RATANH,r_atanh,"RATANH","0.25"} + }; + unsigned k; + for( k = 0; k < sizeof( u ) / sizeof( u[0] ); k++ ) + { + mpu_real64_t ref; + setreal( 64, 0x250, u[k].src ); + u[k].fn( ( mpu_real * ) ref, ( mpu_real * ) P( 64, 0x250 ), 8 ); + I( &x, u[k].op ); + x.operand[0] = R( 64, 0x258 ); + x.operand[1] = R( 64, 0x250 ); + CHECK( __cpu_exec( &x ) == 0, u[k].n ); + CHECK( memcmp( P( 64, 0x258 ), ref, 8 ) == 0, u[k].n ); + } + } + setreal( 64, 0x260, "5.5" ); + setreal( 64, 0x268, "2.0" ); + { + mpu_real64_t ref; + r_remain( ( mpu_real * ) ref, ( mpu_real * ) P( 64, 0x260 ), ( mpu_real * ) P( 64, 0x268 ), 8 ); + I( &x, CPU_OP_RREMAIN ); + x.operand[0] = R( 64, 0x270 ); + x.operand[1] = R( 64, 0x260 ); + x.operand[2] = R( 64, 0x268 ); + CHECK( __cpu_exec( &x ) == 0, "RREMAIN" ); + CHECK( memcmp( P( 64, 0x270 ), ref, 8 ) == 0, "RREMAIN result" ); + } + + /* frexp/ldexp/logb/modf and integer<->real conversions. */ + setreal( 64, 0x280, "6.5" ); + I( &x, CPU_OP_RFREXP ); + x.operand[0] = R( 64, 0x288 ); + x.operand[1] = R( 64, 0x290 ); + x.operand[2] = R( 64, 0x280 ); + CHECK( __cpu_exec( &x ) == 0, "RFREXP" ); + { + mpu_real64_t rs; + mpu_int64_t re; + r_frexp( ( mpu_real * ) rs, ( mpu_int * ) re, ( mpu_real * ) P( 64, 0x280 ), 8, 8 ); + CHECK( memcmp( P( 64, 0x288 ), rs, 8 ) == 0 && memcmp( P( 64, 0x290 ), re, 8 ) == 0, "RFREXP result" ); + } + I( &x, CPU_OP_RLDEXP ); + x.operand[0] = R( 64, 0x298 ); + x.operand[1] = R( 64, 0x288 ); + x.operand[2] = R( 64, 0x290 ); + CHECK( __cpu_exec( &x ) == 0, "RLDEXP" ); + { + mpu_real64_t ref; + r_ldexp( ( mpu_real * ) ref, ( mpu_real * ) P( 64, 0x288 ), ( mpu_int * ) P( 64, 0x290 ), 8, 8 ); + CHECK( memcmp( P( 64, 0x298 ), ref, 8 ) == 0, "RLDEXP result" ); + } + I( &x, CPU_OP_RLOGB ); + x.operand[0] = R( 64, 0x2a0 ); + x.operand[1] = R( 64, 0x280 ); + CHECK( __cpu_exec( &x ) == 0, "RLOGB" ); + { + mpu_int64_t ri; + r_logb( ( mpu_int * ) ri, ( mpu_real * ) P( 64, 0x280 ), 8, 8 ); + CHECK( memcmp( P( 64, 0x2a0 ), ri, 8 ) == 0, "RLOGB result" ); + } + I( &x, CPU_OP_RMODF ); + x.operand[0] = R( 64, 0x2a8 ); + x.operand[1] = R( 64, 0x2b0 ); + x.operand[2] = R( 64, 0x280 ); + CHECK( __cpu_exec( &x ) == 0, "RMODF" ); + { + mpu_real64_t rf, ri; + r_modf( ( mpu_real * ) rf, ( mpu_real * ) ri, ( mpu_real * ) P( 64, 0x280 ), 8 ); + CHECK( memcmp( P( 64, 0x2a8 ), rf, 8 ) == 0 && memcmp( P( 64, 0x2b0 ), ri, 8 ) == 0, "RMODF result" ); + } + put64( 0x2b8, 42 ); + I( &x, CPU_OP_ITOR ); + x.operand[0] = R( 64, 0x2c0 ); + x.operand[1] = R( 64, 0x2b8 ); + CHECK( __cpu_exec( &x ) == 0, "ITOR" ); + { + mpu_real64_t ref; + ltor( ( mpu_real * ) ref, ( mpu_int * ) P( 64, 0x2b8 ), 8, 8 ); + CHECK( memcmp( P( 64, 0x2c0 ), ref, 8 ) == 0, "ITOR result" ); + } + put64( 0x2c8, (__mpu_uint64_t)1844674407370955161UL ); + I( &x, CPU_OP_UITOR ); + x.operand[0] = R( 128, 0x2d0 ); + x.operand[1] = R( 64, 0x2c8 ); + CHECK( __cpu_exec( &x ) == 0, "UITOR" ); + { + mpu_real128_t ref; + ultor( ( mpu_real * ) ref, ( mpu_int * ) P( 64, 0x2c8 ), 16, 8 ); + CHECK( memcmp( P( 128, 0x2d0 ), ref, 16 ) == 0, "UITOR result" ); + } + I( &x, CPU_OP_RTOI ); + x.operand[0] = R( 64, 0x2e0 ); + x.operand[1] = R( 64, 0x2e8 ); + x.operand[2] = R( 64, 0x280 ); + CHECK( __cpu_exec( &x ) == 0, "RTOI" ); + { + mpu_int64_t li; + mpu_real64_t fr; + rtol_frac( ( mpu_int * ) li, ( mpu_real * ) fr, ( mpu_real * ) P( 64, 0x280 ), 8, 8 ); + CHECK( memcmp( P( 64, 0x2e0 ), li, 8 ) == 0 && memcmp( P( 64, 0x2e8 ), fr, 8 ) == 0, "RTOI result" ); + } + setreal( 64, 0x2f0, "42.75" ); + I( &x, CPU_OP_RTOUI ); + x.operand[0] = R( 64, 0x2f8 ); + x.operand[1] = R( 64, 0x300 ); + x.operand[2] = R( 64, 0x2f0 ); + CHECK( __cpu_exec( &x ) == 0, "RTOUI" ); + { + mpu_int64_t li; + mpu_real64_t fr; + rtoul_frac( ( mpu_int * ) li, ( mpu_real * ) fr, ( mpu_real * ) P( 64, 0x2f0 ), 8, 8 ); + CHECK( memcmp( P( 64, 0x2f8 ), li, 8 ) == 0 && memcmp( P( 64, 0x300 ), fr, 8 ) == 0, "RTOUI result" ); + } + + /* Complete RCONST generator audit: every LibMPU value generator exposed by RCONST. */ + { + struct K + { + unsigned id; + void( *f )( mpu_real *, int ); + const char *n; + } + ks[] = + { + {CPU_RCONST_ZERO,_m_zero,"ZERO"},{CPU_RCONST_HALF,_m_half,"HALF"},{CPU_RCONST_ONE,_m_one,"ONE"}, + {CPU_RCONST_TWO,_m_two,"TWO"},{CPU_RCONST_TEN,_m_ten,"TEN"},{CPU_RCONST_MTEN,_m_mten,"MTEN"},{CPU_RCONST_32,_m_32,"32"}, + {CPU_RCONST_PI,_m_PI,"PI"},{CPU_RCONST_E,_m_E,"E"},{CPU_RCONST_1_LN2,_m_1_ln2,"1_LN2"},{CPU_RCONST_LN2,_m_ln2,"LN2"}, + {CPU_RCONST_1_LN10,_m_1_ln10,"1_LN10"},{CPU_RCONST_LN10,_m_ln10,"LN10"},{CPU_RCONST_1_LG2,_m_1_lg2,"1_LG2"},{CPU_RCONST_LG2,_m_lg2,"LG2"}, + {CPU_RCONST_PI_2,_m_PI_2,"PI_2"},{CPU_RCONST_PI_3,_m_PI_3,"PI_3"},{CPU_RCONST_PI_4,_m_PI_4,"PI_4"},{CPU_RCONST_PI_5,_m_PI_5,"PI_5"},{CPU_RCONST_PI_6,_m_PI_6,"PI_6"}, + {CPU_RCONST_1_PI,_m_1_PI,"1_PI"},{CPU_RCONST_2_PI,_m_2_PI,"2_PI"},{CPU_RCONST_3_PI,_m_3_PI,"3_PI"},{CPU_RCONST_4_PI,_m_4_PI,"4_PI"},{CPU_RCONST_5_PI,_m_5_PI,"5_PI"}, + {CPU_RCONST_2PI,_m_2PI,"2PI"},{CPU_RCONST_3PI,_m_3PI,"3PI"},{CPU_RCONST_1_2PI,_m_1_2PI,"1_2PI"},{CPU_RCONST_1_3PI,_m_1_3PI,"1_3PI"}, + {CPU_RCONST_1_4PI,_m_1_4PI,"1_4PI"},{CPU_RCONST_1_5PI,_m_1_5PI,"1_5PI"},{CPU_RCONST_1_6PI,_m_1_6PI,"1_6PI"},{CPU_RCONST_3PI_4,_m_3PI_4,"3PI_4"}, + {CPU_RCONST_SQRTPI,_m_SQRTPI,"SQRTPI"},{CPU_RCONST_1_SQRTPI,_m_1_SQRTPI,"1_SQRTPI"},{CPU_RCONST_2_SQRTPI,_m_2_SQRTPI,"2_SQRTPI"}, + {CPU_RCONST_SQRT2,_m_SQRT2,"SQRT2"},{CPU_RCONST_1_SQRT2,_m_1_SQRT2,"1_SQRT2"},{CPU_RCONST_SQRT3,_m_SQRT3,"SQRT3"},{CPU_RCONST_1_SQRT3,_m_1_SQRT3,"1_SQRT3"}, + {CPU_RCONST_DEGREE,_m_DEGREE,"DEGREE"},{CPU_RCONST_1_DEGREE,_m_1_DEGREE,"1_DEGREE"},{CPU_RCONST_GOLDENRATIO,_m_GOLDENRATIO,"GOLDENRATIO"}, + {CPU_RCONST_EULERGAMMA,_m_EULERGAMMA,"EULERGAMMA"},{CPU_RCONST_CATALAN,_m_CATALAN,"CATALAN"}, + {CPU_RCONST_EPSILON,_real_epsilon,"EPSILON"},{CPU_RCONST_IND,_ind,"IND"} + }; + unsigned k; + for( k = 0; k < sizeof( ks ) / sizeof( ks[0] ); k++ ) + { + mpu_real64_t ref; + ks[k].f( ( mpu_real * ) ref, 8 ); + I( &x, CPU_OP_RCONST ); + x.operand[0] = R( 64, 0x420 ); + x.operand[1].type = CPU_OPERAND_CONST8; + x.operand[1].value.const8 = (__mpu_uint8_t)ks[k].id; + CHECK( __cpu_exec( &x ) == 0, ks[k].n ); + CHECK( memcmp( P( 64, 0x420 ), ref, 8 ) == 0, ks[k].n ); + } + { + struct S + { + unsigned id; + void( *f )( mpu_real *, unsigned int, int ); + unsigned sign; + const char *n; + } + sp[] = + { + {CPU_RCONST_PNAN,_nan,0,"PNAN"},{CPU_RCONST_NNAN,_nan,1,"NNAN"}, + {CPU_RCONST_PNANMAX,_nan_max,0,"PNANMAX"},{CPU_RCONST_NNANMAX,_nan_max,1,"NNANMAX"}, + {CPU_RCONST_PNANMIN,_nan_min,0,"PNANMIN"},{CPU_RCONST_NNANMIN,_nan_min,1,"NNANMIN"}, + {CPU_RCONST_PINF,_inf,0,"PINF"},{CPU_RCONST_NINF,_inf,1,"NINF"}, + {CPU_RCONST_PREALMIN,_real_min,0,"PREALMIN"},{CPU_RCONST_NREALMIN,_real_min,1,"NREALMIN"}, + {CPU_RCONST_PREALMAX,_real_max,0,"PREALMAX"},{CPU_RCONST_NREALMAX,_real_max,1,"NREALMAX"}, + {CPU_RCONST_PZERO,_signull,0,"PZERO"},{CPU_RCONST_NZERO,_signull,1,"NZERO"} + }; + for( k = 0; k < sizeof( sp ) / sizeof( sp[0] ); k++ ) + { + mpu_real64_t ref; + sp[k].f( ( mpu_real * ) ref, sp[k].sign, 8 ); + I( &x, CPU_OP_RCONST ); + x.operand[0] = R( 64, 0x428 ); + x.operand[1].type = CPU_OPERAND_CONST8; + x.operand[1].value.const8 = (__mpu_uint8_t)sp[k].id; + CHECK( __cpu_exec( &x ) == 0, sp[k].n ); + CHECK( memcmp( P( 64, 0x428 ), ref, 8 ) == 0, sp[k].n ); + } + } + I( &x, CPU_OP_RCONST ); + x.operand[0] = R( 64, 0x430 ); + x.operand[1].type = CPU_OPERAND_CONST8; + x.operand[1].value.const8 = CPU_RCONST_NINF; + CHECK( __cpu_exec( &x ) == 0, "NINF predicate source" ); + I( &x, CPU_OP_RISINF ); + x.operand[0] = R( 64, 0x438 ); + x.operand[1] = R( 64, 0x430 ); + CHECK( __cpu_exec( &x ) == 0, "RISINF" ); + CHECK( geti64( 0x438 ) == 1, "RISINF result" ); + } + + /* Complex extraction, generation, conversion, conjugation and predicates. */ + setreal( 64, 0x500, "3.0" ); + setreal( 64, 0x508, "4.0" ); + I( &x, CPU_OP_CGEN ); + x.operand[0] = R( 128, 0x510 ); + x.operand[1] = R( 64, 0x500 ); + x.operand[2] = R( 64, 0x508 ); + CHECK( __cpu_exec( &x ) == 0, "CGEN" ); + { + mpu_complex64_t ref; + c_gen_complex( ( mpu_complex * ) ref, ( mpu_real * ) P( 64, 0x500 ), ( mpu_real * ) P( 64, 0x508 ), 8 ); + CHECK( memcmp( P( 128, 0x510 ), ref, 16 ) == 0, "CGEN result" ); + } + I( &x, CPU_OP_CREAL ); + x.operand[0] = R( 64, 0x520 ); + x.operand[1] = R( 128, 0x510 ); + CHECK( __cpu_exec( &x ) == 0, "CREAL" ); + CHECK( memcmp( P( 64, 0x520 ), P( 64, 0x500 ), 8 ) == 0, "CREAL result" ); + I( &x, CPU_OP_CIMAG ); + x.operand[0] = R( 64, 0x528 ); + x.operand[1] = R( 128, 0x510 ); + CHECK( __cpu_exec( &x ) == 0, "CIMAG" ); + CHECK( memcmp( P( 64, 0x528 ), P( 64, 0x508 ), 8 ) == 0, "CIMAG result" ); + I( &x, CPU_OP_CRTOC ); + x.operand[0] = R( 128, 0x530 ); + x.operand[1] = R( 64, 0x500 ); + CHECK( __cpu_exec( &x ) == 0, "CRTOC" ); + { + mpu_complex64_t ref; + c_real_to_complex( ( mpu_complex * ) ref, ( mpu_real * ) P( 64, 0x500 ), 8 ); + CHECK( memcmp( P( 128, 0x530 ), ref, 16 ) == 0, "CRTOC result" ); + } + I( &x, CPU_OP_CCONJ ); + x.operand[0] = R( 128, 0x540 ); + x.operand[1] = R( 128, 0x510 ); + CHECK( __cpu_exec( &x ) == 0, "CCONJ" ); + { + mpu_complex64_t ref; + c_conj( ( mpu_complex * ) ref, ( mpu_complex * ) P( 128, 0x510 ), 8 ); + CHECK( memcmp( P( 128, 0x540 ), ref, 16 ) == 0, "CCONJ result" ); + } + I( &x, CPU_OP_CABS ); + x.operand[0] = R( 64, 0x550 ); + x.operand[1] = R( 128, 0x510 ); + CHECK( __cpu_exec( &x ) == 0, "CABS" ); + { + mpu_real64_t ref; + c_abs( ( mpu_real * ) ref, ( mpu_complex * ) P( 128, 0x510 ), 8 ); + CHECK( memcmp( P( 64, 0x550 ), ref, 8 ) == 0, "CABS result" ); + } + I( &x, CPU_OP_CNORM ); + x.operand[0] = R( 64, 0x558 ); + x.operand[1] = R( 128, 0x510 ); + CHECK( __cpu_exec( &x ) == 0, "CNORM" ); + { + mpu_real64_t ref; + c_norm( ( mpu_real * ) ref, ( mpu_complex * ) P( 128, 0x510 ), 8 ); + CHECK( memcmp( P( 64, 0x558 ), ref, 8 ) == 0, "CNORM result" ); + } + I( &x, CPU_OP_CARG ); + x.operand[0] = R( 64, 0x560 ); + x.operand[1] = R( 128, 0x510 ); + CHECK( __cpu_exec( &x ) == 0, "CARG" ); + { + mpu_real64_t ref; + c_arg( ( mpu_real * ) ref, ( mpu_complex * ) P( 128, 0x510 ), 8 ); + CHECK( memcmp( P( 64, 0x560 ), ref, 8 ) == 0, "CARG result" ); + } + I( &x, CPU_OP_CISEQ ); + x.operand[0] = R( 64, 0x568 ); + x.operand[1] = R( 128, 0x510 ); + x.operand[2] = R( 128, 0x510 ); + CHECK( __cpu_exec( &x ) == 0, "CISEQ" ); + CHECK( geti64( 0x568 ) == 1, "CISEQ result" ); + I( &x, CPU_OP_CISNE ); + x.operand[0] = R( 64, 0x570 ); + x.operand[1] = R( 128, 0x510 ); + x.operand[2] = R( 128, 0x540 ); + CHECK( __cpu_exec( &x ) == 0, "CISNE" ); + CHECK( geti64( 0x570 ) == 1, "CISNE result" ); + I( &x, CPU_OP_CCPY ); + x.operand[0] = R( 128, 0x580 ); + x.operand[1] = R( 128, 0x510 ); + CHECK( __cpu_exec( &x ) == 0, "CCPY" ); + CHECK( memcmp( P( 128, 0x580 ), P( 128, 0x510 ), 16 ) == 0, "CCPY result" ); + setreal( 64, 0x590, "5.0" ); + setreal( 64, 0x598, "0.5" ); + I( &x, CPU_OP_CPOLAR ); + x.operand[0] = R( 128, 0x5a0 ); + x.operand[1] = R( 64, 0x590 ); + x.operand[2] = R( 64, 0x598 ); + CHECK( __cpu_exec( &x ) == 0, "CPOLAR" ); + { + mpu_complex64_t ref; + c_polar( ( mpu_complex * ) ref, ( mpu_real * ) P( 64, 0x590 ), ( mpu_real * ) P( 64, 0x598 ), 8 ); + CHECK( memcmp( P( 128, 0x5a0 ), ref, 16 ) == 0, "CPOLAR result" ); + } + I( &x, CPU_OP_CCVT ); + x.operand[0] = R( 256, 0x5c0 ); + x.operand[1] = R( 128, 0x510 ); + CHECK( __cpu_exec( &x ) == 0, "CCVT" ); + { + mpu_complex128_t ref; + c_cvt( ( mpu_complex * ) ref, ( mpu_complex * ) P( 128, 0x510 ), 16, 8 ); + CHECK( memcmp( P( 256, 0x5c0 ), ref, 32 ) == 0, "CCVT result" ); + } + + __cpu_free_context(); + if( failures ) + { + fprintf( stderr, "libcpu LibMPU coverage regression: %d failure(s)\n", failures ); + return( 1 ); + } + puts( "libcpu extended LibMPU instruction coverage: PASS" ); + return( 0 ); +} diff --git a/tests/tst-mpu-ops-runner.sh b/tests/tst-mpu-ops-runner.sh new file mode 100755 index 0000000..32bad1d --- /dev/null +++ b/tests/tst-mpu-ops-runner.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +base="${TMPDIR:-/tmp}/libcpu-mpu-ops-$$" +trap 'rm -f "$base.s" "$base.o" "$base.melf"' EXIT HUP INT TERM +"$CPU_AS" -o "$base.o" "$TEST_SRCDIR/fixtures/mpu-ops.s" >/dev/null +"$CPU_LD" --stack-size=512 -o "$base.melf" "$base.o" >/dev/null +out=`"$CPU_RUN" --int-reg=r64@0x00020 "$base.melf"` +test "x$out" = "x42" diff --git a/tests/tst-needed-runtime-runner.sh b/tests/tst-needed-runtime-runner.sh new file mode 100755 index 0000000..1119ff9 --- /dev/null +++ b/tests/tst-needed-runtime-runner.sh @@ -0,0 +1,193 @@ +#!/bin/sh +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${CPU_READELF:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-needed-runtime-$$ +mkdir -p "$TMP/lib" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +cat > "$TMP/main.s" <<'EOS' +.global _life +.extern afoo +.text +_start_life: + LDI r64@0x00000, 40 + CALL afoo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/a.s" <<'EOS' +.global afoo +.extern bfoo +.text +afoo: + CALL bfoo + RET +EOS + +cat > "$TMP/b.s" <<'EOS' +.global bfoo +.text +bfoo: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +"$CPU_AS" -o "$TMP/main.o" "$TMP/main.s" +"$CPU_AS" -o "$TMP/a.o" "$TMP/a.s" +"$CPU_AS" -o "$TMP/b.o" "$TMP/b.s" + +# A real recursive graph: main -> liba.so.1 -> libb.so.1. +"$CPU_LD" --shared --soname=libb.so.1 \ + -o "$TMP/lib/libb.so.1.0.0" "$TMP/b.o" +ln -s libb.so.1.0.0 "$TMP/lib/libb.so.1" +ln -s libb.so.1 "$TMP/lib/libb.so" + +"$CPU_LD" --shared --soname=liba.so.1 \ + -L"$TMP/lib" -lb -o "$TMP/lib/liba.so.1.0.0" "$TMP/a.o" +ln -s liba.so.1.0.0 "$TMP/lib/liba.so.1" +ln -s liba.so.1 "$TMP/lib/liba.so" + +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/lib" -la -o "$TMP/main" "$TMP/main.o" + +"$CPU_READELF" -d "$TMP/main" | grep -q 'NEEDED.*\[liba.so.1\]' +"$CPU_READELF" -d "$TMP/lib/liba.so.1.0.0" | \ + grep -q 'NEEDED.*\[libb.so.1\]' +[ "`"$CPU_RUN" --library-path="$TMP/lib" \ + --int-reg=r64@0x00018 "$TMP/main"`" = 42 ] + +# A missing transitive dependency is reported by SONAME, before process load. +mv "$TMP/lib/libb.so.1" "$TMP/lib/libb.so.1.save" +if "$CPU_RUN" --library-path="$TMP/lib" "$TMP/main" \ + >"$TMP/missing.out" 2>"$TMP/missing.err"; then + echo "missing transitive dependency unexpectedly loaded" >&2 + exit 1 +fi +grep -q "cannot resolve DT_NEEDED 'libb.so.1'" "$TMP/missing.err" +mv "$TMP/lib/libb.so.1.save" "$TMP/lib/libb.so.1" + +# The resolved filename is not trusted: its internal SONAME must be the exact +# DT_NEEDED identity requested by the dependent object. +cat > "$TMP/wrong.s" <<'EOS' +.global afoo +.text +afoo: + RET +EOS +"$CPU_AS" -o "$TMP/wrong.o" "$TMP/wrong.s" +"$CPU_LD" --shared --soname=libwrong.so.9 \ + -o "$TMP/lib/libwrong.so.9.0.0" "$TMP/wrong.o" +rm "$TMP/lib/liba.so.1" +ln -s libwrong.so.9.0.0 "$TMP/lib/liba.so.1" +if "$CPU_RUN" --library-path="$TMP/lib" "$TMP/main" \ + >"$TMP/mismatch.out" 2>"$TMP/mismatch.err"; then + echo "SONAME mismatch unexpectedly loaded" >&2 + exit 1 +fi +grep -q "DT_NEEDED 'liba.so.1' resolved to SONAME 'libwrong.so.9'" \ + "$TMP/mismatch.err" +rm "$TMP/lib/liba.so.1" +ln -s liba.so.1.0.0 "$TMP/lib/liba.so.1" + +# Dependency cycles are legal graph edges. Registration by SONAME before +# recursive descent prevents A -> B -> A from recursing or loading twice. +cat > "$TMP/cycle-main.s" <<'EOS' +.global _life +.extern cycle_a +.text +_start_life: + LDI r64@0x00000, 40 + CALL cycle_a + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +cat > "$TMP/cycle-a.s" <<'EOS' +.global cycle_a +.text +cycle_a: + LDI r64@0x00008, 4 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/cycle-b.s" <<'EOS' +.global cycle_b +.text +cycle_b: + RET +EOS +"$CPU_AS" -o "$TMP/cycle-main.o" "$TMP/cycle-main.s" +"$CPU_AS" -o "$TMP/cycle-a.o" "$TMP/cycle-a.s" +"$CPU_AS" -o "$TMP/cycle-b.o" "$TMP/cycle-b.s" + +# Bootstrap one side of the cycle, build B -> A, then rebuild A -> B. +"$CPU_LD" --shared --soname=libcyclea.so.1 \ + -o "$TMP/lib/libcyclea.so.1.0.0" "$TMP/cycle-a.o" +ln -s libcyclea.so.1.0.0 "$TMP/lib/libcyclea.so.1" +ln -s libcyclea.so.1 "$TMP/lib/libcyclea.so" +"$CPU_LD" --shared --soname=libcycleb.so.1 \ + -L"$TMP/lib" -lcyclea \ + -o "$TMP/lib/libcycleb.so.1.0.0" "$TMP/cycle-b.o" +ln -s libcycleb.so.1.0.0 "$TMP/lib/libcycleb.so.1" +ln -s libcycleb.so.1 "$TMP/lib/libcycleb.so" +"$CPU_LD" --shared --soname=libcyclea.so.1 \ + -L"$TMP/lib" -lcycleb \ + -o "$TMP/lib/libcyclea.so.1.0.0" "$TMP/cycle-a.o" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/lib" -lcyclea -o "$TMP/cycle-main" "$TMP/cycle-main.o" +[ "`"$CPU_RUN" --library-path="$TMP/lib" \ + --int-reg=r64@0x00018 "$TMP/cycle-main"`" = 44 ] + +# Explicit preload remains the override mechanism. An ABI-v3 preload with the +# same SONAME satisfies the normal dependency and keeps the established +# explicit-preload priority. +cat > "$TMP/direct-main.s" <<'EOS' +.global _life +.extern foo +.text +_start_life: + LDI r64@0x00000, 40 + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +cat > "$TMP/foo.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/override.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +"$CPU_AS" -o "$TMP/direct-main.o" "$TMP/direct-main.s" +"$CPU_AS" -o "$TMP/foo.o" "$TMP/foo.s" +"$CPU_AS" -o "$TMP/override.o" "$TMP/override.s" +"$CPU_LD" --shared --soname=libfoo.so.1 \ + -o "$TMP/lib/libfoo.so.1.0.0" "$TMP/foo.o" +ln -s libfoo.so.1.0.0 "$TMP/lib/libfoo.so.1" +ln -s libfoo.so.1 "$TMP/lib/libfoo.so" +"$CPU_LD" --shared --soname=libfoo.so.1 \ + -o "$TMP/override.so" "$TMP/override.o" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/lib" -lfoo -o "$TMP/direct-main" "$TMP/direct-main.o" +[ "`"$CPU_RUN" --library-path="$TMP/lib" \ + --int-reg=r64@0x00018 "$TMP/direct-main"`" = 42 ] +[ "`"$CPU_RUN" --library-path="$TMP/lib" \ + --ldpreload="$TMP/override.so" \ + --int-reg=r64@0x00018 "$TMP/direct-main"`" = 43 ] + +exit 0 diff --git a/tests/tst-nm-runner.sh b/tests/tst-nm-runner.sh new file mode 100755 index 0000000..573edbd --- /dev/null +++ b/tests/tst-nm-runner.sh @@ -0,0 +1,167 @@ +#!/bin/sh +set -eu + +: "${PACKAGE_VERSION:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-nm-$$ +trap 'rm -rf "$TMP"' EXIT HUP INT TERM +mkdir -p "$TMP" + +cat > "$TMP/symbols.s" <<'EOS' +.global zeta +.global data_global +.global bss_global +.extern missing +.weak weak_defined +.weak weak_missing + +.text +local_text: + RET +zeta: + RET +weak_defined: + RET + +.data +data_global: + .integer 64, 7 + +.bss +bss_global: + .zero 16 +EOS + +cat > "$TMP/main.s" <<'EOS' +.global _start +.global _life +.extern zeta +.text +_start_life: + CALL zeta + LDI r32@0x7ffc, 0 + RET +EOS + +cat > "$TMP/dyn.s" <<'EOS' +.global dyn_alpha +.global dyn_data +.weak dyn_weak +.text +dyn_alpha: + RET +dyn_weak: + RET +.data +dyn_data: + .integer 64, 1 +EOS + +cat > "$TMP/a.s" <<'EOS' +.global archive_foo +.text +archive_foo: + RET +EOS + +cat > "$TMP/b.s" <<'EOS' +.global archive_bar +.text +archive_bar: + RET +EOS + +"$CPU_AS" -o "$TMP/symbols.o" "$TMP/symbols.s" +"$CPU_AS" -o "$TMP/main.o" "$TMP/main.s" +"$CPU_AS" -o "$TMP/dyn.o" "$TMP/dyn.s" +"$CPU_AS" -o "$TMP/a.o" "$TMP/a.s" +"$CPU_AS" -o "$TMP/b.o" "$TMP/b.s" + +"$CPU_LD" --stack-size=4096 --no-heap -o "$TMP/program" "$TMP/main.o" "$TMP/symbols.o" +"$CPU_LD" --shared --soname=libnm.so.1 -o "$TMP/libnm.so.1.0.0" "$TMP/dyn.o" +"$CPU_AR" rcs "$TMP/libnm.a" "$TMP/a.o" "$TMP/b.o" + +"$CPU_NM" "$TMP/symbols.o" > "$TMP/default.txt" +grep -Eq ' T zeta$' "$TMP/default.txt" +grep -Eq ' t local_text$' "$TMP/default.txt" +grep -Eq ' D data_global$' "$TMP/default.txt" +grep -Eq ' B bss_global$' "$TMP/default.txt" +grep -Eq ' U missing$' "$TMP/default.txt" +grep -Eq ' W weak_defined$' "$TMP/default.txt" +grep -Eq ' w weak_missing$' "$TMP/default.txt" + +# Default order is lexical by symbol name. +awk '{print $NF}' "$TMP/default.txt" > "$TMP/names.txt" +LC_ALL=C sort "$TMP/names.txt" > "$TMP/names.sorted" +cmp "$TMP/names.txt" "$TMP/names.sorted" + +"$CPU_NM" -g "$TMP/symbols.o" > "$TMP/extern.txt" +if grep -q 'local_text$' "$TMP/extern.txt"; then + echo "mcpu-nm -g unexpectedly displayed a local symbol" >&2 + exit 1 +fi +grep -q 'zeta$' "$TMP/extern.txt" + +"$CPU_NM" -u "$TMP/symbols.o" > "$TMP/undef.txt" +grep -Eq ' U missing$' "$TMP/undef.txt" +grep -Eq ' w weak_missing$' "$TMP/undef.txt" +if grep -q 'zeta$' "$TMP/undef.txt"; then + echo "mcpu-nm -u unexpectedly displayed a defined symbol" >&2 + exit 1 +fi + +"$CPU_NM" -n "$TMP/symbols.o" > "$TMP/numeric.txt" +# The first two defined text symbols are ordered by value, not alphabetically. +line_local=`grep -n ' local_text$' "$TMP/numeric.txt" | cut -d: -f1` +line_zeta=`grep -n ' zeta$' "$TMP/numeric.txt" | cut -d: -f1` +test "$line_local" -lt "$line_zeta" + +"$CPU_NM" -r "$TMP/symbols.o" > "$TMP/reverse.txt" +first_name=`awk 'NF { print $NF; exit }' "$TMP/reverse.txt"` +test "$first_name" = zeta + +"$CPU_NM" -p "$TMP/symbols.o" > "$TMP/nosort.txt" +test -s "$TMP/nosort.txt" +"$CPU_NM" -a "$TMP/symbols.o" > "$TMP/all.txt" +test -s "$TMP/all.txt" + +"$CPU_NM" "$TMP/program" > "$TMP/program.txt" +# ABI-v1 ET_EXEC has no retained .symtab; successful inspection is the contract. +test ! -s "$TMP/program.txt" + +"$CPU_NM" -D "$TMP/libnm.so.1.0.0" > "$TMP/dynamic.txt" +grep -q ' dyn_alpha$' "$TMP/dynamic.txt" +grep -q ' dyn_data$' "$TMP/dynamic.txt" +grep -Eq ' W dyn_weak$' "$TMP/dynamic.txt" + +"$CPU_NM" "$TMP/libnm.a" > "$TMP/archive.txt" +grep -F "$TMP/libnm.a:a.o:" "$TMP/archive.txt" >/dev/null +grep -F "$TMP/libnm.a:b.o:" "$TMP/archive.txt" >/dev/null +grep -q ' archive_foo$' "$TMP/archive.txt" +grep -q ' archive_bar$' "$TMP/archive.txt" + +"$CPU_NM" -A "$TMP/libnm.a" > "$TMP/archive-prefix.txt" +grep -F "$TMP/libnm.a:a.o: " "$TMP/archive-prefix.txt" | grep -q ' archive_foo$' +grep -F "$TMP/libnm.a:b.o: " "$TMP/archive-prefix.txt" | grep -q ' archive_bar$' +if grep -Fx "$TMP/libnm.a:a.o:" "$TMP/archive-prefix.txt" >/dev/null; then + echo "mcpu-nm -A unexpectedly emitted archive member headings" >&2 + exit 1 +fi + +"$CPU_NM" "$TMP/a.o" "$TMP/b.o" > "$TMP/multi.txt" +grep -Fx "$TMP/a.o:" "$TMP/multi.txt" >/dev/null +grep -Fx "$TMP/b.o:" "$TMP/multi.txt" >/dev/null + +"$CPU_NM" -A "$TMP/a.o" > "$TMP/file-prefix.txt" +grep -F "$TMP/a.o: " "$TMP/file-prefix.txt" | grep -q ' archive_foo$' + +dd if=/dev/zero of="$TMP/bad" bs=256 count=1 2>/dev/null +if "$CPU_NM" "$TMP/bad" > /dev/null 2> "$TMP/bad.err"; then + echo "mcpu-nm unexpectedly accepted a non-MELF file" >&2 + exit 1 +fi +grep -q 'not a MELF file' "$TMP/bad.err" + +"$CPU_NM" --help | grep -q -- '--dynamic' +"$CPU_NM" --help | grep -q -- '--print-file-name' +"$CPU_NM" --version | grep -Fq "$PACKAGE_VERSION" diff --git a/tests/tst-objcopy-runner.sh b/tests/tst-objcopy-runner.sh new file mode 100755 index 0000000..96b501b --- /dev/null +++ b/tests/tst-objcopy-runner.sh @@ -0,0 +1,160 @@ +#!/bin/sh +set -eu + +: "${PACKAGE_VERSION:?}" + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${CPU_READELF:?}" +: "${CPU_AR:?}" "${CPU_NM:?}" "${CPU_OBJCOPY:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-objcopy-$$ +mkdir -p "$TMP" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +cat > "$TMP/main.s" <<'EOS' +.global _life +.text +_start_life: + LDI r64@0x00000, 42 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/add-one.s" <<'EOS' +.global add_one +.text +add_one: + LDI r64@0x00008, 1 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +"$CPU_AS" -o "$TMP/main.o" "$TMP/main.s" +"$CPU_AS" -o "$TMP/add-one.o" "$TMP/add-one.s" +"$CPU_LD" --stack-size=4096 --no-heap -o "$TMP/main" "$TMP/main.o" +"$CPU_AR" rcs "$TMP/libadd.a" "$TMP/add-one.o" + +# Plain MELF -> MELF copying is byte-for-byte when no edit is requested. +"$CPU_OBJCOPY" "$TMP/main" "$TMP/main.copy" +cmp "$TMP/main" "$TMP/main.copy" + +printf 'metadata-one\n' > "$TMP/note-one" +printf 'metadata-two\n' > "$TMP/note-two" + +# Add non-ALLOC metadata without touching the executable image. +"$CPU_OBJCOPY" --add-section=.note.test="$TMP/note-one" \ + "$TMP/main" "$TMP/main.note" +"$CPU_READELF" -S "$TMP/main.note" | grep -q '\.note\.test' +test "$("$CPU_RUN" --int-reg=r64@0x00000 "$TMP/main.note")" = 42 + +# Dump is read-only and may inspect runtime sections as well as metadata. +"$CPU_OBJCOPY" --dump-section=.note.test="$TMP/note.dump" \ + "$TMP/main.note" "$TMP/main.dump-copy" +cmp "$TMP/note-one" "$TMP/note.dump" +"$CPU_OBJCOPY" --dump-section=.text="$TMP/text.before" \ + "$TMP/main" "$TMP/main.text-copy" +"$CPU_OBJCOPY" --dump-section=.text="$TMP/text.after" \ + "$TMP/main.note" "$TMP/main.note-copy" +cmp "$TMP/text.before" "$TMP/text.after" + +# Update and rename apply only to safe non-ALLOC PROGBITS metadata. +"$CPU_OBJCOPY" --update-section=.note.test="$TMP/note-two" \ + "$TMP/main.note" "$TMP/main.updated" +"$CPU_OBJCOPY" --dump-section=.note.test="$TMP/updated.dump" \ + "$TMP/main.updated" "$TMP/main.updated-copy" +cmp "$TMP/note-two" "$TMP/updated.dump" + +"$CPU_OBJCOPY" --rename-section=.note.test=.note.renamed \ + "$TMP/main.updated" "$TMP/main.renamed" +"$CPU_READELF" -S "$TMP/main.renamed" | grep -q '\.note\.renamed' +if "$CPU_READELF" -S "$TMP/main.renamed" | grep -q '\.note\.test'; then + echo "old section name survived mcpu-objcopy rename" >&2 + exit 1 +fi + +# Safe metadata can be removed; runtime behavior stays unchanged. +"$CPU_OBJCOPY" -R .note.renamed "$TMP/main.renamed" "$TMP/main.removed" +if "$CPU_READELF" -S "$TMP/main.removed" | grep -q '\.note\.renamed'; then + echo "removed metadata section survived mcpu-objcopy" >&2 + exit 1 +fi +test "$("$CPU_RUN" --int-reg=r64@0x00000 "$TMP/main.removed")" = 42 + +# Runtime-visible and structural sections are protected. +if "$CPU_OBJCOPY" -R .text "$TMP/main" "$TMP/bad-remove" >/dev/null 2>&1; then + echo "mcpu-objcopy removed .text" >&2 + exit 1 +fi +if "$CPU_OBJCOPY" --update-section=.text="$TMP/note-one" \ + "$TMP/main" "$TMP/bad-update" >/dev/null 2>&1; then + echo "mcpu-objcopy updated .text" >&2 + exit 1 +fi +if "$CPU_OBJCOPY" --rename-section=.text=.foo \ + "$TMP/main" "$TMP/bad-rename" >/dev/null 2>&1; then + echo "mcpu-objcopy renamed .text" >&2 + exit 1 +fi +if "$CPU_OBJCOPY" --add-section=.text="$TMP/note-one" \ + "$TMP/main" "$TMP/bad-duplicate" >/dev/null 2>&1; then + echo "mcpu-objcopy added duplicate .text" >&2 + exit 1 +fi + +# --only-section is strict: an executable cannot lose its runtime image. +if "$CPU_OBJCOPY" -j .note.test "$TMP/main.note" "$TMP/bad-only" \ + >/dev/null 2>&1; then + echo "mcpu-objcopy created an incomplete ET_EXEC with -j" >&2 + exit 1 +fi + +# ET_REL link semantics are protected, but optional metadata may be filtered. +"$CPU_OBJCOPY" --add-section=.note.rel="$TMP/note-one" \ + "$TMP/main.o" "$TMP/main.note.o" +"$CPU_OBJCOPY" \ + -j .text -j .symtab -j .strtab -j .mcpu.target -j .shstrtab \ + "$TMP/main.note.o" "$TMP/main.filtered.o" +if "$CPU_READELF" -S "$TMP/main.filtered.o" | grep -q '\.note\.rel'; then + echo "--only-section failed to remove optional ET_REL metadata" >&2 + exit 1 +fi +"$CPU_LD" --stack-size=4096 --no-heap -o "$TMP/main.filtered" \ + "$TMP/main.filtered.o" +test "$("$CPU_RUN" --int-reg=r64@0x00000 "$TMP/main.filtered")" = 42 + +if "$CPU_OBJCOPY" -R .symtab "$TMP/main.o" "$TMP/bad-rel" \ + >/dev/null 2>&1; then + echo "mcpu-objcopy removed ET_REL .symtab" >&2 + exit 1 +fi + +# Archives are transformed member-by-member and retain their symbol index. +"$CPU_OBJCOPY" --add-section=.note.arc="$TMP/note-one" \ + "$TMP/libadd.a" "$TMP/libadd.note.a" +"$CPU_NM" -A "$TMP/libadd.note.a" | grep -q 'add_one' +mkdir "$TMP/extract" +( + cd "$TMP/extract" + "$CPU_AR" x ../libadd.note.a + "$CPU_READELF" -S add-one.o | grep -q '\.note\.arc' +) +"$CPU_OBJCOPY" -R .note.arc "$TMP/libadd.note.a" "$TMP/libadd.clean.a" +"$CPU_NM" -A "$TMP/libadd.clean.a" | grep -q 'add_one' +if "$CPU_OBJCOPY" --dump-section=.text="$TMP/archive.dump" \ + "$TMP/libadd.a" "$TMP/libadd.dump.a" >/dev/null 2>&1; then + echo "mcpu-objcopy accepted ambiguous archive --dump-section" >&2 + exit 1 +fi + +"$CPU_OBJCOPY" --help | grep -q -- '--add-section=NAME=FILE' +"$CPU_OBJCOPY" --version | grep -Fq "$PACKAGE_VERSION" + +if "$CPU_OBJCOPY" "$TMP/main" >/dev/null 2>&1; then + echo "mcpu-objcopy unexpectedly accepted a missing output file" >&2 + exit 1 +fi +if "$CPU_OBJCOPY" "$TMP/main" "$TMP/main" >/dev/null 2>&1; then + echo "mcpu-objcopy unexpectedly accepted in-place operation" >&2 + exit 1 +fi + +exit 0 diff --git a/tests/tst-objdump-runner.sh b/tests/tst-objdump-runner.sh new file mode 100755 index 0000000..94142eb --- /dev/null +++ b/tests/tst-objdump-runner.sh @@ -0,0 +1,157 @@ +#!/bin/sh +set -eu + +: "${PACKAGE_VERSION:?}" + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_AR:?}" "${CPU_OBJDUMP:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-objdump-$$ +trap 'rm -rf "$TMP"' EXIT HUP INT TERM +mkdir -p "$TMP" + +cat > "$TMP/unit.s" <<'EOS' +.global entry +.global data_global +.extern external_function +.text +entry: + SETF CF + CLRF AF + CLRCPUF + CLRMPUF + LDI r64@0x00000, 41 + CALL local_helper + CALL external_function + RET +local_helper: + LDI r64@0x00008, 1 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +.data +data_global: + .integer 64, 7 +data_pointer: + .ptr data_global +EOS + +cat > "$TMP/dyn.s" <<'EOS' +.global dyn_api +.global dyn_data +.text +dyn_api: + CALL local_dyn + RET +local_dyn: + LDI r64@0x00008, 2 + RET +.data +dyn_data: + .integer 64, 9 +EOS + +cat > "$TMP/a.s" <<'EOS' +.global archive_a +.text +archive_a: + RET +EOS + +cat > "$TMP/b.s" <<'EOS' +.global archive_b +.text +archive_b: + RET +EOS + +"$CPU_AS" -o "$TMP/unit.o" "$TMP/unit.s" +"$CPU_AS" -o "$TMP/dyn.o" "$TMP/dyn.s" +"$CPU_AS" -o "$TMP/a.o" "$TMP/a.s" +"$CPU_AS" -o "$TMP/b.o" "$TMP/b.s" +"$CPU_LD" --shared --soname=libobjdump.so.1 -o "$TMP/libobjdump.so" "$TMP/dyn.o" +"$CPU_AR" rcs "$TMP/libobjdump.a" "$TMP/a.o" "$TMP/b.o" + +"$CPU_OBJDUMP" -h "$TMP/unit.o" > "$TMP/headers.txt" +grep -q '^Sections:$' "$TMP/headers.txt" +grep -q '\.text' "$TMP/headers.txt" +grep -q '\.data' "$TMP/headers.txt" +grep -q 'PROGBITS.*CODE' "$TMP/headers.txt" + +"$CPU_OBJDUMP" -t "$TMP/unit.o" > "$TMP/symbols.txt" +grep -q '^SYMBOL TABLE:$' "$TMP/symbols.txt" +grep -q ' entry$' "$TMP/symbols.txt" +grep -q ' local_helper$' "$TMP/symbols.txt" +grep -q ' data_global$' "$TMP/symbols.txt" +grep -q ' external_function$' "$TMP/symbols.txt" + +"$CPU_OBJDUMP" -r "$TMP/unit.o" > "$TMP/relocs.txt" +grep -q 'RELOCATION RECORDS FOR \[.text\]' "$TMP/relocs.txt" +grep -q 'R_MCPU_CODE64.*local_helper' "$TMP/relocs.txt" +grep -q 'R_MCPU_CODE64.*external_function' "$TMP/relocs.txt" + +"$CPU_OBJDUMP" -d -r "$TMP/unit.o" > "$TMP/disasm.txt" +grep -q 'Disassembly of section .text:' "$TMP/disasm.txt" +grep -q '<entry>:' "$TMP/disasm.txt" +grep -q '<local_helper>:' "$TMP/disasm.txt" +grep -q 'SETF.*CF' "$TMP/disasm.txt" +grep -q 'CLRF.*AF' "$TMP/disasm.txt" +grep -q 'CLRCPUF' "$TMP/disasm.txt" +grep -q 'CLRMPUF' "$TMP/disasm.txt" +grep -q 'LDI.*r64@0x00000, 41' "$TMP/disasm.txt" +grep -q 'CALL.*<local_helper>' "$TMP/disasm.txt" +grep -q 'CALL.*<external_function>' "$TMP/disasm.txt" +grep -q 'R_MCPU_CODE64.*local_helper' "$TMP/disasm.txt" +# -r remains complete with -d: non-executable-section relocations are +# printed separately while .text relocations stay next to instructions. +grep -q 'RELOCATION RECORDS FOR \[.data\]' "$TMP/disasm.txt" +grep -q 'R_MCPU_PTR64_DATA.*data_global' "$TMP/disasm.txt" + +"$CPU_OBJDUMP" -s -j .data "$TMP/unit.o" > "$TMP/data.txt" +grep -q 'Contents of section .data:' "$TMP/data.txt" +if grep -q 'Contents of section .text:' "$TMP/data.txt"; then + echo "mcpu-objdump -j .data unexpectedly displayed .text contents" >&2 + exit 1 +fi + +"$CPU_OBJDUMP" -D -j .data "$TMP/unit.o" > "$TMP/all-data.txt" +grep -q 'Disassembly of section .data:' "$TMP/all-data.txt" + +"$CPU_OBJDUMP" -T "$TMP/libobjdump.so" > "$TMP/dynamic.txt" +grep -q '^DYNAMIC SYMBOL TABLE:$' "$TMP/dynamic.txt" +grep -q ' dyn_api$' "$TMP/dynamic.txt" +grep -q ' dyn_data$' "$TMP/dynamic.txt" + +"$CPU_OBJDUMP" -d -r "$TMP/libobjdump.so" > "$TMP/dynamic-disasm.txt" +grep -q '<dyn_api>:' "$TMP/dynamic-disasm.txt" +grep -q 'CALL.*<.L\$0\$0>' "$TMP/dynamic-disasm.txt" +grep -q 'R_MCPU_CODE64.*.L\$0\$0' "$TMP/dynamic-disasm.txt" + +"$CPU_OBJDUMP" -t "$TMP/libobjdump.a" > "$TMP/archive.txt" +grep -F "$TMP/libobjdump.a(a.o):" "$TMP/archive.txt" >/dev/null +grep -F "$TMP/libobjdump.a(b.o):" "$TMP/archive.txt" >/dev/null +grep -q ' archive_a$' "$TMP/archive.txt" +grep -q ' archive_b$' "$TMP/archive.txt" + +# Grouped short options and attached -j argument are accepted. +"$CPU_OBJDUMP" -hdr -j.text "$TMP/unit.o" > "$TMP/grouped.txt" +grep -q 'Sections:' "$TMP/grouped.txt" +grep -q 'Disassembly of section .text:' "$TMP/grouped.txt" +grep -q 'R_MCPU_CODE64' "$TMP/grouped.txt" + +# Missing/invalid input remains a hard error. +dd if=/dev/zero of="$TMP/bad" bs=256 count=1 2>/dev/null +if "$CPU_OBJDUMP" -h "$TMP/bad" > /dev/null 2> "$TMP/bad.err"; then + echo "mcpu-objdump unexpectedly accepted a non-MELF file" >&2 + exit 1 +fi +grep -q 'not a MELF file' "$TMP/bad.err" + +if "$CPU_OBJDUMP" "$TMP/unit.o" > /dev/null 2> "$TMP/no-option.err"; then + echo "mcpu-objdump unexpectedly accepted no display option" >&2 + exit 1 +fi +grep -q 'at least one display option is required' "$TMP/no-option.err" + +"$CPU_OBJDUMP" --help | grep -q -- '--disassemble-all' +"$CPU_OBJDUMP" --help | grep -q -- '--dynamic-syms' +"$CPU_OBJDUMP" --help | grep -q -- '--section=NAME' +"$CPU_OBJDUMP" --version | grep -Fq "$PACKAGE_VERSION" diff --git a/tests/tst-overlap.c b/tests/tst-overlap.c new file mode 100644 index 0000000..9de9286 --- /dev/null +++ b/tests/tst-overlap.c @@ -0,0 +1,104 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> + +#include <libmcpu.h> + + +static int failures = 0; + +#define CHECK(cond,msg) \ + do { \ + if( !(cond) ) \ + { \ + fprintf( stderr, "FAIL: %s\n", (msg) ); \ + ++failures; \ + } \ + } while( 0 ) + + +int +main( void ) +{ + __mpu_UNIT_t *r64; + __mpu_UNIT_t *r32_0; + __mpu_UNIT_t *r32_4; + __mpu_UNIT_t *r16_2; + __mpu_UNIT_t *r8_3; + unsigned int i; + + __cpu_init(); + + r64 = __cpu_reg_ptr( 64, 0 ); + r32_0 = __cpu_reg_ptr( 32, 0 ); + r32_4 = __cpu_reg_ptr( 32, 4 ); + r16_2 = __cpu_reg_ptr( 16, 2 ); + r8_3 = __cpu_reg_ptr( 8, 3 ); + + CHECK( r64 != 0, "r64@0 pointer" ); + CHECK( r32_0 != 0, "r32@0 pointer" ); + CHECK( r32_4 != 0, "r32@4 pointer" ); + CHECK( r16_2 != 0, "r16@2 pointer" ); + CHECK( r8_3 != 0, "r8@3 pointer" ); + + CHECK( r64 == r32_0, "r64@0 and r32@0 start at same byte" ); + CHECK( r32_4 == r64 + 4, "r32@4 overlaps upper four bytes of r64@0" ); + CHECK( r16_2 == r64 + 2, "r16@2 overlaps bytes 2..3 of r64@0" ); + CHECK( r8_3 == r64 + 3, "r8@3 overlaps byte 3 of r64@0" ); + + for( i = 0; i < 8; ++i ) r64[i] = (__mpu_UNIT_t)( 0x10 + i ); + + CHECK( r32_0[0] == 0x10 && r32_0[1] == 0x11 && + r32_0[2] == 0x12 && r32_0[3] == 0x13, + "r32@0 sees bytes written through r64@0" ); + + CHECK( r32_4[0] == 0x14 && r32_4[1] == 0x15 && + r32_4[2] == 0x16 && r32_4[3] == 0x17, + "r32@4 sees bytes written through r64@0" ); + + CHECK( r16_2[0] == 0x12 && r16_2[1] == 0x13, + "r16@2 sees bytes written through r64@0" ); + + *r8_3 = 0xa5; + CHECK( r64[3] == 0xa5, + "write through r8@3 changes r64@0 storage" ); + CHECK( r32_0[3] == 0xa5, + "write through r8@3 changes r32@0 storage" ); + CHECK( r16_2[1] == 0xa5, + "write through r8@3 changes r16@2 storage" ); + + /* Check the same property at a non-zero naturally aligned address. */ + { + __mpu_UNIT_t *r128 = __cpu_reg_ptr( 128, 0x00100 ); + __mpu_UNIT_t *r32 = __cpu_reg_ptr( 32, 0x0010c ); + __mpu_UNIT_t *r8 = __cpu_reg_ptr( 8, 0x0010f ); + + CHECK( r128 != 0 && r32 != 0 && r8 != 0, + "overlap pointers at 0x00100" ); + CHECK( r32 == r128 + 12, + "r32@0x0010c inside r128@0x00100" ); + CHECK( r8 == r128 + 15, + "r8@0x0010f inside r128@0x00100" ); + + r128[15] = 0x5a; + CHECK( *r8 == 0x5a, + "r8 view sees r128 storage" ); + + r32[3] = 0xc3; + CHECK( r128[15] == 0xc3, + "r32 view changes r128 storage" ); + } + + if( failures != 0 ) + { + fprintf( stderr, "libcpu overlap regression: %d failure(s)\n", failures ); + return( 1 ); + } + + printf( "libcpu 0.0.3 overlapping register views regression: PASS\n" ); + printf( "overlap is byte-storage aliasing and is endian independent\n" ); + + return( 0 ); +} diff --git a/tests/tst-partial-link-runner.sh b/tests/tst-partial-link-runner.sh new file mode 100755 index 0000000..2f08e16 --- /dev/null +++ b/tests/tst-partial-link-runner.sh @@ -0,0 +1,276 @@ +#!/bin/bash +set -e + +CPU_AS=${CPU_AS:-../utils/mcpu-as} +CPU_LD=${CPU_LD:-../utils/mcpu-ld} +CPU_RUN=${CPU_RUN:-../utils/mcpu-run} +CPU_READELF=${CPU_READELF:-../utils/mcpu-readelf} +CPU_AR=${CPU_AR:-../utils/mcpu-ar} + +TMP=${TMPDIR:-/tmp}/libmcpu-partial-link-$$ +trap 'rm -rf "$TMP"' EXIT HUP INT TERM +mkdir -p "$TMP/lib" + +cat > "$TMP/main.s" <<'EOS' +.global _life +.extern add_two +.extern shared_value +.text +_start_life: + LDI r64@0x00000, 40 + CALL add_two + STORE shared_value, r64@0x00000 + LOAD r64@0x00018, shared_value + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/lib.s" <<'EOS' +.global add_two +.global shared_value +.global shared_ptr +.global add_two_ptr +.data +.align 32 +shared_value: + .integer 64, 0 +shared_ptr: + .ptr shared_value +add_two_ptr: + .cptr add_two +.text +add_two: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +"$CPU_AS" -o "$TMP/main.o" "$TMP/main.s" +"$CPU_AS" -o "$TMP/lib.o" "$TMP/lib.s" + +# A complete partial link is still ET_REL and may be used as the sole input +# object of a later final link. +"$CPU_LD" -r -o "$TMP/combined.o" "$TMP/main.o" "$TMP/lib.o" +"$CPU_READELF" -h "$TMP/combined.o" > "$TMP/combined.header" +"$CPU_READELF" -s "$TMP/combined.o" > "$TMP/combined.symbols" +"$CPU_READELF" -r "$TMP/combined.o" > "$TMP/combined.relocs" +grep -q 'Type:.*REL (Relocatable file)' "$TMP/combined.header" +grep -q 'add_two' "$TMP/combined.symbols" +grep -q 'shared_value' "$TMP/combined.symbols" +grep -q 'add_two' "$TMP/combined.relocs" +grep -q 'shared_value' "$TMP/combined.relocs" + +"$CPU_LD" --stack-size=4096 --no-heap -o "$TMP/combined-app" "$TMP/combined.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/combined-app"`" = 42 ] + +# -r does not require lifecycle completeness and preserves unresolved symbols. +"$CPU_LD" -r -o "$TMP/main-partial.o" "$TMP/main.o" +"$CPU_READELF" -s "$TMP/main-partial.o" | grep -q 'UND.*add_two' +"$CPU_LD" --stack-size=4096 --no-heap \ + -o "$TMP/late-app" "$TMP/main-partial.o" "$TMP/lib.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/late-app"`" = 42 ] + +"$CPU_LD" -r -o "$TMP/library-only.o" "$TMP/lib.o" +"$CPU_READELF" -h "$TMP/library-only.o" | grep -q 'REL (Relocatable file)' + +# LOCAL symbols from separate objects remain private even when they have the +# same spelling. Their relocations must still refer to their own definitions. +cat > "$TMP/local-a.s" <<'EOS' +.global fa +.text +fa: + CALL local_helper + RET +local_helper: + LDI r64@0x00008, 1 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/local-b.s" <<'EOS' +.global fb +.text +fb: + CALL local_helper + RET +local_helper: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/local-main.s" <<'EOS' +.global _life +.extern fa +.extern fb +.text +_start_life: + LDI r64@0x00000, 39 + CALL fa + CALL fb + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +"$CPU_AS" -o "$TMP/local-a.o" "$TMP/local-a.s" +"$CPU_AS" -o "$TMP/local-b.o" "$TMP/local-b.s" +"$CPU_AS" -o "$TMP/local-main.o" "$TMP/local-main.s" +"$CPU_LD" -r -o "$TMP/local-combined.o" "$TMP/local-a.o" "$TMP/local-b.o" +"$CPU_LD" --stack-size=4096 --no-heap \ + -o "$TMP/local-app" "$TMP/local-main.o" "$TMP/local-combined.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/local-app"`" = 42 ] + +# A strong GLOBAL definition replaces a WEAK definition in partial output. +cat > "$TMP/weak.s" <<'EOS' +.weak pick +.text +pick: + LDI r64@0x00008, 1 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/strong.s" <<'EOS' +.global pick +.text +pick: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/pick-main.s" <<'EOS' +.global _life +.extern pick +.text +_start_life: + LDI r64@0x00000, 40 + CALL pick + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +"$CPU_AS" -o "$TMP/weak.o" "$TMP/weak.s" +"$CPU_AS" -o "$TMP/strong.o" "$TMP/strong.s" +"$CPU_AS" -o "$TMP/pick-main.o" "$TMP/pick-main.s" +"$CPU_LD" -r -o "$TMP/pick.o" "$TMP/weak.o" "$TMP/strong.o" +"$CPU_LD" --stack-size=4096 --no-heap \ + -o "$TMP/pick-app" "$TMP/pick-main.o" "$TMP/pick.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/pick-app"`" = 42 ] + +# Two strong definitions are already an error at partial-link time. +cat > "$TMP/clash-a.s" <<'EOS' +.global clash +.text +clash: + RET +EOS +cat > "$TMP/clash-b.s" <<'EOS' +.global clash +.text +clash: + RET +EOS +"$CPU_AS" -o "$TMP/clash-a.o" "$TMP/clash-a.s" +"$CPU_AS" -o "$TMP/clash-b.o" "$TMP/clash-b.s" +if "$CPU_LD" -r -o "$TMP/clash.o" "$TMP/clash-a.o" "$TMP/clash-b.o" \ + > /dev/null 2> "$TMP/clash.err"; then + echo "partial link unexpectedly accepted duplicate GLOBAL definitions" >&2 + exit 1 +fi +grep -q 'multiple definition of global symbol: clash' "$TMP/clash.err" + +# -r may extract required ET_REL members from libNAME.a, but never consumes a +# same-named shared object. The unused archive member must stay outside the +# partial object even though it contains an unresolved reference of its own. +cat > "$TMP/usepart.s" <<'EOS' +.global call_part +.extern part +.text +call_part: + CALL part + RET +EOS +cat > "$TMP/part-static.s" <<'EOS' +.global part +.text +part: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/part-shared.s" <<'EOS' +.global part +.text +part: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/unused.s" <<'EOS' +.global unused_archive_member +.extern missing_from_unused_member +.text +unused_archive_member: + CALL missing_from_unused_member + RET +EOS +cat > "$TMP/archive-main.s" <<'EOS' +.global _life +.extern call_part +.text +_start_life: + LDI r64@0x00000, 40 + CALL call_part + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +"$CPU_AS" -o "$TMP/usepart.o" "$TMP/usepart.s" +"$CPU_AS" -o "$TMP/part-static.o" "$TMP/part-static.s" +"$CPU_AS" -o "$TMP/part-shared.o" "$TMP/part-shared.s" +"$CPU_AS" -o "$TMP/unused.o" "$TMP/unused.s" +"$CPU_AS" -o "$TMP/archive-main.o" "$TMP/archive-main.s" +"$CPU_AR" rcs "$TMP/lib/libpart.a" "$TMP/part-static.o" "$TMP/unused.o" +"$CPU_LD" --shared --soname=libpart.so.1 \ + -o "$TMP/lib/libpart.so.1.0.0" "$TMP/part-shared.o" +ln -s libpart.so.1.0.0 "$TMP/lib/libpart.so.1" +ln -s libpart.so.1 "$TMP/lib/libpart.so" + +"$CPU_LD" -r -L"$TMP/lib" -lpart -o "$TMP/from-archive.o" "$TMP/usepart.o" +"$CPU_READELF" -s "$TMP/from-archive.o" > "$TMP/from-archive.symbols" +grep -q ' part$' "$TMP/from-archive.symbols" +if grep -q 'unused_archive_member' "$TMP/from-archive.symbols"; then + echo "partial link unexpectedly extracted an unused archive member" >&2 + exit 1 +fi +"$CPU_LD" --stack-size=4096 --no-heap \ + -o "$TMP/archive-app" "$TMP/archive-main.o" "$TMP/from-archive.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/archive-app"`" = 42 ] + +# The resulting partial object is an ordinary ET_REL and can itself be stored +# in a static archive. +"$CPU_AR" rcs "$TMP/libcombined.a" "$TMP/combined.o" +[ "`"$CPU_AR" t "$TMP/libcombined.a"`" = combined.o ] + +# Options that describe final executable/shared-image state are invalid in -r. +if "$CPU_LD" -r --dynamic -o "$TMP/bad.o" "$TMP/lib.o" >/dev/null 2>&1; then + echo "-r --dynamic unexpectedly accepted" >&2 + exit 1 +fi +if "$CPU_LD" -r --shared --soname=bad.so -o "$TMP/bad.o" "$TMP/lib.o" >/dev/null 2>&1; then + echo "-r --shared unexpectedly accepted" >&2 + exit 1 +fi +if "$CPU_LD" -r --stack-size=4096 -o "$TMP/bad.o" "$TMP/lib.o" >/dev/null 2>&1; then + echo "-r --stack-size unexpectedly accepted" >&2 + exit 1 +fi +if "$CPU_LD" -r --no-heap -o "$TMP/bad.o" "$TMP/lib.o" >/dev/null 2>&1; then + echo "-r --no-heap unexpectedly accepted" >&2 + exit 1 +fi +if "$CPU_LD" -r --soname=bad.so -o "$TMP/bad.o" "$TMP/lib.o" >/dev/null 2>&1; then + echo "-r --soname unexpectedly accepted" >&2 + exit 1 +fi + +"$CPU_LD" --help 2>&1 | grep -q -- '-r.*partial-link MELF ET_REL' + +exit 0 diff --git a/tests/tst-pointer-arithmetic.c b/tests/tst-pointer-arithmetic.c new file mode 100644 index 0000000..76414fa --- /dev/null +++ b/tests/tst-pointer-arithmetic.c @@ -0,0 +1,170 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static const char src[]= +".data\n" +".align 8\n" +"values:\n" +" .integer 64, 11\n" +" .integer 64, 22\n" +" .integer 64, 33\n" +".text\n" +" LEA r64@0x00000, values\n" +" PADDI r64@0x00008, r64@0x00000, 16\n" +" LOADP r64@0x00010, r64@0x00008\n" +" PDIFF r64@0x00018, r64@0x00008, r64@0x00000\n" +" PSUBI r64@0x00020, r64@0x00008, 8\n" +" LOADP r64@0x00028, r64@0x00020\n" +" LDI r64@0x00030, 8\n" +" PADD r64@0x00038, r64@0x00000, r64@0x00030\n" +" LOADP r64@0x00040, r64@0x00038\n" +" PSUB r64@0x00048, r64@0x00038, r64@0x00030\n" +" LOADP r64@0x00050, r64@0x00048\n" +" PADDI r64@0x00058, r64@0x00008, -16\n" +" LOADP r64@0x00060, r64@0x00058\n" +" PSUBI r64@0x00068, r64@0x00000, -16\n" +" LOADP r64@0x00070, r64@0x00068\n" +" HALT\n"; + +static __mpu_uint64_t +reg_u64( unsigned address ) +{ + __mpu_uint64_t v = 0; + memcpy( &v, __cpu_reg_ptr( 64, address ), sizeof( v ) ); + return( v ); +} + +static __mpu_int64_t +reg_i64( unsigned address ) +{ + __mpu_int64_t v = 0; + memcpy( &v, __cpu_reg_ptr( 64, address ), sizeof( v ) ); + return( v ); +} + +static void +set_reg_u64( unsigned address, __mpu_uint64_t v ) +{ + memcpy( __cpu_reg_ptr( 64, address ), &v, sizeof( v ) ); +} + +static void +set_reg_i64( unsigned address, __mpu_int64_t v ) +{ + memcpy( __cpu_reg_ptr( 64, address ), &v, sizeof( v ) ); +} + +static void +regop( cpu_operand *o, unsigned address ) +{ + memset( o, 0, sizeof( *o ) ); + o->type = CPU_OPERAND_REGISTER; + o->value.reg.bits = 64; + o->value.reg.address = address; +} + +int +main( void ) +{ + cpu_asm_image im; + cpu_instruction i; + __mpu_uint64_t p, saved, minbits; + __mpu_int64_t mindelta; + __mpu_uint32_t psw; + char err[256]; + + __cpu_init(); + + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + CHECK( __cpu_data_set( im.data, im.data_size ) == 0, "data set" ); + CHECK( __cpu_run() == CPU_EXEC_SUCCESS, "pointer arithmetic program run" ); + + CHECK( __cpu_ptr_segment( reg_u64( 0x00008 ) ) == CPU_PTR_SEG_DATA, "PADDI preserves DATA segment" ); + CHECK( __cpu_ptr_offset( reg_u64( 0x00008 ) ) == 32, "PADDI offset" ); + CHECK( reg_i64( 0x00010 ) == 33, "PADDI + LOADP" ); + CHECK( reg_i64( 0x00018 ) == 16, "PDIFF byte difference" ); + CHECK( __cpu_ptr_offset( reg_u64( 0x00020 ) ) == 24, "PSUBI offset" ); + CHECK( reg_i64( 0x00028 ) == 22, "PSUBI + LOADP" ); + CHECK( __cpu_ptr_offset( reg_u64( 0x00038 ) ) == 24, "PADD register displacement" ); + CHECK( reg_i64( 0x00040 ) == 22, "PADD + LOADP" ); + CHECK( __cpu_ptr_offset( reg_u64( 0x00048 ) ) == 16, "PSUB register displacement" ); + CHECK( reg_i64( 0x00050 ) == 11, "PSUB + LOADP" ); + CHECK( __cpu_ptr_offset( reg_u64( 0x00058 ) ) == 16, "PADDI negative displacement" ); + CHECK( reg_i64( 0x00060 ) == 11, "PADDI negative + LOADP" ); + CHECK( __cpu_ptr_offset( reg_u64( 0x00068 ) ) == 32, "PSUBI negative displacement" ); + CHECK( reg_i64( 0x00070 ) == 33, "PSUBI negative + LOADP" ); + + /* Arithmetic is defined on any structurally valid PTR64 segment selector. */ + CHECK( __cpu_ptr_make( &p, 0x0f, 100 ) == 0, "make reserved-segment PTR64" ); + set_reg_u64( 0x00100, p ); + set_reg_i64( 0x00108, 23 ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_PADD; + regop( &i.operand[0], 0x00110 ); + regop( &i.operand[1], 0x00100 ); + regop( &i.operand[2], 0x00108 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "PADD reserved segment does not dereference" ); + p = reg_u64( 0x00110 ); + CHECK( __cpu_ptr_segment( p ) == 0x0f, "PADD preserves reserved segment selector" ); + CHECK( __cpu_ptr_offset( p ) == 123, "PADD reserved segment offset" ); + + /* PSW is untouched by all pointer arithmetic instructions. */ + __cpu_set_psw( PSW_CF_MASK | PSW_IF_MASK ); + psw = __cpu_get_psw(); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "PADD for PSW preservation" ); + CHECK( __cpu_get_psw() == psw, "PADD preserves PSW" ); + + /* 60-bit offset overflow/underflow faults are atomic. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, CPU_PTR_OFFSET_MASK ) == 0, "make max-offset PTR64" ); + set_reg_u64( 0x00100, p ); + set_reg_i64( 0x00108, 1 ); + saved = __mpu_UINT64_C( 0x1122334455667788 ); + set_reg_u64( 0x00110, saved ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "PADD offset overflow" ); + CHECK( reg_u64( 0x00110 ) == saved, "PADD overflow leaves destination unchanged" ); + + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, 0 ) == 0, "make zero-offset PTR64" ); + set_reg_u64( 0x00100, p ); + set_reg_i64( 0x00108, -1 ); + set_reg_u64( 0x00110, saved ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "PADD offset underflow" ); + CHECK( reg_u64( 0x00110 ) == saved, "PADD underflow leaves destination unchanged" ); + + /* INT64_MIN is handled without signed-overflow undefined behaviour. */ + minbits = __mpu_UINT64_C( 0x8000000000000000 ); + memcpy( &mindelta, &minbits, sizeof( mindelta ) ); + set_reg_i64( 0x00108, mindelta ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "PADD INT64_MIN displacement faults cleanly" ); + + /* PDIFF requires equal segment selectors and is also atomic on fault. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, 64 ) == 0, "make DATA pointer for PDIFF" ); + set_reg_u64( 0x00100, p ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_STACK, 16 ) == 0, "make STACK pointer for PDIFF" ); + set_reg_u64( 0x00108, p ); + set_reg_u64( 0x00110, saved ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_PDIFF; + regop( &i.operand[0], 0x00110 ); + regop( &i.operand[1], 0x00100 ); + regop( &i.operand[2], 0x00108 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "PDIFF rejects different segments" ); + CHECK( reg_u64( 0x00110 ) == saved, "PDIFF segment fault leaves destination unchanged" ); + + /* Same-segment PDIFF may be negative and still fits signed integer64. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_PROGRAM, 10 ) == 0, "make PROGRAM pointer low" ); + set_reg_u64( 0x00100, p ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_PROGRAM, 42 ) == 0, "make PROGRAM pointer high" ); + set_reg_u64( 0x00108, p ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "PDIFF PROGRAM pointers" ); + CHECK( reg_i64( 0x00110 ) == -32, "PDIFF signed negative result" ); + + cpu_asm_image_free( &im ); + __cpu_free_context(); + puts( "libcpu PTR64 arithmetic regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-pointer-policy.c b/tests/tst-pointer-policy.c new file mode 100644 index 0000000..15d3b0f --- /dev/null +++ b/tests/tst-pointer-policy.c @@ -0,0 +1,152 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static void +regop( cpu_operand *o, unsigned bits, unsigned address ) +{ + memset( o, 0, sizeof( *o ) ); + o->type = CPU_OPERAND_REGISTER; + o->value.reg.bits = bits; + o->value.reg.address = address; +} + +static void +set_u64( unsigned address, __mpu_uint64_t v ) +{ + __cpu_data_put_u64( __cpu_reg_ptr( 64, address ), v ); +} + +static __mpu_uint64_t +get_u64( unsigned address ) +{ + return( __cpu_data_get_u64( __cpu_reg_ptr( 64, address ) ) ); +} + +static int +exec2( cpu_opcode op, unsigned a, unsigned b ) +{ + cpu_instruction i; + memset( &i, 0, sizeof( i ) ); + i.opcode = op; + regop( &i.operand[0], 64, a ); + regop( &i.operand[1], 64, b ); + return( __cpu_exec( &i ) ); +} + +static int +exec3( cpu_opcode op, unsigned d, unsigned a, unsigned b ) +{ + cpu_instruction i; + memset( &i, 0, sizeof( i ) ); + i.opcode = op; + regop( &i.operand[0], 64, d ); + regop( &i.operand[1], 64, a ); + regop( &i.operand[2], 64, b ); + return( __cpu_exec( &i ) ); +} + +int +main( void ) +{ + __mpu_uint64_t p, q, saved; + __mpu_UNIT_t code[2] = + { + 0, 0 + }; + cpu_instruction i; + + __cpu_init(); + CHECK( CPU_ABI_STACK_RED_ZONE_SIZE == 0U, "ABI red zone is zero bytes" ); + CHECK( __cpu_data_alloc( 128 ) == 0, "DATA alloc" ); + CHECK( __cpu_stack_alloc( 128 ) == 0, "STACK alloc" ); + CHECK( __cpu_program_set( code, sizeof( code ) ) == 0, "PROGRAM set" ); + + /* Pointer equality is full 64-bit equality and can be tested with ICMP/ZF. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, 32 ) == 0, "make DATA pointer p" ); + CHECK( __cpu_ptr_make( &q, CPU_PTR_SEG_DATA, 32 ) == 0, "make equal DATA pointer q" ); + set_u64( 0x100, p ); + set_u64( 0x108, q ); + CHECK( exec2( CPU_OP_ICMP, 0x100, 0x108 ) == CPU_EXEC_SUCCESS, "ICMP equal pointers" ); + CHECK( ( __cpu_get_psw() & PSW_ZF_MASK ) != 0, "equal PTR64 sets ZF" ); + + CHECK( __cpu_ptr_make( &q, CPU_PTR_SEG_STACK, 32 ) == 0, "make STACK pointer q" ); + set_u64( 0x108, q ); + CHECK( exec2( CPU_OP_ICMP, 0x100, 0x108 ) == CPU_EXEC_SUCCESS, "ICMP cross-segment bit patterns" ); + CHECK( ( __cpu_get_psw() & PSW_ZF_MASK ) == 0, "different segments are not equal" ); + + /* Pointer ordering is defined by PDIFF only inside one selector. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, 64 ) == 0, "make lower DATA pointer" ); + CHECK( __cpu_ptr_make( &q, CPU_PTR_SEG_DATA, 96 ) == 0, "make upper DATA pointer" ); + set_u64( 0x100, p ); + set_u64( 0x108, q ); + CHECK( exec3( CPU_OP_PDIFF, 0x110, 0x100, 0x108 ) == CPU_EXEC_SUCCESS, "same-segment PDIFF" ); + CHECK( (__mpu_int64_t)get_u64( 0x110 ) == -32, "PDIFF gives signed byte difference" ); + CHECK( exec2( CPU_OP_ITEST, 0x110, 0x110 ) == CPU_EXEC_SUCCESS, "ITEST pointer difference" ); + CHECK( ( __cpu_get_psw() & PSW_SF_MASK ) != 0, "negative pointer difference sets SF" ); + + saved = __mpu_UINT64_C( 0x1122334455667788 ); + set_u64( 0x110, saved ); + CHECK( __cpu_ptr_make( &q, CPU_PTR_SEG_STACK, 96 ) == 0, "make cross-segment pointer" ); + set_u64( 0x108, q ); + CHECK( exec3( CPU_OP_PDIFF, 0x110, 0x100, 0x108 ) == CPU_EXEC_POINTER_FAULT, "PDIFF rejects cross-segment ordering" ); + CHECK( get_u64( 0x110 ) == saved, "PDIFF cross-segment fault is atomic" ); + + /* Reserved selectors are structurally valid and support non-dereferencing arithmetic. */ + CHECK( __cpu_ptr_make( &p, 0x0f, 123 ) == 0, "make reserved pointer p" ); + CHECK( __cpu_ptr_make( &q, 0x0f, 100 ) == 0, "make reserved pointer q" ); + set_u64( 0x100, p ); + set_u64( 0x108, q ); + CHECK( exec3( CPU_OP_PDIFF, 0x110, 0x100, 0x108 ) == CPU_EXEC_SUCCESS, "reserved same-selector PDIFF" ); + CHECK( (__mpu_int64_t)get_u64( 0x110 ) == 23, "reserved PDIFF difference" ); + + /* A reserved PTR64 may be stored as data but may not be dereferenced. */ + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_STORE; + i.operand[0].type = CPU_OPERAND_ADDR64; + i.operand[0].value.addr64 = 16; + regop( &i.operand[1], 64, 0x100 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "STORE preserves reserved PTR64 as ordinary data" ); + CHECK( __cpu_data_get_u64( __cpu_data_ptr() + 16 ) == p, "stored reserved PTR64 bits" ); + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_LOADP; + regop( &i.operand[0], 64, 0x118 ); + regop( &i.operand[1], 64, 0x100 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "LOADP rejects reserved selector" ); + + /* Even a zero-length BMOVI requires semantically valid address-space selectors. */ + CHECK( __cpu_ptr_make( &q, CPU_PTR_SEG_DATA, 16 ) == 0, "make valid DATA pointer" ); + set_u64( 0x108, q ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_BMOVI; + regop( &i.operand[0], 64, 0x108 ); + regop( &i.operand[1], 64, 0x100 ); + i.operand[2].type = CPU_OPERAND_IMM64; + i.operand[2].value.imm64 = 0; + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "zero-length BMOVI rejects reserved source" ); + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_JMPP; + regop( &i.operand[0], 64, 0x100 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "JMPP rejects reserved selector" ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_CALLP; + regop( &i.operand[0], 64, 0x100 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "CALLP rejects reserved selector" ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_SETSP; + regop( &i.operand[0], 64, 0x100 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "SETSP rejects reserved selector" ); + + /* DATA offsets 1..15 are ABI-reserved guard values, but only all-zero PTR64 is NULL. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, 1 ) == 0, "make DATA guard pointer" ); + CHECK( p != CPU_NULL_PTR, "DATA:1 is not NULL" ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, 0 ) == 0 && p == CPU_NULL_PTR, "DATA:0 is canonical NULL" ); + + __cpu_free_context(); + puts( "libcpu pointer-comparison/red-zone/reserved-segment policy regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-pointer.c b/tests/tst-pointer.c new file mode 100644 index 0000000..ca0ff99 --- /dev/null +++ b/tests/tst-pointer.c @@ -0,0 +1,154 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static const char src[]= +".data\n" +".align 8\n" +"global_value:\n" +" .integer 64, 41\n" +".text\n" +" LEA r64@0x00000, global_value\n" +" LOADP r64@0x00008, r64@0x00000\n" +" LDI r64@0x00010, 42\n" +" STOREP r64@0x00000, r64@0x00010\n" +" LOADP r64@0x00018, r64@0x00000\n" +" ADJSP -16\n" +" LDI r64@0x00020, 11\n" +" LDI r64@0x00028, 22\n" +" STSP 0, r64@0x00020\n" +" STSP 8, r64@0x00028\n" +" LEASP r64@0x00030, 0\n" +" LOADP r64@0x00038, r64@0x00030\n" +" LEASP r64@0x00040, 8\n" +" LOADP r64@0x00048, r64@0x00040\n" +" LDI r64@0x00050, 33\n" +" STOREP r64@0x00040, r64@0x00050\n" +" LDSP r64@0x00058, 8\n" +" ADJSP 16\n" +" HALT\n"; + +static int +eq64( unsigned address, const char *text ) +{ + mpu_int64_t x; + iatoi( ( mpu_int * ) x, (__mpu_char8_t *)text, NB_I64 ); + return( !memcmp( __cpu_reg_ptr( 64, address ), x, NB_I64 ) ); +} + +static __mpu_uint64_t +reg_u64( unsigned address ) +{ + __mpu_uint64_t v = 0; + memcpy( &v, __cpu_reg_ptr( 64, address ), 8 ); + return( v ); +} + +int +main( void ) +{ + cpu_asm_image im; + cpu_instruction i; + __mpu_uint64_t p; + __mpu_UNIT_t saved[8]; + char err[256]; + int rc; + + __cpu_init(); + + CHECK( CPU_PTR_SEG_SHIFT == 60U, "PTR64 segment shift" ); + CHECK( CPU_PTR_OFFSET_MASK == __mpu_UINT64_C( 0x0fffffffffffffff ), "PTR64 offset mask" ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, 0x1234 ) == 0, "make DATA pointer" ); + CHECK( p == __mpu_UINT64_C( 0x0000000000001234 ), "DATA pointer encoding" ); + CHECK( __cpu_ptr_segment( p ) == CPU_PTR_SEG_DATA, "DATA pointer segment" ); + CHECK( __cpu_ptr_offset( p ) == 0x1234, "DATA pointer offset" ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_STACK, 0x1234 ) == 0, "make STACK pointer" ); + CHECK( p == __mpu_UINT64_C( 0x1000000000001234 ), "STACK pointer encoding" ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_PROGRAM, 0x1234 ) == 0, "make PROGRAM pointer" ); + CHECK( p == __mpu_UINT64_C( 0x2000000000001234 ), "PROGRAM pointer encoding" ); + CHECK( __cpu_ptr_make( &p, 0x10, 0 ) == -1, "reject segment wider than four bits" ); + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_DATA, CPU_PTR_OFFSET_MASK + 1 ) == -1, "reject offset wider than 60 bits" ); + + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + CHECK( __cpu_data_set( im.data, im.data_size ) == 0, "data set" ); + CHECK( __cpu_stack_alloc( 128 ) == 0, "stack alloc" ); + __cpu_pc = 0; + CHECK( __cpu_run() == CPU_EXEC_SUCCESS, "PTR64 program run" ); + + p = reg_u64( 0x00000 ); + CHECK( __cpu_ptr_segment( p ) == CPU_PTR_SEG_DATA, "LEA creates DATA pointer" ); + CHECK( __cpu_ptr_offset( p ) == CPU_DATA_NULL_GUARD_SIZE, "LEA DATA label offset after NULL guard" ); + CHECK( eq64( 0x00008, "41" ), "LOADP reads DATA" ); + CHECK( eq64( 0x00018, "42" ), "STOREP writes DATA and LOADP reads it back" ); + CHECK( eq64( 0x00038, "11" ), "LEASP/LOADP first local object field" ); + CHECK( eq64( 0x00048, "22" ), "LEASP/LOADP second local object field" ); + CHECK( eq64( 0x00058, "33" ), "STOREP writes STACK through PTR64" ); + CHECK( __cpu_sp == 128, "stack frame released" ); + + p = reg_u64( 0x00030 ); + CHECK( __cpu_ptr_segment( p ) == CPU_PTR_SEG_STACK, "LEASP segment selector" ); + CHECK( __cpu_ptr_offset( p ) == 112, "LEASP absolute stack offset for SP+0" ); + + /* PROGRAM is readable through PTR64 but is not writable. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_PROGRAM, 0 ) == 0, "make PROGRAM pointer for indirect access" ); + memcpy( __cpu_reg_ptr( 64, 0x00060 ), &p, 8 ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_LOADP; + i.operand[0].type = CPU_OPERAND_REGISTER; + i.operand[0].value.reg.bits = 16; + i.operand[0].value.reg.address = 0x00068; + i.operand[1].type = CPU_OPERAND_REGISTER; + i.operand[1].value.reg.bits = 64; + i.operand[1].value.reg.address = 0x00060; + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "LOADP may read PROGRAM" ); + CHECK( !memcmp( __cpu_reg_ptr( 16, 0x00068 ), __cpu_program_ptr(), 2 ), "LOADP PROGRAM bytes" ); + + memcpy( saved, __cpu_program_ptr(), sizeof( saved ) ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_STOREP; + i.operand[0].type = CPU_OPERAND_REGISTER; + i.operand[0].value.reg.bits = 64; + i.operand[0].value.reg.address = 0x00060; + i.operand[1].type = CPU_OPERAND_REGISTER; + i.operand[1].value.reg.bits = 64; + i.operand[1].value.reg.address = 0x00010; + CHECK( __cpu_exec( &i ) == CPU_EXEC_MEMORY_FAULT, "STOREP rejects PROGRAM write" ); + CHECK( !memcmp( saved, __cpu_program_ptr(), sizeof( saved ) ), "PROGRAM write fault is atomic" ); + + + /* ABI NULL is all-zero PTR64 and is never dereferenceable. */ + p = CPU_NULL_PTR; + memcpy( __cpu_reg_ptr( 64, 0x00060 ), &p, 8 ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_LOADP; + i.operand[0].type = CPU_OPERAND_REGISTER; + i.operand[0].value.reg.bits = 8; + i.operand[0].value.reg.address = 0x00070; + i.operand[1].type = CPU_OPERAND_REGISTER; + i.operand[1].value.reg.bits = 64; + i.operand[1].value.reg.address = 0x00060; + CHECK( __cpu_exec( &i ) == CPU_EXEC_POINTER_FAULT, "NULL PTR64 dereference faults" ); + + /* Reserved segment selectors decode but cannot currently be dereferenced. */ + CHECK( __cpu_ptr_make( &p, CPU_PTR_SEG_FIRST_RESERVED, 0 ) == 0, "reserved segment fits PTR64" ); + memcpy( __cpu_reg_ptr( 64, 0x00060 ), &p, 8 ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_LOADP; + i.operand[0].type = CPU_OPERAND_REGISTER; + i.operand[0].value.reg.bits = 8; + i.operand[0].value.reg.address = 0x00070; + i.operand[1].type = CPU_OPERAND_REGISTER; + i.operand[1].value.reg.bits = 64; + i.operand[1].value.reg.address = 0x00060; + rc = __cpu_exec( &i ); + CHECK( rc == CPU_EXEC_POINTER_FAULT, "reserved PTR64 segment faults on dereference" ); + + cpu_asm_image_free( &im ); + __cpu_free_context(); + puts( "libcpu PTR64/indirect-addressing regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-psw-abi-runner.sh b/tests/tst-psw-abi-runner.sh new file mode 100755 index 0000000..37fb5fa --- /dev/null +++ b/tests/tst-psw-abi-runner.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +base="${TMPDIR:-/tmp}/libcpu-psw-abi-$$" +trap 'rm -f "$base.s" "$base.o" "$base.melf"' EXIT HUP INT TERM +"$CPU_AS" -o "$base.o" "$TEST_SRCDIR/fixtures/psw-call-abi.s" >/dev/null +"$CPU_LD" --stack-size=512 -o "$base.melf" "$base.o" >/dev/null +out=`"$CPU_RUN" --int-reg=r64@0x00010 "$base.melf"` +test "x$out" = "x42" diff --git a/tests/tst-psw-abi.c b/tests/tst-psw-abi.c new file mode 100644 index 0000000..9e34ef9 --- /dev/null +++ b/tests/tst-psw-abi.c @@ -0,0 +1,133 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> + +#include <libmcpu.h> + +#define CHECK(c,m) do { if(!(c)) { fprintf(stderr,"FAIL: %s\n",(m)); return 1; } } while(0) + +static cpu_operand +reg_operand( unsigned int bits, __mpu_uint32_t address ) +{ + cpu_operand o; + memset( &o, 0, sizeof( o ) ); + o.type = CPU_OPERAND_REGISTER; + o.value.reg.bits = bits; + o.value.reg.address = address; + return( o ); +} + +static void +put_u64_reg( unsigned int address, __mpu_uint64_t value ) +{ + memcpy( __cpu_reg_ptr( 64, address ), &value, sizeof( value ) ); +} + +int +main( void ) +{ + cpu_instruction i; + __mpu_uint32_t before, after, control_before; + __mpu_uint64_t target, ret; + __mpu_UNIT_t *a, *b, *d; + + __cpu_init(); + + CHECK( CPU_PSW_ABI_VOLATILE_STATUS_MASK == PSW_MPU_MASK, + "volatile PSW domain is exactly PSW[15:0]"); + CHECK( CPU_PSW_ABI_PERSISTENT_CONTROL_MASK == + ( PSW_TF_MASK | PSW_IF_MASK | PSW_MODE_MASK ), + "persistent PSW domain is TF/IF/MODE"); + CHECK( CPU_PSW_ABI_EXECUTION_STATE_MASK == PSW_HF_MASK, + "HF is the execution-state PSW domain"); + CHECK( ( CPU_PSW_ABI_VOLATILE_STATUS_MASK & + CPU_PSW_ABI_PERSISTENT_CONTROL_MASK)==0, + "volatile and persistent PSW domains do not overlap"); + CHECK( ( CPU_PSW_ABI_VOLATILE_STATUS_MASK | + CPU_PSW_ABI_PERSISTENT_CONTROL_MASK | + CPU_PSW_ABI_EXECUTION_STATE_MASK)==PSW_USED_MASK, + "PSW ABI domains cover all currently used PSW bits"); + + CHECK( __cpu_program_alloc( 64 ) == 0, "PROGRAM allocation" ); + CHECK( __cpu_stack_alloc( 128 ) == 0, "STACK allocation" ); + + /* CALL/RET are instructions, not implicit PSW save/restore operations. + Their own execution must leave every currently used PSW bit untouched. */ + before = ( PSW_AF_MASK | PSW_CF_MASK | PSW_ZF_MASK | PSW_INXF_MASK | + PSW_HF_MASK | PSW_TF_MASK | PSW_IF_MASK | + ( (__mpu_uint32_t)2U << PSW_MODE_SHIFT )); + __cpu_set_psw( before ); + before = __cpu_get_psw(); + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_CALL; + i.operand[0].type = CPU_OPERAND_CODE64; + i.operand[0].value.code64 = 32; + __cpu_pc = 4; + __cpu_sp = 128; + CHECK( __cpu_exec( &i ) == CPU_EXEC_BRANCH, "CALL executes" ); + CHECK( __cpu_get_psw() == before, "CALL itself is PSW-neutral" ); + CHECK( __cpu_sp == 120, "CALL pushes return PC" ); + memcpy( &ret, __cpu_stack_ptr() + 120, sizeof( ret ) ); + CHECK( ret == 14, "CALL raw return PC" ); + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_RET; + CHECK( __cpu_exec( &i ) == CPU_EXEC_BRANCH, "RET executes" ); + CHECK( __cpu_get_psw() == before, "RET itself is PSW-neutral" ); + CHECK( __cpu_pc == 14 && __cpu_sp == 128, "RET restores PC/SP call state" ); + + /* CALLP has the same PSW-neutral instruction semantics. */ + CHECK( __cpu_ptr_make( &target, CPU_PTR_SEG_PROGRAM, 40 ) == 0, + "make PROGRAM PTR64"); + put_u64_reg( 0x00100, target ); + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_CALLP; + i.operand[0] = reg_operand( 64, 0x00100 ); + __cpu_pc = 8; + CHECK( __cpu_exec( &i ) == CPU_EXEC_BRANCH, "CALLP executes" ); + CHECK( __cpu_get_psw() == before, "CALLP itself is PSW-neutral" ); + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_RET; + CHECK( __cpu_exec( &i ) == CPU_EXEC_BRANCH, "RET after CALLP executes" ); + CHECK( __cpu_get_psw() == before, "RET after CALLP is PSW-neutral" ); + CHECK( __cpu_pc == 13 && __cpu_sp == 128, "CALLP/RET restore PC/SP call state" ); + + /* Arithmetic status is caller-clobbered, while TF/IF/MODE are persistent + control state. IADD is sufficient to demonstrate both domains. */ + a = __cpu_reg_ptr( 8, 0x00200 ); + b = __cpu_reg_ptr( 8, 0x00201 ); + d = __cpu_reg_ptr( 8, 0x00202 ); + CHECK( a && b && d, "IADD register views" ); + *a = 1; + *b = 1; + *d = 0; + + __cpu_set_psw( PSW_CF_MASK | PSW_ZF_MASK | PSW_INXF_MASK | + PSW_TF_MASK | PSW_IF_MASK | + ( (__mpu_uint32_t)3U << PSW_MODE_SHIFT )); + before = __cpu_get_psw(); + control_before = before & CPU_PSW_ABI_PERSISTENT_CONTROL_MASK; + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_IADD; + i.operand[0] = reg_operand( 8, 0x00202 ); + i.operand[1] = reg_operand( 8, 0x00200 ); + i.operand[2] = reg_operand( 8, 0x00201 ); + CHECK( __cpu_exec( &i ) == CPU_EXEC_SUCCESS, "IADD executes" ); + after = __cpu_get_psw(); + CHECK( *d == 2, "IADD result" ); + CHECK( ( after & CPU_PSW_ABI_PERSISTENT_CONTROL_MASK ) == control_before, + "ordinary arithmetic preserves TF/IF/MODE control domain"); + CHECK( ( after & CPU_PSW_ABI_VOLATILE_STATUS_MASK ) != + ( before & CPU_PSW_ABI_VOLATILE_STATUS_MASK ), + "arithmetic may clobber volatile PSW[15:0]"); + + __cpu_free_context(); + puts( "libcpu PSW calling-convention regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-psw-write.c b/tests/tst-psw-write.c new file mode 100644 index 0000000..0410ec6 --- /dev/null +++ b/tests/tst-psw-write.c @@ -0,0 +1,255 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> + +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + + +static int failures = 0; + +#define CHECK(cond,msg) \ + do { \ + if( !(cond) ) \ + { \ + fprintf( stderr, "FAIL: %s\n", (msg) ); \ + ++failures; \ + } \ + } while( 0 ) + + +static void +clear_instruction( cpu_instruction *instruction, cpu_opcode opcode ) +{ + memset( instruction, 0, sizeof(*instruction) ); + instruction->opcode = opcode; +} + + +static cpu_operand +psw_operand( unsigned int bit ) +{ + cpu_operand operand; + + memset( &operand, 0, sizeof(operand) ); + operand.type = CPU_OPERAND_PSWBIT8; + operand.value.pswbit8 = bit; + + return( operand ); +} + + +static cpu_operand +reg_operand( unsigned int bits, __mpu_uint32_t address ) +{ + cpu_operand operand; + + memset( &operand, 0, sizeof(operand) ); + operand.type = CPU_OPERAND_REGISTER; + operand.value.reg.bits = bits; + operand.value.reg.address = address; + + return( operand ); +} + + +static void +check_assembler_contract( void ) +{ + static const char good_source[] = + ".text\n" + "SETF CF\n" + "CLRF AF\n" + "CLRCPUF\n" + "CLRMPUF\n" + "HALT\n"; + static const char *bad_source[] = + { + ".text\nSETF HF\n", + ".text\nSETF TF\n", + ".text\nSETF IF\n", + ".text\nCLRF HF\n", + ".text\nCLRF TF\n", + ".text\nCLRF IF\n" + }; + cpu_asm_image image; + cpu_instruction instruction; + char err[256]; + __mpu_uint64_t pc = 0; + unsigned int i; + + memset( &image, 0, sizeof(image) ); + CHECK( cpu_assemble_text( good_source, &image, err, sizeof(err) ) == 0, + "assemble writable PSW flag instructions" ); + if( image.code ) + { + CHECK( __cpu_decode_at( image.code, image.code_size, pc, &instruction ) == CPU_EXEC_SUCCESS, + "decode SETF" ); + CHECK( instruction.opcode == CPU_OP_SETF, "SETF opcode" ); + CHECK( instruction.operand[0].type == CPU_OPERAND_PSWBIT8 && + instruction.operand[0].value.pswbit8 == CPU_PSWBIT_CF, + "SETF selector" ); + pc += instruction.length; + + CHECK( __cpu_decode_at( image.code, image.code_size, pc, &instruction ) == CPU_EXEC_SUCCESS, + "decode CLRF" ); + CHECK( instruction.opcode == CPU_OP_CLRF, "CLRF opcode" ); + CHECK( instruction.operand[0].value.pswbit8 == CPU_PSWBIT_AF, + "CLRF selector" ); + pc += instruction.length; + + CHECK( __cpu_decode_at( image.code, image.code_size, pc, &instruction ) == CPU_EXEC_SUCCESS, + "decode CLRCPUF" ); + CHECK( instruction.opcode == CPU_OP_CLRCPUF, "CLRCPUF opcode" ); + pc += instruction.length; + + CHECK( __cpu_decode_at( image.code, image.code_size, pc, &instruction ) == CPU_EXEC_SUCCESS, + "decode CLRMPUF" ); + CHECK( instruction.opcode == CPU_OP_CLRMPUF, "CLRMPUF opcode" ); + } + cpu_asm_image_free( &image ); + + for( i = 0; i < sizeof(bad_source) / sizeof(bad_source[0]); ++i ) + { + memset( &image, 0, sizeof(image) ); + CHECK( cpu_assemble_text( bad_source[i], &image, err, sizeof(err) ) != 0, + "assembler rejects control/execution PSW selector write" ); + cpu_asm_image_free( &image ); + } +} + + +int +main( void ) +{ + cpu_instruction instruction; + __mpu_uint32_t psw, expected; + unsigned int bit; + + __cpu_init(); + + CHECK( CPU_OP_SETF == 0x0010, "SETF OPCODE16" ); + CHECK( CPU_OP_CLRF == 0x0011, "CLRF OPCODE16" ); + CHECK( CPU_OP_CLRCPUF == 0x0012, "CLRCPUF OPCODE16" ); + CHECK( CPU_OP_CLRMPUF == 0x0013, "CLRMPUF OPCODE16" ); + CHECK( PSW_CPUF_MASK == __mpu_UINT32_C(0x000000ff), "CPU flag mask" ); + CHECK( PSW_MPUF_MASK == __mpu_UINT32_C(0x0000ff00), "MPU flag mask" ); + + /* SETF/CLRF write exactly one arithmetic/status flag in PSW[15:0]. */ + for( bit = CPU_PSWBIT_AF; bit <= CPU_PSWBIT_WRITE_MAX; ++bit ) + { + __mpu_uint32_t mask = (__mpu_uint32_t)1U << bit; + + __cpu_set_psw( PSW_TF_MASK | PSW_IF_MASK | (__mpu_uint32_t)(2U << PSW_MODE_SHIFT) ); + psw = __cpu_get_psw(); + + clear_instruction( &instruction, CPU_OP_SETF ); + instruction.operand[0] = psw_operand( bit ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "SETF return code" ); + CHECK( __cpu_get_psw() == ( psw | mask ), "SETF changes only selected flag" ); + + psw = __cpu_get_psw(); + clear_instruction( &instruction, CPU_OP_CLRF ); + instruction.operand[0] = psw_operand( bit ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "CLRF return code" ); + CHECK( __cpu_get_psw() == ( psw & ~mask ), "CLRF changes only selected flag" ); + } + + /* HF/TF/IF are readable by branches but are not writable through SETF/CLRF. */ + for( bit = CPU_PSWBIT_HF; bit <= CPU_PSWBIT_IF; ++bit ) + { + clear_instruction( &instruction, CPU_OP_SETF ); + instruction.operand[0] = psw_operand( bit ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_OPERAND_FAULT, + "SETF rejects HF/TF/IF" ); + + clear_instruction( &instruction, CPU_OP_CLRF ); + instruction.operand[0] = psw_operand( bit ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_OPERAND_FAULT, + "CLRF rejects HF/TF/IF" ); + } + + /* CLRCPUF clears only PSW[7:0]. */ + __cpu_set_psw( PSW_USED_MASK ); + psw = __cpu_get_psw(); + expected = psw & ~PSW_CPUF_MASK; + clear_instruction( &instruction, CPU_OP_CLRCPUF ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "CLRCPUF return code" ); + CHECK( __cpu_get_psw() == expected, "CLRCPUF preserves PSW[15:8] and control state" ); + + /* CLRMPUF clears only PSW[15:8]. */ + __cpu_set_psw( PSW_USED_MASK ); + psw = __cpu_get_psw(); + expected = psw & ~PSW_MPUF_MASK; + clear_instruction( &instruction, CPU_OP_CLRMPUF ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "CLRMPUF return code" ); + CHECK( __cpu_get_psw() == expected, "CLRMPUF preserves PSW[7:0] and control state" ); + + /* SETF/CLRF provide an explicit initial carry for LibMPU IADC. */ + { + __mpu_UNIT_t *dst = __cpu_reg_ptr( 8, 0x00600 ); + __mpu_UNIT_t *a = __cpu_reg_ptr( 8, 0x00601 ); + __mpu_UNIT_t *b = __cpu_reg_ptr( 8, 0x00602 ); + + *a = 1; + *b = 2; + + clear_instruction( &instruction, CPU_OP_SETF ); + instruction.operand[0] = psw_operand( CPU_PSWBIT_CF ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "SETF CF before IADC" ); + + clear_instruction( &instruction, CPU_OP_IADC ); + instruction.operand[0] = reg_operand( 8, 0x00600 ); + instruction.operand[1] = reg_operand( 8, 0x00601 ); + instruction.operand[2] = reg_operand( 8, 0x00602 ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "IADC after SETF CF" ); + CHECK( *dst == 4, "IADC consumes SETF CF" ); + + clear_instruction( &instruction, CPU_OP_CLRF ); + instruction.operand[0] = psw_operand( CPU_PSWBIT_CF ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "CLRF CF before IADC" ); + clear_instruction( &instruction, CPU_OP_IADC ); + instruction.operand[0] = reg_operand( 8, 0x00600 ); + instruction.operand[1] = reg_operand( 8, 0x00601 ); + instruction.operand[2] = reg_operand( 8, 0x00602 ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "IADC after CLRF CF" ); + CHECK( *dst == 3, "IADC consumes cleared CF" ); + } + + /* SETF CF likewise provides the input borrow consumed by LibMPU ISBB. */ + { + __mpu_UNIT_t *dst = __cpu_reg_ptr( 8, 0x00610 ); + __mpu_UNIT_t *a = __cpu_reg_ptr( 8, 0x00611 ); + __mpu_UNIT_t *b = __cpu_reg_ptr( 8, 0x00612 ); + + *a = 9; + *b = 3; + + clear_instruction( &instruction, CPU_OP_SETF ); + instruction.operand[0] = psw_operand( CPU_PSWBIT_CF ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "SETF CF before ISBB" ); + + clear_instruction( &instruction, CPU_OP_ISBB ); + instruction.operand[0] = reg_operand( 8, 0x00610 ); + instruction.operand[1] = reg_operand( 8, 0x00611 ); + instruction.operand[2] = reg_operand( 8, 0x00612 ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "ISBB after SETF CF" ); + CHECK( *dst == 5, "ISBB consumes SETF CF" ); + } + + check_assembler_contract(); + + __cpu_free_context(); + + if( failures ) + { + fprintf( stderr, "LibMCPU PSW write regression: %d failure(s)\n", failures ); + return( 1 ); + } + + puts( "LibMCPU PSW write regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-readelf-runner.sh b/tests/tst-readelf-runner.sh new file mode 100755 index 0000000..cf14a1d --- /dev/null +++ b/tests/tst-readelf-runner.sh @@ -0,0 +1,69 @@ +#!/bin/sh +set -eu + +: "${PACKAGE_VERSION:?}" + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_READELF:?}" "${TEST_SRCDIR:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-readelf-$$ +mkdir -p "$TMP" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +EXAMPLES=`cd "$TEST_SRCDIR/fixtures/shared-libs" && pwd` + +"$CPU_AS" -o "$TMP/main.o" "$EXAMPLES/dynamic-main.s" +"$CPU_AS" -o "$TMP/liba.o" "$EXAMPLES/dynamic-liba.s" + +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -o "$TMP/main" "$TMP/main.o" +"$CPU_LD" --shared --soname=liba.so.1 -o "$TMP/liba.so" "$TMP/liba.o" + +"$CPU_READELF" -h -S -s -r "$TMP/main.o" > "$TMP/rel.txt" +grep -q 'REL (Relocatable file)' "$TMP/rel.txt" +grep -q "Symbol table '.symtab'" "$TMP/rel.txt" +grep -q "Relocation section '.rela.text'" "$TMP/rel.txt" +grep -q 'R_MCPU_CODE64' "$TMP/rel.txt" + +"$CPU_READELF" -a "$TMP/main" > "$TMP/exec.txt" +grep -q 'ABI Version: 3' "$TMP/exec.txt" +grep -q 'EXEC (Executable file)' "$TMP/exec.txt" +grep -q 'Program Headers:' "$TMP/exec.txt" +grep -q 'MCPU_STACK' "$TMP/exec.txt" +grep -q "Symbol table '.dynsym'" "$TMP/exec.txt" +grep -q 'MCPU target attributes:' "$TMP/exec.txt" +grep -q 'MCPU lifecycle attributes:' "$TMP/exec.txt" +grep -q "Dynamic section '.dynamic'" "$TMP/exec.txt" +grep -q 'NULL' "$TMP/exec.txt" + +"$CPU_READELF" -hSlrsdA "$TMP/liba.so" > "$TMP/dyn.txt" +grep -q 'DYN (Shared object file)' "$TMP/dyn.txt" +grep -q "Symbol table '.dynsym'" "$TMP/dyn.txt" +grep -q 'foo' "$TMP/dyn.txt" +grep -q 'MCPU target attributes:' "$TMP/dyn.txt" +grep -q 'ABI Version: 3' "$TMP/dyn.txt" +grep -q "Dynamic section '.dynamic'" "$TMP/dyn.txt" +grep -q 'SONAME' "$TMP/dyn.txt" +grep -q 'Library soname: \[liba.so.1\]' "$TMP/dyn.txt" + +# Section Headers are intentionally wide: one section occupies exactly one +# physical output line rather than a wrapped two-line record. +"$CPU_READELF" -S "$TMP/main" > "$TMP/sections.txt" +grep -q '^ \[Nr\] Name.*Size.*EntSize.*Flags Link Info Align$' "$TMP/sections.txt" +section_count=`grep -c '^ \[[ 0-9][ 0-9]\] ' "$TMP/sections.txt"` +test "$section_count" -gt 0 +if grep -q '^ [0-9a-f][0-9a-f]* ' "$TMP/sections.txt"; then + echo "mcpu-readelf unexpectedly wrapped a section header" >&2 + exit 1 +fi + +"$CPU_READELF" --version | grep -Fq "$PACKAGE_VERSION" +"$CPU_READELF" --help | grep -q -- '--arch-specific' +"$CPU_READELF" --help | grep -q -- '--dynamic' + +printf 'not MELF\n' > "$TMP/bad" +if "$CPU_READELF" -h "$TMP/bad" >/dev/null 2>&1; then + echo "mcpu-readelf unexpectedly accepted a non-MELF file" >&2 + exit 1 +fi + +exit 0 diff --git a/tests/tst-registers.c b/tests/tst-registers.c new file mode 100644 index 0000000..c8b1c99 --- /dev/null +++ b/tests/tst-registers.c @@ -0,0 +1,128 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> + +#include <libmcpu.h> + + +static int failures = 0; + +#define CHECK(cond,msg) \ + do { \ + if( !(cond) ) \ + { \ + fprintf( stderr, "FAIL: %s\n", (msg) ); \ + ++failures; \ + } \ + } while( 0 ) + + +struct width_test +{ + unsigned int bits; + __mpu_size_t bytes; +}; + + +int +main( void ) +{ + static const struct width_test widths[] = + { + { 8, 1 }, + { 16, 2 }, + { 32, 4 }, + { 64, 8 }, + { 128, 16 }, + { 256, 32 }, + { 512, 64 }, + { 1024, 128 }, + { 2048, 256 }, + { 4096, 512 }, + { 8192, 1024 }, + { 16384, 2048 }, + { 32768, 4096 }, + { 65536, 8192 } + }; + + __mpu_size_t i; + + __cpu_init(); + + CHECK( CPU_REGISTERS_MEM_SIZE_IN_UNITS == 524288, + "512 KiB register file size" ); + CHECK( CPU_REGISTER_ADDRESS_MAX == 0x0007ffffU, + "register address maximum" ); + CHECK( CPU_REGISTER_MIN_BITS == 8, + "minimum register width" ); + CHECK( CPU_REGISTER_MAX_BITS == 65536, + "maximum register width" ); + + for( i = 0; i < sizeof( widths ) / sizeof( widths[0] ); ++i ) + { + __mpu_uint32_t last; + + CHECK( __cpu_reg_size( widths[i].bits ) == widths[i].bytes, + "register width size" ); + + CHECK( __cpu_reg_valid( widths[i].bits, 0 ) == 1, + "register at address zero" ); + + CHECK( __cpu_reg_ptr( widths[i].bits, 0 ) == __cpu_registers, + "register pointer at address zero" ); + + last = (__mpu_uint32_t)(CPU_REGISTERS_MEM_SIZE_IN_UNITS - widths[i].bytes); + + CHECK( __cpu_reg_valid( widths[i].bits, last ) == 1, + "last naturally aligned register" ); + + CHECK( __cpu_reg_ptr( widths[i].bits, last ) == &__cpu_registers[last], + "last register pointer" ); + + if( widths[i].bytes > 1 ) + { + CHECK( __cpu_reg_valid( widths[i].bits, last + 1 ) == 0, + "natural alignment check" ); + CHECK( __cpu_reg_ptr( widths[i].bits, last + 1 ) == 0, + "unaligned register pointer" ); + } + } + + CHECK( __cpu_reg_size( 0 ) == 0, "zero register width" ); + CHECK( __cpu_reg_size( 7 ) == 0, "unsupported 7-bit register width" ); + CHECK( __cpu_reg_size( 24 ) == 0, "unsupported 24-bit register width" ); + CHECK( __cpu_reg_size( 131072 ) == 0, "register width above 65536" ); + + CHECK( __cpu_reg_valid( 8, CPU_REGISTERS_MEM_SIZE_IN_UNITS - 1 ) == 1, + "last byte is a valid r8" ); + CHECK( __cpu_reg_valid( 8, CPU_REGISTERS_MEM_SIZE_IN_UNITS ) == 0, + "address one byte beyond register file" ); + CHECK( __cpu_reg_ptr( 8, CPU_REGISTERS_MEM_SIZE_IN_UNITS ) == 0, + "pointer beyond register file" ); + + CHECK( __cpu_reg_valid( 32, 12 ) == 1, + "r32 naturally aligned address" ); + CHECK( __cpu_reg_valid( 32, 13 ) == 0, + "r32 unaligned address" ); + + CHECK( __cpu_reg_valid( 65536, 0x0007e000U ) == 1, + "last r65536 region" ); + CHECK( __cpu_reg_valid( 65536, 0x0007e001U ) == 0, + "unaligned r65536 region" ); + CHECK( __cpu_reg_valid( 65536, 0x00080000U ) == 0, + "r65536 beyond register file" ); + + if( failures != 0 ) + { + fprintf( stderr, "libcpu register regression: %d failure(s)\n", failures ); + return( 1 ); + } + + printf( "libcpu 0.0.3 register width/alignment/bounds regression: PASS\n" ); + printf( "register widths: r8 ... r65536\n" ); + printf( "register address range: 0x00000 ... 0x7ffff\n" ); + + return( 0 ); +} diff --git a/tests/tst-relocatable-runner.sh b/tests/tst-relocatable-runner.sh new file mode 100755 index 0000000..20d1c35 --- /dev/null +++ b/tests/tst-relocatable-runner.sh @@ -0,0 +1,75 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" +: "${CPU_LD:?}" +: "${CPU_RUN:?}" +: "${TEST_SRCDIR:?}" + +D="${TMPDIR:-/tmp}/libcpu-reloc-$$" +trap 'rm -rf "$D"' EXIT HUP INT TERM +mkdir -p "$D" + +cat > "$D/main.s" <<'EOS' +.global _start +.global _life +.extern add_two +.extern shared +.text +_start_life: + LDI r64@0x00000, 40 + CALL add_two + STORE shared, r64@0x00000 + LOAD r64@0x00018, shared + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$D/lib.s" <<'EOS' +.global add_two +.global shared +.data +.align 32 +shared: + .integer 64, 0 +raw_bytes: + .byte 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x40, 0x00 + .byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + .byte 0x00, 0x00, 0xd4, 0x37 +.text +add_two: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +"$CPU_AS" -o "$D/main.o" "$D/main.s" >/dev/null +"$CPU_AS" "$D/lib.s" -o "$D/lib.o" >/dev/null +"$CPU_LD" --stack-size=512 --heap-size=1M --heap-limit=8G -o "$D/a" "$D/main.o" "$D/lib.o" >/dev/null +"$CPU_LD" --stack-size=1KiB --heap-size=1MiB --heap-limit=1GiB -o "$D/a-units" "$D/main.o" "$D/lib.o" >/dev/null +if "$CPU_LD" --stack-size=1KiB --heap-size=-1M --heap-limit=1GiB -o "$D/a-bad" "$D/main.o" "$D/lib.o" >/dev/null 2>&1; then + echo "mcpu-ld accepted negative heap size" >&2 + exit 1 +fi +if "$CPU_LD" --stack-size=1KiB --heap-size=1B --heap-limit=1GiB -o "$D/a-bad" "$D/main.o" "$D/lib.o" >/dev/null 2>&1; then + echo "mcpu-ld accepted unsupported B suffix" >&2 + exit 1 +fi +v=`"$CPU_RUN" --int-reg=r64@0x00018 "$D/a"` +test "x$v" = x42 + +# DATA integer output is target-representation aware. +v=`"$CPU_RUN" --int-data=64@32 "$D/a"` +test "x$v" = x42 + +# raw_bytes starts at DATA offset 40. Request beyond the end of .data: +# cpu-run must clip the dump to the 20 bytes that remain in .data. +out=`"$CPU_RUN" --raw-data=64@40 "$D/a"` +printf '%s\n' "$out" | grep -F 'DATA memory dump: (20 bytes):' >/dev/null +printf '%s\n' "$out" | grep -F ' 0000 : 02 00 01 00 01 00 40 00 00 00 00 00 00 00 00 00 | ......@.........' >/dev/null +printf '%s\n' "$out" | grep -F ' 0010 : 00 00 d4 37 ' >/dev/null + +# The linker-owned DATA NULL guard is not part of .data and must not be dumpable. +if "$CPU_RUN" --raw-data=1@16 "$D/a" >/dev/null 2>&1; then + echo 'raw DATA dump escaped the linked .data section' >&2 + exit 1 +fi diff --git a/tests/tst-relocatable.c b/tests/tst-relocatable.c new file mode 100644 index 0000000..a2df8c4 --- /dev/null +++ b/tests/tst-relocatable.c @@ -0,0 +1,326 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "mcpu-asm.h" +#include "mcpu-link.h" + +static int +sym( const cpu_asm_object *o, const char *n, size_t *idx ) +{ + size_t i; + for( i = 0; i < o->symbol_count; i++ ) if( !strcmp( o->symbols[i].name, n ) ) + { + *idx = i; + return( 0 ); + } + return( -1 ); +} + + +static int +expect_link_fail( const char *a, const char *b, const char *needle ) +{ + cpu_asm_object o[2]; + cpu_asm_image im; + char err[512]; + int ok = 0; + memset( o, 0, sizeof( o ) ); + memset( &im, 0, sizeof( im ) ); + if( cpu_assemble_object( a, &o[0], err, sizeof( err ) ) ) + { + fprintf( stderr, "negative object A did not assemble: %s\n", err ); + goto out; + } + if( cpu_assemble_object( b, &o[1], err, sizeof( err ) ) ) + { + fprintf( stderr, "negative object B did not assemble: %s\n", err ); + goto out; + } + if( !cpu_link_objects( o, 2, &im, err, sizeof( err ) ) ) + { + fprintf( stderr, "negative link unexpectedly succeeded\n" ); + cpu_asm_image_free( &im ); + goto out; + } + if( needle && !strstr( err, needle ) ) + { + fprintf( stderr, "negative link error mismatch: %s\n", err ); + goto out; + } + ok = 1; +out: + cpu_asm_object_free( &o[0] ); + cpu_asm_object_free( &o[1] ); + return( ok ? 0 : -1 ); +} + +int +main( void ) +{ + const char *main_src= + ".global _start, _life\n" + ".extern add_two\n" + ".extern shared\n" + ".text\n" + "_start_life:\n" + " LDI r64@0x00000, 40\n" + " CALL add_two\n" + " STORE shared, r64@0x00000\n" + " LOAD r64@0x00018, shared\n" + " LEA r64@0x00020, shared+0\n" + " LDI r32@0x00030, 0\n" + " EXIT r32@0x00030\n"; + const char *lib_src= + ".global add_two\n" + ".global shared\n" + ".global shared_ptr\n" + ".global fn_ptr\n" + ".data\n" + ".align 32\n" + "shared:\n" + " .integer 64, 0\n" + "shared_ptr:\n" + " .ptr shared\n" + "fn_ptr:\n" + " .cptr add_two\n" + ".text\n" + "add_two:\n" + " LDI r64@0x00008, 2\n" + " IADD r64@0x00000, r64@0x00000, r64@0x00008\n" + " RET\n"; + cpu_asm_object o[2]; + cpu_asm_image im; + char err[512]; + size_t i, ix; + __mpu_uint64_t p; + __mpu_init(); + memset( o, 0, sizeof( o ) ); + memset( &im, 0, sizeof( im ) ); + if( cpu_assemble_object( main_src, &o[0], err, sizeof( err ) ) ) + { + fprintf( stderr, "main object: %s\n", err ); + return( 1 ); + } + if( cpu_assemble_object( lib_src, &o[1], err, sizeof( err ) ) ) + { + fprintf( stderr, "lib object: %s\n", err ); + return( 1 ); + } + if( o[0].data_size != 0 ) + { + fprintf( stderr, "relocatable DATA unexpectedly contains NULL guard\n" ); + return( 1 ); + } + if( o[1].data_align != 32 ) + { + fprintf( stderr, "DATA alignment not preserved: %llu\n", (unsigned long long)o[1].data_align ); + return( 1 ); + } + if( sym( &o[0], "add_two", &ix ) || o[0].symbols[ix].section != CPU_OBJ_SEC_UNDEF ) + { + fprintf( stderr, "extern symbol missing\n" ); + return( 1 ); + } + if( sym( &o[1], "shared", &ix ) || o[1].symbols[ix].binding != CPU_OBJ_BIND_GLOBAL || o[1].symbols[ix].value != 0 ) + { + fprintf( stderr, "defined global shared bad\n" ); + return( 1 ); + } + if( o[0].reloc_count != 4 || o[1].reloc_count != 2 ) + { + fprintf( stderr, "unexpected relocation counts %zu %zu\n", o[0].reloc_count, o[1].reloc_count ); + return( 1 ); + } + if( cpu_link_objects( o, 2, &im, err, sizeof( err ) ) ) + { + fprintf( stderr, "link: %s\n", err ); + return( 1 ); + } + if( !im.has_life || im.life_entry != 0 ) + { + fprintf( stderr, "bad linked _life entry\n" ); + return( 1 ); + } + if( im.data_size != 56 ) + { + fprintf( stderr, "linked DATA size/alignment/guard mismatch: %zu\n", im.data_size ); + return( 1 ); + } + for( i = 0; i < CPU_DATA_NULL_GUARD_SIZE; i++ ) if( im.data[i] ) + { + fprintf( stderr, "DATA guard is not zero\n" ); + return( 1 ); + } + /* shared is aligned at final DATA offset 32; shared_ptr immediately follows. */ + p = __cpu_data_get_u64( im.data + 40 ); + if( __cpu_ptr_segment( p ) != CPU_PTR_SEG_DATA || __cpu_ptr_offset( p ) != 32 ) + { + fprintf( stderr, ".ptr relocation bad: 0x%llx\n", (unsigned long long)p ); + return( 1 ); + } + p = __cpu_data_get_u64( im.data + 48 ); + if( __cpu_ptr_segment( p ) != CPU_PTR_SEG_PROGRAM ) + { + fprintf( stderr, ".cptr segment bad\n" ); + return( 1 ); + } + cpu_asm_image_free( &im ); + cpu_asm_object_free( &o[0] ); + cpu_asm_object_free( &o[1] ); + + /* _end is an executable linker-defined HEAP pointer at HEAP:16. */ + { + cpu_asm_object x = + { + 0 + }; + cpu_asm_image xi; + const char *src= + ".global _start, _life\n" + ".extern _end\n" + ".data\n" + "end_ptr:\n" + " .ptr _end\n" + ".text\n" + "_start_life:\n" + " LEA r64@0x00000, _end\n" + " LDI r32@0x00010, 0\n" + " EXIT r32@0x00010\n"; + memset( &xi, 0, sizeof( xi ) ); + if( cpu_assemble_object( src, &x, err, sizeof( err ) ) ) + { + fprintf( stderr, "_end object: %s\n", err ); + return( 1 ); + } + if( cpu_link_objects( &x, 1, &xi, err, sizeof( err ) ) ) + { + fprintf( stderr, "_end link: %s\n", err ); + return( 1 ); + } + if( xi.data_size < 24 ) + { + fprintf( stderr, "_end DATA image too small\n" ); + return( 1 ); + } + p = __cpu_data_get_u64( xi.data + CPU_DATA_NULL_GUARD_SIZE ); + if( __cpu_ptr_segment( p ) != CPU_PTR_SEG_HEAP || __cpu_ptr_offset( p ) != CPU_HEAP_BEGIN ) + { + fprintf( stderr, "_end .ptr relocation bad: 0x%llx\n", (unsigned long long)p ); + return( 1 ); + } + p = __cpu_code_get_u64( xi.code + 5 ); + if( __cpu_ptr_segment( p ) != CPU_PTR_SEG_HEAP || __cpu_ptr_offset( p ) != CPU_HEAP_BEGIN ) + { + fprintf( stderr, "_end LEA relocation bad: 0x%llx\n", (unsigned long long)p ); + return( 1 ); + } + cpu_asm_image_free( &xi ); + + memset( &xi, 0, sizeof( xi ) ); + if( cpu_link_objects_mode( &x, 1, &xi, CPU_LINK_DYNAMIC_EXEC, err, sizeof( err ) ) ) + { + fprintf( stderr, "_end dynamic link: %s\n", err ); + return( 1 ); + } + if( xi.data_size < 24 ) + { + fprintf( stderr, "_end dynamic DATA image too small\n" ); + return( 1 ); + } + p = __cpu_data_get_u64( xi.data + CPU_DATA_NULL_GUARD_SIZE ); + if( __cpu_ptr_segment( p ) != CPU_PTR_SEG_HEAP || __cpu_ptr_offset( p ) != CPU_HEAP_BEGIN ) + { + fprintf( stderr, "_end dynamic .ptr relocation bad: 0x%llx\n", (unsigned long long)p ); + return( 1 ); + } + p = __cpu_code_get_u64( xi.code + 5 ); + if( __cpu_ptr_segment( p ) != CPU_PTR_SEG_HEAP || __cpu_ptr_offset( p ) != CPU_HEAP_BEGIN ) + { + fprintf( stderr, "_end dynamic LEA relocation bad: 0x%llx\n", (unsigned long long)p ); + return( 1 ); + } + if( xi.dyn_reloc_count != 0 ) + { + fprintf( stderr, "_end must not remain a dynamic relocation\n" ); + return( 1 ); + } + cpu_asm_image_free( &xi ); + cpu_asm_object_free( &x ); + } + + /* A hexadecimal absolute expression must not mistake the `x` in 0x20 for + a relocatable symbol. */ + { + cpu_asm_object x = + { + 0 + }; + const char *src= + ".global _start, _life\n" + ".text\n" + "_start_life:\n" + " LEA r64@0x00000, 0x20\n" + " LDI r32@0x00010, 0\n" + " EXIT r32@0x00010\n"; + if( cpu_assemble_object( src, &x, err, sizeof( err ) ) ) + { + fprintf( stderr, "hex absolute expression: %s\n", err ); + return( 1 ); + } + if( x.code_size < 13 ) + { + fprintf( stderr, "hex absolute expression produced short code\n" ); + return( 1 ); + } + p = __cpu_code_get_u64( x.code + 5 ); + if( __cpu_ptr_segment( p ) != CPU_PTR_SEG_DATA || __cpu_ptr_offset( p ) != 0x20 ) + { + fprintf( stderr, "hex absolute LEA bad: 0x%llx\n", (unsigned long long)p ); + return( 1 ); + } + cpu_asm_object_free( &x ); + } + + /* .equ and relocatable global/extern symbols occupy different namespaces + and may not reuse one name. */ + { + cpu_asm_object x = + { + 0 + }; + const char *src= + ".equ N, 4*512\n" + ".global N\n" + ".global _start, _life\n" + ".text\n_start_life:\n LDI r32@0x00000, 0\n EXIT r32@0x00000\n"; + if( !cpu_assemble_object( src, &x, err, sizeof( err ) ) ) + { + fprintf( stderr, ".equ/global conflict unexpectedly accepted\n" ); + cpu_asm_object_free( &x ); + return( 1 ); + } + if( !strstr( err, "conflicts with .equ" ) ) + { + fprintf( stderr, ".equ/global conflict error mismatch: %s\n", err ); + return( 1 ); + } + } + + if( expect_link_fail( + ".global _start, _life\n.extern missing\n.text\n_start_life:\n CALL missing\n LDI r32@0x10,0\n EXIT r32@0x10\n", + ".data\nlocal:\n .integer 64,0\n", + "undefined symbol: missing")) return( 1 ); + + if( expect_link_fail( + ".global _start, _life, foo\n.data\nfoo:\n .integer 64,1\n.text\n_start_life:\n LDI r32@0x10,0\n EXIT r32@0x10\n", + ".global foo\n.data\nfoo:\n .integer 64,2\n", + "multiple definition of global symbol: foo")) return( 1 ); + + if( expect_link_fail( + ".global _start, _life\n.extern data_sym\n.data\np:\n .cptr data_sym\n.text\n_start_life:\n LDI r32@0x10,0\n EXIT r32@0x10\n", + ".global data_sym\n.data\ndata_sym:\n .integer 64,0\n", + ".cptr target is not PROGRAM: data_sym")) return( 1 ); + + __mpu_free_context(); + return( 0 ); +} diff --git a/tests/tst-run-memory-options.sh b/tests/tst-run-memory-options.sh new file mode 100755 index 0000000..ab176ce --- /dev/null +++ b/tests/tst-run-memory-options.sh @@ -0,0 +1,121 @@ +#!/bin/sh + +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" + +base="${TMPDIR:-/tmp}/libmcpu-run-memory-$$" +trap 'rm -f "$base.s" "$base.o" "$base.melf"' EXIT HUP INT TERM + +cat > "$base.s" <<'EOS' +.data + .integer 64, 11 + .real 64, 1.5 + +.control + .integer 64, 21 + .real 64, 2.5 + +.input + .integer 64, 31 + .real 64, 3.5 + +.output + .integer 64, 41 + .real 64, 4.5 + +.trace + .integer 64, 51 + .real 64, 5.5 + +.text +_start_life: + HALT +EOS + +"$CPU_AS" -o "$base.o" "$base.s" >/dev/null +"$CPU_LD" -o "$base.melf" --stack-size=1K --heap-size=64K --heap-limit=1M \ + "$base.o" >/dev/null + +check_integer() +{ + option="$1" + expected="$2" + out=`"$CPU_RUN" "$option" "$base.melf"` + if test "$out" != "$expected"; then + echo "unexpected $option result: $out" >&2 + exit 1 + fi +} + +check_real() +{ + option="$1" + expected="$2" + out=`"$CPU_RUN" --digits=20 "$option" "$base.melf"` + case "$out" in + "$expected"*) ;; + *) echo "unexpected $option result: $out" >&2; exit 1 ;; + esac +} + +check_raw() +{ + option="$1" + memory="$2" + bytes="$3" + out=`"$CPU_RUN" "$option" "$base.melf"` + case "$out" in + "$memory memory dump: ($bytes bytes):"*) ;; + *) echo "unexpected $option dump: $out" >&2; exit 1 ;; + esac +} + +check_integer --int-data=64@16 11 +check_real --real-data=64@24 1.500000000000000E+0 +check_integer --int-control=64@0 21 +check_real --real-control=64@8 2.500000000000000E+0 +check_integer --int-heap=64@16 0 +check_real --real-heap=64@16 0.000000000000000E+0 +check_integer --int-input=64@0 31 +check_real --real-input=64@8 3.500000000000000E+0 +check_integer --int-output=64@0 41 +check_real --real-output=64@8 4.500000000000000E+0 +check_integer --int-trace=64@0 51 +check_real --real-trace=64@8 5.500000000000000E+0 + +# Exercise every accepted binary suffix while forcing clipping to 16 bytes. +check_raw --raw-data=1K@16 DATA 16 +check_raw --raw-control=1M@0 CONTROL 16 +check_raw --raw-input=1G@0 INPUT 16 +check_raw --raw-output=1KiB@0 OUTPUT 16 +check_raw --raw-trace=1MiB@0 TRACE 16 +check_raw --raw-heap=1GiB@65520 HEAP 16 + +# Segment/object bounds must reject an address at or beyond the end. +if "$CPU_RUN" --raw-control=1@16 "$base.melf" >/dev/null 2>&1; then + echo "--raw-control accepted address at CONTROL end" >&2 + exit 1 +fi +if "$CPU_RUN" --int-heap=64@65530 "$base.melf" >/dev/null 2>&1; then + echo "--int-heap accepted object crossing HEAP end" >&2 + exit 1 +fi +if "$CPU_RUN" --raw-data=1@32 "$base.melf" >/dev/null 2>&1; then + echo "--raw-data accepted address at .data end" >&2 + exit 1 +fi +if "$CPU_RUN" --raw-data=18446744073709551615GiB@16 "$base.melf" >/dev/null 2>&1; then + echo "--raw-data accepted overflowing byte count" >&2 + exit 1 +fi + +# The descriptions in --help intentionally begin in one vertical column. +help=`"$CPU_RUN" --help 2>&1` +for option in int-control real-control raw-control int-heap real-heap raw-heap \ + int-input real-input raw-input int-output real-output raw-output \ + int-trace real-trace raw-trace; do + echo "$help" | grep -- "--$option=" >/dev/null || exit 1 +done + +exit 0 diff --git a/tests/tst-runner.sh b/tests/tst-runner.sh new file mode 100755 index 0000000..54343b1 --- /dev/null +++ b/tests/tst-runner.sh @@ -0,0 +1,17 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +base="${TMPDIR:-/tmp}/libcpu-radd-pi-$$" +trap 'rm -f "$base.o" "$base.melf"' EXIT HUP INT TERM +"$CPU_AS" -o "$base.o" "$TEST_SRCDIR/fixtures/radd-pi.s" >/dev/null +"$CPU_LD" --stack-size=512 -o "$base.melf" "$base.o" >/dev/null +out=`"$CPU_RUN" --digits=60 --real-reg=r4096@0x00400 "$base.melf"` +case "$out" in + 5.14159265358979323846264338327950288419716939937510582097*) ;; + *) echo "unexpected register result: $out" >&2; exit 1 ;; +esac +out=`"$CPU_RUN" --digits=60 --real-data=4096@16 "$base.melf"` +case "$out" in + 5.14159265358979323846264338327950288419716939937510582097*) ;; + *) echo "unexpected DATA result: $out" >&2; exit 1 ;; +esac diff --git a/tests/tst-stack-runner.sh b/tests/tst-stack-runner.sh new file mode 100755 index 0000000..2bba333 --- /dev/null +++ b/tests/tst-stack-runner.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -eu +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" +base="${TMPDIR:-/tmp}/libcpu-stack-$$" +trap 'rm -f "$base.s" "$base.o" "$base.melf"' EXIT HUP INT TERM +"$CPU_AS" -o "$base.o" "$TEST_SRCDIR/fixtures/stack.s" >/dev/null +"$CPU_LD" --stack-size=128 -o "$base.melf" "$base.o" >/dev/null +out=`"$CPU_RUN" --int-reg=r64@0x00028 "$base.melf"` +test "x$out" = "x22" diff --git a/tests/tst-stack.c b/tests/tst-stack.c new file mode 100644 index 0000000..9c719ed --- /dev/null +++ b/tests/tst-stack.c @@ -0,0 +1,104 @@ +#include <stdio.h> +#include <string.h> +#include <libmcpu.h> +#include "../utils/mcpu-asm.h" + +#define CHECK(c,m) do{if(!(c)){fprintf(stderr,"FAIL: %s\n",m);return 1;}}while(0) + +static const char src[]= +".text\n" +" LDI r64@0x00000, 11\n" +" LDI r64@0x00008, 22\n" +" PUSH r64@0x00000\n" +" PUSH r64@0x00008\n" +" LDSP r64@0x00010, 8\n" +" STSP 0, r64@0x00000\n" +" POP r64@0x00018\n" +" POP r64@0x00020\n" +" ADJSP -16\n" +" STSP 0, r64@0x00008\n" +" LDSP r64@0x00028, 0\n" +" ADJSP 16\n" +" HALT\n"; + +static int +eq64( unsigned address, const char *text ) +{ + mpu_int64_t x; + iatoi( ( mpu_int * ) x, (__mpu_char8_t *)text, NB_I64 ); + return( !memcmp( __cpu_reg_ptr( 64, address ), x, NB_I64 ) ); +} + +int +main( void ) +{ + cpu_asm_image im; + cpu_instruction i; + cpu_operand *o; + char err[256]; + __mpu_uint64_t sp; + __mpu_UNIT_t before[8]; + int rc; + + __cpu_init(); + CHECK( __cpu_stack_alloc( 128 ) == 0, "allocate STACK" ); + CHECK( __cpu_stack_ptr() != 0, "STACK pointer" ); + CHECK( __cpu_stack_size() == 128, "STACK size" ); + CHECK( __cpu_sp == 128, "empty STACK starts at stack_size" ); + + CHECK( cpu_assemble_text( src, &im, err, sizeof( err ) ) == 0, err ); + CHECK( im.code_size > 0, "stack bytecode produced" ); + CHECK( __cpu_program_set( im.code, im.code_size ) == 0, "program set" ); + + rc = __cpu_run(); + CHECK( rc == CPU_EXEC_SUCCESS, "stack program run" ); + CHECK( __cpu_sp == 128, "SP restored after balanced stack operations" ); + CHECK( eq64( 16, "11" ), "LDSP reads SP+8" ); + CHECK( eq64( 24, "11" ), "STSP overwrites top before POP" ); + CHECK( eq64( 32, "11" ), "second POP value" ); + CHECK( eq64( 40, "22" ), "ADJSP frame with STSP/LDSP" ); + + /* PUSH overflow/underflow faults are atomic. */ + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_PUSH; + i.operand[0].type = CPU_OPERAND_REGISTER; + i.operand[0].value.reg.bits = 64; + i.operand[0].value.reg.address = 0; + __cpu_sp = 4; + memcpy( before, __cpu_stack_ptr(), sizeof( before ) ); + sp = __cpu_sp; + CHECK( __cpu_exec( &i ) == CPU_EXEC_STACK_FAULT, "PUSH stack overflow fault" ); + CHECK( __cpu_sp == sp, "PUSH fault preserves SP" ); + CHECK( !memcmp( before, __cpu_stack_ptr(), sizeof( before ) ), "PUSH fault preserves STACK" ); + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_POP; + o = &i.operand[0]; + o->type = CPU_OPERAND_REGISTER; + o->value.reg.bits = 64; + o->value.reg.address = 48; + memset( __cpu_reg_ptr( 64, 48 ), 0xa5, 8 ); + __cpu_sp = 128; + sp = __cpu_sp; + CHECK( __cpu_exec( &i ) == CPU_EXEC_STACK_FAULT, "POP empty stack fault" ); + CHECK( __cpu_sp == sp, "POP fault preserves SP" ); + { + __mpu_UNIT_t a5[8]; + memset( a5, 0xa5, sizeof( a5 ) ); + CHECK( !memcmp( __cpu_reg_ptr( 64, 48 ), a5, 8 ), "POP fault preserves destination" ); + } + + memset( &i, 0, sizeof( i ) ); + i.opcode = CPU_OP_ADJSP; + i.operand[0].type = CPU_OPERAND_OFF64; + i.operand[0].value.off64 = 1; + __cpu_sp = 128; + CHECK( __cpu_exec( &i ) == CPU_EXEC_STACK_FAULT, "ADJSP beyond stack end" ); + CHECK( __cpu_sp == 128, "ADJSP fault preserves SP" ); + + cpu_asm_image_free( &im ); + __cpu_free_context(); + CHECK( __cpu_sp == 0, "SP cleared by free context" ); + puts( "libcpu stack/SP regression: PASS" ); + return( 0 ); +} diff --git a/tests/tst-stage-actions-runner.sh b/tests/tst-stage-actions-runner.sh new file mode 100755 index 0000000..1deef01 --- /dev/null +++ b/tests/tst-stage-actions-runner.sh @@ -0,0 +1,111 @@ +#!/bin/sh + +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" + +base="${TMPDIR:-/tmp}/libmcpu-stage-actions-$$" +trap 'rm -f "$base"*' EXIT HUP INT TERM + +# The published LIFE example: one scalar print action, two registers, one line. +"$CPU_AS" -o "$base-life.o" "$TEST_SRCDIR/fixtures/life-print.s" >/dev/null +"$CPU_LD" -o "$base-life" --stack-size=1K "$base-life.o" >/dev/null +out=`"$CPU_RUN" --steps=3 \ + --on-step=print-int-regs:r64@0x00000,r64@0x00008 \ + -- "$base-life"` +expected='1 10 +2 20 +3 30' +test "x$out" = "x$expected" + +# AFTER_INIT also exists when the executable has no separate .init section. +out=`"$CPU_RUN" --steps=1 \ + --before-run=set-int-regs:r64@0x00000=77 \ + --after-init=print-int-regs:r64@0x00000 \ + --on-step=print-int-regs:r64@0x00000,r64@0x00008 \ + -- "$base-life"` +expected='77 +1 10' +test "x$out" = "x$expected" + +# Explicit -- must make option-looking tokens ordinary guest argv strings. +"$CPU_AS" -o "$base-argv.o" "$TEST_SRCDIR/fixtures/argc-argv.s" >/dev/null +"$CPU_LD" -o "$base-argv" --stack-size=4K "$base-argv.o" >/dev/null +out=`"$CPU_RUN" --on-step=print-int-regs:r64@0x00000,r8@0x00020,r8@0x00021 \ + -- "$base-argv" --help -x` +test "x$out" = "x3 104 120" + +cat > "$base-model.s" <<'EOS' +.data + .real 64, 0.0 + +.input + .integer 64, 0 + +.output + .integer 64, 0 + .integer 64, 0 + +.init +_start: + END + +.text +_life: + END + JMP _life +EOS + +"$CPU_AS" -o "$base-model.o" "$base-model.s" >/dev/null +"$CPU_LD" -o "$base-model" --stack-size=1K "$base-model.o" >/dev/null + +# Multiple set/print objects, stage ordering and mpu_sscanf()-based values. +out=`"$CPU_RUN" --steps=1 --digits=20 \ + --before-run=set-int-input:64@0=100 \ + --before-run=set-real-data:64@16=1.5 \ + --after-init=print-int-input:64@0 \ + --after-init=print-real-data:64@16 \ + --on-step=set-int-output:64@0=41,64@8=42 \ + --on-step=print-int-output:64@0,64@8 \ + --after-run=print-int-output:64@0,64@8 \ + -- "$base-model"` +case "$out" in + "100 +1.500000000000000"*"E+0 +41 42 +41 42") ;; + *) echo "unexpected stage-action output:" >&2; echo "$out" >&2; exit 1 ;; +esac + +# AFTER_RUN follows FINI -> END on graceful EXIT. +cat > "$base-fini.s" <<'EOS' +.text +_start_life: + LDI r32@0x00008, 0 + EXIT r32@0x00008 + +.fini +_fini: + LDI r64@0x00000, 99 + END +EOS +"$CPU_AS" -o "$base-fini.o" "$base-fini.s" >/dev/null +"$CPU_LD" -o "$base-fini" --stack-size=1K "$base-fini.o" >/dev/null +out=`"$CPU_RUN" --after-run=print-int-regs:r64@0x00000 -- "$base-fini"` +test "x$out" = "x99" + +# A raw stage action is exactly one dump; comma lists are deliberately invalid. +if "$CPU_RUN" --before-run=print-raw-data:1@16,1@17 -- "$base-model" \ + >/dev/null 2>&1; then + echo "raw stage action accepted a comma-separated list" >&2 + exit 1 +fi + +# Arbitrary raw writes are intentionally absent. +if "$CPU_RUN" --before-run=set-raw-input:1@0=00 -- "$base-model" \ + >/dev/null 2>&1; then + echo "set-raw-input unexpectedly accepted" >&2 + exit 1 +fi + +exit 0 diff --git a/tests/tst-static-archive-link-runner.sh b/tests/tst-static-archive-link-runner.sh new file mode 100755 index 0000000..596fa16 --- /dev/null +++ b/tests/tst-static-archive-link-runner.sh @@ -0,0 +1,263 @@ +#!/bin/sh +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${CPU_READELF:?}" "${CPU_AR:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-static-archive-link-$$ +mkdir -p "$TMP/lib" "$TMP/first" "$TMP/second" "$TMP/order" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +cat > "$TMP/main-entry.s" <<'EOS' +.global _life +.extern entry +.text +_start_life: + LDI r64@0x00000, 40 + CALL entry + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/helper.s" <<'EOS' +.global helper +.text +helper: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +cat > "$TMP/entry.s" <<'EOS' +.global entry +.extern helper +.text +entry: + CALL helper + RET +EOS + +cat > "$TMP/unused.s" <<'EOS' +.global unused +.extern missing_forever +.text +unused: + CALL missing_forever + RET +EOS + +"$CPU_AS" -o "$TMP/main-entry.o" "$TMP/main-entry.s" +"$CPU_AS" -o "$TMP/helper.o" "$TMP/helper.s" +"$CPU_AS" -o "$TMP/entry.o" "$TMP/entry.s" +"$CPU_AS" -o "$TMP/unused.o" "$TMP/unused.s" + +# The archive intentionally has no symbol-index member. helper.o precedes +# entry.o: entry.o is extracted first, introduces an undefined helper, and a +# repeated scan must then reach the earlier helper.o. unused.o must never be +# extracted, otherwise its missing_forever reference would break the link. +"$CPU_AR" rc "$TMP/lib/libchain.a" \ + "$TMP/helper.o" "$TMP/entry.o" "$TMP/unused.o" + +"$CPU_LD" --stack-size=4096 --no-heap -L"$TMP/lib" -lchain \ + -o "$TMP/static-chain" "$TMP/main-entry.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/static-chain"`" = 42 ] + +# ABI-v3 also falls back to a static archive when no libchain.so exists. +# Extracted members are part of the output image and therefore add no NEEDED. +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/lib" -lchain -o "$TMP/dynamic-chain" "$TMP/main-entry.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/dynamic-chain"`" = 42 ] +"$CPU_READELF" -d "$TMP/dynamic-chain" > "$TMP/dynamic-chain.dynamic" +[ "`grep -c 'NEEDED' "$TMP/dynamic-chain.dynamic" || true`" -eq 0 ] + +# ABI-v3 ET_DYN output follows the same static-archive fallback rule. The +# selected members become part of the shared object and do not add NEEDED. +cat > "$TMP/wrapper.s" <<'EOS' +.global wrapper +.extern entry +.text +wrapper: + CALL entry + RET +EOS + +"$CPU_AS" -o "$TMP/wrapper.o" "$TMP/wrapper.s" +"$CPU_LD" --shared --soname=libwrapper.so.1 \ + -L"$TMP/lib" -lchain -o "$TMP/libwrapper.so" "$TMP/wrapper.o" +"$CPU_READELF" -d "$TMP/libwrapper.so" > "$TMP/libwrapper.dynamic" +grep -q 'SONAME.*\[libwrapper.so.1\]' "$TMP/libwrapper.dynamic" +[ "`grep -c 'NEEDED' "$TMP/libwrapper.dynamic" || true`" -eq 0 ] + +# A weak undefined symbol does not cause archive extraction. Its PTR64 +# relocation therefore remains the established NULL weak result. +cat > "$TMP/weak-main.s" <<'EOS' +.global _life +.weak optional +.data +optional_ptr: + .ptr optional +.text +_start_life: + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/optional.s" <<'EOS' +.global optional +.text +optional: + RET +EOS + +"$CPU_AS" -o "$TMP/weak-main.o" "$TMP/weak-main.s" +"$CPU_AS" -o "$TMP/optional.o" "$TMP/optional.s" +"$CPU_AR" rcs "$TMP/lib/liboptional.a" "$TMP/optional.o" +"$CPU_LD" --stack-size=4096 --no-heap -L"$TMP/lib" -loptional \ + -o "$TMP/weak-main" "$TMP/weak-main.o" +[ "`"$CPU_RUN" --int-data=64@16 "$TMP/weak-main"`" = 0 ] + +# Static archive order across -l operands follows traditional one-pass library +# semantics. A later archive can satisfy references introduced by an earlier +# one, but an already-scanned archive is not implicitly revisited. +cat > "$TMP/order-main.s" <<'EOS' +.global _life +.extern a1 +.text +_start_life: + LDI r64@0x00000, 40 + CALL a1 + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/a1.s" <<'EOS' +.global a1 +.extern b1 +.text +a1: + CALL b1 + RET +EOS + +cat > "$TMP/a2.s" <<'EOS' +.global a2 +.text +a2: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +cat > "$TMP/b1.s" <<'EOS' +.global b1 +.extern a2 +.text +b1: + CALL a2 + RET +EOS + +"$CPU_AS" -o "$TMP/order-main.o" "$TMP/order-main.s" +"$CPU_AS" -o "$TMP/a1.o" "$TMP/a1.s" +"$CPU_AS" -o "$TMP/a2.o" "$TMP/a2.s" +"$CPU_AS" -o "$TMP/b1.o" "$TMP/b1.s" +"$CPU_AR" rcs "$TMP/order/liba.a" "$TMP/a1.o" "$TMP/a2.o" +"$CPU_AR" rcs "$TMP/order/libb.a" "$TMP/b1.o" + +if "$CPU_LD" --stack-size=4096 --no-heap -L"$TMP/order" \ + -la -lb -o "$TMP/order-fail" "$TMP/order-main.o" \ + >/dev/null 2>"$TMP/order-fail.err"; then + echo "previous archive was unexpectedly rescanned across -l operands" >&2 + exit 1 +fi +grep -q 'undefined symbol: a2' "$TMP/order-fail.err" + +"$CPU_LD" --stack-size=4096 --no-heap -L"$TMP/order" \ + -la -lb -la -o "$TMP/order-ok" "$TMP/order-main.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/order-ok"`" = 42 ] + +# Prepare two providers for -lpick: a static one returns 42, a shared one 43. +cat > "$TMP/pick-main.s" <<'EOS' +.global _life +.extern pick +.text +_start_life: + LDI r64@0x00000, 40 + CALL pick + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/pick-static.s" <<'EOS' +.global pick +.text +pick: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +cat > "$TMP/pick-shared.s" <<'EOS' +.global pick +.text +pick: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +"$CPU_AS" -o "$TMP/pick-main.o" "$TMP/pick-main.s" +"$CPU_AS" -o "$TMP/pick-static.o" "$TMP/pick-static.s" +"$CPU_AS" -o "$TMP/pick-shared.o" "$TMP/pick-shared.s" + +"$CPU_AR" rcs "$TMP/first/libpick.a" "$TMP/pick-static.o" +"$CPU_LD" --shared --soname=libpick.so.7 \ + -o "$TMP/second/libpick.so.7.0.0" "$TMP/pick-shared.o" +ln -s libpick.so.7.0.0 "$TMP/second/libpick.so.7" +ln -s libpick.so.7 "$TMP/second/libpick.so" + +# Search is directory-major. The .a in the first -L directory wins over a +# .so in a later directory. +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/first" -L"$TMP/second" -lpick \ + -o "$TMP/pick-first-static" "$TMP/pick-main.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/pick-first-static"`" = 42 ] +"$CPU_READELF" -d "$TMP/pick-first-static" > "$TMP/pick-first-static.dynamic" +[ "`grep -c 'NEEDED' "$TMP/pick-first-static.dynamic" || true`" -eq 0 ] + +# Reversing -L order selects the shared object and records its SONAME. +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/second" -L"$TMP/first" -lpick \ + -o "$TMP/pick-first-shared" "$TMP/pick-main.o" +"$CPU_READELF" -d "$TMP/pick-first-shared" > "$TMP/pick-first-shared.dynamic" +grep -q 'NEEDED.*\[libpick.so.7\]' "$TMP/pick-first-shared.dynamic" +[ "`"$CPU_RUN" --library-path="$TMP/second" \ + --int-reg=r64@0x00018 "$TMP/pick-first-shared"`" = 43 ] + +# Within one directory ABI-v3 prefers .so over .a. +cp "$TMP/first/libpick.a" "$TMP/second/libpick.a" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/second" -lpick -o "$TMP/pick-same-dir" "$TMP/pick-main.o" +"$CPU_READELF" -d "$TMP/pick-same-dir" | grep -q 'NEEDED.*\[libpick.so.7\]' + +# ABI-v1 never selects .so for -l. It uses the archive even when both exist. +"$CPU_LD" --stack-size=4096 --no-heap \ + -L"$TMP/second" -lpick -o "$TMP/pick-abi1" "$TMP/pick-main.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/pick-abi1"`" = 42 ] + +# With only a shared provider available, ABI-v1 -l must fail rather than +# silently introducing a dynamic dependency into a static executable. +rm -f "$TMP/second/libpick.a" +if "$CPU_LD" --stack-size=4096 --no-heap \ + -L"$TMP/second" -lpick -o "$TMP/pick-abi1-bad" "$TMP/pick-main.o" \ + >/dev/null 2>&1; then + echo "ABI-v1 unexpectedly selected a shared library" >&2 + exit 1 +fi + +"$CPU_LD" --help 2>&1 | grep -q 'ABI-v1 and -r search .a' +"$CPU_LD" --help 2>&1 | grep -q 'ABI-v3 searches .so then .a' + +exit 0 diff --git a/tests/tst-static-option-runner.sh b/tests/tst-static-option-runner.sh new file mode 100755 index 0000000..1515a7f --- /dev/null +++ b/tests/tst-static-option-runner.sh @@ -0,0 +1,124 @@ +#!/bin/sh +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${CPU_READELF:?}" "${CPU_AR:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-static-option-$$ +mkdir -p "$TMP/both" "$TMP/soonly" "$TMP/aonly" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +cat > "$TMP/main.s" <<'EOS' +.global _life +.extern pick +.text +_start_life: + LDI r64@0x00000, 40 + CALL pick + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/wrapper.s" <<'EOS' +.global wrapper +.extern pick +.text +wrapper: + CALL pick + RET +EOS + +cat > "$TMP/pick-static.s" <<'EOS' +.global pick +.text +pick: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +cat > "$TMP/pick-shared.s" <<'EOS' +.global pick +.text +pick: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +"$CPU_AS" -o "$TMP/main.o" "$TMP/main.s" +"$CPU_AS" -o "$TMP/wrapper.o" "$TMP/wrapper.s" +"$CPU_AS" -o "$TMP/pick-static.o" "$TMP/pick-static.s" +"$CPU_AS" -o "$TMP/pick-shared.o" "$TMP/pick-shared.s" + +"$CPU_AR" rcs "$TMP/both/libpick.a" "$TMP/pick-static.o" +cp "$TMP/both/libpick.a" "$TMP/aonly/libpick.a" +"$CPU_LD" --shared --soname=libpick.so.1 \ + -o "$TMP/both/libpick.so.1.0.0" "$TMP/pick-shared.o" +ln -s libpick.so.1.0.0 "$TMP/both/libpick.so.1" +ln -s libpick.so.1 "$TMP/both/libpick.so" +cp "$TMP/both/libpick.so.1.0.0" "$TMP/soonly/libpick.so.1.0.0" +ln -s libpick.so.1.0.0 "$TMP/soonly/libpick.so.1" +ln -s libpick.so.1 "$TMP/soonly/libpick.so" + +# Normal ABI-v3 lookup prefers .so in a directory containing both providers. +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/both" -lpick -o "$TMP/dynamic-shared" "$TMP/main.o" +"$CPU_READELF" -d "$TMP/dynamic-shared" > "$TMP/dynamic-shared.dynamic" +grep -q 'NEEDED.*\[libpick.so.1\]' "$TMP/dynamic-shared.dynamic" +[ "`"$CPU_RUN" --library-path="$TMP/both" \ + --int-reg=r64@0x00018 "$TMP/dynamic-shared"`" = 43 ] + +# --static changes only -l resolution. The output is still ABI-v3 ET_EXEC, +# but libpick.a is selected and no DT_NEEDED is emitted. +"$CPU_LD" --dynamic --static --stack-size=4096 --no-heap \ + -L"$TMP/both" -lpick -o "$TMP/dynamic-static" "$TMP/main.o" +"$CPU_READELF" -h "$TMP/dynamic-static" > "$TMP/dynamic-static.header" +"$CPU_READELF" -d "$TMP/dynamic-static" > "$TMP/dynamic-static.dynamic" +grep -q 'ABI Version:.*3' "$TMP/dynamic-static.header" +[ "`grep -c 'NEEDED' "$TMP/dynamic-static.dynamic" || true`" -eq 0 ] +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/dynamic-static"`" = 42 ] + +# --shared --static remains ABI-v3 ET_DYN with its own SONAME; the selected +# archive member is incorporated into the image and adds no NEEDED entry. +"$CPU_LD" --shared --static --soname=libwrapper.so.1 \ + -L"$TMP/both" -lpick -o "$TMP/libwrapper.so" "$TMP/wrapper.o" +"$CPU_READELF" -d "$TMP/libwrapper.so" > "$TMP/libwrapper.dynamic" +grep -q 'SONAME.*\[libwrapper.so.1\]' "$TMP/libwrapper.dynamic" +[ "`grep -c 'NEEDED' "$TMP/libwrapper.dynamic" || true`" -eq 0 ] + +# -r already accepts static archives only; --static is therefore a harmless +# explicit statement of the same library-selection policy. +"$CPU_LD" -r --static -L"$TMP/both" -lpick \ + -o "$TMP/partial.o" "$TMP/wrapper.o" +"$CPU_READELF" -s "$TMP/partial.o" > "$TMP/partial.sym" +grep -q '[[:space:]]pick$' "$TMP/partial.sym" + +# ABI-v1 is already fully static; accepting --static must preserve that path. +"$CPU_LD" --static --stack-size=4096 --no-heap \ + -L"$TMP/both" -lpick -o "$TMP/abi1-static" "$TMP/main.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/abi1-static"`" = 42 ] + +# Forced-static lookup skips a .so-only earlier directory and continues until +# it finds libpick.a in a later directory. +"$CPU_LD" --dynamic --static --stack-size=4096 --no-heap \ + -L"$TMP/soonly" -L"$TMP/aonly" -lpick \ + -o "$TMP/later-archive" "$TMP/main.o" +[ "`"$CPU_RUN" --int-reg=r64@0x00018 "$TMP/later-archive"`" = 42 ] +"$CPU_READELF" -d "$TMP/later-archive" > "$TMP/later-archive.dynamic" +[ "`grep -c 'NEEDED' "$TMP/later-archive.dynamic" || true`" -eq 0 ] + +# With only a shared provider available, --static must fail and must not fall +# back to libpick.so. +if "$CPU_LD" --dynamic --static --stack-size=4096 --no-heap \ + -L"$TMP/soonly" -lpick -o "$TMP/missing-static" "$TMP/main.o" \ + >/dev/null 2>"$TMP/missing-static.err"; then + echo "--static unexpectedly selected a shared library" >&2 + exit 1 +fi +grep -q 'libpick.a' "$TMP/missing-static.err" + +"$CPU_LD" --help 2>&1 | grep -q -- '--static' +"$CPU_LD" --help 2>&1 | grep -q 'force every -l<name> lookup to use lib<name>.a' + +exit 0 diff --git a/tests/tst-strip-fixture.c b/tests/tst-strip-fixture.c new file mode 100644 index 0000000..1d1fae3 --- /dev/null +++ b/tests/tst-strip-fixture.c @@ -0,0 +1,266 @@ +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <libmcpu.h> +#include <mcpu-elf.h> + +#include "mcpu-melf.h" + + +typedef struct fixture_shdr +{ + __mpu_uint32_t name; + __mpu_uint32_t type; + __mpu_uint64_t flags; + __mpu_uint64_t addr; + __mpu_uint64_t offset; + __mpu_uint64_t size; + __mpu_uint32_t link; + __mpu_uint32_t info; + __mpu_uint64_t align; + __mpu_uint64_t entsize; +} fixture_shdr; + + +static void +get_shdr( const unsigned char *p, int data, fixture_shdr *sh ) +{ + memset( sh, 0, sizeof( *sh ) ); + sh->name = cpu_melf_get32( p + MELF64_SHDR_SH_NAME_OFF, data ); + sh->type = cpu_melf_get32( p + MELF64_SHDR_SH_TYPE_OFF, data ); + sh->flags = cpu_melf_get64( p + MELF64_SHDR_SH_FLAGS_OFF, data ); + sh->addr = cpu_melf_get64( p + MELF64_SHDR_SH_ADDR_OFF, data ); + sh->offset = cpu_melf_get64( p + MELF64_SHDR_SH_OFFSET_OFF, data ); + sh->size = cpu_melf_get64( p + MELF64_SHDR_SH_SIZE_OFF, data ); + sh->link = cpu_melf_get32( p + MELF64_SHDR_SH_LINK_OFF, data ); + sh->info = cpu_melf_get32( p + MELF64_SHDR_SH_INFO_OFF, data ); + sh->align = cpu_melf_get64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, data ); + sh->entsize = cpu_melf_get64( p + MELF64_SHDR_SH_ENTSIZE_OFF, data ); + +} /* End of get_shdr() */ + + +static void +put_shdr( unsigned char *p, int data, const fixture_shdr *sh ) +{ + memset( p, 0, MELF64_SHDR_SIZE ); + cpu_melf_put32( p + MELF64_SHDR_SH_NAME_OFF, sh->name, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_TYPE_OFF, sh->type, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_FLAGS_OFF, sh->flags, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ADDR_OFF, sh->addr, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_OFFSET_OFF, sh->offset, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_SIZE_OFF, sh->size, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_LINK_OFF, sh->link, data ); + cpu_melf_put32( p + MELF64_SHDR_SH_INFO_OFF, sh->info, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ADDRALIGN_OFF, sh->align, data ); + cpu_melf_put64( p + MELF64_SHDR_SH_ENTSIZE_OFF, sh->entsize, data ); + +} /* End of put_shdr() */ + + +static const char * +name_at( const unsigned char *table, size_t size, __mpu_uint32_t offset ) +{ + const unsigned char *p; + + if( offset >= size ) + return( NULL ); + p = table + offset; + if( !memchr( p, 0, size - offset ) ) + return( NULL ); + return( (const char *)p ); + +} /* End of name_at() */ + + +int +main( int argc, char **argv ) +{ + unsigned char *file = NULL; + unsigned char *out = NULL; + fixture_shdr *sh = NULL; + const unsigned char *old_shstr; + size_t file_size = 0; + size_t old_shstr_size; + size_t new_shstr_size; + size_t i; + size_t text_index = 0; + int data; + int overflow = 0; + __mpu_uint64_t old_shoff; + __mpu_uint64_t cur; + __mpu_uint64_t new_shoff; + __mpu_uint64_t total; + __mpu_uint16_t shnum; + __mpu_uint16_t shentsize; + __mpu_uint16_t shstrndx; + fixture_shdr strtab; + fixture_shdr symtab; + fixture_shdr shstr; + unsigned char symbols[2 * MELF64_SYM_SIZE]; + const char fixture_strtab[] = "\0fixture_symbol\0"; + const char strtab_name[] = ".strtab"; + const char symtab_name[] = ".symtab"; + char err[256]; + int rc = EXIT_FAILURE; + + if( argc != 3 ) + { + fprintf( stderr, "usage: %s INPUT-ET_EXEC-OR-ET_DYN OUTPUT\n", argv[0] ); + return( EXIT_FAILURE ); + } + + if( cpu_melf_readall( argv[1], &file, &file_size, err, sizeof( err ) ) ) + { + fprintf( stderr, "%s\n", err ); + return( EXIT_FAILURE ); + } + + if( file_size < MELF64_EHDR_SIZE || + file[MELF_EI_MAG0] != MELFMAG0 || file[MELF_EI_MAG1] != MELFMAG1 || + file[MELF_EI_MAG2] != MELFMAG2 || file[MELF_EI_MAG3] != MELFMAG3 || + file[MELF_EI_CLASS] != MELF_CLASS_64 ) + goto out; + + data = file[MELF_EI_DATA]; + if( data != MELF_DATA_LSB && data != MELF_DATA_MSB ) + goto out; + { + __mpu_uint16_t type = cpu_melf_get16( + file + MELF64_EHDR_E_TYPE_OFF, data ); + + if( type != MELF_ET_EXEC && type != MELF_ET_DYN ) + goto out; + } + + old_shoff = cpu_melf_get64( file + MELF64_EHDR_E_SHOFF_OFF, data ); + shnum = cpu_melf_get16( file + MELF64_EHDR_E_SHNUM_OFF, data ); + shentsize = cpu_melf_get16( file + MELF64_EHDR_E_SHENTSIZE_OFF, data ); + shstrndx = cpu_melf_get16( file + MELF64_EHDR_E_SHSTRNDX_OFF, data ); + if( !shnum || shnum > UINT16_MAX - 2 || shentsize != MELF64_SHDR_SIZE || + shstrndx >= shnum || + !cpu_melf_span( file_size, old_shoff, + (__mpu_uint64_t)shnum * MELF64_SHDR_SIZE ) ) + goto out; + + sh = (fixture_shdr *)calloc( (size_t)shnum + 2, sizeof( *sh ) ); + if( !sh ) + goto out; + for( i = 0; i < shnum; ++i ) + get_shdr( file + (size_t)old_shoff + i * MELF64_SHDR_SIZE, + data, &sh[i] ); + + if( sh[shstrndx].type != MELF_SHT_STRTAB || + !cpu_melf_span( file_size, sh[shstrndx].offset, + sh[shstrndx].size ) || + sh[shstrndx].size > SIZE_MAX ) + goto out; + + old_shstr = file + (size_t)sh[shstrndx].offset; + old_shstr_size = (size_t)sh[shstrndx].size; + for( i = 1; i < shnum; ++i ) + { + const char *name = name_at( old_shstr, old_shstr_size, sh[i].name ); + + if( !name ) + goto out; + if( !strcmp( name, MELF_SEC_TEXT ) ) + text_index = i; + } + if( !text_index ) + goto out; + + cur = old_shoff; + memset( &strtab, 0, sizeof( strtab ) ); + strtab.type = MELF_SHT_STRTAB; + strtab.offset = cur; + strtab.size = sizeof( fixture_strtab ); + strtab.align = 1; + cur += strtab.size; + + cur = cpu_melf_align_up( cur, 8, &overflow ); + if( overflow ) + goto out; + memset( &symtab, 0, sizeof( symtab ) ); + symtab.type = MELF_SHT_SYMTAB; + symtab.offset = cur; + symtab.size = sizeof( symbols ); + symtab.link = shnum; + symtab.info = 1; + symtab.align = 8; + symtab.entsize = MELF64_SYM_SIZE; + cur += symtab.size; + + if( old_shstr_size > SIZE_MAX - sizeof( strtab_name ) || + old_shstr_size + sizeof( strtab_name ) > SIZE_MAX - sizeof( symtab_name ) ) + goto out; + new_shstr_size = old_shstr_size + sizeof( strtab_name ) + + sizeof( symtab_name ); + shstr = sh[shstrndx]; + shstr.offset = cur; + shstr.size = new_shstr_size; + cur += new_shstr_size; + + new_shoff = cpu_melf_align_up( cur, 8, &overflow ); + if( overflow || new_shoff > UINT64_MAX - + (__mpu_uint64_t)( shnum + 2 ) * MELF64_SHDR_SIZE ) + goto out; + total = new_shoff + (__mpu_uint64_t)( shnum + 2 ) * MELF64_SHDR_SIZE; + if( total > SIZE_MAX ) + goto out; + + out = (unsigned char *)calloc( (size_t)total, 1 ); + if( !out ) + goto out; + memcpy( out, file, (size_t)old_shoff ); + memcpy( out + (size_t)strtab.offset, fixture_strtab, + sizeof( fixture_strtab ) ); + + memset( symbols, 0, sizeof( symbols ) ); + cpu_melf_put32( symbols + MELF64_SYM_SIZE + MELF64_SYM_ST_NAME_OFF, + 1, data ); + symbols[MELF64_SYM_SIZE + MELF64_SYM_ST_INFO_OFF] = + MELF64_ST_INFO( MELF_STB_GLOBAL, MELF_STT_FUNC ); + symbols[MELF64_SYM_SIZE + MELF64_SYM_ST_OTHER_OFF] = MELF_STV_DEFAULT; + cpu_melf_put16( symbols + MELF64_SYM_SIZE + MELF64_SYM_ST_SHNDX_OFF, + (__mpu_uint16_t)text_index, data ); + cpu_melf_put64( symbols + MELF64_SYM_SIZE + MELF64_SYM_ST_VALUE_OFF, + sh[text_index].addr, data ); + memcpy( out + (size_t)symtab.offset, symbols, sizeof( symbols ) ); + + memcpy( out + (size_t)shstr.offset, old_shstr, old_shstr_size ); + memcpy( out + (size_t)shstr.offset + old_shstr_size, + strtab_name, sizeof( strtab_name ) ); + memcpy( out + (size_t)shstr.offset + old_shstr_size + sizeof( strtab_name ), + symtab_name, sizeof( symtab_name ) ); + + strtab.name = (__mpu_uint32_t)old_shstr_size; + symtab.name = (__mpu_uint32_t)( old_shstr_size + sizeof( strtab_name ) ); + sh[shstrndx] = shstr; + sh[shnum] = strtab; + sh[shnum + 1] = symtab; + + for( i = 0; i < (size_t)shnum + 2; ++i ) + put_shdr( out + (size_t)new_shoff + i * MELF64_SHDR_SIZE, + data, &sh[i] ); + + cpu_melf_put64( out + MELF64_EHDR_E_SHOFF_OFF, new_shoff, data ); + cpu_melf_put16( out + MELF64_EHDR_E_SHNUM_OFF, + (__mpu_uint16_t)( shnum + 2 ), data ); + + if( cpu_melf_writeall( argv[2], out, (size_t)total, err, sizeof( err ) ) ) + { + fprintf( stderr, "%s\n", err ); + goto out; + } + rc = EXIT_SUCCESS; + +out: + free( out ); + free( sh ); + free( file ); + return( rc ); + +} /* End of main() */ diff --git a/tests/tst-strip-runner.sh b/tests/tst-strip-runner.sh new file mode 100755 index 0000000..6d0005d --- /dev/null +++ b/tests/tst-strip-runner.sh @@ -0,0 +1,129 @@ +#!/bin/sh +set -eu + +: "${PACKAGE_VERSION:?}" + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${CPU_READELF:?}" +: "${CPU_AR:?}" "${CPU_NM:?}" "${CPU_STRIP:?}" "${STRIP_FIXTURE:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-strip-$$ +mkdir -p "$TMP" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +cat > "$TMP/main.s" <<'EOS' +.global _life +.text +_start_life: + LDI r64@0x00000, 42 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + + +cat > "$TMP/dynamic-main.s" <<'EOS' +.global _life +.extern add_one +.text +_start_life: + LDI r64@0x00000, 41 + CALL add_one + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/add-one.s" <<'EOS' +.global add_one +.text +add_one: + LDI r64@0x00008, 1 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +"$CPU_AS" -o "$TMP/main.o" "$TMP/main.s" +"$CPU_AS" -o "$TMP/add-one.o" "$TMP/add-one.s" +"$CPU_AS" -o "$TMP/dynamic-main.o" "$TMP/dynamic-main.s" +"$CPU_LD" --stack-size=4096 --no-heap -o "$TMP/main" "$TMP/main.o" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap -o "$TMP/dynamic-main" "$TMP/dynamic-main.o" +"$CPU_LD" --shared --soname=libadd.so.1 -o "$TMP/libadd.so" "$TMP/add-one.o" +"$CPU_AR" rcs "$TMP/libadd.a" "$TMP/add-one.o" + +# ET_REL .symtab/.strtab are link semantics and are preserved byte-for-byte. +"$CPU_STRIP" -o "$TMP/main.stripped.o" "$TMP/main.o" +cmp "$TMP/main.o" "$TMP/main.stripped.o" +"$CPU_READELF" -s "$TMP/main.stripped.o" | grep -q '_life' + +# Current final writers already emit no ordinary .symtab, so these are no-ops. +"$CPU_STRIP" -o "$TMP/main.clean" "$TMP/main" +cmp "$TMP/main" "$TMP/main.clean" +"$CPU_STRIP" -o "$TMP/libadd.clean.so" "$TMP/libadd.so" +cmp "$TMP/libadd.so" "$TMP/libadd.clean.so" +"$CPU_READELF" -S "$TMP/libadd.clean.so" > "$TMP/libadd.sections" +grep -q '\.dynsym' "$TMP/libadd.sections" +grep -q '\.dynstr' "$TMP/libadd.sections" +grep -q '\.mcpu.target' "$TMP/libadd.sections" + +# The same removal policy on ET_DYN must preserve dynamic symbols/relocations. +"$STRIP_FIXTURE" "$TMP/libadd.so" "$TMP/libadd.with-sym.so" +"$CPU_READELF" -S -s "$TMP/libadd.with-sym.so" > "$TMP/dyn-before.txt" +grep -q '\.symtab' "$TMP/dyn-before.txt" +"$CPU_STRIP" -o "$TMP/libadd.really-stripped.so" "$TMP/libadd.with-sym.so" +"$CPU_READELF" -S -s "$TMP/libadd.really-stripped.so" > "$TMP/dyn-after.txt" +if grep -q '\.symtab' "$TMP/dyn-after.txt"; then + echo "ordinary ET_DYN .symtab survived mcpu-strip" >&2 + exit 1 +fi +grep -q '\.dynsym' "$TMP/dyn-after.txt" +grep -q '\.dynstr' "$TMP/dyn-after.txt" +test "`MCPU_LD_PRELOAD= "$CPU_RUN" --ldpreload="$TMP/libadd.really-stripped.so" --int-reg=r64@0x00018 "$TMP/dynamic-main"`" = 42 + +# Deterministic ET_REL archives keep their members and symbol index intact. +"$CPU_STRIP" -o "$TMP/libadd.clean.a" "$TMP/libadd.a" +cmp "$TMP/libadd.a" "$TMP/libadd.clean.a" +"$CPU_NM" -A "$TMP/libadd.clean.a" | grep -q 'add_one' + +# Add a conventional non-runtime .symtab/.strtab to a valid ET_EXEC fixture. +# mcpu-strip must remove them, shrink the file and leave execution untouched. +"$STRIP_FIXTURE" "$TMP/main" "$TMP/main.with-sym" +chmod 755 "$TMP/main.with-sym" +"$CPU_READELF" -S -s "$TMP/main.with-sym" > "$TMP/before.txt" +grep -q '\.symtab' "$TMP/before.txt" +grep -q 'fixture_symbol' "$TMP/before.txt" +before=`wc -c < "$TMP/main.with-sym"` + +"$CPU_STRIP" -o "$TMP/main.really-stripped" "$TMP/main.with-sym" +"$CPU_READELF" -S -s "$TMP/main.really-stripped" > "$TMP/after.txt" +if grep -q '\.symtab' "$TMP/after.txt"; then + echo "ordinary .symtab survived mcpu-strip" >&2 + exit 1 +fi +if grep -q 'fixture_symbol' "$TMP/after.txt"; then + echo "ordinary symbol string survived mcpu-strip" >&2 + exit 1 +fi +after=`wc -c < "$TMP/main.really-stripped"` +test "$after" -lt "$before" +test "`stat -c %a "$TMP/main.really-stripped"`" = 755 +test "`"$CPU_RUN" --int-reg=r64@0x00000 "$TMP/main.really-stripped"`" = 42 + +# In-place replacement is atomic from the caller's point of view and keeps mode. +cp "$TMP/main.with-sym" "$TMP/main.inplace" +chmod 751 "$TMP/main.inplace" +"$CPU_STRIP" "$TMP/main.inplace" +test "`stat -c %a "$TMP/main.inplace"`" = 751 +test "`"$CPU_RUN" --int-reg=r64@0x00000 "$TMP/main.inplace"`" = 42 +if "$CPU_READELF" -S "$TMP/main.inplace" | grep -q '\.symtab'; then + echo "in-place strip left .symtab" >&2 + exit 1 +fi + +"$CPU_STRIP" --help | grep -q -- '--output=FILE' +"$CPU_STRIP" --version | grep -Fq "$PACKAGE_VERSION" + +if "$CPU_STRIP" "$TMP/main" "$TMP/main.clean" >/dev/null 2>&1; then + echo "mcpu-strip unexpectedly accepted multiple input files" >&2 + exit 1 +fi + +exit 0 diff --git a/tests/tst-symbol-scope-runner.sh b/tests/tst-symbol-scope-runner.sh new file mode 100755 index 0000000..8a4154e --- /dev/null +++ b/tests/tst-symbol-scope-runner.sh @@ -0,0 +1,276 @@ +#!/bin/sh +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-symbol-scope-$$ +mkdir -p "$TMP/lib" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +cat > "$TMP/main-import.s" <<'EOS' +.global _life +.extern foo +.text +_start_life: + LDI r64@0x00000, 40 + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS + +cat > "$TMP/a.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +cat > "$TMP/b.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS + +"$CPU_AS" -o "$TMP/main-import.o" "$TMP/main-import.s" +"$CPU_AS" -o "$TMP/a.o" "$TMP/a.s" +"$CPU_AS" -o "$TMP/b.o" "$TMP/b.s" +"$CPU_LD" --shared --soname=liba.so.1 -o "$TMP/lib/liba.so.1.0.0" "$TMP/a.o" +"$CPU_LD" --shared --soname=libb.so.1 -o "$TMP/lib/libb.so.1.0.0" "$TMP/b.o" +ln -s liba.so.1.0.0 "$TMP/lib/liba.so.1" +ln -s liba.so.1 "$TMP/lib/liba.so" +ln -s libb.so.1.0.0 "$TMP/lib/libb.so.1" +ln -s libb.so.1 "$TMP/lib/libb.so" + +# Normal DT_NEEDED scope is breadth-first and left-to-right. For equal +# GLOBAL definitions the first direct dependency wins. +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/lib" -la -lb -o "$TMP/main-ab" "$TMP/main-import.o" +[ "`"$CPU_RUN" --library-path="$TMP/lib" --int-reg=r64@0x00018 "$TMP/main-ab"`" = 42 ] + +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/lib" -lb -la -o "$TMP/main-ba" "$TMP/main-import.o" +[ "`"$CPU_RUN" --library-path="$TMP/lib" --int-reg=r64@0x00018 "$TMP/main-ba"`" = 43 ] + +# A main-executable GLOBAL outranks normal dependencies. +cat > "$TMP/main-own.s" <<'EOS' +.global _life +.global foo +.text +_start_life: + LDI r64@0x00000, 40 + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +foo: + LDI r64@0x00008, 1 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +"$CPU_AS" -o "$TMP/main-own.o" "$TMP/main-own.s" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/lib" -la -o "$TMP/main-own" "$TMP/main-own.o" +[ "`"$CPU_RUN" --library-path="$TMP/lib" --int-reg=r64@0x00018 "$TMP/main-own"`" = 41 ] + +# Explicit preload is the intentional override scope. Equal-strength GLOBAL +# preloads keep the historical rightmost-higher-priority rule. +[ "`"$CPU_RUN" --library-path="$TMP/lib" \ + --ldpreload=liba.so:libb.so \ + --int-reg=r64@0x00018 "$TMP/main-own"`" = 43 ] +[ "`"$CPU_RUN" --library-path="$TMP/lib" \ + --ldpreload=libb.so:liba.so \ + --int-reg=r64@0x00018 "$TMP/main-own"`" = 42 ] + +# Binding strength outranks scope class: a WEAK preload must not preempt a +# normal GLOBAL definition. +cat > "$TMP/weak.s" <<'EOS' +.weak foo +.text +foo: + LDI r64@0x00008, 9 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +"$CPU_AS" -o "$TMP/weak.o" "$TMP/weak.s" +"$CPU_LD" --shared --soname=libweak.so.1 -o "$TMP/libweak.so" "$TMP/weak.o" +[ "`"$CPU_RUN" --library-path="$TMP/lib" --ldpreload="$TMP/libweak.so" \ + --int-reg=r64@0x00018 "$TMP/main-ab"`" = 42 ] + +# If only WEAK definitions exist, the same scope ordering applies: first normal +# dependency wins, while rightmost explicit preload wins. +cat > "$TMP/weak-main.s" <<'EOS' +.global _life +.weak foo +.text +_start_life: + LDI r64@0x00000, 40 + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +cat > "$TMP/wa.s" <<'EOS' +.weak foo +.text +foo: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/wb.s" <<'EOS' +.weak foo +.text +foo: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +"$CPU_AS" -o "$TMP/weak-main.o" "$TMP/weak-main.s" +"$CPU_AS" -o "$TMP/wa.o" "$TMP/wa.s" +"$CPU_AS" -o "$TMP/wb.o" "$TMP/wb.s" +"$CPU_LD" --shared --soname=libwa.so.1 -o "$TMP/lib/libwa.so.1" "$TMP/wa.o" +"$CPU_LD" --shared --soname=libwb.so.1 -o "$TMP/lib/libwb.so.1" "$TMP/wb.o" +ln -s libwa.so.1 "$TMP/lib/libwa.so" +ln -s libwb.so.1 "$TMP/lib/libwb.so" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/lib" -lwa -lwb -o "$TMP/weak-main" "$TMP/weak-main.o" +[ "`"$CPU_RUN" --library-path="$TMP/lib" --int-reg=r64@0x00018 "$TMP/weak-main"`" = 42 ] +[ "`"$CPU_RUN" --ldpreload="$TMP/lib/libwa.so.1:$TMP/lib/libwb.so.1" \ + --int-reg=r64@0x00018 "$TMP/weak-main"`" = 43 ] + +# A direct dependency outranks a conflicting transitive dependency. The +# resolver must therefore build a true breadth-first scope rather than expose +# DFS traversal order to symbol resolution. +cat > "$TMP/deep.s" <<'EOS' +.global deep_anchor +.global foo +.text +deep_anchor: + RET +foo: + LDI r64@0x00008, 9 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/mid.s" <<'EOS' +.global mid_anchor +.extern deep_anchor +.text +mid_anchor: + CALL deep_anchor + RET +EOS +cat > "$TMP/direct.s" <<'EOS' +.global foo +.text +foo: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/bfs-main.s" <<'EOS' +.global _life +.extern mid_anchor +.extern foo +.text +_start_life: + LDI r64@0x00000, 40 + CALL mid_anchor + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +"$CPU_AS" -o "$TMP/deep.o" "$TMP/deep.s" +"$CPU_AS" -o "$TMP/mid.o" "$TMP/mid.s" +"$CPU_AS" -o "$TMP/direct.o" "$TMP/direct.s" +"$CPU_AS" -o "$TMP/bfs-main.o" "$TMP/bfs-main.s" +"$CPU_LD" --shared --soname=libdeep.so.1 -o "$TMP/lib/libdeep.so.1.0.0" "$TMP/deep.o" +ln -s libdeep.so.1.0.0 "$TMP/lib/libdeep.so.1" +ln -s libdeep.so.1 "$TMP/lib/libdeep.so" +"$CPU_LD" --shared --soname=libmid.so.1 -L"$TMP/lib" -ldeep \ + -o "$TMP/lib/libmid.so.1.0.0" "$TMP/mid.o" +ln -s libmid.so.1.0.0 "$TMP/lib/libmid.so.1" +ln -s libmid.so.1 "$TMP/lib/libmid.so" +"$CPU_LD" --shared --soname=libdirect.so.1 -o "$TMP/lib/libdirect.so.1.0.0" "$TMP/direct.o" +ln -s libdirect.so.1.0.0 "$TMP/lib/libdirect.so.1" +ln -s libdirect.so.1 "$TMP/lib/libdirect.so" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/lib" -lmid -ldirect -o "$TMP/bfs-main" "$TMP/bfs-main.o" +[ "`"$CPU_RUN" --library-path="$TMP/lib" --int-reg=r64@0x00018 "$TMP/bfs-main"`" = 43 ] + +# Diamond graph: A and B both depend on C. SONAME deduplication loads C once, +# while the direct A/B scope still follows left-to-right order. +cat > "$TMP/common.s" <<'EOS' +.global common_anchor +.text +common_anchor: + RET +EOS +cat > "$TMP/da.s" <<'EOS' +.global a_anchor +.global foo +.extern common_anchor +.text +a_anchor: + CALL common_anchor + RET +foo: + LDI r64@0x00008, 2 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/db.s" <<'EOS' +.global b_anchor +.global foo +.extern common_anchor +.text +b_anchor: + CALL common_anchor + RET +foo: + LDI r64@0x00008, 3 + IADD r64@0x00000, r64@0x00000, r64@0x00008 + RET +EOS +cat > "$TMP/diamond-main.s" <<'EOS' +.global _life +.extern a_anchor +.extern b_anchor +.extern foo +.text +_start_life: + LDI r64@0x00000, 40 + CALL a_anchor + CALL b_anchor + CALL foo + MOV r64@0x00018, r64@0x00000 + LDI r32@0x00030, 0 + EXIT r32@0x00030 +EOS +"$CPU_AS" -o "$TMP/common.o" "$TMP/common.s" +"$CPU_AS" -o "$TMP/da.o" "$TMP/da.s" +"$CPU_AS" -o "$TMP/db.o" "$TMP/db.s" +"$CPU_AS" -o "$TMP/diamond-main.o" "$TMP/diamond-main.s" +"$CPU_LD" --shared --soname=libcommon.so.1 -o "$TMP/lib/libcommon.so.1.0.0" "$TMP/common.o" +ln -s libcommon.so.1.0.0 "$TMP/lib/libcommon.so.1" +ln -s libcommon.so.1 "$TMP/lib/libcommon.so" +"$CPU_LD" --shared --soname=libda.so.1 -L"$TMP/lib" -lcommon \ + -o "$TMP/lib/libda.so.1.0.0" "$TMP/da.o" +ln -s libda.so.1.0.0 "$TMP/lib/libda.so.1" +ln -s libda.so.1 "$TMP/lib/libda.so" +"$CPU_LD" --shared --soname=libdb.so.1 -L"$TMP/lib" -lcommon \ + -o "$TMP/lib/libdb.so.1.0.0" "$TMP/db.o" +ln -s libdb.so.1.0.0 "$TMP/lib/libdb.so.1" +ln -s libdb.so.1 "$TMP/lib/libdb.so" +"$CPU_LD" --dynamic --stack-size=4096 --no-heap \ + -L"$TMP/lib" -lda -ldb -o "$TMP/diamond-main" "$TMP/diamond-main.o" +[ "`"$CPU_RUN" --library-path="$TMP/lib" --int-reg=r64@0x00018 "$TMP/diamond-main"`" = 42 ] + +exit 0 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() */ diff --git a/tests/tst-tool-config-runner.sh b/tests/tst-tool-config-runner.sh new file mode 100755 index 0000000..790da58 --- /dev/null +++ b/tests/tst-tool-config-runner.sh @@ -0,0 +1,97 @@ +#!/bin/sh +set -eu + +: "${TOOL_CONFIG_TEST:?}" "${CPU_AS:?}" "${CPU_LD:?}" "${CPU_RUN:?}" "${TEST_SRCDIR:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-tool-config-$$ +mkdir -p "$TMP/home/.mcpu/etc" "$TMP/relative-root/lib" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +HOME="$TMP/home" +export HOME + +# Baseline: no config files, executable-derived root remains active. +"$TOOL_CONFIG_TEST" --no-config > "$TMP/default.txt" +DEFAULT_LIB=$(sed -n 's/^lib=//p' "$TMP/default.txt") +test -n "$DEFAULT_LIB" + +# The obsolete per-user path must be ignored. +printf '%s\n' 'MCPU_LD_SYSTEM_LIBRARY_PATH = /obsolete/path;' \ + > "$HOME/.mcpu/mcpu-ld.conf" +"$TOOL_CONFIG_TEST" > "$TMP/old-user.txt" +test "$(sed -n 's/^lib=//p' "$TMP/old-user.txt")" = "$DEFAULT_LIB" + +# The new per-user path has highest user-level priority and expands $HOME. +cat > "$HOME/.mcpu/etc/mcpu-ld.conf" <<'EOC' +MCPU_LD_SYSTEM_LIBRARY_PATH = $HOME/mcpu-next/lib; +EOC +"$TOOL_CONFIG_TEST" > "$TMP/user.txt" +test "$(sed -n 's/^lib=//p' "$TMP/user.txt")" = "$HOME/mcpu-next/lib" + +# An explicit empty value disables the configured system library tree. +printf '%s\n' 'MCPU_LD_SYSTEM_LIBRARY_PATH = ;' \ + > "$HOME/.mcpu/etc/mcpu-ld.conf" +"$TOOL_CONFIG_TEST" > "$TMP/empty.txt" +test -z "$(sed -n 's/^lib=//p' "$TMP/empty.txt")" + +# --no-config ignores even a malformed user configuration and keeps default. +printf '%s\n' 'MCPU_LD_SYSTEM_LIBRARY_PATH = broken' \ + > "$HOME/.mcpu/etc/mcpu-ld.conf" +"$TOOL_CONFIG_TEST" --no-config > "$TMP/no-config.txt" +test "$(sed -n 's/^lib=//p' "$TMP/no-config.txt")" = "$DEFAULT_LIB" +if "$TOOL_CONFIG_TEST" > /dev/null 2>&1; then + echo "malformed user configuration was unexpectedly accepted" >&2 + exit 1 +fi + +# Absolute --sys-root implies --no-config and uses PATH/lib. +"$TOOL_CONFIG_TEST" --sys-root="$TMP/absolute-root" > "$TMP/absolute.txt" +test "$(sed -n 's/^root=//p' "$TMP/absolute.txt")" = "$TMP/absolute-root" +test "$(sed -n 's/^lib=//p' "$TMP/absolute.txt")" = "$TMP/absolute-root/lib" + +# Relative --sys-root is resolved from the invocation working directory. +( + cd "$TMP" + "$TOOL_CONFIG_TEST" --sys-root=relative-root > "$TMP/relative.txt" +) +test "$(sed -n 's/^root=//p' "$TMP/relative.txt")" = "$TMP/relative-root" +test "$(sed -n 's/^lib=//p' "$TMP/relative.txt")" = "$TMP/relative-root/lib" + + +# The production linker and runner use the same policy state. +"$CPU_AS" -o "$TMP/radd.o" "$TEST_SRCDIR/fixtures/radd-pi.s" >/dev/null +if "$CPU_LD" --stack-size=512 -o "$TMP/radd.melf" "$TMP/radd.o" >/dev/null 2>&1; then + echo "mcpu-ld unexpectedly accepted malformed user configuration" >&2 + exit 1 +fi +"$CPU_LD" --no-config --stack-size=512 \ + -o "$TMP/radd.melf" "$TMP/radd.o" >/dev/null +"$CPU_RUN" --no-config --digits=60 --real-data=4096@16 \ + "$TMP/radd.melf" > "$TMP/run-no-config.txt" +case "$(cat "$TMP/run-no-config.txt")" in + 5.14159265358979323846264338327950288419716939937510582097*) ;; + *) echo "unexpected --no-config execution result" >&2; exit 1 ;; +esac + +if "$CPU_RUN" --digits=60 --real-data=4096@16 \ + "$TMP/radd.melf" >/dev/null 2>&1; then + echo "mcpu-run unexpectedly accepted malformed user configuration" >&2 + exit 1 +fi + +"$CPU_LD" --sys-root "$TMP/absolute-root" --stack-size=512 \ + -o "$TMP/radd-sysroot.melf" "$TMP/radd.o" >/dev/null +"$CPU_RUN" --sys-root="$TMP/absolute-root" --digits=60 \ + --real-data=4096@16 "$TMP/radd-sysroot.melf" > "$TMP/run-sysroot.txt" +case "$(cat "$TMP/run-sysroot.txt")" in + 5.14159265358979323846264338327950288419716939937510582097*) ;; + *) echo "unexpected --sys-root execution result" >&2; exit 1 ;; +esac + +# Both production tools expose the common policy options. +"$CPU_LD" --help 2>&1 | grep -q -- '--no-config' +"$CPU_LD" --help 2>&1 | grep -q -- '--sys-root=PATH' +"$CPU_RUN" --help 2>&1 | grep -q -- '--no-config' +"$CPU_RUN" --help 2>&1 | grep -q -- '--sys-root=PATH' + +exit 0 diff --git a/tests/tst-tool-config.c b/tests/tst-tool-config.c new file mode 100644 index 0000000..8ea0ec6 --- /dev/null +++ b/tests/tst-tool-config.c @@ -0,0 +1,45 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> + +#include "mcpu-tool-options.h" + +int +main( int argc, char **argv ) +{ + mcpu_tool_options options; + int i; + int rc = 1; + + mcpu_tool_options_init( &options, argv[0] ); + + for( i = 1; i < argc; ++i ) + { + int parsed = mcpu_tool_parse_common_option( &options, argc, argv, &i ); + + if( parsed < 0 ) + goto done; + if( parsed == 0 ) + { + fprintf( stderr, "%s: unknown option '%s'\n", + options.progname, argv[i] ); + goto done; + } + } + + if( mcpu_tool_options_prepare(&options, argv[0]) != 0 ) + goto done; + + printf( "root=%s\n", options.runtime.installation_root ); + printf( "config=%s\n", options.runtime.config_file ); + printf( "lib=%s\n", options.runtime.system_library_path ); + rc = 0; + +done: + mcpu_tool_options_free( &options ); + return( rc ); + +} /* End of main() */ diff --git a/tests/tst-tool-inspection-runner.sh b/tests/tst-tool-inspection-runner.sh new file mode 100755 index 0000000..fa34715 --- /dev/null +++ b/tests/tst-tool-inspection-runner.sh @@ -0,0 +1,80 @@ +#!/bin/sh +set -eu + +: "${CPU_LD:?}" "${CPU_RUN:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-tool-inspection-$$ +mkdir -p "$TMP/home/.mcpu/etc" "$TMP/root/lib" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +HOME="$TMP/home" +export HOME + +# --sys-root is self-contained, suppresses config files and needs no input. +"$CPU_LD" --sys-root="$TMP/root" --dump-config > "$TMP/ld-sysroot-config.txt" +"$CPU_RUN" --sys-root="$TMP/root" --dump-config > "$TMP/run-sysroot-config.txt" +cmp "$TMP/ld-sysroot-config.txt" "$TMP/run-sysroot-config.txt" +grep '^ runtime root: '"$TMP/root"'$' "$TMP/ld-sysroot-config.txt" >/dev/null +grep '^ configuration: disabled by --sys-root$' "$TMP/ld-sysroot-config.txt" >/dev/null +grep '^MCPU_LD_SYSTEM_LIBRARY_PATH = '"$TMP/root"'/lib;$' "$TMP/ld-sysroot-config.txt" >/dev/null + +cat > "$TMP/search.expected" <<EOT +libraries: + $TMP/root/lib +EOT +"$CPU_LD" --sys-root="$TMP/root" --print-search-dirs > "$TMP/ld-search.txt" +"$CPU_RUN" --sys-root="$TMP/root" --print-search-dirs > "$TMP/run-search.txt" +cmp "$TMP/search.expected" "$TMP/ld-search.txt" +cmp "$TMP/search.expected" "$TMP/run-search.txt" + +# The effective user configuration is visible and is identical in both tools. +cat > "$HOME/.mcpu/etc/mcpu-ld.conf" <<EOC +MCPU_LD_SYSTEM_LIBRARY_PATH = $TMP/user/lib; +EOC +"$CPU_LD" --dump-config > "$TMP/ld-user-config.txt" +"$CPU_RUN" --dump-config > "$TMP/run-user-config.txt" +cmp "$TMP/ld-user-config.txt" "$TMP/run-user-config.txt" +grep '^ config: '"$HOME/.mcpu/etc/mcpu-ld.conf"' \[read\]$' "$TMP/ld-user-config.txt" >/dev/null +grep '^MCPU_LD_SYSTEM_LIBRARY_PATH = '"$TMP/user/lib"';$' "$TMP/ld-user-config.txt" >/dev/null + +cat > "$TMP/user-search.expected" <<EOT +libraries: + $TMP/user/lib +EOT +"$CPU_LD" --print-search-dirs > "$TMP/ld-user-search.txt" +"$CPU_RUN" --print-search-dirs > "$TMP/run-user-search.txt" +cmp "$TMP/user-search.expected" "$TMP/ld-user-search.txt" +cmp "$TMP/user-search.expected" "$TMP/run-user-search.txt" + +# Empty effective system path means an empty search list; there is no fallback. +printf '%s\n' 'MCPU_LD_SYSTEM_LIBRARY_PATH = ;' \ + > "$HOME/.mcpu/etc/mcpu-ld.conf" +printf '%s\n' 'libraries:' > "$TMP/empty.expected" +"$CPU_LD" --print-search-dirs > "$TMP/ld-empty.txt" +"$CPU_RUN" --print-search-dirs > "$TMP/run-empty.txt" +cmp "$TMP/empty.expected" "$TMP/ld-empty.txt" +cmp "$TMP/empty.expected" "$TMP/run-empty.txt" + +# A malformed config breaks normal inspection, but explicit suppression works. +printf '%s\n' 'MCPU_LD_SYSTEM_LIBRARY_PATH = broken' \ + > "$HOME/.mcpu/etc/mcpu-ld.conf" +if "$CPU_LD" --dump-config >/dev/null 2>&1; then + echo "mcpu-ld --dump-config unexpectedly accepted malformed configuration" >&2 + exit 1 +fi +if "$CPU_RUN" --print-search-dirs >/dev/null 2>&1; then + echo "mcpu-run --print-search-dirs unexpectedly accepted malformed configuration" >&2 + exit 1 +fi + +"$CPU_LD" --no-config --dump-config > "$TMP/no-config.txt" +grep '^ configuration: disabled by --no-config$' "$TMP/no-config.txt" >/dev/null +"$CPU_RUN" --sys-root="$TMP/root" --print-search-dirs > /dev/null + +# Both public help texts expose the same diagnostic actions. +"$CPU_LD" --help 2>&1 | grep -q -- '--dump-config' +"$CPU_LD" --help 2>&1 | grep -q -- '--print-search-dirs' +"$CPU_RUN" --help 2>&1 | grep -q -- '--dump-config' +"$CPU_RUN" --help 2>&1 | grep -q -- '--print-search-dirs' + +exit 0 diff --git a/tests/tst-verbose-runner.sh b/tests/tst-verbose-runner.sh new file mode 100755 index 0000000..e1362e4 --- /dev/null +++ b/tests/tst-verbose-runner.sh @@ -0,0 +1,68 @@ +#!/bin/sh +set -eu + +: "${CPU_AS:?}" "${CPU_LD:?}" "${TEST_SRCDIR:?}" + +TMP=${TMPDIR:-/tmp}/libmcpu-verbose-$$ +mkdir -p "$TMP" +trap 'rm -rf "$TMP"' EXIT HUP INT TERM + +src="$TEST_SRCDIR/fixtures/radd-pi.s" + +# Production default is quiet: successful assembly and linking write no +# developer summary unless -v/--verbose is requested. +out=`"$CPU_AS" -o "$TMP/main.o" "$src"` +[ -z "$out" ] + +out=`"$CPU_LD" --stack-size=512 --no-heap -o "$TMP/main" "$TMP/main.o"` +[ -z "$out" ] + +# Both spellings enable the existing developer summaries without changing +# their content or the generated object/executable semantics. +out=`"$CPU_AS" -v -o "$TMP/main-v.o" "$src"` +case "$out" in + "MELF ET_REL: "*" -> $TMP/main-v.o") ;; + *) echo "unexpected mcpu-as -v output: $out" >&2; exit 1 ;; +esac + +out=`"$CPU_AS" --verbose -o "$TMP/main-long.o" "$src"` +case "$out" in + "MELF ET_REL: "*" -> $TMP/main-long.o") ;; + *) echo "unexpected mcpu-as --verbose output: $out" >&2; exit 1 ;; +esac + +out=`"$CPU_LD" -v --stack-size=512 --no-heap \ + -o "$TMP/main-v" "$TMP/main-v.o"` +case "$out" in + "MELF ABI1 ET_EXEC: "*" -> $TMP/main-v") ;; + *) echo "unexpected mcpu-ld -v output: $out" >&2; exit 1 ;; +esac + +out=`"$CPU_LD" --verbose --dynamic --stack-size=512 --no-heap \ + -o "$TMP/main-dyn" "$TMP/main-long.o"` +case "$out" in + "MELF ABI3 ET_EXEC: "*" -> $TMP/main-dyn") ;; + *) echo "unexpected mcpu-ld --verbose dynamic output: $out" >&2; exit 1 ;; +esac + +out=`"$CPU_LD" -v -r -o "$TMP/main-partial.o" "$TMP/main-v.o"` +case "$out" in + "MELF ET_REL partial link: "*" -> $TMP/main-partial.o") ;; + *) echo "unexpected mcpu-ld -v -r output: $out" >&2; exit 1 ;; +esac + +cat > "$TMP/shared.s" <<'EOS' +.global shared_value +.text +shared_value: + RET +EOS +"$CPU_AS" -o "$TMP/shared.o" "$TMP/shared.s" +out=`"$CPU_LD" --verbose --shared --soname=libverbose.so.1 \ + -o "$TMP/libverbose.so" "$TMP/shared.o"` +case "$out" in + "MELF ABI3 ET_DYN: "*" -> $TMP/libverbose.so") ;; + *) echo "unexpected mcpu-ld --verbose shared output: $out" >&2; exit 1 ;; +esac + +exit 0 |
