You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Profiler中同一函数多次触发JIT编译的解决方法

解决.NET Profiler中同一函数重复触发JITCompilationStarted及重复插桩的问题

问题描述

我使用CORPROFILER API开发了自定义C++版.NET Profiler,通过插桩调用C#辅助程序集,并设置了如下事件掩码:

ProfilerInfo->SetEventMask(
COR_PRF_DISABLE_TRANSPARENCY_CHECKS_UNDER_FULL_TRUST | COR_PRF_MONITOR_EXCEPTIONS | COR_PRF_USE_PROFILE_IMAGES
| COR_PRF_MONITOR_JIT_COMPILATION | COR_PRF_MONITOR_MODULE_LOADS);

目前发现部分函数会反复触发JITCompilationStarted回调,导致Profiler崩溃,日志显示同一函数多次出现(如下),请问如何避免同一函数被重复插桩以及多次触发JIT编译?

日志信息

Wed May 15 12:58:07 2024 - [INFO1] System.Void Builder:EnsureCapacity(System.Int32) 2540 PID: 6020
Wed May 15 12:58:07 2024 - [INFO1] System.Void DisposableData:Release() 2540 PID: 6020
Wed May 15 12:58:07 2024 - [INFO1] System.Void Builder:EnsureCapacity(System.Int32) 6656 PID: 6020
Wed May 15 12:58:07 2024 - [INFO1] System.Void DisposableData:Release() 6656 PID: 6020
Wed May 15 12:58:07 2024 - [INFO1] System.Void Builder:EnsureCapacity(System.Int32) 2540 PID: 6020
Wed May 15 12:58:07 2024 - [INFO1] System.Void DisposableData:Release() 2540 PID: 6020
Wed May 15 12:58:07 2024 - [INFO1] System.Void Builder:EnsureCapacity(System.Int32) 6656 PID: 6020
Wed May 15 12:58:07 2024 - [INFO1] System.Void DisposableData:Release() 6656 PID: 6020
Wed May 15 12:58:07 2024 - [INFO1] System.Void Builder:EnsureCapacity(System.Int32) 2540 PID: 6020
Wed May 15 12:58:07 2024 - [INFO1] System.Void DisposableData:Release() 2540 PID: 6020
Wed May 15 12:58:07 2024 - [INFO1] System.Void Builder:EnsureCapacity(System.Int32) 6656 PID: 6020
Wed May 15 12:58:07 2024 - [INFO1] System.Void DisposableData:Release() 6656 PID: 6020
Wed May 15 12:58:07 2024 - [INFO1] System.Void Builder:EnsureCapacity(System.Int32) 2540 PID: 6020
Wed May 15 12:58:07 2024 - [INFO1] System.Void DisposableData:Release() 2540 PID: 6020

核心原因分析

同一函数反复触发JITCompilationStarted通常源于以下几点:

  • 插桩修改IL后未标记函数已处理,导致CLR重新编译时再次触发回调,形成循环
  • 多线程环境下未做线程安全的已插桩函数追踪,并发回调重复处理同一函数
  • 忽略isSafeToBlock参数,在不安全阻塞场景执行插桩,触发CLR重新调度JIT
  • COR_PRF_USE_PROFILE_IMAGES启用后,镜像处理逻辑非幂等,导致模块重复加载触发编译

具体解决方案

1. 实现线程安全的已插桩函数缓存

创建全局线程安全的哈希容器记录已完成插桩的FunctionID,每次回调先校验是否已处理:

#include <unordered_map>
#include <windows.h>

std::unordered_map<FunctionID, bool> g_instrumentedFunctions;
SRWLOCK g_instrumentedLock = SRWLOCK_INIT;

void __stdcall JITCompilationStarted(FunctionID functionId, BOOL isSafeToBlock)
{
    // 共享锁检查是否已插桩
    AcquireSRWLockShared(&g_instrumentedLock);
    bool alreadyInstrumented = (g_instrumentedFunctions.find(functionId) != g_instrumentedFunctions.end());
    ReleaseSRWLockShared(&g_instrumentedLock);

    if (alreadyInstrumented)
        return;

    // 执行插桩逻辑(确保逻辑幂等,不会触发重复编译)
    // ...

    // 独占锁标记已插桩
    AcquireSRWLockExclusive(&g_instrumentedLock);
    g_instrumentedFunctions[functionId] = true;
    ReleaseSRWLockExclusive(&g_instrumentedLock);
}

2. 严格处理isSafeToBlock参数

当isSafeToBlock为FALSE时,禁止执行任何可能阻塞的操作(如加载辅助集、复杂IL修改),直接跳过或延迟处理:

void __stdcall JITCompilationStarted(FunctionID functionId, BOOL isSafeToBlock)
{
    if (!isSafeToBlock)
    {
        // 不安全阻塞,跳过本次回调,避免触发CLR重新调度
        return;
    }

    // 执行安全的插桩逻辑
    // ...
}

3. 过滤无需插桩的高频函数

对Builder:EnsureCapacity这类基础高频函数直接过滤,减少不必要的插桩和编译触发:

void __stdcall JITCompilationStarted(FunctionID functionId, BOOL isSafeToBlock)
{
    WCHAR functionName[1024];
    ULONG nameLength = 1024;
    HRESULT hr = g_profilerInfo->GetFunctionInfo(functionId, nullptr, nullptr, functionName, &nameLength, nullptr, nullptr);
    
    if (SUCCEEDED(hr))
    {
        if (wcscmp(functionName, L"Builder:EnsureCapacity") == 0 || wcscmp(functionName, L"DisposableData:Release") == 0)
        {
            return;
        }
    }

    // 执行插桩逻辑
    // ...
}

4. 确保IL修改逻辑幂等

使用ICorProfilerInfo::SetILFunctionBody修改IL时,只执行一次,避免重复修改导致CLR重新编译。修改前可通过GetILFunctionBody检查是否已被修改。

5. 校验COR_PRF_USE_PROFILE_IMAGES的处理逻辑

启用该标志后,模块加载时的镜像修改逻辑必须是幂等的,确保同一模块不会被重复修改。可通过ModuleID维护已处理模块的缓存,避免重复处理。


内容的提问来源于stack exchange,提问作者Durai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 23:59:50