Add evmc::loader library to support dynamic loading

This commit is contained in:
Paweł Bylica 2018-07-05 01:10:33 +02:00
parent b3755f2a1d
commit 02b52e2926
No known key found for this signature in database
GPG Key ID: 7A0C037434FE77EF
8 changed files with 354 additions and 2 deletions

View File

@ -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

24
include/evmc/loader.h Normal file
View File

@ -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

View File

@ -3,3 +3,4 @@
# Licensed under the MIT License. See the LICENSE file.
add_subdirectory(instructions)
add_subdirectory(loader)

18
lib/loader/CMakeLists.txt Normal file
View File

@ -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 $<BUILD_INTERFACE:${include_dir}>$<INSTALL_INTERFACE:include>)
if(CMAKE_DL_LIBS)
target_link_libraries(loader INTERFACE ${CMAKE_DL_LIBS})
endif()
install(TARGETS loader EXPORT evmcTargets DESTINATION ${CMAKE_INSTALL_LIBDIR})

94
lib/loader/loader.c Normal file
View File

@ -0,0 +1,94 @@
/* EVMC: Ethereum Client-VM Connector API.
* Copyright 2018 Pawel Bylica.
* Licensed under the MIT License. See the LICENSE file.
*/
#include <evmc/loader.h>
#include <stdint.h>
#include <string.h>
#include <dlfcn.h>
#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;
}

View File

@ -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} $<TARGET_FILE_NAME:vm-mock> ${CMAKE_SHARED_LIBRARY_PREFIX}aaa${CMAKE_SHARED_LIBRARY_SUFFIX}
COMMAND ${CMAKE_COMMAND} -E ${cmd} $<TARGET_FILE_NAME:vm-mock> double_prefix_aaa.evm
COMMAND ${CMAKE_COMMAND} -E ${cmd} $<TARGET_FILE_NAME:vm-mock> double-prefix-aaa.evm
COMMAND ${CMAKE_COMMAND} -E ${cmd} $<TARGET_FILE_NAME:vm-mock> eee-bbb.dll
COMMAND ${CMAKE_COMMAND} -E ${cmd} $<TARGET_FILE_NAME:vm-mock> ${CMAKE_SHARED_LIBRARY_PREFIX}eee1${CMAKE_SHARED_LIBRARY_SUFFIX}
COMMAND ${CMAKE_COMMAND} -E ${cmd} $<TARGET_FILE_NAME:vm-mock> eee2${CMAKE_SHARED_LIBRARY_SUFFIX}
COMMAND ${CMAKE_COMMAND} -E ${cmd} $<TARGET_FILE_NAME:vm-mock> ${CMAKE_SHARED_LIBRARY_PREFIX}eee3
COMMAND ${CMAKE_COMMAND} -E ${cmd} $<TARGET_FILE_NAME:vm-mock> eee4
COMMAND ${CMAKE_COMMAND} -E ${cmd} $<TARGET_FILE:vm-mock> ../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)

View File

@ -0,0 +1,176 @@
// EVMC: Ethereum Client-VM Connector API.
// Copyright 2018 Pawel Bylica.
// Licensed under the MIT License. See the LICENSE file.
#include <evmc/loader.h>
#include <gtest/gtest.h>
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<char> 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);
}

14
test/unittests/vm_mock.c Normal file
View File

@ -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;
}