unittests: Add FEXLinuxTests, with a few signal and smc tests

This commit is contained in:
Stefanos Kornilios Misis Poiitidis 2022-06-02 02:22:01 +03:00
parent c027acecf8
commit 40ec910108
29 changed files with 1199 additions and 8 deletions

View File

@ -65,7 +65,7 @@ jobs:
# Note the current convention is to use the -S and -B options here to specify source
# and build directories, but this is only available with CMake 3.13 and higher.
# The CMake binaries on the Github Actions machines are (as of this writing) 3.12
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja -DENABLE_LTO=False -DENABLE_ASSERTIONS=True -DENABLE_X86_HOST_DEBUG=True -DENABLE_INTERPRETER=True
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -G Ninja -DENABLE_LTO=False -DENABLE_ASSERTIONS=True -DENABLE_X86_HOST_DEBUG=True -DENABLE_INTERPRETER=True -DBUILD_FEX_LINUX_TESTS=True
- name: Build
working-directory: ${{runner.workspace}}/build
@ -167,6 +167,17 @@ jobs:
working-directory: ${{runner.workspace}}/build
run: mv ${{runner.workspace}}/build/Testing/Temporary/LastTest.log ${{runner.workspace}}/build/Testing/Temporary/LastTest_APITests.log || true
- name: FEXLinuxTests
working-directory: ${{runner.workspace}}/build
shell: bash
run: cmake --build . --config $BUILD_TYPE --target fex_linux_tests_all
- name: FEXLinuxTests Results move
if: ${{ always() }}
shell: bash
working-directory: ${{runner.workspace}}/build
run: mv ${{runner.workspace}}/build/Testing/Temporary/LastTest.log ${{runner.workspace}}/build/Testing/Temporary/LastTest_FEXLinuxTests.log || true
- name: Truncate test results
if: ${{ always() }}
shell: bash

View File

@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.14)
project(FEX)
option(BUILD_TESTS "Build unit tests to ensure sanity" TRUE)
option(BUILD_FEX_LINUX_TESTS "Build FEXLinuxTests, requires g++/g++-multilib or g++-x86-64-linux-gnu/g++-multilib-x86-64-linux-gnu" FALSE)
option(BUILD_THUNKS "Build thunks" FALSE)
option(ENABLE_CLANG_FORMAT "Run clang format over the source" FALSE)
option(ENABLE_IWYU "Enables include what you use program" FALSE)

View File

@ -14,7 +14,8 @@ known_failures_file = sys.argv[1]
expected_output_file = sys.argv[2]
disabled_tests_file = sys.argv[3]
test_name = sys.argv[4]
fexecutable = sys.argv[5]
mode = sys.argv[5]
fexecutable = sys.argv[6]
known_failures = { }
expected_output = { }
@ -46,14 +47,15 @@ RunnerArgs = []
RunnerArgs.append(fexecutable)
ROOTFS_ENV = os.getenv("ROOTFS")
if ROOTFS_ENV != None:
RunnerArgs.append("-R")
RunnerArgs.append(ROOTFS_ENV)
if (mode == "guest"):
ROOTFS_ENV = os.getenv("ROOTFS")
if ROOTFS_ENV != None:
RunnerArgs.append("-R")
RunnerArgs.append(ROOTFS_ENV)
# Add the rest of the arguments
for i in range(len(sys.argv) - 6):
RunnerArgs.append(sys.argv[6 + i])
for i in range(len(sys.argv) - 7):
RunnerArgs.append(sys.argv[7 + i])
#print(RunnerArgs)

View File

@ -9,3 +9,8 @@ add_subdirectory(gcc-target-tests-64/)
if (BUILD_THUNKS)
add_subdirectory(ThunkLibs)
endif()
if (BUILD_FEX_LINUX_TESTS)
add_subdirectory(FEXLinuxTests/)
endif()

View File

