1//
2// Copyright (c) Microsoft. All rights reserved.
3// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4//
5
6//----------------------------------------------------------
7// CallUtils.cpp - Utility code for analyzing and working with managed calls
8//----------------------------------------------------------
9
10#include "standardpch.h"
11#include "callutils.h"
12#include "typeutils.h"
13#include "errorhandling.h"
14#include "logging.h"
15
16// String representations of the JIT helper functions
17const char* kHelperName[CORINFO_HELP_COUNT] = {
18#define JITHELPER(code, pfnHelper, sig) #code,
19#define DYNAMICJITHELPER(code, pfnHelper, sig) #code,
20#include "jithelpers.h"
21};
22
23//-------------------------------------------------------------------------------------------------
24
25//
26// Provides information about the target of an outgoing call, based on where it was emitted in the
27// generated code stream.
28//
29// Primarily, this returns what the destination of the call is (e.g. a method, a helper function), but this
30// can also provide:
31//
32// - A symbolic name for the call target (i.e. the function name).
33// - For non-helper methods, the method signature of the call target.
34//
35// Arguments:
36// mc - The method context of the method containing this call site.
37// cr - The compile result for the method containing this call site.
38// callInstrOffset - The native offset of the call site in the generated code stream.
39// outSigInfo - [out] The signature of the outgoing call. Optional (pass nullptr if unwanted).
40// outCallTargetSymbol - [out] A string representation of the outgoing call. Optional (pass nullptr if
41// unwanted).
42//
43// Return Value:
44// What type of call the outgoing call is.
45//
46// Notes:
47// - This depends on the JIT having registered the call site with the EE through recordCallSite. If the
48// JIT didn't do this, GetDirectCallSite can obtain most of the same information for direct calls.
49// - If the call site is for a helper method, then outSigInfo will not be changed, since helper calls
50// have no signature information.
51// - If you pass in a valid pointer for outCallTargetSymbol, this function will allocate memory for it
52// if it is able to understand that call (i.e. if it does not return CallType_Unknown). You, the caller,
53// are responsible for freeing the memory (with delete[]).
54//
55CallType CallUtils::GetRecordedCallSiteInfo(MethodContext* mc,
56 CompileResult* cr,
57 unsigned int callInstrOffset,
58 /*out*/ CORINFO_SIG_INFO* outSigInfo,
59 /*out*/ char** outCallTargetSymbol)
60{
61 AssertCodeMsg(mc != nullptr, EXCEPTIONCODE_CALLUTILS,
62 "Null method context passed into GetCallTargetInfo for call at offset %x.", callInstrOffset);
63 AssertCodeMsg(cr != nullptr, EXCEPTIONCODE_CALLUTILS,
64 "Null compile result passed into GetCallTargetInfo for call at offset %x.", callInstrOffset);
65
66 CallType targetType = CallType_Unknown;
67
68 CORINFO_SIG_INFO callSig;
69 bool recordedCallSig = cr->fndRecordCallSiteSigInfo(callInstrOffset, &callSig);
70
71 CORINFO_METHOD_HANDLE methodHandle = nullptr;
72 bool recordedMethodHandle = cr->fndRecordCallSiteMethodHandle(callInstrOffset, &methodHandle);
73
74 if (recordedCallSig)
75 {
76 if (outSigInfo != nullptr)
77 *outSigInfo = callSig;
78
79 if (outCallTargetSymbol != nullptr)
80 *outCallTargetSymbol = (char*)GetMethodFullName(mc, methodHandle, callSig);
81
82 targetType = CallType_UserFunction;
83 }
84 else if (recordedMethodHandle)
85 {
86 CorInfoHelpFunc helperNum = CallUtils::GetHelperNum(methodHandle);
87 AssertCodeMsg(helperNum != CORINFO_HELP_UNDEF, EXCEPTIONCODE_CALLUTILS,
88 "Unknown call at offset %x with method handle %016llX.", callInstrOffset, methodHandle);
89
90 size_t length = strlen(kHelperName[helperNum]) + 1;
91 *outCallTargetSymbol = new char[length];
92 strcpy_s(*outCallTargetSymbol, length, kHelperName[helperNum]);
93
94 targetType = CallType_Helper;
95 }
96 else
97 {
98 LogWarning("Call site at offset %x was not recorded via recordCallSite.", callInstrOffset);
99 }
100
101 return targetType;
102}
103
104//
105// Provides information about the target of an outgoing call, based on the outgoing call's target address.
106//
107// Primarily, this returns what the destination of the call is (e.g. a method, a helper function), but this
108// can also provide:
109//
110// - A symbolic name for the call target (i.e. the function name).
111// - For certain types of managed methods, the method signature of the call target.
112//
113// Arguments:
114// mc - The method context of the method containing this outgoing call.
115// callTarget - The target address of the outgoing call.
116// outSigInfo - [out] The signature of the outgoing call. Optional (pass nullptr if unwanted).
117// outCallTargetSymbol - [out] A string representation of the outgoing call. Optional (pass nullptr if
118// unwanted).
119//
120// Return Value:
121// What type of call the outgoing call is.
122//
123// Assumptions:
124// The given method address does not point to a jump stub.
125//
126// Notes:
127// - This only works for direct calls that have a static target address.
128// - If you pass in a valid pointer for outCallTargetSymbol, this function will allocate memory for it
129// if it is able to understand that call (i.e. if it does not return CallType_Unknown). You, the caller,
130// are responsible for freeing the memory (with delete[]).
131//
132
133CallType CallUtils::GetDirectCallSiteInfo(MethodContext* mc,
134 void* callTarget,
135 /*out*/ CORINFO_SIG_INFO* outSigInfo,
136 /*out*/ char** outCallTargetSymbol)
137{
138 AssertCodeMsg(mc != nullptr, EXCEPTIONCODE_CALLUTILS,
139 "Null method context passed into GetCallTargetInfo for call to target %016llX.", callTarget);
140
141 CallType targetType = CallType_Unknown;
142 MethodContext::DLD functionEntryPoint;
143 CORINFO_METHOD_HANDLE methodHandle;
144
145 // Try to first obtain a method handle associated with this call target
146 functionEntryPoint.A = (DWORDLONG)callTarget;
147 functionEntryPoint.B = 0; // TODO-Cleanup: we should be more conscious of this...
148
149 if (mc->fndGetFunctionEntryPoint(functionEntryPoint, &methodHandle))
150 {
151 // Now try to obtain the call info associated with this method handle
152
153 struct Param
154 {
155 MethodContext* mc;
156 CORINFO_SIG_INFO* outSigInfo;
157 char** outCallTargetSymbol;
158 CallType* pTargetType;
159 CORINFO_METHOD_HANDLE* pMethodHandle;
160 } param;
161 param.mc = mc;
162 param.outSigInfo = outSigInfo;
163 param.outCallTargetSymbol = outCallTargetSymbol;
164 param.pTargetType = &targetType;
165 param.pMethodHandle = &methodHandle;
166
167 PAL_TRY(Param*, pParam, &param)
168 {
169 CORINFO_CALL_INFO callInfo;
170
171 pParam->mc->repGetCallInfoFromMethodHandle(*pParam->pMethodHandle, &callInfo);
172
173 if (pParam->outSigInfo != nullptr)
174 *pParam->outSigInfo = callInfo.sig;
175
176 if (pParam->outCallTargetSymbol != nullptr)
177 *pParam->outCallTargetSymbol =
178 (char*)GetMethodFullName(pParam->mc, *pParam->pMethodHandle, callInfo.sig);
179
180 *pParam->pTargetType = CallType_UserFunction;
181 }
182 PAL_EXCEPT_FILTER(FilterSuperPMIExceptions_CatchMC)
183 {
184 LogWarning("Didn't find call info for method handle %016llX (call target: %016llX)", methodHandle,
185 callTarget);
186 }
187 PAL_ENDTRY
188 }
189 else
190 {
191 // No method handle associated with this target, so check if it's a helper
192 CorInfoHelpFunc helperNum;
193
194 if (mc->fndGetHelperFtn(callTarget, &helperNum))
195 {
196 if (outCallTargetSymbol != nullptr)
197 {
198 size_t length = strlen(kHelperName[helperNum]) + 1;
199 *outCallTargetSymbol = new char[length];
200 strcpy_s(*outCallTargetSymbol, length, kHelperName[helperNum]);
201 }
202
203 targetType = CallType_Helper;
204 }
205 else
206 {
207 LogWarning("Call to target %016llX has no method handle and is not a helper call.", callTarget);
208 }
209 }
210
211 return targetType;
212}
213
214//-------------------------------------------------------------------------------------------------
215// Utilty code that was stolen from various sections of the JIT codebase and tweaked to go through
216// SuperPMI's method context replaying instead of directly making calls into the JIT/EE interface.
217//-------------------------------------------------------------------------------------------------
218
219// Stolen from Compiler::impMethodInfo_hasRetBuffArg (in the importer)
220bool CallUtils::HasRetBuffArg(MethodContext* mc, CORINFO_SIG_INFO args)
221{
222 if (args.retType != CORINFO_TYPE_VALUECLASS && args.retType != CORINFO_TYPE_REFANY)
223 {
224 return false;
225 }
226
227#if defined(_TARGET_AMD64_)
228 // We don't need a return buffer if:
229 // i) TYP_STRUCT argument that can fit into a single register and
230 // ii) Power of two sized TYP_STRUCT on AMD64.
231 unsigned size = mc->repGetClassSize(args.retTypeClass);
232 return (size > sizeof(void*)) || ((size & (size - 1)) != 0);
233#else
234 return true;
235#endif
236}
237
238// Originally from src/jit/ee_il_dll.cpp
239const char* CallUtils::GetMethodName(MethodContext* mc, CORINFO_METHOD_HANDLE method, const char** classNamePtr)
240{
241 if (GetHelperNum(method))
242 {
243 if (classNamePtr != nullptr)
244 *classNamePtr = "HELPER";
245
246 // The JIT version uses the getHelperName JIT/EE interface call, but this is easier for us
247 return kHelperName[GetHelperNum(method)];
248 }
249
250 if (IsNativeMethod(method))
251 {
252 if (classNamePtr != nullptr)
253 *classNamePtr = "NATIVE";
254 method = GetMethodHandleForNative(method);
255 }
256
257 return (mc->repGetMethodName(method, classNamePtr));
258}
259
260// Originally from src/jit/eeinterface.cpp
261const char* CallUtils::GetMethodFullName(MethodContext* mc, CORINFO_METHOD_HANDLE hnd, CORINFO_SIG_INFO sig)
262{
263 const char* returnType = NULL;
264
265 const char* className;
266 const char* methodName = GetMethodName(mc, hnd, &className);
267 if ((GetHelperNum(hnd) != CORINFO_HELP_UNDEF) || IsNativeMethod(hnd))
268 {
269 return methodName;
270 }
271
272 size_t length = 0;
273 unsigned i;
274
275 /* Generating the full signature is a two-pass process. First we have to walk
276 the components in order to assess the total size, then we allocate the buffer
277 and copy the elements into it.
278 */
279
280 /* Right now there is a race-condition in the EE, className can be NULL */
281
282 /* initialize length with length of className and '.' */
283
284 if (className != nullptr)
285 length = strlen(className) + 1;
286 else
287 {
288 // Tweaked to avoid using CRT assertions
289 Assert(strlen("<NULL>.") == 7);
290 length = 7;
291 }
292
293 /* add length of methodName and opening bracket */
294 length += strlen(methodName) + 1;
295
296 CORINFO_ARG_LIST_HANDLE argList = sig.args;
297
298 for (i = 0; i < sig.numArgs; i++)
299 {
300 // Tweaked to use EE types instead of JIT-specific types
301 CORINFO_CLASS_HANDLE typeHandle;
302 DWORD exception;
303 CorInfoType type = strip(mc->repGetArgType(&sig, argList, &typeHandle, &exception));
304
305 length += strlen(TypeUtils::GetCorInfoTypeName(type));
306 argList = mc->repGetArgNext(argList);
307 }
308
309 /* add ',' if there is more than one argument */
310
311 if (sig.numArgs > 1)
312 length += (sig.numArgs - 1);
313
314 // Tweaked to use EE types instead of JIT-specific types
315 if (sig.retType != CORINFO_TYPE_VOID)
316 {
317 returnType = TypeUtils::GetCorInfoTypeName(sig.retType);
318 length += strlen(returnType) + 1; // don't forget the delimiter ':'
319 }
320
321 // Does it have a 'this' pointer? Don't count explicit this, which has the this pointer type as the first element of
322 // the arg type list
323 if (sig.hasThis() && !sig.hasExplicitThis())
324 {
325 // Tweaked to avoid using CRT assertions
326 Assert(strlen(":this") == 5);
327 length += 5;
328 }
329
330 /* add closing bracket and null terminator */
331
332 length += 2;
333
334 char* retName = new char[length]; // Tweaked to use "new" instead of compGetMem
335
336 /* Now generate the full signature string in the allocated buffer */
337
338 if (className)
339 {
340 strcpy_s(retName, length, className);
341 strcat_s(retName, length, ":");
342 }
343 else
344 {
345 strcpy_s(retName, length, "<NULL>.");
346 }
347
348 strcat_s(retName, length, methodName);
349
350 // append the signature
351 strcat_s(retName, length, "(");
352
353 argList = sig.args;
354
355 for (i = 0; i < sig.numArgs; i++)
356 {
357 // Tweaked to use EE types instead of JIT-specific types
358 CORINFO_CLASS_HANDLE typeHandle;
359 DWORD exception;
360 CorInfoType type = strip(mc->repGetArgType(&sig, argList, &typeHandle, &exception));
361 strcat_s(retName, length, TypeUtils::GetCorInfoTypeName(type));
362
363 argList = mc->repGetArgNext(argList);
364 if (i + 1 < sig.numArgs)
365 strcat_s(retName, length, ",");
366 }
367
368 strcat_s(retName, length, ")");
369
370 if (returnType)
371 {
372 strcat_s(retName, length, ":");
373 strcat_s(retName, length, returnType);
374 }
375
376 // Does it have a 'this' pointer? Don't count explicit this, which has the this pointer type as the first element of
377 // the arg type list
378 if (sig.hasThis() && !sig.hasExplicitThis())
379 {
380 strcat_s(retName, length, ":this");
381 }
382
383 // Tweaked to avoid using CRT assertions
384 Assert(strlen(retName) == (length - 1));
385
386 return (retName);
387}
388
389// Originally from jit/compiler.hpp
390inline CorInfoHelpFunc CallUtils::GetHelperNum(CORINFO_METHOD_HANDLE method)
391{
392 // Helpers are marked by the fact that they are odd numbers
393 if (!(((size_t)method) & 1))
394 return (CORINFO_HELP_UNDEF);
395 return ((CorInfoHelpFunc)(((size_t)method) >> 2));
396}
397
398// Originally from jit/compiler.hpp
399inline bool CallUtils::IsNativeMethod(CORINFO_METHOD_HANDLE method)
400{
401 return ((((size_t)method) & 0x2) == 0x2);
402}
403
404// Originally from jit/compiler.hpp
405inline CORINFO_METHOD_HANDLE CallUtils::GetMethodHandleForNative(CORINFO_METHOD_HANDLE method)
406{
407 // Tweaked to avoid using CRT assertions
408 Assert((((size_t)method) & 0x3) == 0x2);
409 return (CORINFO_METHOD_HANDLE)(((size_t)method) & ~0x3);
410}
411