Skip to content
This repository was archived by the owner on Oct 12, 2022. It is now read-only.

Support merging of EH tables across Win64 DLLs #2874

Closed
wants to merge 7 commits into from
Closed
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
21 changes: 15 additions & 6 deletions src/rt/cast_.d
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@
*/
module rt.cast_;

// because using == does a dynamic cast, but we
// are trying to implement dynamic cast.
Copy link
Member

Choose a reason for hiding this comment

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

Since this is comparing for equality, the name for it should be something like "equals".

bool compareClassInfo(ClassInfo a, ClassInfo b)
{
if (a is b)
return true;
return (a && b) && a.info.name == b.info.name;
Copy link
Member

Choose a reason for hiding this comment

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

When would the ClassInfo ever be null?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I vaguely recall seeing it happen in December when working on this originally but I don't remember where. Possible I was just being defensive too.

}

extern (C):

/******************************************
Expand Down Expand Up @@ -76,19 +85,19 @@ void* _d_dynamic_cast(Object o, ClassInfo c)

int _d_isbaseof2(ClassInfo oc, ClassInfo c, ref size_t offset)
{
if (oc is c)
if (oc.compareClassInfo(c))
return true;

do
{
if (oc.base is c)
if (oc.base.compareClassInfo(c))
return true;

// Bugzilla 2013: Use depth-first search to calculate offset
// from the derived (oc) to the base (c).
foreach (iface; oc.interfaces)
{
if (iface.classinfo is c || _d_isbaseof2(iface.classinfo, c, offset))
if (iface.classinfo.compareClassInfo(c) || _d_isbaseof2(iface.classinfo, c, offset))
{
offset += iface.offset;
return true;
Expand All @@ -103,17 +112,17 @@ int _d_isbaseof2(ClassInfo oc, ClassInfo c, ref size_t offset)

int _d_isbaseof(ClassInfo oc, ClassInfo c)
{
if (oc is c)
if (oc.compareClassInfo(c))
return true;

do
{
if (oc.base is c)
if (oc.base.compareClassInfo(c))
return true;

foreach (iface; oc.interfaces)
{
if (iface.classinfo is c || _d_isbaseof(iface.classinfo, c))
if (iface.classinfo.compareClassInfo(c) || _d_isbaseof(iface.classinfo, c))
return true;
}

Expand Down
96 changes: 96 additions & 0 deletions src/rt/dmain2.d
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ version (CRuntime_Microsoft)
extern(C) void init_msvc();
}

version (Win64)
import rt.deh_win64_posix;

/***********************************
* These are a temporary means of providing a GC hook for DLL use. They may be
* replaced with some other similar functionality later.
Expand All @@ -102,6 +105,13 @@ extern (C)
alias void* function() gcGetFn;
alias void function(void*) gcSetFn;
alias void function() gcClrFn;

// for exception handling, at least until druntime.dll actually works.
version (Win64)
{
alias void function(const(FuncTable)[]*) ehSetFn;
alias const(FuncTable)[] function() ehGetFn;
}
}

version (Windows)
Expand All @@ -126,13 +136,60 @@ version (Windows)
{
// BUG: LoadLibrary() call calls rt_init(), which fails if proxy is not set!
// (What? LoadLibrary() is a Windows API call, it shouldn't call rt_init().)
// FYI LoadLibrary calls DllMain. DllMain calls rt_init.
if (mod is null)
return mod;
gcSetFn gcSet = cast(gcSetFn) GetProcAddress(mod, "gc_setProxy");
if (gcSet !is null)
{ // BUG: Set proxy, but too late
gcSet(gc_getProxy());
}

version (Win64)
{
import rt.sections_win64;

auto ehGet = cast(ehGetFn) GetProcAddress(mod, "_d_innerEhTable");
auto ehSet = cast(ehSetFn) GetProcAddress(mod, "_d_setEhTablePointer");

if (ehGet)
{
auto libraryEh = ehGet();
if (ehTablesGlobal is null)
{
// first time: copy the local table into it as well as
// the new library
auto local = _d_innerEhTable();
auto len = libraryEh.length + local.length;
auto ptr = cast(FuncTable*) malloc(typeof(ehTablesGlobal[0]).sizeof * len);
ptr[0 .. local.length] = cast(FuncTable[]) local[];
ptr[local.length .. local.length + libraryEh.length] = cast(FuncTable[]) libraryEh[];
ehTablesGlobal = ptr[0 .. len];
}
else
{
// otherwise we need to realloc it to append the library table
auto ptr = ehTablesGlobal.ptr;
ptr = cast(FuncTable*) realloc(cast(void*) ptr,
typeof(ehTablesGlobal[0]).sizeof * (ehTablesGlobal.length + libraryEh.length));
if (ptr is null)
abort();
auto orig = ehTablesGlobal.length;
cast(FuncTable[]) ptr[orig .. orig + libraryEh.length] = cast(FuncTable[]) libraryEh[];
ehTablesGlobal = ptr[0 .. orig + libraryEh.length];
}

// set the local pointer
_d_setEhTablePointer(&ehTablesGlobal);

if (ehSet)
{
// and set the remote table too so throwing from the dll also sees the whole thing
ehSet(&ehTablesGlobal);
}
}
}

return mod;
}

Expand All @@ -146,6 +203,39 @@ version (Windows)
*/
extern (C) int rt_unloadLibrary(void* ptr)
{
// should this logic just be done in the dll's DllMain instead?

// need to clear the DLL's table out of the global list, so first that
// means fetching the dll's table...
version (Win64)
{
ehGetFn ehGet = cast(ehGetFn) GetProcAddress(ptr, "_d_innerEhTable");
if (ehGet)
{
const(FuncTable)[] dllTables = ehGet();
import rt.sections_win64;

loop: foreach (entry; dllTables)
{
foreach (idx, ge; ehTablesGlobal)
{
if (ge == entry)
{
import core.stdc.string;
// assumes continuous append! If you ever sort the loading code or something
// be sure to fix this too.
memmove(cast(void*) &ehTablesGlobal[idx], cast(void*) &ehTablesGlobal[idx + dllTables.length], typeof(ge).sizeof * (ehTablesGlobal.length - (idx + dllTables.length)));
ehTablesGlobal = ehTablesGlobal[idx + dllTables.length .. $];
// could realloc it down to the new size, but I suspect it will be useful
// again in the future anyway so might as well let the realloc on load reuse
// space if needed and also simplify this part of the code a little.
break loop;
}
}
}
}
}

gcClrFn gcClr = cast(gcClrFn) GetProcAddress(ptr, "gc_clrProxy");
if (gcClr !is null)
gcClr();
Expand Down Expand Up @@ -191,6 +281,12 @@ extern (C) int rt_init()
version (CRuntime_Microsoft)
init_msvc();

version (Win64)
{{
import rt.sections_win64;
loadDefaultEhTables();
}}

_d_monitor_staticctor();
_d_critical_init();

Expand Down
30 changes: 27 additions & 3 deletions src/rt/sections_win64.d
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ struct SectionGroup
version (Win64)
@property immutable(FuncTable)[] ehTables() const nothrow @nogc
{
auto pbeg = cast(immutable(FuncTable)*)&_deh_beg;
auto pend = cast(immutable(FuncTable)*)&_deh_end;
return pbeg[0 .. pend - pbeg];
return cast(typeof(return)) *ehTablesFull;
}

@property inout(void[])[] gcRanges() inout nothrow @nogc
Expand Down Expand Up @@ -218,6 +216,32 @@ do
return cast(immutable)result;
}

