我想将 .Net 程序集分析为独立于 C#、VB.NET 或其他语言的语言。
我知道 Roslyn 和 NRefactory,但它们似乎只适用于 C# 源代码级别?
CodePlex 上还有“通用编译器基础结构:代码模型和 AST API ”项目,该项目声称“支持以独立于语言的结构化形式表示代码块的分层对象模型”,这听起来完全符合我的要求。
但是,我找不到任何实际执行此操作的有用文档或代码。
任何建议如何存档?
Mono.Cecil 可以做点什么吗?
4 回答
你可以这样做,在 ILSpy 的源代码中也有一个(虽然很小)的例子。
var assembly = AssemblyDefinition.ReadAssembly("path/to/assembly.dll");
var astBuilder = new AstBuilder(new DecompilerContext(assembly.MainModule));
decompiler.AddAssembly(assembly);
astBuilder.SyntaxTree...
CCI 代码模型介于 IL 反汇编器和完整的 C# 反编译器之间:它为您的代码提供了一些结构(例如if
语句和表达式),但它也包含一些低级堆栈操作,例如push
and pop
。
CCI 包含一个显示此内容的示例:PeToText。
例如,要获取Program
类型的第一个方法(在全局命名空间中)的代码模型,您可以使用如下代码:
string fileName = "whatever.exe";
using (var host = new PeReader.DefaultHost())
{
var module = (IModule)host.LoadUnitFrom(fileName);
var type = (ITypeDefinition)module.UnitNamespaceRoot.Members
.Single(m => m.Name.Value == "Program");
var method = (IMethodDefinition)type.Members.First();
var methodBody = new SourceMethodBody(method.Body, host, null, null);
}
为了演示,如果你反编译上面的代码并使用 PeToText 显示它,你将得到:
Microsoft.Cci.ITypeDefinition local_3;
Microsoft.Cci.ILToCodeModel.SourceMethodBody local_5;
string local_0 = "C:\\code\\tmp\\nuget tmp 2015\\bin\\Debug\\nuget tmp 2015.exe";
Microsoft.Cci.PeReader.DefaultHost local_1 = new Microsoft.Cci.PeReader.DefaultHost();
try
{
push (Microsoft.Cci.IModule)local_1.LoadUnitFrom(local_0).UnitNamespaceRoot.Members;
push Program.<>c.<>9__0_0;
if (dup == default(System.Func<Microsoft.Cci.INamespaceMember, bool>))
{
pop;
push Program.<>c.<>9.<Main0>b__0_0;
Program.<>c.<>9__0_0 = dup;
}
local_3 = (Microsoft.Cci.ITypeDefinition)System.Linq.Enumerable.Single<Microsoft.Cci.INamespaceMember>(pop, pop);
local_5 = new Microsoft.Cci.ILToCodeModel.SourceMethodBody((Microsoft.Cci.IMethodDefinition)System.Linq.Enumerable.First<Microsoft.Cci.ITypeDefinitionMember>(local_3.Members).Body, local_1, (Microsoft.Cci.ISourceLocationProvider)null, (Microsoft.Cci.ILocalScopeProvider)null, 0);
}
finally
{
if (local_1 != default(Microsoft.Cci.PeReader.DefaultHost))
{
local_1.Dispose();
}
}
值得注意的是所有这些push
, pop
anddup
语句和 lambda 缓存条件。
据我所知,不可能从二进制(没有源代码)构建 AST,因为 AST 本身是由解析器生成的,作为源代码编译过程的一部分。Mono.Cecil 无济于事,因为您只能使用它们修改操作码/元数据,而不能分析程序集。
但由于它是 .NET,您可以在 ildasm 的帮助下从 dll 中转储 IL 代码。然后,您可以将生成的源传递给任何连接了 CIL 字典的解析器,并从解析器获取 AST。问题是,据我所知,解析器只有一种公开可用的 CIL 语法,所以你真的别无选择。而且 ECMA-355 足够大,所以编写自己的语法是个坏主意。所以我只能建议你一种解决方案:
- 将程序集传递给 ildasm.exe 以获取 CIL。
- 然后使用此CIL 语法将 CIL 传递给ANTLR v3解析器(注意它有点过时 - 语法创建于 2004 年,最新的 CIL 规范是 2006 年,但 CIL 并没有真正改变太多)
- 之后您可以自由访问由 ANTLR 生成的 AST
请注意,您将需要 ANTLR v3 而不是 v4,因为语法是为第三版编写的,如果不熟悉 ANTLR 语法,几乎不可能将其移植到 v4。
您也可以尝试在 github(CoreCLR 的一部分)上查看新的 Microsoft ryujit编译器源 - 我不确定它是否有帮助,但理论上它必须包含 CIL 语法和解析器实现,因为它适用于 CIL 代码。但是它是用 CPP 编写的,由于它处于积极的开发阶段,因此具有庞大的代码库并且缺乏文档,因此使用 ANTLR 可能更容易卡住。
如果您将 .net 二进制文件视为字节流,您应该能够很好地“解析”它。
您只需编写一个其标记本质上是字节的语法。您当然可以使用几乎任何一组词法分析器/解析器工具来构建经典词法分析器/解析器,方法是将词法分析器定义为将单个字节读取为标记。
然后,您可以使用标准 AST 构建机器为解析引擎构建 AST(对于 YACC,您自己构建,使用 ANTLR4 自动构建)。
当然,您会发现“解析”是不够的。如果您要认真分析相应的代码,您仍然需要构建符号表,并进行控制和数据流分析。请参阅我关于 LifeAfterParsing 的文章。
您可能还必须考虑为实际生成 CIL 代码的特定编程语言提供关键运行时工具的“杰出”函数。这些将使您的分析器依赖于语言。是的,您仍然可以分享适用于通用 CIL 的分析部分。