使用mono_cecil替换对类型/命名空间的引用



背景(没有必要,容易混淆,只适合好奇的人)

我正在使用Unity3D for Mobile的免费版本,它不允许我在移动设备上使用System.Net.Sockets命名空间。问题是我正在使用引用System.Net.Sockets的编译.dll库(即IKVM)。我实际上并没有使用引用引用System.Net.Sockets的IKVM中的类,所以我没有购买3000美元的Unity Pro移动许可证,而是制作了Sockets命名空间的存根库,称为dudeprgm.Net.Sockets,它只是用存根替换了所有的类和方法(我使用Mono源代码做到了这一点)。<标题>

我的问题我需要将我的dll中的所有System.Net.Sockets.*引用替换为dudeprgm.Net.Sockets.*我知道这样的事情是可能的,并且是由其他人完成的(参见页面底部的EDIT)。我想知道怎样自己做这件事。

我能够使用Mono.Cecil编写以下代码。
它遍历所有的IL指令,检查操作数是否为InlineType,然后检查内联类型是否为System.Net.Sockets的一部分,然后将其重命名为dudeprgm.Net.Sockets并写入。**我不确定这是否是在Mono.Cecil中"寻找和替换"的正确方法。问题是,这并不能捕获所有Sockets用法(见下文)。

private static AssemblyDefinition stubsAssembly;
static void Main(string[] args) {
AssemblyDefinition asm = AssemblyDefinition.ReadAssembly(args[0]);
stubsAssembly = AssemblyDefinition.ReadAssembly("Socket Stubs.dll");
// ...
// Call ProcessSockets on everything
// ...
asm.Write(args[1]);
}
/*
* This will be run on every property, constructor and method in the entire dll given
*/
private static void ProcessSockets(MethodDefinition method) {
if (method.HasBody) {
Mono.Collections.Generic.Collection<Instruction> instructions = method.Body.Instructions;
for (int i = 0; i < instructions.Count; i++) {
Instruction instruction = instructions[i];
if (instruction.OpCode.OperandType == OperandType.InlineType) {
string operand = instruction.Operand.ToString();
if (operand.StartsWith("System.Net.Sockets")) {
Console.WriteLine(method.DeclaringType + "." + method.Name + "(...) uses type " + operand);
Console.WriteLine("t(Instruction: " + instruction.OpCode.ToString() + " " + instruction.Operand.ToString() + ")");
instruction.Operand = method.Module.Import(stubsAssembly.MainModule.GetType("dudeprgm.Net.Sockets", operand.Substring(19)));
Console.WriteLine("tReplaced with type " + "dudeprgm.Net.Sockets" + operand.Substring(18));
}
}
}
}
}

它工作得很好,但只能捕获"简单"的指令。用ildasm反编译,我可以看到它取代了如下类型:

box        ['Socket Stubs'/*23000009*/]dudeprgm.Net.Sockets.SocketOptionLevel/*01000058*/

但是它没有捕捉到这些"复杂"的指令:

callvirt   instance void [System/*23000003*/]System.Net.Sockets.Socket/*0100003F*/::SetSocketOption(valuetype [System/*23000003*/]System.Net.Sockets.SocketOptionLevel/*01000055*/,
                                                                          valuetype [System/*23000003*/]System.Net.Sockets.SocketOptionName/*01000056*/,
                                                                          int32) /* 0A000094 */

现在.dlls是dudeprgm.Net.SocketsSystem.Net.Sockets引用的混乱。

我很确定这是发生的,因为我只改变OperandType.InlineTypes,但我不确定如何做到这一点。我试着到处找,但在我看来像是单核细胞增多症。Cecil没有办法将操作数设置为string,似乎只能使用Cecil API (https://stackoverflow.com/a/7215711/837703)。

(对不起,如果我使用了不正确的术语,我对IL总的来说是很新的。)

<标题>

如何替换所有System.Net.Sockets出现在Mono的地方。Cecil,而不仅仅是操作数是InlineType的地方?我真的不想遍历Cecil中的每个OperandType,我只是在Cecil中寻找一些查找和替换方法,这样我就不必自己使用普通的IL了。

编辑:(也是不必要的,令人困惑的,仅供好奇的人使用)
这个人花25美元也做了类似的事情:http://www.reddit.com/r/Unity3D/comments/1xq516/good_ol_sockets_net_sockets_for_mobile_without/.

自动修补程序工具,用于检测和修复脚本和.dll中的套接字使用情况。

,,,,

"DLL的补丁使用monoc . cecil。

你可以去https://www.assetstore.unity3d.com/en/#看看第二个截图!/content/13166,查看是否可以替换命名空间

那个库不适合我的需要,因为1)它没有重命名为我想要的命名空间(dudeprgm.Net.Sockets), 2)它重命名为的库不支持IKVM需要的所有System.Net.Sockets类,因为IKVM几乎使用每个Sockets类,3)它花费25美元,我真的不想买我不打算使用的东西。我只是想展示在Mono中替换命名空间/类型引用。塞西尔是可能的。