// Used for EH table merging across DLL boundaries
version (Win64)
{

__gshared const(FuncTable)[]* ehTablesFull; // this is the proxy throw actually uses. may point to local or global
__gshared const(FuncTable)[] ehTablesLocal; // never changed, just the local module's things

// this is only valid if we are an exe, it contains the concatenation of the exe and all loaded dlls
__gshared package(rt) const(FuncTable)[] ehTablesGlobal;

package(rt) void loadDefaultEhTables() {
ehTablesLocal = _d_innerEhTable();
ehTablesFull = &ehTablesLocal;
}

package(rt) extern(C) void _d_setEhTablePointer(const(FuncTable)[]* ptr) {
ehTablesFull = ptr;
}

package(rt) extern(C) const(FuncTable)[] _d_innerEhTable() {
auto pbeg = cast(const(FuncTable)*)&_deh_beg;
auto pend = cast(const(FuncTable)*)&_deh_end;
return pbeg[0 .. pend - pbeg];
}
}

extern(C)
{
/* Symbols created by the compiler/linker and inserted into the
Expand Down
11 changes: 10 additions & 1 deletion test/shared/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ LINK_SHARED:=1

include ../common.mak

TESTS:=link load linkD linkDR loadDR host finalize
TESTS:=link load linkD linkDR loadDR host finalize dynamiccast
TESTS+=link_linkdep load_linkdep link_loaddep load_loaddep load_13414

EXPORT_DYNAMIC=$(if $(findstring $(OS),linux freebsd dragonflybsd),-L--export-dynamic,)
Expand All @@ -13,9 +13,12 @@ all: $(addprefix $(ROOT)/,$(addsuffix .done,$(TESTS)))

$(ROOT)/loadDR.done $(ROOT)/host.done: RUN_ARGS:=$(DRUNTIMESO)

$(ROOT)/dynamiccast.done: CLEANUP:=rm dynamiccast_endmain dynamiccast_endbar

$(ROOT)/%.done: $(ROOT)/%
@echo Testing $*
$(QUIET)$(TIMELIMIT)$< $(RUN_ARGS)
$(CLEANUP)
@touch $@

$(ROOT)/link: $(SRC)/link.d $(ROOT)/lib.so $(DRUNTIMESO)
Expand All @@ -39,6 +42,12 @@ $(ROOT)/load $(ROOT)/finalize: $(ROOT)/%: $(SRC)/%.d $(ROOT)/lib.so $(DRUNTIMESO
$(ROOT)/load_13414: $(ROOT)/%: $(SRC)/%.d $(ROOT)/lib_13414.so $(DRUNTIMESO)
$(QUIET)$(DMD) $(DFLAGS) -of$@ $< $(LINKDL)

$(ROOT)/dynamiccast: $(SRC)/dynamiccast.d $(SRC)/classdef.d $(ROOT)/dynamiccast.so $(DRUNTIMESO)
$(QUIET)$(DMD) $(DFLAGS) -of$@ $(SRC)/dynamiccast.d $(SRC)/classdef.d $(LINKDL)

$(ROOT)/dynamiccast.so: $(SRC)/dynamiccast.d $(SRC)/classdef.d $(DRUNTIMESO)
$(QUIET)$(DMD) $(DFLAGS) -of$@ $< -version=DLL -fPIC -shared $(LINKDL)

$(ROOT)/linkD: $(SRC)/linkD.c $(ROOT)/lib.so $(DRUNTIMESO)
$(QUIET)$(CC) $(CFLAGS) -o $@ $< $(ROOT)/lib.so $(LDL) -pthread

Expand Down
4 changes: 4 additions & 0 deletions test/shared/src/classdef.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class C : Exception
{
this() { super(""); }
}
87 changes: 87 additions & 0 deletions test/shared/src/dynamiccast.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
version (DLL)
{
version (Windows)
{
import core.sys.windows.dll;
mixin SimpleDllMain;
}

pragma(mangle, "foo")
export Object foo(Object o)
{
import classdef : C;

assert(cast(C) o);
return new C;
}

pragma(mangle, "bar")
export void bar(void function() f)
{
import core.stdc.stdio : fopen, fclose;
import classdef : C;
bool caught;
try
f();
catch (C e)
caught = true;
assert(caught);

// verify we've actually got to the end, because for some reason we can
// end up exiting with code 0 when throwing an exception
fclose(fopen("dynamiccast_endbar", "w"));
throw new C;
}
}
else
{
T getFunc(T)(const(char)* sym, string thisExePath)
{
import core.runtime : Runtime;

version (Windows)
{
import core.sys.windows.winbase : GetProcAddress;
return cast(T) Runtime.loadLibrary("dynamiccast.dll")
.GetProcAddress(sym);
}
else version (Posix)
{
import core.sys.posix.dlfcn : dlsym;
import core.stdc.string : strrchr;

auto name = thisExePath ~ '\0';
const pathlen = strrchr(name.ptr, '/') - name.ptr + 1;
name = name[0 .. pathlen] ~ "dynamiccast.so";
return cast(T) Runtime.loadLibrary(name)
.dlsym(sym);
}
else static assert(0);
}

void main(string[] args)
{
import classdef : C;
import core.stdc.stdio : fopen, fclose, remove;

remove("dynamiccast_endmain");
remove("dynamiccast_endbar");

C c = new C;

auto o = getFunc!(Object function(Object))("foo", args[0])(c);
assert(cast(C) o);

bool caught;
try
getFunc!(void function(void function()))("bar", args[0])(
{ throw new C; });
catch (C e)
caught = true;
assert(caught);

// verify we've actually got to the end, because for some reason we can
// end up exiting with code 0 when throwing an exception
fclose(fopen("dynamiccast_endmain", "w"));
}
}
1 change: 1 addition & 0 deletions test/shared/src/dynamiccast32.def
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
EXETYPE NT
1 change: 1 addition & 0 deletions test/shared/src/dynamiccast32mscoff.def
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
EXETYPE NT
4 changes: 4 additions & 0 deletions test/shared/src/dynamiccast64.def
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
LIBRARY l
EXPORTS
_d_innerEhTable = _d_innerEhTable
_d_setEhTablePointer = _d_setEhTablePointer
Loading