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 // Extract all the information immediately, and then release the handles to avoid file
50 // locking.
51 mWordSize = ntHeaderWordSize(mNtHeaders);
52 mMachineArch = mNtHeaders->FileHeader.Machine;
53 if (mWordSize == 32) {
54 mDependentLibs =
55 determineDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS32 *>(mNtHeaders));
56 mIsDebug = determineDebug(reinterpret_cast<const IMAGE_NT_HEADERS32 *>(mNtHeaders));
57 } else {
58 mDependentLibs =
59 determineDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS64 *>(mNtHeaders));
60 mIsDebug = determineDebug(reinterpret_cast<const IMAGE_NT_HEADERS64 *>(mNtHeaders));
61 }
62
63 mValid = true;
64
65 releaseFileResources();
66}
67
68PeHeaderInfo::~PeHeaderInfo()
69{
70 releaseFileResources();
71}
72
73void PeHeaderInfo::releaseFileResources()
74{
75 if (mFileMemory) {
76 UnmapViewOfFile(mFileMemory);
77 mFileMemory = nullptr;
78 }
79
80 if (mFileMapHandle != NULL) {
81 CloseHandle(mFileMapHandle);
82 mFileMapHandle = NULL;
83 }
84
85 if (mFileHandle != NULL && mFileHandle != INVALID_HANDLE_VALUE) {
86 CloseHandle(mFileHandle);
87 mFileHandle = NULL;
88 }
89
90 mNtHeaders = nullptr;
91}
92
93bool PeHeaderInfo::isValid()
94{
95 return mValid;
96}
97
98QString PeHeaderInfo::errorMessage()
99{
100 return mErrorMessage;
101}
102
103unsigned int PeHeaderInfo::wordSize()
104{
105 return mWordSize;
106}
107
108unsigned int PeHeaderInfo::machineArch()
109{
110 return mMachineArch;
111}
112
113QStringList PeHeaderInfo::dependentLibs()
114{
115 return mDependentLibs;
116}
117
118bool PeHeaderInfo::isDebug()
119{
120 return mIsDebug.value_or(false);
121}
122
123IMAGE_NT_HEADERS *PeHeaderInfo::getNtHeader()
124{
125 IMAGE_DOS_HEADER *dosHeader = static_cast<PIMAGE_DOS_HEADER>(mFileMemory);
126 // Check DOS header consistency
127 if (IsBadReadPtr(dosHeader, sizeof(IMAGE_DOS_HEADER))
128 || dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
129 mErrorMessage = QString::fromLatin1("DOS header check failed.");
130 return 0;
131 }
132 // Retrieve NT header
133 char *ntHeaderC = static_cast<char *>(mFileMemory) + dosHeader->e_lfanew;
134 IMAGE_NT_HEADERS *ntHeaders = reinterpret_cast<IMAGE_NT_HEADERS *>(ntHeaderC);
135 // check NT header consistency
136 if (IsBadReadPtr(ntHeaders, sizeof(ntHeaders->Signature))
137 || ntHeaders->Signature != IMAGE_NT_SIGNATURE
138 || IsBadReadPtr(&ntHeaders->FileHeader, sizeof(IMAGE_FILE_HEADER))) {
139 mErrorMessage = QString::fromLatin1("NT header check failed.");
140 return 0;
141 }
142 // Check magic
143 if (!ntHeaderWordSize(ntHeaders)) {
144 mErrorMessage = QString::fromLatin1("NT header check failed; magic %1 is invalid.").arg(ntHeaders->OptionalHeader.Magic);
145 return 0;
146 }
147 // Check section headers
148 IMAGE_SECTION_HEADER *sectionHeaders = IMAGE_FIRST_SECTION(ntHeaders);
149 if (IsBadReadPtr(sectionHeaders,
150 ntHeaders->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER))) {
151 mErrorMessage = QString::fromLatin1("NT header section header check failed.");
152 return 0;
153 }
154 return ntHeaders;
155}
156
157QString PeHeaderInfo::stringFromRvaPtr(const void *rvaPtr)
158{
159 return QString::fromLocal8Bit(static_cast<const char *>(rvaPtr));
160}
161
162PeHeaderInfo::MsvcDebugRuntimeResult PeHeaderInfo::checkMsvcDebugRuntime()
163{
164 for (const QString &lib : mDependentLibs) {
165 qsizetype pos = 0;
166 if (lib.startsWith("MSVCR"_L1, Qt::CaseInsensitive)
167 || lib.startsWith("MSVCP"_L1, Qt::CaseInsensitive)
168 || lib.startsWith("VCRUNTIME"_L1, Qt::CaseInsensitive)
169 || lib.startsWith("VCCORLIB"_L1, Qt::CaseInsensitive)
170 || lib.startsWith("CONCRT"_L1, Qt::CaseInsensitive)
171 || lib.startsWith("UCRTBASE"_L1, Qt::CaseInsensitive)) {
172 qsizetype lastDotPos = lib.lastIndexOf(u'.');
173 pos = -1 == lastDotPos ? 0 : lastDotPos - 1;
174 }
175
176 if (pos > 0) {
177 const auto removeExtraSuffix = [&lib, &pos](const QString &suffix) -> void {
178 if (lib.contains(suffix, Qt::CaseInsensitive))
179 pos -= suffix.size();
180 };
181 removeExtraSuffix("_app"_L1);
182 removeExtraSuffix("_atomic_wait"_L1);
183 removeExtraSuffix("_codecvt_ids"_L1);
184 }
185
186 if (pos)
187 return lib.at(pos).toLower() == u'd' ? MsvcDebugRuntime : MsvcReleaseRuntime;
188 }
189 return NoMsvcRuntime;
190}
191
192template <class ImageNtHeader>
193QStringList PeHeaderInfo::readImportSections(const ImageNtHeader *ntHeaders)
194{
195 // Get import directory entry RVA and read out
196 const DWORD importsStartRVA = mNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
197 if (!importsStartRVA) {
198 mErrorMessage = QString::fromLatin1("Failed to find IMAGE_DIRECTORY_ENTRY_IMPORT entry.");
199 return QStringList();
200 }
201 const IMAGE_IMPORT_DESCRIPTOR *importDesc = static_cast<const IMAGE_IMPORT_DESCRIPTOR *>(rvaToPtr(importsStartRVA, ntHeaders, mFileMemory));
202 if (!importDesc) {
203 mErrorMessage = QString::fromLatin1("Failed to find IMAGE_IMPORT_DESCRIPTOR entry.");
204 return QStringList();
205 }
206 QStringList result;
207 for ( ; importDesc->Name; ++importDesc)
208 result.push_back(stringFromRvaPtr(rvaToPtr(importDesc->Name, ntHeaders, mFileMemory)));
209
210 // Read delay-loaded DLLs, see http://msdn.microsoft.com/en-us/magazine/cc301808.aspx .
211 // Check on grAttr bit 1 whether this is the format using RVA's > VS 6
212 if (const DWORD delayedImportsStartRVA = mNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT].VirtualAddress) {
213 const ImgDelayDescr *delayedImportDesc = static_cast<const ImgDelayDescr *>(rvaToPtr(delayedImportsStartRVA, ntHeaders, mFileMemory));
214 for ( ; delayedImportDesc->rvaDLLName && (delayedImportDesc->grAttrs & 1); ++delayedImportDesc)
215 result.push_back(stringFromRvaPtr(rvaToPtr(delayedImportDesc->rvaDLLName, ntHeaders, mFileMemory)));
216 }
217
218 return result;
219}
220
221template <class ImageNtHeader>
222QStringList PeHeaderInfo::determineDependentLibs(const ImageNtHeader *nth)
223{
224 return readImportSections(nth);
225}
226
227template <class ImageNtHeader>
228bool PeHeaderInfo::determineDebug(const ImageNtHeader *nth)
229{
230 if (nth->FileHeader.Characteristics & IMAGE_FILE_DEBUG_STRIPPED)
231 return false;
232
233 if (mDependentLibs.isEmpty())
234 mDependentLibs = determineDependentLibs(nth);
235
236 const bool hasDebugEntry = mNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size;
237 // When an MSVC debug entry is present, check whether the debug runtime
238 // is actually used to detect -release / -force-debug-info builds.
239 const MsvcDebugRuntimeResult msvcrt = checkMsvcDebugRuntime();
240 if (msvcrt == NoMsvcRuntime)
241 return hasDebugEntry;
242 else
243 return hasDebugEntry && msvcrt == MsvcDebugRuntime;
244}
245
246template <class ImageNtHeader>
247const IMAGE_SECTION_HEADER *PeHeaderInfo::findSectionHeader(DWORD rva, const ImageNtHeader *nTHeader)
248{
249 const IMAGE_SECTION_HEADER *section = IMAGE_FIRST_SECTION(nTHeader);
250 const IMAGE_SECTION_HEADER *sectionEnd = section + mNtHeaders->FileHeader.NumberOfSections;
251 for ( ; section < sectionEnd; ++section)
252 if (rva >= section->VirtualAddress && rva < (section->VirtualAddress + section->Misc.VirtualSize))
253 return section;
254 return 0;
255}
256
257template <class ImageNtHeader>
258const void *PeHeaderInfo::rvaToPtr(DWORD rva, const ImageNtHeader *nTHeader, const void *imageBase)
259{
260 const IMAGE_SECTION_HEADER *sectionHdr = findSectionHeader(rva, nTHeader);
261 if (!sectionHdr)
262 return 0;
263 const DWORD delta = sectionHdr->VirtualAddress - sectionHdr->PointerToRawData;
264 return static_cast<const char *>(imageBase) + rva - delta;
265}
266
267template <class ImageNtHeader>
268unsigned int PeHeaderInfo::ntHeaderWordSize(const ImageNtHeader *header)
269{
270 // defines IMAGE_NT_OPTIONAL_HDR32_MAGIC, IMAGE_NT_OPTIONAL_HDR64_MAGIC
271 enum { imageNtOptionlHeader32Magic = 0x10b, imageNtOptionlHeader64Magic = 0x20b };
272 if (header->OptionalHeader.Magic == imageNtOptionlHeader32Magic)
273 return 32;
274 if (header->OptionalHeader.Magic == imageNtOptionlHeader64Magic)
275 return 64;
276 return 0;
277}
278
281
282PeHeaderInfo *PeHeaderInfoCache::peHeaderInfo(const QString &fileName)
283{
284 if (peCache->contains(fileName))
285 return peCache->value(fileName);
286
287 auto *peHeaderInfo = new PeHeaderInfo(fileName);
288 peCache->insert(fileName, peHeaderInfo);
289 return peHeaderInfo;
290}
291
292QT_END_NAMESPACE
unsigned int ntHeaderWordSize(const ImageNtHeader *header)
Combined button and popup list for selecting options.
QMap< QString, PeHeaderInfo * > PeHeaderInfoMap
Q_GLOBAL_STATIC(QReadWriteLock, g_updateMutex)