@ -0,0 +1,104 @@
include(ExternalProject)
ExternalProject_Add(FEXLinuxTests
PREFIX FEXLinuxTests
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tests"
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/FEXLinuxTests"
CMAKE_ARGS
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}"
"-DX86_C_COMPILER:STRING=${X86_C_COMPILER}"
"-DX86_CXX_COMPILER:STRING=${X86_CXX_COMPILER}"
INSTALL_COMMAND ""
BUILD_ALWAYS ON
)
# this kind of sucks, but reglob
file(GLOB_RECURSE TESTS CONFIGURE_DEPENDS tests/*.cpp)
foreach(TEST ${TESTS})
get_filename_component(TEST_NAME ${TEST} NAME_WLE)
file(READ ${TEST} TEST_CODE)
# Used to insert a configuration dependency to the test file
CONFIGURE_FILE(${TEST} ${CMAKE_BINARY_DIR}/junk.file)
set(ARGS_REGEX "auto args = \"([^\"]+)\";")
string(REGEX MATCH ${ARGS_REGEX} TEST_ARGS ${TEST_CODE})
# if cannot handle multiline variables, so we have to match the line first
if(${TEST_ARGS} MATCHES ${ARGS_REGEX})
string(REGEX REPLACE " |," ";" ARGS "${CMAKE_MATCH_1}")
set(VARIATIONS "")
foreach(ARG ${ARGS})
list(APPEND VARIATIONS "${TEST_NAME}-${ARG}:${ARG}")
endforeach()
else()
set(VARIATIONS "${TEST_NAME}:")
endif()
set(ALL_BITNESS 32 64)
foreach(VARIATION ${VARIATIONS})
foreach(BITNESS ${ALL_BITNESS})
string(REGEX REPLACE ":" ";" VARIATION "${VARIATION}")
list(GET VARIATION 0 VARIATION_NAME)
list(GET VARIATION 1 VARIATION_ARG)
set(BIN_PATH "${CMAKE_CURRENT_BINARY_DIR}/FEXLinuxTests/${TEST_NAME}.${BITNESS}")
set(TEST_CASE "${VARIATION_NAME}.${BITNESS}")
# Add jit test case
add_test(NAME "${TEST_CASE}.jit.flt"
COMMAND "python3" "${CMAKE_SOURCE_DIR}/Scripts/guest_test_runner.py"
"${CMAKE_CURRENT_SOURCE_DIR}/Known_Failures"
"${CMAKE_CURRENT_SOURCE_DIR}/Expected_Output"
"${CMAKE_CURRENT_SOURCE_DIR}/Disabled_Tests"
"${TEST_CASE}"
"guest"
"$<TARGET_FILE:FEXLoader>"
"--no-silent" "-c" "irjit" "-n" "500" "--"
"${BIN_PATH}"
"${VARIATION_ARG}")
if (_M_X86_64)
# Add host test case
add_test(NAME "${TEST_CASE}.host.flt"
COMMAND "python3" "${CMAKE_SOURCE_DIR}/Scripts/guest_test_runner.py"
"${CMAKE_CURRENT_SOURCE_DIR}/Known_Failures_Host"
"${CMAKE_CURRENT_SOURCE_DIR}/Expected_Output"
"${CMAKE_CURRENT_SOURCE_DIR}/Disabled_Tests_Host"
"${TEST_CASE}"
"host"
"${BIN_PATH}"
"${VARIATION_ARG}")
endif()
endforeach()
endforeach()
endforeach()
execute_process(COMMAND "nproc" OUTPUT_VARIABLE CORES)
string(STRIP ${CORES} CORES)
# Only emulated
add_custom_target(
fex_linux_tests
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
USES_TERMINAL
COMMAND "ctest" "--timeout" "30" "-j${CORES}" "-R" "\.*\.jit\.flt$$" "--output-on-failure"
DEPENDS FEXLinuxTests FEXLoader
)
# Only host
add_custom_target(
fex_linux_tests_host
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
USES_TERMINAL
COMMAND "ctest" "--timeout" "30" "-j${CORES}" "-R" "\.*\.host\.flt$$" "--output-on-failure"
DEPENDS FEXLinuxTests
)
# Both host and emulated
add_custom_target(
fex_linux_tests_all
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
USES_TERMINAL
COMMAND "ctest" "--timeout" "30" "-j${CORES}" "-R" "\.*\.flt$$" "--output-on-failure"
DEPENDS FEXLinuxTests FEXLoader
)

View File

@ -0,0 +1,17 @@
###
### Disabled tests ###
###
# These sometimes crash FEX with SIGSEGV
timer-sigev-thread.32
timer-sigev-thread.64
# These fail on arm because of sigbus handling
synchronous-signal-block-sbus.32
synchronous-signal-block-sbus.64
synchronous-signal-block-abus.32
synchronous-signal-block-abus.64
# These fail on arm because we don't raise FPE
synchronous-signal-block-sfpe.32
synchronous-signal-block-sfpe.64

View File

@ -0,0 +1,8 @@
synchronous-signal-block-ssegv.32 -11
synchronous-signal-block-ssegv.64 -11
synchronous-signal-block-sill.32 -4
synchronous-signal-block-sill.64 -4
synchronous-signal-block-sbus.32 -7
synchronous-signal-block-sbus.64 -7
synchronous-signal-block-sfpe.32 -8
synchronous-signal-block-sfpe.64 -8

View File

@ -0,0 +1,39 @@
###
### Disabled tests ###
###
# These sometimes crash FEX with SIGSEGV
timer-sigev-thread.32
timer-sigev-thread.64
# These fail on arm because of sigbus handling
synchronous-signal-block-sbus.32
synchronous-signal-block-sbus.64
synchronous-signal-block-abus.32
synchronous-signal-block-abus.64
# These fail on arm because we don't raise FPE
synchronous-signal-block-sfpe.32
synchronous-signal-block-sfpe.64
###
### Failing Tests ###
###
# these will be fixed with FEX_TICKET(1725)
sigtest_samask.32
sigtest_samask.64
sigtest_sigmask.32
sigtest_sigmask.64
# These fail to do default signal catching behaviour
synchronous-signal-block-ssegv.32
synchronous-signal-block-ssegv.64
synchronous-signal-block-sill.32
synchronous-signal-block-sill.64
# These fail to queue the signals
synchronous-signal-block-asegv.32
synchronous-signal-block-asegv.64
synchronous-signal-block-aill.32
synchronous-signal-block-aill.64

View File

@ -0,0 +1,68 @@
cmake_minimum_required(VERSION 3.14)
project(FEXLinuxTests)
set(CMAKE_CXX_STANDARD 17)
set (X86_C_COMPILER "x86_64-linux-gnu-gcc" CACHE STRING "c compiler for compiling x86 guest libs")
set (X86_CXX_COMPILER "x86_64-linux-gnu-g++" CACHE STRING "c++ compiler for compiling x86 guest libs")
set(CMAKE_C_COMPILER "${X86_C_COMPILER}")
set(CMAKE_CXX_COMPILER "${X86_CXX_COMPILER}")
unset (CMAKE_C_FLAGS)
unset (CMAKE_CXX_FLAGS)
set(GENERATE_GUEST_INSTALL_TARGETS TRUE)
file(GLOB_RECURSE TESTS CONFIGURE_DEPENDS *.cpp)
foreach(TEST ${TESTS})
get_filename_component(TEST_NAME ${TEST} NAME_WLE)
# Used to insert a configuration dependency to the test file
CONFIGURE_FILE(${TEST} ${CMAKE_BINARY_DIR}/junk.file)
file(READ ${TEST} TEST_CODE)
set(FLAGS_REGEX "//[ ]*append cxxflags: ([^\n]+)")
string(REGEX MATCH ${FLAGS_REGEX} APPEND_CXX_FLAGS ${TEST_CODE})
# if cannot handle multiline variables, so we have to match the line first
if(${APPEND_CXX_FLAGS} MATCHES ${FLAGS_REGEX})
set(APPEND_CXX_FLAGS "${CMAKE_MATCH_1}")
else()
set(APPEND_CXX_FLAGS "")
endif()
set(FLAGS_REGEX "//[ ]*append ldflags: ([^\n]+)")
string(REGEX MATCH ${FLAGS_REGEX} APPEND_LD_FLAGS ${TEST_CODE})
# if cannot handle multiline variables, so we have to match the line first
if(${APPEND_LD_FLAGS} MATCHES ${FLAGS_REGEX})
set(APPEND_LD_FLAGS "${CMAKE_MATCH_1}" )
else()
set(APPEND_LD_FLAGS "")
endif()
set(FLAGS_REGEX "//[ ]*libs: ([^\n]+)")
string(REGEX MATCH ${FLAGS_REGEX} LIBS ${TEST_CODE})
# if cannot handle multiline variables, so we have to match the line first
if(${LIBS} MATCHES ${FLAGS_REGEX})
set(LIBS "${CMAKE_MATCH_1}" )
string(REGEX REPLACE " |," ";" LIBS "${LIBS}")
else()
set(LIBS "")
endif()
set(BIN_NAME_32 "${TEST_NAME}.32")
set(BIN_NAME_64 "${TEST_NAME}.64")
add_executable(${BIN_NAME_32} ${TEST})
set_target_properties(${BIN_NAME_32} PROPERTIES COMPILE_FLAGS "${APPEND_CXX_FLAGS} -m32 -g -O2 " LINK_FLAGS "${APPEND_LD_FLAGS} -m32")
target_link_libraries(${BIN_NAME_32} ${LIBS})
add_executable(${BIN_NAME_64} ${TEST})
set_target_properties(${BIN_NAME_64} PROPERTIES COMPILE_FLAGS "${APPEND_CXX_FLAGS} -g -O2" LINK_FLAGS "${APPEND_LD_FLAGS}")
target_link_libraries(${BIN_NAME_64} ${LIBS})
endforeach()

View File

@ -0,0 +1,95 @@
//libs: pthread
#include <atomic>
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// Derived from example in https://manual.cs50.io/3/pthread_cancel
// <<Manual pages for the C standard library, C POSIX library, and the CS50 Library>>
std::atomic<bool> thread_ready;
std::atomic<bool> cancel_sent;
static pthread_key_t key;
void key_dtor(void *ptr) {
puts("key_dtor: Thread aborted\n");
free(ptr);
}
#define handle_error_en(en, msg) \
do { \
errno = en; \
perror(msg); \
exit(EXIT_FAILURE); \
} while (0)
static void *thread_func(void *ignored_argument) {
pthread_key_create(&key, &key_dtor);
pthread_setspecific(key, malloc(32));
int s;
/* Disable cancellation for a while, so that we don't
immediately react to a cancellation request. */
s = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
if (s != 0)
handle_error_en(s, "pthread_setcancelstate");
printf("thread_func(): started; cancellation disabled\n");
thread_ready = true;
while (!cancel_sent.load())
;
printf("thread_func(): about to enable cancellation\n");
s = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
if (s != 0)
handle_error_en(s, "pthread_setcancelstate");
/* sleep() is a cancellation point. */
for (;;)
sleep(1000); /* Should get canceled while we sleep */
/* Should never get here. */
printf("thread_func(): not canceled!\n");
return NULL;
}
int main(void) {
pthread_t thr;
void *res;
int s;
/* Start a thread and then send it a cancellation request. */
s = pthread_create(&thr, NULL, &thread_func, NULL);
if (s != 0)
handle_error_en(s, "pthread_create");
while (!thread_ready.load())
;
printf("main(): sending cancellation request\n");
s = pthread_cancel(thr);
if (s != 0)
handle_error_en(s, "pthread_cancel");
cancel_sent = true;
/* Join with thread to see what its exit status was. */
s = pthread_join(thr, &res);
if (s != 0)
handle_error_en(s, "pthread_join");
if (res == PTHREAD_CANCELED)
printf("main(): thread was canceled\n");
else
printf("main(): thread wasn't canceled (shouldn't happen!)\n");
exit(EXIT_SUCCESS);
}

