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

improve weak link handling #3

Merged
merged 4 commits into from
Jul 16, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
270 changes: 3 additions & 267 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,271 +7,7 @@ project(weak_linking_example C)
include(CTest)
enable_testing()

if(NOT DEFINED CAN_DOUBLE_LINK)
set(TC_SRC_DIR
"${CMAKE_BINARY_DIR}/try_compile_src/double-link")
set(TC_BIN_DIR
"${CMAKE_BINARY_DIR}/try_compile_build/double-link")

file(MAKE_DIRECTORY ${TC_SRC_DIR})
file(MAKE_DIRECTORY ${TC_BIN_DIR})

file(WRITE "${TC_SRC_DIR}/CMakeLists.txt" "
cmake_minimum_required(VERSION ${CMAKE_VERSION})
include_directories(${TC_SRC_DIR})
project(double_link_try C)

add_library(number SHARED number.c)

add_library(counter MODULE counter.c)
set_target_properties(counter PROPERTIES PREFIX \"\")
target_link_libraries(counter number)

add_executable(main main.c)
target_link_libraries(main number)
target_link_libraries(main ${CMAKE_DL_LIBS})

install(TARGETS number LIBRARY DESTINATION \"${TC_BIN_DIR}\")
install(TARGETS counter LIBRARY DESTINATION \"${TC_BIN_DIR}\")
install(FILES main DESTINATION \"${TC_BIN_DIR}\")
")

file(WRITE "${TC_SRC_DIR}/number.c" "
#include <number.h>

static int _number;
void set_number(int number) { _number = number; }
int get_number() { return _number; }
")

file(WRITE "${TC_SRC_DIR}/number.h" "
#ifndef _NUMBER_H
#define _NUMBER_H
extern void set_number(int);
extern int get_number(void);
#endif
")

file(WRITE "${TC_SRC_DIR}/counter.c" "
#include <number.h>
int count() {
int result = get_number();
set_number(result + 1);
return result;
}
")

file(WRITE "${TC_SRC_DIR}/counter.h" "
#ifndef _COUNTER_H
#define _COUNTER_H
extern int count(void);
#endif
")

file(WRITE "${TC_SRC_DIR}/main.c" "
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>

int my_count() {
int result = get_number();
set_number(result + 1);
return result;
}

int main(int argc, char **argv) {
void *counter_module;
int (*count)(void);
int result;

counter_module = dlopen(\"./counter.so\", RTLD_LAZY);
if(!counter_module) goto error;

count = dlsym(counter_module, \"count\");
if(!count) goto error;

result = count() != 0 ? 1 :
my_count() != 1 ? 1 :
my_count() != 2 ? 1 :
count() != 3 ? 1 :
count() != 4 ? 1 :
count() != 5 ? 1 :
my_count() != 6 ? 1 : 0;


goto done;
error:
fprintf(stderr, \"Error occured:\\n %s\\n\", dlerror());
result = 1;

done:
if(counter_module) dlclose(counter_module);
return result;
}
")

try_compile(double_link_compiles
"${TC_BIN_DIR}"
"${TC_SRC_DIR}"
"double_link_try")

set(double_link_works 1)
if(double_link_compiles)
execute_process(COMMAND "${TC_BIN_DIR}/main"
WORKING_DIRECTORY "${TC_BIN_DIR}"
RESULT_VARIABLE double_link_works)
endif()

if(double_link_works EQUAL 0)
set(double_link_works TRUE)
else()
set(double_link_works FALSE)
endif()

set(CAN_DOUBLE_LINK
${double_link_works} CACHE BOOL "ok to double link")
endif()


IF(NOT DEFINED HAS_DYNAMIC_LOOKUP)
set(TC_SRC_DIR
"${CMAKE_BINARY_DIR}/try_compile_src/dynamic-lookup")
set(TC_BIN_DIR
"${CMAKE_BINARY_DIR}/try_compile_build/dynamic-lookup")

file(MAKE_DIRECTORY ${TC_SRC_DIR})
file(MAKE_DIRECTORY ${TC_BIN_DIR})

file(WRITE "${TC_SRC_DIR}/CMakeLists.txt" "
cmake_minimum_required(VERSION ${CMAKE_VERSION})
include_directories(${TC_SRC_DIR})
project(dynamic_lookup_try C)

add_library(number SHARED number.c)

add_library(counter MODULE counter.c)
set_target_properties(counter PROPERTIES PREFIX \"\")
set_target_properties(
counter
PROPERTIES LINK_FLAGS \"-undefined dynamic_lookup\")

add_executable(main main.c)
target_link_libraries(main number)
target_link_libraries(main ${CMAKE_DL_LIBS})

install(TARGETS number LIBRARY DESTINATION \"${TC_BIN_DIR}\")
install(TARGETS counter LIBRARY DESTINATION \"${TC_BIN_DIR}\")
install(FILES main DESTINATION \"${TC_BIN_DIR}\")
")

file(WRITE "${TC_SRC_DIR}/number.c" "
#include <number.h>

static int _number;
void set_number(int number) { _number = number; }
int get_number() { return _number; }
")

file(WRITE "${TC_SRC_DIR}/number.h" "
#ifndef _NUMBER_H
#define _NUMBER_H
extern void set_number(int);
extern int get_number(void);
#endif
")

file(WRITE "${TC_SRC_DIR}/counter.c" "
#include <number.h>
int count() {
int result = get_number();
set_number(result + 1);
return result;
}
")

file(WRITE "${TC_SRC_DIR}/counter.h" "
#ifndef _COUNTER_H
#define _COUNTER_H
extern int count(void);
#endif
")

file(WRITE "${TC_SRC_DIR}/main.c" "
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>

int my_count() {
int result = get_number();
set_number(result + 1);
return result;
}

int main(int argc, char **argv) {
void *counter_module;
int (*count)(void);
int result;

counter_module = dlopen(\"./counter.so\", RTLD_LAZY);
if(!counter_module) goto error;

count = dlsym(counter_module, \"count\");
if(!count) goto error;

result = count() != 0 ? 1 :
my_count() != 1 ? 1 :
my_count() != 2 ? 1 :
count() != 3 ? 1 :
count() != 4 ? 1 :
count() != 5 ? 1 :
my_count() != 6 ? 1 : 0;


goto done;
error:
fprintf(stderr, \"Error occured:\\n %s\\n\", dlerror());
result = 1;

done:
if(counter_module) dlclose(counter_module);
return result;
}
")

try_compile(dynamic_lookup_compiles
"${TC_BIN_DIR}"
"${TC_SRC_DIR}"
"dynamic_lookup_try")

set(dynamic_lookup_works 1)
if(dynamic_lookup_compiles)
execute_process(COMMAND "${TC_BIN_DIR}/main"
WORKING_DIRECTORY "${TC_BIN_DIR}"
RESULT_VARIABLE dynamic_lookup_works)
endif()

if(dynamic_lookup_works EQUAL 0)
set(dynamic_lookup_works TRUE)
else()
set(dynamic_lookup_works FALSE)
endif()

set(HAS_DYNAMIC_LOOKUP ${dynamic_lookup_works}
CACHE BOOL "linker supports dynamic_lookup")
endif()


function(target_weak_link_libraries target)
if(HAS_DYNAMIC_LOOKUP)
set_target_properties(${target}
PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
elseif(CAN_DOUBLE_LINK)
target_link_libraries(${target} ${ARGN})
else()
message(FATAL_ERROR "Cannot weak link target: ${target}")
endif()
endfunction()

include(targetLinkLibrariesWithDynamicLookup.cmake)

set(LIB_TYPE SHARED CACHE STRING "library type")
set(WEAK_LINK_MODULE TRUE CACHE BOOL "weakly link module against library")
Expand All @@ -286,7 +22,7 @@ add_library(number ${LIB_TYPE} number.c)
add_library(counter MODULE counter.c)
set_target_properties(counter PROPERTIES PREFIX "")
if(WEAK_LINK_MODULE)
target_weak_link_libraries(counter number)
target_link_libraries_with_dynamic_lookup(counter number)
else()
target_link_libraries(counter number)
endif()
Expand All @@ -295,7 +31,7 @@ endif()
# EXECUTABLE (main)
add_executable(main main.c)
if(WEAK_LINK_EXE)
target_weak_link_libraries(main number)
target_link_libraries_with_dynamic_lookup(main number)
else()
target_link_libraries(main number)
endif()
Expand Down
68 changes: 38 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,54 +55,62 @@ Replace "d10" with your desired combination to run that particular build.

##### Expected Results

NOTE: This demo performs two series of host examinations. The first is to
determine if the host system supports weak linking. The second is to determine
if the dynamic loader can properly merge symbol entries that have been
duplicated across link boundaries (e.g: Linux). Even if the host is found to
not support weak linking, the "weak link" operation would still succeed if the
loader can cope with duplicate symbols. In this case, the link is silently
promoted to a proper linking.
NOTE: The test step of the demo can fail for a number of reasons. Their
abbreviations and meanings are: `RTE` for "runtime error", `DSYM` for "duplicate
symbols", and `DLLF` for "dynamic library load failure".

###### Linux (GCC)

|CASE|CONFIG |BUILD |TEST |NOTES|
|----|------------------|------------------|------------------|----:|
|s00 |:white_check_mark:|:white_check_mark:|:x: | 1,2|
|s01 |:white_check_mark:|:white_check_mark:|:x: | 1,2|
|s10 |:white_check_mark:|:white_check_mark:|:x: | 1,2|
|s11 |:white_check_mark:|:white_check_mark:|:x: | 1,2|
|d00 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 1|
|d01 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 1|
|d10 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 1|
|d11 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 1,6|
|s00 |:white_check_mark:|:white_check_mark:|:x:RTE | 1,2|
|s01 |:white_check_mark:|:white_check_mark:|:x:DSYM | 3|
|s10 |:white_check_mark:|:white_check_mark:|:x:RTE | 2|
|s11 |:white_check_mark:|:white_check_mark:|:x:DSYM | 4|
|d00 |:white_check_mark:|:white_check_mark:|:x:RTE | 2|
|d01 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 5|
|d10 |:white_check_mark:|:white_check_mark:|:x:RTE | 2|
|d11 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 7|

###### OSX (XCODE)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specify the compiler used, and version.


|CASE|CONFIG |BUILD |TEST |NOTES|
|----|------------------|------------------|------------------|-----|
|s00 |:white_check_mark:|:white_check_mark:|:x: | 3|
|s01 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 4|
|s10 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 5|
|s11 |:white_check_mark:|:white_check_mark:|:x: | 2|
|d00 |:white_check_mark:|:white_check_mark:|:x: | 3|
|d01 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 4|
|d10 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 5|
|d11 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 6|
|s00 |:white_check_mark:|:white_check_mark:|:x:RTE | 1|
|s01 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 5|
|s10 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 6|
|s11 |:white_check_mark:|:white_check_mark:|:x:DSYM | 4|
|d00 |:white_check_mark:|:white_check_mark:|:x:RTE | 1|
|d01 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 5|
|d10 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 6|
|d11 |:white_check_mark:|:white_check_mark:|:white_check_mark:| 7|

###### NOTES

1. On Linux, the only thing that matters is whether the library in question is
shared or static. The dynamic loader doesn't seem to have any problems with
multiple identical symbols.
1. Test fails due to unresolved symbols.

1. Test case fails unique symbols test (program output looks like
`0 0 1 1 2 2 ...` instead of `0 1 2 ...`).
1. On Linux, all symbols in a binary's namespace must be resolved when the
namespace is instantiated. Because of this requirement, leaving symbols
unresolved in an executable is almost never useful, since the missing
symbols must be provided before the executable is even ran (e.g.: using
`LD_PRELOAD`).

1. Test fails due to unresolved symbols.
1. In the actual system test, the produced binary fails due to unresolved
symbols in the module's namespace. The result is reported as symbol
duplication because the check falls back to double-linking in an attempt to
produce a working executable (with the same result as the `s11` case).

1. Test case fails unique symbols test (program output looks like
`0 0 1 1 2 2 ...` instead of `0 1 2 ...`).

1. Module pulls symbols from the executable (ala Python extension modules).

1. Executable pulls symbols from the module (unusual, but it works).

1. Duplicate symbols are successfully resolved and unified at load time.
1. Duplicate symbols are successfully resolved and coalesced at load time.

Tested on:

- Arch Linux GCC 6.1.1
- Mac OSX 10.11.4 LLVM 7.3.0

Loading