1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4//*****************************************************************************
5// dbgutil.cpp
6//
7
8//
9//*****************************************************************************
10
11//
12// Various common helpers for PE resource reading used by multiple debug components.
13//
14
15#include <dbgutil.h>
16#include "corerror.h"
17#include <assert.h>
18#include <stdio.h>
19
20// Returns the RVA of the resource section for the module specified by the given data target and module base.
21// Returns failure if the module doesn't have a resource section.
22//
23// Arguments
24// pDataTarget - dataTarget for the process we are inspecting
25// moduleBaseAddress - base address of a module we should inspect
26// pwImageFileMachine - updated with the Machine from the IMAGE_FILE_HEADER
27// pdwResourceSectionRVA - updated with the resultant RVA on success
28HRESULT GetMachineAndResourceSectionRVA(ICorDebugDataTarget* pDataTarget,
29 ULONG64 moduleBaseAddress,
30 WORD* pwImageFileMachine,
31 DWORD* pdwResourceSectionRVA)
32{
33 // Fun code ahead... below is a hand written PE decoder with some of the file offsets hardcoded.
34 // It supports no more than what we absolutely have to to get to the resources we need. Any of the
35 // magic numbers used below can be determined by using the public documentation on the web.
36 //
37 // Yes utilcode has a PE decoder, no it does not support reading its data through a datatarget
38 // It was easier to inspect the small portion that I needed than to shove an abstraction layer under
39 // our utilcode and then make sure everything still worked.
40
41 // SECURITY WARNING: all data provided by the data target should be considered untrusted.
42 // Do not allow malicious data to cause large reads, memory allocations, buffer overflow,
43 // or any other undesirable behavior.
44
45 HRESULT hr = S_OK;
46
47 // at offset 3c in the image is a 4 byte file pointer that indicates where the PE signature is
48 IMAGE_DOS_HEADER dosHeader;
49 hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress, (BYTE*)&dosHeader, sizeof(dosHeader));
50
51 // verify there is a 4 byte PE signature there
52 DWORD peSigFilePointer = 0;
53 if (SUCCEEDED(hr))
54 {
55 peSigFilePointer = dosHeader.e_lfanew;
56 DWORD peSig = 0;
57 hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + peSigFilePointer, (BYTE*)&peSig, 4);
58 if (SUCCEEDED(hr) && peSig != IMAGE_NT_SIGNATURE)
59 {
60 hr = E_FAIL; // PE signature not present
61 }
62 }
63
64 // after the signature is a 20 byte image file header
65 // we need to parse this to figure out the target architecture
66 IMAGE_FILE_HEADER imageFileHeader;
67 if (SUCCEEDED(hr))
68 {
69 hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + peSigFilePointer + 4, (BYTE*)&imageFileHeader, IMAGE_SIZEOF_FILE_HEADER);
70 }
71
72
73
74 WORD optHeaderMagic = 0;
75 DWORD peOptImageHeaderFilePointer = 0;
76 if (SUCCEEDED(hr))
77 {
78 if(pwImageFileMachine != NULL)
79 {
80 *pwImageFileMachine = imageFileHeader.Machine;
81 }
82
83 // 4 bytes after the signature is the 20 byte image file header
84 // 24 bytes after the signature is the image-only header
85 // at the beginning of the image-only header is a 2 byte magic number indicating its format
86 peOptImageHeaderFilePointer = peSigFilePointer + IMAGE_SIZEOF_FILE_HEADER + sizeof(DWORD);
87 hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + peOptImageHeaderFilePointer, (BYTE*)&optHeaderMagic, 2);
88 }
89
90 // Either 112 or 128 bytes after the beginning of the image-only header is an 8 byte resource table
91 // depending on whether the image is PE32 or PE32+
92 DWORD resourceSectionRVA = 0;
93 if (SUCCEEDED(hr))
94 {
95 if (optHeaderMagic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) // PE32
96 {
97 IMAGE_OPTIONAL_HEADER32 header32;
98 hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + peOptImageHeaderFilePointer,
99 (BYTE*)&header32, sizeof(header32));
100 if (SUCCEEDED(hr))
101 {
102 resourceSectionRVA = header32.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress;
103 }
104 }
105 else if (optHeaderMagic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) //PE32+
106 {
107 IMAGE_OPTIONAL_HEADER64 header64;
108 hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + peOptImageHeaderFilePointer,
109 (BYTE*)&header64, sizeof(header64));
110 if (SUCCEEDED(hr))
111 {
112 resourceSectionRVA = header64.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress;
113 }
114 }
115 else
116 {
117 hr = E_FAIL; // Invalid PE
118 }
119 }
120
121 *pdwResourceSectionRVA = resourceSectionRVA;
122 return S_OK;
123}
124
125HRESULT GetResourceRvaFromResourceSectionRva(ICorDebugDataTarget* pDataTarget,
126 ULONG64 moduleBaseAddress,
127 DWORD resourceSectionRva,
128 DWORD type,
129 DWORD name,
130 DWORD language,
131 DWORD* pResourceRva,
132 DWORD* pResourceSize)
133{
134 HRESULT hr = S_OK;
135 DWORD nameTableRva = 0;
136 DWORD langTableRva = 0;
137 DWORD resourceDataEntryRva = 0;
138 *pResourceRva = 0;
139 *pResourceSize = 0;
140
141 // The resource section begins with a resource directory that indexes all the resources by type.
142 // Each entry it points to is another resource directory that indexes all the same type
143 // resources by name. And each entry in that table points to another resource directory that indexes
144 // all the same type/name resources by language. Entries in the final table give the RVA of the actual
145 // resource.
146 // Note all RVAs in this section are relative to the beginning of the resource section,
147 // not the beginning of the image.
148
149 hr = GetNextLevelResourceEntryRVA(pDataTarget, type, moduleBaseAddress, resourceSectionRva, &nameTableRva);
150
151
152 if (SUCCEEDED(hr))
153 {
154 nameTableRva += resourceSectionRva;
155 hr = GetNextLevelResourceEntryRVA(pDataTarget, name, moduleBaseAddress, nameTableRva, &langTableRva);
156
157 }
158 if (SUCCEEDED(hr))
159 {
160 langTableRva += resourceSectionRva;
161 hr = GetNextLevelResourceEntryRVA(pDataTarget, language, moduleBaseAddress, langTableRva, &resourceDataEntryRva);
162 }
163
164 // The resource data entry has the first 4 bytes indicating the RVA of the resource
165 // The next 4 bytes indicate the size of the resource
166 if (SUCCEEDED(hr))
167 {
168 resourceDataEntryRva += resourceSectionRva;
169 IMAGE_RESOURCE_DATA_ENTRY dataEntry;
170 hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceDataEntryRva,
171 (BYTE*)&dataEntry, sizeof(dataEntry));
172 *pResourceRva = dataEntry.OffsetToData;
173 *pResourceSize = dataEntry.Size;
174 }
175
176 return hr;
177}
178
179HRESULT GetResourceRvaFromResourceSectionRvaByName(ICorDebugDataTarget* pDataTarget,
180 ULONG64 moduleBaseAddress,
181 DWORD resourceSectionRva,
182 DWORD type,
183 LPCWSTR pwszName,
184 DWORD language,
185 DWORD* pResourceRva,
186 DWORD* pResourceSize)
187{
188 HRESULT hr = S_OK;
189 DWORD nameTableRva = 0;
190 DWORD langTableRva = 0;
191 DWORD resourceDataEntryRva = 0;
192 *pResourceRva = 0;
193 *pResourceSize = 0;
194
195 // The resource section begins with a resource directory that indexes all the resources by type.
196 // Each entry it points to is another resource directory that indexes all the same type
197 // resources by name. And each entry in that table points to another resource directory that indexes
198 // all the same type/name resources by language. Entries in the final table give the RVA of the actual
199 // resource.
200 // Note all RVAs in this section are relative to the beginning of the resource section,
201 // not the beginning of the image.
202 hr = GetNextLevelResourceEntryRVA(pDataTarget, type, moduleBaseAddress, resourceSectionRva, &nameTableRva);
203
204
205 if (SUCCEEDED(hr))
206 {
207 nameTableRva += resourceSectionRva;
208 hr = GetNextLevelResourceEntryRVAByName(pDataTarget, pwszName, moduleBaseAddress, nameTableRva, resourceSectionRva, &langTableRva);
209 }
210 if (SUCCEEDED(hr))
211 {
212 langTableRva += resourceSectionRva;
213 hr = GetNextLevelResourceEntryRVA(pDataTarget, language, moduleBaseAddress, langTableRva, &resourceDataEntryRva);
214 }
215
216 // The resource data entry has the first 4 bytes indicating the RVA of the resource
217 // The next 4 bytes indicate the size of the resource
218 if (SUCCEEDED(hr))
219 {
220 resourceDataEntryRva += resourceSectionRva;
221 IMAGE_RESOURCE_DATA_ENTRY dataEntry;
222 hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceDataEntryRva,
223 (BYTE*)&dataEntry, sizeof(dataEntry));
224 *pResourceRva = dataEntry.OffsetToData;
225 *pResourceSize = dataEntry.Size;
226 }
227
228 return hr;
229}
230
231// Traverses down one level in the PE resource tree structure
232//
233// Arguments:
234// pDataTarget - the data target for inspecting this process
235// id - the id of the next node in the resource tree you want
236// moduleBaseAddress - the base address of the module being inspected
237// resourceDirectoryRVA - the base address of the beginning of the resource directory for this
238// level of the tree
239// pNextLevelRVA - out - The RVA for the next level tree directory or the RVA of the resource entry
240//
241// Returns:
242// S_OK if succesful or an appropriate failing HRESULT
243HRESULT GetNextLevelResourceEntryRVA(ICorDebugDataTarget* pDataTarget,
244 DWORD id,
245 ULONG64 moduleBaseAddress,
246 DWORD resourceDirectoryRVA,
247 DWORD* pNextLevelRVA)
248{
249 *pNextLevelRVA = 0;
250 HRESULT hr = S_OK;
251
252 // A resource directory which consists of
253 // a header followed by a number of entries. In the header at offset 12 is
254 // the number entries identified by name, followed by the number of entries
255 // identified by ID at offset 14. Both are 2 bytes.
256 // This method only supports locating entries by ID, not by name
257 IMAGE_RESOURCE_DIRECTORY resourceDirectory;
258 hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceDirectoryRVA, (BYTE*)&resourceDirectory, sizeof(resourceDirectory));
259
260
261
262 // The ith resource directory entry is at offset 16 + 8i from the beginning of the resource
263 // directory table
264 WORD numNameEntries;
265 WORD numIDEntries;
266 if (SUCCEEDED(hr))
267 {
268 numNameEntries = resourceDirectory.NumberOfNamedEntries;
269 numIDEntries = resourceDirectory.NumberOfIdEntries;
270
271 for (WORD i = numNameEntries; i < numNameEntries + numIDEntries; i++)
272 {
273 IMAGE_RESOURCE_DIRECTORY_ENTRY entry;
274 hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceDirectoryRVA + sizeof(resourceDirectory) + sizeof(entry)*i,
275 (BYTE*)&entry, sizeof(entry));
276 if (FAILED(hr))
277 {
278 break;
279 }
280 if (entry.Id == id)
281 {
282 *pNextLevelRVA = entry.OffsetToDirectory;
283 break;
284 }
285 }
286 }
287
288 // If we didn't find the entry
289 if (SUCCEEDED(hr) && *pNextLevelRVA == 0)
290 {
291 hr = E_FAIL;
292 }
293
294 return hr; // resource not found
295}
296
297// Traverses down one level in the PE resource tree structure
298//
299// Arguments:
300// pDataTarget - the data target for inspecting this process
301// name - the name of the next node in the resource tree you want
302// moduleBaseAddress - the base address of the module being inspected
303// resourceDirectoryRVA - the base address of the beginning of the resource directory for this
304// level of the tree
305// resourceSectionRVA - the rva of the beginning of the resource section of the PE file
306// pNextLevelRVA - out - The RVA for the next level tree directory or the RVA of the resource entry
307//
308// Returns:
309// S_OK if succesful or an appropriate failing HRESULT
310HRESULT GetNextLevelResourceEntryRVAByName(ICorDebugDataTarget* pDataTarget,
311 LPCWSTR pwzName,
312 ULONG64 moduleBaseAddress,
313 DWORD resourceDirectoryRva,
314 DWORD resourceSectionRva,
315 DWORD* pNextLevelRva)
316{
317 HRESULT hr = S_OK;
318 DWORD nameLength = (DWORD)wcslen(pwzName);
319 WCHAR entryName[50];
320 assert(nameLength < 50); // this implementation won't support matching a name longer
321 // than 50 characters. We only look up the hard coded name
322 // of the debug resource in clr.dll though, so it shouldn't be
323 // an issue. Increase this count if we ever want to look up
324 // larger names
325 if (nameLength >= 50)
326 {
327 hr = E_FAIL; // invalid name length
328 }
329
330 // A resource directory which consists of
331 // a header followed by a number of entries. In the header at offset 12 is
332 // the number entries identified by name, followed by the number of entries
333 // identified by ID at offset 14. Both are 2 bytes.
334 // This method only supports locating entries by ID, not by name
335 IMAGE_RESOURCE_DIRECTORY resourceDirectory = { 0 };
336 if (SUCCEEDED(hr))
337 {
338 hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceDirectoryRva, (BYTE*)&resourceDirectory, sizeof(resourceDirectory));
339 }
340
341 // The ith resource directory entry is at offset 16 + 8i from the beginning of the resource
342 // directory table
343 if (SUCCEEDED(hr))
344 {
345 WORD numNameEntries = resourceDirectory.NumberOfNamedEntries;
346 for (WORD i = 0; i < numNameEntries; i++)
347 {
348 IMAGE_RESOURCE_DIRECTORY_ENTRY entry;
349 hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceDirectoryRva + sizeof(resourceDirectory) + sizeof(entry)*i,
350 (BYTE*)&entry, sizeof(entry));
351 if (FAILED(hr))
352 {
353 break;
354 }
355
356 // the NameRVAOrID field points to a UTF16 string with a 2 byte length in front of it
357 // read the 2 byte length first. The doc of course doesn't mention this, but the RVA is
358 // relative to the base of the resource section and needs the leading bit stripped.
359 WORD entryNameLength = 0;
360 hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceSectionRva +
361 entry.NameOffset, (BYTE*)&entryNameLength, sizeof(entryNameLength));
362 if (FAILED(hr))
363 {
364 break;
365 }
366 if (entryNameLength != nameLength)
367 {
368 continue; // names aren't the same length, not a match
369 }
370
371 // read the rest of the string data and check for a match
372 hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceSectionRva +
373 entry.NameOffset + 2, (BYTE*)entryName, entryNameLength*sizeof(WCHAR));
374 if (FAILED(hr))
375 {
376 break;
377 }
378 if (memcmp(entryName, pwzName, entryNameLength*sizeof(WCHAR)) == 0)
379 {
380 *pNextLevelRva = entry.OffsetToDirectory;
381 break;
382 }
383 }
384 }
385
386 if (SUCCEEDED(hr) && *pNextLevelRva == 0)
387 {
388 hr = E_FAIL; // resource not found
389 }
390
391 return hr;
392}
393
394// A small wrapper that reads from the data target and throws on error
395HRESULT ReadFromDataTarget(ICorDebugDataTarget* pDataTarget,
396 ULONG64 addr,
397 BYTE* pBuffer,
398 ULONG32 bytesToRead)
399{
400 //PRECONDITION(CheckPointer(pDataTarget));
401 //PRECONDITION(CheckPointer(pBuffer));
402
403 HRESULT hr = S_OK;
404 ULONG32 bytesReadTotal = 0;
405 ULONG32 bytesRead = 0;
406 do
407 {
408 if (FAILED(pDataTarget->ReadVirtual((CORDB_ADDRESS)(addr + bytesReadTotal),
409 pBuffer,
410 bytesToRead - bytesReadTotal,
411 &bytesRead)))
412 {
413 hr = CORDBG_E_READVIRTUAL_FAILURE;
414 break;
415 }
416 bytesReadTotal += bytesRead;
417 } while (bytesRead != 0 && (bytesReadTotal < bytesToRead));
418
419 // If we can't read all the expected memory, then fail
420 if (SUCCEEDED(hr) && (bytesReadTotal != bytesToRead))
421 {
422 hr = HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
423 }
424
425 return hr;
426}
427