View File

@ -0,0 +1,60 @@
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
volatile bool loop = false;
volatile int count = 0;
volatile int count2 = 0;
#define NUMCOUNT 10
#define SIGN SIGTSTP
void sig_handler(int signum, siginfo_t *info, void *context) {
loop = false;
printf("Inside handler function\n");
if (count != 0) {
printf("SA_NODEFER bug\n");
exit(-1);
}
if (count2 != 0) {
printf("Nested raise correctly raised, trying sigprocmask\n");
sigset_t old;
// test if sigmask returned by sigprocmask is the one currently active
sigprocmask(0, 0, &old);
sigprocmask(SIG_SETMASK, &old, 0);
}
if (count2 < NUMCOUNT) {
printf("Nested Raising %d, %d of %d times\n", signum, 1 + count, NUMCOUNT);
count2++;
raise(signum);
count++;
} else {
exit(0);
printf("Exiting\n");
}
}
int main() {
struct sigaction act = {0};
act.sa_flags = SA_SIGINFO | SA_NODEFER;
act.sa_sigaction = &sig_handler;
if (sigaction(SIGN, &act, NULL) != 0) {
printf("sigaction failed\n");
return -3;
}
loop = true;
while (loop) {
printf("Inside main loop, raising signal\n");
raise(SIGN);
if (loop) {
printf("Error: Signal did not get raised\n");
return -3;
}
}
return -2;
}

