1 | // Copyright (C) 2020 The Qt Company Ltd. |
2 | // Copyright (C) 2021 Intel Corporation. |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
4 | #include "qlibrary.h" |
5 | #include "qlibrary_p.h" |
6 | |
7 | #include <q20algorithm.h> |
8 | #include <qbytearraymatcher.h> |
9 | #include <qdebug.h> |
10 | #include <qendian.h> |
11 | #include <qfile.h> |
12 | #include <qfileinfo.h> |
13 | #include <qjsondocument.h> |
14 | #include <qmap.h> |
15 | #include <qmutex.h> |
16 | #include <qoperatingsystemversion.h> |
17 | #include <qstringlist.h> |
18 | |
19 | #ifdef Q_OS_DARWIN |
20 | # include <private/qcore_mac_p.h> |
21 | #endif |
22 | #include <private/qcoreapplication_p.h> |
23 | #include <private/qloggingregistry_p.h> |
24 | #include <private/qsystemerror_p.h> |
25 | |
26 | #include "qcoffpeparser_p.h" |
27 | #include "qelfparser_p.h" |
28 | #include "qfactoryloader_p.h" |
29 | #include "qmachparser_p.h" |
30 | |
31 | #include <qtcore_tracepoints_p.h> |
32 | |
33 | QT_BEGIN_NAMESPACE |
34 | |
35 | using namespace Qt::StringLiterals; |
36 | |
37 | Q_TRACE_POINT(qtcore, QLibraryPrivate_load_entry, const QString &fileName); |
38 | Q_TRACE_POINT(qtcore, QLibraryPrivate_load_exit, bool success); |
39 | |
40 | // On Unix systema and on Windows with MinGW, we can mix and match debug and |
41 | // release plugins without problems. (unless compiled in debug-and-release mode |
42 | // - why?) |
43 | static constexpr bool PluginMustMatchQtDebug = |
44 | QOperatingSystemVersion::currentType() == QOperatingSystemVersion::Windows |
45 | #if defined(Q_CC_MINGW) |
46 | && QT_CONFIG(debug_and_release) |
47 | #endif |
48 | ; |
49 | |
50 | #ifdef QT_NO_DEBUG |
51 | static constexpr bool QtBuildIsDebug = false; |
52 | #else |
53 | static constexpr bool QtBuildIsDebug = true; |
54 | #endif |
55 | |
56 | Q_LOGGING_CATEGORY_WITH_ENV_OVERRIDE(qt_lcDebugPlugins, "QT_DEBUG_PLUGINS" , "qt.core.plugin.loader" ) |
57 | static Q_LOGGING_CATEGORY_WITH_ENV_OVERRIDE(lcDebugLibrary, "QT_DEBUG_PLUGINS" , "qt.core.library" ) |
58 | |
59 | /*! |
60 | \class QLibrary |
61 | \inmodule QtCore |
62 | \reentrant |
63 | \brief The QLibrary class loads shared libraries at runtime. |
64 | |
65 | |
66 | \ingroup plugins |
67 | |
68 | An instance of a QLibrary object operates on a single shared |
69 | object file (which we call a "library", but is also known as a |
70 | "DLL"). A QLibrary provides access to the functionality in the |
71 | library in a platform independent way. You can either pass a file |
72 | name in the constructor, or set it explicitly with setFileName(). |
73 | When loading the library, QLibrary searches in all the |
74 | system-specific library locations (e.g. \c LD_LIBRARY_PATH on |
75 | Unix), unless the file name has an absolute path. |
76 | |
77 | If the file name is an absolute path then an attempt is made to |
78 | load this path first. If the file cannot be found, QLibrary tries |
79 | the name with different platform-specific file prefixes, like |
80 | "lib" on Unix and Mac, and suffixes, like ".so" on Unix, ".dylib" |
81 | on the Mac, or ".dll" on Windows. |
82 | |
83 | If the file path is not absolute then QLibrary modifies the search |
84 | order to try the system-specific prefixes and suffixes first, |
85 | followed by the file path specified. |
86 | |
87 | This makes it possible to specify shared libraries that are only |
88 | identified by their basename (i.e. without their suffix), so the |
89 | same code will work on different operating systems yet still |
90 | minimise the number of attempts to find the library. |
91 | |
92 | The most important functions are load() to dynamically load the |
93 | library file, isLoaded() to check whether loading was successful, |
94 | and resolve() to resolve a symbol in the library. The resolve() |
95 | function implicitly tries to load the library if it has not been |
96 | loaded yet. Multiple instances of QLibrary can be used to access |
97 | the same physical library. Once loaded, libraries remain in memory |
98 | until the application terminates. You can attempt to unload a |
99 | library using unload(), but if other instances of QLibrary are |
100 | using the same library, the call will fail, and unloading will |
101 | only happen when every instance has called unload(). |
102 | |
103 | A typical use of QLibrary is to resolve an exported symbol in a |
104 | library, and to call the C function that this symbol represents. |
105 | This is called "explicit linking" in contrast to "implicit |
106 | linking", which is done by the link step in the build process when |
107 | linking an executable against a library. |
108 | |
109 | The following code snippet loads a library, resolves the symbol |
110 | "mysymbol", and calls the function if everything succeeded. If |
111 | something goes wrong, e.g. the library file does not exist or the |
112 | symbol is not defined, the function pointer will be \nullptr and |
113 | won't be called. |
114 | |
115 | \snippet code/src_corelib_plugin_qlibrary.cpp 0 |
116 | |
117 | The symbol must be exported as a C function from the library for |
118 | resolve() to work. This means that the function must be wrapped in |
119 | an \c{extern "C"} block if the library is compiled with a C++ |
120 | compiler. On Windows, this also requires the use of a \c dllexport |
121 | macro; see resolve() for the details of how this is done. For |
122 | convenience, there is a static resolve() function which you can |
123 | use if you just want to call a function in a library without |
124 | explicitly loading the library first: |
125 | |
126 | \snippet code/src_corelib_plugin_qlibrary.cpp 1 |
127 | |
128 | \sa QPluginLoader |
129 | */ |
130 | |
131 | /*! |
132 | \enum QLibrary::LoadHint |
133 | |
134 | This enum describes the possible hints that can be used to change the way |
135 | libraries are handled when they are loaded. These values indicate how |
136 | symbols are resolved when libraries are loaded, and are specified using |
137 | the setLoadHints() function. |
138 | |
139 | \value ResolveAllSymbolsHint |
140 | Causes all symbols in a library to be resolved when it is loaded, not |
141 | simply when resolve() is called. |
142 | \value ExportExternalSymbolsHint |
143 | Exports unresolved and external symbols in the library so that they can be |
144 | resolved in other dynamically-loaded libraries loaded later. |
145 | \value LoadArchiveMemberHint |
146 | Allows the file name of the library to specify a particular object file |
147 | within an archive file. |
148 | If this hint is given, the filename of the library consists of |
149 | a path, which is a reference to an archive file, followed by |
150 | a reference to the archive member. |
151 | \value PreventUnloadHint |
152 | Prevents the library from being unloaded from the address space if close() |
153 | is called. The library's static variables are not reinitialized if open() |
154 | is called at a later time. |
155 | \value DeepBindHint |
156 | Instructs the linker to prefer definitions in the loaded library |
157 | over exported definitions in the loading application when resolving |
158 | external symbols in the loaded library. This option is only supported |
159 | on Linux. |
160 | |
161 | \sa loadHints |
162 | */ |
163 | |
164 | static QLibraryScanResult qt_find_pattern(const char *s, qsizetype s_len, QString *errMsg) |
165 | { |
166 | /* |
167 | We used to search from the end of the file so we'd skip the code and find |
168 | the read-only data that usually follows. Unfortunately, in debug builds, |
169 | the debug sections come after and are usually much bigger than everything |
170 | else, making this process slower than necessary with debug plugins. |
171 | |
172 | More importantly, the pattern string may exist in the debug information due |
173 | to it being used in the plugin in the first place. |
174 | */ |
175 | #if defined(Q_OF_ELF) |
176 | return QElfParser::parse(data: {s, s_len}, errMsg); |
177 | #elif defined(Q_OF_MACH_O) |
178 | return QMachOParser::parse(s, s_len, errMsg); |
179 | #elif defined(Q_OS_WIN) |
180 | return QCoffPeParser::parse({s, s_len}, errMsg); |
181 | #else |
182 | # warning "Qt does not know how to efficiently parse your platform's binary format; using slow fall-back." |
183 | #endif |
184 | static constexpr auto matcher = [] { |
185 | // QPluginMetaData::MagicString is not NUL-terminated, but |
186 | // qMakeStaticByteArrayMatcher requires its argument to be, so |
187 | // duplicate here, but statically check we didn't mess up: |
188 | constexpr auto &pattern = "QTMETADATA !" ; |
189 | constexpr auto magic = std::string_view(QPluginMetaData::MagicString, |
190 | sizeof(QPluginMetaData::MagicString)); |
191 | static_assert(pattern == magic); |
192 | return qMakeStaticByteArrayMatcher(pattern); |
193 | }(); |
194 | qsizetype i = matcher.indexIn(haystack: {s, s_len}); |
195 | if (i < 0) { |
196 | *errMsg = QLibrary::tr(s: "'%1' is not a Qt plugin" ).arg(a: *errMsg); |
197 | return QLibraryScanResult{}; |
198 | } |
199 | i += sizeof(QPluginMetaData::MagicString); |
200 | return { .pos: i, .length: s_len - i }; |
201 | } |
202 | |
203 | /* |
204 | This opens the specified library, mmaps it into memory, and searches |
205 | for the QT_PLUGIN_VERIFICATION_DATA. The advantage of this approach is that |
206 | we can get the verification data without have to actually load the library. |
207 | This lets us detect mismatches more safely. |
208 | |
209 | Returns \c false if version information is not present, or if the |
210 | information could not be read. |
211 | Returns true if version information is present and successfully read. |
212 | */ |
213 | static QLibraryScanResult findPatternUnloaded(const QString &library, QLibraryPrivate *lib) |
214 | { |
215 | QFile file(library); |
216 | if (!file.open(flags: QIODevice::ReadOnly)) { |
217 | if (lib) |
218 | lib->errorString = file.errorString(); |
219 | qCWarning(qt_lcDebugPlugins, "%ls: cannot open: %ls" , qUtf16Printable(library), |
220 | qUtf16Printable(file.errorString())); |
221 | return {}; |
222 | } |
223 | |
224 | // Files can be bigger than the virtual memory size on 32-bit systems, so |
225 | // we limit to 512 MB there. For 64-bit, we allow up to 2^40 bytes. |
226 | constexpr qint64 MaxMemoryMapSize = |
227 | Q_INT64_C(1) << (sizeof(qsizetype) > 4 ? 40 : 29); |
228 | |
229 | qsizetype fdlen = qMin(a: file.size(), b: MaxMemoryMapSize); |
230 | const char *filedata = reinterpret_cast<char *>(file.map(offset: 0, size: fdlen)); |
231 | |
232 | #ifdef Q_OS_UNIX |
233 | if (filedata == nullptr) { |
234 | // If we can't mmap(), then the dynamic loader won't be able to either. |
235 | // This can't be used as a plugin. |
236 | qCWarning(qt_lcDebugPlugins, "%ls: failed to map to memory: %ls" , |
237 | qUtf16Printable(library), qUtf16Printable(file.errorString())); |
238 | return {}; |
239 | } |
240 | #else |
241 | QByteArray data; |
242 | if (filedata == nullptr) { |
243 | // It's unknown at this point whether Windows supports LoadLibrary() on |
244 | // files that fail to CreateFileMapping / MapViewOfFile, so we err on |
245 | // the side of doing a regular read into memory (up to 64 MB). |
246 | data = file.read(64 * 1024 * 1024); |
247 | filedata = data.constData(); |
248 | fdlen = data.size(); |
249 | } |
250 | #endif |
251 | |
252 | QString errMsg = library; |
253 | QLibraryScanResult r = qt_find_pattern(s: filedata, s_len: fdlen, errMsg: &errMsg); |
254 | if (r.length) { |
255 | #if defined(Q_OF_MACH_O) |
256 | if (r.isEncrypted) |
257 | return r; |
258 | #endif |
259 | if (!lib->metaData.parse(input: QByteArrayView(filedata + r.pos, r.length))) { |
260 | errMsg = lib->metaData.errorString(); |
261 | qCDebug(qt_lcDebugPlugins, "Found invalid metadata in lib %ls: %ls" , |
262 | qUtf16Printable(library), qUtf16Printable(errMsg)); |
263 | } else { |
264 | qCDebug(qt_lcDebugPlugins, "Found metadata in lib %ls, metadata=\n%s\n" , |
265 | qUtf16Printable(library), |
266 | QJsonDocument(lib->metaData.toJson()).toJson().constData()); |
267 | return r; |
268 | } |
269 | } else { |
270 | qCDebug(qt_lcDebugPlugins, "Failed to find metadata in lib %ls: %ls" , |
271 | qUtf16Printable(library), qUtf16Printable(errMsg)); |
272 | } |
273 | |
274 | lib->errorString = QLibrary::tr(s: "Failed to extract plugin meta data from '%1': %2" ) |
275 | .arg(args: library, args&: errMsg); |
276 | return {}; |
277 | } |
278 | |
279 | static void installCoverageTool(QLibraryPrivate *libPrivate) |
280 | { |
281 | #ifdef __COVERAGESCANNER__ |
282 | /* |
283 | __COVERAGESCANNER__ is defined when Qt has been instrumented for code |
284 | coverage by TestCocoon. CoverageScanner is the name of the tool that |
285 | generates the code instrumentation. |
286 | This code is required here when code coverage analysis with TestCocoon |
287 | is enabled in order to allow the loading application to register the plugin |
288 | and then store its execution report. The execution report gathers information |
289 | about each part of the plugin's code that has been used when |
290 | the plugin was loaded by the launching application. |
291 | The execution report for the plugin will go to the same execution report |
292 | as the one defined for the application loading it. |
293 | */ |
294 | |
295 | int ret = __coveragescanner_register_library(libPrivate->fileName.toLocal8Bit()); |
296 | |
297 | if (ret >= 0) { |
298 | qDebug("coverage data for %ls registered" , |
299 | qUtf16Printable(libPrivate->fileName)); |
300 | } else { |
301 | qWarning("could not register %ls: error %d; coverage data may be incomplete" , |
302 | qUtf16Printable(libPrivate->fileName), |
303 | ret); |
304 | } |
305 | } |
306 | #else |
307 | Q_UNUSED(libPrivate); |
308 | #endif |
309 | } |
310 | |
311 | class QLibraryStore |
312 | { |
313 | public: |
314 | inline ~QLibraryStore(); |
315 | static inline QLibraryPrivate *findOrCreate(const QString &fileName, const QString &version, QLibrary::LoadHints loadHints); |
316 | static inline void releaseLibrary(QLibraryPrivate *lib); |
317 | |
318 | static inline void cleanup(); |
319 | |
320 | private: |
321 | static inline QLibraryStore *instance(); |
322 | |
323 | // all members and instance() are protected by qt_library_mutex |
324 | typedef QMap<QString, QLibraryPrivate *> LibraryMap; |
325 | LibraryMap libraryMap; |
326 | }; |
327 | |
328 | Q_CONSTINIT static QBasicMutex qt_library_mutex; |
329 | Q_CONSTINIT static QLibraryStore *qt_library_data = nullptr; |
330 | Q_CONSTINIT static bool qt_library_data_once; |
331 | |
332 | QLibraryStore::~QLibraryStore() |
333 | { |
334 | qt_library_data = nullptr; |
335 | } |
336 | |
337 | inline void QLibraryStore::cleanup() |
338 | { |
339 | QLibraryStore *data = qt_library_data; |
340 | if (!data) |
341 | return; |
342 | |
343 | // find any libraries that are still loaded but have a no one attached to them |
344 | LibraryMap::Iterator it = data->libraryMap.begin(); |
345 | for (; it != data->libraryMap.end(); ++it) { |
346 | QLibraryPrivate *lib = it.value(); |
347 | if (lib->libraryRefCount.loadRelaxed() == 1) { |
348 | if (lib->libraryUnloadCount.loadRelaxed() > 0) { |
349 | Q_ASSERT(lib->pHnd.loadRelaxed()); |
350 | lib->libraryUnloadCount.storeRelaxed(newValue: 1); |
351 | #if defined(Q_OS_DARWIN) |
352 | // We cannot fully unload libraries, as we don't know if there are |
353 | // lingering references (in system threads e.g.) to Objective-C classes |
354 | // defined in the library. |
355 | lib->unload(QLibraryPrivate::NoUnloadSys); |
356 | #else |
357 | lib->unload(); |
358 | #endif |
359 | } |
360 | delete lib; |
361 | it.value() = nullptr; |
362 | } |
363 | } |
364 | |
365 | // dump all objects that remain |
366 | if (lcDebugLibrary().isDebugEnabled()) { |
367 | for (QLibraryPrivate *lib : std::as_const(t&: data->libraryMap)) { |
368 | if (lib) |
369 | qDebug(catFunc: lcDebugLibrary) |
370 | << "On QtCore unload," << lib->fileName << "was leaked, with" |
371 | << lib->libraryRefCount.loadRelaxed() << "users" ; |
372 | } |
373 | } |
374 | |
375 | delete data; |
376 | } |
377 | |
378 | static void qlibraryCleanup() |
379 | { |
380 | QLibraryStore::cleanup(); |
381 | } |
382 | Q_DESTRUCTOR_FUNCTION(qlibraryCleanup) |
383 | |
384 | // must be called with a locked mutex |
385 | QLibraryStore *QLibraryStore::instance() |
386 | { |
387 | if (Q_UNLIKELY(!qt_library_data_once && !qt_library_data)) { |
388 | // only create once per process lifetime |
389 | qt_library_data = new QLibraryStore; |
390 | qt_library_data_once = true; |
391 | } |
392 | return qt_library_data; |
393 | } |
394 | |
395 | inline QLibraryPrivate *QLibraryStore::findOrCreate(const QString &fileName, const QString &version, |
396 | QLibrary::LoadHints loadHints) |
397 | { |
398 | QMutexLocker locker(&qt_library_mutex); |
399 | QLibraryStore *data = instance(); |
400 | |
401 | QString mapName = version.isEmpty() ? fileName : fileName + u'\0' + version; |
402 | |
403 | // check if this library is already loaded |
404 | QLibraryPrivate *lib = nullptr; |
405 | if (Q_LIKELY(data)) { |
406 | lib = data->libraryMap.value(key: mapName); |
407 | if (lib) |
408 | lib->mergeLoadHints(loadHints); |
409 | } |
410 | if (!lib) |
411 | lib = new QLibraryPrivate(fileName, version, loadHints); |
412 | |
413 | // track this library |
414 | if (Q_LIKELY(data) && !fileName.isEmpty()) |
415 | data->libraryMap.insert(key: mapName, value: lib); |
416 | |
417 | lib->libraryRefCount.ref(); |
418 | return lib; |
419 | } |
420 | |
421 | inline void QLibraryStore::releaseLibrary(QLibraryPrivate *lib) |
422 | { |
423 | QMutexLocker locker(&qt_library_mutex); |
424 | QLibraryStore *data = instance(); |
425 | |
426 | if (lib->libraryRefCount.deref()) { |
427 | // still in use |
428 | return; |
429 | } |
430 | |
431 | // no one else is using |
432 | Q_ASSERT(lib->libraryUnloadCount.loadRelaxed() == 0); |
433 | |
434 | if (Q_LIKELY(data) && !lib->fileName.isEmpty()) { |
435 | qsizetype n = erase_if(map&: data->libraryMap, pred: [lib](LibraryMap::iterator it) { |
436 | return it.value() == lib; |
437 | }); |
438 | Q_ASSERT_X(n, "~QLibrary" , "Did not find this library in the library map" ); |
439 | Q_UNUSED(n); |
440 | } |
441 | delete lib; |
442 | } |
443 | |
444 | QLibraryPrivate::QLibraryPrivate(const QString &canonicalFileName, const QString &version, QLibrary::LoadHints loadHints) |
445 | : fileName(canonicalFileName), fullVersion(version), pluginState(MightBeAPlugin) |
446 | { |
447 | loadHintsInt.storeRelaxed(newValue: loadHints.toInt()); |
448 | if (canonicalFileName.isEmpty()) |
449 | errorString = QLibrary::tr(s: "The shared library was not found." ); |
450 | } |
451 | |
452 | QLibraryPrivate *QLibraryPrivate::findOrCreate(const QString &fileName, const QString &version, |
453 | QLibrary::LoadHints loadHints) |
454 | { |
455 | return QLibraryStore::findOrCreate(fileName, version, loadHints); |
456 | } |
457 | |
458 | QLibraryPrivate::~QLibraryPrivate() |
459 | { |
460 | } |
461 | |
462 | void QLibraryPrivate::mergeLoadHints(QLibrary::LoadHints lh) |
463 | { |
464 | // if the library is already loaded, we can't change the load hints |
465 | if (pHnd.loadRelaxed()) |
466 | return; |
467 | |
468 | loadHintsInt.storeRelaxed(newValue: lh.toInt()); |
469 | } |
470 | |
471 | QFunctionPointer QLibraryPrivate::resolve(const char *symbol) |
472 | { |
473 | if (!pHnd.loadRelaxed()) |
474 | return nullptr; |
475 | return resolve_sys(symbol); |
476 | } |
477 | |
478 | void QLibraryPrivate::setLoadHints(QLibrary::LoadHints lh) |
479 | { |
480 | // this locks a global mutex |
481 | QMutexLocker lock(&qt_library_mutex); |
482 | mergeLoadHints(lh); |
483 | } |
484 | |
485 | QObject *QLibraryPrivate::pluginInstance() |
486 | { |
487 | // first, check if the instance is cached and hasn't been deleted |
488 | QObject *obj = [&](){ QMutexLocker locker(&mutex); return inst.data(); }(); |
489 | if (obj) |
490 | return obj; |
491 | |
492 | // We need to call the plugin's factory function. Is that cached? |
493 | // skip increasing the reference count (why? -Thiago) |
494 | QtPluginInstanceFunction factory = instanceFactory.loadAcquire(); |
495 | if (!factory) |
496 | factory = loadPlugin(); |
497 | |
498 | if (!factory) |
499 | return nullptr; |
500 | |
501 | obj = factory(); |
502 | |
503 | // cache again |
504 | QMutexLocker locker(&mutex); |
505 | if (inst) |
506 | obj = inst; |
507 | else |
508 | inst = obj; |
509 | return obj; |
510 | } |
511 | |
512 | bool QLibraryPrivate::load() |
513 | { |
514 | if (pHnd.loadRelaxed()) { |
515 | libraryUnloadCount.ref(); |
516 | return true; |
517 | } |
518 | if (fileName.isEmpty()) |
519 | return false; |
520 | |
521 | Q_TRACE(QLibraryPrivate_load_entry, fileName); |
522 | |
523 | bool ret = load_sys(); |
524 | qCDebug(lcDebugLibrary) |
525 | << fileName |
526 | << (ret ? "loaded library" : qUtf8Printable(u"cannot load: " + errorString)); |
527 | if (ret) { |
528 | //when loading a library we add a reference to it so that the QLibraryPrivate won't get deleted |
529 | //this allows to unload the library at a later time |
530 | libraryUnloadCount.ref(); |
531 | libraryRefCount.ref(); |
532 | installCoverageTool(libPrivate: this); |
533 | } |
534 | |
535 | Q_TRACE(QLibraryPrivate_load_exit, ret); |
536 | |
537 | return ret; |
538 | } |
539 | |
540 | bool QLibraryPrivate::unload(UnloadFlag flag) |
541 | { |
542 | if (!pHnd.loadRelaxed()) |
543 | return false; |
544 | if (libraryUnloadCount.loadRelaxed() > 0 && !libraryUnloadCount.deref()) { // only unload if ALL QLibrary instance wanted to |
545 | QMutexLocker locker(&mutex); |
546 | delete inst.data(); |
547 | if (flag == NoUnloadSys || unload_sys()) { |
548 | qCDebug(lcDebugLibrary) << fileName << "unloaded library" |
549 | << (flag == NoUnloadSys ? "(faked)" : "" ); |
550 | // when the library is unloaded, we release the reference on it so that 'this' |
551 | // can get deleted |
552 | libraryRefCount.deref(); |
553 | pHnd.storeRelaxed(newValue: nullptr); |
554 | instanceFactory.storeRelaxed(newValue: nullptr); |
555 | return true; |
556 | } |
557 | } |
558 | |
559 | return false; |
560 | } |
561 | |
562 | void QLibraryPrivate::release() |
563 | { |
564 | QLibraryStore::releaseLibrary(lib: this); |
565 | } |
566 | |
567 | QtPluginInstanceFunction QLibraryPrivate::loadPlugin() |
568 | { |
569 | if (auto ptr = instanceFactory.loadAcquire()) { |
570 | libraryUnloadCount.ref(); |
571 | return ptr; |
572 | } |
573 | if (pluginState == IsNotAPlugin) |
574 | return nullptr; |
575 | if (load()) { |
576 | auto ptr = reinterpret_cast<QtPluginInstanceFunction>(resolve(symbol: "qt_plugin_instance" )); |
577 | instanceFactory.storeRelease(newValue: ptr); // two threads may store the same value |
578 | return ptr; |
579 | } |
580 | qCDebug(qt_lcDebugPlugins) << "QLibraryPrivate::loadPlugin failed on" << fileName << ":" << errorString; |
581 | pluginState = IsNotAPlugin; |
582 | return nullptr; |
583 | } |
584 | |
585 | /*! |
586 | Returns \c true if \a fileName has a valid suffix for a loadable |
587 | library; otherwise returns \c false. |
588 | |
589 | \table |
590 | \header \li Platform \li Valid suffixes |
591 | \row \li Windows \li \c .dll, \c .DLL |
592 | \row \li Unix/Linux \li \c .so |
593 | \row \li AIX \li \c .a |
594 | \row \li HP-UX \li \c .sl, \c .so (HP-UXi) |
595 | \row \li \macos and iOS \li \c .dylib, \c .bundle, \c .so |
596 | \endtable |
597 | |
598 | Trailing versioning numbers on Unix are ignored. |
599 | */ |
600 | bool QLibrary::isLibrary(const QString &fileName) |
601 | { |
602 | #if defined(Q_OS_WIN) |
603 | return fileName.endsWith(".dll"_L1 , Qt::CaseInsensitive); |
604 | #else // Generic Unix |
605 | # if defined(Q_OS_DARWIN) |
606 | // On Apple platforms, dylib look like libmylib.1.0.0.dylib |
607 | if (fileName.endsWith(".dylib"_L1 )) |
608 | return true; |
609 | # endif |
610 | QString completeSuffix = QFileInfo(fileName).completeSuffix(); |
611 | if (completeSuffix.isEmpty()) |
612 | return false; |
613 | |
614 | // if this throws an empty-array error, you need to fix the #ifdef's: |
615 | const QLatin1StringView candidates[] = { |
616 | # if defined(Q_OS_HPUX) |
617 | /* |
618 | See "HP-UX Linker and Libraries User's Guide", section "Link-time Differences between PA-RISC and IPF": |
619 | "In PA-RISC (PA-32 and PA-64) shared libraries are suffixed with .sl. In IPF (32-bit and 64-bit), |
620 | the shared libraries are suffixed with .so. For compatibility, the IPF linker also supports the .sl suffix." |
621 | */ |
622 | "sl"_L1 , |
623 | # if defined __ia64 |
624 | "so"_L1 , |
625 | # endif |
626 | # elif defined(Q_OS_AIX) |
627 | "a"_L1 , |
628 | "so"_L1 , |
629 | # elif defined(Q_OS_DARWIN) |
630 | "so"_L1 , |
631 | "bundle"_L1 , |
632 | # elif defined(Q_OS_UNIX) |
633 | "so"_L1 , |
634 | # endif |
635 | }; // candidates |
636 | |
637 | auto isValidSuffix = [&candidates](QStringView s) { |
638 | return std::find(first: std::begin(arr: candidates), last: std::end(arr: candidates), val: s) != std::end(arr: candidates); |
639 | }; |
640 | |
641 | // Examples of valid library names: |
642 | // libfoo.so |
643 | // libfoo.so.0 |
644 | // libfoo.so.0.3 |
645 | // libfoo-0.3.so |
646 | // libfoo-0.3.so.0.3.0 |
647 | |
648 | auto suffixes = qTokenize(h&: completeSuffix, n: u'.'); |
649 | auto it = suffixes.begin(); |
650 | const auto end = suffixes.end(); |
651 | |
652 | auto isNumeric = [](QStringView s) { bool ok; (void)s.toInt(ok: &ok); return ok; }; |
653 | |
654 | while (it != end) { |
655 | if (isValidSuffix(*it++)) |
656 | return q20::ranges::all_of(it, end, isNumeric); |
657 | } |
658 | return false; // no valid suffix found |
659 | #endif |
660 | } |
661 | |
662 | static bool qt_get_metadata(QLibraryPrivate *priv, QString *errMsg) |
663 | { |
664 | auto error = [=](QString &&explanation) { |
665 | *errMsg = QLibrary::tr(s: "'%1' is not a Qt plugin (%2)" ).arg(args: priv->fileName, args: std::move(explanation)); |
666 | return false; |
667 | }; |
668 | |
669 | QPluginMetaData metaData; |
670 | QFunctionPointer pfn = priv->resolve(symbol: "qt_plugin_query_metadata_v2" ); |
671 | if (pfn) { |
672 | metaData = reinterpret_cast<QPluginMetaData (*)()>(pfn)(); |
673 | #if QT_VERSION <= QT_VERSION_CHECK(7, 0, 0) |
674 | } else if ((pfn = priv->resolve(symbol: "qt_plugin_query_metadata" ))) { |
675 | metaData = reinterpret_cast<QPluginMetaData (*)()>(pfn)(); |
676 | if (metaData.size < sizeof(QPluginMetaData::MagicHeader)) |
677 | return error(QLibrary::tr(s: "metadata too small" )); |
678 | |
679 | // adjust the meta data to point to the header |
680 | auto data = reinterpret_cast<const char *>(metaData.data); |
681 | data += sizeof(QPluginMetaData::MagicString); |
682 | metaData.data = data; |
683 | metaData.size -= sizeof(QPluginMetaData::MagicString); |
684 | #endif |
685 | } else { |
686 | return error(QLibrary::tr(s: "entrypoint to query the plugin meta data not found" )); |
687 | } |
688 | |
689 | if (metaData.size < sizeof(QPluginMetaData::Header)) |
690 | return error(QLibrary::tr(s: "metadata too small" )); |
691 | |
692 | if (priv->metaData.parse(metaData)) |
693 | return true; |
694 | *errMsg = priv->metaData.errorString(); |
695 | return false; |
696 | } |
697 | |
698 | bool QLibraryPrivate::isPlugin() |
699 | { |
700 | if (pluginState == MightBeAPlugin) |
701 | updatePluginState(); |
702 | |
703 | return pluginState == IsAPlugin; |
704 | } |
705 | |
706 | void QLibraryPrivate::updatePluginState() |
707 | { |
708 | QMutexLocker locker(&mutex); |
709 | errorString.clear(); |
710 | if (pluginState != MightBeAPlugin) |
711 | return; |
712 | |
713 | bool success = false; |
714 | |
715 | #if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) |
716 | if (fileName.endsWith(s: ".debug"_L1 )) { |
717 | // refuse to load a file that ends in .debug |
718 | // these are the debug symbols from the libraries |
719 | // the problem is that they are valid shared library files |
720 | // and dlopen is known to crash while opening them |
721 | |
722 | // pretend we didn't see the file |
723 | errorString = QLibrary::tr(s: "The shared library was not found." ); |
724 | pluginState = IsNotAPlugin; |
725 | return; |
726 | } |
727 | #endif |
728 | |
729 | if (!pHnd.loadRelaxed()) { |
730 | // scan for the plugin metadata without loading |
731 | QLibraryScanResult result = findPatternUnloaded(library: fileName, lib: this); |
732 | #if defined(Q_OF_MACH_O) |
733 | if (result.length && result.isEncrypted) { |
734 | // We found the .qtmetadata section, but since the library is encrypted |
735 | // we need to dlopen() it before we can parse the metadata for further |
736 | // validation. |
737 | qCDebug(qt_lcDebugPlugins, "Library is encrypted. Doing prospective load before parsing metadata" ); |
738 | locker.unlock(); |
739 | load(); |
740 | locker.relock(); |
741 | success = qt_get_metadata(this, &errorString); |
742 | } else |
743 | #endif |
744 | { |
745 | success = result.length != 0; |
746 | } |
747 | } else { |
748 | // library is already loaded (probably via QLibrary) |
749 | // simply get the target function and call it. |
750 | success = qt_get_metadata(priv: this, errMsg: &errorString); |
751 | } |
752 | |
753 | if (!success) { |
754 | if (errorString.isEmpty()) { |
755 | if (fileName.isEmpty()) |
756 | errorString = QLibrary::tr(s: "The shared library was not found." ); |
757 | else |
758 | errorString = QLibrary::tr(s: "The file '%1' is not a valid Qt plugin." ).arg(a: fileName); |
759 | } |
760 | pluginState = IsNotAPlugin; |
761 | return; |
762 | } |
763 | |
764 | pluginState = IsNotAPlugin; // be pessimistic |
765 | |
766 | uint qt_version = uint(metaData.value(k: QtPluginMetaDataKeys::QtVersion).toInteger()); |
767 | bool debug = metaData.value(k: QtPluginMetaDataKeys::IsDebug).toBool(); |
768 | if ((qt_version & 0x00ff00) > (QT_VERSION & 0x00ff00) || (qt_version & 0xff0000) != (QT_VERSION & 0xff0000)) { |
769 | qCDebug(qt_lcDebugPlugins, "In %s:\n" |
770 | " Plugin uses incompatible Qt library (%d.%d.%d) [%s]" , |
771 | QFile::encodeName(fileName).constData(), |
772 | (qt_version&0xff0000) >> 16, (qt_version&0xff00) >> 8, qt_version&0xff, |
773 | debug ? "debug" : "release" ); |
774 | errorString = QLibrary::tr(s: "The plugin '%1' uses incompatible Qt library. (%2.%3.%4) [%5]" ) |
775 | .arg(args: fileName, |
776 | args: QString::number((qt_version & 0xff0000) >> 16), |
777 | args: QString::number((qt_version & 0xff00) >> 8), |
778 | args: QString::number(qt_version & 0xff), |
779 | args: debug ? "debug"_L1 : "release"_L1 ); |
780 | } else if (PluginMustMatchQtDebug && debug != QtBuildIsDebug) { |
781 | //don't issue a qWarning since we will hopefully find a non-debug? --Sam |
782 | errorString = QLibrary::tr(s: "The plugin '%1' uses incompatible Qt library." |
783 | " (Cannot mix debug and release libraries.)" ).arg(a: fileName); |
784 | } else { |
785 | pluginState = IsAPlugin; |
786 | } |
787 | } |
788 | |
789 | /*! |
790 | Loads the library and returns \c true if the library was loaded |
791 | successfully; otherwise returns \c false. Since resolve() always |
792 | calls this function before resolving any symbols it is not |
793 | necessary to call it explicitly. In some situations you might want |
794 | the library loaded in advance, in which case you would use this |
795 | function. |
796 | |
797 | \sa unload() |
798 | */ |
799 | bool QLibrary::load() |
800 | { |
801 | if (!d) |
802 | return false; |
803 | if (d.tag() == Loaded) |
804 | return d->pHnd.loadRelaxed(); |
805 | if (d->load()) { |
806 | d.setTag(Loaded); |
807 | return true; |
808 | } |
809 | return false; |
810 | } |
811 | |
812 | /*! |
813 | Unloads the library and returns \c true if the library could be |
814 | unloaded; otherwise returns \c false. |
815 | |
816 | This happens automatically on application termination, so you |
817 | shouldn't normally need to call this function. |
818 | |
819 | If other instances of QLibrary are using the same library, the |
820 | call will fail, and unloading will only happen when every instance |
821 | has called unload(). |
822 | |
823 | Note that on \macos, dynamic libraries cannot be unloaded. |
824 | QLibrary::unload() will return \c true, but the library will remain |
825 | loaded into the process. |
826 | |
827 | \sa resolve(), load() |
828 | */ |
829 | bool QLibrary::unload() |
830 | { |
831 | if (d.tag() == Loaded) { |
832 | d.setTag(NotLoaded); |
833 | return d->unload(); |
834 | } |
835 | return false; |
836 | } |
837 | |
838 | /*! |
839 | Returns \c true if load() succeeded; otherwise returns \c false. |
840 | |
841 | \note Prior to Qt 6.6, this function would return \c true even without a |
842 | call to load() if another QLibrary object on the same library had caused it |
843 | to be loaded. |
844 | |
845 | \sa load() |
846 | */ |
847 | bool QLibrary::isLoaded() const |
848 | { |
849 | return d.tag() == Loaded; |
850 | } |
851 | |
852 | |
853 | /*! |
854 | Constructs a library with the given \a parent. |
855 | */ |
856 | QLibrary::QLibrary(QObject *parent) : QObject(parent) |
857 | { |
858 | } |
859 | |
860 | |
861 | /*! |
862 | Constructs a library object with the given \a parent that will |
863 | load the library specified by \a fileName. |
864 | |
865 | We recommend omitting the file's suffix in \a fileName, since |
866 | QLibrary will automatically look for the file with the appropriate |
867 | suffix in accordance with the platform, e.g. ".so" on Unix, |
868 | ".dylib" on \macos and iOS, and ".dll" on Windows. (See \l{fileName}.) |
869 | */ |
870 | QLibrary::QLibrary(const QString &fileName, QObject *parent) : QObject(parent) |
871 | { |
872 | setFileName(fileName); |
873 | } |
874 | |
875 | /*! |
876 | Constructs a library object with the given \a parent that will |
877 | load the library specified by \a fileName and major version number \a verNum. |
878 | Currently, the version number is ignored on Windows. |
879 | |
880 | We recommend omitting the file's suffix in \a fileName, since |
881 | QLibrary will automatically look for the file with the appropriate |
882 | suffix in accordance with the platform, e.g. ".so" on Unix, |
883 | ".dylib" on \macos and iOS, and ".dll" on Windows. (See \l{fileName}.) |
884 | */ |
885 | QLibrary::QLibrary(const QString &fileName, int verNum, QObject *parent) : QObject(parent) |
886 | { |
887 | setFileNameAndVersion(fileName, verNum); |
888 | } |
889 | |
890 | /*! |
891 | Constructs a library object with the given \a parent that will |
892 | load the library specified by \a fileName and full version number \a version. |
893 | Currently, the version number is ignored on Windows. |
894 | |
895 | We recommend omitting the file's suffix in \a fileName, since |
896 | QLibrary will automatically look for the file with the appropriate |
897 | suffix in accordance with the platform, e.g. ".so" on Unix, |
898 | ".dylib" on \macos and iOS, and ".dll" on Windows. (See \l{fileName}.) |
899 | */ |
900 | QLibrary::QLibrary(const QString &fileName, const QString &version, QObject *parent) |
901 | : QObject(parent) |
902 | { |
903 | setFileNameAndVersion(fileName, version); |
904 | } |
905 | |
906 | /*! |
907 | Destroys the QLibrary object. |
908 | |
909 | Unless unload() was called explicitly, the library stays in memory |
910 | until the application terminates. |
911 | |
912 | \sa isLoaded(), unload() |
913 | */ |
914 | QLibrary::~QLibrary() |
915 | { |
916 | if (d) |
917 | d->release(); |
918 | } |
919 | |
920 | /*! |
921 | \property QLibrary::fileName |
922 | \brief the file name of the library |
923 | |
924 | We recommend omitting the file's suffix in the file name, since |
925 | QLibrary will automatically look for the file with the appropriate |
926 | suffix (see isLibrary()). |
927 | |
928 | When loading the library, QLibrary searches in all system-specific |
929 | library locations (for example, \c LD_LIBRARY_PATH on Unix), unless the |
930 | file name has an absolute path. After loading the library |
931 | successfully, fileName() returns the fully-qualified file name of |
932 | the library, including the full path to the library if one was given |
933 | in the constructor or passed to setFileName(). |
934 | |
935 | For example, after successfully loading the "GL" library on Unix |
936 | platforms, fileName() will return "libGL.so". If the file name was |
937 | originally passed as "/usr/lib/libGL", fileName() will return |
938 | "/usr/lib/libGL.so". |
939 | */ |
940 | |
941 | void QLibrary::setFileName(const QString &fileName) |
942 | { |
943 | setFileNameAndVersion(fileName, version: QString()); |
944 | } |
945 | |
946 | QString QLibrary::fileName() const |
947 | { |
948 | if (d) { |
949 | QMutexLocker locker(&d->mutex); |
950 | return d->qualifiedFileName.isEmpty() ? d->fileName : d->qualifiedFileName; |
951 | } |
952 | return QString(); |
953 | } |
954 | |
955 | /*! |
956 | \fn void QLibrary::setFileNameAndVersion(const QString &fileName, int versionNumber) |
957 | |
958 | Sets the fileName property and major version number to \a fileName |
959 | and \a versionNumber respectively. |
960 | The \a versionNumber is ignored on Windows. |
961 | |
962 | \sa setFileName() |
963 | */ |
964 | void QLibrary::setFileNameAndVersion(const QString &fileName, int verNum) |
965 | { |
966 | setFileNameAndVersion(fileName, version: verNum >= 0 ? QString::number(verNum) : QString()); |
967 | } |
968 | |
969 | /*! |
970 | \since 4.4 |
971 | |
972 | Sets the fileName property and full version number to \a fileName |
973 | and \a version respectively. |
974 | The \a version parameter is ignored on Windows. |
975 | |
976 | \sa setFileName() |
977 | */ |
978 | void QLibrary::setFileNameAndVersion(const QString &fileName, const QString &version) |
979 | { |
980 | QLibrary::LoadHints lh; |
981 | if (d) { |
982 | lh = d->loadHints(); |
983 | d->release(); |
984 | } |
985 | QLibraryPrivate *dd = QLibraryPrivate::findOrCreate(fileName, version, loadHints: lh); |
986 | d = QTaggedPointer(dd, NotLoaded); // we haven't load()ed |
987 | } |
988 | |
989 | /*! |
990 | Returns the address of the exported symbol \a symbol. The library is |
991 | loaded if necessary. The function returns \nullptr if the symbol could |
992 | not be resolved or if the library could not be loaded. |
993 | |
994 | Example: |
995 | \snippet code/src_corelib_plugin_qlibrary.cpp 2 |
996 | |
997 | The symbol must be exported as a C function from the library. This |
998 | means that the function must be wrapped in an \c{extern "C"} if |
999 | the library is compiled with a C++ compiler. On Windows you must |
1000 | also explicitly export the function from the DLL using the |
1001 | \c{__declspec(dllexport)} compiler directive, for example: |
1002 | |
1003 | \snippet code/src_corelib_plugin_qlibrary.cpp 3 |
1004 | |
1005 | with \c MY_EXPORT defined as |
1006 | |
1007 | \snippet code/src_corelib_plugin_qlibrary.cpp 4 |
1008 | */ |
1009 | QFunctionPointer QLibrary::resolve(const char *symbol) |
1010 | { |
1011 | if (!isLoaded() && !load()) |
1012 | return nullptr; |
1013 | return d->resolve(symbol); |
1014 | } |
1015 | |
1016 | /*! |
1017 | \overload |
1018 | |
1019 | Loads the library \a fileName and returns the address of the |
1020 | exported symbol \a symbol. Note that \a fileName should not |
1021 | include the platform-specific file suffix; (see \l{fileName}). The |
1022 | library remains loaded until the application exits. |
1023 | |
1024 | The function returns \nullptr if the symbol could not be resolved or if |
1025 | the library could not be loaded. |
1026 | |
1027 | \sa resolve() |
1028 | */ |
1029 | QFunctionPointer QLibrary::resolve(const QString &fileName, const char *symbol) |
1030 | { |
1031 | QLibrary library(fileName); |
1032 | return library.resolve(symbol); |
1033 | } |
1034 | |
1035 | /*! |
1036 | \overload |
1037 | |
1038 | Loads the library \a fileName with major version number \a verNum and |
1039 | returns the address of the exported symbol \a symbol. |
1040 | Note that \a fileName should not include the platform-specific file suffix; |
1041 | (see \l{fileName}). The library remains loaded until the application exits. |
1042 | \a verNum is ignored on Windows. |
1043 | |
1044 | The function returns \nullptr if the symbol could not be resolved or if |
1045 | the library could not be loaded. |
1046 | |
1047 | \sa resolve() |
1048 | */ |
1049 | QFunctionPointer QLibrary::resolve(const QString &fileName, int verNum, const char *symbol) |
1050 | { |
1051 | QLibrary library(fileName, verNum); |
1052 | return library.resolve(symbol); |
1053 | } |
1054 | |
1055 | /*! |
1056 | \overload |
1057 | \since 4.4 |
1058 | |
1059 | Loads the library \a fileName with full version number \a version and |
1060 | returns the address of the exported symbol \a symbol. |
1061 | Note that \a fileName should not include the platform-specific file suffix; |
1062 | (see \l{fileName}). The library remains loaded until the application exits. |
1063 | \a version is ignored on Windows. |
1064 | |
1065 | The function returns \nullptr if the symbol could not be resolved or if |
1066 | the library could not be loaded. |
1067 | |
1068 | \sa resolve() |
1069 | */ |
1070 | QFunctionPointer QLibrary::resolve(const QString &fileName, const QString &version, const char *symbol) |
1071 | { |
1072 | QLibrary library(fileName, version); |
1073 | return library.resolve(symbol); |
1074 | } |
1075 | |
1076 | /*! |
1077 | \since 4.2 |
1078 | |
1079 | Returns a text string with the description of the last error that occurred. |
1080 | Currently, errorString will only be set if load(), unload() or resolve() for some reason fails. |
1081 | */ |
1082 | QString QLibrary::errorString() const |
1083 | { |
1084 | QString str; |
1085 | if (d) { |
1086 | QMutexLocker locker(&d->mutex); |
1087 | str = d->errorString; |
1088 | } |
1089 | return str.isEmpty() ? tr(s: "Unknown error" ) : str; |
1090 | } |
1091 | |
1092 | /*! |
1093 | \property QLibrary::loadHints |
1094 | \brief Give the load() function some hints on how it should behave. |
1095 | |
1096 | You can give some hints on how the symbols are resolved. Usually, |
1097 | the symbols are not resolved at load time, but resolved lazily, |
1098 | (that is, when resolve() is called). If you set the loadHints to |
1099 | ResolveAllSymbolsHint, then all symbols will be resolved at load time |
1100 | if the platform supports it. |
1101 | |
1102 | Setting ExportExternalSymbolsHint will make the external symbols in the |
1103 | library available for resolution in subsequent loaded libraries. |
1104 | |
1105 | If LoadArchiveMemberHint is set, the file name |
1106 | is composed of two components: A path which is a reference to an |
1107 | archive file followed by the second component which is the reference to |
1108 | the archive member. For instance, the fileName \c libGL.a(shr_64.o) will refer |
1109 | to the library \c shr_64.o in the archive file named \c libGL.a. This |
1110 | is only supported on the AIX platform. |
1111 | |
1112 | The interpretation of the load hints is platform dependent, and if |
1113 | you use it you are probably making some assumptions on which platform |
1114 | you are compiling for, so use them only if you understand the consequences |
1115 | of them. |
1116 | |
1117 | By default, none of these flags are set, so libraries will be loaded with |
1118 | lazy symbol resolution, and will not export external symbols for resolution |
1119 | in other dynamically-loaded libraries. |
1120 | |
1121 | \note Setting this property after the library has been loaded has no effect |
1122 | and loadHints() will not reflect those changes. |
1123 | |
1124 | \note This property is shared among all QLibrary instances that refer to |
1125 | the same library. |
1126 | */ |
1127 | void QLibrary::setLoadHints(LoadHints hints) |
1128 | { |
1129 | if (!d) { |
1130 | d = QLibraryPrivate::findOrCreate(fileName: QString()); // ugly, but we need a d-ptr |
1131 | d->errorString.clear(); |
1132 | } |
1133 | d->setLoadHints(hints); |
1134 | } |
1135 | |
1136 | QLibrary::LoadHints QLibrary::loadHints() const |
1137 | { |
1138 | return d ? d->loadHints() : QLibrary::LoadHints(); |
1139 | } |
1140 | |
1141 | /* Internal, for debugging */ |
1142 | bool qt_debug_component() |
1143 | { |
1144 | static int debug_env = QT_PREPEND_NAMESPACE(qEnvironmentVariableIntValue)(varName: "QT_DEBUG_PLUGINS" ); |
1145 | return debug_env != 0; |
1146 | } |
1147 | |
1148 | QT_END_NAMESPACE |
1149 | |
1150 | #include "moc_qlibrary.cpp" |
1151 | |