Как перейти к созданию программного обеспечения, которое отслеживает вызовы функций другого программного обеспечения?

17

Я хочу создать программное обеспечение, которое отслеживает вызовы функций другого программного обеспечения.
Он должен быть похож на то, какая функция вызывается, откуда «во время выполнения».

Пример:

int main ()
{
    a ();
    b ();
    c ();
    return 0;
}

a () 
{
   d ();
   e ();
}

b ()
{
   e ();
   f ();
}

Предполагая, что я хочу записать это в настоящее время в C для C, как мне отслеживать вызовы во время выполнения (начиная с первого вызова) - с потоками и без потоков?

подсказки?

    
задан Aquarius_Girl 19.03.2013 в 09:21
источник
  • Это похоже на потенциально зависимый от платформы вопрос. Вы пытаетесь сделать это в Linux, OSX или Windows? –  merlin2011 19.03.2013 в 09:23
  • Как насчет этого? –  Alexey Frunze 19.03.2013 в 09:24
  • @ merlin2011 Ну, «в настоящее время» на Linux. –  Aquarius_Girl 19.03.2013 в 09:24
  • Использование GCC? -строчные функции - ваш друг. –  -finstrument-functions 19.03.2013 в 09:24
  • @ H2CO3, эта ссылка не работает. –  Aquarius_Girl 19.03.2013 в 09:25
Показать остальные комментарии

5 ответов

7

Это очень зависит от платформы. Вам нужно сделать больше или меньше того, что делает отладчик.

Как я подхожу к этому (я действительно реализовал все это один раз, когда мне пришлось отлаживать инструменты, необходимые для сборки gdb для новой архитектуры):

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

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

Далее вам нужно выяснить крючки отладки в динамическом загрузчике, чтобы вы знали, когда загружаются разделяемые библиотеки. Вам понадобятся также таблицы символов библиотек.

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

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

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

    
ответ дан Art 19.03.2013 в 10:13
  • Это длинный, довольно конкретный пример, который неявно говорит: «Инструмент объектного кода». –  Ira Baxter 21.06.2014 в 16:12
8

Вы можете сделать это до времени выполнения статическим анализом или во время выполнения динамическим анализом .

У вас есть только два способа сделать это во время выполнения, и оба они составляют код приложения:

Инструмент исходный код

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

Вы можете сделать это полностью автоматизированным способом, используя программу Transformation System (PTS) , которая может анализировать источник код, чтобы сделать АСТ, изменить АСТ, а затем восстановить источник. Часто PTS позволяет вам записывать изменения в качестве шаблонов исходного уровня формы, «если вы видите this , замените его на , который ". (Нет, регулярные выражения не могут этого сделать).

Расширенный пример того, как это можно найти в моей технической статье, Отраслевое покрытие для произвольных языков . В документе показано, что такое PTS, а затем показывает типичные преобразования инструментов.

Как сделать это «правильно» для языка, такого как C, может быть сложно, потому что вам нужно как основа, полный C-парсер, который является основным техническим достижением в своем собственном праве. Лучше всего получить такого зверя в заполненной форме (некоторые из PTS имеют это, считают Concinnelle или DMS), или вы никогда не сможете обойти часть инструментария. (Еще один ответ указывает на то, что GCC имеет большую часть встроенной функциональности инструментария).

Разумно, стоимость исполнения дополнительного инструментария довольно скромная (10-30%). Эта концепция была использована для реализации инструментов тестирования и временных профилей, которые работают, отслеживая динамический набор вызовов, который, как представляется, именно то, что вы хотите.

Инструмент объектного кода

Измените механизм выполнения, который запускает код, чтобы отслеживать нужные детали. Учитывая, что вы хотите сделать это с помощью программ C, механизм выполнения, по сути, является базовым набором команд.

Поскольку вы не можете разумно изменить процессорный чип, вам придется либо:

  • изменить объектный код (используя эквиваленты объектных кодов программных преобразований, см. предыдущий параграф для понятий или просмотреть инструменты, такие как PIN и Valgrind, для деталей реализации),

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

Реализация этих правил для реальных наборов команд усложняется самими наборами команд (набор инструкций Intel X86 огромен), форматы объектных файлов и т. д. Не ожидайте, что этот путь будет проще, чем исходный путь инструментария; просто ожидайте, что у него будет другой набор проблем.

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

Еще одна сложность с инструментами объектного кода заключается в том, что трудно определить, была ли выполнена функция foo, которая была встроена, («покрыта»). У исходной инструментальной схемы эта проблема отсутствует, потому что foo получает инструментальную до , ее компилируют / вставляют, поэтому инструментарий также становится встроенным.

Темы

Обработка потоков - это ортогональная проблема. В каждом месте, где вы записываете факт X-calls-Y , вы просто присоединяете идентификатор потока (или его эквивалент) T, полученный из ОС, для создания X-calls-Y- в-Т . Из вашего вопроса неясно, почему вы хотите это сделать.

Статический анализ

Вы можете решить пропустить все проблемы реализации runtime и построить / получить статический анализатор, который может создать граф вызовов. Получение этих данных для C возможно с помощью Clang или DMS. Построить его сами, вероятно, сложно; теперь вам нужен полный анализатор C, полный поток данных и анализ точек и построение графика вызовов. Детали не будут вписываться в этот параграф.

    
ответ дан Ira Baxter 20.06.2014 в 11:27
4

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

В Linux попробуйте взглянуть на то, как Pin работает.

В Windows просмотрите Обход .     

ответ дан merlin2011 19.03.2013 в 09:29
3

Я бы использовал функциональность инструментария, встроенную в GCC , чтобы выполнить для вас инструментарий исходного кода.

Вот ссылка для учебника: Ссылка

В основном вы говорите программе для вызова контрольной функции перед каждым вызовом функции и даете два адреса в качестве параметров. Вы сами предоставляете эту инструментальную функцию, и ее вызывают с адресным адресом и адресом. Из-адрес указывает, откуда вызывается функция в адресе.

Затем вам необходимо перевести адреса в имена функций (которые могут выполняться с учетом номеров строк в файле исходного кода). Используя инструмент GNU binutils addr2line , вы можете преобразовать значения to- и from-address в строки, учитывая, что двоичный файл был скомпилирован с информацией об отладке ( gcc -g ... ).

Очень быстро начать работу с инструментами, используя этот подход.

EDIT:

Если у вас нет источника и только двоичные файлы под рукой, вы можете попробовать разобрать или разбор бинарных файлов на сборку. Таким образом, вы можете разделить программу на логические блоки, которые перескакивают в / из. Названия функций не могут быть легко восстановлены без исходного кода, поэтому вы можете рискнуть только отслеживать прыжки в логических блоках, в которые вы ранее разделили программу. Это то, что несколько отладчиков делают AFAIK. Я думаю, OllyDbg (только для Windows) и ответ дан Morten Jensen 20.06.2014 в 11:55