上一篇我们讲到了注解特性,harmony 在内部提供了 20个HarmonyPatch重载方法尽可能的让大家满足业务开发,那时候我也说了,特性虽然简单粗暴,但只能解决 95% 的问题,言外之意还有一些事情做不到,所以剩下的5%只能靠完全手工的方式了。摘要:internal sealed class ServiceProviderEngineScope : IServiceScope, IDisposable, IServiceProvider, IKeyedServiceProvider, IAsyncDisp
虽然有20个重载方法,但还不能达到100%覆盖,不要以为我说的这种情况比较罕见,是很正常的场景,比如说:
嵌套类。
程序集中的某些特殊不对外公开类。
这里我就拿第二种来说把,参考代码如下:
internal sealed class ServiceProviderEngineScope : IServiceScope, IDisposable, IServiceProvider, IKeyedServiceProvider, IAsyncDisposable, IServiceScopeFactory
{
public ServiceProviderEngineScope(ServiceProvider provider, bool isRootScope)
{
ResolvedServices = new Dictionary;
RootProvider = provider;
IsRootScope = isRootScope;
}
}
这段代码有几个要素:
代码是程序集可访问,所以你不能使用任何typeof(xxx)形式的构造函数,否则就会报错,参考如下:[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Delegate, AllowMultiple = true)]
public class HarmonyPatch : HarmonyAttribute
{
...
public HarmonyPatch(string typeName, string methodName, MethodType methodType = MethodType.Normal);
...
}
所以这个就是很无语的事情了,哈哈,上面所说的其实就是我最近遇到了一例.NET托管内存暴涨问题,观察托管堆之后,发现有 975w 的 ServiceProviderEngineScope 类,截图如下:熟悉这个类的朋友应该明白,这是上层调用serviceProvider.CreateScope方法没有释放导致的,那接下来的问题是到底谁在不断的调用CreateScope呢? 直接监控ServiceProviderEngineScope的构造函数就可以了。1. 使用 TargetMethod 口子函数上一篇跟大家聊过 harmony 的口子函数TargetMethods,它可以批量返回需要被 patch 的方法,如果你明确知道只需返回一个,可以用TargetMethod口子来实现,有了这些思路之后,完整的实现代码如下:
internalclassProgram
{
static void Main(string args)
{
var harmony = new Harmony("com.dotnetdebug.www");
harmony.PatchAll;
// 1. 创建服务集合
var services = new ServiceCollection;
// 2. 注册一个作用域服务
services.AddScoped;
// 3. 构建服务提供者
var serviceProvider = services.BuildServiceProvider;
// 4. 创建作用域
var scope = serviceProvider.CreateScope;
var myService = scope.ServiceProvider.GetRequiredService;
myService.DoSomething;
Console.ReadLine;
}
}
classMyService : IDisposable
{
public MyService
{
Console.WriteLine("i'm MyService...");
}
public void DoSomething
{
Console.WriteLine($"{DateTime.Now}Doing work...");
}
public void Dispose
{
Console.WriteLine($"{DateTime.Now}Disposing MyService");
}
}
[HarmonyPatch]
publicclassHookServiceProviderEngineScope
{
[HarmonyTargetMethod]
static MethodBase TargetMethod
{
var engineScopeType = Type.GetType("Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope, Microsoft.Extensions.DependencyInjection");
var constructor = engineScopeType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)[0];
return constructor;
}
public static void Prefix(bool isRootScope)
{
Console.WriteLine("");
Console.WriteLine($"isRootScope:{isRootScope}");
Console.WriteLine(Environment.StackTrace);
}
}
写到这里的时候,出门抽了个烟,突然灵光一现,既然20个单重载方法不够用,我完全可以使用 HarmonyPatch 注解特性组合呀。。。相当于平级补充,说干就干,参考代码如下:
[HarmonyPatch("Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope, Microsoft.Extensions.DependencyInjection", , MethodType.Constructor)]
[HarmonyPatch(new Type[2] { typeof(ServiceProvider), typeof(bool) })]
publicclassHookServiceProviderEngineScope
{
public static void Prefix(bool isRootScope)
{
Console.WriteLine("");
Console.WriteLine($"isRootScope:{isRootScope}");
Console.WriteLine(Environment.StackTrace);
}
}
嵌套类的问题,纠结了之后用HarmonyPatch特性理论上搞不定。2. 完全动态hook整体上来说前面的TargetMethod模式属于混合编程(特性+手工),如果让代码更纯粹一点话,就要把所有的 Attribute 摘掉,这就需要包装器类HarmonyMethod
internalclassProgram
{
static void Main(string args)
{
var harmony = new Harmony("com.dotnetdebug.www");
var engineScopeType = Type.GetType("Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope, Microsoft.Extensions.DependencyInjection");
var originalMethod = engineScopeType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)[0];
var prefixMethod = typeof(HookServiceProviderEngineScope).GetMethod("Prefix");
harmony.Patch(originalMethod, new HarmonyMethod(prefixMethod));
// 1. 创建服务集合
var services = new ServiceCollection;
// 2. 注册一个作用域服务
services.AddScoped;
// 3. 构建服务提供者
var serviceProvider = services.BuildServiceProvider;
// 4. 创建作用域
var scope = serviceProvider.CreateScope;
var myService = scope.ServiceProvider.GetRequiredService;
myService.DoSomething;
Console.ReadLine;
}
}
classMyService : IDisposable
{
public MyService
{
Console.WriteLine("i'm MyService...");
}
public void DoSomething
{
Console.WriteLine($"{DateTime.Now}Doing work...");
}
public void Dispose
{
Console.WriteLine($"{DateTime.Now}Disposing MyService");
}
}
publicclassHookServiceProviderEngineScope
{
public static void Prefix(bool isRootScope)
{
Console.WriteLine("");
Console.WriteLine($"isRootScope:{isRootScope}");
Console.WriteLine(Environment.StackTrace);
}
}
public classHarmonyMethod
{
public MethodInfo method;
publicstring category;
public Type declaringType;
publicstring methodName;
public MethodType? methodType;
public Type argumentTypes;
publicint priority =-1;
publicstring before;
publicstring after;
public HarmonyReversePatchType? reversePatchType;
publicbool? debug;
publicbool nonVirtualDelegate;
}
在特性搞不定的时候,手工HarmonyMethod编程是一个很好的补充,这几篇我们只关注了Prefix,毕竟从高级调试的角度看,我们更关注问题代码的调用栈,从而寻找引发故障的元凶。
来源:opendotnet
免责声明:本站系转载,并不代表本网赞同其观点和对其真实性负责。如涉及作品内容、版权和其它问题,请在30日内与本站联系,我们将在第一时间删除内容!