mirror of
https://github.com/status-im/evmc.git
synced 2025-02-22 16:08:22 +00:00
Add evmc::loader library to support dynamic loading
This commit is contained in:
parent
b3755f2a1d
commit
02b52e2926
@ -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
24
include/evmc/loader.h
Normal 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
|
@ -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
18
lib/loader/CMakeLists.txt
Normal 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
94
lib/loader/loader.c
Normal 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;
|
||||
}
|
@ -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)
|
||||
|
176
test/unittests/test_loader.cpp
Normal file
176
test/unittests/test_loader.cpp
Normal 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
14
test/unittests/vm_mock.c
Normal 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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user