1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "peutils.h"
27
28 #include <QStringList>
29 #ifdef Q_OS_WIN
30 # include <utils/winutils.h>
31 # include <QDir>
32 # include <QDebug>
33 # include <climits>
34 # include <windows.h>
35
36 using Utils::winErrorMessage;
37
38 // Create a pointer from base and offset when rummaging around in
39 // a memory mapped file
40
41 template <class Ptr>
makePtr(void * base,ptrdiff_t offset)42 inline Ptr *makePtr(void *base, ptrdiff_t offset)
43 {
44 return reinterpret_cast<Ptr*>(static_cast<char*>(base) + offset);
45 }
46
47 // CodeView header
48 struct CV_HEADER
49 {
50 DWORD CvSignature; // NBxx
51 LONG Offset; // Always 0 for NB10
52 };
53
54 // CodeView NB10 debug information of a PDB 2.00 file (VS 6)
55 struct CV_INFO_PDB20
56 {
57 CV_HEADER Header;
58 DWORD Signature;
59 DWORD Age;
60 BYTE PdbFileName[1];
61 };
62
63 // CodeView RSDS debug information of a PDB 7.00 file
64 struct CV_INFO_PDB70
65 {
66 DWORD CvSignature;
67 GUID Signature;
68 DWORD Age;
69 BYTE PdbFileName[1];
70 };
71
72 // Retrieve the NT image header of an executable via the legacy DOS header.
getNtHeader(void * fileMemory,QString * errorMessage)73 static IMAGE_NT_HEADERS *getNtHeader(void *fileMemory, QString *errorMessage)
74 {
75 auto dosHeader = static_cast<PIMAGE_DOS_HEADER>(fileMemory);
76 // Check DOS header consistency
77 if (IsBadReadPtr(dosHeader, sizeof(IMAGE_DOS_HEADER))
78 || dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
79 *errorMessage = QString::fromLatin1("DOS header check failed.");
80 return nullptr;
81 }
82 // Retrieve NT header
83 auto ntHeaders = makePtr<IMAGE_NT_HEADERS>(dosHeader, dosHeader->e_lfanew);
84 // check NT header consistency
85 if (IsBadReadPtr(ntHeaders, sizeof(ntHeaders->Signature))
86 || ntHeaders->Signature != IMAGE_NT_SIGNATURE
87 || IsBadReadPtr(&ntHeaders->FileHeader, sizeof(IMAGE_FILE_HEADER))) {
88 *errorMessage = QString::fromLatin1("NT header check failed.");
89 return nullptr;
90 }
91 // Check magic
92 const WORD magic = ntHeaders->OptionalHeader.Magic;
93 #ifdef __GNUC__ // MinGW does not have complete 64bit definitions.
94 if (magic != 0x10b) {
95 *errorMessage = QString::fromLatin1("NT header check failed; magic %1 is not that of a 32-bit executable.").
96 arg(magic);
97 return 0;
98 }
99 #else
100 if (magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC && magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
101 *errorMessage = QString::fromLatin1("NT header check failed; magic %1 is none of %2, %3.").
102 arg(magic).arg(IMAGE_NT_OPTIONAL_HDR32_MAGIC).arg(IMAGE_NT_OPTIONAL_HDR64_MAGIC);
103 return nullptr;
104 }
105 #endif
106 // Check section headers
107 IMAGE_SECTION_HEADER *sectionHeaders = IMAGE_FIRST_SECTION(ntHeaders);
108 if (IsBadReadPtr(sectionHeaders, ntHeaders->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER))) {
109 *errorMessage = QString::fromLatin1("NT header section header check failed.");
110 return nullptr;
111 }
112 return ntHeaders;
113 }
114
115 // Find the COFF section an RVA belongs to and convert to file offset
getFileOffsetFromRVA(IMAGE_NT_HEADERS * ntHeaders,DWORD rva,DWORD * fileOffset)116 static bool getFileOffsetFromRVA(IMAGE_NT_HEADERS *ntHeaders, DWORD rva, DWORD* fileOffset)
117 {
118 IMAGE_SECTION_HEADER *sectionHeader = IMAGE_FIRST_SECTION(ntHeaders);
119 for (int i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++, sectionHeader++) {
120 const DWORD sectionSize = sectionHeader->Misc.VirtualSize ?
121 sectionHeader->Misc.VirtualSize : sectionHeader->SizeOfRawData;
122 if ((rva >= sectionHeader->VirtualAddress) && (rva < sectionHeader->VirtualAddress + sectionSize)) {
123 const DWORD diff = sectionHeader->VirtualAddress - sectionHeader->PointerToRawData;
124 *fileOffset = rva - diff;
125 return true;
126 }
127 }
128 return false;
129 }
130
131 // Retrieve debug directory and number of entries
getDebugDirectory(IMAGE_NT_HEADERS * ntHeaders,void * fileMemory,IMAGE_DEBUG_DIRECTORY ** debugDir,int * count,QString * errorMessage)132 static bool getDebugDirectory(IMAGE_NT_HEADERS *ntHeaders, void *fileMemory,
133 IMAGE_DEBUG_DIRECTORY **debugDir, int *count, QString *errorMessage)
134 {
135 DWORD debugDirRva = 0;
136 DWORD debugDirSize;
137 *debugDir = nullptr;
138 *count = 0;
139
140 #ifdef __GNUC__ // MinGW does not have complete 64bit definitions.
141 auto optionalHeader = reinterpret_cast<IMAGE_OPTIONAL_HEADER*>(&(ntHeaders->OptionalHeader));
142 debugDirRva = optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress;
143 debugDirSize = optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size;
144 #else
145 // Find the virtual address
146 const bool is64Bit = ntHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC;
147 if (is64Bit) {
148 auto optionalHeader64 = reinterpret_cast<IMAGE_OPTIONAL_HEADER64*>(&(ntHeaders->OptionalHeader));
149 debugDirRva = optionalHeader64->DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress;
150 debugDirSize = optionalHeader64->DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size;
151 } else {
152 auto optionalHeader32 = reinterpret_cast<IMAGE_OPTIONAL_HEADER32*>(&(ntHeaders->OptionalHeader));
153 debugDirRva = optionalHeader32->DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress;
154 debugDirSize = optionalHeader32->DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size;
155 }
156 #endif
157
158 // Empty. This is the case for MinGW binaries
159 if (debugDirSize == 0)
160 return true;
161 // Look up in file
162 DWORD debugDirOffset;
163 if (!getFileOffsetFromRVA(ntHeaders, debugDirRva, &debugDirOffset)) {
164 *errorMessage = QString::fromLatin1("Unable to locate debug dir RVA %1/%2.").arg(debugDirRva).arg(debugDirSize);
165 return false;
166 }
167 *debugDir = makePtr<IMAGE_DEBUG_DIRECTORY>(fileMemory, debugDirOffset);
168 // Check
169 if (IsBadReadPtr(*debugDir, debugDirSize) || debugDirSize < sizeof(IMAGE_DEBUG_DIRECTORY)) {
170 *errorMessage = QString::fromLatin1("Debug directory corrupted.");
171 return false;
172 }
173
174 *count = debugDirSize / sizeof(IMAGE_DEBUG_DIRECTORY);
175 return debugDir;
176 }
177
178 // Return the PDB file of a Code View debug section
getPDBFileOfCodeViewSection(void * debugInfo,DWORD size)179 static QString getPDBFileOfCodeViewSection(void *debugInfo, DWORD size)
180 {
181 static const DWORD CV_SIGNATURE_NB10 = 0x3031424e; // '01BN';
182 static const DWORD CV_SIGNATURE_RSDS = 0x53445352; // 'SDSR';
183 if (IsBadReadPtr(debugInfo, size) || size < sizeof(DWORD))
184 return QString();
185
186 const DWORD cvSignature = *static_cast<DWORD*>(debugInfo);
187 if (cvSignature == CV_SIGNATURE_NB10) {
188 auto cvInfo = static_cast<CV_INFO_PDB20*>(debugInfo);
189 if (IsBadReadPtr(debugInfo, sizeof(CV_INFO_PDB20)))
190 return QString();
191 auto pdbFileName = reinterpret_cast<CHAR*>(cvInfo->PdbFileName);
192 if (IsBadStringPtrA(pdbFileName, UINT_MAX))
193 return QString();
194 return QString::fromLocal8Bit(pdbFileName);
195 }
196 if (cvSignature == CV_SIGNATURE_RSDS) {
197 auto cvInfo = static_cast<CV_INFO_PDB70*>(debugInfo);
198 if (IsBadReadPtr(debugInfo, sizeof(CV_INFO_PDB70)))
199 return QString();
200 auto pdbFileName = reinterpret_cast<CHAR*>(cvInfo->PdbFileName);
201 if (IsBadStringPtrA(pdbFileName, UINT_MAX))
202 return QString();
203 return QString::fromLocal8Bit(pdbFileName);
204 }
205 return QString();
206 }
207
208 // Collect all PDB files of all debug sections
collectPDBfiles(void * fileMemory,IMAGE_DEBUG_DIRECTORY * directoryBase,int count,QStringList * pdbFiles)209 static void collectPDBfiles(void *fileMemory, IMAGE_DEBUG_DIRECTORY *directoryBase, int count, QStringList *pdbFiles)
210 {
211 for (int i = 0; i < count; i++, directoryBase++)
212 if (directoryBase->Type == IMAGE_DEBUG_TYPE_CODEVIEW) {
213 const QString pdb = getPDBFileOfCodeViewSection(static_cast<char*>(fileMemory) + directoryBase->PointerToRawData, directoryBase->SizeOfData);
214 if (!pdb.isEmpty())
215 pdbFiles->push_back(pdb);
216 }
217 }
218
219 namespace Debugger {
220 namespace Internal {
221
getPDBFiles(const QString & peExecutableFileName,QStringList * rc,QString * errorMessage)222 bool getPDBFiles(const QString &peExecutableFileName, QStringList *rc, QString *errorMessage)
223 {
224 HANDLE hFile = NULL;
225 HANDLE hFileMap = NULL;
226 void *fileMemory = nullptr;
227 bool success = false;
228
229 rc->clear();
230 do {
231 // Create a memory mapping of the file
232 hFile = CreateFile(reinterpret_cast<const WCHAR*>(peExecutableFileName.utf16()), GENERIC_READ, FILE_SHARE_READ, NULL,
233 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
234 if (hFile == INVALID_HANDLE_VALUE || hFile == NULL) {
235 *errorMessage = QString::fromLatin1("Cannot open \"%1\": %2")
236 .arg(QDir::toNativeSeparators(peExecutableFileName),
237 winErrorMessage(GetLastError()));
238 break;
239 }
240
241 hFileMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
242 if (hFileMap == NULL) {
243 *errorMessage = QString::fromLatin1("Cannot create file mapping of \"%1\": %2")
244 .arg(QDir::toNativeSeparators(peExecutableFileName),
245 winErrorMessage(GetLastError()));
246 break;
247 }
248
249 fileMemory = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
250 if (!fileMemory) {
251 *errorMessage = QString::fromLatin1("Cannot map \"%1\": %2")
252 .arg(QDir::toNativeSeparators(peExecutableFileName),
253 winErrorMessage(GetLastError()));
254 break;
255 }
256
257 IMAGE_NT_HEADERS *ntHeaders = getNtHeader(fileMemory, errorMessage);
258 if (!ntHeaders)
259 break;
260
261 int debugSectionCount;
262 IMAGE_DEBUG_DIRECTORY *debugDir;
263 if (!getDebugDirectory(ntHeaders, fileMemory, &debugDir, &debugSectionCount, errorMessage))
264 break;
265 if (debugSectionCount)
266 collectPDBfiles(fileMemory, debugDir, debugSectionCount, rc);
267 success = true;
268 } while (false);
269
270 if (fileMemory)
271 UnmapViewOfFile(fileMemory);
272
273 if (hFileMap != NULL)
274 CloseHandle(hFileMap);
275
276 if (hFile != NULL && hFile != INVALID_HANDLE_VALUE)
277 CloseHandle(hFile);
278
279 return success;
280 }
281
282 #else // Q_OS_WIN
283
284 namespace Debugger {
285 namespace Internal {
286
287 bool getPDBFiles(const QString &peExecutableFileName, QStringList *rc, QString *errorMessage)
288 {
289 Q_UNUSED(peExecutableFileName)
290 rc->clear();
291 *errorMessage = "Not implemented.";
292 return false;
293 }
294
295 #endif // Q_OS_WIN
296
297 } // namespace Internal
298 } // namespace Debugger
299