Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
peheaderinfo.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "peheaderinfo.h"
5
6#include <QtCore/private/qsystemerror_p.h>
7#include <QString>
8#include <QMap>
9
10#if defined(Q_OS_WIN)
11# include <QtCore/qt_windows.h>
12# include <QtCore/private/qsystemerror_p.h>
13# include <shlwapi.h>
14# include <delayimp.h>
15#endif // Q_OS_WIN
16
18
19using namespace Qt::StringLiterals;
20
21PeHeaderInfo::PeHeaderInfo(const QString fileName)
22{
23 mFileHandle = CreateFile(reinterpret_cast<const WCHAR*>(fileName.utf16()), GENERIC_READ, FILE_SHARE_READ, NULL,
24 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
25 if (mFileHandle == INVALID_HANDLE_VALUE || mFileHandle == NULL) {
26 mErrorMessage = QString::fromLatin1("Cannot open '%1': %2")
27 .arg(fileName, QSystemError::windowsString());
28 return;
29 }
30
31 mFileMapHandle = CreateFileMapping(mFileHandle, NULL, PAGE_READONLY, 0, 0, NULL);
32 if (mFileMapHandle == NULL) {
33 mErrorMessage = QString::fromLatin1("Cannot create file mapping of '%1': %2")
34 .arg(fileName, QSystemError::windowsString());
35 return;
36 }
37
38 mFileMemory = MapViewOfFile(mFileMapHandle, FILE_MAP_READ, 0, 0, 0);
39 if (!mFileMemory) {
40 mErrorMessage = QString::fromLatin1("Cannot map '%1': %2")
41 .arg(fileName, QSystemError::windowsString());
42 return;
43 }
44
45 mNtHeaders = getNtHeader();
46 if (!mNtHeaders)
47 return;
48
49 mValid = true;
50}
51
52PeHeaderInfo::~PeHeaderInfo()
53{
54 if (mFileMemory)
55 UnmapViewOfFile(mFileMemory);
56
57 if (mFileMapHandle != NULL)
58 CloseHandle(mFileMapHandle);
59
60 if (mFileHandle != NULL && mFileHandle != INVALID_HANDLE_VALUE)
61 CloseHandle(mFileHandle);
62}
63
64bool PeHeaderInfo::isValid()
65{
66 return mValid;
67}
68
69QString PeHeaderInfo::errorMessage()
70{
71 return mErrorMessage;
72}
73
74unsigned int PeHeaderInfo::wordSize()
75{
76 if (!isValid())
77 return 0;
78
79 return ntHeaderWordSize(mNtHeaders);
80}
81
82unsigned int PeHeaderInfo::machineArch()
83{
84 if (!isValid())
85 return 0;
86
87 return mNtHeaders->FileHeader.Machine;
88}
89
90QStringList PeHeaderInfo::dependentLibs()
91{
92 if (!isValid())
93 return QStringList();
94
95 if (!mDependentLibs.isEmpty())
96 return mDependentLibs;
97
98 if (wordSize() == 32) {
99 mDependentLibs
100 = determineDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS32 *>(mNtHeaders));
101 } else {
102 mDependentLibs
103 = determineDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS64 *>(mNtHeaders));
104 }
105 return mDependentLibs;
106}
107
108bool PeHeaderInfo::isDebug()
109{
110 if (!mIsDebug.has_value()) {
111 QStringList dependents = dependentLibs();
112 if (wordSize() == 32) {
113 mIsDebug = determineDebug(reinterpret_cast<const IMAGE_NT_HEADERS32 *>(mNtHeaders));
114 } else {
115 mIsDebug = determineDebug(reinterpret_cast<const IMAGE_NT_HEADERS64 *>(mNtHeaders));
116 }
117 }
118
119 return mIsDebug.value();
120}
121
122IMAGE_NT_HEADERS *PeHeaderInfo::getNtHeader()
123{
124 IMAGE_DOS_HEADER *dosHeader = static_cast<PIMAGE_DOS_HEADER>(mFileMemory);
125 // Check DOS header consistency
126 if (IsBadReadPtr(dosHeader, sizeof(IMAGE_DOS_HEADER))
127 || dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
128 mErrorMessage = QString::fromLatin1("DOS header check failed.");
129 return 0;
130 }
131 // Retrieve NT header
132 char *ntHeaderC = static_cast<char *>(mFileMemory) + dosHeader->e_lfanew;
133 IMAGE_NT_HEADERS *ntHeaders = reinterpret_cast<IMAGE_NT_HEADERS *>(ntHeaderC);
134 // check NT header consistency
135 if (IsBadReadPtr(ntHeaders, sizeof(ntHeaders->Signature))
136 || ntHeaders->Signature != IMAGE_NT_SIGNATURE
137 || IsBadReadPtr(&ntHeaders->FileHeader, sizeof(IMAGE_FILE_HEADER))) {
138 mErrorMessage = QString::fromLatin1("NT header check failed.");
139 return 0;
140 }
141 // Check magic
142 if (!ntHeaderWordSize(ntHeaders)) {
143 mErrorMessage = QString::fromLatin1("NT header check failed; magic %1 is invalid.").arg(ntHeaders->OptionalHeader.Magic);
144 return 0;
145 }
146 // Check section headers
147 IMAGE_SECTION_HEADER *sectionHeaders = IMAGE_FIRST_SECTION(ntHeaders);
148 if (IsBadReadPtr(sectionHeaders,
149 ntHeaders->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER))) {
150 mErrorMessage = QString::fromLatin1("NT header section header check failed.");
151 return 0;
152 }
153 return ntHeaders;
154}
155
156QString PeHeaderInfo::stringFromRvaPtr(const void *rvaPtr)
157{
158 return QString::fromLocal8Bit(static_cast<const char *>(rvaPtr));
159}
160
161PeHeaderInfo::MsvcDebugRuntimeResult PeHeaderInfo::checkMsvcDebugRuntime()
162{
163 for (const QString &lib : mDependentLibs) {
164 qsizetype pos = 0;
165 if (lib.startsWith("MSVCR"_L1, Qt::CaseInsensitive)
166 || lib.startsWith("MSVCP"_L1, Qt::CaseInsensitive)
167 || lib.startsWith("VCRUNTIME"_L1, Qt::CaseInsensitive)
168 || lib.startsWith("VCCORLIB"_L1, Qt::CaseInsensitive)
169 || lib.startsWith("CONCRT"_L1, Qt::CaseInsensitive)
170 || lib.startsWith("UCRTBASE"_L1, Qt::CaseInsensitive)) {
171 qsizetype lastDotPos = lib.lastIndexOf(u'.');
172 pos = -1 == lastDotPos ? 0 : lastDotPos - 1;
173 }
174
175 if (pos > 0) {
176 const auto removeExtraSuffix = [&lib, &pos](const QString &suffix) -> void {
177 if (lib.contains(suffix, Qt::CaseInsensitive))
178 pos -= suffix.size();
179 };
180 removeExtraSuffix("_app"_L1);
181 removeExtraSuffix("_atomic_wait"_L1);
182 removeExtraSuffix("_codecvt_ids"_L1);
183 }
184
185 if (pos)
186 return lib.at(pos).toLower() == u'd' ? MsvcDebugRuntime : MsvcReleaseRuntime;
187 }
188 return NoMsvcRuntime;
189}
190
191template <class ImageNtHeader>
192QStringList PeHeaderInfo::readImportSections(const ImageNtHeader *ntHeaders)
193{
194 // Get import directory entry RVA and read out
195 const DWORD importsStartRVA = mNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
196 if (!importsStartRVA) {
197 mErrorMessage = QString::fromLatin1("Failed to find IMAGE_DIRECTORY_ENTRY_IMPORT entry.");
198 return QStringList();
199 }
200 const IMAGE_IMPORT_DESCRIPTOR *importDesc = static_cast<const IMAGE_IMPORT_DESCRIPTOR *>(rvaToPtr(importsStartRVA, ntHeaders, mFileMemory));
201 if (!importDesc) {
202 mErrorMessage = QString::fromLatin1("Failed to find IMAGE_IMPORT_DESCRIPTOR entry.");
203 return QStringList();
204 }
205 QStringList result;
206 for ( ; importDesc->Name; ++importDesc)
207 result.push_back(stringFromRvaPtr(rvaToPtr(importDesc->Name, ntHeaders, mFileMemory)));
208
209 // Read delay-loaded DLLs, see http://msdn.microsoft.com/en-us/magazine/cc301808.aspx .
210 // Check on grAttr bit 1 whether this is the format using RVA's > VS 6
211 if (const DWORD delayedImportsStartRVA = mNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT].VirtualAddress) {
212 const ImgDelayDescr *delayedImportDesc = static_cast<const ImgDelayDescr *>(rvaToPtr(delayedImportsStartRVA, ntHeaders, mFileMemory));
213 for ( ; delayedImportDesc->rvaDLLName && (delayedImportDesc->grAttrs & 1); ++delayedImportDesc)
214 result.push_back(stringFromRvaPtr(rvaToPtr(delayedImportDesc->rvaDLLName, ntHeaders, mFileMemory)));
215 }
216
217 return result;
218}
219
220template <class ImageNtHeader>
221QStringList PeHeaderInfo::determineDependentLibs(const ImageNtHeader *nth)
222{
223 return readImportSections(nth);
224}
225
226template <class ImageNtHeader>
227bool PeHeaderInfo::determineDebug(const ImageNtHeader *nth)
228{
229 if (nth->FileHeader.Characteristics & IMAGE_FILE_DEBUG_STRIPPED)
230 return false;
231
232 if (mDependentLibs.isEmpty())
233 mDependentLibs = determineDependentLibs(nth);
234
235 const bool hasDebugEntry = mNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size;
236 // When an MSVC debug entry is present, check whether the debug runtime
237 // is actually used to detect -release / -force-debug-info builds.
238 const MsvcDebugRuntimeResult msvcrt = checkMsvcDebugRuntime();
239 if (msvcrt == NoMsvcRuntime)
240 return hasDebugEntry;
241 else
242 return hasDebugEntry && msvcrt == MsvcDebugRuntime;
243}
244
245template <class ImageNtHeader>
246const IMAGE_SECTION_HEADER *PeHeaderInfo::findSectionHeader(DWORD rva, const ImageNtHeader *nTHeader)
247{
248 const IMAGE_SECTION_HEADER *section = IMAGE_FIRST_SECTION(nTHeader);
249 const IMAGE_SECTION_HEADER *sectionEnd = section + mNtHeaders->FileHeader.NumberOfSections;
250 for ( ; section < sectionEnd; ++section)
251 if (rva >= section->VirtualAddress && rva < (section->VirtualAddress + section->Misc.VirtualSize))
252 return section;
253 return 0;
254}
255
256template <class ImageNtHeader>
257const void *PeHeaderInfo::rvaToPtr(DWORD rva, const ImageNtHeader *nTHeader, const void *imageBase)
258{
259 const IMAGE_SECTION_HEADER *sectionHdr = findSectionHeader(rva, nTHeader);
260 if (!sectionHdr)
261 return 0;
262 const DWORD delta = sectionHdr->VirtualAddress - sectionHdr->PointerToRawData;
263 return static_cast<const char *>(imageBase) + rva - delta;
264}
265
266template <class ImageNtHeader>
267unsigned int PeHeaderInfo::ntHeaderWordSize(const ImageNtHeader *header)
268{
269 // defines IMAGE_NT_OPTIONAL_HDR32_MAGIC, IMAGE_NT_OPTIONAL_HDR64_MAGIC
270 enum { imageNtOptionlHeader32Magic = 0x10b, imageNtOptionlHeader64Magic = 0x20b };
271 if (header->OptionalHeader.Magic == imageNtOptionlHeader32Magic)
272 return 32;
273 if (header->OptionalHeader.Magic == imageNtOptionlHeader64Magic)
274 return 64;
275 return 0;
276}
277
280
281PeHeaderInfo *PeHeaderInfoCache::peHeaderInfo(const QString &fileName)
282{
283 if (peCache->contains(fileName))
284 return peCache->value(fileName);
285
286 auto *peHeaderInfo = new PeHeaderInfo(fileName);
287 peCache->insert(fileName, peHeaderInfo);
288 return peHeaderInfo;
289}
290
291QT_END_NAMESPACE
unsigned int ntHeaderWordSize(const ImageNtHeader *header)
QMap< QString, PeHeaderInfo * > PeHeaderInfoMap
Q_GLOBAL_STATIC(QReadWriteLock, g_updateMutex)