From f9ae98ab88f522219cd6be6fd282ef30adbc5365 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 26 Jul 2017 00:55:22 +0200 Subject: [PATCH] [profiler] Implement call context introspection for enter/leave events. When this feature is enabled for a method, the enter/leave event receives an additional argument, a so-called 'call context'. This call context contains enough information about the stack frame of the instrumented method to allow the enter/leave callback to inspect the 'this' reference, method arguments, local variables, and the return value (for non-void methods). This feature enables some interesting scenarios that were not possible with the regular enter/leave events. For example, a profiler could instrument well-known methods in the managed thread pool code to get an idea of how an application is using the thread pool, or it could instrument network-related methods to gather statistics or even log all network traffic. This is implemented by storing a MonoProfilerCallContext on the stack, whose MonoContext field is populated by executing an OP_FILL_PROF_CALL_CTX opcode which stores the stack pointer, frame pointer, and all callee-saved registers to it. For the epilogue, a pointer to the return value (for non-void methods) is also stored in the MonoProfilerCallContext. An address to this context is then passed to mono_profiler_raise_method_enter/leave. Based on debug info, all arguments and locals can then be looked up in the instrumented method's stack frame. For the interpreter, we just store an InterpFrame pointer on the MonoProfilerCallContext and look everything up from that. We don't need debug info in this case. This feature is currently not supported with LLVM (for regular LLVM mode, it will fall back to Mono's JIT, while for LLVM-only mode, it's not available). I also refactored the interpreter code so that enter/leave events are generated not only when interpreter debugging is enabled. Also, the interpreter will only call mono_profiler_get_call_instrumentation_flags () once per method now. Finally, I made the interpreter also generate exception leave events. --- CODEOWNERS | 1 + mono/metadata/profiler-events.h | 4 +- mono/metadata/profiler-private.h | 33 +++- mono/metadata/profiler.c | 66 ++++++-- mono/metadata/profiler.h | 118 +++++++++++++- mono/mini/Makefile.am.in | 3 +- mono/mini/cpu-amd64.md | 2 + mono/mini/cpu-arm.md | 2 + mono/mini/cpu-arm64.md | 2 + mono/mini/cpu-x86.md | 2 + mono/mini/debug-mini.c | 2 +- mono/mini/interp/interp-internals.h | 1 + mono/mini/interp/interp.c | 60 ++++++- mono/mini/interp/mintops.def | 6 + mono/mini/interp/transform.c | 3 + mono/mini/method-to-ir.c | 33 ++-- mono/mini/mini-amd64.c | 5 + mono/mini/mini-arm.c | 6 +- mono/mini/mini-arm64.c | 6 +- mono/mini/mini-ops.h | 7 + mono/mini/mini-profiler.c | 233 ++++++++++++++++++++++++++++ mono/mini/mini-runtime.c | 11 +- mono/mini/mini-x86.c | 7 + mono/mini/mini.c | 4 +- mono/mini/mini.h | 12 ++ mono/profiler/log.c | 4 +- msvc/libmono-static.vcxproj | 1 + msvc/libmono-static.vcxproj.filters | 3 + msvc/mono.def | 6 + msvc/monosgen.def | 6 + 30 files changed, 591 insertions(+), 58 deletions(-) create mode 100644 mono/mini/mini-profiler.c diff --git a/CODEOWNERS b/CODEOWNERS index d835c00f0b2..4a0ef8ae081 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -33,6 +33,7 @@ mono/metadata/profiler* @alexrp mono/metadata/threads* @luhenry @kumpera mono/metadata/threadpool* @luhenry mono/metadata/w32* @luhenry +mono/mini/profiler* @alexrp mono/profiler @alexrp mono/utils/atomic* @alexrp mono/utils/mono-hwcap* @alexrp diff --git a/mono/metadata/profiler-events.h b/mono/metadata/profiler-events.h index 7b85f00fb0c..1a7e0b25ef9 100644 --- a/mono/metadata/profiler-events.h +++ b/mono/metadata/profiler-events.h @@ -54,8 +54,8 @@ MONO_PROFILER_EVENT_1(assembly_loaded, AssemblyLLoaded, MonoAssembly *, assembly MONO_PROFILER_EVENT_1(assembly_unloading, AssemblyLUnloading, MonoAssembly *, assembly) MONO_PROFILER_EVENT_1(assembly_unloaded, AssemblyLUnloaded, MonoAssembly *, assembly) -MONO_PROFILER_EVENT_1(method_enter, MethodEnter, MonoMethod *, method) -MONO_PROFILER_EVENT_1(method_leave, MethodLeave, MonoMethod *, method) +MONO_PROFILER_EVENT_2(method_enter, MethodEnter, MonoMethod *, method, MonoProfilerCallContext *, context) +MONO_PROFILER_EVENT_2(method_leave, MethodLeave, MonoMethod *, method, MonoProfilerCallContext *, context) MONO_PROFILER_EVENT_2(method_exception_leave, MethodExceptionLeave, MonoMethod *, method, MonoObject *, exception) MONO_PROFILER_EVENT_1(method_free, MethodFree, MonoMethod *, method) MONO_PROFILER_EVENT_1(method_begin_invoke, MethodBeginInvoke, MonoMethod *, method) diff --git a/mono/metadata/profiler-private.h b/mono/metadata/profiler-private.h index e087be07eb6..38e353b5b35 100644 --- a/mono/metadata/profiler-private.h +++ b/mono/metadata/profiler-private.h @@ -9,6 +9,7 @@ #define MONO_PROFILER_UNSTABLE_GC_ROOTS #include +#include #include #include #include @@ -42,16 +43,28 @@ struct _MonoProfilerDesc { typedef struct { gboolean startup_done; + MonoProfilerHandle profilers; + mono_lazy_init_t coverage_status; mono_mutex_t coverage_mutex; GHashTable *coverage_hash; + MonoProfilerHandle sampling_owner; MonoSemType sampling_semaphore; MonoProfilerSampleMode sample_mode; guint32 sample_freq; + gboolean allocations; + gboolean call_contexts; + void (*context_enable) (void); + gpointer (*context_get_this) (MonoProfilerCallContext *); + gpointer (*context_get_argument) (MonoProfilerCallContext *, guint32); + gpointer (*context_get_local) (MonoProfilerCallContext *, guint32); + gpointer (*context_get_result) (MonoProfilerCallContext *); + gpointer (*context_free_buffer) (gpointer); + #define _MONO_PROFILER_EVENT(name) \ volatile gint32 name ## _count; #define MONO_PROFILER_EVENT_0(name, type) \ @@ -95,7 +108,25 @@ mono_profiler_installed (void) MonoProfilerCoverageInfo *mono_profiler_coverage_alloc (MonoMethod *method, guint32 entries); void mono_profiler_coverage_free (MonoMethod *method); -gboolean mono_profiler_should_instrument_method (MonoMethod *method, gboolean entry); +struct _MonoProfilerCallContext { + /* + * Must be the first field (the JIT relies on it). Only filled out if this + * is a JIT frame; otherwise, zeroed. + */ + MonoContext context; + /* + * A non-NULL MonoInterpFrameHandle if this is an interpreter frame. + */ + gpointer interp_frame; + MonoMethod *method; + /* + * Points to the return value for an epilogue context. For a prologue, this + * is set to NULL. + */ + gpointer return_value; +}; + +MonoProfilerCallInstrumentationFlags mono_profiler_get_call_instrumentation_flags (MonoMethod *method); gboolean mono_profiler_sampling_enabled (void); void mono_profiler_sampling_thread_post (void); diff --git a/mono/metadata/profiler.c b/mono/metadata/profiler.c index ccdb022a14f..36b864a60e1 100644 --- a/mono/metadata/profiler.c +++ b/mono/metadata/profiler.c @@ -361,9 +361,7 @@ mono_profiler_enable_allocations (void) if (mono_profiler_state.startup_done) return FALSE; - mono_profiler_state.allocations = TRUE; - - return TRUE; + return mono_profiler_state.allocations = TRUE; } void @@ -372,8 +370,61 @@ mono_profiler_set_call_instrumentation_filter_callback (MonoProfilerHandle handl InterlockedWritePointer (&handle->call_instrumentation_filter, (gpointer) cb); } -gboolean -mono_profiler_should_instrument_method (MonoMethod *method, gboolean entry) +mono_bool +mono_profiler_enable_call_context_introspection (void) +{ + if (mono_profiler_state.startup_done) + return FALSE; + + mono_profiler_state.context_enable (); + + return mono_profiler_state.call_contexts = TRUE; +} + +void * +mono_profiler_call_context_get_this (MonoProfilerCallContext *context) +{ + if (!mono_profiler_state.call_contexts) + return NULL; + + return mono_profiler_state.context_get_this (context); +} + +void * +mono_profiler_call_context_get_argument (MonoProfilerCallContext *context, uint32_t position) +{ + if (!mono_profiler_state.call_contexts) + return NULL; + + return mono_profiler_state.context_get_argument (context, position); +} + +void * +mono_profiler_call_context_get_local (MonoProfilerCallContext *context, uint32_t position) +{ + if (!mono_profiler_state.call_contexts) + return NULL; + + return mono_profiler_state.context_get_local (context, position); +} + +void * +mono_profiler_call_context_get_result (MonoProfilerCallContext *context) +{ + if (!mono_profiler_state.call_contexts) + return NULL; + + return mono_profiler_state.context_get_result (context); +} + +void +mono_profiler_call_context_free_buffer (void *buffer) +{ + mono_profiler_state.context_free_buffer (buffer); +} + +MonoProfilerCallInstrumentationFlags +mono_profiler_get_call_instrumentation_flags (MonoMethod *method) { MonoProfilerCallInstrumentationFlags flags = MONO_PROFILER_CALL_INSTRUMENTATION_NONE; @@ -384,10 +435,7 @@ mono_profiler_should_instrument_method (MonoMethod *method, gboolean entry) flags |= cb (handle->prof, method); } - if (entry) - return flags & MONO_PROFILER_CALL_INSTRUMENTATION_PROLOGUE; - else - return flags & MONO_PROFILER_CALL_INSTRUMENTATION_EPILOGUE; + return flags; } void diff --git a/mono/metadata/profiler.h b/mono/metadata/profiler.h index 0bbc0fe0061..08929a5d231 100644 --- a/mono/metadata/profiler.h +++ b/mono/metadata/profiler.h @@ -67,7 +67,7 @@ typedef mono_bool (*MonoProfilerCoverageFilterCallback) (MonoProfiler *prof, Mon * Sets a code coverage filter function. The profiler API will invoke filter * functions from all installed profilers. If any of them return TRUE, then the * given method will be instrumented for coverage analysis. All filters are - * guaranteed to be called exactly once per method, even if an earlier filter + * guaranteed to be called at least once per method, even if an earlier filter * has already returned TRUE. * * Note that filter functions must be installed before a method is compiled in @@ -170,8 +170,12 @@ typedef enum { MONO_PROFILER_CALL_INSTRUMENTATION_NONE = 1 << 0, /* Instrument method prologues. */ MONO_PROFILER_CALL_INSTRUMENTATION_PROLOGUE = 1 << 1, + /* Also capture a call context for prologues. */ + MONO_PROFILER_CALL_INSTRUMENTATION_PROLOGUE_CONTEXT = 1 << 2, /* Instrument method epilogues. */ - MONO_PROFILER_CALL_INSTRUMENTATION_EPILOGUE = 1 << 2, + MONO_PROFILER_CALL_INSTRUMENTATION_EPILOGUE = 1 << 3, + /* Also capture a call context for epilogues. */ + MONO_PROFILER_CALL_INSTRUMENTATION_EPILOGUE_CONTEXT = 1 << 4, } MonoProfilerCallInstrumentationFlags; typedef MonoProfilerCallInstrumentationFlags (*MonoProfilerCallInstrumentationFilterCallback) (MonoProfiler *prof, MonoMethod *method); @@ -181,21 +185,119 @@ typedef MonoProfilerCallInstrumentationFlags (*MonoProfilerCallInstrumentationFi * filter functions from all installed profilers. If any of them return flags * other than MONO_PROFILER_CALL_INSTRUMENTATION_NONE, then the given method * will be instrumented as requested. All filters are guaranteed to be called - * at least once (possibly more) per method entry and exit, even if earlier - * filters have already specified all flags. + * exactly once per method, even if earlier filters have already specified all + * flags. * * Note that filter functions must be installed before a method is compiled in * order to have any effect, i.e. you should register your filter function in - * your profiler's init function. + * your profiler's init function. Also, if you want to instrument a method + * that's going to be AOT-compiled, you must attach your profiler and install a + * call instrumentation filter function at AOT time. This can be done in + * exactly the same way as you would normally, i.e. by passing the --profile + * option on the command line, by calling mono_profiler_load, or simply by + * using the profiler API as an embedder. * - * Keep in mind that method instrumentation is extremely heavy and will slow - * down most applications to a crawl. Consider using sampling instead if it - * would work for your use case. + * Keep in mind that indiscriminate method instrumentation is extremely heavy + * and will slow down most applications to a crawl. Consider using sampling + * instead if it would work for your use case. * * This function is async safe. */ MONO_API void mono_profiler_set_call_instrumentation_filter_callback (MonoProfilerHandle handle, MonoProfilerCallInstrumentationFilterCallback cb); +/* + * Enables support for retrieving stack frame data from a call context. At the + * moment, this means enabling the debug info subsystem. If you do not call + * this function, you will not be able to use the call context introspection + * functions (they will simply return NULL). Returns TRUE if call context + * introspection was enabled, or FALSE if the function was called too late for + * this to be possible. + * + * Please note: Mono's LLVM backend does not support this feature. This means + * that methods with call context instrumentation will be handled by Mono's + * JIT even in LLVM mode. There is also a special case when Mono is compiling + * in LLVM-only mode: Since LLVM does not provide a way to implement call + * contexts, a NULL context will always be passed to enter/leave events even + * though this method returns TRUE. + * + * This function may only be called from your profiler's init function. + * + * This function is not async safe. + */ +MONO_API mono_bool mono_profiler_enable_call_context_introspection (void); + +typedef struct _MonoProfilerCallContext MonoProfilerCallContext; + +/* + * Given a valid call context from an enter/leave event, retrieves a pointer to + * the this reference for the method. Returns NULL if none exists (i.e. it's a + * static method) or if call context introspection was not enabled. + * + * The buffer returned by this function must be freed with + * mono_profiler_call_context_free_buffer. + * + * Please note that a call context is only valid for the duration of the + * enter/leave callback it was passed to. + * + * This function is not async safe. + */ +MONO_API void *mono_profiler_call_context_get_this (MonoProfilerCallContext *context); + +/* + * Given a valid call context from an enter/leave event, retrieves a pointer to + * the method argument at the given position. Returns NULL if position is out + * of bounds or if call context introspection was not enabled. + * + * The buffer returned by this function must be freed with + * mono_profiler_call_context_free_buffer. + * + * Please note that a call context is only valid for the duration of the + * enter/leave callback it was passed to. + * + * This function is not async safe. + */ +MONO_API void *mono_profiler_call_context_get_argument (MonoProfilerCallContext *context, uint32_t position); + +/* + * Given a valid call context from an enter/leave event, retrieves a pointer to + * the local variable at the given position. Returns NULL if position is out of + * bounds or if call context introspection was not enabled. + * + * The buffer returned by this function must be freed with + * mono_profiler_call_context_free_buffer. + * + * Please note that a call context is only valid for the duration of the + * enter/leave callback it was passed to. + * + * This function is not async safe. + */ +MONO_API void *mono_profiler_call_context_get_local (MonoProfilerCallContext *context, uint32_t position); + +/* + * Given a valid call context from an enter/leave event, retrieves a pointer to + * return value of a method. Returns NULL if the method has no return value + * (i.e. it returns void), if the leave event was the result of a tail call, if + * the function is called on a context from an enter event, or if call context + * introspection was not enabled. + * + * The buffer returned by this function must be freed with + * mono_profiler_call_context_free_buffer. + * + * Please note that a call context is only valid for the duration of the + * enter/leave callback it was passed to. + * + * This function is not async safe. + */ +MONO_API void *mono_profiler_call_context_get_result (MonoProfilerCallContext *context); + +/* + * Frees a buffer returned by one of the call context introspection functions. + * Passing a NULL buffer is allowed, which makes this function a no-op. + * + * This function is not async safe. + */ +MONO_API void mono_profiler_call_context_free_buffer (void *buffer); + #ifdef MONO_PROFILER_UNSTABLE_GC_ROOTS typedef enum { /* Upper 2 bytes. */ diff --git a/mono/mini/Makefile.am.in b/mono/mini/Makefile.am.in index 5e68273c645..848f75d3fe1 100755 --- a/mono/mini/Makefile.am.in +++ b/mono/mini/Makefile.am.in @@ -488,7 +488,8 @@ common_sources = \ type-checking.c \ lldb.h \ lldb.c \ - memory-access.c + memory-access.c \ + mini-profiler.c test_sources = \ basic-calls.cs \ diff --git a/mono/mini/cpu-amd64.md b/mono/mini/cpu-amd64.md index f1dfac557bc..d35a867778f 100755 --- a/mono/mini/cpu-amd64.md +++ b/mono/mini/cpu-amd64.md @@ -792,3 +792,5 @@ gc_param_slot_liveness_def: len:0 generic_class_init: src1:A len:32 clob:c get_last_error: dest:i len:32 + +fill_prof_call_ctx: src1:i len:128 diff --git a/mono/mini/cpu-arm.md b/mono/mini/cpu-arm.md index 25e5f931118..e6a91fc176d 100644 --- a/mono/mini/cpu-arm.md +++ b/mono/mini/cpu-arm.md @@ -404,3 +404,5 @@ atomic_store_r4: dest:b src1:f len:80 atomic_store_r8: dest:b src1:f len:32 generic_class_init: src1:a len:44 clob:c + +fill_prof_call_ctx: src1:i len:128 diff --git a/mono/mini/cpu-arm64.md b/mono/mini/cpu-arm64.md index c0651233eb5..230a975dbe3 100644 --- a/mono/mini/cpu-arm64.md +++ b/mono/mini/cpu-arm64.md @@ -480,3 +480,5 @@ atomic_store_r8: dest:b src1:f len:24 generic_class_init: src1:a len:44 clob:c gc_safe_point: src1:i len:12 clob:c + +fill_prof_call_ctx: src1:i len:128 diff --git a/mono/mini/cpu-x86.md b/mono/mini/cpu-x86.md index 280eda7c0ac..870b10b2397 100644 --- a/mono/mini/cpu-x86.md +++ b/mono/mini/cpu-x86.md @@ -651,3 +651,5 @@ gc_spill_slot_liveness_def: len:0 gc_param_slot_liveness_def: len:0 get_sp: dest:i len:6 set_sp: src1:i len:6 + +fill_prof_call_ctx: src1:i len:128 diff --git a/mono/mini/debug-mini.c b/mono/mini/debug-mini.c index 8c1a71e147f..30874eb3cf3 100644 --- a/mono/mini/debug-mini.c +++ b/mono/mini/debug-mini.c @@ -241,7 +241,7 @@ mono_debug_close_method (MonoCompile *cfg) jit->code_start = cfg->native_code; jit->epilogue_begin = cfg->epilog_begin; jit->code_size = cfg->code_len; - jit->has_var_info = debug_options.mdb_optimizations != 0; + jit->has_var_info = debug_options.mdb_optimizations || MONO_CFG_PROFILE_CALL_CONTEXT (cfg); if (jit->epilogue_begin) record_line_number (info, jit->epilogue_begin, header->code_size); diff --git a/mono/mini/interp/interp-internals.h b/mono/mini/interp/interp-internals.h index e566f535a6e..894747e23d0 100644 --- a/mono/mini/interp/interp-internals.h +++ b/mono/mini/interp/interp-internals.h @@ -96,6 +96,7 @@ typedef struct _InterpMethod MonoType **param_types; MonoJitInfo *jinfo; MonoDomain *domain; + MonoProfilerCallInstrumentationFlags prof_flags; } InterpMethod; struct _InterpFrame { diff --git a/mono/mini/interp/interp.c b/mono/mini/interp/interp.c index e5d50d5aa5e..fdbbe262ba4 100644 --- a/mono/mini/interp/interp.c +++ b/mono/mini/interp/interp.c @@ -173,8 +173,6 @@ debug_enter (InterpFrame *frame, int *tracing) g_print ("%s)\n", args); g_free (args); } - if (mono_profiler_should_instrument_method (frame->imethod->method, TRUE)) - MONO_PROFILER_RAISE (method_enter, (frame->imethod->method)); } @@ -190,9 +188,7 @@ debug_enter (InterpFrame *frame, int *tracing) g_free (args); \ debug_indent_level--; \ if (tracing == 3) global_tracing = 0; \ - } \ - if (mono_profiler_should_instrument_method (frame->imethod->method, FALSE)) \ - MONO_PROFILER_RAISE (method_leave, (frame->imethod->method)); + } #else @@ -297,6 +293,8 @@ mono_interp_get_imethod (MonoDomain *domain, MonoMethod *method, MonoError *erro mono_internal_hash_table_insert (&info->interp_code_hash, method, rtm); mono_domain_jit_code_hash_unlock (domain); + rtm->prof_flags = mono_profiler_get_call_instrumentation_flags (rtm->method); + return rtm; } @@ -603,6 +601,7 @@ fill_in_trace (MonoException *exception, InterpFrame *frame) if (!rethrow) { \ FILL_IN_TRACE(frame->ex, frame); \ } \ + MONO_PROFILER_RAISE (exception_throw, ((MonoObject *) exception)); \ goto handle_exception; \ } while (0) @@ -4764,6 +4763,26 @@ array_constructed: MINT_IN_BREAK; } + MINT_IN_CASE(MINT_PROF_ENTER) { + ip += 1; + + if (MONO_PROFILER_ENABLED (method_enter)) { + MonoProfilerCallContext *prof_ctx = NULL; + + if (frame->imethod->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_PROLOGUE_CONTEXT) { + prof_ctx = g_new0 (MonoProfilerCallContext, 1); + prof_ctx->interp_frame = frame; + prof_ctx->method = frame->imethod->method; + } + + MONO_PROFILER_RAISE (method_enter, (frame->imethod->method, prof_ctx)); + + g_free (prof_ctx); + } + + MINT_IN_BREAK; + } + MINT_IN_CASE(MINT_LDARGA) sp->data.p = frame->args + * (guint16 *)(ip + 1); ip += 2; @@ -5120,6 +5139,37 @@ die_on_ex: goto exit_frame; } exit_frame: + + if (!frame->ex) { + if (MONO_PROFILER_ENABLED (method_leave) && frame->imethod->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_EPILOGUE) { + MonoProfilerCallContext *prof_ctx = NULL; + + if (frame->imethod->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_EPILOGUE_CONTEXT) { + prof_ctx = g_new0 (MonoProfilerCallContext, 1); + prof_ctx->interp_frame = frame; + prof_ctx->method = frame->imethod->method; + + MonoType *rtype = mono_method_signature (frame->imethod->method)->ret; + + switch (rtype->type) { + case MONO_TYPE_VOID: + break; + case MONO_TYPE_VALUETYPE: + prof_ctx->return_value = frame->retval->data.p; + break; + default: + prof_ctx->return_value = frame->retval; + break; + } + } + + MONO_PROFILER_RAISE (method_leave, (frame->imethod->method, prof_ctx)); + + g_free (prof_ctx); + } + } else + MONO_PROFILER_RAISE (method_exception_leave, (frame->imethod->method, (MonoObject *) frame->ex)); + DEBUG_LEAVE (); } diff --git a/mono/mini/interp/mintops.def b/mono/mini/interp/mintops.def index c270a2b18e4..869c5f2df74 100644 --- a/mono/mini/interp/mintops.def +++ b/mono/mini/interp/mintops.def @@ -526,3 +526,9 @@ OPDEF(MINT_JIT_CALL, "mono_jit_call", 2, MintOpNoArgs) OPDEF(MINT_SDB_INTR_LOC, "sdb_intr_loc", 1, MintOpNoArgs) OPDEF(MINT_SDB_SEQ_POINT, "sdb_seq_point", 1, MintOpNoArgs) OPDEF(MINT_SDB_BREAKPOINT, "sdb_breakpoint", 1, MintOpNoArgs) + +/* + * This needs to be an opcode because we need to trigger the enter event after + * the STINARG* opcodes have executed. + */ +OPDEF(MINT_PROF_ENTER, "prof_enter", 1, MintOpNoArgs) diff --git a/mono/mini/interp/transform.c b/mono/mini/interp/transform.c index 1d98608c61c..790f8b191d2 100644 --- a/mono/mini/interp/transform.c +++ b/mono/mini/interp/transform.c @@ -1514,6 +1514,9 @@ generate (MonoMethod *method, InterpMethod *rtm, unsigned char *is_bb_start, Mon } } + if (rtm->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_PROLOGUE) + ADD_CODE (td, MINT_PROF_ENTER); + if (sym_seq_points) { InterpBasicBlock *cbb = td->offset_to_bb [0]; g_assert (cbb); diff --git a/mono/mini/method-to-ir.c b/mono/mini/method-to-ir.c index a74291148e8..84c01946051 100644 --- a/mono/mini/method-to-ir.c +++ b/mono/mini/method-to-ir.c @@ -1764,24 +1764,6 @@ emit_pop_lmf (MonoCompile *cfg) EMIT_NEW_STORE_MEMBASE (cfg, ins, OP_STORE_MEMBASE_REG, lmf_addr_reg, 0, prev_lmf_reg); } -static void -emit_instrumentation_call (MonoCompile *cfg, void *func, gboolean entry) -{ - MonoInst *iargs [1]; - - /* - * Avoid instrumenting inlined methods since it can - * distort profiling results. - */ - if (cfg->method != cfg->current_method) - return; - - if (mono_profiler_should_instrument_method (cfg->method, entry)) { - EMIT_NEW_METHODCONST (cfg, iargs [0], cfg->method); - mono_emit_jit_icall (cfg, func, iargs); - } -} - static int ret_type_to_call_opcode (MonoCompile *cfg, MonoType *type, int calli, int virt) { @@ -2247,7 +2229,7 @@ mono_emit_call_args (MonoCompile *cfg, MonoMethodSignature *sig, tail = FALSE; if (tail) { - emit_instrumentation_call (cfg, mono_profiler_raise_method_leave, FALSE); + mini_profiler_emit_instrumentation_call (cfg, mono_profiler_raise_method_leave, FALSE, NULL, NULL); MONO_INST_NEW_CALL (cfg, call, OP_TAILCALL); } else @@ -4362,6 +4344,9 @@ mono_method_check_inlining (MonoCompile *cfg, MonoMethod *method) if (g_list_find (cfg->dont_inline, method)) return FALSE; + if (mono_profiler_get_call_instrumentation_flags (method)) + return FALSE; + return TRUE; } @@ -8045,7 +8030,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b if (cfg->gshared && mono_method_check_context_used (cmethod)) GENERIC_SHARING_FAILURE (CEE_JMP); - emit_instrumentation_call (cfg, mono_profiler_raise_method_leave, FALSE); + mini_profiler_emit_instrumentation_call (cfg, mono_profiler_raise_method_leave, FALSE, NULL, NULL); fsig = mono_method_signature (cmethod); n = fsig->param_count + fsig->hasthis; @@ -8987,7 +8972,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b /* Handle tail calls similarly to normal calls */ tail_call = TRUE; } else { - emit_instrumentation_call (cfg, mono_profiler_raise_method_leave, FALSE); + mini_profiler_emit_instrumentation_call (cfg, mono_profiler_raise_method_leave, FALSE, NULL, NULL); MONO_INST_NEW_CALL (cfg, call, OP_JMP); call->tail_call = TRUE; @@ -9098,6 +9083,8 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b break; } case CEE_RET: + mini_profiler_emit_instrumentation_call (cfg, mono_profiler_raise_method_leave, FALSE, sp - 1, sig->ret); + if (cfg->method != method) { /* return from inlined method */ /* @@ -9121,8 +9108,6 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b cfg->ret_var_set = TRUE; } } else { - emit_instrumentation_call (cfg, mono_profiler_raise_method_leave, FALSE); - if (cfg->lmf_var && cfg->cbb->in_count && !cfg->llvm_only) emit_pop_lmf (cfg); @@ -12650,7 +12635,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b } cfg->cbb = init_localsbb; - emit_instrumentation_call (cfg, mono_profiler_raise_method_enter, TRUE); + mini_profiler_emit_instrumentation_call (cfg, mono_profiler_raise_method_enter, TRUE, NULL, NULL); if (seq_points) { MonoBasicBlock *bb; diff --git a/mono/mini/mini-amd64.c b/mono/mini/mini-amd64.c index 5a376b89450..d544ee34445 100644 --- a/mono/mini/mini-amd64.c +++ b/mono/mini/mini-amd64.c @@ -6419,6 +6419,11 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) case OP_GET_LAST_ERROR: emit_get_last_error(code, ins->dreg); break; + case OP_FILL_PROF_CALL_CTX: + for (int i = 0; i < AMD64_NREG; i++) + if (AMD64_IS_CALLEE_SAVED_REG (i) || i == AMD64_RSP) + amd64_mov_membase_reg (code, ins->sreg1, MONO_STRUCT_OFFSET (MonoContext, gregs) + i * sizeof (mgreg_t), i, sizeof (mgreg_t)); + break; default: g_warning ("unknown opcode %s in %s()\n", mono_inst_name (ins->opcode), __FUNCTION__); g_assert_not_reached (); diff --git a/mono/mini/mini-arm.c b/mono/mini/mini-arm.c index e61e7b00c71..a304dbef080 100644 --- a/mono/mini/mini-arm.c +++ b/mono/mini/mini-arm.c @@ -5890,7 +5890,11 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) arm_patch (buf [0], code); break; } - + case OP_FILL_PROF_CALL_CTX: + for (int i = 0; i < ARMREG_MAX; i++) + if ((MONO_ARCH_CALLEE_SAVED_REGS & (1 << i)) || i == ARMREG_SP || i == ARMREG_FP) + ARM_STR_IMM (code, i, ins->sreg1, MONO_STRUCT_OFFSET (MonoContext, regs) + i * sizeof (mgreg_t)); + break; default: g_warning ("unknown opcode %s in %s()\n", mono_inst_name (ins->opcode), __FUNCTION__); g_assert_not_reached (); diff --git a/mono/mini/mini-arm64.c b/mono/mini/mini-arm64.c index aacdd599afc..46fb083b282 100644 --- a/mono/mini/mini-arm64.c +++ b/mono/mini/mini-arm64.c @@ -4329,7 +4329,11 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) #endif break; } - + case OP_FILL_PROF_CALL_CTX: + for (int i = 0; i < MONO_MAX_IREGS; i++) + if ((MONO_ARCH_CALLEE_SAVED_REGS & (1 << i)) || i == ARMREG_SP || i == ARMREG_FP) + arm_strx (code, i, ins->sreg1, MONO_STRUCT_OFFSET (MonoContext, regs) + i * sizeof (mgreg_t)); + break; default: g_warning ("unknown opcode %s in %s()\n", mono_inst_name (ins->opcode), __FUNCTION__); g_assert_not_reached (); diff --git a/mono/mini/mini-ops.h b/mono/mini/mini-ops.h index ae9165e1174..29d8cb09fb7 100644 --- a/mono/mini/mini-ops.h +++ b/mono/mini/mini-ops.h @@ -1305,3 +1305,10 @@ MINI_OP(OP_GET_SP, "get_sp", IREG, NONE, NONE) MINI_OP(OP_SET_SP, "set_sp", NONE, IREG, NONE) MINI_OP(OP_GET_LAST_ERROR, "get_last_error", IREG, NONE, NONE) + +/* + * Fill out a MonoContext contained in a MonoProfilerCallContext. This only + * stores the stack pointer, frame pointer, and callee-saved registers. This + * should be enough to locate arguments and variables. + */ +MINI_OP(OP_FILL_PROF_CALL_CTX, "fill_prof_call_ctx", NONE, IREG, NONE) diff --git a/mono/mini/mini-profiler.c b/mono/mini/mini-profiler.c new file mode 100644 index 00000000000..3b6149d9272 --- /dev/null +++ b/mono/mini/mini-profiler.c @@ -0,0 +1,233 @@ +/* + * Licensed to the .NET Foundation under one or more agreements. + * The .NET Foundation licenses this file to you under the MIT license. + * See the LICENSE file in the project root for more information. + */ + +#include +#include + +#include "interp/interp.h" +#include "ir-emit.h" +#include "mini.h" + +void +mini_profiler_emit_instrumentation_call (MonoCompile *cfg, void *func, gboolean entry, MonoInst **ret, MonoType *rtype) +{ + gboolean instrument, capture; + + if (entry) { + instrument = cfg->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_PROLOGUE; + capture = cfg->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_PROLOGUE_CONTEXT; + } else { + instrument = cfg->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_EPILOGUE; + capture = cfg->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_EPILOGUE_CONTEXT; + } + + if (!instrument) + return; + + g_assert (cfg->current_method == cfg->method); + + MonoInst *iargs [2]; + + EMIT_NEW_METHODCONST (cfg, iargs [0], cfg->method); + + if (capture && !cfg->llvm_only) { + cfg->flags |= MONO_CFG_HAS_ALLOCA; + + MonoInst *size, *fill_ctx; + + EMIT_NEW_ICONST (cfg, size, sizeof (MonoProfilerCallContext)); + MONO_INST_NEW (cfg, iargs [1], OP_LOCALLOC); + iargs [1]->dreg = alloc_preg (cfg); + iargs [1]->sreg1 = size->dreg; + iargs [1]->flags |= MONO_INST_INIT; + MONO_ADD_INS (cfg->cbb, iargs [1]); + MONO_INST_NEW (cfg, fill_ctx, OP_FILL_PROF_CALL_CTX); + fill_ctx->sreg1 = iargs [1]->dreg; + MONO_ADD_INS (cfg->cbb, fill_ctx); + MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STORE_MEMBASE_REG, iargs [1]->dreg, MONO_STRUCT_OFFSET (MonoProfilerCallContext, method), iargs [0]->dreg); + + if (rtype && rtype->type != MONO_TYPE_VOID) { + MonoInst *var = mono_compile_create_var (cfg, rtype, OP_LOCAL); + + MonoInst *store, *addr; + + EMIT_NEW_TEMPSTORE (cfg, store, var->inst_c0, *ret); + EMIT_NEW_VARLOADA (cfg, addr, var, NULL); + MONO_EMIT_NEW_STORE_MEMBASE (cfg, OP_STORE_MEMBASE_REG, iargs [1]->dreg, MONO_STRUCT_OFFSET (MonoProfilerCallContext, return_value), addr->dreg); + } + } else + EMIT_NEW_PCONST (cfg, iargs [1], NULL); + + mono_emit_jit_icall (cfg, func, iargs); +} + +void +mini_profiler_context_enable (void) +{ + if (!mono_debug_enabled ()) + mono_debug_init (MONO_DEBUG_FORMAT_MONO); +} + +static gpointer +memdup_with_type (gpointer data, MonoType *t) +{ + int dummy; + + return g_memdup (data, mono_type_size (t, &dummy)); +} + +static guint8 * +get_int_reg (MonoContext *ctx, guint32 reg) +{ + return (guint8 *) mono_arch_context_get_int_reg (ctx, reg); +} + +static gpointer +get_variable_buffer (MonoDebugMethodJitInfo *jit, MonoDebugVarInfo *var, MonoContext *ctx) +{ + guint32 flags = var->index & MONO_DEBUG_VAR_ADDRESS_MODE_FLAGS; + guint32 reg = var->index & ~MONO_DEBUG_VAR_ADDRESS_MODE_FLAGS; + + switch (flags) { + case MONO_DEBUG_VAR_ADDRESS_MODE_REGISTER: { + /* + * This is kind of a special case: All other address modes ultimately + * produce an address to where the actual value is located, but this + * address mode gets us the value itself as an mgreg_t value. + */ + mgreg_t value = (mgreg_t) get_int_reg (ctx, reg); + + return memdup_with_type (&value, var->type); + } + case MONO_DEBUG_VAR_ADDRESS_MODE_REGOFFSET: + return memdup_with_type (get_int_reg (ctx, reg) + (gint32) var->offset, var->type); + case MONO_DEBUG_VAR_ADDRESS_MODE_REGOFFSET_INDIR: + case MONO_DEBUG_VAR_ADDRESS_MODE_VTADDR: + return memdup_with_type (*(guint8 **) (get_int_reg (ctx, reg) + (gint32) var->offset), var->type); + case MONO_DEBUG_VAR_ADDRESS_MODE_GSHAREDVT_LOCAL: { + guint32 idx = reg; + + MonoDebugVarInfo *info_var = jit->gsharedvt_info_var; + + flags = info_var->index & MONO_DEBUG_VAR_ADDRESS_MODE_FLAGS; + reg = info_var->index & ~MONO_DEBUG_VAR_ADDRESS_MODE_FLAGS; + + MonoGSharedVtMethodRuntimeInfo *info; + + switch (flags) { + case MONO_DEBUG_VAR_ADDRESS_MODE_REGISTER: + info = (MonoGSharedVtMethodRuntimeInfo *) get_int_reg (ctx, reg); + break; + case MONO_DEBUG_VAR_ADDRESS_MODE_REGOFFSET: + info = *(MonoGSharedVtMethodRuntimeInfo **) (get_int_reg (ctx, reg) + (gint32) info_var->offset); + break; + default: + g_assert_not_reached (); + } + + MonoDebugVarInfo *locals_var = jit->gsharedvt_locals_var; + + flags = locals_var->index & MONO_DEBUG_VAR_ADDRESS_MODE_FLAGS; + reg = locals_var->index & ~MONO_DEBUG_VAR_ADDRESS_MODE_FLAGS; + + guint8 *locals; + + switch (flags) { + case MONO_DEBUG_VAR_ADDRESS_MODE_REGISTER: + locals = get_int_reg (ctx, reg); + break; + case MONO_DEBUG_VAR_ADDRESS_MODE_REGOFFSET: + locals = *(guint8 **) (get_int_reg (ctx, reg) + (gint32) info_var->offset); + break; + default: + g_assert_not_reached (); + } + + return memdup_with_type (locals + (gsize) info->entries [idx], var->type); + } + default: + g_assert_not_reached (); + return NULL; + } +} + +gpointer +mini_profiler_context_get_this (MonoProfilerCallContext *ctx) +{ + if (!mono_method_signature (ctx->method)->hasthis) + return NULL; + + if (ctx->interp_frame) + return memdup_with_type (mono_interp_frame_get_this (ctx->interp_frame), &ctx->method->klass->this_arg); + + MonoDebugMethodJitInfo *info = mono_debug_find_method (ctx->method, mono_domain_get ()); + + if (!info) + return NULL; + + return get_variable_buffer (info, info->this_var, &ctx->context); +} + +gpointer +mini_profiler_context_get_argument (MonoProfilerCallContext *ctx, guint32 pos) +{ + MonoMethodSignature *sig = mono_method_signature (ctx->method); + + if (pos >= sig->param_count) + return NULL; + + if (ctx->interp_frame) + return memdup_with_type (mono_interp_frame_get_arg (ctx->interp_frame, pos), sig->params [pos]); + + MonoDebugMethodJitInfo *info = mono_debug_find_method (ctx->method, mono_domain_get ()); + + if (!info) + return NULL; + + return get_variable_buffer (info, &info->params [pos], &ctx->context); +} + +gpointer +mini_profiler_context_get_local (MonoProfilerCallContext *ctx, guint32 pos) +{ + MonoError error; + MonoMethodHeader *header = mono_method_get_header_checked (ctx->method, &error); + mono_error_assert_ok (&error); // Must be a valid method at this point. + + if (pos >= header->num_locals) { + mono_metadata_free_mh (header); + return NULL; + } + + MonoType *t = header->locals [pos]; + + mono_metadata_free_mh (header); + + if (ctx->interp_frame) + return memdup_with_type (mono_interp_frame_get_local (ctx->interp_frame, pos), t); + + MonoDebugMethodJitInfo *info = mono_debug_find_method (ctx->method, mono_domain_get ()); + + if (!info) + return NULL; + + return get_variable_buffer (info, &info->locals [pos], &ctx->context); +} + +gpointer +mini_profiler_context_get_result (MonoProfilerCallContext *ctx) +{ + if (!ctx->return_value) + return NULL; + + return memdup_with_type (ctx->return_value, mono_method_signature (ctx->method)->ret); +} + +void +mini_profiler_context_free_buffer (void *buffer) +{ + g_free (buffer); +} diff --git a/mono/mini/mini-runtime.c b/mono/mini/mini-runtime.c index 7c22cc94157..af88efa4abd 100644 --- a/mono/mini/mini-runtime.c +++ b/mono/mini/mini-runtime.c @@ -3938,6 +3938,13 @@ mini_init (const char *filename, const char *runtime_version) mono_install_get_class_from_name (mono_aot_get_class_from_name); mono_install_jit_info_find_in_aot (mono_aot_find_jit_info); + mono_profiler_state.context_enable = mini_profiler_context_enable; + mono_profiler_state.context_get_this = mini_profiler_context_get_this; + mono_profiler_state.context_get_argument = mini_profiler_context_get_argument; + mono_profiler_state.context_get_local = mini_profiler_context_get_local; + mono_profiler_state.context_get_result = mini_profiler_context_get_result; + mono_profiler_state.context_free_buffer = mini_profiler_context_free_buffer; + if (profile_options) for (guint i = 0; i < profile_options->len; i++) mono_profiler_load ((const char *) g_ptr_array_index (profile_options, i)); @@ -4056,8 +4063,8 @@ register_icalls (void) * the wrapper would call the icall which would call the wrapper and * so on. */ - register_icall (mono_profiler_raise_method_enter, "mono_profiler_raise_method_enter", "void ptr", TRUE); - register_icall (mono_profiler_raise_method_leave, "mono_profiler_raise_method_leave", "void ptr", TRUE); + register_icall (mono_profiler_raise_method_enter, "mono_profiler_raise_method_enter", "void ptr ptr", TRUE); + register_icall (mono_profiler_raise_method_leave, "mono_profiler_raise_method_leave", "void ptr ptr", TRUE); register_icall (mono_trace_enter_method, "mono_trace_enter_method", NULL, TRUE); register_icall (mono_trace_leave_method, "mono_trace_leave_method", NULL, TRUE); diff --git a/mono/mini/mini-x86.c b/mono/mini/mini-x86.c index 54966b1dc8f..a3e893a1e2b 100644 --- a/mono/mini/mini-x86.c +++ b/mono/mini/mini-x86.c @@ -4927,6 +4927,13 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) case OP_SET_SP: x86_mov_reg_reg (code, X86_ESP, ins->sreg1, sizeof (mgreg_t)); break; + case OP_FILL_PROF_CALL_CTX: + x86_mov_membase_reg (code, ins->sreg1, MONO_STRUCT_OFFSET (MonoContext, esp), X86_ESP, sizeof (mgreg_t)); + x86_mov_membase_reg (code, ins->sreg1, MONO_STRUCT_OFFSET (MonoContext, ebp), X86_EBP, sizeof (mgreg_t)); + x86_mov_membase_reg (code, ins->sreg1, MONO_STRUCT_OFFSET (MonoContext, ebx), X86_EBX, sizeof (mgreg_t)); + x86_mov_membase_reg (code, ins->sreg1, MONO_STRUCT_OFFSET (MonoContext, esi), X86_ESI, sizeof (mgreg_t)); + x86_mov_membase_reg (code, ins->sreg1, MONO_STRUCT_OFFSET (MonoContext, edi), X86_EDI, sizeof (mgreg_t)); + break; default: g_warning ("unknown opcode %s\n", mono_inst_name (ins->opcode)); g_assert_not_reached (); diff --git a/mono/mini/mini.c b/mono/mini/mini.c index 5374f9a3653..5e6e718486f 100644 --- a/mono/mini/mini.c +++ b/mono/mini/mini.c @@ -3294,8 +3294,10 @@ mini_method_compile (MonoMethod *method, guint32 opts, MonoDomain *domain, JitFl } #endif + cfg->prof_flags = mono_profiler_get_call_instrumentation_flags (cfg->method); + /* The debugger has no liveness information, so avoid sharing registers/stack slots */ - if (debug_options.mdb_optimizations) { + if (debug_options.mdb_optimizations || MONO_CFG_PROFILE_CALL_CONTEXT (cfg)) { cfg->disable_reuse_registers = TRUE; cfg->disable_reuse_stack_slots = TRUE; /* diff --git a/mono/mini/mini.h b/mono/mini/mini.h index a8bd6f70221..7d681122d43 100644 --- a/mono/mini/mini.h +++ b/mono/mini/mini.h @@ -1916,8 +1916,13 @@ typedef struct { int stat_inlineable_methods; int stat_inlined_methods; int stat_code_reallocs; + + MonoProfilerCallInstrumentationFlags prof_flags; } MonoCompile; +#define MONO_CFG_PROFILE_CALL_CONTEXT(cfg) \ + ((cfg)->prof_flags & (MONO_PROFILER_CALL_INSTRUMENTATION_PROLOGUE_CONTEXT | MONO_PROFILER_CALL_INSTRUMENTATION_EPILOGUE_CONTEXT)) + typedef enum { MONO_CFG_HAS_ALLOCA = 1 << 0, MONO_CFG_HAS_CALLS = 1 << 1, @@ -2353,6 +2358,13 @@ void mini_cleanup (MonoDomain *domain); MONO_API MonoDebugOptions *mini_get_debug_options (void); MONO_API gboolean mini_parse_debug_option (const char *option); void mini_add_profiler_argument (const char *desc); +void mini_profiler_emit_instrumentation_call (MonoCompile *cfg, void *func, gboolean entry, MonoInst **ret, MonoType *rtype); +void mini_profiler_context_enable (void); +gpointer mini_profiler_context_get_this (MonoProfilerCallContext *ctx); +gpointer mini_profiler_context_get_argument (MonoProfilerCallContext *ctx, guint32 pos); +gpointer mini_profiler_context_get_local (MonoProfilerCallContext *ctx, guint32 pos); +gpointer mini_profiler_context_get_result (MonoProfilerCallContext *ctx); +void mini_profiler_context_free_buffer (gpointer buffer); /* graph dumping */ void mono_cfg_dump_create_context (MonoCompile *cfg); diff --git a/mono/profiler/log.c b/mono/profiler/log.c index ed7a2122659..90f73098847 100644 --- a/mono/profiler/log.c +++ b/mono/profiler/log.c @@ -1760,7 +1760,7 @@ class_loaded (MonoProfiler *prof, MonoClass *klass) } static void -method_enter (MonoProfiler *prof, MonoMethod *method) +method_enter (MonoProfiler *prof, MonoMethod *method, MonoProfilerCallContext *ctx) { if (get_thread ()->call_depth++ <= log_config.max_call_depth) { ENTER_LOG (&method_entries_ctr, logbuffer, @@ -1776,7 +1776,7 @@ method_enter (MonoProfiler *prof, MonoMethod *method) } static void -method_leave (MonoProfiler *prof, MonoMethod *method) +method_leave (MonoProfiler *prof, MonoMethod *method, MonoProfilerCallContext *ctx) { if (--get_thread ()->call_depth <= log_config.max_call_depth) { ENTER_LOG (&method_exits_ctr, logbuffer, diff --git a/msvc/libmono-static.vcxproj b/msvc/libmono-static.vcxproj index 70dc4b0554c..b23daec2505 100644 --- a/msvc/libmono-static.vcxproj +++ b/msvc/libmono-static.vcxproj @@ -138,6 +138,7 @@ + {CB0D9E92-293C-439C-9AC7-C5F59B6E0772} diff --git a/msvc/libmono-static.vcxproj.filters b/msvc/libmono-static.vcxproj.filters index 029b9d162c1..672e6052f41 100644 --- a/msvc/libmono-static.vcxproj.filters +++ b/msvc/libmono-static.vcxproj.filters @@ -160,6 +160,9 @@ Source Files + + Source Files + diff --git a/msvc/mono.def b/msvc/mono.def index 884457efa69..acf9e59d1b4 100644 --- a/msvc/mono.def +++ b/msvc/mono.def @@ -704,8 +704,14 @@ mono_print_method_from_ip mono_print_thread_dump mono_print_thread_dump_from_ctx mono_print_unhandled_exception +mono_profiler_call_context_free_buffer +mono_profiler_call_context_get_argument +mono_profiler_call_context_get_local +mono_profiler_call_context_get_result +mono_profiler_call_context_get_this mono_profiler_create mono_profiler_enable_allocations +mono_profiler_enable_call_context_introspection mono_profiler_enable_sampling mono_profiler_get_coverage_data mono_profiler_get_sample_mode diff --git a/msvc/monosgen.def b/msvc/monosgen.def index 64c8c16f7b3..513e2eda72e 100644 --- a/msvc/monosgen.def +++ b/msvc/monosgen.def @@ -706,8 +706,14 @@ mono_print_method_from_ip mono_print_thread_dump mono_print_thread_dump_from_ctx mono_print_unhandled_exception +mono_profiler_call_context_free_buffer +mono_profiler_call_context_get_argument +mono_profiler_call_context_get_local +mono_profiler_call_context_get_result +mono_profiler_call_context_get_this mono_profiler_create mono_profiler_enable_allocations +mono_profiler_enable_call_context_introspection mono_profiler_enable_sampling mono_profiler_get_coverage_data mono_profiler_get_sample_mode -- 2.25.1