shared libs for OSX

bitwise via Digitalmars-d digitalmars-d at puremagic.com
Sun May 24 12:40:04 PDT 2015


I've read through these now, which I missed the first time around, so  
sorry for making you guys repeat yourselves ;)

<Runtime issue on Mac OS X>
http://comments.gmane.org/gmane.comp.lang.d.runtime/1214

<ideas for runtime loading of shared libraries.>
http://forum.dlang.org/thread/mailman.2052.1325532031.24802.digitalmars-d@puremagic.com

So in terms of a shared lib having it's own runtime, we have these  
problems:

[1] problem

On Fri, 22 May 2015 12:04:24 -0400, Martin Nowak <code at dawg.eu> wrote:
> Yes separate shared libraries (with multiple runtimes) work on every  
> other platform.
> The problem for OSX is that onAddImage gets called for the executable  
> and every shared library. It would be trivial to only register the image  
> containing the current runtime, by comparing the address of a private  
> symbol with the memory region of the images.
>
> https://github.com/D-Programming-Language/druntime/blob/6331ab1ae19f3ff82449a5734b59d81b128685f4/src/rt/sections_osx.d#L186

[2] problem

On Thu, 21 May 2015 15:34:56 -0400, Jacob Carlborg <doob at me.com> wrote:
> The runtime uses the "_dyld_register_func_for_add_image" function  
> provided by the dynamic linker. This function is used to register a  
> callback. The callback will be called for each currently loaded image  
> (executable/dynamic library) in the executable. The callback will also  
> be called for every newly loaded image, i.e. using dlopen. You need to  
> keep track of which image is yourself and which are other images you  
> should ignore.
>
> Then, the other problem with "_dyld_register_func_for_add_image" is that  
> it's not possible to unregister the callback. Which means, if you  
> register a callback using this function from a dynamic library loaded  
> with dlopen and then unload it. Next time dlopen is called it will  
> crash, because the callback points to an address that doesn't exist  
> anymore.


I think I have found solutions for these problems.


[1] solution

I've modified sections_osx.d as follows:

extern (C) void _sections_osx_onAddImage_STUB(in mach_header* h, intptr_t  
slide) {
     // empty
}

extern (C) void sections_osx_onAddImage(in mach_header* h, intptr_t slide)
{
// on mac osx, Dl_info.dli_fbase is a pointer to the mach_header for the  
library. [I]
// here we return unless onAddImage is being called for the current library
     mach_header* myHeader = null;

     Dl_info info;
     // this line also makes sure that the stub isn't
     // removed by the linker(it's needed for [2])
     if(dladdr(&_sections_osx_onAddImage_STUB, &info))
     {
         mach_header* mh = cast(mach_header*)info.dli_fbase;
         if(mh == cast(mach_header*)h)
             myHeader = mh;
     }

     if(!myHeader)
         return;

     // initialize sections.....

}

[2] solution

Although the callback passed to "_dyld_register_func_for_add_image" cannot  
be removed, it can be replaced, so I've replaced it with a pointer to the  
stub located in the main program.

I've modified initSections() in sections_osx.d as follows:

void initSections()
{
     pthread_key_create(&_tlsKey, null);

     // register the callback as usual. This will call the callback
     // for every library currently loaded before returning, so
     // once it has returned, it should be safe to set the callback
     // to something else(the empty stub)
     _dyld_register_func_for_add_image(&sections_osx_onAddImage);

     // dlopen(null, ..) will retrieve a handle to the main program [II]
     // OSX docs says it returns the first symbol found using
     // "RTLD_DEFAULT" or "the default library search order" which
     // should(and does as far as I can tell) return the handle
     // to the main program
     void *main = dlopen(null, RTLD_NOW);
     assert(main);
     alias typeof(&_sections_osx_onAddImage_STUB) addImgFn;
     addImgFn func = cast(addImgFn)dlsym(main,  
"_sections_osx_onAddImage_STUB");
     assert(func);

     // set the callback to the empty stub in the main program
     _dyld_register_func_for_add_image(func);

     _isRuntimeInitialized = true;
}

[I]   
https://developer.apple.com/library/ios/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dladdr.3.html
[II]  
https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/dlopen.3.html
      http://linux.die.net/man/3/dlopen



So at this point, it seems like these two fixes work as expected, but now,  
I'm having some new and very strange problems.

I have a simple shared library and program I've been using to test this:

[main.d]
module main;
import std.stdio;
import std.conv;
import std.string;
import core.sys.posix.dlfcn;

void main(string[] args)
{
     alias void function() fnType;

     void *handle = dlopen("myShared.dylib", RTLD_NOW);
     assert(handle);

     fnType init = cast(fnType)dlsym(handle, "initLib");
     assert(init);
     init();

     fnType term = cast(fnType)dlsym(handle, "termLib");
     assert(term);
     term();

     dlclose(handle);
     writeln("done");
}

[myShared.d]
module myShared;
import core.runtime;
import std.stdio;

extern(C) void initLib() {
     writeln("Initializing Runtime");
     Runtime.initialize();
}

extern(C) void termLib() {
     writeln("Terminating Runtime");
     Runtime.terminate();
}


So, when I run the above program, rt_init() should be called once for the  
main program, and once for the shared library. However, when I run the  
above program, rt_init() from the main program seems to get called twice.  
To clarify, I mean that when I retrieve "initLib()" with dlsym() and call  
it, rt_init() from the main module gets called.

This seems to prove the above:

In dmain2.d, I have modified rt_init() as follows:

extern (C) int rt_init()
{
     import core.sys.posix.dlfcn;
     Dl_info info;
     if(dladdr(&rt_init, &info))
         fprintf(stdout, "RT INIT: %s\n", info.dli_fname);   // this prints  
"main" for both calls

     if (atomicOp!"+="(_initCount, 1) > 1)
     {
         fprintf(stdout, "RT ALREADY INITIALIZED\n");
         return 1;
     }

// ...

     fprintf(stdout, "RT INIT COMPLETE\n");
}

When the main program calls rt_init(), the output correctly reads "RT INIT  
COMPLETE".
When I load the dynamic library however, I get the output "RT ALREADY  
INITIALIZED"

How is this possible? I am not using a shared druntime afaik..

   Bit


More information about the Digitalmars-d mailing list