Line data Source code
1 : /* Copyright (C) 2021 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
4 : * 0 A.D. is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 2 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * 0 A.D. is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "precompiled.h"
19 :
20 : #include "DllLoader.h"
21 :
22 : #include "lib/timer.h"
23 : #include "lib/posix/posix_dlfcn.h"
24 : #include "ps/CStr.h"
25 :
26 : #if OS_MACOSX
27 : # include "lib/sysdep/os/osx/osx_bundle.h"
28 : #endif
29 :
30 : static void* const HANDLE_UNAVAILABLE = (void*)-1;
31 :
32 : // directory to search for libraries (optionally set by --libdir at build-time,
33 : // optionally overridden by -libdir at run-time in the test executable);
34 : // if we don't have an explicit libdir then the linker will look in DT_RUNPATH
35 : // (which we set to $ORIGIN) to find it in the executable's directory
36 : #ifdef INSTALLED_LIBDIR
37 : static CStr g_Libdir = STRINGIZE(INSTALLED_LIBDIR);
38 : #else
39 1 : static CStr g_Libdir = "";
40 : #endif
41 :
42 : // note: on Linux, lib is prepended to the SO file name
43 : #if OS_UNIX
44 1 : static CStr g_DllPrefix = "lib";
45 : #else
46 : static CStr g_DllPrefix = "";
47 : #endif
48 :
49 : // we usually want to use the same debug/release build type as the
50 : // main executable (the library should also be efficient in release builds and
51 : // allow easy symbol access in debug builds). however, that version of the
52 : // library might be missing, so we check for both.
53 : // this works because the interface is binary-compatible.
54 : #ifndef NDEBUG
55 2 : static CStr suffixes[] = { "_dbg", "" }; // (order matters)
56 : #else
57 : static CStr suffixes[] = { "", "_dbg" };
58 : #endif
59 :
60 : // NB: our Windows dlopen() function changes the extension to .dll
61 2 : static CStr extensions[] = {
62 : ".so",
63 : #if OS_MACOSX
64 : ".dylib" // supported by OS X dlopen
65 : #endif
66 1 : };
67 :
68 : // (This class is currently only used by 'Collada' and 'AtlasUI' which follow
69 : // the naming/location convention above - it'll need to be changed if we want
70 : // to support other DLLs.)
71 :
72 6 : static CStr GenerateFilename(const CStr& name, const CStr& suffix, const CStr& extension)
73 : {
74 6 : CStr n;
75 :
76 6 : if (!g_Libdir.empty())
77 0 : n = g_Libdir + "/";
78 :
79 : #if OS_MACOSX
80 : if (osx_IsAppBundleValid())
81 : {
82 : // We are in a bundle, in which case the lib directory is ../Frameworks
83 : // relative to the binary, so we use a helper function to get the system path
84 : // (alternately we could use @executable_path as below, but this seems better)
85 : CStr frameworksPath = osx_GetBundleFrameworksPath();
86 : if (!frameworksPath.empty())
87 : n = frameworksPath + "/";
88 : }
89 : else
90 : {
91 : // On OS X, dlopen will search the current working directory for the library to
92 : // to load if given only a filename. But the CWD is not guaranteed to be
93 : // binaries/system (where our dylibs are placed) and it's not when e.g. the user
94 : // launches the game from Finder.
95 : // To work around this, we use the @executable_path variable, which should always
96 : // resolve to binaries/system, if we're not a bundle.
97 : // (see Apple's dyld(1) and dlopen(3) man pages for more info)
98 : n = "@executable_path/";
99 : }
100 : #endif
101 :
102 6 : n += g_DllPrefix + name + suffix + extension;
103 6 : return n;
104 : }
105 :
106 :
107 :
108 : // @param name base name of the library (excluding prefix/suffix/extension)
109 : // @param errors receives descriptions of any and all errors encountered
110 : // @return valid handle or 0
111 6 : static void* LoadAnyVariant(const CStr& name, std::stringstream& errors)
112 : {
113 6 : for (size_t idxSuffix = 0; idxSuffix < ARRAY_SIZE(suffixes); idxSuffix++)
114 : {
115 6 : for (size_t idxExtension = 0; idxExtension < ARRAY_SIZE(extensions); idxExtension++)
116 : {
117 6 : CStr filename = GenerateFilename(name, suffixes[idxSuffix], extensions[idxExtension]);
118 :
119 : // we don't really care when relocations take place, but one of
120 : // {RTLD_NOW, RTLD_LAZY} must be specified. go with the former because
121 : // it is safer and matches the Windows load behavior.
122 6 : const int flags = RTLD_LOCAL|RTLD_NOW;
123 6 : void* handle = dlopen(filename.c_str(), flags);
124 6 : if (handle)
125 6 : return handle;
126 : else
127 0 : errors << "dlopen(" << filename << ") failed: " << dlerror() << "; ";
128 : }
129 : }
130 :
131 0 : return 0; // none worked
132 : }
133 :
134 :
135 12 : DllLoader::DllLoader(const char* name, CLogger::ELogMethod loadErrorLogMethod)
136 12 : : m_Name(name), m_Handle(0), m_LoadErrorLogMethod(loadErrorLogMethod)
137 : {
138 12 : }
139 :
140 24 : DllLoader::~DllLoader()
141 : {
142 12 : if (IsLoaded())
143 5 : dlclose(m_Handle);
144 12 : }
145 :
146 54 : bool DllLoader::IsLoaded() const
147 : {
148 54 : return (m_Handle != 0 && m_Handle != HANDLE_UNAVAILABLE);
149 : }
150 :
151 6 : bool DllLoader::LoadDLL()
152 : {
153 : // first time: try to open the shared object
154 : // postcondition: m_Handle valid or == HANDLE_UNAVAILABLE.
155 6 : if (m_Handle == 0)
156 : {
157 12 : TIMER(L"LoadDLL");
158 :
159 12 : std::stringstream errors;
160 6 : m_Handle = LoadAnyVariant(m_Name, errors);
161 6 : if (!m_Handle) // (only report errors if nothing worked)
162 : {
163 0 : LogLoadError(errors.str().c_str());
164 0 : m_Handle = HANDLE_UNAVAILABLE;
165 : }
166 : }
167 :
168 6 : return (m_Handle != HANDLE_UNAVAILABLE);
169 : }
170 :
171 1 : void DllLoader::Unload()
172 : {
173 1 : if (!IsLoaded())
174 0 : return;
175 :
176 1 : dlclose(m_Handle);
177 1 : m_Handle = 0;
178 : }
179 :
180 24 : void DllLoader::LoadSymbolInternal(const char* name, void** fptr) const
181 : {
182 24 : if (!IsLoaded())
183 : {
184 0 : debug_warn(L"Loading symbol from invalid DLL");
185 0 : *fptr = NULL;
186 0 : throw PSERROR_DllLoader_DllNotLoaded();
187 : }
188 :
189 24 : *fptr = dlsym(m_Handle, name);
190 24 : if (*fptr == NULL)
191 0 : throw PSERROR_DllLoader_SymbolNotFound();
192 24 : }
193 :
194 0 : void DllLoader::LogLoadError(const char* errors)
195 : {
196 0 : switch (m_LoadErrorLogMethod)
197 : {
198 0 : case CLogger::Normal:
199 0 : LOGMESSAGE("DllLoader: %s", errors);
200 0 : break;
201 0 : case CLogger::Warning:
202 0 : LOGWARNING("DllLoader: %s", errors);
203 0 : break;
204 0 : case CLogger::Error:
205 0 : LOGERROR("DllLoader: %s", errors);
206 0 : break;
207 : }
208 0 : }
209 :
210 0 : void DllLoader::OverrideLibdir(const char* libdir)
211 : {
212 0 : g_Libdir = libdir;
213 3 : }
|