[01]类似问题

用另一个dll(和内部类型)替换对dll(和内部类型)的引用的问题在技术上类似于被称为

的问题

Google: "c#为第三方程序集添加强名称">

在这个问题中,你想让你的应用程序通过强名称签名,并可能安装到GAC或Ngen-ed中,但你的应用程序依赖于一个遗留的第三方库,该库在编译时没有添加强名称,这打破了强命名程序集只能使用强命名程序集的要求。你没有第三方库的源代码,只有二进制文件,所以你不能重新编译它(== "简化描述")

有几种可能的解决方案,最典型的有3种:

[02]类似问题的解决方案#1

您可以使用ildasm/ilasmround trip,将所有二进制文件转换为文本形式,将所有引用更改为它们的强名称等效(递归),并将文本转换回代码。示例:http://buffered.io/posts/net-fu-signing-an-unsigned-assembly-without-delay-signing/和https://stackoverflow.com/a/6546134/2626313

[03]类似问题的解决方案#2

您可以使用已经编写的工具来解决这个问题,例如:http://brutaldev.com/post/2013/10/18/NET-Assembly-Strong-Name-Signer

[04]类似问题的解决方案#3

您可以创建一个精心制作的工具,以满足您的确切需求。这是可能的,我已经这样做了,它花了几个星期,代码重达几千行。对于最脏的工作,我主要重用(略微修改)来自(无序)的源代码:

  • ApiChange.Api.Introspection.CorFlagsReader.cs
  • GACManagerApi。融合
  • brutaldev/StrongNameSigner
  • icsharpcode/ILSpy
  • Mono.Cecil.Binary
  • Mono.Cecil.Metadata
  • Mono。ResGen
  • Ricciolo.StylesExplorer.MarkupReflection
  • 和读取http://referencesource.microsoft.com/

[05]你的问题

虽然你所描述的问题看起来只是我上面描述的问题的一个子集,但如果你想使用GAC安装,而GAC安装又需要强名称签名,那么它很可能会变成同样的问题。

我给你的建议是

[06]你的问题解决方案#1

给出最简单的解[02]尝试使用Mono包中的ilasm/ildasm工具,而不是微软的。net框架提供的工具(微软的。net Framework 4.5中的Resgen被破坏,不能往返resx格式,ildasm输出不能正确处理非ascii字符等)。虽然你不能修复微软坏掉的闭源,但你可以修复Mono的开源,但我不需要。

[07]你的问题解决方案#2

如果[06]不适合你,然后学习(调试)→ILSpy←和学习Mono文档的各种命令行工具做什么你需要和他们的来源-你会看到他们是如何使用Mono。塞西尔图书馆

如果您需要验证强命名或甚至签名程序集(篡改它们将使签名无效)或删除签名等。你要钻研的代码比一个简单的Stack Overflow答案所能描述的要长。

[08]你的问题解决方案#3

关于ILMerge的功能以及如何为您提供更简单的解决方案

[09]你的问题解决方案#4

另一个更简单的解决方案可能是(如果IKVM支持的话)挂钩AssemblyResolve事件,在那里你可以将dll名称重新映射到物理dll中,例如从完全不同的文件或从资源流等加载。在编译后的可执行文件中嵌入dll

