Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix multi-library loading #8

Merged
merged 13 commits into from
Jan 31, 2024
4 changes: 4 additions & 0 deletions include/mwr/core/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ using std::optional;
bool file_exists(const string& file);
bool directory_exists(const string& file);

bool is_absolute_path(const string& path);
bool is_relative_path(const string& path);
bool is_path_equal(const string& p1, const string& p2);

string dirname(const string& path);
string filename(const string& path);
string filename_noext(const string& path);
Expand Down
3 changes: 3 additions & 0 deletions include/mwr/utils/library.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "mwr/core/types.h"
#include "mwr/core/report.h"
#include "mwr/core/compiler.h"
#include "mwr/core/utils.h"

#include "mwr/stl/strings.h"

Expand All @@ -22,6 +23,7 @@ namespace mwr {
class library
{
private:
string m_name;
string m_path;
string m_copy;
void* m_handle;
Expand All @@ -30,6 +32,7 @@ class library
void* lookup(const string& name) const;

public:
const char* name() const { return m_name.c_str(); }
const char* path() const { return m_path.c_str(); }
bool is_open() const { return m_handle != nullptr; }

Expand Down
12 changes: 12 additions & 0 deletions src/mwr/core/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ bool directory_exists(const string& dir) {
}
}

bool is_absolute_path(const string& path) {
return fs::path(path).is_absolute();
}

bool is_relative_path(const string& path) {
return fs::path(path).is_relative();
}

bool is_path_equal(const string& p1, const string& p2) {
return fs::path(p1) == fs::path(p2);
}

string dirname(const string& path) {
fs::path full(path);
string result = full.parent_path().string();
Expand Down
76 changes: 64 additions & 12 deletions src/mwr/utils/library_posix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,47 @@
#include <filesystem>
#include <dlfcn.h>

#ifdef MWR_LINUX
#include <link.h>
#endif

#ifdef MWR_MACOS
#include <mach-o/dyld.h>
#endif

namespace mwr {

static string library_path(void* handle, const string& name) {
#ifdef MWR_LINUX
struct link_map* map;
if (dlinfo(handle, RTLD_DI_LINKMAP, &map) < 0)
MWR_ERROR("error dlinfo: %s", dlerror());
return map->l_name;
#endif
#ifdef MWR_MACOS
for (u32 i = 0; i < _dyld_image_count(); i++) {
string path = _dyld_get_image_name(i);
if (ends_with(path, name))
return path;
}

MWR_ERROR("cannot find path to library %s", name.c_str());
#endif
}

void* library::lookup(const string& name) const {
void* sym = dlsym(m_handle, name.c_str());
MWR_REPORT_ON(!sym, "error loading %s: %s", name.c_str(), dlerror());
return sym;
}

library::library(): m_path(), m_copy(), m_handle(nullptr), m_keep(false) {
library::library():
m_name(), m_path(), m_copy(), m_handle(nullptr), m_keep(false) {
// nothing to do
}

library::library(library&& other) noexcept:
m_name(std::move(other.m_name)),
m_path(std::move(other.m_path)),
m_copy(std::move(other.m_copy)),
m_handle(other.m_handle),
Expand All @@ -51,24 +79,45 @@ void library::open(const string& path, int mode) {

m_handle = dlopen(path.c_str(), mode);
MWR_REPORT_ON(!m_handle, "failed to open %s: %s", path.c_str(), dlerror());
m_path = path;

if (is_absolute_path(path)) {
m_path = path;
m_name = filename(path);
} else {
m_name = path;
m_path = library_path(m_handle, path);
}
}

void library::mopen(const string& path, int mode) {
if (is_open())
close();

static unordered_map<string, size_t> maps;
size_t& count = maps[path];
if (count > 0) {
m_copy = mkstr("%s-%zu", path.c_str(), count++);
if (!std::filesystem::exists(m_copy))
std::filesystem::copy(path, m_copy);
open(m_copy, mode);
m_path = path;
} else {
string name = path;
if (is_absolute_path(path))
name = filename(name);

struct openinfo {
string path;
size_t copies;
};

static unordered_map<string, openinfo> maps;
openinfo& info = maps[name];

if (info.copies == 0) {
open(path, mode);
count++;
info.path = m_path;
info.copies++;
} else {
string copy = temp_dir() + "/" + name + "-" + to_string(info.copies);
if (!std::filesystem::exists(copy)) {
std::filesystem::copy(info.path, copy);
m_copy = copy;
}

open(copy, mode);
m_name = name;
}
}

Expand All @@ -82,6 +131,9 @@ void library::close() {
std::filesystem::remove(m_copy);
m_copy.clear();
}

m_name.clear();
m_path.clear();
}

bool library::has(const string& name) const {
Expand Down
54 changes: 42 additions & 12 deletions src/mwr/utils/library_win32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,27 @@ static const char* library_strerror(DWORD err = GetLastError()) {
return buffer;
}

static string library_path(void* handle) {
char path[MAX_PATH];
DWORD result = GetModuleFileNameA((HMODULE)handle, path, MAX_PATH);
MWR_ERROR_ON(!result, "GetModuleFileName: %s", library_strerror());
return path;
}

void* library::lookup(const string& name) const {
void* sym = GetProcAddress((HMODULE)m_handle, name.c_str());
MWR_REPORT_ON(!sym, "error loading %s: %s", name.c_str(),
library_strerror());
return sym;
}

library::library(): m_path(), m_copy(), m_handle(nullptr), m_keep(false) {
library::library():
m_name(), m_path(), m_copy(), m_handle(nullptr), m_keep(false) {
// nothing to do
}

library::library(library&& other) noexcept:
m_name(std::move(other.m_name)),
m_path(std::move(other.m_path)),
m_copy(std::move(other.m_copy)),
m_handle(other.m_handle),
Expand All @@ -62,24 +71,45 @@ void library::open(const string& path, int mode) {
m_handle = LoadLibraryA(path.c_str());
MWR_REPORT_ON(!m_handle, "failed to open dll at %s: %s", path.c_str(),
library_strerror());
m_path = path;

if (is_absolute_path(path)) {
m_path = path;
m_name = filename(path);
} else {
m_name = path;
m_path = library_path(m_handle);
}
}

void library::mopen(const string& path, int mode) {
if (is_open())
close();

static unordered_map<string, size_t> maps;
size_t& count = maps[path];
if (count > 0) {
m_copy = mkstr("%s-%zu", path.c_str(), count++);
if (!std::filesystem::exists(m_copy))
std::filesystem::copy(path, m_copy);
open(m_copy, mode);
m_path = path;
} else {
string name = path;
if (is_absolute_path(path))
name = filename(name);

struct openinfo {
string path;
size_t copies;
};

static unordered_map<string, openinfo> maps;
openinfo& info = maps[name];

if (info.copies == 0) {
open(path, mode);
count++;
info.path = m_path;
info.copies++;
} else {
string copy = temp_dir() + "/" + name + "-" + to_string(info.copies);
if (!std::filesystem::exists(copy)) {
std::filesystem::copy(info.path, copy);
m_copy = copy;
}

open(copy, mode);
m_name = name;
}
}

Expand Down
16 changes: 12 additions & 4 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,20 @@ endif()
set(resources ${CMAKE_CURRENT_SOURCE_DIR}/resources)

if(CMAKE_BUILD_TYPE MATCHES "ASAN")
string(APPEND ENVVARS "ASAN_OPTIONS=suppressions=${CMAKE_CURRENT_SOURCE_DIR}/sanitizer/asan.suppress:detect_leaks=1:fast_unwind_on_malloc=0;")
string(APPEND ENVVARS "LSAN_OPTIONS=suppressions=${CMAKE_CURRENT_SOURCE_DIR}/sanitizer/lsan.suppress;")
string(APPEND MWR_ENVVAR "ASAN_OPTIONS=suppressions=${CMAKE_CURRENT_SOURCE_DIR}/sanitizer/asan.suppress:detect_leaks=1:fast_unwind_on_malloc=0;")
string(APPEND MWR_ENVVAR "LSAN_OPTIONS=suppressions=${CMAKE_CURRENT_SOURCE_DIR}/sanitizer/lsan.suppress;")
elseif(CMAKE_BUILD_TYPE MATCHES "TSAN")
string(APPEND ENVVARS "TSAN_OPTIONS=suppressions=${CMAKE_CURRENT_SOURCE_DIR}/sanitizer/tsan.suppress;")
string(APPEND MWR_ENVVAR "TSAN_OPTIONS=suppressions=${CMAKE_CURRENT_SOURCE_DIR}/sanitizer/tsan.suppress;")
elseif(CMAKE_BUILD_TYPE MATCHES "UBSAN")
string(APPEND ENVVARS "UBSAN_OPTIONS=suppressions=${CMAKE_CURRENT_SOURCE_DIR}/sanitizer/ubsan.suppress:print_stacktrace=1;")
string(APPEND MWR_ENVVAR "UBSAN_OPTIONS=suppressions=${CMAKE_CURRENT_SOURCE_DIR}/sanitizer/ubsan.suppress:print_stacktrace=1;")
endif()

if(APPLE)
string(APPEND MWR_ENVVAR "DYLD_LIBRARY_PATH=${resources};")
elseif(WIN32)
string(APPEND MWR_ENVVAR "PATH=${resources};")
else()
string(APPEND MWR_ENVVAR "LD_LIBRARY_PATH=${resources};")
endif()

add_subdirectory(core)
Expand Down
24 changes: 22 additions & 2 deletions test/utils/library.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ using namespace mwr;
#error Unknown architecture
#endif

#define SHARED_NAME "shared-" SHARED_ARCH SHARED_SUFFIX

static string get_test_library() {
return get_resource_path("shared-" SHARED_ARCH SHARED_SUFFIX);
return get_resource_path(SHARED_NAME);
}

TEST(library, basic) {
Expand All @@ -44,6 +46,7 @@ TEST(library, basic) {
ASSERT_NO_THROW(lib.open(path));
ASSERT_TRUE(lib.is_open());
EXPECT_EQ(lib.path(), path);
EXPECT_STREQ(lib.name(), SHARED_NAME);
EXPECT_TRUE(lib.has("global"));
EXPECT_TRUE(lib.has("function"));
EXPECT_FALSE(lib.has("notfound"));
Expand Down Expand Up @@ -93,7 +96,8 @@ TEST(libary, mopen) {

ASSERT_NO_THROW(a.mopen(path));
ASSERT_NO_THROW(b.mopen(path));
EXPECT_STREQ(a.path(), b.path());
EXPECT_STREQ(a.name(), b.name());
EXPECT_STRNE(a.path(), b.path());

ASSERT_TRUE(a.has("global"));
ASSERT_TRUE(b.has("global"));
Expand All @@ -106,4 +110,20 @@ TEST(libary, mopen) {
ASSERT_TRUE(a_global);
ASSERT_TRUE(b_global);
EXPECT_NE(a_global, b_global);

string path_a = a.path();
string path_b = b.path();

a.close();
b.close();

EXPECT_TRUE(file_exists(path_a)); // original must not be deleted
EXPECT_FALSE(file_exists(path_b));
}

TEST(libary, relpath) {
library lib;
lib.open(SHARED_NAME);
EXPECT_STREQ(lib.name(), SHARED_NAME);
EXPECT_TRUE(is_path_equal(lib.path(), get_test_library()));
}
Loading