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 resolving global symbols and implement RTLD_DEFAULT and RTLD_NEXT #44

Merged
merged 2 commits into from
Feb 14, 2019

Conversation

pali
Copy link
Collaborator

@pali pali commented Jan 29, 2019

This pull request contains two changes:

  • Fix resolving global symbols when LoadLibrary() is called after dlopen()
  • Implement support for dlsym() with RTLD_DEFAULT and RTLD_NEXT

Second one depends on the first. Details are in commit messages and also below.


Fix resolving global symbols when LoadLibrary() is called after dlopen()

Usage of first_automatic_object cache is wrong. This cache is filled by all
loaded DLL files (either implicitly or explicitly with LoadLibrary() call)
by EnumProcessModules() call at first usage of dlopen(). So dlsym() can
resolve global symbols only if they were loaded prior to dlopen() call. Any
future usage of LoadLibrary() does not include newly loaded DLLs into
first_automatic_object cache.

To fix this problem, first_automatic_object cache is fully removed and
EnumProcessModules() call is issued directly in dlsym() call.

As EnumProcessModules() returns all DLLs, included those which were loaded
by dlopen() with RTLD_LOCAL, it may break RTLD_LOCAL support. To address
this problem switch linked-list of all loaded DLLs with RTLD_GLOBAL to
linked-list of all loaded DLLs with RTLD_LOCAL flag. And then skip modules
from EnumProcessModules() which are in linked-list.

Also in WinAPI all DLLs loaded by LoadLibrary() behaves like RTLD_GLOBAL.
So above change is compatible with this behavior.

There may be another problem. Before retrieving HMODULE for DLL filename
(which is done by LoadLibrary()), it is not possible to detect if DLL was
already loaded by RTLD_LOCAL or not. And after calling LoadLibrary() it is
not possible to know if DLL was loaded either by dlsym() with RTLD_LOCAL or
by LoadLibrary() (which is equivalent to RTLD_GLOBAL). To address this
problem, compare number of loaded modules (counted by EnumProcessModules())
before and after LoadLibrary() called from dlsym(). If number does not
change it means that DLL was already loaded. So based on this result either
add or remove HMODULE from linked-list of RTLD_LOCAL modules.

Added test demonstrate usage of:

global = dlopen(NULL, RTLD_GLOBAL); /* global handle /
LoadLibrary("library.dll"); /
this provides function /
function = dlsym(global, "function"); /
resolve function from library.dll */


Implement support for dlsym() with RTLD_DEFAULT and RTLD_NEXT

dlsym() with RTLD_DEFAULT handle behaves in same way like with global handle
returned by dlopen() with NULL file name.

dlsym() with RTLD_NEXT handle search for next loaded module which provides
specified symbol. "Next" means module which in EnumProcessModules() result
after the module which called dlsym().

To get caller function of dlsym() use _ReturnAddress() intrinsic. To get
module where is caller function use the fact that HMODULE is the same value
as the module's base address.

When compiling under gcc, defines _ReturnAddress() macro via gcc's builtin
as it does not provide MSC's specific _ReturnAddress() intrinsic.

Added tests demonstrate that both RTLD_DEFAULT and RTLD_NEXT are working as
expected.

@traversaro
Copy link
Collaborator

traversaro commented Jan 29, 2019

Thanks for the PR. I will probably need a few days to process this, but first of all thanks for the exhaustive documentation and tests.

@pali
Copy link
Collaborator Author

pali commented Feb 6, 2019

Ok, if you have any objections, let me know.

Copy link
Collaborator

@traversaro traversaro left a comment

Choose a reason for hiding this comment

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

Thanks a lot for the PR. The first commit on resolving global symbols seems fine.

For the second commit, I just have one small inline comment on the use of __declspec(noinline), that you can see in the PR.

@@ -312,9 +323,11 @@ int dlclose( void *handle )
return (int) ret;
}

__declspec(noinline) /* Needed for _ReturnAddress() */
Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems to be MSVC-specific attribute. Should be used __attribute__ ((noinline)) instead for GCC and Clang or this is not necessary for some reason?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You are already using __declspec(dllexport) in dlfcn.h/dlfcn.c and also in tests. Even for GCC / Clang. So I chose __declspec(noinline) instead of __attribute__ ((noinline)).

I tested __declspec(noinline) with i686-w64-mingw32-gcc 6.3.0 and it is working fine. Seems that gcc supports some of MSVC __declspec attributes (dllexport/noinline).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Great, thanks!

