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 //
6 // Implementation of the portions of the Redhawk Platform Abstraction Layer (PAL) library that are common among
7 // multiple PAL variants.
8 //
9 // Note that in general we don't want to assume that Windows and Redhawk global definitions can co-exist.
10 // Since this code must include Windows headers to do its job we can't therefore safely include general
11 // Redhawk header files.
12 //
13
14 #include <windows.h>
15 #include <stdio.h>
16 #include <errno.h>
17 #include <evntprov.h>
18 #include "CommonTypes.h"
19 #include "daccess.h"
20 #include "PalRedhawkCommon.h"
21 #include "PalRedhawk.h"
22 #include <winternl.h>
23 #include "CommonMacros.h"
24 #include "rhassert.h"
25
26
27 #define REDHAWK_PALEXPORT extern "C"
28 #define REDHAWK_PALAPI __stdcall
29
30
31 // Given the OS handle of a loaded module, compute the upper and lower virtual address bounds (inclusive).
PalGetModuleBounds(HANDLE hOsHandle,_Out_ UInt8 ** ppLowerBound,_Out_ UInt8 ** ppUpperBound)32 REDHAWK_PALEXPORT void REDHAWK_PALAPI PalGetModuleBounds(HANDLE hOsHandle, _Out_ UInt8 ** ppLowerBound, _Out_ UInt8 ** ppUpperBound)
33 {
34 BYTE *pbModule = (BYTE*)hOsHandle;
35 DWORD cbModule;
36
37 IMAGE_NT_HEADERS *pNtHeaders = (IMAGE_NT_HEADERS*)(pbModule + ((IMAGE_DOS_HEADER*)hOsHandle)->e_lfanew);
38 if (pNtHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
39 cbModule = ((IMAGE_OPTIONAL_HEADER32*)&pNtHeaders->OptionalHeader)->SizeOfImage;
40 else
41 cbModule = ((IMAGE_OPTIONAL_HEADER64*)&pNtHeaders->OptionalHeader)->SizeOfImage;
42
43 *ppLowerBound = pbModule;
44 *ppUpperBound = pbModule + cbModule - 1;
45 }
46
47 // Reads through the PE header of the specified module, and returns
48 // the module's matching PDB's signature GUID, age, and build path by
49 // fishing them out of the last IMAGE_DEBUG_DIRECTORY of type
50 // IMAGE_DEBUG_TYPE_CODEVIEW. Used when sending the ModuleLoad event
51 // to help profilers find matching PDBs for loaded modules.
52 //
53 // Arguments:
54 //
55 // [in] hOsHandle - OS Handle for module from which to get PDB info
56 // [out] pGuidSignature - PDB's signature GUID to be placed here
57 // [out] pdwAge - PDB's age to be placed here
58 // [out] wszPath - PDB's build path to be placed here
59 // [in] cchPath - Number of wide characters allocated in wszPath, including NULL terminator
60 //
61 // This is a simplification of similar code in desktop CLR's GetCodeViewInfo
62 // in eventtrace.cpp.
PalGetPDBInfo(HANDLE hOsHandle,_Out_ GUID * pGuidSignature,_Out_ UInt32 * pdwAge,_Out_writes_z_ (cchPath)WCHAR * wszPath,Int32 cchPath)63 REDHAWK_PALEXPORT void REDHAWK_PALAPI PalGetPDBInfo(HANDLE hOsHandle, _Out_ GUID * pGuidSignature, _Out_ UInt32 * pdwAge, _Out_writes_z_(cchPath) WCHAR * wszPath, Int32 cchPath)
64 {
65 // Zero-init [out]-params
66 ZeroMemory(pGuidSignature, sizeof(*pGuidSignature));
67 *pdwAge = 0;
68 if (cchPath <= 0)
69 return;
70 wszPath[0] = L'\0';
71
72 BYTE *pbModule = (BYTE*)hOsHandle;
73
74 IMAGE_NT_HEADERS const * pNtHeaders = (IMAGE_NT_HEADERS*)(pbModule + ((IMAGE_DOS_HEADER*)hOsHandle)->e_lfanew);
75 IMAGE_DATA_DIRECTORY const * rgDataDirectory = NULL;
76 if (pNtHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
77 rgDataDirectory = ((IMAGE_OPTIONAL_HEADER32 const *)&pNtHeaders->OptionalHeader)->DataDirectory;
78 else
79 rgDataDirectory = ((IMAGE_OPTIONAL_HEADER64 const *)&pNtHeaders->OptionalHeader)->DataDirectory;
80
81 IMAGE_DATA_DIRECTORY const * pDebugDataDirectory = &rgDataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG];
82
83 // In Redhawk, modules are loaded as MAPPED, so we don't have to worry about dealing
84 // with FLAT files (with padding missing), so header addresses can be used as is
85 IMAGE_DEBUG_DIRECTORY const *rgDebugEntries = (IMAGE_DEBUG_DIRECTORY const *) (pbModule + pDebugDataDirectory->VirtualAddress);
86 DWORD cbDebugEntries = pDebugDataDirectory->Size;
87 if (cbDebugEntries < sizeof(IMAGE_DEBUG_DIRECTORY))
88 return;
89
90 // Since rgDebugEntries is an array of IMAGE_DEBUG_DIRECTORYs, cbDebugEntries
91 // should be a multiple of sizeof(IMAGE_DEBUG_DIRECTORY).
92 if (cbDebugEntries % sizeof(IMAGE_DEBUG_DIRECTORY) != 0)
93 return;
94
95 // CodeView RSDS debug information -> PDB 7.00
96 struct CV_INFO_PDB70
97 {
98 DWORD magic;
99 GUID signature; // unique identifier
100 DWORD age; // an always-incrementing value
101 _Field_z_ char path[MAX_PATH]; // zero terminated string with the name of the PDB file
102 };
103
104 // Temporary storage for a CV_INFO_PDB70 and its size (which could be less than
105 // sizeof(CV_INFO_PDB70); see below).
106 struct PdbInfo
107 {
108 CV_INFO_PDB70 * m_pPdb70;
109 ULONG m_cbPdb70;
110 };
111
112 // Grab module bounds so we can do some rough sanity checking before we follow any
113 // RVAs
114 UInt8 * pbModuleLowerBound = NULL;
115 UInt8 * pbModuleUpperBound = NULL;
116 PalGetModuleBounds(hOsHandle, &pbModuleLowerBound, &pbModuleUpperBound);
117
118 // Iterate through all debug directory entries. The convention is that debuggers &
119 // profilers typically just use the very last IMAGE_DEBUG_TYPE_CODEVIEW entry. Treat raw
120 // bytes we read as untrusted.
121 PdbInfo pdbInfoLast = {0};
122 int cEntries = cbDebugEntries / sizeof(IMAGE_DEBUG_DIRECTORY);
123 for (int i = 0; i < cEntries; i++)
124 {
125 if ((UInt8 *)(&rgDebugEntries[i]) + sizeof(rgDebugEntries[i]) >= pbModuleUpperBound)
126 {
127 // Bogus pointer
128 return;
129 }
130
131 if (rgDebugEntries[i].Type != IMAGE_DEBUG_TYPE_CODEVIEW)
132 continue;
133
134 // Get raw data pointed to by this IMAGE_DEBUG_DIRECTORY
135
136 // AddressOfRawData is generally set properly for Redhawk modules, so we don't
137 // have to worry about using PointerToRawData and converting it to an RVA
138 if (rgDebugEntries[i].AddressOfRawData == NULL)
139 continue;
140
141 DWORD rvaOfRawData = rgDebugEntries[i].AddressOfRawData;
142 ULONG cbDebugData = rgDebugEntries[i].SizeOfData;
143 if (cbDebugData < size_t(&((CV_INFO_PDB70*)0)->magic) + sizeof(((CV_INFO_PDB70*)0)->magic))
144 {
145 // raw data too small to contain magic number at expected spot, so its format
146 // is not recognizeable. Skip
147 continue;
148 }
149
150 // Verify the magic number is as expected
151 const DWORD CV_SIGNATURE_RSDS = 0x53445352;
152 CV_INFO_PDB70 * pPdb70 = (CV_INFO_PDB70 *) (pbModule + rvaOfRawData);
153 if ((UInt8 *)(pPdb70) + cbDebugData >= pbModuleUpperBound)
154 {
155 // Bogus pointer
156 return;
157 }
158
159 if (pPdb70->magic != CV_SIGNATURE_RSDS)
160 {
161 // Unrecognized magic number. Skip
162 continue;
163 }
164
165 // From this point forward, the format should adhere to the expected layout of
166 // CV_INFO_PDB70. If we find otherwise, then assume the IMAGE_DEBUG_DIRECTORY is
167 // outright corrupt.
168
169 // Verify sane size of raw data
170 if (cbDebugData > sizeof(CV_INFO_PDB70))
171 return;
172
173 // cbDebugData actually can be < sizeof(CV_INFO_PDB70), since the "path" field
174 // can be truncated to its actual data length (i.e., fewer than MAX_PATH chars
175 // may be present in the PE file). In some cases, though, cbDebugData will
176 // include all MAX_PATH chars even though path gets null-terminated well before
177 // the MAX_PATH limit.
178
179 // Gotta have at least one byte of the path
180 if (cbDebugData < offsetof(CV_INFO_PDB70, path) + sizeof(char))
181 return;
182
183 // How much space is available for the path?
184 size_t cchPathMaxIncludingNullTerminator = (cbDebugData - offsetof(CV_INFO_PDB70, path)) / sizeof(char);
185 ASSERT(cchPathMaxIncludingNullTerminator >= 1); // Guaranteed above
186
187 // Verify path string fits inside the declared size
188 size_t cchPathActualExcludingNullTerminator = strnlen_s(pPdb70->path, cchPathMaxIncludingNullTerminator);
189 if (cchPathActualExcludingNullTerminator == cchPathMaxIncludingNullTerminator)
190 {
191 // This is how strnlen indicates failure--it couldn't find the null
192 // terminator within the buffer size specified
193 return;
194 }
195
196 // Looks valid. Remember it.
197 pdbInfoLast.m_pPdb70 = pPdb70;
198 pdbInfoLast.m_cbPdb70 = cbDebugData;
199 }
200
201 // Take the last IMAGE_DEBUG_TYPE_CODEVIEW entry we saw, and return it to the caller
202 if (pdbInfoLast.m_pPdb70 != NULL)
203 {
204 memcpy(pGuidSignature, &pdbInfoLast.m_pPdb70->signature, sizeof(GUID));
205 *pdwAge = pdbInfoLast.m_pPdb70->age;
206
207 // Convert build path from ANSI to UNICODE
208 errno_t ret;
209 size_t cchConverted;
210 ret = mbstowcs_s(
211 &cchConverted,
212 wszPath,
213 cchPath,
214 pdbInfoLast.m_pPdb70->path,
215 _countof(pdbInfoLast.m_pPdb70->path) - 1);
216 if ((ret != 0) && (ret != STRUNCATE))
217 {
218 // PDB path isn't essential. An empty string will do if we hit an error.
219 ASSERT(cchPath > 0); // Guaranteed at top of function
220 wszPath[0] = L'\0';
221 }
222 }
223 }
224
PalGetProcessCpuCount()225 REDHAWK_PALEXPORT Int32 REDHAWK_PALAPI PalGetProcessCpuCount()
226 {
227 static int CpuCount = 0;
228
229 if (CpuCount != 0)
230 return CpuCount;
231 else
232 {
233 // The concept of process CPU affinity is going away and so CoreSystem obsoletes the APIs used to
234 // fetch this information. Instead we'll just return total cpu count.
235 SYSTEM_INFO sysInfo;
236 #ifndef APP_LOCAL_RUNTIME
237 ::GetSystemInfo(&sysInfo);
238 #else
239 ::GetNativeSystemInfo(&sysInfo);
240 #endif
241 CpuCount = sysInfo.dwNumberOfProcessors;
242 return sysInfo.dwNumberOfProcessors;
243 }
244 }
245
246 //Reads the entire contents of the file into the specified buffer, buff
247 //returns the number of bytes read if the file is successfully read
248 //returns 0 if the file is not found, size is greater than maxBytesToRead or the file couldn't be opened or read
PalReadFileContents(_In_z_ const TCHAR * fileName,_Out_writes_all_ (maxBytesToRead)char * buff,_In_ UInt32 maxBytesToRead)249 REDHAWK_PALEXPORT UInt32 REDHAWK_PALAPI PalReadFileContents(_In_z_ const TCHAR* fileName, _Out_writes_all_(maxBytesToRead) char* buff, _In_ UInt32 maxBytesToRead)
250 {
251 WIN32_FILE_ATTRIBUTE_DATA attrData;
252
253 BOOL getAttrSuccess = GetFileAttributesExW(fileName, GetFileExInfoStandard, &attrData);
254
255 //if we weren't able to get the file attributes, or the file is larger than maxBytesToRead, or the file size is zero
256 if ((!getAttrSuccess) || (attrData.nFileSizeHigh != 0) || (attrData.nFileSizeLow > (DWORD)maxBytesToRead) || (attrData.nFileSizeLow == 0))
257 {
258 return 0;
259 }
260
261 HANDLE hFile = PalCreateFileW(fileName, GENERIC_READ, FILE_SHARE_DELETE | FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
262 if (hFile == INVALID_HANDLE_VALUE)
263 {
264 return 0;
265 }
266
267 UInt32 bytesRead;
268
269 BOOL readSuccess = ReadFile(hFile, buff, (DWORD)maxBytesToRead, (DWORD*)&bytesRead, NULL);
270
271 CloseHandle(hFile);
272
273 if (!readSuccess)
274 {
275 return 0;
276 }
277
278 return bytesRead;
279 }
280
281
282 // Retrieves the entire range of memory dedicated to the calling thread's stack. This does
283 // not get the current dynamic bounds of the stack, which can be significantly smaller than
284 // the maximum bounds.
PalGetMaximumStackBounds(_Out_ void ** ppStackLowOut,_Out_ void ** ppStackHighOut)285 REDHAWK_PALEXPORT bool REDHAWK_PALAPI PalGetMaximumStackBounds(_Out_ void** ppStackLowOut, _Out_ void** ppStackHighOut)
286 {
287 // VirtualQuery on the address of a local variable to get the allocation
288 // base of the stack. Then use the StackBase field in the TEB to give
289 // the highest address of the stack region.
290 MEMORY_BASIC_INFORMATION mbi = { 0 };
291 SIZE_T cb = VirtualQuery(&mbi, &mbi, sizeof(mbi));
292 if (cb != sizeof(mbi))
293 return false;
294
295 NT_TIB* pTib = (NT_TIB*)NtCurrentTeb();
296 *ppStackHighOut = pTib->StackBase; // stack base is the highest address
297 *ppStackLowOut = mbi.AllocationBase; // allocation base is the lowest address
298 return true;
299 }
300
301 #if !defined(_INC_WINDOWS) || defined(APP_LOCAL_RUNTIME)
302
303 typedef struct _UNICODE_STRING {
304 USHORT Length;
305 USHORT MaximumLength;
306 PWSTR Buffer;
307 } UNICODE_STRING;
308 typedef UNICODE_STRING *PUNICODE_STRING;
309 typedef const UNICODE_STRING *PCUNICODE_STRING;
310
311 typedef struct _PEB_LDR_DATA {
312 BYTE Reserved1[8];
313 PVOID Reserved2[3];
314 LIST_ENTRY InMemoryOrderModuleList;
315 } PEB_LDR_DATA, *PPEB_LDR_DATA;
316
317 typedef struct _LDR_DATA_TABLE_ENTRY {
318 PVOID Reserved1[2];
319 LIST_ENTRY InMemoryOrderLinks;
320 PVOID Reserved2[2];
321 PVOID DllBase;
322 PVOID Reserved3[2];
323 UNICODE_STRING FullDllName;
324 BYTE Reserved4[8];
325 PVOID Reserved5[3];
326 union {
327 ULONG CheckSum;
328 PVOID Reserved6;
329 } DUMMYUNIONNAME;
330 ULONG TimeDateStamp;
331 } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
332
333 typedef struct _PEB {
334 BYTE Reserved1[2];
335 BYTE BeingDebugged;
336 BYTE Reserved2[1];
337 PVOID Reserved3[2];
338 PPEB_LDR_DATA Ldr;
339 PVOID /*PRTL_USER_PROCESS_PARAMETERS*/ ProcessParameters;
340 PVOID Reserved4[3];
341 PVOID AtlThunkSListPtr;
342 PVOID Reserved5;
343 ULONG Reserved6;
344 PVOID Reserved7;
345 ULONG Reserved8;
346 ULONG AtlThunkSListPtr32;
347 PVOID Reserved9[45];
348 BYTE Reserved10[96];
349 PVOID /*PPS_POST_PROCESS_INIT_ROUTINE*/ PostProcessInitRoutine;
350 BYTE Reserved11[128];
351 PVOID Reserved12[1];
352 ULONG SessionId;
353 } PEB, *PPEB;
354
355 typedef struct _TEB {
356 PVOID Reserved1[12];
357 PPEB ProcessEnvironmentBlock;
358 PVOID Reserved2[399];
359 BYTE Reserved3[1952];
360 PVOID TlsSlots[64];
361 BYTE Reserved4[8];
362 PVOID Reserved5[26];
363 PVOID ReservedForOle; // Windows 2000 only
364 PVOID Reserved6[4];
365 PVOID TlsExpansionSlots;
366 } TEB, *PTEB;
367
368 #endif // !defined(_INC_WINDOWS) || defined(APP_LOCAL_RUNTIME)
369
370 // retrieves the full path to the specified module, if moduleBase is NULL retreieves the full path to the
371 // executable module of the current process.
372 //
373 // Return value: number of characters in name string
374 //
375 //NOTE: This implementation exists because calling GetModuleFileName is not wack compliant. if we later decide
376 // that the framework package containing mrt100_app no longer needs to be wack compliant, this should be
377 // removed and the windows implementation of GetModuleFileName should be substitued on windows.
PalGetModuleFileName(_Out_ const TCHAR ** pModuleNameOut,HANDLE moduleBase)378 REDHAWK_PALEXPORT Int32 PalGetModuleFileName(_Out_ const TCHAR** pModuleNameOut, HANDLE moduleBase)
379 {
380 TEB* pTEB = NtCurrentTeb();
381 LIST_ENTRY* pStartLink = &(pTEB->ProcessEnvironmentBlock->Ldr->InMemoryOrderModuleList);
382 LIST_ENTRY* pCurLink = pStartLink->Flink;
383
384 do
385 {
386 LDR_DATA_TABLE_ENTRY* pEntry = CONTAINING_RECORD(pCurLink, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
387
388 //null moduleBase will result in the first module being returned
389 //since the module list is ordered this is the executable module of the current process
390 if ((pEntry->DllBase == moduleBase) || (moduleBase == NULL))
391 {
392 *pModuleNameOut = pEntry->FullDllName.Buffer;
393 return pEntry->FullDllName.Length / 2;
394 }
395 pCurLink = pCurLink->Flink;
396 }
397 while (pCurLink != pStartLink);
398
399 *pModuleNameOut = NULL;
400 return 0;
401 }
402
PalGetTickCount64()403 REDHAWK_PALEXPORT UInt64 __cdecl PalGetTickCount64()
404 {
405 return GetTickCount64();
406 }
407