Files
Miro Kropacek e09b9dfad6 PLUGINS: Provide __cxa_atexit & __cxa_finalize
Atari's mintelf platform has ELF support but its libc does not provide
__cxa_atexit / __cxa_finalize, so -fuse-cxa-atexit cannot be used out of
the box. Add a minimal shim (plus a __dso_handle definition for the main
executable) so plugins can register and finalize their function-local
static destructors per DSO on unload.

Note: earlier version ran __cxa_finalize from the host via
findSymbol("__dso_handle"). That works as long as exactly one
__dso_handle ends up in the plugin's symtab (the plugin's own).

When the main binary also defines __dso_handle (so the host's
function-local statics can link with -fuse-cxa-atexit), it breaks: the
plugin link uses --just-symbols=<main binary> and imports that as a
second __dso_handle, and findSymbol can then return the host-imported
one instead of the plugin's own. The two addresses differ, so
__cxa_finalize matches nothing.

The plugin-side helper avoids the ambiguity on every target by using
whichever __dso_handle the plugin's own code already resolved against --
the same one embedded in its __cxa_atexit calls. (PSP2's plugin runtime
has always called __cxa_finalize from inside the plugin for the same
reason; see backends/plugins/psp2/plugin.cpp.)
2026-04-27 02:05:36 +10:00

