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// File: perfmap.cpp
6//
7
8#include "common.h"
9
10#if defined(FEATURE_PERFMAP) && !defined(DACCESS_COMPILE)
11#include "perfmap.h"
12#include "perfinfo.h"
13#include "pal.h"
14
15// The code addresses are actually native image offsets during crossgen. Print
16// them as 32-bit numbers for consistent output when cross-targeting and to
17// make the output more compact.
18
19#ifdef CROSSGEN_COMPILE
20#define FMT_CODE_ADDR "%08x"
21#else
22#define FMT_CODE_ADDR "%p"
23#endif
24
25PerfMap * PerfMap::s_Current = nullptr;
26
27// Initialize the map for the process - called from EEStartupHelper.
28void PerfMap::Initialize()
29{
30 LIMITED_METHOD_CONTRACT;
31
32 // Only enable the map if requested.
33 if (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_PerfMapEnabled))
34 {
35 // Get the current process id.
36 int currentPid = GetCurrentProcessId();
37
38 // Create the map.
39 s_Current = new PerfMap(currentPid);
40
41 int signalNum = (int) CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_PerfMapIgnoreSignal);
42
43 if (signalNum > 0)
44 {
45 PAL_IgnoreProfileSignal(signalNum);
46 }
47 }
48}
49
50// Destroy the map for the process - called from EEShutdownHelper.
51void PerfMap::Destroy()
52{
53 LIMITED_METHOD_CONTRACT;
54
55 if (s_Current != nullptr)
56 {
57 delete s_Current;
58 s_Current = nullptr;
59 }
60}
61
62// Construct a new map for the process.
63PerfMap::PerfMap(int pid)
64{
65 LIMITED_METHOD_CONTRACT;
66
67 // Initialize with no failures.
68 m_ErrorEncountered = false;
69
70 m_StubsMapped = 0;
71
72 // Build the path to the map file on disk.
73 WCHAR tempPath[MAX_LONGPATH+1];
74 if(!GetTempPathW(MAX_LONGPATH, tempPath))
75 {
76 return;
77 }
78
79 SString path;
80 path.Printf("%Sperf-%d.map", &tempPath, pid);
81
82 // Open the map file for writing.
83 OpenFile(path);
84
85 m_PerfInfo = new PerfInfo(pid);
86}
87
88// Construct a new map without a specified file name.
89// Used for offline creation of NGEN map files.
90PerfMap::PerfMap()
91 : m_FileStream(nullptr)
92 , m_PerfInfo(nullptr)
93{
94 LIMITED_METHOD_CONTRACT;
95
96 // Initialize with no failures.
97 m_ErrorEncountered = false;
98
99 m_StubsMapped = 0;
100}
101
102// Clean-up resources.
103PerfMap::~PerfMap()
104{
105 LIMITED_METHOD_CONTRACT;
106
107 delete m_FileStream;
108 m_FileStream = nullptr;
109
110 delete m_PerfInfo;
111 m_PerfInfo = nullptr;
112}
113
114// Open the specified destination map file.
115void PerfMap::OpenFile(SString& path)
116{
117 STANDARD_VM_CONTRACT;
118
119 // Open the file stream.
120 m_FileStream = new (nothrow) CFileStream();
121 if(m_FileStream != nullptr)
122 {
123 HRESULT hr = m_FileStream->OpenForWrite(path.GetUnicode());
124 if(FAILED(hr))
125 {
126 delete m_FileStream;
127 m_FileStream = nullptr;
128 }
129 }
130}
131
132// Write a line to the map file.
133void PerfMap::WriteLine(SString& line)
134{
135 STANDARD_VM_CONTRACT;
136
137 EX_TRY
138 {
139 // Write the line.
140 // The PAL already takes a lock when writing, so we don't need to do so here.
141 StackScratchBuffer scratch;
142 const char * strLine = line.GetANSI(scratch);
143 ULONG inCount = line.GetCount();
144 ULONG outCount;
145 m_FileStream->Write(strLine, inCount, &outCount);
146
147 if (inCount != outCount)
148 {
149 // This will cause us to stop writing to the file.
150 // The file will still remain open until shutdown so that we don't have to take a lock at this level when we touch the file stream.
151 m_ErrorEncountered = true;
152 }
153
154 }
155 EX_CATCH{} EX_END_CATCH(SwallowAllExceptions);
156}
157
158// Log a method to the map.
159void PerfMap::LogMethod(MethodDesc * pMethod, PCODE pCode, size_t codeSize)
160{
161 CONTRACTL{
162 THROWS;
163 GC_NOTRIGGER;
164 MODE_PREEMPTIVE;
165 PRECONDITION(pMethod != nullptr);
166 PRECONDITION(pCode != nullptr);
167 PRECONDITION(codeSize > 0);
168 } CONTRACTL_END;
169
170 if (m_FileStream == nullptr || m_ErrorEncountered)
171 {
172 // A failure occurred, do not log.
173 return;
174 }
175
176 // Logging failures should not cause any exceptions to flow upstream.
177 EX_TRY
178 {
179 // Get the full method signature.
180 SString fullMethodSignature;
181 pMethod->GetFullMethodInfo(fullMethodSignature);
182
183 // Build the map file line.
184 StackScratchBuffer scratch;
185 SString line;
186 line.Printf(FMT_CODE_ADDR " %x %s\n", pCode, codeSize, fullMethodSignature.GetANSI(scratch));
187
188 // Write the line.
189 WriteLine(line);
190 }
191 EX_CATCH{} EX_END_CATCH(SwallowAllExceptions);
192}
193
194
195void PerfMap::LogImageLoad(PEFile * pFile)
196{
197 if (s_Current != nullptr)
198 {
199 s_Current->LogImage(pFile);
200 }
201}
202
203// Log an image load to the map.
204void PerfMap::LogImage(PEFile * pFile)
205{
206 CONTRACTL{
207 THROWS;
208 GC_NOTRIGGER;
209 MODE_PREEMPTIVE;
210 PRECONDITION(pFile != nullptr);
211 } CONTRACTL_END;
212
213
214 if (m_FileStream == nullptr || m_ErrorEncountered)
215 {
216 // A failure occurred, do not log.
217 return;
218 }
219
220 EX_TRY
221 {
222 WCHAR wszSignature[39];
223 GetNativeImageSignature(pFile, wszSignature, lengthof(wszSignature));
224
225 m_PerfInfo->LogImage(pFile, wszSignature);
226 }
227 EX_CATCH{} EX_END_CATCH(SwallowAllExceptions);
228}
229
230
231// Log a method to the map.
232void PerfMap::LogJITCompiledMethod(MethodDesc * pMethod, PCODE pCode, size_t codeSize)
233{
234 LIMITED_METHOD_CONTRACT;
235
236 if (s_Current != nullptr)
237 {
238 s_Current->LogMethod(pMethod, pCode, codeSize);
239 }
240}
241
242// Log a set of stub to the map.
243void PerfMap::LogStubs(const char* stubType, const char* stubOwner, PCODE pCode, size_t codeSize)
244{
245 LIMITED_METHOD_CONTRACT;
246
247 if (s_Current == nullptr || s_Current->m_FileStream == nullptr)
248 {
249 return;
250 }
251
252 // Logging failures should not cause any exceptions to flow upstream.
253 EX_TRY
254 {
255 if(!stubOwner)
256 {
257 stubOwner = "?";
258 }
259 if(!stubType)
260 {
261 stubOwner = "?";
262 }
263
264 // Build the map file line.
265 SString line;
266 line.Printf(FMT_CODE_ADDR " %x stub<%d> %s<%s>\n", pCode, codeSize, ++(s_Current->m_StubsMapped), stubType, stubOwner);
267
268 // Write the line.
269 s_Current->WriteLine(line);
270 }
271 EX_CATCH{} EX_END_CATCH(SwallowAllExceptions);
272}
273
274void PerfMap::GetNativeImageSignature(PEFile * pFile, WCHAR * pwszSig, unsigned int nSigSize)
275{
276 CONTRACTL{
277 PRECONDITION(pFile != nullptr);
278 PRECONDITION(pwszSig != nullptr);
279 PRECONDITION(nSigSize >= 39);
280 } CONTRACTL_END;
281
282 // We use the MVID as the signature, since ready to run images
283 // don't have a native image signature.
284 GUID mvid;
285 pFile->GetMVID(&mvid);
286 if(!StringFromGUID2(mvid, pwszSig, nSigSize))
287 {
288 pwszSig[0] = '\0';
289 }
290}
291
292// Create a new native image perf map.
293NativeImagePerfMap::NativeImagePerfMap(Assembly * pAssembly, BSTR pDestPath)
294 : PerfMap()
295{
296 STANDARD_VM_CONTRACT;
297
298 // Generate perfmap path.
299
300 // Get the assembly simple name.
301 LPCUTF8 lpcSimpleName = pAssembly->GetSimpleName();
302
303 // Get the native image signature (GUID).
304 // Used to ensure that we match symbols to the correct NGEN image.
305 WCHAR wszSignature[39];
306 GetNativeImageSignature(pAssembly->GetManifestFile(), wszSignature, lengthof(wszSignature));
307
308 // Build the path to the perfmap file, which consists of <inputpath><imagesimplename>.ni.<signature>.map.
309 // Example: /tmp/mscorlib.ni.{GUID}.map
310 SString sDestPerfMapPath;
311 sDestPerfMapPath.Printf("%S%s.ni.%S.map", pDestPath, lpcSimpleName, wszSignature);
312
313 // Open the perf map file.
314 OpenFile(sDestPerfMapPath);
315}
316
317// Log data to the perfmap for the specified module.
318void NativeImagePerfMap::LogDataForModule(Module * pModule)
319{
320 STANDARD_VM_CONTRACT;
321
322 PEImageLayout * pLoadedLayout = pModule->GetFile()->GetLoaded();
323 _ASSERTE(pLoadedLayout != nullptr);
324
325 SIZE_T baseAddr = (SIZE_T)pLoadedLayout->GetBase();
326
327#ifdef FEATURE_READYTORUN_COMPILER
328 if (pLoadedLayout->HasReadyToRunHeader())
329 {
330 ReadyToRunInfo::MethodIterator mi(pModule->GetReadyToRunInfo());
331 while (mi.Next())
332 {
333 MethodDesc *hotDesc = mi.GetMethodDesc();
334
335 LogPreCompiledMethod(hotDesc, mi.GetMethodStartAddress(), baseAddr);
336 }
337 }
338 else
339#endif // FEATURE_READYTORUN_COMPILER
340 {
341 MethodIterator mi((PTR_Module)pModule);
342 while (mi.Next())
343 {
344 MethodDesc *hotDesc = mi.GetMethodDesc();
345 hotDesc->CheckRestore();
346
347 LogPreCompiledMethod(hotDesc, mi.GetMethodStartAddress(), baseAddr);
348 }
349 }
350}
351
352// Log a pre-compiled method to the perfmap.
353void NativeImagePerfMap::LogPreCompiledMethod(MethodDesc * pMethod, PCODE pCode, SIZE_T baseAddr)
354{
355 STANDARD_VM_CONTRACT;
356
357 // Get information about the NGEN'd method code.
358 EECodeInfo codeInfo(pCode);
359 _ASSERTE(codeInfo.IsValid());
360
361 IJitManager::MethodRegionInfo methodRegionInfo;
362 codeInfo.GetMethodRegionInfo(&methodRegionInfo);
363
364 // NGEN can split code between hot and cold sections which are separate in memory.
365 // Emit an entry for each section if it is used.
366 if (methodRegionInfo.hotSize > 0)
367 {
368 LogMethod(pMethod, (PCODE)methodRegionInfo.hotStartAddress - baseAddr, methodRegionInfo.hotSize);
369 }
370
371 if (methodRegionInfo.coldSize > 0)
372 {
373 LogMethod(pMethod, (PCODE)methodRegionInfo.coldStartAddress - baseAddr, methodRegionInfo.coldSize);
374 }
375}
376
377#endif // FEATURE_PERFMAP && !DACCESS_COMPILE
378