View File

@ -0,0 +1,80 @@
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
volatile bool loop = true;
volatile bool last = false;
volatile int count = 0;
volatile int count2 = 0;
// OPTIONS
// TESTSIGPROCMASK
#define NUMCOUNT 10
#define SIGN SIGTSTP
void sig_handler(int signum) {
loop = false;
printf("Inside handler function\n");
if (last) {
printf("Handling last raise\n");
return;
}
if (count2 != count) {
printf("Signal reentering bug\n");
exit(-1);
}
if (count < NUMCOUNT) {
printf("Nested Raising sig%d, %d of %d times\n", signum, 1 + count, NUMCOUNT);
count2++;
raise(signum);
printf("Nested raise correctly blocked, trying sigprocmask\n");
sigset_t old;
// test if sigmask returned by sigprocmask is the one currently active
sigprocmask(0, 0, &old);
sigprocmask(SIG_SETMASK, &old, 0);
printf("sigprocmask worked correctly, should trigger next iteration on signal return\n");
count++;
}
}
int main() {
if (signal(SIGN, sig_handler) != 0) {
printf("Signal() failed\n");
return -2;
}
// test if sigmask blocks during execution as expected
last = false;
loop = true;
while (loop) {
printf("Inside main loop, raising signal\n");
raise(SIGN);
if (loop) {
printf("Error: Signal did not get raised\n");
return -4;
}
}
last = true;
loop = true;
// test if sigmask returned by sigprocmask is the one set by the signal return
sigset_t old;
sigprocmask(0, 0, &old);
sigprocmask(SIG_SETMASK, &old, 0);
while (loop) {
printf("Inside last loop, raising signal\n");
raise(SIGN);
if (loop) {
printf("Error: Signal did not get raised\n");
return -3;
}
}
printf("All good, Exiting\n");
return 0;
}

View File

@ -0,0 +1,48 @@
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>
#include <unistd.h>
volatile bool loop = false;
volatile bool inhandler = false;
#define SIGN SIGTSTP
void sig_handler(int signum, siginfo_t *info, void *context) {
loop = false;
printf("Inside handler function\n");
if (inhandler) {
printf("Signal reentering bug\n");
exit(-1);
}
inhandler = true;
raise(signum);
auto uctx = (ucontext_t *)context;
sigfillset(&uctx->uc_sigmask);
}
int main() {
struct sigaction act = {0};
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = &sig_handler;
if (sigaction(SIGN, &act, NULL) != 0) {
printf("sigaction() failed\n");
return -2;
}
loop = true;
while (loop) {
printf("Inside main loop, raising signal\n");
raise(SIGN);
if (loop) {
printf("Error: Signal did not get raised\n");
return -3;
}
}
printf("Exiting\n");
return 0;
}

View File

@ -0,0 +1,73 @@
auto args = "ssegv, asegv, sill, aill, sbus, abus, sfpe, afpe";
#include <cstring>
#include <errno.h>
#include <malloc.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#define handle_error(msg) \
do { \
perror(msg); \
exit(EXIT_FAILURE); \
} while (0)
char *buffer;
int flag = 0;
static void handler(int sig, siginfo_t *si, void *unused) {
printf("Got %d at address: 0x%lx\n", sig, (long)si->si_addr);
exit(1);
}
int main(int argc, char *argv[]) {
if (argc == 1) {
printf("please specify one of %s\n", args);
}
struct sigaction sa;
sa.sa_flags = SA_SIGINFO;
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = handler;
sigaction(SIGSEGV, &sa, NULL);
sigaction(SIGBUS, &sa, NULL);
sigaction(SIGILL, &sa, NULL);
sigaction(SIGFPE, &sa, NULL);
auto map1 = mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0);
auto map2 = (char *)mremap(map1, 4096, 8192, MREMAP_MAYMOVE);
sigset_t set;
sigfillset(&set);
sigprocmask(SIG_SETMASK, &set, nullptr);
if (strcmp(argv[1], "ssegv") == 0) {
*(int *)(0x32) = 0x64;
} else if (strcmp(argv[1], "sill") == 0) {
asm volatile("ud2\n");
} else if (strcmp(argv[1], "sbus") == 0) {
map2[4096] = 2;
} else if (strcmp(argv[1], "sfpe") == 0) {
volatile int a = 10;
volatile int b = 0;
volatile int c = a / b;
printf("result: %d\n", c);
} else if (strcmp(argv[1], "asegv") == 0) {
raise(SIGSEGV);
} else if (strcmp(argv[1], "aill") == 0) {
raise(SIGILL);
} else if (strcmp(argv[1], "abus") == 0) {
raise(SIGBUS);
} else if (strcmp(argv[1], "afpe") == 0) {
raise(SIGFPE);
} else {
printf("Invalid argument %s\n", argv[1]);
printf("please specify one of %s\n", args);
}
exit(0);
}