@traversaro
Copy link
Collaborator

The usage of the _ReturnAddress could probably create problems in not commonly used compilers such as the Intel C++ compiler, but as I have no access to them there is no way we can support them anyway. If anyone reading this wants support for a minor compiler, please open an issue, thanks.

@pali
Copy link
Collaborator Author

pali commented Feb 10, 2019

It looks like that Intel C++ compiler has support for both _ReturnAddress and __builtin_return_address . (See: http://www.info.univ-angers.fr/pub/richer/ens/l3info/ao/intel_intrinsics.pdf)
But I have not tested it with Intel C++ so do not know.

Clang has __builtin_return_address via llvm's @llvm.returnaddress: https://llvm.org/docs/LangRef.html#llvm-returnaddress-intrinsic

@traversaro
Copy link
Collaborator

Thanks a lot @pali . At this point I would first merge #46 and then rebase the PR on top of master, to make sure that this PR works fine on mingw/mingw-w64, if that is ok for you.

@pali
Copy link
Collaborator Author

pali commented Feb 11, 2019

I had a bug in test for RTLD_NEXT. Now test.c checks that it called really "wrapper" function and that "wrapper" function called "original" function via different return values.

Now I cross compiled it via x86_64-w64-mingw32 on Debian and test passed in wine.

@traversaro traversaro added this to the v1.2.0 milestone Feb 12, 2019
@traversaro
Copy link
Collaborator

Sorry for the additional delay, can you rebase on top of the latest master with mingw-based tests enabled? Thanks!

Usage of first_automatic_object cache is wrong. This cache is filled by all
loaded DLL files (either implicitly or explicitly with LoadLibrary() call)
by EnumProcessModules() call at first usage of dlopen(). So dlsym() can
resolve global symbols only if they were loaded prior to dlopen() call. Any
future usage of LoadLibrary() does not include newly loaded DLLs into
first_automatic_object cache.

To fix this problem, first_automatic_object cache is fully removed and
EnumProcessModules() call is issued directly in dlsym() call.

As EnumProcessModules() returns all DLLs, included those which were loaded
by dlopen() with RTLD_LOCAL, it may break RTLD_LOCAL support. To address
this problem switch linked-list of all loaded DLLs with RTLD_GLOBAL to
linked-list of all loaded DLLs with RTLD_LOCAL flag. And then skip modules
from EnumProcessModules() which are in linked-list.

Also in WinAPI all DLLs loaded by LoadLibrary() behaves like RTLD_GLOBAL.
So above change is compatible with this behavior.

There may be another problem. Before retrieving HMODULE for DLL filename
(which is done by LoadLibrary()), it is not possible to detect if DLL was
already loaded by RTLD_LOCAL or not. And after calling LoadLibrary() it is
not possible to know if DLL was loaded either by dlsym() with RTLD_LOCAL or
by LoadLibrary() (which is equivalent to RTLD_GLOBAL). To address this
problem, compare number of loaded modules (counted by EnumProcessModules())
before and after LoadLibrary() called from dlsym(). If number does not
change it means that DLL was already loaded. So based on this result either
add or remove HMODULE from linked-list of RTLD_LOCAL modules.

Added test demonstrate usage of:

global = dlopen(NULL, RTLD_GLOBAL);   /* global handle */
LoadLibrary("library.dll");           /* this provides function */
function = dlsym(global, "function"); /* resolve function from library.dll */
dlsym() with RTLD_DEFAULT handle behaves in same way like with global handle
returned by dlopen() with NULL file name.

dlsym() with RTLD_NEXT handle search for next loaded module which provides
specified symbol. "Next" means module which in EnumProcessModules() result
after the module which called dlsym().

To get caller function of dlsym() use _ReturnAddress() intrinsic. To get
module where is caller function use the fact that HMODULE is the same value
as the module's base address.

When compiling under gcc, defines _ReturnAddress() macro via gcc's builtin
as it does not provide MSC's specific _ReturnAddress() intrinsic.

Added tests demonstrate that both RTLD_DEFAULT and RTLD_NEXT are working as
expected.
@pali
Copy link
Collaborator Author

pali commented Feb 14, 2019

Done, rebased.

@traversaro
Copy link
Collaborator

Great, thanks a lot for the great PR!

@traversaro traversaro merged commit 278c782 into dlfcn-win32:master Feb 14, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants