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 |
17 | const 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 | // |
55 | CallType 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 | |
133 | CallType 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, ¶m) |
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) |
220 | bool 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 |
239 | const 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 |
261 | const 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 |
390 | inline 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 |
399 | inline bool CallUtils::IsNativeMethod(CORINFO_METHOD_HANDLE method) |
400 | { |
401 | return ((((size_t)method) & 0x2) == 0x2); |
402 | } |
403 | |
404 | // Originally from jit/compiler.hpp |
405 | inline 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 | |