summaryrefslogtreecommitdiff
path: root/src/debug/dbgutil/dbgutil.cpp
blob: a16849868ca5c13b282ab6633858cca49015c00e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//*****************************************************************************
// dbgutil.cpp
// 

//
//*****************************************************************************

//
// Various common helpers for PE resource reading used by multiple debug components.
//

#include <dbgutil.h>
#include "corerror.h"
#include <assert.h>
#include <stdio.h>

// Returns the RVA of the resource section for the module specified by the given data target and module base.
// Returns failure if the module doesn't have a resource section.
//
// Arguments
//   pDataTarget - dataTarget for the process we are inspecting
//   moduleBaseAddress - base address of a module we should inspect
//   pwImageFileMachine - updated with the Machine from the IMAGE_FILE_HEADER
//   pdwResourceSectionRVA - updated with the resultant RVA on success
HRESULT GetMachineAndResourceSectionRVA(ICorDebugDataTarget* pDataTarget,
    ULONG64 moduleBaseAddress,
    WORD* pwImageFileMachine,
    DWORD* pdwResourceSectionRVA)
{
    // Fun code ahead... below is a hand written PE decoder with some of the file offsets hardcoded.
    // It supports no more than what we absolutely have to to get to the resources we need. Any of the
    // magic numbers used below can be determined by using the public documentation on the web.
    //
    // Yes utilcode has a PE decoder, no it does not support reading its data through a datatarget
    // It was easier to inspect the small portion that I needed than to shove an abstraction layer under
    // our utilcode and then make sure everything still worked.

    // SECURITY WARNING: all data provided by the data target should be considered untrusted.
    // Do not allow malicious data to cause large reads, memory allocations, buffer overflow,
    // or any other undesirable behavior.

    HRESULT hr = S_OK;

    // at offset 3c in the image is a 4 byte file pointer that indicates where the PE signature is
    IMAGE_DOS_HEADER dosHeader;
    hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress, (BYTE*)&dosHeader, sizeof(dosHeader));

    // verify there is a 4 byte PE signature there
    DWORD peSigFilePointer = 0;
    if (SUCCEEDED(hr))
    {
        peSigFilePointer = dosHeader.e_lfanew;
        DWORD peSig = 0;
        hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + peSigFilePointer, (BYTE*)&peSig, 4);
        if (SUCCEEDED(hr) && peSig != IMAGE_NT_SIGNATURE)
        {
            hr = E_FAIL; // PE signature not present
        }
    }

    // after the signature is a 20 byte image file header
    // we need to parse this to figure out the target architecture
    IMAGE_FILE_HEADER imageFileHeader;
    if (SUCCEEDED(hr))
    {
        hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + peSigFilePointer + 4, (BYTE*)&imageFileHeader, IMAGE_SIZEOF_FILE_HEADER);
    }



    WORD optHeaderMagic = 0;
    DWORD peOptImageHeaderFilePointer = 0;
    if (SUCCEEDED(hr))
    {
        if(pwImageFileMachine != NULL)
        {
            *pwImageFileMachine = imageFileHeader.Machine;
        }

        // 4 bytes after the signature is the 20 byte image file header
        // 24 bytes after the signature is the image-only header
        // at the beginning of the image-only header is a 2 byte magic number indicating its format
        peOptImageHeaderFilePointer = peSigFilePointer + IMAGE_SIZEOF_FILE_HEADER + sizeof(DWORD);
        hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + peOptImageHeaderFilePointer, (BYTE*)&optHeaderMagic, 2);
    }

    // Either 112 or 128 bytes after the beginning of the image-only header is an 8 byte resource table
    // depending on whether the image is PE32 or PE32+
    DWORD resourceSectionRVA = 0;
    if (SUCCEEDED(hr))
    {
        if (optHeaderMagic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) // PE32
        {
            IMAGE_OPTIONAL_HEADER32 header32;
            hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + peOptImageHeaderFilePointer,
                (BYTE*)&header32, sizeof(header32));
            if (SUCCEEDED(hr))
            {
                resourceSectionRVA = header32.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress;
            }
        }
        else if (optHeaderMagic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) //PE32+
        {
            IMAGE_OPTIONAL_HEADER64 header64;
            hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + peOptImageHeaderFilePointer,
                (BYTE*)&header64, sizeof(header64));
            if (SUCCEEDED(hr))
            {
                resourceSectionRVA = header64.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress;
            }
        }
        else
        {
            hr = E_FAIL; // Invalid PE
        }
    }

    *pdwResourceSectionRVA = resourceSectionRVA;
    return S_OK;
}

