ILWeaving 帮助 – [ValidSystemPath] 属性

huangapple go评论75阅读模式
英文:

ILWeaving help - [ValidSystemPath] attribute

问题

I'll provide a translation of the code and text you've shared:

**问题**

我正在使用Mono.Cecil来对具有我的自定义[ValidSystemPath]属性的字符串属性getter进行IL编织。该属性的目的是确保属性仅返回文件名和路径等有效的系统字符。问题是,目前代码不起作用,但在编织过程中没有引发任何异常。我对编织和IL不熟悉,因此需要一些指导。

**编织之前的代码(C#)**

```csharp
private string path = "test|.txt";
[ValidSystemPath]
public string Path => path;

期望的编织后代码(C#)

这实际上是我尝试编织的代码的灵感来源... https://stackoverflow.com/a/23182807/1995360

public string Path {
	get {
		string ReplaceIllegal(string p)
		{
			char[] invalid = Path.GetInvalidFileNameChars().Concat(Path.GetInvalidPathChars()).ToArray();
			return string.Join("_", p.Split(invalid));
		}
		
		return ReplaceIllegal(path);
	}
}

我想使用嵌套方法,因为如果getter包含条件语句,可能会有多个返回语句,因此我需要在每个返回语句之前对嵌套方法进行简单调用。

编织器代码(C#)

private static void ValidSystemPath(ModuleDefinition module, TypeDefinition type, PropertyDefinition property)
{
    // 获取getter方法并创建ILProcessor
    MethodDefinition getter = property.GetMethod;
    ILProcessor getterProcessor = getter.Body.GetILProcessor();

    // 导入方法引用
    MethodReference joinMethod = module.ImportReference(typeof(string).GetMethod("Join", new Type[] { typeof(string), typeof(string[]) }));
    MethodReference splitMethod = module.ImportReference(typeof(string).GetMethod("Split", new Type[] { typeof(char[]) }));
    MethodReference getInvalidPathCharsMethod = module.ImportReference(typeof(Path).GetMethod("GetInvalidPathChars", new Type[] { }));
    MethodReference getInvalidFileNameCharsMethod = module.ImportReference(typeof(Path).GetMethod("GetInvalidFileNameChars", new Type[] { }));
    MethodReference concatMethod = module.ImportReference(typeof(Enumerable).GetMethod("Concat"));
    MethodReference toArrayMethod = module.ImportReference(typeof(Enumerable).GetMethod("ToArray"));

    // 在getter中创建新的嵌套方法
    MethodDefinition nested = new(
        $"<{getter.Name}>g__ReplaceIllegalChars|2_0",
        Mono.Cecil.MethodAttributes.Assembly | Mono.Cecil.MethodAttributes.HideBySig | Mono.Cecil.MethodAttributes.Static,
        module.TypeSystem.String
    );
    type.Methods.Add(nested);

    // 为嵌套方法编写指令
    ILProcessor nestedProcessor = nested.Body.GetILProcessor();
    nestedProcessor.Emit(OpCodes.Nop);
    nestedProcessor.Emit(OpCodes.Call, getInvalidFileNameCharsMethod);
    nestedProcessor.Emit(OpCodes.Call, getInvalidPathCharsMethod);
    nestedProcessor.Emit(OpCodes.Call, concatMethod);
    nestedProcessor.Emit(OpCodes.Call, toArrayMethod);
    nestedProcessor.Emit(OpCodes.Stloc_0); // 返回值在堆栈顶部
    nestedProcessor.Emit(OpCodes.Ldstr, "_");
    nestedProcessor.Emit(OpCodes.Ldarg_0);
    nestedProcessor.Emit(OpCodes.Ldloc_0);
    nestedProcessor.Emit(OpCodes.Callvirt, splitMethod); // 非静态方法
    nestedProcessor.Emit(OpCodes.Call, joinMethod);
    nestedProcessor.Emit(OpCodes.Stloc_1);
    nestedProcessor.Emit(OpCodes.Ldloc_1);

    // 在每个返回语句之前添加嵌套调用
    IEnumerable<Instruction> returnInstructions = getterProcessor.Body.Instructions.Where(instruction => instruction.OpCode == OpCodes.Ret);
    returnInstructions.ToList().ForEach(ret => getterProcessor.InsertBefore(ret, Instruction.Create(OpCodes.Call, nested)));
}

编织器的分解

  1. 找到注入点并创建一个ILProcessor。
  2. 导入将要调用的所有方法的方法引用(string Split/Join、Path GetInvalidPathChars/GetInvalidFileNameChars和Enumerable Concat/ToArray)。
  3. 创建嵌套方法并添加。
  4. 发出嵌套方法的方法体。
  5. 在每个返回语句之前添加嵌套方法调用(这里有很多代码被注释掉,因为我在测试插入每个方法调用的最佳方法。我还尝试使用SimplifyMacros()和OptimizeMacros(),但不确定它们的作用,所以将其注释掉)。

期望/实际运行时输出

期望输出:"test_.txt" / 实际输出:"test|.txt"

谢谢您提供的帮助。

英文:

Problem

I'm using Mono.Cecil to IL Weave string property getters that have my custom [ValidSystemPath] attribute on them. The purpose of the attribute is to ensure the property only ever returns valid system characters for file names and paths etc. Problem is, the code is not currently working, yet is not raising any exceptions during weaving. I'm new to weaving and IL, so I'd benefit from a guiding-hand.

Code before weaving (C#)

private string path = &quot;test|.txt&quot;;
[ValidSystemPath]
public string Path =&gt; path;

Expected code after weaving (C#)

This is effectively my source of inspiration for the code I'm trying to weave in... https://stackoverflow.com/a/23182807/1995360

public string Path {
	get {
		string ReplaceIllegal(string p)
		{
			char[] invalid = Path.GetInvalidFileNameChars().Concat(Path.GetInvalidPathChars()).ToArray();
			return string.Join(&quot;_&quot;, p.Split(invalid));
		}
		
		return ReplaceIllegal(path);
	}
}

I'd like to use a nested method, because if the getter contains conditionals, there could be multiple return statements, thus I need to make a simple call to the nested method before each return statement.

Weaver code (C#)

private static void ValidSystemPath(ModuleDefinition module, TypeDefinition type, PropertyDefinition property)
{
    // Getter method - site of injection
    MethodDefinition getter = property.GetMethod;
    ILProcessor getterProcessor = getter.Body.GetILProcessor();

    // Import the methods
    MethodReference joinMethod = module.ImportReference(typeof(string).GetMethod(&quot;Join&quot;, new Type[] { typeof(string), typeof(string[]) }));
    MethodReference splitMethod = module.ImportReference(typeof(string).GetMethod(&quot;Split&quot;, new Type[] { typeof(char[]) }));
    MethodReference getInvalidPathCharsMethod = module.ImportReference(typeof(Path).GetMethod(&quot;GetInvalidPathChars&quot;, new Type[] { }));
    MethodReference getInvalidFileNameCharsMethod = module.ImportReference(typeof(Path).GetMethod(&quot;GetInvalidFileNameChars&quot;, new Type[] { }));
    MethodReference concatMethod = module.ImportReference(typeof(Enumerable).GetMethod(&quot;Concat&quot;));
    MethodReference toArrayMethod = module.ImportReference(typeof(Enumerable).GetMethod(&quot;ToArray&quot;));
    //MethodReference toArrayMethod = module.ImportReference(typeof(Enumerable).GetMethodExt(&quot;ToArray&quot;, new Type[] { typeof(IEnumerable&lt;char&gt;) }));

    // Create new nested method in getter
    MethodDefinition nested = new(
        $&quot;&lt;{getter.Name}&gt;g__ReplaceIllegalChars|2_0&quot;,
        Mono.Cecil.MethodAttributes.Assembly | Mono.Cecil.MethodAttributes.HideBySig | Mono.Cecil.MethodAttributes.Static,
        module.TypeSystem.String
    );
    type.Methods.Add(nested);

    // Write instructions for method
    ILProcessor nestedProcessor = nested.Body.GetILProcessor();
    nestedProcessor.Emit(OpCodes.Nop);
    nestedProcessor.Emit(OpCodes.Call, getInvalidFileNameCharsMethod);
    nestedProcessor.Emit(OpCodes.Call, getInvalidPathCharsMethod);
    nestedProcessor.Emit(OpCodes.Call, concatMethod);
    nestedProcessor.Emit(OpCodes.Call, toArrayMethod);
    nestedProcessor.Emit(OpCodes.Stloc_0); // Return value is top stack
    nestedProcessor.Emit(OpCodes.Ldstr, &quot;_&quot;);
    nestedProcessor.Emit(OpCodes.Ldarg_0);
    nestedProcessor.Emit(OpCodes.Ldloc_0);
    nestedProcessor.Emit(OpCodes.Callvirt, splitMethod); // Non static
    nestedProcessor.Emit(OpCodes.Call, joinMethod);
    nestedProcessor.Emit(OpCodes.Stloc_1);
    nestedProcessor.Emit(OpCodes.Ldloc_1);

    //getterProcessor.Body.SimplifyMacros();
    // Add nested call before each return
    IEnumerable&lt;Instruction&gt; returnInstructions = getterProcessor.Body.Instructions.Where(instruction =&gt; instruction.OpCode == OpCodes.Ret);
    returnInstructions.ToList().ForEach(ret =&gt; getterProcessor.InsertBefore(ret, Instruction.Create(OpCodes.Call, nested)));
    /*foreach (Instruction ret in returnInstructions)
    {
        // Call nested method and return that value
        getterProcessor.InsertBefore(ret, Instruction.Create(OpCodes.Call, nested));
    }*/
    //getterProcessor.Body.OptimizeMacros();
}

Breakdown of weaver

  1. Find the site of injection and create an ILProcessor.
  2. Import method references for all the method calls we'll be making (string Split/Join, Path GetInvalidPathChars/GetInvalidFileNameChars, and Enumerable Concat/ToArray).
  3. Create the nested method and add.
  4. Emit the method body.
  5. Add nested method calls before each return statement. (there's a lot of code commented out here, as I was testing the best way to insert each method call. I've also tried using SimplifyMacros() and OptimizeMacros() but was unsure what they did so commented out).

Expected / Actual runtime output

&quot;test_.txt&quot; / &quot;test|.txt&quot;

Thank you for any help you can provide me in getting this code working.

答案1

得分: 2

以下是翻译好的内容:

正如我在评论中提到的,保存文件时并不需要有效的IL代码。有些混淆器会使用此方法,IL代码只在方法执行前才被修复。如果这不是你想要的,请确保你的操作生成了正确的IL代码,以便运行时正确执行。

在我看来,最佳方法是编写你想要生成的代码,然后在ILSpy/dnSpy中查看生成的IL代码。

你的代码存在以下问题:

缺少 ret 语句。

只需在生成的IL代码末尾添加 nestedProcessor.Emit(OpCodes.Ret); 即可。

使用参数

在第 nestedProcessor.Emit(OpCodes.Ldarg_0); 行中,你正在加载参数0,但没有定义参数。添加 nested.Parameters.Add(new ParameterDefinition(module.TypeSystem.String)); 表示该函数接受一个类型为 string 的参数。

使用局部变量

在第 nestedProcessor.Emit(OpCodes.Stloc_0);nestedProcessor.Emit(OpCodes.Stloc_1); 行中,你使用了局部变量,但也没有定义它们。

添加以下行:

nested.Body.Variables.Add(new VariableDefinition(module.ImportReference(typeof(char[]))));
nested.Body.Variables.Add(new VariableDefinition(module.TypeSystem.String));

以表示该方法有两个局部变量,第一个是 char[] 类型,第二个是 string 类型。

泛型

在你的代码中,你使用了泛型(ConcatToArray 调用),在使用IL之前需要对它们进行特化。调用 MakeGenericMethod 并提供泛型类型,以正确特化它们后再使用。

var concat = typeof(Enumerable).GetMethod("Concat");
var concat_spec = concat.MakeGenericMethod(typeof(char));
MethodReference concatMethod = module.ImportReference(concat_spec);

var toArray = typeof(Enumerable).GetMethod("ToArray");
var toArray_spec = toArray.MakeGenericMethod(typeof(char));
MethodReference toArrayMethod = module.ImportReference(toArray_spec);
英文:

As I've mentioned in the comment, it's not required to have valid IL to be able to save the file. This is sometimes used by some obfuscators and IL is only fixed before the method get executed. If this is not what you want, you need to be sure that what you are doing is producing correct IL, that will be correctly executed by the runtime.

The best approach (IMO) is to write the code you want to generate and see the generate IL in ILSpy/dnSpy.

Problems with the your code are:

Missing ret statement.

Just add nestedProcessor.Emit(OpCodes.Ret); at the end of the generated ILs.

Using arguments

In line nestedProcessor.Emit(OpCodes.Ldarg_0); you are loading the argument 0, but there's no arguments defined. Add nested.Parameters.Add(new ParameterDefinition(module.TypeSystem.String)); to indicate that this function takes one argument of type string.

Using locals

In lines nestedProcessor.Emit(OpCodes.Stloc_0); and nestedProcessor.Emit(OpCodes.Stloc_1); you are using local variables, but those are not defined too.

Add lines

nested.Body.Variables.Add(new VariableDefinition(module.ImportReference(typeof(char[])));
nested.Body.Variables.Add(new VariableDefinition(module.TypeSystem.String));

to indicate that this method has 2 local variables, first of type char[] and second of type string.

Generics

In your code, you use generics (Concat and ToArray calls) and those need to be specialized before being used in IL. Call MakeGenericMethod providing the generic type, to correctly specialized them before use.

var concat = typeof(Enumerable).GetMethod(&quot;Concat&quot;);
var conact_spec = concat.MakeGenericMethod(typeof(char));
MethodReference concatMethod = module.ImportReference(conact_spec);

var toArray = typeof(Enumerable).GetMethod(&quot;ToArray&quot;);
var toArray_spec = toArray.MakeGenericMethod(typeof(char));
MethodReference toArrayMethod = module.ImportReference(toArray_spec);

huangapple
  • 本文由 发表于 2023年5月6日 20:45:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/76188963.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定