2014-11-21 4 views
5

Я бы хотел проанализировать .Net сборки, которые не зависят от языка C#, VB.NET или что-то еще.
Я знаю Roslyn и NRefactory, но они только работают на уровне исходного кода C#?
Существует также проект «Common Compiler Infrastructure: Code Model and AST API» на CodePlex, который утверждает, что «поддерживает иерархическую объектную модель, которая представляет кодовые блоки в независимой от языка структурированной форме», которые звучат именно для того, что я ищу.
Однако я не могу найти полезную документацию или код, который действительно делает это.
Любой совет, как это сделать?
Может ли Mono.Cecil что-то делать?Получите AST от .Net сборки без исходного кода (код IL)

ответ

0

Насколько я знаю, невозможно построить АСТ из двоичного (без источников), поскольку сам АСТ, созданный парсером, является частью процесса компиляции из источников. Mono.Cecil не поможет, потому что вы можете изменять только коды операций/метаданных, а не анализировать сборку.

Но так как это .NET, вы можете сбрасывать IL-код из dll с помощью ildasm. Затем вы можете передать сгенерированные источники в любой парсер с помощью словаря CIL и получить AST от парсера. Проблема в том, что, насколько я знаю, существует только одна общедоступная грамматика CIL для парсера, поэтому у вас действительно нет выбора. И ECMA-355 достаточно велик, поэтому неплохо писать собственную грамматику. Поэтому я могу предложить вам только одно решение:

  1. Пройдите сборку до ildasm.exe, чтобы получить CIL.
  2. Затем передайте КСС в ANTLR v3 парсер с this CIL грамматикой проволоки вверх (обратите внимание, что это немного устарело - грамматика создана в 2004 году и последняя спецификация CIL является 2006, но КСС не меняет на много)
  3. После этого вам может свободно доступ AST порождена ANTLR

Заметим, что вам нужно будет ANTLR не v3 v4, так как грамматика написана для 3-й версии, и это вряд ли возможно портирование на v4 без хорошего знания синтаксиса ANTLR.

Также вы можете попытаться взглянуть на новые Microsoft ryujit источников компилятора на GitHub (часть CoreCLR) - я не уверен, что это помогает, но и в теории должны содержит CIL грамматики и синтаксического анализа реализации, так как он работает с Код CIL. Но это написано в CPP, имеет огромную базу кода и недостаток документации, так как она находится в стадии активного развития, поэтому ее может быть проще застрять с ANTLR.

+0

Если вы хотите получить IL, этот подход слишком запутан. Использование Cecil будет намного проще. – svick

0

Если вы обрабатываете двоичный файл .net как поток байтов, вы должны иметь возможность «разбирать» его просто отлично.

Вы просто пишете грамматику, чьи жетоны суть по существу байты. Вы можете, конечно, создать классический лексер/парсер практически с любым набором инструментов lexer/parser, указав lexer, чтобы читать одиночные байты как токены.

Вы можете затем построить AST с использованием стандартного строительного оборудования AST для синтаксического анализатора (самостоятельно для YACC, автоматически с ANTLR4).

Что вы обнаружите, конечно, в том, что «разбора» недостаточно; вам все равно придется создавать таблицы символов и выполнять анализ управления и потоков данных, если вы собираетесь серьезно проанализировать соответствующий код. См. Мое эссе о LifeAfterParsing.

Вам также, вероятно, придется учитывать «отличные» функции, которые предоставляют ключевые средства выполнения для конкретных языков программирования, которые фактически генерируют код CIL. И это сделает ваши анализаторы зависимыми от языка. Да, вы все равно можете поделиться частью анализа, который работает на универсальном CIL.

+0

Формат двоичного файла .NET не квалифицируется как контекстно-свободный (или контекстно-зависимый, если на то пошло), поэтому ни YACC, ни ANTLR не смогут создать для него парсер. –

+0

Где это нарушает c-free или c-чувствительный? –

+0

1) Расположение различных структур в модуле задается с помощью RVA (относительный виртуальный адрес), для нахождения смещения внутри файла требуется сопоставление через таблицу разделов. 2) Число строк для каждой таблицы появляется перед любым из них, эти подсчеты могут влиять на определенные ширины столбцов. 3) Большинство DLL, которые я изучил, помещают метаданные после тел функции (хотя это не требуется), поэтому, если вы читаете ее только в прямом режиме (например, с помощью сгенерированного парсера), вы не будете знать, когда вы столкнулись с телом метода до тех пор, пока не пройдете его. –

2

Вы можете сделать это, и есть один (хотя и крошечный) example of this в источнике ILSpy.

var assembly = AssemblyDefinition.ReadAssembly("path/to/assembly.dll"); 
var astBuilder = new AstBuilder(new DecompilerContext(assembly.MainModule)); 
decompiler.AddAssembly(assembly); 
astBuilder.SyntaxTree... 
1

Кодекс Модель CCI находится где-то между дизассемблером IL и полным C# декомпилятором: это дает код некоторой структуре (например, if операторам и выражение), но он также содержит некоторые операции низкого стека уровня, как push и 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 и dup заявления и условие лямбда-кэширования.