View File

@ -0,0 +1,49 @@
//libs: rt pthread
// Simple test of timer_create + SIGEV_THREAD, glibc implements it via SIG32
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <cassert>
#include <signal.h>
#include <time.h>
int test;
void timer_handler(union sigval sv) {
auto ok = sv.sival_ptr == &test;
printf("timer_handler called, ok = %d\n", ok);
exit(ok ? 0 : -1);
}
int main() {
timer_t timer;
sigevent sige;
itimerspec spec;
memset(&sige, 0, sizeof(sige));
sige.sigev_notify = SIGEV_THREAD;
sige.sigev_notify_function = &timer_handler;
sige.sigev_value.sival_ptr = &test;
timer_create(CLOCK_REALTIME, &sige, &timer);
memset(&spec, 0, sizeof(spec));
spec.it_value.tv_sec = 0;
spec.it_value.tv_nsec = 1;
timer_settime(timer, 0, &spec, NULL);
for (;;)
sleep(1);
assert(false && "should never get here");
return -2;
}

View File

@ -0,0 +1,11 @@
// append ldflags: -z execstack
auto args = "stack, data_sym, text_sym";
#define EXECSTACK
#include "smc-1.inl"
/*
We cannot test the omagic or the static version of this, due to cross compiling issues
//#define OMAGIC // when the g++ driver is used to link, -Wl,--omagic breaks -static, so this can't be tested
#include "smc-1.inl"
*/

View File

@ -0,0 +1,45 @@
/*
tests for smc changes in .text, stack and bss
*/
char data_sym[16384];
char text_sym[16384] __attribute__((section(".text")));
#include "smc-common.h"
int main(int argc, char *argv[]) {
if (argc == 2) {
if (strcmp(argv[1], "stack") == 0) {
// stack, depends on -z execstack or mprotect
char stack[16384];
auto code = (char *)(((uintptr_t)stack + 4095) & ~4095);
#if !defined(EXECSTACK)
mprotect(code, 4096, PROT_READ | PROT_WRITE | PROT_EXEC);
#endif
return test(code, "stack");
} else if (strcmp(argv[1], "data_sym") == 0) {
// data_sym, must use mprotect
auto code = (char *)(((uintptr_t)data_sym + 4095) & ~4095);
mprotect(code, 4096, PROT_READ | PROT_WRITE | PROT_EXEC);
return test(code, "data_sym");
} else if (strcmp(argv[1], "text_sym") == 0) {
// text_sym, depends on -Wl,omagic or mprotect
auto code = (char *)(((uintptr_t)text_sym + 4095) & ~4095);
#if !defined(OMAGIC)
mprotect(code, 4096, PROT_READ | PROT_WRITE | PROT_EXEC);
#endif
return test(code, "text_sym");
}
}
printf("Invalid arguments\n");
printf("please specify one of %s\n", args);
return -1;
}

View File

@ -0,0 +1,43 @@
/*
tests for smc changes memory mapped via mmap, mremap, shmat without mirroring
*/
auto args = "mmap, mremap, shmat, shmat_mremap, mmap_shmdt";
#include "smc-common.h"
int main(int argc, char *argv[]) {
if (argc == 2) {
if (strcmp(argv[1], "mmap") == 0) {
auto code = (char *)mmap(0, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, 0, 0);
return test(code, argv[1]);
} else if (strcmp(argv[1], "mremap") == 0) {
auto code = (char *)mmap(0, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_ANON, 0, 0);
auto code2 = (char *)mremap(code, 0, 4096, MREMAP_MAYMOVE);
return test(code2, argv[1]);
} else if (strcmp(argv[1], "shmat") == 0) {
auto shm = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0777);
auto code = (char *)shmat(shm, nullptr, SHM_EXEC);
return test(code, argv[1]);
} else if (strcmp(argv[1], "shmat_mremap") == 0) {
auto shm = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0777);
auto code = (char *)shmat(shm, nullptr, SHM_EXEC);
auto code2 = (char *)mremap(code, 0, 4096, MREMAP_MAYMOVE);
return test(code2, argv[1]);
} else if (strcmp(argv[1], "mmap_shmdt") == 0) {
auto shmid = shmget(IPC_PRIVATE, 4096 * 3, IPC_CREAT | 0777);
auto ptrshm = (char *)shmat(shmid, 0, 0);
shmctl(shmid, IPC_RMID, NULL);
auto ptrmmap = (char *)mmap(ptrshm + 4096, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED | MAP_PRIVATE | MAP_ANON, 0, 0);
shmdt(ptrshm);
test(ptrmmap, argv[1]);
return 0;
}
}
printf("Invalid arguments\n");
printf("please specify one of %s\n", args);
return -1;
}

