Repost of a message in https://issues.dlang.org/show_bug.cgi?id=14824
also reported here with a $50 bounty since I originally found it with LDC, but it also happens with DMD: https://github.com/ldc-developers/ldc/issues/1071
DMD version = 2.068.1
OS = OS X 10.10.4
== Setup ==
Here the host program source.
What it does is:
- for each dynlib in command line:
- load dynlibs
- call the VSTPluginMain function if it exist,
- unload it
--------------------- ldvst.cpp --------------------
#include <dlfcn.h>
#include <cstdio>
#include <cstring>
#include <vector>
typedef __cdecl void* (*VSTPluginMain_t)(void*);
int main(int argc, char**argv)
{
std::vector<char*> dllPaths;
if (argc < 2)
{
printf("usage: ldvst [-lazy] <thing.vst>\n");
return 1;
}
bool lazy = false;
for (int i = 1; i < argc; ++i)
{
char* arg = argv[i];
if (strcmp(arg, "-lazy") == 0)
lazy = true;
else if (strcmp(arg, "-now") == 0)
lazy = false;
else
dllPaths.push_back(arg);
}
for (int i = 0; i < dllPaths.size(); ++i)
{
char* dllPath = dllPaths[i];
printf("dlopen(%s)\n", dllPath);
void* handle = dlopen(dllPath, lazy ? RTLD_LAZY : RTLD_NOW);
if (handle == NULL)
{
printf("error: dlopen of %s failed\n", dllPath);
return 2;
}
VSTPluginMain_t VSTPluginMain = (VSTPluginMain_t) dlsym(handle, "VSTPluginMain");
printf("dlsym returned %p\n", (void*)VSTPluginMain);
if (VSTPluginMain != NULL)
{
void* result = VSTPluginMain(NULL);
printf("VSTPluginMain returned %p\n", result);
}
printf("dlclose(%s)\n\n", dllPath);
dlclose(handle);
}
return 0;
}
-----------------------------------------------------
The host is compiled with:
$ clang++ ldvst.cpp -o ldvst-cpp
Here the whole dynlib source:
----------------- distort.d -------------------------
extern(C) void* VSTPluginMain(void* hostCallback)
{
import core.runtime;
Runtime.initialize();
import std.stdio;
import core.stdc.stdio;
printf("Hello !\n");
Runtime.terminate();
return null;
}
------------------------------------------------------
This dynlib can be built with ldc:
$ ldc2 -shared -oflibdistort.so -g -w -I. distort.d -relocation-model=pic
or with dmd
$ dmd -c -ofdistort.o -debug -g -w -version=Have_distort -I. distort.d -fPIC
$ dmd -oflibdistort.so distort.o -shared -g
For the purpose of demonstration you need another C dynlib, for example
/System/Library/Frameworks/Cocoa.framework/Cocoa on OS X.
Now the bug triggers when calling:
== How to reproduce ==
$ ldvst-cpp libdistort.so
=> works
$ ldvst-cpp libdistort.so libdistort.so
=> works
$ ldvst-cpp /System/Library/Frameworks/Cocoa.framework/Cocoa libdistort.so
=> works
$ ldvst-cpp libdistort.so /System/Library/Frameworks/Cocoa.framework/Cocoa
=> FAIL, and that's precisely the case that happen in production :(
$ ldvst-cpp /System/Library/Frameworks/Cocoa.framework/Cocoa libdistort.so /System/Library/Frameworks/Cocoa.framework/Cocoa
=> works
In other words, if the host program loads a D dynlib first, then a C dynlib, then the second dlopen fail.
This is pernicious since the host program would scan my program, dlclose successfully, then put in jail another program.
As of today, this is the only thing between me and customers, all other bugs can be work-arounded, this one I'm not sure.
Comment #1 by aliloko — 2015-09-16T09:26:56Z
Suggestion from David Nadlinger
"
This is OS X only, right? In this case, it is hard to judge where exactly it sits and what needs to be done to fix it, as no work has been done on dylib support recently in either compiler. A wild guess is that there might still be an image change handler, etc. registered that dyld tries to call when loading the new library, but which no longer exists in the program. Searching druntime for "_dyld" might reveal something of this sort."
Can someone please try to fix this properly in druntime?
If it's not doable using the dyld API, I might work on adding init/fini calls to any OSX binary (similar to what we do on ELF).
(In reply to Martin Nowak from comment #4)
> Can someone please try to fix this properly in druntime?
> If it's not doable using the dyld API, I might work on adding init/fini
> calls to any OSX binary (similar to what we do on ELF).
I've looked in the source code of dyld. There's no code that removes anything from the list of callbacks. I don't see any other way than using the init/fini calls.
Comment #8 by nicolas.jinchereau — 2015-09-28T14:10:38Z
>> ponce 2015-09-18 09:47:37 UTC
>> Hacky workaround found by bitwise and Martin Nowak:
>> http://forum.dlang.org/post/[email protected]
I suppose I should note that I was expanding on Jacob's solution ;)
Comment #9 by aliloko — 2015-10-01T11:37:45Z
If this need back-end support, maybe Walter could implement global ctor/dtor like in LDC?
@bitwise: how much would you take to do it? :))
FWIW, I'm having problem with the workaround (calling dyld_register_image_state_change_handler crash). It's as if dyld_register_image_state_change_handler has a different signature or calling convention in 32-bit.
Comment #10 by doob — 2015-10-01T13:39:46Z
(In reply to ponce from comment #9)
> If this need back-end support, maybe Walter could implement global ctor/dtor
> like in LDC?
DMD already does what's necessary for ELF (Linux, FreeBSD). Just do the same for Mach-O and OS X.
> FWIW, I'm having problem with the workaround (calling
> dyld_register_image_state_change_handler crash). It's as if
> dyld_register_image_state_change_handler has a different signature or
> calling convention in 32-bit.
The declaration of dyld_register_image_state_change_handler is available here [1]. The declaration of "dyld_image_info" [2] and "mach_header" [3].
Looking at your implementation, both the function pointer for the callback and the function it points to need to be declared as extern(C).
[1] http://www.opensource.apple.com/source/dyld/dyld-96.2/include/mach-o/dyld_priv.h
[2] http://opensource.apple.com/source/dyld/dyld-132.13/include/mach-o/dyld_images.h
[3] http://www.opensource.apple.com/source/xnu/xnu-1456.1.26/EXTERNAL_HEADERS/mach-o/loader.h
Comment #11 by aliloko — 2015-10-01T15:46:02Z
:X Thanks!!! can't believe I forgot that.
Comment #12 by nicolas.jinchereau — 2015-10-02T02:12:18Z
(In reply to Jacob Carlborg from comment #10)
> (In reply to ponce from comment #9)
> > If this need back-end support, maybe Walter could implement global ctor/dtor
> > like in LDC?
>
> DMD already does what's necessary for ELF (Linux, FreeBSD). Just do the same
> for Mach-O and OS X.
>> DMD already does what's necessary for ELF (Linux, FreeBSD). Just do the same
>> for Mach-O and OS X.
That's easier said than done ;)
Making druntime compile as a shared lib and making it support multiple images is the easy part. Modifying DMD to output the ctors/dtors properly is much more difficult than it sounds.
The current solution outputs the ctors/dtors by manually outputting byte codes:
https://github.com/D-Programming-Language/dmd/blob/master/src/backend/elfobj.c#L3183
This is why I was saying we should just add some(or one special one) pragmas that will allow this to be done in the front end.
>> @bitwise: how much would you take to do it? :))
Wish I knew how =/
Comment #13 by doob — 2015-10-02T07:04:42Z
(In reply to bitwise from comment #12)
> (In reply to Jacob Carlborg from comment #10)
> >> DMD already does what's necessary for ELF (Linux, FreeBSD). Just do the same
> >> for Mach-O and OS X.
>
> That's easier said than done ;)
:)
> This is why I was saying we should just add some(or one special one) pragmas
> that will allow this to be done in the front end.
I don't think this can be completely done in the front end. The code (or that function) that should be run as a global ctor/dtor need to be placed in a special segment/section, if I recall correctly.
Comment #14 by nicolas.jinchereau — 2015-10-02T13:07:14Z
(In reply to Jacob Carlborg from comment #13)
> (In reply to bitwise from comment #12)
> > (In reply to Jacob Carlborg from comment #10)
> > >> DMD already does what's necessary for ELF (Linux, FreeBSD). Just do the same
> > >> for Mach-O and OS X.
> >
> > That's easier said than done ;)
>
> :)
>
> > This is why I was saying we should just add some(or one special one) pragmas
> > that will allow this to be done in the front end.
>
> I don't think this can be completely done in the front end. The code (or
> that function) that should be run as a global ctor/dtor need to be placed in
> a special segment/section, if I recall correctly.
Right, which is why I am recommending the special pragmas(or pragma).
Comment #15 by nicolas.jinchereau — 2015-10-02T13:24:18Z
(In reply to bitwise from comment #14)
> (In reply to Jacob Carlborg from comment #13)
> > [...]
>
> Right, which is why I am recommending the special pragmas(or pragma).
Basically, we need these:
pragma(attribute, weak)
pragma(visibility, hidden)
pragma(section, "__mod_init_func");
pragma(section, "__mod_term_func");
Comment #16 by robert.schadek — 2024-12-07T13:35:44Z