【问题标题】:Replace references to a type/namespace using Mono.Cecil使用 Mono.Cecil 替换对类型/命名空间的引用
【发布时间】:2014-09-24 12:22:21
【问题描述】:

背景(不必要的,令人困惑的,仅供好奇的人使用)

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


我的问题

我需要将我的 dll 中的所有 System.Net.Sockets.* 引用替换为 dudeprgm.Net.Sockets.*我知道这样的事情是可能的并且由其他人完成(请参阅下面的编辑,在页面底部)。我想知道如何自己做。

我能够使用 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,但我不确定如何做到这一点。我试过到处看看,但在我看来,Mono.Cecil 无法将操作数设置为string,一切似乎都只能使用 Cecil API (https://stackoverflow.com/a/7215711/837703) 来完成。

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

问题

如何替换所有在 Mono.Cecil 中出现 System.Net.Sockets 的位置,而不仅仅是操作数是 InlineType 的位置?我真的不想遍历 Cecil 中的每个 OperandType,我只是在 Cecil 中寻找一些 find-and-replace 方法,我不必使用普通的 IL我自己。

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

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

...

“DLL 是使用 Mono.Cecil 修补的。...

您可以查看https://www.assetstore.unity3d.com/en/#!/content/13166 的第二个屏幕截图,看到它说它可以替换命名空间。

那个库不符合我的需要,因为 1) 它没有重命名为我想要的命名空间 (dudeprgm.Net.Sockets),2) 它重命名的库不支持所有 System.Net.Sockets 类IKVM 需要,因为 IKVM 几乎使用每个 Sockets 类,并且 3) 它的成本为 25 美元,我真的不想购买我不会使用的东西。我只是想表明在 Mono.Cecil 中替换命名空间/类型引用是可能的。

【问题讨论】:

  • ...有人吗?请帮忙。
  • 如果你能得到统一的 dll,你可以反汇编它们看看发生了什么
  • @PhilipPittle 好的,但我不确定这会有所帮助。我认为我无法编辑它们以允许 Unity Free 编译引用 System.Net.Sockets 的代码(另外,认为统一的那部分只是在一个大的 .exe 中)。我一直在寻找一种编辑 IKVM dll 以不包含 System.Net.Sockets 的方法:基本上,只需在 dll 中将一个命名空间替换为另一个命名空间。 :)

标签: c# replace types il mono.cecil


【解决方案1】:

[01] 类似问题

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

的问题

Google:“c# add strong name to 3rd party assembly”

在这个问题中,您希望您的应用程序使用强名称签名并可能安装到 GAC 或 Ngen-ed 中,但您的应用程序依赖于旧的 3rd 方库,该库在编译时没有添加强名称,这会破坏要求强命名程序集只能使用强命名程序集。您没有第 3 方库的源代码,只有二进制文件,因此您无法重新编译它(== "simplified description")

有几种可能的解决方案,最典型的 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.Fusion
  • brutaldev/StrongNameSigner
  • icsharpcode/ILSpy
  • Mono.Cecil.Binary
  • Mono.Cecil.Metadata
  • Mono.ResGen
  • Ricciolo.StylesExplorer.MarkupReflection
  • 和阅读http://referencesource.microsoft.com/

[05] 你的问题

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

我给你的建议是

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