HRESULT GetResourceRvaFromResourceSectionRva(ICorDebugDataTarget* pDataTarget,
    ULONG64 moduleBaseAddress,
    DWORD resourceSectionRva,
    DWORD type,
    DWORD name,
    DWORD language,
    DWORD* pResourceRva,
    DWORD* pResourceSize)
{
    HRESULT hr = S_OK;
    DWORD nameTableRva = 0;
    DWORD langTableRva = 0;
    DWORD resourceDataEntryRva = 0;
    *pResourceRva = 0;
    *pResourceSize = 0;

    // The resource section begins with a resource directory that indexes all the resources by type.
    // Each entry it points to is another resource directory that indexes all the same type
    // resources by name. And each entry in that table points to another resource directory that indexes
    // all the same type/name resources by language. Entries in the final table give the RVA of the actual
    // resource. 
    // Note all RVAs in this section are relative to the beginning of the resource section,
    // not the beginning of the image.

    hr = GetNextLevelResourceEntryRVA(pDataTarget, type, moduleBaseAddress, resourceSectionRva, &nameTableRva);


    if (SUCCEEDED(hr))
    {
        nameTableRva += resourceSectionRva;
        hr = GetNextLevelResourceEntryRVA(pDataTarget, name, moduleBaseAddress, nameTableRva, &langTableRva);

    }
    if (SUCCEEDED(hr))
    {
        langTableRva += resourceSectionRva;
        hr = GetNextLevelResourceEntryRVA(pDataTarget, language, moduleBaseAddress, langTableRva, &resourceDataEntryRva);
    }

    // The resource data entry has the first 4 bytes indicating the RVA of the resource
    // The next 4 bytes indicate the size of the resource
    if (SUCCEEDED(hr))
    {
        resourceDataEntryRva += resourceSectionRva;
        IMAGE_RESOURCE_DATA_ENTRY dataEntry;
        hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceDataEntryRva,
            (BYTE*)&dataEntry, sizeof(dataEntry));
        *pResourceRva = dataEntry.OffsetToData;
        *pResourceSize = dataEntry.Size;
    }

    return hr;
}

HRESULT GetResourceRvaFromResourceSectionRvaByName(ICorDebugDataTarget* pDataTarget,
    ULONG64 moduleBaseAddress,
    DWORD resourceSectionRva,
    DWORD type,
    LPCWSTR pwszName,
    DWORD language,
    DWORD* pResourceRva,
    DWORD* pResourceSize)
{
    HRESULT hr = S_OK;
    DWORD nameTableRva = 0;
    DWORD langTableRva = 0;
    DWORD resourceDataEntryRva = 0;
    *pResourceRva = 0;
    *pResourceSize = 0;

    // The resource section begins with a resource directory that indexes all the resources by type.
    // Each entry it points to is another resource directory that indexes all the same type
    // resources by name. And each entry in that table points to another resource directory that indexes
    // all the same type/name resources by language. Entries in the final table give the RVA of the actual
    // resource. 
    // Note all RVAs in this section are relative to the beginning of the resource section,
    // not the beginning of the image.
    hr = GetNextLevelResourceEntryRVA(pDataTarget, type, moduleBaseAddress, resourceSectionRva, &nameTableRva);


    if (SUCCEEDED(hr))
    {
        nameTableRva += resourceSectionRva;
        hr = GetNextLevelResourceEntryRVAByName(pDataTarget, pwszName, moduleBaseAddress, nameTableRva, resourceSectionRva, &langTableRva);
    }
    if (SUCCEEDED(hr))
    {
        langTableRva += resourceSectionRva;
        hr = GetNextLevelResourceEntryRVA(pDataTarget, language, moduleBaseAddress, langTableRva, &resourceDataEntryRva);
    }

    // The resource data entry has the first 4 bytes indicating the RVA of the resource
    // The next 4 bytes indicate the size of the resource
    if (SUCCEEDED(hr))
    {
        resourceDataEntryRva += resourceSectionRva;
        IMAGE_RESOURCE_DATA_ENTRY dataEntry;
        hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceDataEntryRva,
            (BYTE*)&dataEntry, sizeof(dataEntry));
        *pResourceRva = dataEntry.OffsetToData;
        *pResourceSize = dataEntry.Size;
    }

    return hr;
}