View File

@ -0,0 +1,114 @@
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <sys/wait.h>
int test(char *code, const char *name) {
// mov eax, imm32
code[0] = 0xB8;
code[1] = 0xAA;
code[2] = 0xBB;
code[3] = 0xCC;
code[4] = 0xDD;
// ret
code[5] = 0xC3;
auto fn = (int (*)())code;
auto e1 = fn();
// patch imm
code[3] = 0xFE;
auto e2 = fn();
mprotect(code, 4096, PROT_READ | PROT_EXEC);
mprotect(code, 4096, PROT_READ | PROT_WRITE | PROT_EXEC);
// patch imm
code[3] = 0xF3;
mprotect(code, 4096, PROT_READ | PROT_EXEC);
auto e3 = fn();
mprotect(code, 4096, PROT_READ | PROT_WRITE | PROT_EXEC);
// patch imm
code[3] = 0xF1;
auto e4 = fn();
int failure_set = 0;
failure_set |= (e1 != 0xDDCCBBAA) << 0;
printf("%s-1: %X, %s\n", name, e1, e1 != 0xDDCCBBAA ? "FAIL" : "PASS");
failure_set |= (e2 != 0xDDFEBBAA) << 1;
printf("%s-2: %X, %s\n", name, e2, e2 != 0xDDFEBBAA ? "FAIL" : "PASS");
failure_set |= (e3 != 0xDDF3BBAA) << 2;
printf("%s-3: %X, %s\n", name, e3, e3 != 0xDDF3BBAA ? "FAIL" : "PASS");
failure_set |= (e4 != 0xDDF1BBAA) << 3;
printf("%s-4: %X, %s\n", name, e4, e4 != 0xDDF1BBAA ? "FAIL" : "PASS");
return failure_set;
}
int test_shared(char* code, char* codeexec, const char* name) {
assert(code != codeexec);
code[0] = 0xB8;
code[1] = 0xAA;
code[2] = 0xBB;
code[3] = 0xCC;
code[4] = 0xDD;
code[5] = 0xC3;
auto fn = (int(*)())codeexec;
auto e1 = fn();
code[3]=0xFE;
auto e2 = fn();
int failure_set = 0;
failure_set |= (e1 != 0xDDCCBBAA) << 0;
printf("%s-1: %X, %s\n", name, e1, e1 != 0xDDCCBBAA? "FAIL" : "PASS");
failure_set |= (e2 != 0xDDFEBBAA) << 1;
printf("%s-2: %X, %s\n", name, e2, e2 != 0xDDFEBBAA? "FAIL" : "PASS");
return failure_set;
}
int test_forked(char* code, char* codeexec, const char* name) {
code[0] = 0xB8;
code[1] = 0xAA;
code[2] = 0xBB;
code[3] = 0xCC;
code[4] = 0xDD;
code[5] = 0xC3;
auto fn = (int(*)())codeexec;
auto e1 = fn();
auto pid = fork();
if (pid == 0) {
code[3]=0xFE;
exit(0);
} else {
int status;
wait(&status);
return WEXITSTATUS(status);
}
auto e2 = fn();
printf("%s-1: %X, %s\n", name, e1, e1 != 0xDDCCBBAA? "FAIL" : "PASS");
printf("%s-2: %X, %s\n", name, e2, e2 != 0xDDFEBBAA? "FAIL" : "PASS");
}

View File

@ -0,0 +1,82 @@
// libs: pthread
/*
tests concurrent invalidation of different code from different threads
creates 10 threads
each thread does an smc test 10 times
*/
#include <cstdio>
#include <pthread.h>
#include <sys/mman.h>
#include <atomic>
std::atomic<int> result;
std::atomic<bool> go;
void *thread(void *) {
auto code = (char *)mmap(0, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, 0, 0);
for (int k = 0; k < 10; k++) {
code[0] = 0xB8;
code[1] = 0xAA;
code[2] = 0xBB;
code[3] = 0xCC;
code[4] = 0xDD;
code[5] = 0xC3;
while(!go) ;
auto fn = (int (*)())code;
auto e1 = fn();
code[3] = 0xFE;
auto e2 = fn();
mprotect(code, 4096, PROT_READ | PROT_EXEC);
mprotect(code, 4096, PROT_READ | PROT_WRITE | PROT_EXEC);
code[3] = 0xF3;
mprotect(code, 4096, PROT_READ | PROT_EXEC);
auto e3 = fn();
mprotect(code, 4096, PROT_READ | PROT_WRITE | PROT_EXEC);
code[3] = 0xF1;
auto e4 = fn();
result |= e1 != 0xDDCCBBAA;
printf("Exec1: %X, %s\n", e1, e1 != 0xDDCCBBAA ? "FAIL" : "PASS");
result |= e2 != 0xDDFEBBAA;
printf("Exec2: %X, %s\n", e2, e2 != 0xDDFEBBAA ? "FAIL" : "PASS");
result |= e3 != 0xDDF3BBAA;
printf("Exec3: %X, %s\n", e3, e3 != 0xDDF3BBAA ? "FAIL" : "PASS");
result |= e4 != 0xDDF1BBAA;
printf("Exec4: %X, %s\n", e4, e4 != 0xDDF1BBAA ? "FAIL" : "PASS");
}
return 0;
}
int main() {
pthread_t tid[10];
for (int i = 0; i < 10; i++) {
pthread_create(&tid[i], 0, &thread, 0);
}
go = true;
for (int i = 0; i < 10; i++) {
void *rv;
pthread_join(tid[i], &rv);
}
return result;
}