(编辑# 1:评论后)

[10]你的问题解决方案#5

如果你或多或少的一般问题实际上归结为"我怎么能使IKVM.dll使用我的套接字类,而不是那些从命名空间System.Net。那么很简单的解决方案可能是:

编译和部署您自己的定制版本的IKVM.dll使用源代码可在http://www.ikvm.net/download.html -没有二进制Mono。塞西尔需要魔法

由于所有代码都是打开的,因此应该可以通过

找到并将指向命名空间System.Net的所有引用重定向到dudeprgm.Net
  • [10.1]获取IKVM源代码和所有其他先决条件,并确保您可以编译工作IKVM.dll
  • [10.2] adddudeprgm.Net.csproject to solution
  • [10.3]在所有源文件中找到并删除所有看起来像using System.Net的东西
  • [10.4]在所有的源文件全文找到并替换所有看起来像System.Netdudeprgm.Net
  • [10.5]编译。当编译器报错一个丢失的符号(之前在系统中)。. Net命名空间),然后将其添加到存根文件中。转到(10.5)
  • [10.6]如果上述步骤在2小时后仍未确定为"构建ok",则考虑另一种解决方案(或睡一觉)
  • [10.7]检查IKVM许可证(http://sourceforge.net/p/ikvm/wiki/License/),如果有一些东西你必须改变/声明/承认原始源代码被修改

(编辑# 2:评论后)

[11]你的问题解决方案#6

如果你选择track[04]和使用文本文件和ilasm/ildasm工具(style[02]))似乎没有成效,那么下面是我的自动强名称签名器的关键相关部分,它使用Mono.Cecil将程序集引用更改为其他引用。代码按原样粘贴(没有之前,之后和周围的代码行),以适合我的形式。读取键:a is Mono.Cecil.AssemblyDefinition,b implements Mono.Cecil.IAssemblyResolver,b实例中的键方法是AssemblyDefinition Resolve(AssemblyNameReference name)方法,它将所需的DLL名称转换为对AssemblyDefinition.ReadAssembly(..)的调用。我不需要解析指令流,重新映射程序集引用就足够了(如果需要,我可以从我的代码中粘贴一些其他片段)

/// <summary>
/// Fixes references in assembly pointing to other assemblies to make their PublicKeyToken-s compatible. Returns true if some changes were made.
/// <para>Inspiration comes from https://github.com/brutaldev/StrongNameSigner/blob/master/src/Brutal.Dev.StrongNameSigner.UI/MainForm.cs
/// see call to SigningHelper.FixAssemblyReference
/// </para>
/// </summary>
public static bool FixStrongNameReferences(IEngine engine, string assemblyFile, string keyFile, string password)
{
var modified = false;
assemblyFile = Path.GetFullPath(assemblyFile);
var assemblyHasStrongName = GetAssemblyInfo(assemblyFile, AssemblyInfoFlags.Read_StrongNameStatus)
.StrongNameStatus == StrongNameStatus.Present;
using (var handle = new AssemblyHandle(engine, assemblyFile))
{
AssemblyDefinition a;
var resolver = handle.GetAssemblyResolver();
a = handle.AssemblyDefinition;
foreach (var reference in a.MainModule.AssemblyReferences)
{
var b = resolver.Resolve(reference);
if (b != null)
{
// Found a matching reference, let's set the public key token.
if (BitConverter.ToString(reference.PublicKeyToken) != BitConverter.ToString(b.Name.PublicKeyToken))
{
reference.PublicKeyToken = b.Name.PublicKeyToken ?? new byte[0];
modified = true;
}
}
}
foreach (var resource in a.MainModule.Resources.ToList())
{
var er = resource as EmbeddedResource;
if (er != null && er.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase))
{
using (var targetStream = new MemoryStream())
{
bool resourceModified = false;
using (var sourceStream = er.GetResourceStream())
{
using (System.Resources.IResourceReader reader = new System.Resources.ResourceReader(sourceStream))
{
using (var writer = new System.Resources.ResourceWriter(targetStream))
{
foreach (DictionaryEntry entry in reader)
{
var key = (string)entry.Key;
if (entry.Value is string)
{
writer.AddResource(key, (string)entry.Value);
}
else
{
if (key.EndsWith(".baml", StringComparison.OrdinalIgnoreCase) && entry.Value is Stream)
{
Stream newBamlStream = null;
if (FixStrongNameReferences(handle, (Stream)entry.Value, ref newBamlStream))
{
writer.AddResource(key, newBamlStream, closeAfterWrite: true);
resourceModified = true;
}
else
{
writer.AddResource(key, entry.Value);
}
}
else
{
writer.AddResource(key, entry.Value);
}
}
}
}
}
if (resourceModified)
{
targetStream.Flush();
// I'll swap new resource instead of the old one
a.MainModule.Resources.Remove(resource);
a.MainModule.Resources.Add(new EmbeddedResource(er.Name, resource.Attributes, targetStream.ToArray()));
modified = true;
}
}
}
}
}
if (modified)
{
string backupFile = SigningHelper.GetTemporaryFile(assemblyFile, 1);
// Make a backup before overwriting.
File.Copy(assemblyFile, backupFile, true);
try
{
try
{
AssemblyResolver.RunDefaultAssemblyResolver(Path.GetDirectoryName(assemblyFile), () => {
// remove previous strong name https://groups.google.com/forum/#!topic/mono-cecil/5If6OnZCpWo
a.Name.HasPublicKey = false;
a.Name.PublicKey = new byte[0];
a.MainModule.Attributes &= ~ModuleAttributes.StrongNameSigned;
a.Write(assemblyFile);
});
if (assemblyHasStrongName)
{
SigningHelper.SignAssembly(assemblyFile, keyFile, null, password);
}
}
catch (Exception)
{
// Restore the backup if something goes wrong.
File.Copy(backupFile, assemblyFile, true);
throw;
}
}
finally
{
File.Delete(backupFile);
}
}
}
return modified;
}

