Инструментарий кодов C / C ++ с использованием LLVM - PullRequest
17 голосов
/ 23 сентября 2011

Я только что прочитал о проекте LLVM и о том, что его можно использовать для статического анализа кодов C / C ++ с использованием анализатора Clang, входящего в состав LLVM. Я хотел знать, возможно ли извлечь все обращения к памяти (переменные, локальные и глобальные) в исходном коде, используя LLVM.

Есть ли в LLVM встроенная библиотека, которую я мог бы использовать для извлечения этой информации. Если нет, пожалуйста, предложите мне, как написать функции, чтобы сделать то же самое. (Существующий исходный код, ссылка, учебник, пример ...) Что я подумал, так это то, что я сначала преобразовал бы исходный код в LLVM bc, а затем использовал его для анализа, но не знаю точно, как это сделать.


Я пытался выяснить, какой IR следует использовать для своих целей (абстрактное синтаксическое дерево Clang (AST) или промежуточное представление SSA LLVM (IR).), Но на самом деле не смог понять, какой использовать. Вот что я пытаюсь сделать. Для любой программы на C / C ++ (например, приведенной ниже) я пытаюсь вставить вызовы какой-либо функции до и после каждой инструкции, которая читает / записывает в / из памяти. Например, рассмотрим приведенную ниже программу на C ++ (Account.cpp)

#include <stdio.h>

class Account {
  int balance;

public:
  Account(int b) {
    balance = b;
  }

  int read() {
    int r;
    r = balance;
    return r;
  }

  void deposit(int n) {
    balance = balance + n;
  }

  void withdraw(int n) {
    int r = read();
    balance = r - n;
  }
};

int main () {
  Account* a = new Account(10);
  a->deposit(1);
  a->withdraw(2);
  delete a;
}

Итак, после инструментария моя программа должна выглядеть так:

#include <stdio.h>

class Account {
  int balance;

public:
  Account(int b) {
    balance = b;
  }

  int read() {
    int r;
    foo();
    r = balance;
    foo();
    return r;
  }

  void deposit(int n) {
    foo();
    balance = balance + n;
    foo();
  }

  void withdraw(int n) {
    foo();
    int r = read();
    foo();
    foo();
    balance = r - n;
    foo();
  }
};

int main () {
  Account* a = new Account(10);
  a->deposit(1);
  a->withdraw(2);
  delete a;
}

где foo () может быть любой функцией, такой как получение текущего системного времени или увеличение счетчика ... и так далее. Я понимаю, что для вставки функции, подобной описанной выше, мне сначала нужно получить IR, а затем запустить инструментарий IR, который будет вставлять такие вызовы в IR, но я не знаю, как этого добиться. Пожалуйста, предложите мне примеры того, как это сделать.

Также я понимаю, что, как только я скомпилирую программу в IR, будет очень трудно получить отображение 1: 1 между моей исходной программой и инструментированным IR. Итак, возможно ли отразить изменения, внесенные в ИК (из-за контрольно-измерительных приборов) в исходную программу.

