diff options
author | Thiago Macieira <thiago.macieira@intel.com> | 2015-09-17 20:20:52 -0700 |
---|---|---|
committer | Thiago Macieira <thiago.macieira@intel.com> | 2022-03-14 18:26:09 -0800 |
commit | 13ad42c67988aab09bff0ee83903e1264d6048fe (patch) | |
tree | 400e3146a68d13631664051c3ad37c571f4b5fc7 | |
parent | b2edef557cf97ae9251be3675f8179a7d2298faa (diff) |
qversiontagging: Add Windows support
Details in the comment in the header. I haven't recently tested with
MSVC, but I designed this originally in 2015 when I did have access to
Visual Studio so I know it works (I've confirmed with Godbolt).
With MinGW, this produces assembly:
.globl qt_version_tag_used
.section .qtversion,"dr"
.linkonce same_size
.align 16
qt_version_tag_used:
.quad __imp_qt_version_tag_6_4
.quad 394240
394240 is 0x060400. The DLL import table contains:
0015f03c 0015f294 00000000 00000000 00165478 0016012c
DLL Name: Qt6Core.t.dll
vma: Hint/Ord Member-Name Bound-To
161318 7 _Z11qt_assert_xPKcS0_S0_i
...
164cf8 6073 qt_version_tag_6_4
Change-Id: I42e7ef1a481840699a8dffff1404f45b13f60ba6
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
Reviewed-by: Kai Koehne <kai.koehne@qt.io>
-rw-r--r-- | src/corelib/global/qversiontagging.cpp | 14 | ||||
-rw-r--r-- | src/corelib/global/qversiontagging.h | 112 |
2 files changed, 113 insertions, 13 deletions
diff --git a/src/corelib/global/qversiontagging.cpp b/src/corelib/global/qversiontagging.cpp index a0d15ae871..8f33c8af8c 100644 --- a/src/corelib/global/qversiontagging.cpp +++ b/src/corelib/global/qversiontagging.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 Intel Corporation. +** Copyright (C) 2022 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -38,6 +38,7 @@ ****************************************************************************/ #include "qglobal.h" +#include "qversiontagging.h" #define SYM QT_MANGLE_NAMESPACE(qt_version_tag) //#define SSYM QT_STRINGIFY(SYM) @@ -47,11 +48,18 @@ Q_CORE_EXPORT extern const char sym ## _ ## m ## _ ## n = 0; \ asm(".symver " QT_STRINGIFY(sym) "_" QT_STRINGIFY(m) "_" QT_STRINGIFY(n) ", " \ QT_STRINGIFY(sym) separator "Qt_" QT_STRINGIFY(m) "." QT_STRINGIFY(n)) + +#elif defined(Q_OS_WIN) +# define make_versioned_symbol2(sym, m, n, separator) \ + Q_CORE_EXPORT extern const char sym ## _ ## m ## _ ## n = 0; + #else # define make_versioned_symbol2(sym, m, n, separator) #endif #define make_versioned_symbol(sym, m, n, separator) make_versioned_symbol2(sym, m, n, separator) +QT_BEGIN_NAMESPACE + extern "C" { #if QT_VERSION_MINOR > 0 make_versioned_symbol(SYM, QT_VERSION_MAJOR, 0, "@"); @@ -108,3 +116,7 @@ make_versioned_symbol(SYM, QT_VERSION_MAJOR, 15, "@"); // the default version: make_versioned_symbol(SYM, QT_VERSION_MAJOR, QT_VERSION_MINOR, "@@"); } + +static_assert(std::is_trivially_destructible_v<QtPrivate::QVersionTag>); + +QT_END_NAMESPACE diff --git a/src/corelib/global/qversiontagging.h b/src/corelib/global/qversiontagging.h index 75c2e9df7e..721835cb85 100644 --- a/src/corelib/global/qversiontagging.h +++ b/src/corelib/global/qversiontagging.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 Intel Corporation. +** Copyright (C) 2022 Intel Corporation. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -46,22 +46,93 @@ QT_BEGIN_NAMESPACE /* - * Ugly hack warning and explanation: + * Explanation * - * This file causes all ELF modules, be they libraries or applications, to use the - * qt_version_tag symbol that is present in QtCore. Such symbol is versioned, - * so the linker will automatically pull the current Qt version and add it to - * the ELF header of the library/application. The assembly produces one section - * called ".qtversion" containing two 32-bit values. The first is a - * relocation to the qt_version_tag symbol (which is what causes the ELF - * version to get used). The second value is the current Qt version at the time - * of compilation. + * This file causes all libraries, plugins, and applications that #include this + * file to automatically pull in a symbol found in QtCore that encodes the + * current Qt version number at the time of compilation. The relocation is + * designed so that it's impossible for the dynamic linker to perform lazy + * binding. Instead, it must resolve at load time or fail. That way, attempting + * to load such a library or plugin while an older QtCore is loaded will fail. + * Similarly, if an older QtCore is found when launching an application, the + * application will fail to launch. + * + * It's also possible to inspect which version is required by decoding the + * .qtversion section. The second pointer-sized variable is the required + * version, for example, for Qt 6.4.1: + * + * Hex dump of section [18] '.qtversion', 16 bytes at offset 0x1ee48: + * 0x00000000 b0ffffff ffffffff 01040600 00000000 ................ + * ^^^^^^^^ ^^^^^^^^ * * There will only be one copy of the section in the output library or application. + * + * This functionality can be disabled by defining QT_NO_VERSION_TAGGING. It's + * disabled if Qt was built statically. + * + * Windows notes: + * + * On Windows, the address of a __declspec(dllimport) variable is not a + * constant expression, unlike Unix systems. So we instead use the address of + * the import variable, which is created by prefixing the external name with + * "__imp_". Using that variable causes an import of the corresponding symbol + * from QtCore DLL. + * + * With MinGW (GCC and Clang), we use a C++17 inline variable, so the compiler + * and linker automatically merge the variables. The "used" __attribute__ + * tells the compiler to always emit that variable, whether it's used or not. + * + * MSVC has no equivalent to that attribute, so instead we create an extern + * const variable and tell the linker to merge them all via + * __declspec(selectany). + * + * ELF notes (Linux, FreeBSD): + * + * The symbol in question is simply "qt_version_tag" in both QtCore and in + * this ELF module, but it has an ELF version attached to it (see + * qversiontagging.cpp and QtFlagHandlingHelpers.cmake). That way, the error + * message from the dynamic linker will say it can't find version "Qt_6.x". + * + * This is currently implemented only for x86 by way of inline assembly. We + * inform the the linker to merge all TUs' .qtversion sections by way of the + * "comdat" attribute in the .section directive. The first pointer-sized + * variable is a GOT reference to qt_version_tag (that is: the address of + * qt_version_tag is stored in the .got section and the linker inserts the + * displacement to that pointer). */ +namespace QtPrivate { +struct QVersionTag +{ + const void *symbol; + quintptr version; + constexpr QVersionTag(const void *sym, int currentVersion = QT_VERSION) + : symbol(sym), version(currentVersion) + {} +}; +} + #if defined(QT_BUILD_CORE_LIB) || defined(QT_BOOTSTRAPPED) || defined(QT_NO_VERSION_TAGGING) || defined(QT_STATIC) // don't make tags in QtCore, bootstrapped systems or if the user asked not to +#elif defined(Q_OS_WIN) +# ifdef _WIN64 +// 64-bit calling convention does not prepend a _ +# define QT_MANGLE_IMPORT_PREFIX __imp_ +# else +// 32-bit convention does prepend a _ +# define QT_MANGLE_IMPORT_PREFIX _imp__ +# endif +# ifdef Q_CC_MSVC +# pragma section(".qtversion",read,shared) +# define QT_VERSION_TAG_SECTION __declspec(allocate(".qtversion")) +# define QT_VERSION_TAG_ATTRIBUTE __declspec(selectany) extern const +# else +# define QT_VERSION_TAG_ATTRIBUTE __attribute__((used)) constexpr inline +# endif +# define QT_VERSION_TAG2(sym, imp) \ + extern "C" const char * const imp; \ + QT_VERSION_TAG_ATTRIBUTE QT_VERSION_TAG_SECTION QtPrivate::QVersionTag sym ## _used(&imp) +# define QT_VERSION_TAG(sym, imp) QT_VERSION_TAG2(sym, imp) #elif defined(Q_CC_GNU) && !defined(Q_OS_ANDROID) # if defined(Q_PROCESSOR_X86) && (defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD_KERNEL)) # if defined(Q_PROCESSOR_X86_64) && QT_POINTER_SIZE == 8 // x86-64 64-bit @@ -69,7 +140,7 @@ QT_BEGIN_NAMESPACE # else // x86 or x86-64 32-bit (x32) # define QT_VERSION_TAG_RELOC(sym) ".long " QT_STRINGIFY(QT_MANGLE_NAMESPACE(sym)) "@GOT\n" # endif -# define QT_VERSION_TAG(sym) \ +# define QT_VERSION_TAG(sym, imp) \ asm ( \ ".section .qtversion, \"aG\", @progbits, " QT_STRINGIFY(QT_MANGLE_NAMESPACE(sym)) ", comdat\n" \ ".align 8\n" \ @@ -81,8 +152,25 @@ QT_BEGIN_NAMESPACE # endif #endif +#ifdef Q_OF_ELF +# define QT_VERSION_TAG_SYMBOL(prefix, sym, m, n) sym +#else +# define QT_VERSION_TAG_SYMBOL2(prefix, sym, m, n) prefix ## sym ## _ ## m ## _ ## n +# define QT_VERSION_TAG_SYMBOL(prefix, sym, m, n) QT_VERSION_TAG_SYMBOL2(prefix, sym, m, n) +#endif + #if defined(QT_VERSION_TAG) -QT_VERSION_TAG(qt_version_tag); +# ifndef QT_VERSION_TAG_SECTION +# define QT_VERSION_TAG_SECTION __attribute__((section(".qtversion"))) +# endif +# define QT_MANGLED_VERSION_TAG_IMPORT QT_VERSION_TAG_SYMBOL(QT_MANGLE_IMPORT_PREFIX, qt_version_tag, QT_VERSION_MAJOR, QT_VERSION_MINOR) +# define QT_MANGLED_VERSION_TAG QT_VERSION_TAG_SYMBOL(, QT_MANGLE_NAMESPACE(qt_version_tag), QT_VERSION_MAJOR, QT_VERSION_MINOR) + +QT_VERSION_TAG(QT_MANGLED_VERSION_TAG, QT_MANGLED_VERSION_TAG_IMPORT); + +# undef QT_MANGLED_VERSION_TAG +# undef QT_MANGLED_VERSION_TAG_IMPORT +# undef QT_VERSION_TAG_SECTION #endif QT_END_NAMESPACE |