// Traverses down one level in the PE resource tree structure
// 
// Arguments:
//   pDataTarget - the data target for inspecting this process
//   id - the id of the next node in the resource tree you want
//   moduleBaseAddress - the base address of the module being inspected
//   resourceDirectoryRVA - the base address of the beginning of the resource directory for this
//                          level of the tree
//   pNextLevelRVA - out - The RVA for the next level tree directory or the RVA of the resource entry
//
// Returns:
//   S_OK if succesful or an appropriate failing HRESULT
HRESULT GetNextLevelResourceEntryRVA(ICorDebugDataTarget* pDataTarget,
    DWORD id,
    ULONG64 moduleBaseAddress,
    DWORD resourceDirectoryRVA,
    DWORD* pNextLevelRVA)
{
    *pNextLevelRVA = 0;
    HRESULT hr = S_OK;

    // A resource directory which consists of
    // a header followed by a number of entries. In the header at offset 12 is
    // the number entries identified by name, followed by the number of entries
    // identified by ID at offset 14. Both are 2 bytes.
    // This method only supports locating entries by ID, not by name
    IMAGE_RESOURCE_DIRECTORY resourceDirectory;
    hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceDirectoryRVA, (BYTE*)&resourceDirectory, sizeof(resourceDirectory));



    // The ith resource directory entry is at offset 16 + 8i from the beginning of the resource
    // directory table
    WORD numNameEntries;
    WORD numIDEntries;
    if (SUCCEEDED(hr))
    {
        numNameEntries = resourceDirectory.NumberOfNamedEntries;
        numIDEntries = resourceDirectory.NumberOfIdEntries;

        for (WORD i = numNameEntries; i < numNameEntries + numIDEntries; i++)
        {
            IMAGE_RESOURCE_DIRECTORY_ENTRY entry;
            hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceDirectoryRVA + sizeof(resourceDirectory) + sizeof(entry)*i,
                (BYTE*)&entry, sizeof(entry));
            if (FAILED(hr))
            {
                break;
            }
            if (entry.Id == id)
            {
                *pNextLevelRVA = entry.OffsetToDirectory;
                break;
            }
        }
    }

    // If we didn't find the entry
    if (SUCCEEDED(hr) && *pNextLevelRVA == 0)
    {
        hr = E_FAIL;
    }

    return hr; // resource not found
}