View File

@ -0,0 +1,99 @@
// libs: pthread
/*
tests one thread modifying another thread's code
main thread
- allocates code buffer
- starts secondary thread
- waits to be signaled from secondary thread
- modifies the code
- waits for secondary thread to exit, while making sure it doesn't run the old code after modification
- exits
secondary thread
- generates some code and runs it once
- signals main thread to modify the code
- calls the to be code and checks if the result is the modified or non modified one
- exits
*/
#include <cstdio>
#include <cstdlib>
#include <pthread.h>
#include <sys/mman.h>
#include <unistd.h>
#include <atomic>
std::atomic<bool> ready_for_modification;
std::atomic<bool> thread_unblocked;
std::atomic<int> thread_counter;
char *code;
void *thread(void *) {
printf("Generating code on thread\n");
code[0] = 0xB8;
code[1] = 0xAA;
code[2] = 0xBB;
code[3] = 0xCC;
code[4] = 0xDD;
code[5] = 0xC3;
auto fn = (int (*)())code;
fn();
ready_for_modification = true;
printf("Waiting for code to be modified\n");
while (fn() == 0xDDCCBBAA)
thread_counter++;
thread_unblocked = true;
printf("Thread exiting\n");
return 0;
}
int main() {
code = (char *)mmap(0, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, 0, 0);
pthread_t tid;
pthread_create(&tid, 0, &thread, 0);
while (!ready_for_modification)
;
printf("Modifying code from another thread\n");
code[3] = 0xFE;
auto counter = thread_counter.load();
printf("Waiting for thread to get unblocked\n");
bool once = false;
while (!thread_unblocked) {
if (thread_counter != counter) {
// depending on the patch timing, this might happen once
if (once) {
printf("Thread should have been patched to not modify counter here\n");
exit(1);
}
printf("Thread overshoot once, this is non fatal\n");
once = true;
counter = thread_counter.load();
}
}
printf("Should exit now\n");
void *rv;
pthread_join(tid, &rv);
return 0;
}

View File

@ -0,0 +1,90 @@
/*
tests shared / mirrored mappings
*/
// libs: rt pthread
auto args = "mmap_mremap, mmap_mremap_mid, shmat, shmat_mremap, shmat_mremap_mid, mmap_mmap, mmap_mmap_fd_fd2, shm_open_mmap_mmap, shm_open_mmap_mmap_fd_fd2";
#include "smc-common.h"
int main(int argc, char *argv[]) {
if (argc == 2) {
if (strcmp(argv[1], "mmap_mremap") == 0) {
auto code = (char *)mmap(0, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_ANON, 0, 0);
auto code2 = (char *)mremap(code, 0, 4096, MREMAP_MAYMOVE);
return test_shared(code, code2, argv[1]);
} else if (strcmp(argv[1], "mmap_mremap_mid") == 0) {
auto code = (char *)mmap(0, 8192, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_ANON, 0, 0);
auto code2 = (char *)mremap(code + 4096, 0, 4096, MREMAP_MAYMOVE);
return test_shared(code + 4096, code2, argv[1]);
} else if (strcmp(argv[1], "shmat") == 0) {
auto shm = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0777);
auto code3 = (char *)shmat(shm, nullptr, 0);
auto code4 = (char *)shmat(shm, nullptr, SHM_EXEC);
return test_shared(code3, code4, "shmat");
} else if (strcmp(argv[1], "shmat_mremap") == 0) {
auto shm2 = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0777);
auto code5 = (char *)shmat(shm2, nullptr, SHM_EXEC);
auto code6 = (char *)mremap(code5, 0, 4096, MREMAP_MAYMOVE);
return test_shared(code5, code6, argv[1]);
} else if (strcmp(argv[1], "shmat_mremap_mid") == 0) {
auto shm2 = shmget(IPC_PRIVATE, 8192, IPC_CREAT | 0777);
auto code5 = (char *)shmat(shm2, nullptr, SHM_EXEC);
auto code6 = (char *)mremap(code5 + 4096, 0, 4096, MREMAP_MAYMOVE);
return test_shared(code5 + 4096, code6, argv[1]);
} else if (strcmp(argv[1], "mmap_mmap") == 0) {
char file[] = "smc-tests.XXXXXXXX";
int fd = mkstemp(file);
unlink(file);
ftruncate(fd, 4096);
auto code7 = (char *)mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
auto code8 = (char *)mmap(0, 4096, PROT_READ | PROT_EXEC, MAP_SHARED, fd, 0);
return test_shared(code7, code8, argv[1]);
} else if (strcmp(argv[1], "mmap_mmap_fd_fd2") == 0) {
char file[] = "smc-tests.XXXXXXXX";
int fd = mkstemp(file);
int fd2 = open(file, O_RDONLY);
unlink(file);
ftruncate(fd, 4096);
auto code = (char *)mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
auto code2 = (char *)mmap(0, 4096, PROT_READ | PROT_EXEC, MAP_SHARED, fd2, 0);
return test_shared(code, code2, argv[1]);
} else if (strcmp(argv[1], "shm_open_mmap_mmap") == 0) {
char file[] = "smc-tests.XXXXXXXX";
mktemp(file);
int fd = shm_open(file, O_RDWR | O_CREAT, 0700);
shm_unlink(file);
ftruncate(fd, 4096);
auto code7 = (char *)mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
auto code8 = (char *)mmap(0, 4096, PROT_READ | PROT_EXEC, MAP_SHARED, fd, 0);
return test_shared(code7, code8, argv[1]);
} else if (strcmp(argv[1], "shm_open_mmap_mmap_fd_fd2") == 0) {
char file[] = "smc-tests.XXXXXXXX";
mktemp(file);
int fd = shm_open(file, O_RDWR | O_CREAT, 0700);
int fd2 = shm_open(file, O_RDONLY, 0700);
shm_unlink(file);
ftruncate(fd, 4096);
auto code7 = (char *)mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
auto code8 = (char *)mmap(0, 4096, PROT_READ | PROT_EXEC, MAP_SHARED, fd2, 0);
return test_shared(code7, code8, argv[1]);
}
}
printf("Invalid arguments\n");
printf("please specify one of %s\n", args);
return -1;
}

