diff --git a/circle.yml b/circle.yml index 5c2d109..2a8fd41 100644 --- a/circle.yml +++ b/circle.yml @@ -15,7 +15,8 @@ jobs: command: cmake --build ~/build - run: name: "Unit tests" - command: ~/build/test/evmc-test + working_directory: ~/build/test + command: ./evmc-test - run: name: "Test" command: cmake --build ~/build --target test diff --git a/include/evmc/loader.h b/include/evmc/loader.h new file mode 100644 index 0000000..595079c --- /dev/null +++ b/include/evmc/loader.h @@ -0,0 +1,24 @@ +/* EVMC: Ethereum Client-VM Connector API. + * Copyright 2018 Pawel Bylica. + * Licensed under the MIT License. See the LICENSE file. + */ + +#pragma once + +#if __cplusplus +extern "C" { +#endif + +enum evmc_loader_error_code +{ + EVMC_LOADER_SUCCESS = 0, + EVMC_LOADER_CANNOT_OPEN, + EVMC_LOADER_SYMBOL_NOT_FOUND, + EVMC_LOADER_INVALID_ARGUMENT, +}; + +struct evmc_instance* evmc_load(const char* filename, enum evmc_loader_error_code* error_code); + +#if __cplusplus +} +#endif diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index af29825..2f751e2 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -3,3 +3,4 @@ # Licensed under the MIT License. See the LICENSE file. add_subdirectory(instructions) +add_subdirectory(loader) diff --git a/lib/loader/CMakeLists.txt b/lib/loader/CMakeLists.txt new file mode 100644 index 0000000..db7c5b3 --- /dev/null +++ b/lib/loader/CMakeLists.txt @@ -0,0 +1,18 @@ +# EVMC: Ethereum Client-VM Connector API. +# Copyright 2018 Pawel Bylica. +# Licensed under the MIT License. See the LICENSE file. + +add_library( + loader STATIC + ${include_dir}/evmc/loader.h + loader.c +) + +add_library(evmc::loader ALIAS loader) +set_target_properties(loader PROPERTIES OUTPUT_NAME evmc-loader) +target_include_directories(loader PUBLIC $$) +if(CMAKE_DL_LIBS) + target_link_libraries(loader INTERFACE ${CMAKE_DL_LIBS}) +endif() + +install(TARGETS loader EXPORT evmcTargets DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/lib/loader/loader.c b/lib/loader/loader.c new file mode 100644 index 0000000..b888cd8 --- /dev/null +++ b/lib/loader/loader.c @@ -0,0 +1,94 @@ +/* EVMC: Ethereum Client-VM Connector API. + * Copyright 2018 Pawel Bylica. + * Licensed under the MIT License. See the LICENSE file. + */ + +#include + +#include +#include + +#include + +#define PATH_MAX_LENGTH 4096 + +#pragma GCC diagnostic ignored "-Wincompatible-pointer-types" + +typedef struct evmc_instance* (*evmc_create_fn)(); + +struct evmc_instance* evmc_load(const char* filename, enum evmc_loader_error_code* error_code) +{ + enum evmc_loader_error_code ec = EVMC_LOADER_SUCCESS; + struct evmc_instance* instance = NULL; + + if (!filename) + { + ec = EVMC_LOADER_INVALID_ARGUMENT; + goto exit; + } + + const size_t length = strlen(filename); + if (length == 0 || length > PATH_MAX_LENGTH) + { + ec = EVMC_LOADER_INVALID_ARGUMENT; + goto exit; + } + + void* handle = dlopen(filename, RTLD_LAZY); + if (!handle) + { + ec = EVMC_LOADER_CANNOT_OPEN; + goto exit; + } + + const char prefix[] = "evmc_create_"; + const size_t prefix_length = strlen(prefix); + char name[sizeof(prefix) + PATH_MAX_LENGTH]; + strcpy(name, prefix); + + const char* sep_pos = strrchr(filename, '/'); + const char* name_pos = sep_pos ? sep_pos + 1 : filename; + + const char lib_prefix[] = "lib"; + const size_t lib_prefix_length = strlen(lib_prefix); + if (strncmp(name_pos, lib_prefix, lib_prefix_length) == 0) + name_pos += lib_prefix_length; + + strncpy(name + prefix_length, name_pos, PATH_MAX_LENGTH); + + char* ext_pos = strrchr(name, '.'); + if (ext_pos) + *ext_pos = 0; + + char* dash_pos = name; + while ((dash_pos = strchr(dash_pos, '-')) != NULL) + *dash_pos++ = '_'; + + const void* symbol = dlsym(handle, name); + if (!symbol) + { + const char* short_name_pos = strrchr(name, '_'); + if (short_name_pos) + { + short_name_pos += 1; + memmove(name + prefix_length, short_name_pos, strlen(short_name_pos) + 1); + symbol = dlsym(handle, name); + } + } + + if (symbol) + { + evmc_create_fn create_fn = (evmc_create_fn)(uintptr_t)symbol; + instance = create_fn(); + } + else + { + dlclose(handle); + ec = EVMC_LOADER_SYMBOL_NOT_FOUND; + } + +exit: + if (error_code) + *error_code = ec; + return instance; +} diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index cdd3b31..6fd2db3 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -2,10 +2,34 @@ # Copyright 2018 Pawel Bylica. # Licensed under the MIT License. See the LICENSE file. +add_library(vm-mock SHARED vm_mock.c) + +if(UNIX) + set(cmd create_symlink) +else() + set(cmd copy) +endif() + +add_custom_command( + TARGET vm-mock POST_BUILD + COMMAND ${CMAKE_COMMAND} -E ${cmd} $ ${CMAKE_SHARED_LIBRARY_PREFIX}aaa${CMAKE_SHARED_LIBRARY_SUFFIX} + COMMAND ${CMAKE_COMMAND} -E ${cmd} $ double_prefix_aaa.evm + COMMAND ${CMAKE_COMMAND} -E ${cmd} $ double-prefix-aaa.evm + COMMAND ${CMAKE_COMMAND} -E ${cmd} $ eee-bbb.dll + COMMAND ${CMAKE_COMMAND} -E ${cmd} $ ${CMAKE_SHARED_LIBRARY_PREFIX}eee1${CMAKE_SHARED_LIBRARY_SUFFIX} + COMMAND ${CMAKE_COMMAND} -E ${cmd} $ eee2${CMAKE_SHARED_LIBRARY_SUFFIX} + COMMAND ${CMAKE_COMMAND} -E ${cmd} $ ${CMAKE_SHARED_LIBRARY_PREFIX}eee3 + COMMAND ${CMAKE_COMMAND} -E ${cmd} $ eee4 + COMMAND ${CMAKE_COMMAND} -E ${cmd} $ ../aaa.evm + COMMAND ${CMAKE_COMMAND} -E touch empty.file +) + add_executable( evmc-test test_instructions.cpp + test_loader.cpp ) -target_link_libraries(evmc-test PRIVATE instructions GTest::gtest GTest::main) +target_link_libraries(evmc-test PRIVATE instructions loader GTest::gtest GTest::main) set_target_properties(evmc-test PROPERTIES RUNTIME_OUTPUT_DIRECTORY ..) +add_dependencies(evmc-test vm-mock) diff --git a/test/unittests/test_loader.cpp b/test/unittests/test_loader.cpp new file mode 100644 index 0000000..b371b91 --- /dev/null +++ b/test/unittests/test_loader.cpp @@ -0,0 +1,176 @@ +// EVMC: Ethereum Client-VM Connector API. +// Copyright 2018 Pawel Bylica. +// Licensed under the MIT License. See the LICENSE file. + +#include + +#include + +TEST(loader, nonexistent) +{ + evmc_loader_error_code ec; + auto x = evmc_load("nonexistent", &ec); + EXPECT_EQ(ec, EVMC_LOADER_CANNOT_OPEN); + EXPECT_EQ(x, nullptr); + + x = evmc_load("nonexistent", nullptr); + EXPECT_EQ(x, nullptr); +} + +TEST(loader, longpath) +{ + std::vector path(5000, 'a'); + *path.rbegin() = 0; + + evmc_loader_error_code ec; + auto x = evmc_load(path.data(), &ec); + EXPECT_EQ(ec, EVMC_LOADER_INVALID_ARGUMENT); + EXPECT_EQ(x, nullptr); + + x = evmc_load(path.data(), nullptr); + EXPECT_EQ(x, nullptr); +} + +TEST(loader, not_so) +{ + auto path = "unittests/empty.file"; + + evmc_loader_error_code ec; + auto x = evmc_load(path, &ec); + EXPECT_EQ(ec, EVMC_LOADER_CANNOT_OPEN); + EXPECT_EQ(x, nullptr); + + x = evmc_load(path, nullptr); + EXPECT_EQ(x, nullptr); +} + +TEST(loader, null_path) +{ + evmc_loader_error_code ec; + auto x = evmc_load(nullptr, &ec); + EXPECT_EQ(ec, EVMC_LOADER_INVALID_ARGUMENT); + EXPECT_EQ(x, nullptr); + + x = evmc_load(nullptr, nullptr); + EXPECT_EQ(x, nullptr); +} + +TEST(loader, empty_path) +{ + evmc_loader_error_code ec; + auto x = evmc_load("", &ec); + EXPECT_EQ(ec, EVMC_LOADER_INVALID_ARGUMENT); + EXPECT_EQ(x, nullptr); + + x = evmc_load("", nullptr); + EXPECT_EQ(x, nullptr); +} + +TEST(loader, aaa) +{ + auto path = "unittests/libaaa.so"; + + evmc_loader_error_code ec; + auto x = (uintptr_t)evmc_load(path, &ec); + EXPECT_EQ(ec, EVMC_LOADER_SUCCESS); + EXPECT_EQ(x, 0xaaa); + + x = (uintptr_t)evmc_load(path, nullptr); + EXPECT_EQ(x, 0xaaa); +} + +TEST(loader, prefix_aaa) +{ + auto paths = {"unittests/double-prefix-aaa.evm", "unittests/double_prefix_aaa.evm"}; + + for (auto& path : paths) + { + evmc_loader_error_code ec; + auto x = (uintptr_t)evmc_load(path, &ec); + EXPECT_EQ(ec, EVMC_LOADER_SUCCESS); + EXPECT_EQ(x, 0xaaa); + + x = (uintptr_t)evmc_load(path, nullptr); + EXPECT_EQ(x, 0xaaa); + } +} + +TEST(loader, eee_bbb) +{ + auto path = "unittests/eee-bbb.dll"; + + evmc_loader_error_code ec; + auto x = (uintptr_t)evmc_load(path, &ec); + EXPECT_EQ(ec, EVMC_LOADER_SUCCESS); + EXPECT_EQ(x, 0xeeebbb); + + x = (uintptr_t)evmc_load(path, nullptr); + EXPECT_EQ(x, 0xeeebbb); +} + +TEST(loader, DISABLED_nextto) +{ + // FIXME: Does not work because dlopen searches only system paths. + + auto path = "aaa.evm"; + + evmc_loader_error_code ec; + auto x = (uintptr_t)evmc_load(path, &ec); + EXPECT_EQ(ec, EVMC_LOADER_SUCCESS); + EXPECT_EQ(x, 0xaaa); + + x = (uintptr_t)evmc_load(path, nullptr); + EXPECT_EQ(x, 0xaaa); +} + +TEST(loader, eee1) +{ + auto path = "unittests/libeee1.so"; + + evmc_loader_error_code ec; + auto x = evmc_load(path, &ec); + EXPECT_EQ(ec, EVMC_LOADER_SYMBOL_NOT_FOUND); + EXPECT_EQ(x, nullptr); + + x = evmc_load(path, nullptr); + EXPECT_EQ(x, nullptr); +} + +TEST(loader, eee2) +{ + auto path = "unittests/eee2.so"; + + evmc_loader_error_code ec; + auto x = evmc_load(path, &ec); + EXPECT_EQ(ec, EVMC_LOADER_SYMBOL_NOT_FOUND); + EXPECT_EQ(x, nullptr); + + x = evmc_load(path, nullptr); + EXPECT_EQ(x, nullptr); +} + +TEST(loader, eee3) +{ + auto path = "unittests/libeee3"; + + evmc_loader_error_code ec; + auto x = evmc_load(path, &ec); + EXPECT_EQ(ec, EVMC_LOADER_SYMBOL_NOT_FOUND); + EXPECT_EQ(x, nullptr); + + x = evmc_load(path, nullptr); + EXPECT_EQ(x, nullptr); +} + +TEST(loader, eee4) +{ + auto path = "unittests/eee4"; + + evmc_loader_error_code ec; + auto x = evmc_load(path, &ec); + EXPECT_EQ(ec, EVMC_LOADER_SYMBOL_NOT_FOUND); + EXPECT_EQ(x, nullptr); + + x = evmc_load(path, nullptr); + EXPECT_EQ(x, nullptr); +} \ No newline at end of file diff --git a/test/unittests/vm_mock.c b/test/unittests/vm_mock.c new file mode 100644 index 0000000..7a1422b --- /dev/null +++ b/test/unittests/vm_mock.c @@ -0,0 +1,14 @@ +/* EVMC: Ethereum Client-VM Connector API. + * Copyright 2018 Pawel Bylica. + * Licensed under the MIT License. See the LICENSE file. + */ + +void* evmc_create_aaa() +{ + return (void*)0xaaa; +} + +void* evmc_create_eee_bbb() +{ + return (void*)0xeeebbb; +}