// Traverses down one level in the PE resource tree structure
// 
// Arguments:
//   pDataTarget - the data target for inspecting this process
//   name - the name of the next node in the resource tree you want
//   moduleBaseAddress - the base address of the module being inspected
//   resourceDirectoryRVA - the base address of the beginning of the resource directory for this
//                          level of the tree
//   resourceSectionRVA - the rva of the beginning of the resource section of the PE file
//   pNextLevelRVA - out - The RVA for the next level tree directory or the RVA of the resource entry
//
// Returns:
//   S_OK if succesful or an appropriate failing HRESULT
HRESULT GetNextLevelResourceEntryRVAByName(ICorDebugDataTarget* pDataTarget,
    LPCWSTR pwzName,
    ULONG64 moduleBaseAddress,
    DWORD resourceDirectoryRva,
    DWORD resourceSectionRva,
    DWORD* pNextLevelRva)
{
    HRESULT hr = S_OK;
    DWORD nameLength = (DWORD)wcslen(pwzName);
    WCHAR entryName[50];
    assert(nameLength < 50);     // this implementation won't support matching a name longer
    // than 50 characters. We only look up the hard coded name
    // of the debug resource in clr.dll though, so it shouldn't be
    // an issue. Increase this count if we ever want to look up
    // larger names
    if (nameLength >= 50)
    {
        hr = E_FAIL; // invalid name length
    }

    // A resource directory which consists of
    // a header followed by a number of entries. In the header at offset 12 is
    // the number entries identified by name, followed by the number of entries
    // identified by ID at offset 14. Both are 2 bytes.
    // This method only supports locating entries by ID, not by name
    IMAGE_RESOURCE_DIRECTORY resourceDirectory = { 0 };
    if (SUCCEEDED(hr))
    {
        hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceDirectoryRva, (BYTE*)&resourceDirectory, sizeof(resourceDirectory));
    }

    // The ith resource directory entry is at offset 16 + 8i from the beginning of the resource
    // directory table
    if (SUCCEEDED(hr))
    {
        WORD numNameEntries = resourceDirectory.NumberOfNamedEntries;
        for (WORD i = 0; i < numNameEntries; i++)
        {
            IMAGE_RESOURCE_DIRECTORY_ENTRY entry;
            hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceDirectoryRva + sizeof(resourceDirectory) + sizeof(entry)*i,
                (BYTE*)&entry, sizeof(entry));
            if (FAILED(hr))
            {
                break;
            }

            // the NameRVAOrID field points to a UTF16 string with a 2 byte length in front of it
            // read the 2 byte length first. The doc of course doesn't mention this, but the RVA is
            // relative to the base of the resource section and needs the leading bit stripped.
            WORD entryNameLength = 0;
            hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceSectionRva +
                entry.NameOffset, (BYTE*)&entryNameLength, sizeof(entryNameLength));
            if (FAILED(hr))
            {
                break;
            }
            if (entryNameLength != nameLength)
            {
                continue; // names aren't the same length, not a match
            }

            // read the rest of the string data and check for a match
            hr = ReadFromDataTarget(pDataTarget, moduleBaseAddress + resourceSectionRva +
                entry.NameOffset + 2, (BYTE*)entryName, entryNameLength*sizeof(WCHAR));
            if (FAILED(hr))
            {
                break;
            }
            if (memcmp(entryName, pwzName, entryNameLength*sizeof(WCHAR)) == 0)
            {
                *pNextLevelRva = entry.OffsetToDirectory;
                break;
            }
        }
    }

    if (SUCCEEDED(hr) && *pNextLevelRva == 0)
    {
        hr = E_FAIL; // resource not found
    }

    return hr;
}

// A small wrapper that reads from the data target and throws on error
HRESULT ReadFromDataTarget(ICorDebugDataTarget* pDataTarget,
    ULONG64 addr,
    BYTE* pBuffer,
    ULONG32 bytesToRead)
{
    //PRECONDITION(CheckPointer(pDataTarget));
    //PRECONDITION(CheckPointer(pBuffer));

    HRESULT hr = S_OK;
    ULONG32 bytesReadTotal = 0;
    ULONG32 bytesRead = 0;
    do
    {
        if (FAILED(pDataTarget->ReadVirtual((CORDB_ADDRESS)(addr + bytesReadTotal),
            pBuffer,
            bytesToRead - bytesReadTotal,
            &bytesRead)))
        {
            hr = CORDBG_E_READVIRTUAL_FAILURE;
            break;
        }
        bytesReadTotal += bytesRead;
    } while (bytesRead != 0 && (bytesReadTotal < bytesToRead));

    // If we can't read all the expected memory, then fail
    if (SUCCEEDED(hr) && (bytesReadTotal != bytesToRead))
    {
        hr = HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY);
    }

    return hr;
}