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