View File

@ -0,0 +1,42 @@
/*
tests shared / mirrored mappings
*/
// libs: rt pthread
auto args = "mmap_fork, shmat_fork, fork_shmat_same_shmid";
#include "smc-common.h"
int main(int argc, char *argv[]) {
if (argc == 2) {
if (strcmp(argv[1], "mmap_fork") == 0) {
auto code = (char *)mmap(0, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_ANON, 0, 0);
return test_forked(code, code, argv[1]);
} else if (strcmp(argv[1], "shmat_fork") == 0) {
auto shm = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0777);
auto code = (char *)shmat(shm, nullptr, SHM_EXEC);
return test_forked(code, code, argv[1]);
} else if (strcmp(argv[1], "fork_shmat_same_shmid") == 0) {
auto shm = shmget(IPC_PRIVATE, 4096, IPC_CREAT | 0777);
auto code3 = (char *)shmat(shm, nullptr, 0);
if (fork() == 0) {
auto code4 = (char *)shmat(shm, nullptr, SHM_EXEC);
return test_shared(code3, code4, argv[1]);
} else {
int status;
wait(&status);
return WEXITSTATUS(status);
}
}
}
printf("Invalid arguments\n");
printf("please specify one of %s\n", args);
return -1;
}

View File

@ -16,6 +16,7 @@ foreach(POSIX_TEST ${POSIX_TESTS})
"${CMAKE_SOURCE_DIR}/unittests/POSIX/Expected_Output"
"${CMAKE_SOURCE_DIR}/unittests/POSIX/Disabled_Tests"
"${TEST_NAME}"
"guest"
"${CMAKE_BINARY_DIR}/Bin/FEXLoader"
"--no-silent" "-c" "irint" "-n" "500" "--"
"${POSIX_TEST}")
@ -27,6 +28,7 @@ foreach(POSIX_TEST ${POSIX_TESTS})
"${CMAKE_SOURCE_DIR}/unittests/POSIX/Expected_Output"
"${CMAKE_SOURCE_DIR}/unittests/POSIX/Disabled_Tests"
"${TEST_NAME}"
"guest"
"${CMAKE_BINARY_DIR}/Bin/FEXLoader"
"--no-silent" "-c" "irjit" "-n" "500" "--"
"${POSIX_TEST}")

View File

@ -17,6 +17,7 @@ foreach(TEST ${TESTS})
"${CMAKE_SOURCE_DIR}/unittests/gcc-target-tests-32/Expected_Output"
"${CMAKE_SOURCE_DIR}/unittests/gcc-target-tests-32/Disabled_Tests"
"${TEST_NAME}"
"guest"
"${CMAKE_BINARY_DIR}/Bin/FEXLoader"
"--no-silent" "-c" "irjit" "-n" "500" "--"
"${TEST}")

View File

@ -17,6 +17,7 @@ foreach(TEST ${TESTS})
"${CMAKE_SOURCE_DIR}/unittests/gcc-target-tests-64/Expected_Output"
"${CMAKE_SOURCE_DIR}/unittests/gcc-target-tests-64/Disabled_Tests"
"${TEST_NAME}"
"guest"
"${CMAKE_BINARY_DIR}/Bin/FEXLoader"
"--no-silent" "-c" "irjit" "-n" "500" "--"
"${TEST}")

View File

@ -17,6 +17,7 @@ foreach(TEST ${TESTS})
"${CMAKE_SOURCE_DIR}/unittests/gvisor-tests/Expected_Output"
"${CMAKE_SOURCE_DIR}/unittests/gvisor-tests/Disabled_Tests"
"${TEST_NAME}"
"guest"
"${CMAKE_BINARY_DIR}/Bin/FEXLoader"
"--no-silent" "-c" "irjit" "-n" "500" "--"
"${TEST}")