-
Notifications
You must be signed in to change notification settings - Fork 93
/
method_rewriter.cpp
568 lines (518 loc) · 22.7 KB
/
method_rewriter.cpp
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
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
#include "method_rewriter.h"
#include "cor_profiler.h"
#include "il_rewriter_wrapper.h"
#include "logger.h"
#include "stats.h"
#include "version.h"
#include "environment_variables_util.h"
namespace trace
{
/// <summary>
/// Rewrite the target method body with the calltarget implementation. (This is function is triggered by the ReJIT
/// handler) Resulting code structure:
///
/// - Add locals for TReturn (if non-void method), CallTargetState, CallTargetReturn/CallTargetReturn<TReturn>,
/// Exception
/// - Initialize locals
///
/// try
/// {
/// try
/// {
/// try
/// {
/// - Invoke BeginMethod with object instance (or null if static method) and original method arguments
/// - Store result into CallTargetState local
/// }
/// catch
/// {
/// - Invoke LogException(Exception)
/// }
///
/// - Execute original method instructions
/// * All RET instructions are replaced with a LEAVE_S. If non-void method, the value on the stack is first stored
/// in the TReturn local.
/// }
/// catch (Exception)
/// {
/// - Store exception into Exception local
/// - throw
/// }
/// }
/// finally
/// {
/// try
/// {
/// - Invoke EndMethod with object instance (or null if static method), TReturn local (if non-void method),
/// CallTargetState local, and Exception local
/// - Store result into CallTargetReturn/CallTargetReturn<TReturn> local
/// - If non-void method, store CallTargetReturn<TReturn>.GetReturnValue() into TReturn local
/// }
/// catch
/// {
/// - Invoke LogException(Exception)
/// }
/// }
///
/// - If non-void method, load TReturn local
/// - RET
/// </summary>
/// <param name="moduleHandler">Module ReJIT handler representation</param>
/// <param name="methodHandler">Method ReJIT handler representation</param>
/// <returns>Result of the rewriting</returns>
HRESULT TracerMethodRewriter::Rewrite(RejitHandlerModule* moduleHandler, RejitHandlerModuleMethod* methodHandler)
{
if (methodHandler == nullptr)
{
Logger::Error("TracerMethodRewriter::Rewrite: methodHandler is null. "
"MethodDef: ",
methodHandler->GetMethodDef());
return S_FALSE;
}
auto tracerMethodHandler = static_cast<TracerRejitHandlerModuleMethod*>(methodHandler);
if (tracerMethodHandler->GetIntegrationDefinition() == nullptr)
{
Logger::Warn("TracerMethodRewriter::Rewrite: IntegrationDefinition is missing for "
"MethodDef: ",
methodHandler->GetMethodDef());
return S_FALSE;
}
auto _ = trace::Stats::Instance()->CallTargetRewriterCallbackMeasure();
auto corProfiler = trace::profiler;
ModuleID module_id = moduleHandler->GetModuleId();
ModuleMetadata& module_metadata = *moduleHandler->GetModuleMetadata();
FunctionInfo* caller = methodHandler->GetFunctionInfo();
TracerTokens* tracerTokens = module_metadata.GetTracerTokens();
mdToken function_token = caller->id;
TypeSignature retFuncArg = caller->method_signature.GetReturnValue();
IntegrationDefinition* integration_definition = tracerMethodHandler->GetIntegrationDefinition();
const auto[retFuncElementType, retTypeFlags] = retFuncArg.GetElementTypeAndFlags();
bool isVoid = (retTypeFlags & TypeFlagVoid) > 0;
bool isStatic = !(caller->method_signature.CallingConvention() & IMAGE_CEE_CS_CALLCONV_HASTHIS);
const auto& methodArguments = caller->method_signature.GetMethodArguments();
int numArgs = caller->method_signature.NumberOfArguments();
auto metaEmit = module_metadata.metadata_emit;
auto metaImport = module_metadata.metadata_import;
// *** Get reference to the integration type
mdTypeRef integration_type_ref = mdTypeRefNil;
if (!corProfiler->GetIntegrationTypeRef(module_metadata, module_id, *integration_definition, integration_type_ref))
{
Logger::Warn("*** CallTarget_RewriterCallback() skipping method: Integration Type Ref cannot be found for ",
" token=", function_token, " caller_name=", caller->type.name, ".", caller->name, "()");
return S_FALSE;
}
if (Logger::IsDebugEnabled())
{
Logger::Debug("*** CallTarget_RewriterCallback() Start: ", caller->type.name, ".", caller->name, "() [IsVoid=",
isVoid, ", IsStatic=", isStatic, ", IntegrationType=",
integration_definition->integration_type.name, ", Arguments=", numArgs, "]");
}
// First we check if the managed profiler has not been loaded yet
if (!corProfiler->ProfilerAssemblyIsLoadedIntoAppDomain(module_metadata.app_domain_id))
{
Logger::Warn(
"*** CallTarget_RewriterCallback() skipping method: Method replacement found but the managed profiler has "
"not yet been loaded into AppDomain with id=",
module_metadata.app_domain_id, " token=", function_token, " caller_name=", caller->type.name, ".",
caller->name, "()");
return S_FALSE;
}
// *** Create rewriter
ILRewriter rewriter(corProfiler->info_, methodHandler->GetFunctionControl(), module_id, function_token);
bool modified = false;
auto hr = rewriter.Import();
if (FAILED(hr))
{
Logger::Warn("*** CallTarget_RewriterCallback(): Call to ILRewriter.Import() failed for ", module_id, " ",
function_token);
return S_FALSE;
}
// *** Store the original il code text if the dump_il option is enabled.
std::string original_code;
if (IsDumpILRewriteEnabled())
{
original_code = corProfiler->GetILCodes("*** CallTarget_RewriterCallback(): Original Code: ", &rewriter,
*caller, module_metadata.metadata_import);
}
// *** Create the rewriter wrapper helper
ILRewriterWrapper reWriterWrapper(&rewriter);
reWriterWrapper.SetILPosition(rewriter.GetILList()->m_pNext);
// *** Modify the Local Var Signature of the method and initialize the new local vars
ULONG callTargetStateIndex = static_cast<ULONG>(ULONG_MAX);
ULONG exceptionIndex = static_cast<ULONG>(ULONG_MAX);
ULONG callTargetReturnIndex = static_cast<ULONG>(ULONG_MAX);
ULONG returnValueIndex = static_cast<ULONG>(ULONG_MAX);
mdToken callTargetStateToken = mdTokenNil;
mdToken exceptionToken = mdTokenNil;
mdToken callTargetReturnToken = mdTokenNil;
ILInstr* firstInstruction;
tracerTokens->ModifyLocalSigAndInitialize(&reWriterWrapper, caller, &callTargetStateIndex, &exceptionIndex,
&callTargetReturnIndex, &returnValueIndex, &callTargetStateToken,
&exceptionToken, &callTargetReturnToken, &firstInstruction);
// ***
// BEGIN METHOD PART
// ***
// *** Load instance into the stack (if not static)
if (isStatic)
{
if (caller->type.valueType)
{
// Static methods in a ValueType can't be instrumented.
// In the future this can be supported by adding a local for the valuetype and initialize it to the default
// value. After the signature modification we need to emit the following IL to initialize and load into the
// stack.
// ldloca.s [localIndex]
// initobj [valueType]
// ldloc.s [localIndex]
Logger::Warn("*** CallTarget_RewriterCallback(): Static methods in a ValueType cannot be instrumented. ");
return S_FALSE;
}
reWriterWrapper.LoadNull();
}
else
{
reWriterWrapper.LoadArgument(0);
if (caller->type.valueType)
{
if (caller->type.type_spec != mdTypeSpecNil)
{
reWriterWrapper.LoadObj(caller->type.type_spec);
}
else if (!caller->type.isGeneric)
{
reWriterWrapper.LoadObj(caller->type.id);
}
else
{
// Generic struct instrumentation is not supported
// IMetaDataImport::GetMemberProps and IMetaDataImport::GetMemberRefProps returns
// The parent token as mdTypeDef and not as a mdTypeSpec
// that's because the method definition is stored in the mdTypeDef
// The problem is that we don't have the exact Spec of that generic
// We can't emit LoadObj or Box because that would result in an invalid IL.
// This problem doesn't occur on a class type because we can always relay in the
// object type.
return S_FALSE;
}
}
}
// *** Load the method arguments to the stack
if (numArgs < FASTPATH_COUNT)
{
// Load the arguments directly (FastPath)
for (int i = 0; i < numArgs; i++)
{
const auto[elementType, argTypeFlags] = methodArguments[i].GetElementTypeAndFlags();
if (corProfiler->enable_by_ref_instrumentation)
{
if (argTypeFlags & TypeFlagByRef)
{
reWriterWrapper.LoadArgument(i + (isStatic ? 0 : 1));
}
else
{
reWriterWrapper.LoadArgumentRef(i + (isStatic ? 0 : 1));
}
}
else
{
reWriterWrapper.LoadArgument(i + (isStatic ? 0 : 1));
if (argTypeFlags & TypeFlagByRef)
{
Logger::Warn("*** CallTarget_RewriterCallback(): Methods with ref parameters "
"cannot be instrumented. ");
return S_FALSE;
}
}
}
}
else
{
// Load the arguments inside an object array (SlowPath)
reWriterWrapper.CreateArray(tracerTokens->GetObjectTypeRef(), numArgs);
for (int i = 0; i < numArgs; i++)
{
reWriterWrapper.BeginLoadValueIntoArray(i);
reWriterWrapper.LoadArgument(i + (isStatic ? 0 : 1));
const auto[elementType, argTypeFlags] = methodArguments[i].GetElementTypeAndFlags();
if (argTypeFlags & TypeFlagByRef)
{
Logger::Warn("*** CallTarget_RewriterCallback(): Methods with ref parameters "
"cannot be instrumented. ");
return S_FALSE;
}
if (argTypeFlags & TypeFlagBoxedType)
{
const auto& tok = methodArguments[i].GetTypeTok(metaEmit, tracerTokens->GetCorLibAssemblyRef());
if (tok == mdTokenNil)
{
return S_FALSE;
}
reWriterWrapper.Box(tok);
}
reWriterWrapper.EndLoadValueIntoArray();
}
}
// *** Emit BeginMethod call
if (Logger::IsDebugEnabled())
{
Logger::Debug("Caller Type.Id: ", HexStr(&caller->type.id, sizeof(mdToken)));
Logger::Debug("Caller Type.IsGeneric: ", caller->type.isGeneric);
Logger::Debug("Caller Type.IsValid: ", caller->type.IsValid());
Logger::Debug("Caller Type.Name: ", caller->type.name);
Logger::Debug("Caller Type.TokenType: ", caller->type.token_type);
Logger::Debug("Caller Type.Spec: ", HexStr(&caller->type.type_spec, sizeof(mdTypeSpec)));
Logger::Debug("Caller Type.ValueType: ", caller->type.valueType);
//
if (caller->type.extend_from != nullptr)
{
Logger::Debug("Caller Type Extend From.Id: ", HexStr(&caller->type.extend_from->id, sizeof(mdToken)));
Logger::Debug("Caller Type Extend From.IsGeneric: ", caller->type.extend_from->isGeneric);
Logger::Debug("Caller Type Extend From.IsValid: ", caller->type.extend_from->IsValid());
Logger::Debug("Caller Type Extend From.Name: ", caller->type.extend_from->name);
Logger::Debug("Caller Type Extend From.TokenType: ", caller->type.extend_from->token_type);
Logger::Debug("Caller Type Extend From.Spec: ",
HexStr(&caller->type.extend_from->type_spec, sizeof(mdTypeSpec)));
Logger::Debug("Caller Type Extend From.ValueType: ", caller->type.extend_from->valueType);
}
//
if (caller->type.parent_type != nullptr)
{
Logger::Debug("Caller ParentType.Id: ", HexStr(&caller->type.parent_type->id, sizeof(mdToken)));
Logger::Debug("Caller ParentType.IsGeneric: ", caller->type.parent_type->isGeneric);
Logger::Debug("Caller ParentType.IsValid: ", caller->type.parent_type->IsValid());
Logger::Debug("Caller ParentType.Name: ", caller->type.parent_type->name);
Logger::Debug("Caller ParentType.TokenType: ", caller->type.parent_type->token_type);
Logger::Debug("Caller ParentType.Spec: ", HexStr(&caller->type.parent_type->type_spec, sizeof(mdTypeSpec)));
Logger::Debug("Caller ParentType.ValueType: ", caller->type.parent_type->valueType);
}
}
ILInstr* beginCallInstruction;
hr = tracerTokens->WriteBeginMethod(&reWriterWrapper, integration_type_ref, &caller->type, methodArguments,
&beginCallInstruction);
if (FAILED(hr))
{
// Error message is written to the log in WriteBeginMethod.
return S_FALSE;
}
reWriterWrapper.StLocal(callTargetStateIndex);
ILInstr* pStateLeaveToBeginOriginalMethodInstr = reWriterWrapper.CreateInstr(CEE_LEAVE_S);
// *** BeginMethod call catch
ILInstr* beginMethodCatchFirstInstr = nullptr;
tracerTokens->WriteLogException(&reWriterWrapper, integration_type_ref, &caller->type, &beginMethodCatchFirstInstr);
ILInstr* beginMethodCatchLeaveInstr = reWriterWrapper.CreateInstr(CEE_LEAVE_S);
// *** BeginMethod exception handling clause
EHClause beginMethodExClause{};
beginMethodExClause.m_Flags = COR_ILEXCEPTION_CLAUSE_NONE;
beginMethodExClause.m_pTryBegin = firstInstruction;
beginMethodExClause.m_pTryEnd = beginMethodCatchFirstInstr;
beginMethodExClause.m_pHandlerBegin = beginMethodCatchFirstInstr;
beginMethodExClause.m_pHandlerEnd = beginMethodCatchLeaveInstr;
beginMethodExClause.m_ClassToken = tracerTokens->GetExceptionTypeRef();
// ***
// METHOD EXECUTION
// ***
ILInstr* beginOriginalMethodInstr = reWriterWrapper.GetCurrentILInstr();
pStateLeaveToBeginOriginalMethodInstr->m_pTarget = beginOriginalMethodInstr;
beginMethodCatchLeaveInstr->m_pTarget = beginOriginalMethodInstr;
// ***
// ENDING OF THE METHOD EXECUTION
// ***
// *** Create return instruction and insert it at the end
ILInstr* methodReturnInstr = rewriter.NewILInstr();
methodReturnInstr->m_opcode = CEE_RET;
rewriter.InsertAfter(rewriter.GetILList()->m_pPrev, methodReturnInstr);
reWriterWrapper.SetILPosition(methodReturnInstr);
// ***
// EXCEPTION CATCH
// ***
ILInstr* startExceptionCatch = reWriterWrapper.StLocal(exceptionIndex);
reWriterWrapper.SetILPosition(methodReturnInstr);
ILInstr* rethrowInstr = reWriterWrapper.Rethrow();
// ***
// EXCEPTION FINALLY / END METHOD PART
// ***
ILInstr* endMethodTryStartInstr;
// *** Load instance into the stack (if not static)
if (isStatic)
{
if (caller->type.valueType)
{
// Static methods in a ValueType can't be instrumented.
// In the future this can be supported by adding a local for the valuetype
// and initialize it to the default value. After the signature
// modification we need to emit the following IL to initialize and load
// into the stack.
// ldloca.s [localIndex]
// initobj [valueType]
// ldloc.s [localIndex]
Logger::Warn("CallTarget_RewriterCallback: Static methods in a ValueType cannot "
"be instrumented. ");
return S_FALSE;
}
endMethodTryStartInstr = reWriterWrapper.LoadNull();
}
else
{
endMethodTryStartInstr = reWriterWrapper.LoadArgument(0);
if (caller->type.valueType)
{
if (caller->type.type_spec != mdTypeSpecNil)
{
reWriterWrapper.LoadObj(caller->type.type_spec);
}
else if (!caller->type.isGeneric)
{
reWriterWrapper.LoadObj(caller->type.id);
}
else
{
// Generic struct instrumentation is not supported
// IMetaDataImport::GetMemberProps and IMetaDataImport::GetMemberRefProps returns
// The parent token as mdTypeDef and not as a mdTypeSpec
// that's because the method definition is stored in the mdTypeDef
// The problem is that we don't have the exact Spec of that generic
// We can't emit LoadObj or Box because that would result in an invalid IL.
// This problem doesn't occur on a class type because we can always relay in the
// object type.
return S_FALSE;
}
}
}
// *** Load the return value is is not void
if (!isVoid)
{
reWriterWrapper.LoadLocal(returnValueIndex);
}
reWriterWrapper.LoadLocal(exceptionIndex);
if (corProfiler->enable_calltarget_state_by_ref)
{
reWriterWrapper.LoadLocalAddress(callTargetStateIndex);
}
else
{
reWriterWrapper.LoadLocal(callTargetStateIndex);
}
ILInstr* endMethodCallInstr;
if (isVoid)
{
tracerTokens->WriteEndVoidReturnMemberRef(&reWriterWrapper, integration_type_ref, &caller->type,
&endMethodCallInstr);
}
else
{
tracerTokens->WriteEndReturnMemberRef(&reWriterWrapper, integration_type_ref, &caller->type, &retFuncArg,
&endMethodCallInstr);
}
reWriterWrapper.StLocal(callTargetReturnIndex);
if (!isVoid)
{
ILInstr* callTargetReturnGetReturnInstr;
reWriterWrapper.LoadLocalAddress(callTargetReturnIndex);
tracerTokens->WriteCallTargetReturnGetReturnValue(&reWriterWrapper, callTargetReturnToken,
&callTargetReturnGetReturnInstr);
reWriterWrapper.StLocal(returnValueIndex);
}
ILInstr* endMethodTryLeave = reWriterWrapper.CreateInstr(CEE_LEAVE_S);
// *** EndMethod call catch
ILInstr* endMethodCatchFirstInstr = nullptr;
tracerTokens->WriteLogException(&reWriterWrapper, integration_type_ref, &caller->type, &endMethodCatchFirstInstr);
ILInstr* endMethodCatchLeaveInstr = reWriterWrapper.CreateInstr(CEE_LEAVE_S);
// *** EndMethod exception handling clause
EHClause endMethodExClause{};
endMethodExClause.m_Flags = COR_ILEXCEPTION_CLAUSE_NONE;
endMethodExClause.m_pTryBegin = endMethodTryStartInstr;
endMethodExClause.m_pTryEnd = endMethodCatchFirstInstr;
endMethodExClause.m_pHandlerBegin = endMethodCatchFirstInstr;
endMethodExClause.m_pHandlerEnd = endMethodCatchLeaveInstr;
endMethodExClause.m_ClassToken = tracerTokens->GetExceptionTypeRef();
// *** EndMethod leave to finally
ILInstr* endFinallyInstr = reWriterWrapper.EndFinally();
endMethodTryLeave->m_pTarget = endFinallyInstr;
endMethodCatchLeaveInstr->m_pTarget = endFinallyInstr;
// ***
// METHOD RETURN
// ***
// Load the current return value from the local var
if (!isVoid)
{
reWriterWrapper.LoadLocal(returnValueIndex);
}
// Changes all returns to a LEAVE.S
for (ILInstr* pInstr = rewriter.GetILList()->m_pNext; pInstr != rewriter.GetILList(); pInstr = pInstr->m_pNext)
{
switch (pInstr->m_opcode)
{
case CEE_RET:
{
if (pInstr != methodReturnInstr)
{
if (!isVoid)
{
reWriterWrapper.SetILPosition(pInstr);
reWriterWrapper.StLocal(returnValueIndex);
}
pInstr->m_opcode = CEE_LEAVE_S;
pInstr->m_pTarget = endFinallyInstr->m_pNext;
}
break;
}
default:
break;
}
}
// Exception handling clauses
EHClause exClause{};
exClause.m_Flags = COR_ILEXCEPTION_CLAUSE_NONE;
exClause.m_pTryBegin = firstInstruction;
exClause.m_pTryEnd = startExceptionCatch;
exClause.m_pHandlerBegin = startExceptionCatch;
exClause.m_pHandlerEnd = rethrowInstr;
exClause.m_ClassToken = tracerTokens->GetExceptionTypeRef();
EHClause finallyClause{};
finallyClause.m_Flags = COR_ILEXCEPTION_CLAUSE_FINALLY;
finallyClause.m_pTryBegin = firstInstruction;
finallyClause.m_pTryEnd = rethrowInstr->m_pNext;
finallyClause.m_pHandlerBegin = rethrowInstr->m_pNext;
finallyClause.m_pHandlerEnd = endFinallyInstr;
// ***
// Update and Add exception clauses
// ***
auto ehCount = rewriter.GetEHCount();
auto ehPointer = rewriter.GetEHPointer();
auto newEHClauses = new EHClause[ehCount + 4];
for (unsigned i = 0; i < ehCount; i++)
{
newEHClauses[i] = ehPointer[i];
}
// *** Add the new EH clauses
ehCount += 4;
newEHClauses[ehCount - 4] = beginMethodExClause;
newEHClauses[ehCount - 3] = endMethodExClause;
newEHClauses[ehCount - 2] = exClause;
newEHClauses[ehCount - 1] = finallyClause;
rewriter.SetEHClause(newEHClauses, ehCount);
if (IsDumpILRewriteEnabled())
{
Logger::Info(original_code);
Logger::Info(corProfiler->GetILCodes("*** Rewriter(): Modified Code: ", &rewriter, *caller,
module_metadata.metadata_import));
}
hr = rewriter.Export();
if (FAILED(hr))
{
Logger::Warn("*** CallTarget_RewriterCallback(): Call to ILRewriter.Export() failed for "
"ModuleID=",
module_id, " ", function_token);
return S_FALSE;
}
Logger::Info("*** CallTarget_RewriterCallback() Finished: ", caller->type.name, ".", caller->name, "() [IsVoid=",
isVoid, ", IsStatic=", isStatic, ", IntegrationType=", integration_definition->integration_type.name,
", Arguments=", numArgs, "]");
return S_OK;
}
} // namespace trace