给出最简单的解决方案[02]尝试使用 Mono 包中的 ilasm/ildasm 工具而不是 Microsoft 的 .NET Framework 提供的工具(Microsoft's Resgen in .NET Framework 4.5已损坏且无法往返 resx 格式,Ildasm 输出无法正确处理非 ASCII 字符等。虽然您无法修复 Microsoft 损坏的封闭源代码,但您可以修复 Mono 的开源代码,但我不必这样做。)

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

如果 [06] 对您不起作用,请学习(调试)→ ILSpy ← 并研究 Mono 文档,了解各种命令行工具的需求及其来源 - 您会看到他们究竟是如何使用 Mono.Cecil 库的

如果您需要验证强命名甚至签名的程序集(篡改它们会使签名无效)或删除签名等。您将深入研究代码,而不是简单的 Stack Overflow 答案所能描述的。

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

了解ILMerge 的作用以及如何为您提供更简单的解决方案

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

另一个更简单的解决方案可能是(如果 IKVM 支持)挂钩 AssemblyResolve 事件,您可以在其中将 dll 名称重新映射到物理 dll,例如加载来自完全不同的文件或来自资源流等。如旧堆栈溢出问题Embedding DLLs in a compiled executable的几个答案所示@

(编辑 #1:在 cmets 之后)

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

如果您或多或少的一般性问题实际上归结为“如何让 IKVM.dll 使用我的套接字类而不是命名空间 System.Net.Sockets 中的那些”,那么非常简单的解决方案可能是:

使用http://www.ikvm.net/download.html 提供的源代码编译和部署您自己定制的 IKVM.dll 版本 - 无需二进制 Mono.Cecil 魔法。

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

找到所有指向命名空间System.Net 的引用并将其重定向到dudeprgm.Net
  • [10.1] 获取 IKVM 源代码和所有其他先决条件,并确保您可以编译工作的 IKVM.dll
  • [10.2] 将dudeprgm.Net.cs项目添加到解决方案中
  • [10.3] 在所有源文件中查找并删除所有类似于 using System.Net 的内容
  • [10.4] 在所有源文件全文中查找并用dudeprgm.Net 替换所有看起来像System.Net 的内容
  • [10.5] 编译。当编译器抱怨缺少符号(之前在 System.Net 命名空间中)时,将其添加到您的存根文件中。转到 [10.5]
  • [10.6] 如果上述步骤在 2 小时后仍未稳定为“构建正常”,请考虑另一种解决方案(或睡一觉)
  • [10.7] 检查 IKVM 许可证 (http://sourceforge.net/p/ikvm/wiki/License/),如果原始源代码已修改,您必须更改/声明/确认某些内容

(编辑#2:在 cmets 之后)

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

如果您选择跟踪 [04] 并使用文本文件和 ilasm/ildasm 工具(样式 [02])似乎没有效率,那么以下是相关的关键我的自动强名称签名器的一部分,它使用 Mono.Cecil 将程序集引用更改为其他引用。代码以适合我的形式按原样粘贴(之前、之后和周围没有代码行)。读取密钥:a is Mono.Cecil.AssemblyDefinitionb implements Mono.Cecil.IAssemblyResolverb 实例中的密钥方法是 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] 轮到你了

【讨论】:

  • +1 感谢您提供详细的选项列表!我看到这比我预期的要难。遗憾。我对 IL 很陌生,所以我想要一种相对快速的方法来做到这一点,而无需反汇编/重新组装代码。
  • 好的,我正在尝试 [02],使用 Mono 的 monodisilasm,但我不断收到这个神秘错误:[ERROR] FATAL UNHANDLED EXCEPTION: System.ArgumentException: Key duplication when adding: System.Collections.DictionaryEntry跨度>
  • 知道这意味着什么吗?我只是在做monodis,然后立即在生成IL上做ilasm,看看它是否会在没有任何修改的情况下编译,但即使这样也行不通。
  • 我在 VS 命令提示符中使用 Microsoft 的 ilasmildasm 修复了该错误。我正在尝试编写一个能够为我所有的 dll 执行此操作的脚本。 (另外,[10] 并不完全适合我,因为我有一些引用 System.Data 的封闭源 dll,而后者又引用 System.Net.Sockets,我将不得不做同样的事情也与他们有关)。
  • 多么美妙的旅程啊,stackoverflow 应该有更多这样的答案......这个特别的答案非常适合任何考虑“以上任何一个”的人,只是为了了解他们正在进入的内容。
【解决方案2】:

这实际上是对@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"

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

修改发生在awk 命令中。它添加了对我的存根库(称为socketstubs.dll,并存储在同一目录中)的引用。通过添加

.assembly extern sockectstubs { }

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

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

最终修补的 .dll 将被放置在一个名为“Sockets_Patched”的新目录中。

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

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

【讨论】:

  • 应用您的脚本后...您的应用程序运行了吗?我的意思是,如果您没有遇到程序集强名称问题。当 DLL 二进制文件被修改时,DLL 的校验和会发生变化,这会使(可能的)数字签名和强名称散列无效。因此,.NET Framework 可能会拒绝加载此类 DLL。那么它真的有效吗?
  • 我认为 Unity 不会进行任何此类检查(或者它可能会抑制它),而且我没有将它放入 GAC,所以它至少对我有用。也许是因为 unity 使用 Mono。不过,我不确定它对其他人有多大帮助。
【解决方案3】:

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

【讨论】:

  • 对不起,我想我可能误读了你的问题。替换第 3 方 DLL 中的引用会更困难。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-19
  • 2021-08-23
  • 1970-01-01
  • 2021-05-17
  • 1970-01-01
  • 2017-06-04
相关资源
最近更新 更多