208 lines
6.5 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/scummsys.h"
#if defined(DYNAMIC_MODULES) && defined(USE_ELF_LOADER)
#include "backends/plugins/elf/elf-provider.h"
#include "backends/plugins/dynamic-plugin.h"
#include "backends/plugins/elf/memory-manager.h"
#include "common/debug.h"
#include "common/fs.h"
/* Note about ELF_LOADER_CXA_ATEXIT:
*
* consider the code:
*
* class Foobar {
* const char *work() {
* static String foo = "bar";
* return s.c_str();
* }
* }
*
* When instantiating Foobar and calling work() for the first time the String
* foo will be constructed. GCC automatically registers its destruction via
* either atexit() or __cxa_atexit(). Only the latter will add information
* about which DSO did the construction (Using &__dso_handle).
*
* __cxa_atexit allows plugins to reference C++ ABI symbols in the main
* executable without code duplication (No need to link the plugin against
* libstdc++), since we can distinguish which registered exit functions belong
* to a specific DSO. When unloading a plugin, we just use the C++ ABI call
* __cxa_finalize(&__dso_handle) to call all destructors of only that DSO.
*
* Prerequisites:
* - The used libc needs to support __cxa_atexit (or the target must supply it;
* see backends/plugins/elf/cxa-atexit.cpp)
* - -fuse-cxa-atexit in CXXFLAGS
* - Every plugin needs its own hidden __dso_handle symbol, and an exported
* PLUGIN_finalize() helper that calls __cxa_finalize(&__dso_handle) from
* inside the plugin. This is automatically done via REGISTER_PLUGIN_DYNAMIC,
* see base/plugins.h.
*
* When __cxa_atexit can not be used, each plugin needs to link against
* libstdc++ to embed its own set of C++ ABI symbols. When not doing so,
* registered destructors of already unloaded plugins will crash the
* application upon returning from main().
*
* See "3.3.5 DSO Object Destruction API" of the C++ ABI
*/
DynamicPlugin::VoidFunc ELFPlugin::findSymbol(const char *symbol) {
void *func = 0;
if (_dlHandle)
func = _dlHandle->symbol(symbol);
if (!func) {
if (!_dlHandle)
warning("elfloader: Failed loading symbol '%s' from plugin '%s' (Handle is NULL)", symbol, _filename.toString(Common::Path::kNativeSeparator).c_str());
else
warning("elfloader: Failed loading symbol '%s' from plugin '%s'", symbol, _filename.toString(Common::Path::kNativeSeparator).c_str());
}
// FIXME HACK: This is a HACK to circumvent a clash between the ISO C++
// standard and POSIX: ISO C++ disallows casting between function pointers
// and data pointers, but dlsym always returns a void pointer. For details,
// see e.g. <https://web.archive.org/web/20061205092618/http://www.trilithium.com/johan/2004/12/problem-with-dlsym/>.
assert(sizeof(VoidFunc) == sizeof(func));
VoidFunc tmp;
memcpy(&tmp, &func, sizeof(VoidFunc));
return tmp;
}
/**
* Test the size of the plugin.
*/
void ELFPlugin::trackSize() {
// All we need to do is create our object, track its size, then delete it
DLObject *obj = makeDLObject();
obj->trackSize(_filename);
delete obj;
}
bool ELFPlugin::loadPlugin() {
assert(!_dlHandle);
DLObject *obj = makeDLObject();
if (obj->open(_filename)) {
_dlHandle = obj;
} else {
delete obj;
_dlHandle = 0;
}
if (!_dlHandle) {
warning("elfloader: Failed loading plugin '%s'", _filename.toString(Common::Path::kNativeSeparator).c_str());
return false;
}
CharFunc buildDateFunc = (CharFunc)findSymbol("PLUGIN_getBuildDate");
if (!buildDateFunc) {
unloadPlugin();
warning("elfloader: plugin '%s' is missing symbols", _filename.toString(Common::Path::kNativeSeparator).c_str());
return false;
}
if (strncmp(gScummVMPluginBuildDate, buildDateFunc(), strlen(gScummVMPluginBuildDate))) {
unloadPlugin();
warning("elfloader: plugin '%s' has a different build date", _filename.toString(Common::Path::kNativeSeparator).c_str());
return false;
}
bool ret = DynamicPlugin::loadPlugin();
#ifdef ELF_LOADER_CXA_ATEXIT
if (ret) {
_finalizeFunc = findSymbol("PLUGIN_finalize");
if (!_finalizeFunc)
warning("elfloader: plugin '%s' is missing PLUGIN_finalize", _filename.toString(Common::Path::kNativeSeparator).c_str());
}
#endif
_dlHandle->discardSymtab();
return ret;
}
void ELFPlugin::unloadPlugin() {
DynamicPlugin::unloadPlugin();
if (_dlHandle) {
#ifdef ELF_LOADER_CXA_ATEXIT
if (_finalizeFunc) {
debug(2, "elfloader: calling PLUGIN_finalize");
_finalizeFunc();
_finalizeFunc = 0;
}
#endif
if (!_dlHandle->close())
warning("elfloader: Failed unloading plugin '%s'", _filename.toString(Common::Path::kNativeSeparator).c_str());
delete _dlHandle;
_dlHandle = 0;
}
}
/**
* We override this function in FilePluginProvider to allow the single
* plugin method to create a non-fragmenting memory allocation. We take
* the plugins found and tell the memory manager to allocate space for
* them.
*/
PluginList ELFPluginProvider::getPlugins() {
PluginList pl = FilePluginProvider::getPlugins();
#if defined(UNCACHED_PLUGINS) && !defined(ELF_NO_MEM_MANAGER)
// This static downcast is safe because all of the plugins must
// be ELF plugins
for (PluginList::iterator p = pl.begin(); p != pl.end(); ++p) {
(static_cast<ELFPlugin *>(*p))->trackSize();
}
// The Memory Manager should now allocate space based on the information
// it collected
if (!pl.empty()) {
ELFMemMan.allocateHeap();
}
#endif
return pl;
}
bool ELFPluginProvider::isPluginFilename(const Common::FSNode &node) const {
// Check the plugin suffix
Common::String filename = node.getFileName();
if (!filename.hasSuffix(".PLG") && !filename.hasSuffix(".plg") &&
!filename.hasSuffix(".PLUGIN") && !filename.hasSuffix(".plugin"))
return false;
return true;
}
#endif // defined(DYNAMIC_MODULES) && defined(USE_ELF_LOADER)