Skip to content

Commit aec3da4

Browse files
authored
Improve shared library relative paths handling (#320)
Signed-off-by: Michel Hidalgo <[email protected]>
1 parent b794f1d commit aec3da4

File tree

3 files changed

+210
-44
lines changed

3 files changed

+210
-44
lines changed

CMakeLists.txt

+53-10
Original file line numberDiff line numberDiff line change
@@ -372,20 +372,63 @@ if(BUILD_TESTING)
372372
target_link_libraries(test_repl_str ${PROJECT_NAME})
373373
endif()
374374

375-
set(append_library_dirs "$<TARGET_FILE_DIR:${PROJECT_NAME}>")
376-
377-
ament_add_gtest(test_shared_library test/test_shared_library.cpp
378-
APPEND_LIBRARY_DIRS "${append_library_dirs}"
379-
)
380-
381-
if(TARGET test_shared_library)
382-
add_library(dummy_shared_library test/dummy_shared_library/dummy_shared_library.c)
375+
macro(add_dummy_shared_library target)
376+
add_library(${target} test/dummy_shared_library/dummy_shared_library.c)
383377
if(WIN32)
384378
# Causes the visibility macros to use dllexport rather than dllimport
385379
# which is appropriate when building the dll but not consuming it.
386-
target_compile_definitions(dummy_shared_library PRIVATE "DUMMY_SHARED_LIBRARY_BUILDING_DLL")
380+
target_compile_definitions(${target} PRIVATE "DUMMY_SHARED_LIBRARY_BUILDING_DLL")
381+
endif()
382+
endmacro()
383+
384+
ament_add_gtest(test_shared_library_in_run_paths test/test_shared_library.cpp)
385+
if(TARGET test_shared_library_in_run_paths)
386+
# Rely on CMake setting build tree RUNPATHs by default on Unix systems.
387+
add_dummy_shared_library(dummy_shared_library_in_run_paths)
388+
target_compile_definitions(test_shared_library_in_run_paths PRIVATE
389+
"SHARED_LIBRARY_UNDER_TEST=dummy_shared_library_in_run_paths")
390+
if(NOT WIN32 AND NOT APPLE)
391+
# NOTE(hidmic): DT_RUNPATH entries are ignored by dlopen in Linux despite the fact
392+
# documentation says otherwise, so here we fallback to DT_RPATH entries.
393+
target_link_libraries(test_shared_library_in_run_paths "-Wl,--disable-new-dtags")
394+
endif()
395+
target_link_libraries(test_shared_library_in_run_paths ${PROJECT_NAME} mimick)
396+
endif()
397+
398+
set(project_binary_dir "$<TARGET_FILE_DIR:${PROJECT_NAME}>")
399+
set(test_libraries_dir "$<TARGET_FILE_DIR:${PROJECT_NAME}>/test_libraries")
400+
ament_add_gtest(test_shared_library_in_load_paths test/test_shared_library.cpp
401+
APPEND_LIBRARY_DIRS ${project_binary_dir} ${test_libraries_dir}
402+
)
403+
if(TARGET test_shared_library_in_load_paths)
404+
# Make sure CMake adds no RPATHs/RUNPATHs.
405+
set_target_properties(test_shared_library_in_load_paths
406+
PROPERTIES SKIP_BUILD_RPATH TRUE)
407+
add_dummy_shared_library(dummy_shared_library_in_load_paths)
408+
set_target_properties(dummy_shared_library_in_load_paths PROPERTIES
409+
LIBRARY_OUTPUT_DIRECTORY ${test_libraries_dir})
410+
target_compile_definitions(test_shared_library_in_load_paths PRIVATE
411+
"SHARED_LIBRARY_UNDER_TEST=dummy_shared_library_in_load_paths")
412+
target_link_libraries(test_shared_library_in_load_paths ${PROJECT_NAME} mimick)
413+
endif()
414+
415+
ament_add_gtest(test_shared_library_preloaded test/test_shared_library.cpp)
416+
if(TARGET test_shared_library_in_load_paths)
417+
add_dummy_shared_library(dummy_shared_library_preloaded)
418+
set_target_properties(dummy_shared_library_preloaded PROPERTIES
419+
LIBRARY_OUTPUT_DIRECTORY ${test_libraries_dir})
420+
target_compile_definitions(test_shared_library_preloaded PRIVATE
421+
"SHARED_LIBRARY_UNDER_TEST=dummy_shared_library_preloaded")
422+
if(NOT WIN32)
423+
# Force (apparently) unused libraries to be linked in.
424+
if(CMAKE_CXX_COMPILER_ID MATCHES "(C|c)lang")
425+
target_link_libraries(test_shared_library_preloaded "-Wl,-all_load")
426+
else()
427+
target_link_libraries(test_shared_library_preloaded "-Wl,--no-as-needed")
428+
endif()
387429
endif()
388-
target_link_libraries(test_shared_library ${PROJECT_NAME} mimick)
430+
target_link_libraries(test_shared_library_preloaded dummy_shared_library_preloaded)
431+
target_link_libraries(test_shared_library_preloaded ${PROJECT_NAME} mimick)
389432
endif()
390433

391434
rcutils_custom_add_gtest(test_time

src/shared_library.c

+115-14
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ extern "C"
2020
#include <stdlib.h>
2121

2222
#ifndef _WIN32
23+
#if defined(__APPLE__)
24+
#include <mach-o/dyld.h>
25+
#elif defined (_GNU_SOURCE)
26+
#include <link.h>
27+
#endif
2328
#include <dlfcn.h>
2429
#else
2530
// When building with MSVC 19.28.29333.0 on Windows 10 (as of 2020-11-11),
@@ -35,6 +40,10 @@ extern "C"
3540
#include <windows.h>
3641
#pragma warning(pop)
3742
C_ASSERT(sizeof(void *) == sizeof(HINSTANCE));
43+
#ifdef UNICODE
44+
#error "rcutils does not support Unicode paths"
45+
#endif
46+
C_ASSERT(sizeof(char) == sizeof(TCHAR));
3847
#endif // _WIN32
3948

4049
#include "rcutils/error_handling.h"
@@ -65,33 +74,125 @@ rcutils_load_shared_library(
6574
RCUTILS_CHECK_ARGUMENT_FOR_NULL(lib, RCUTILS_RET_INVALID_ARGUMENT);
6675
RCUTILS_CHECK_ARGUMENT_FOR_NULL(library_path, RCUTILS_RET_INVALID_ARGUMENT);
6776
RCUTILS_CHECK_ALLOCATOR(&allocator, return RCUTILS_RET_INVALID_ARGUMENT);
68-
69-
if (lib->library_path != NULL) {
70-
lib->allocator.deallocate(lib->library_path, lib->allocator.state);
77+
if (NULL != lib->lib_pointer) {
78+
RCUTILS_SET_ERROR_MSG("lib argument is not zero-initialized");
79+
return RCUTILS_RET_INVALID_ARGUMENT;
7180
}
7281

82+
rcutils_ret_t ret = RCUTILS_RET_OK;
7383
lib->allocator = allocator;
7484

85+
// Delegate library path resolution to dynamic linker.
86+
87+
// Since the given library path might be relative, let the dynamic
88+
// linker search for the binary object in all standard locations and
89+
// current process space (if already loaded) first.
90+
// Then lookup its full path with the handle obtained.
91+
// See POSIX dlopen() API and Windows' LoadLibrary() API documentation
92+
// for further reference.
93+
94+
#ifndef _WIN32
95+
lib->lib_pointer = dlopen(library_path, RTLD_LAZY);
96+
if (NULL == lib->lib_pointer) {
97+
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING("dlopen error: %s", dlerror());
98+
return RCUTILS_RET_ERROR;
99+
}
100+
101+
#if defined(__APPLE__)
102+
const char * image_name = NULL;
103+
uint32_t image_count = _dyld_image_count();
104+
for (uint32_t i = 0; NULL == image_name && i < image_count; ++i) {
105+
// Iterate in reverse as the library is likely near the end of the list.
106+
const char * candidate_name = _dyld_get_image_name(image_count - i - 1);
107+
if (NULL == candidate_name) {
108+
RCUTILS_SET_ERROR_MSG("dyld image index out of range");
109+
ret = RCUTILS_RET_ERROR;
110+
goto fail;
111+
}
112+
void * handle = dlopen(candidate_name, RTLD_LAZY | RTLD_NOLOAD);
113+
if (handle == lib->lib_pointer) {
114+
image_name = candidate_name;
115+
}
116+
if (dlclose(handle) != 0) {
117+
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING("dlclose error: %s", dlerror());
118+
ret = RCUTILS_RET_ERROR;
119+
goto fail;
120+
}
121+
}
122+
if (NULL == image_name) {
123+
RCUTILS_SET_ERROR_MSG("dyld image name could not be found");
124+
ret = RCUTILS_RET_ERROR;
125+
goto fail;
126+
}
127+
lib->library_path = rcutils_strdup(image_name, lib->allocator);
128+
#elif defined(_GNU_SOURCE)
129+
struct link_map * map = NULL;
130+
if (dlinfo(lib->lib_pointer, RTLD_DI_LINKMAP, &map) != 0) {
131+
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING("dlinfo error: %s", dlerror());
132+
ret = RCUTILS_RET_ERROR;
133+
goto fail;
134+
}
135+
lib->library_path = rcutils_strdup(map->l_name, lib->allocator);
136+
#else
75137
lib->library_path = rcutils_strdup(library_path, lib->allocator);
138+
#endif
76139
if (NULL == lib->library_path) {
77140
RCUTILS_SET_ERROR_MSG("unable to allocate memory");
78-
return RCUTILS_RET_BAD_ALLOC;
141+
ret = RCUTILS_RET_BAD_ALLOC;
142+
goto fail;
79143
}
80144

81-
#ifndef _WIN32
82-
lib->lib_pointer = dlopen(lib->library_path, RTLD_LAZY);
83-
if (!lib->lib_pointer) {
84-
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING("LoadLibrary error: %s", dlerror());
145+
return RCUTILS_RET_OK;
146+
fail:
147+
if (dlclose(lib->lib_pointer) != 0) {
148+
RCUTILS_SAFE_FWRITE_TO_STDERR_WITH_FORMAT_STRING(
149+
"dlclose error: %s\n", dlerror());
150+
}
151+
lib->lib_pointer = NULL;
152+
return ret;
85153
#else
86-
lib->lib_pointer = (void *)(LoadLibrary(lib->library_path));
87-
if (!lib->lib_pointer) {
88-
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING("LoadLibrary error: %lu", GetLastError());
89-
#endif // _WIN32
90-
lib->allocator.deallocate(lib->library_path, lib->allocator.state);
91-
lib->library_path = NULL;
154+
HMODULE module = LoadLibrary(library_path);
155+
if (!module) {
156+
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(
157+
"LoadLibrary error: %lu", GetLastError());
92158
return RCUTILS_RET_ERROR;
93159
}
160+
161+
for (DWORD buffer_capacity = MAX_PATH; ; buffer_capacity *= 2) {
162+
LPSTR buffer = lib->allocator.allocate(buffer_capacity, lib->allocator.state);
163+
if (NULL == buffer) {
164+
RCUTILS_SET_ERROR_MSG("unable to allocate memory");
165+
ret = RCUTILS_RET_BAD_ALLOC;
166+
goto fail;
167+
}
168+
DWORD buffer_size = GetModuleFileName(module, buffer, buffer_capacity);
169+
if (0 == buffer_size) {
170+
RCUTILS_SET_ERROR_MSG_WITH_FORMAT_STRING(
171+
"GetModuleFileName error: %lu", GetLastError());
172+
ret = RCUTILS_RET_ERROR;
173+
goto fail;
174+
}
175+
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
176+
lib->allocator.deallocate(buffer, lib->allocator.state);
177+
continue;
178+
}
179+
lib->library_path = lib->allocator.reallocate(
180+
buffer, buffer_size, lib->allocator.state);
181+
if (NULL == lib->library_path) {
182+
lib->library_path = buffer;
183+
}
184+
break;
185+
}
186+
lib->lib_pointer = (void *)module;
187+
94188
return RCUTILS_RET_OK;
189+
fail:
190+
if (!FreeLibrary(module)) {
191+
RCUTILS_SAFE_FWRITE_TO_STDERR_WITH_FORMAT_STRING(
192+
"FreeLibrary error: %lu\n", GetLastError());
193+
}
194+
return ret;
195+
#endif // _WIN32
95196
}
96197

97198
void *

test/test_shared_library.cpp

+42-20
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,17 @@ TEST_F(TestSharedLibrary, basic_load) {
4949

5050
// Check debug name works first because rcutils_load_shared_library should be called on
5151
// non-debug symbol name
52-
ret = rcutils_get_platform_library_name("dummy_shared_library", library_path, 1024, true);
52+
ret = rcutils_get_platform_library_name(
53+
RCUTILS_STRINGIFY(SHARED_LIBRARY_UNDER_TEST), library_path, 1024, true);
5354
ASSERT_EQ(RCUTILS_RET_OK, ret);
5455

55-
ret = rcutils_get_platform_library_name("dummy_shared_library", library_path, 1024, false);
56+
ret = rcutils_get_platform_library_name(
57+
RCUTILS_STRINGIFY(SHARED_LIBRARY_UNDER_TEST), library_path, 1024, false);
5658
ASSERT_EQ(RCUTILS_RET_OK, ret);
5759

5860
// getting shared library
5961
ret = rcutils_load_shared_library(&lib, library_path, rcutils_get_default_allocator());
60-
ASSERT_EQ(RCUTILS_RET_OK, ret);
62+
ASSERT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str;
6163
EXPECT_TRUE(rcutils_is_shared_library_loaded(&lib));
6264

6365
// unload shared_library
@@ -80,7 +82,8 @@ TEST_F(TestSharedLibrary, bad_load) {
8082

8183
TEST_F(TestSharedLibrary, fail_allocator) {
8284
rcutils_ret_t ret;
83-
ret = rcutils_get_platform_library_name("dummy_shared_library", library_path, 1024, false);
85+
ret = rcutils_get_platform_library_name(
86+
RCUTILS_STRINGIFY(SHARED_LIBRARY_UNDER_TEST), library_path, 1024, false);
8487
ASSERT_EQ(RCUTILS_RET_OK, ret);
8588

8689
rcutils_allocator_t failing_allocator = get_failing_allocator();
@@ -91,46 +94,63 @@ TEST_F(TestSharedLibrary, fail_allocator) {
9194
TEST_F(TestSharedLibrary, load_two_times) {
9295
rcutils_ret_t ret;
9396

94-
ret = rcutils_get_platform_library_name("dummy_shared_library", library_path, 1024, false);
97+
ret = rcutils_get_platform_library_name(
98+
RCUTILS_STRINGIFY(SHARED_LIBRARY_UNDER_TEST), library_path, 1024, false);
9599
ASSERT_EQ(RCUTILS_RET_OK, ret);
96100

97101
// getting shared library
98102
ret = rcutils_load_shared_library(&lib, library_path, rcutils_get_default_allocator());
99-
ASSERT_EQ(RCUTILS_RET_OK, ret);
103+
EXPECT_EQ(RCUTILS_RET_OK, ret);
100104

101105
// getting shared library
102-
ret = rcutils_load_shared_library(&lib, library_path, rcutils_get_default_allocator());
103-
ASSERT_EQ(RCUTILS_RET_OK, ret);
106+
rcutils_shared_library_t clone = rcutils_get_zero_initialized_shared_library();
107+
ret = rcutils_load_shared_library(&clone, library_path, rcutils_get_default_allocator());
108+
EXPECT_EQ(RCUTILS_RET_OK, ret);
109+
110+
// unload shared_library
111+
ret = rcutils_unload_shared_library(&clone);
112+
EXPECT_EQ(RCUTILS_RET_OK, ret);
104113

105114
// unload shared_library
106115
ret = rcutils_unload_shared_library(&lib);
107-
ASSERT_EQ(RCUTILS_RET_OK, ret);
116+
EXPECT_EQ(RCUTILS_RET_OK, ret);
108117
}
109118

110119
TEST_F(TestSharedLibrary, error_load) {
111120
rcutils_ret_t ret;
112121

113-
rcutils_shared_library_t lib_empty;
114-
ret = rcutils_load_shared_library(&lib_empty, NULL, rcutils_get_zero_initialized_allocator());
115-
ASSERT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret);
116-
117-
lib_empty = rcutils_get_zero_initialized_shared_library();
118-
ret = rcutils_load_shared_library(&lib_empty, NULL, rcutils_get_zero_initialized_allocator());
122+
ret = rcutils_load_shared_library(&lib, NULL, rcutils_get_default_allocator());
119123
ASSERT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret);
124+
rcutils_reset_error();
120125

121-
ret = rcutils_get_platform_library_name("dummy_shared_library", library_path, 1024, false);
126+
ret = rcutils_get_platform_library_name(
127+
RCUTILS_STRINGIFY(SHARED_LIBRARY_UNDER_TEST), library_path, 1024, false);
122128
ASSERT_EQ(RCUTILS_RET_OK, ret);
123129

124130
ret = rcutils_load_shared_library(
125-
&lib_empty,
131+
&lib,
126132
library_path, rcutils_get_zero_initialized_allocator());
127133
ASSERT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret);
134+
rcutils_reset_error();
135+
136+
ret = rcutils_load_shared_library(
137+
&lib, library_path, rcutils_get_default_allocator());
138+
ASSERT_EQ(RCUTILS_RET_OK, ret);
139+
140+
ret = rcutils_load_shared_library(
141+
&lib, library_path, rcutils_get_default_allocator());
142+
EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret);
143+
rcutils_reset_error();
144+
145+
ret = rcutils_unload_shared_library(&lib);
146+
EXPECT_EQ(RCUTILS_RET_OK, ret);
128147
}
129148

130149
TEST_F(TestSharedLibrary, error_unload) {
131150
rcutils_ret_t ret;
132151

133-
ret = rcutils_get_platform_library_name("dummy_shared_library", library_path, 1024, false);
152+
ret = rcutils_get_platform_library_name(
153+
RCUTILS_STRINGIFY(SHARED_LIBRARY_UNDER_TEST), library_path, 1024, false);
134154
ASSERT_EQ(RCUTILS_RET_OK, ret);
135155

136156
ret = rcutils_load_shared_library(&lib, library_path, rcutils_get_default_allocator());
@@ -153,7 +173,8 @@ TEST_F(TestSharedLibrary, error_symbol) {
153173
void * symbol = rcutils_get_symbol(&lib, "print_name");
154174
EXPECT_TRUE(symbol == NULL);
155175

156-
ret = rcutils_get_platform_library_name("dummy_shared_library", library_path, 1024, false);
176+
ret = rcutils_get_platform_library_name(
177+
RCUTILS_STRINGIFY(SHARED_LIBRARY_UNDER_TEST), library_path, 1024, false);
157178
ASSERT_EQ(RCUTILS_RET_OK, ret);
158179
ret = rcutils_load_shared_library(&lib, library_path, rcutils_get_default_allocator());
159180
ASSERT_EQ(RCUTILS_RET_OK, ret);
@@ -177,7 +198,8 @@ TEST_F(TestSharedLibrary, basic_symbol) {
177198
ret = rcutils_has_symbol(nullptr, "symbol");
178199
EXPECT_FALSE(ret);
179200

180-
ret = rcutils_get_platform_library_name("dummy_shared_library", library_path, 1024, false);
201+
ret = rcutils_get_platform_library_name(
202+
RCUTILS_STRINGIFY(SHARED_LIBRARY_UNDER_TEST), library_path, 1024, false);
181203
ASSERT_EQ(RCUTILS_RET_OK, ret);
182204

183205
// getting shared library

0 commit comments

Comments
 (0)