【发布时间】:2016-02-02 15:21:58
【问题描述】:
我正在对程序集进行混淆处理,在混淆后 PEVerify 发出以下错误:
[MD]: Error: Method has a duplicate, token=0x060035d8. [token:0x060035D5]
[MD]: Error: Method has a duplicate, token=0x060035d5. [token:0x060035D8]
这是第一个带有标题的方法声明:
// Token: 0x060035D5 RID: 13781 RVA: 0x000D7828 File Offset: 0x000D5A28
.method private final hidebysig newslot virtual
instance void b () cil managed
{
.override method instance void [mscorlib]System.IDisposable::Dispose()
// Header Size: 12 bytes
// Code Size: 52 (0x34) bytes
// LocalVarSig Token: 0x11000050 RID: 80
.maxstack 2
.locals init (
[0] int32
)
这是第二个:
// Token: 0x060035D8 RID: 13784 RVA: 0x000248BC File Offset: 0x00022ABC
.method private hidebysig
instance void b () cil managed
{
// Header Size: 1 byte
// Code Size: 31 (0x1F) bytes
.maxstack 8
对我来说似乎是一个显式的 IDisposable 接口实现。两种方法也都被调用,所以并不是所有对一个方法的调用都被对另一个方法的调用替换了。他们只是同名
如果用 C# 编写了类似的代码 - 编译器将发出 System.IDisposable.Dispose() 和 Dispose() 方法,从而消除相同的名称并使 PEVerify 保持沉默。
为了确保当其中一个是接口方法的显式覆盖而另一个不是时,相同的名称使 IL 有效,我编写了这样的示例应用程序:
namespace ClassLibrary1 {
public interface IX { void M(); }
public class Class1 : IX {
void IX.M() { Console.WriteLine("IX.M()"); }
public void M() { Console.WriteLine("M()"); }
}
public class Class2 {
public static void Main(string[] args) {
var x = new Class1();
x.M();
((IX)x).M();
}
}
}
IL 看起来像这样:
// Token: 0x06000002 RID: 2 RVA: 0x00002050 File Offset: 0x00000250
.method private final hidebysig newslot virtual
instance void ClassLibrary1.IX.M () cil managed
{
.override method instance void ClassLibrary1.IX::M()
// Header Size: 1 byte
// Code Size: 13 (0xD) bytes
.maxstack 8
/* 0x00000251 00 */ IL_0000: nop
/* 0x00000252 7201000070 */ IL_0001: ldstr "IX.M()"
/* 0x00000257 280F00000A */ IL_0006: call void [mscorlib]System.Console::WriteLine(string)
/* 0x0000025C 00 */ IL_000B: nop
/* 0x0000025D 2A */ IL_000C: ret
} // end of method Class1::ClassLibrary1.IX.M
// Token: 0x06000003 RID: 3 RVA: 0x0000205E File Offset: 0x0000025E
.method public hidebysig
instance void M () cil managed
{
// Header Size: 1 byte
// Code Size: 13 (0xD) bytes
.maxstack 8
/* 0x0000025F 00 */ IL_0000: nop
/* 0x00000260 720F000070 */ IL_0001: ldstr "M()"
/* 0x00000265 280F00000A */ IL_0006: call void [mscorlib]System.Console::WriteLine(string)
/* 0x0000026A 00 */ IL_000B: nop
/* 0x0000026B 2A */ IL_000C: ret
} // end of method Class1::M
注意不同的方法名称。
然后我使用生成的 exe 并将 ClassLibrary1.IX.M 编辑为 Class1 中的 M(我已经使用 dnSpy 来做到这一点)。 PEVerify 确实开始发布有关重复方法的相同问题,但 exe 仍然可以正常打印 M() IX.M(),如预期的那样。
问题是,PEVerify 是否在这里过于谨慎,或者我看不到重合名称确实存在问题?
【问题讨论】:
-
“这两种方法都被调用了,所以并不是所有对一个方法的调用都被替换为对另一个方法的调用。” - 当你调用非释放覆盖的方法时,它怎么知道你'不是试图调用 dispose-overrides 吗?是的,您可以通过 idisposable 调用它,但您也可以显式调用它(来自 MSIL。您不能在 C# 中执行此操作。但您可以在 VB 中执行此操作)。
-
@Random832 如果我查看 ildasm'ed 编辑的程序集 - 它通过他们的 MD 标记显示方法,我认为这就是它保存在调用站点的方式。
-
不过,那是在 cmets 中。源代码具有名称。问题是它无法知道编译器是否真的选择了正确的 MD 标记,因为有两个可以用相同的代码命名。
-
@Random832 好吧,我在这里谈论的是第一句话中所述的混淆——无论如何,你不能在 C# 中使用这样的程序集。祝你好运,使用名为
2的类,例如来自 C#。因此,C# 编译器能做什么或不能做什么是无关紧要的——无论如何你都做不到。 -
我的观点是,同一个类的MSIL源代码中的调用站点甚至不知道调用哪个方法,所以PEVerify调用因为 MSIL 编译器 (ilasm) 可能选择了错误的编译器。反射等也一样。覆盖不仅可以按名称调用,因为它是显式接口实现。