[12]轮到你了

这实际上是对@xmojmer的回答的扩展。

我写了一个小的bash脚本来自动执行xmojmer的选项[02]:

# vsilscript.sh
# Usage:
# Open Cygwin
# . vsilscript.sh <original .dll to patch>
# output in "Sockets_Patched" subdirectory
nodosfilewarning=true # just in case cygwin complains
ILASM_PATH="/cygdrive/c/Windows/Microsoft.NET/Framework64/" # modify for your needs
ILASM_PATH="$ILASM_PATH"$(ls "$ILASM_PATH" -v | tail -n 1)
ILDASM_PATH="/cygdrive/c/Program Files (x86)/Microsoft SDKs/Windows/v8.1A/bin/NETFX 4.5.1 Tools" # modify for your needs
PATH="$ILDASM_PATH:$ILASM_PATH:$PATH"
base=$(echo $1 | sed "s/.dll//g")
ildasm /out=$base".il" /all /typelist $base".dll"
cp $base".il" "tempdisassembled.il"
cat "tempdisassembled.il" | awk '
BEGIN { print ".assembly extern socketstubs { }"; }
{ gsub(/[System[^]]*]System.Net.Sockets/, "[socketstubs]dudeprgm.Net.Sockets", $0); print $0; }
END { print "n"; }
' 1> $base".il" #change the awk program to swap out different types
rm "tempdisassembled.il"
mkdir "Sockets_Patched"
# read -p "Press Enter to assemble..."
ilasm /output:"Sockets_Patched/"$base".dll" /dll /resource:$base".res" $base".il"

修改ILASM_PATHILDASM_PATH变量,如果您的计算机是32位或ilasmildasm在不同的位置。此脚本假设您在Windows上使用Cygwin。

修改发生在awk命令中。它通过添加

向我的存根库(称为socketstubs.dll,并存储在同一目录中)添加一个引用。
.assembly extern sockectstubs { }

在反汇编的IL代码的开头。然后,对于每一行,它查找[System]System.Net.Sockets并将它们替换为[socketstubs]dudeprgm.Net.Sockets。它在IL代码的末尾添加了一个换行符,否则ilasm将不会重新组装它,按照文档:

如果.il源文件中的最后一行代码没有尾随空格或行尾字符,则编译可能会失败。

最终补丁的。dll将被放置在一个名为"Sockets_Patched"的新目录中。

您可以修改此脚本以交换任何dll中的任意两个命名空间:

  1. [System[^]]中的System替换为包含旧命名空间的程序集
  2. System.Net.Sockets替换为旧的命名空间,并在每个句点前加上反斜杠
  3. 用包含新命名空间
  4. 的库替换脚本中的所有socketstubs
  5. 用新的命名空间
  6. 替换dudeprgm.Net.Sockets

您可以将套接字对象包装在接口中,然后依赖注入您真正想要的实现。代码可能有点乏味,但是您可以在之后将套接字替换为您想要的任何内容,而无需直接引用System.Net.Sockets

最新更新