Чтобы начать работу с проходом LLVM и как сделать его самостоятельно, я рассмотрел пример прохода, который добавляет проверки во время выполнения для загрузки и сохранения IR LLVM, проход инструментария загрузки / сохранения SAFECode (http://llvm.org/viewvc/llvm-project/safecode/trunk/include/safecode/LoadStoreChecks.h?view=markup и http://llvm.org/viewvc/llvm-project/safecode/trunk/lib/InsertPoolChecks/LoadStoreChecks.cpp?view=markup). Но я не мог понять, как запустить этот проход. Пожалуйста, дайте мне шаги, как запустить этот проход в какой-то программе, например, в Account.cpp выше.

Ответы [ 2 ]

29 голосов
/ 26 сентября 2011

Прежде всего, вы должны решить, хотите ли вы работать с Clang или LLVM. Они оба работают с очень разными структурами данных, которые имеют свои преимущества и недостатки.

Из вашего скудного описания вашей проблемы я рекомендую перейти на этапы оптимизации в LLVM. Работа с IR значительно упростит санитарную обработку, анализ и внедрение кода, потому что это то, для чего он предназначен. Недостатком является то, что ваш проект будет зависеть от LLVM, что может или не может быть проблемой для вас. Вы можете вывести результат, используя бэкэнд C, но он не может быть использован человеком.

Другим важным недостатком при работе с проходами оптимизации является то, что вы также теряете все символы из исходного исходного кода. Даже если класс Value (подробнее об этом позже) имеет метод getName, вы не должны никогда полагаться на него, чтобы он содержал что-либо значимое. Он предназначен для отладки ваших пропусков и ничего больше.

Вы также должны иметь базовые знания о компиляторах. Например, необходимо знать о базовых блоках и статической форме одиночного назначения . К счастью, это не очень сложная концепция для изучения или понимания (статьи в Википедии должны быть адекватными).

Прежде чем приступить к написанию кода, сначала нужно немного почитать, поэтому вот несколько ссылок, с которых можно начать:

  • Обзор архитектуры : Краткий обзор архитектуры LLVM. Даст вам хорошее представление о том, с чем вы работаете, и подходит ли вам LLVM.

  • Глава документации : Здесь вы найдете все ссылки ниже и многое другое. Обратитесь к этому, если я что-то пропустил.

  • ИК-ссылка LLVM : Это полное описание IR LLVM, которым вы будете манипулировать. Язык относительно простой, поэтому его не так много для изучения.

  • Руководство программиста : краткий обзор основных вещей, которые необходимо знать при работе с LLVM.

  • Письменные проходы : Все, что вам нужно знать для написания проходов преобразования или анализа.

  • LLVM Passes : Полный список всех пропусков, предоставленных LLVM, которые вы можете и должны использовать. Это действительно может помочь очистить код и упростить анализ. Например, при работе с петлями пропуска lcssa, simplify-loop и indvar спасут вам жизнь.

  • Дерево наследования значений : Это страница Doxygen для класса Value. Важным битом здесь является дерево наследования, которому вы можете следовать, чтобы получить документацию для всех инструкций, определенных на справочной странице IR. Просто игнорируйте безбожного чудовища, которое они называют диаграммой сотрудничества.

  • Дерево наследования типов : То же, что и выше, но для типов.

Как только вы все это поймете, тогда это торт. Чтобы найти доступ к памяти? Найдите инструкции store и load. К инструменту? Просто создайте то, что вам нужно, используя соответствующий подкласс класса Value, и вставьте его до или после инструкции сохранения и загрузки. Поскольку ваш вопрос слишком широкий, я не могу вам больше помочь. (см. Исправление ниже)

Кстати, я должен был сделать нечто подобное несколько недель назад. Примерно через 2-3 недели я смог узнать все, что мне нужно о LLVM, создать проход анализа, чтобы найти доступ к памяти (и более) в цикле, и снабдить их созданным мной проходом преобразования. Там не было никаких причудливых алгоритмов (кроме тех, которые предоставлены LLVM), и все было довольно просто. Мораль этой истории заключается в том, что LLVM прост в освоении и работе с ним.


Исправление : я допустил ошибку, когда сказал, что все, что вам нужно сделать, - это найти load и store инструкции.

load и storeинструкция будет давать доступ только к куче, используя указатели.Для того, чтобы получить все обращения к памяти, вы также должны посмотреть на значения, которые могут представлять ячейку памяти в стеке.Записывается ли значение в стек или сохраняется в регистре, определяется на этапе выделения регистров, который происходит на этапе оптимизации бэкэнда.Это означает, что она зависит от платформы и на нее не следует полагаться.

Теперь, если вы не предоставите больше информации о том, какой тип доступа к памяти вы ищете, в каком контексте и как вы намереваетесь его использовать, я могуэто не поможет тебе намного больше.

3 голосов
/ 26 сентября 2011

Поскольку по прошествии двух дней ответа на ваш вопрос не будет, я предложу его, который немного, но не совсем не по теме.

В качестве альтернативы LLVM для статического анализа программ на C вы можете написать плагин Frama-C .

Существующий плагин, который вычисляет список входных данных для функции C, должен посещать каждое значение l в теле функции. Это реализовано в файле src / inout / input.ml. Реализация короткая (сложность в других плагинах, которые предоставляют свои результаты этому, например, в разрешении указателей) и может использоваться в качестве каркаса для вашего собственного плагина.

Посетитель для Абстрактного синтаксического дерева обеспечивается платформой. Чтобы сделать что-то особенное для lvalues, вы просто определяете соответствующий метод. Сердцем плагина входов является определение метода:

method vlval lv = ...

Вот пример того, что делает плагин входов:

int a, b, c, d, *p;

main(){
  p = &a;
  b = c + *p;
}

Входные данные main() вычисляются следующим образом:

$ frama-c -input t.c
...
[inout] Inputs for function main:
          a; c; p;

Более подробную информацию о написании плагинов Frama-C в целом можно найти здесь .

...