СПОСОБ ДИНАМИЧЕСКОЙ ИНСТРУМЕНТАЦИИ
DYNAMIC INSTRUMENTATION TECHNIQUE

Изобретение относится к области вычислительной техники, а именно к способам динамической инструментации, и может быть применено для трассировки вызовов функций с целью анализа приложений различных типов, работающих под контролем операционной системы, в том числе встроенной операционной системы.
Мониторинг является мощным способом анализа поведения приложения, определения результатов работы и исправления ошибок. Поскольку функциональность приложения разделена на функции, для анализа его поведения разработчикам нужно выполнять трассировку вызовов функций. Для этих целей необходимо осуществлять инструментацию приложения. Инструментация может быть статической или динамической.
Статическая инструментация требует изменения исходного кода приложения или бинарного исполняемого файла. После чего нужно перезапустить приложение. Если исходный код приложения изменен, его, кроме того, необходимо перекомпилировать. Данный подход может применяться, когда необходимо добиться малой дополнительной нагрузки на процессор, и разработчик может воспроизвести исходное состояние приложения для анализа в разумных временных рамках.
Динамическая инструментация не требует ни изменения исходного кода приложения, ни его перекомпиляции, ни перезапуска. Она полезна, когда у разработчика нет исходного кода приложения и/или когда воспроизведение исходного состояния приложения для анализа занимает слишком много времени, и/или анализ должен быть выполнен для уже запущенного приложения.
В патенте US 006769117 описаны система и способ динамической инструментации, недостатком которого является ограниченная функциональность, так как он может применяться только для функций ядра.
В заявке US 20080133896 приведен способ трассировки возврата из функций пользовательского приложения, который решает часть задач заявленного способа. В нем для вызова трассирующего обработчика применяют «инструкцию-исключение», размещенную на входе в функцию, в качестве трамплина возврата. Однако в данном способе не уточняют, как различить вызовы обработчика, сгенерированные на входе и на выходе из функции. Кроме того, данный способ не описывает решение для ситуации, когда код инструментации удаляют в процессе выполнения функции.
Известен способ инструментации с помощью модуля Kprobe (Ananth Mavinakayanahalli, Prasanna Panchamukhi, Jim Keniston, Anil Keshavamurthy, Masami Hiramatsu, Probing the guts of Kprobes, Ottawa Linux Symposium, pp.101-114, July 2006), в котором «инструкцию-исключение» используют для вызова трассирующего обработчика. Недостатком данного способа является то, что он позволяет инструментировать только функции ядра.
Система Dyninst (Giridhar Ravipati, Andrew R. Bernat, Nate Rosenblum, Barton P. Miller and Jeffrey K. Hollingsworth, Toward the Deconstruction of Dyninst, Technical Report, Computer Sciences Department, University of Wisconsin, Madison, June, 2007), позволяет генерировать и вставлять куски бинарного кода в выполняемое приложение динамически. В ней используют «инструкцию-прыжок» или «инструкцию-исключение» для вызова вставленного кода. Данную систему можно применять для трассировки приложения путем генерации и вставки в него трассирующего кода. Система имеет следующие недостатки:
а) занимает память в виртуальном адресном пространстве трассируемого процесса для хранения вставленного кода, что может оказывать влияние на поведение системы;
b) основана на функциональности ядра, которая может быть недоступна в некоторых системах;
с) при использовании «инструкции-исключения» в данной системе контролируют выполнение трассируемого процесса из другого процесса (путем обработки системных сигналов), что дополнительно загружает операционную систему;
d) в ней не проверяют, загружен инструментируемый код приложения в память или нет, таким образом, она может изменить обычную последовательность загрузки бинарного кода приложения и может загрузить все приложение после выполнения инструментации.
Наиболее близкой к заявленному изобретению является мощная скриптовая система SystemTap (Proceedings of the Linux Symposium Volume One, pp.261-268, July 2006, Frank Ch. Eigler, RedHat, Problem Solving With Systemtap.), которая позволяет выполнять инструментацию ядра и функций пользовательских приложений динамически. В ней используют «инструкцию-исключение» для вызова трассирующего обработчика. Данная система выбрана в качестве прототипа заявленного изобретения. Система-прототип имеет следующие недостатки:
а) занимает память в виртуальном адресном пространстве
трассируемого процесса для хранения трамплина для возврата из функции пользовательского приложения, что может оказывать влияние на поведение системы;
b) использует функциональность ядра, которая может быть недоступна в некоторых системах;
с) не проверяет, загружен инструментируемый код приложения в память или нет, таким образом, она может изменить обычную последовательность загрузки бинарного кода приложения и может загрузить все приложение после выполнения инструментации.
Предлагаемый способ основан на технологиях Kprobe и «User-space return probes». Он расширяет данные технологии и устраняет их недостатки. Парадигму Kprobe расширяют для инструментации пользовательских приложений и с помощью нее решают проблемы, создаваемые различиями в структурах памяти ядра и пользовательских приложений.
Заявленный способ динамической инструментации обладает следующими преимуществами по сравнению с перечисленными выше аналогами: не имеет ограничений на адрес инструментируемой функции; события собирают в ядре, поэтому можно собрать больше информации в момент генерации события; способ не зависит от опциональной функциональности ядра ptrace/utrace; способ не занимает память в виртуальном адресном пространстве трассируемого процесса; способ не загружает код приложения, который не нужен; исключения, сгенерированные при возврате из функции и входе в нее, могут быть легко различены; код инструментации возврата из функции пользовательского приложения может быть удален в любое время.
Перечисленные преимущества позволяют собирать информацию о приложении во время его выполнения, позволяют создать гибкий интерфейс для мониторинга данных без рекомпиляции, перезагрузки приложения и выполнения других ненужных действий, а также позволяют легко решать большое число задач мониторинга с большой точностью и малой дополнительной нагрузкой на процессор, что дает возможность использовать данный способ в условиях ограниченных ресурсов встроенных систем.
Задачей заявленного изобретения является создание способа динамической инструментации, который создает малую дополнительную нагрузку на процессор и требует меньше памяти, что обеспечивает высокую точность мониторинга и возможность применения способа во встроенных системах, за счет использования модуля ядра с трассирующей функцией-обработчиком «инструкций-исключений», которая выполнена с возможностью инструментирования функций пользовательских приложений, только тогда, когда они реально загружены в память; за счет применения механизма «отложенного удаления кода инструментации» для удаления трассирующего кода инструментации и возврата из функции пользовательского приложения во время выполнения этой функции; а также за счет применения разных «инструкций-исключений» в первой и второй инструкциях соответственно для различения исключений, сгенерированных на входе и выходе из функции.
Поставленная задача решена путем создания способа динамической инструментации пользовательских приложений, в компьютерной системе, по меньшей мере, с одним процессором, выполненной с возможностью взаимодействия с памятью и включающей в себя операционную систему, взаимодействующую с данным процессором, при этом операционная система выполнена с возможностью работы в пространстве ядра и выполнения действий по запросу одной или более пользовательских программ; процессор выполнен с возможностью выполнения процедуры вызова подпрограммы путем сохранения адреса возврата во временное хранилище при вызове подпрограммы; операционная система выполнена с возможностью использования механизма загрузки страниц памяти по запросу, содержащего следующие операции:
- конфигурируют компьютерную систему;
- вставляют первую инструкцию-исключение по адресу из пользовательского адресного пространства, при запросе на трассировку вызова или возврат из подпрограммы одной из пользовательских программ, при этом данный адрес является точкой входа наблюдаемой подпрограммы;
- вставляют вторую инструкцию-исключение по адресу из пользовательского адресного пространства, при запросе трассировать вызов или возврат из подпрограммы одной из пользовательских программ, при этом данный адрес является адресом инструкции, следующей за точкой входа наблюдаемой процедуры;
- запоминают адрес возврата вызванной подпрограммы и заменяют данный адрес возврата адресом второй инструкции-исключения;
- передают контроль ядру от первой инструкции-исключения при вызове данной подпрограммы;
- обработчик инструментации выполняется в ядре операционной системы при вызове пользовательской подпрограммы, и в результате передают контроль по адресу первой инструкции-исключения;
- передают контроль ядру от второй инструкции-исключения при возврате из данной подпрограммы;
- обработчик инструментации выполняется в ядре операционной системы при возврате из пользовательской подпрограммы, и в результате передают контроль по адресу второй инструкции-исключения;
- удаляют первую инструкцию-исключение, при возникновении запроса на остановку наблюдения за вызовами функции и при остановке наблюдения за возвратом из функции;
- удаляют вторую инструкцию-исключение при возникновении запроса на остановку наблюдения за возвратом из функции;
отличающегося тем, что
- добавляют в ядро операционной системы хранилище для данных о пользовательских подпрограммах, которые должны быть инструментированы;
- получают адреса пользовательских подпрограмм для инструментации из таблиц символов бинарных файлов, которые должны быть инструментированы и сохраняют эти адреса в хранилище из предыдущего пункта;
- инструментируют обработчик do_page_fault для наблюдения за возвратом из него; обработчик наблюдения вызывается каждый раз, когда происходит возврат из do_page_fault;
- ищут в хранилище данных подпрограммы, точки входа которых находятся на странице памяти, загруженной обработчиком do_page_fault, когда do_page_fault вызывается для приложения, которое должно быть инструментировано;
- инструментируют обработчик do_exit для наблюдения за его вызовом; обработчик наблюдения вызывается каждый раз, когда происходит вызов do_ exit;
- запрашивают удаление всей установленной инструментации, когда вызывается обработчик наблюдения за вызовом do_exit;
- инструментируют send_signal для наблюдения за его вызовом;
обработчик наблюдения вызывается каждый раз, когда происходит вызов send_signal;
- запрашивают удаление всей установленной инструментации, когда вызывается обработчик наблюдения за вызовом send_signal;
- проверяют, выполняется ли все еще подпрограмма, когда получен запрос на останов наблюдения за возвратом из нее;
- если условие из предыдущего пункта выполняется, откладывают удаление кода инструментации до возврата из подпрограммы и удаляют его, когда происходит возврат из функции;
- не изменяют адрес возврата при вызове подпрограммы с отложенным удалением кода инструментации.
Технический результат заявленного изобретения заключается в уменьшении дополнительной нагрузки на процессор и уменьшении использования памяти для обеспечения высокой точности мониторинга и возможности применения способа во встроенных системах.
Для лучшего понимания заявленного изобретения далее приводится его подробное описание с соответствующими чертежами.
Чтобы трассировать вызовы функции в заявленном способе заменяют первую инструкцию функции специальной инструкцией (трамплином), которая инициирует вызов трассирующей функции-обработчика. При вызове функции ее первая инструкция инициирует вызов обработчика, при этом генерируется сообщение «вход в функцию». Затем, чтобы не нарушить выполнение функции, посредством обработчика восстанавливают первую инструкцию и заменяют вторую трамплином. Когда обработчик вызывают со второй инструкции, он заменяет первую инструкцию трамплином, восстанавливает вторую инструкцию, и выполнение функции продолжается. Чтобы определить момент возврата из функции адрес возврата модифицируют в момент входа в функцию (когда трассирующий обработчик вызывают для первой инструкции). Адрес возврата модифицируют так, чтобы он указывал на специальный трамплин (трамплин возврата). Когда происходит возврат из функции, вызывают трассирующий обработчик и генерируют сообщение «возврат из функции». Затем исходный адрес возврата восстанавливают, и выполнение продолжается с этого адреса.
При инициализации модуля инструментации в ядро устанавливают специальный хук, чтобы определить, когда код приложения будет загружен в память. Если приложение уже запущено (при инициализации модуля инструментации) код инструментации инсталлируют только в функции, которые реально загружены в память. При загрузке в память каждого нового куска кода приложения вызывается хук, и загруженные функции инструментируют при необходимости.
При инструментации возврата из функции пользовательского приложения поверх первых двух инструкций функции записывают две разные «инструкции-исключения». Когда исключение происходит при входе в функцию, адрес возврата изменяют так, чтобы он указывал на вторую инструкцию. После выполнения функции, когда генерируют исключение на второй инструкции, и «инструкция-исключение» соответствует событию «возврат из функции», оригинальный адрес возврата восстанавливают.
Код инструментации возврата из функции пользовательского приложения может быть удален в любое время. Может возникнуть такая ситуация, когда функция вызвана (адрес возврата модифицирован), но еще не выполнена до конца. В этом случае удаление кода инструментации возврата из функции откладывают до возврата из нее, при этом все дальнейшие вызовы функции не изменят адрес возврата и не сгенерируют событие «вход в/возврат из функции».
Фиг.1 - общая схема инструментации приложения согласно изобретению. Элементы, вовлеченные в инструментацию: приложение, подлежащее инструментации, программа трассировки и модуль инструментации. Программа трассировки запрашивает модуль ядра для инструментации некоторых функций приложения и затем получает соответствующие события от модуля инструментации. Модуль инструментации записывает бинарный код в образ приложения в памяти и обрабатывает сгенерированные исключения.
Фиг.2 - схема инсталляции кода инструментации согласно изобретению. На данном чертеже показана концепция инструментации.
Приведены две страницы памяти: загруженная в память (резидентная) и та, которая еще не загружена (нерезидентная). Обе эти страницы имеют функции, которые необходимо инструментировать. Первые две инструкции функции, которая находится на резидентной странице, уже перезаписаны «инструкциями-исключениями». Первые две инструкции функции на другой странице нетронуты, поскольку страница еще не в памяти. При выполнении программа обращается к нерезидентной странице, после чего эту страницу загружают и перезаписывают инструкции в начале второй функции.
Фиг.3 - схема отложенного удаления кода инструментации возврата из функции, согласно изобретению. На данном чертеже показан механизм, который позволяет безопасно удалять код инструментации возврата из функции пользовательского приложения. Приложение вызывает инструментированную функцию (InstrumentedFunctionl()), первая инструкция генерирует процессорное исключение, трассирующий обработчик модифицирует адрес возврата функции и ее выполнение продолжается. Затем программа трассировки запрашивает модуль инструментации, чтобы удалить код инструментации возврата из этой функции, и модуль инструментации запоминает, что код инструментации должен быть удален. При генерации исключения на выходе из функции адрес возврата восстанавливают и инструментацию безопасно удаляют.
Фиг.4 - схема пошагового выполнения способа динамической инструментации согласно изобретению.
Рассмотрим один из вариантов выполнения заявленного изобретения в виде модуля ядра операционной системы (Фиг.1). Модуль может быть загружен в ядро динамически. Для функционирования способа необходимо присутствие в ядре следующих элементов:
- обработчик ошибок декодирования инструкций. Данная функция ядра обрабатывает ошибки декодирования инструкций. В заявленном способе необходимо установить хук в данную функцию ядра, чтобы перехватывать исключения, генерируемые «инструкциями-исключениями» из пользовательских приложений;
- обработчик ошибок обращений к памяти. Данная функция ядра обрабатывает ошибки обращения к памяти, которые возникают, когда приложение пытается получить доступ к странице памяти, которая не находится в памяти. В заявленном способе необходимо установить хук в данную функцию ядра, чтобы отследить, когда код функции загружается в память. Для простоты данный тип функций ядра в этом документе назван do_page_fault;
- обработчик завершения приложения. Данная функция ядра вызывается каждый раз при завершении приложения пользователя. В предложенном способе необходимо установить хук в данную функцию ядра, чтобы определить момент завершения приложения, когда нужно удалить инструментацию. Для простоты данный тип функций ядра в этом документе назван do_exit;
- функция отправления системного сигнала. Данная функция ядра вызывается каждый раз при необходимости отправки сигнала в приложение пользователя. В предложенном способе необходимо установить хук в данную функцию ядра, чтобы определить, когда приложение завершается принудительно с применением системного сигнала, когда нужно удалить инструментацию. Для простоты данный тип опций ядра в этом документе назван send_signal.
Программа трассировки должна передать в модуль данные инструментации. Функции пользовательского приложения инструментируют только тогда, когда они реально загружены в память. Пример данного процесса показан на Фиг.2. Когда приложение пытается выполнить функцию (которая должна быть инструментирована) со страницы, которая еще не в памяти, возникает ошибка, вызывают функцию ядра do_page_fault, и страницу загружают в память. После завершения do_page_fault, вызывают хук-функцию модуля, которая при необходимости инструментирует функции на странице, и затем выполнение приложения продолжают. Данный подход позволяет инструментировать функции прозрачно, без изменения процесса выполнения приложения, а также позволяет не загружать страницы, которые не используются.
При инструментации функции ее первые две инструкции перезаписывают инструкциями-исключениями. Первую инструкцию-исключение используют для определения момента входа в функцию, а вторую - для определения момента возврата из нее. При вызове функции возникает исключение в первой инструкции, модуль ядра перехватывает его и генерирует сообщение «вход в функцию» для программы трассировки. Кроме того, модуль ядра подменяет оригинальный адрес возврата функции на адрес второй инструкции в начале функции. Затем, когда выполнение функции завершается, вторая инструкция генерирует исключение. Модуль ядра перехватывает его и генерирует сообщение «возврат из функции» для программы трассировки. Кроме того, в этот момент модуль ядра восстанавливает оригинальный адрес возврата из функции.
Поскольку код приложения перезаписывают, чтобы не нарушить выполнение приложения, перезаписанный код должен быть выполнен в обычном порядке. Для этого применяют следующий метод пошагового исполнения программы. При возникновении исключения обработчик модуля восстанавливает оригинальную инструкцию по адресу, который вызвал исключение, заменяет следующую инструкцию инструкцией-исключением и продолжает выполнение приложения. При возникновении исключения в следующей инструкции модуль записывает инструкцию-исключение обратно в предыдущее место, восстанавливает начальную инструкцию по адресу текущего исключения и продолжает выполнение приложения.
После выхода из приложения или его принудительного завершения инструментация должна быть удалена. Существует потенциальная проблема с кодом инструментации возврата из функции. Суть данной проблемы заключается в следующем. Как было описано выше, адрес возврата модифицируют при входе в функцию и восстанавливают при генерации исключения при выходе из функции. Таким образом, если код инструментации возврата из функции удаляют до выхода из функции, выполнение программы будет нарушено. Следовательно, если код инструментации возврата из функции необходимо удалить, когда еще не завершено выполнение функции, то его удаление должно быть отложено до завершения функции. Примеры выполнения способа для удаления кода инструментации возврата из функции показаны на Фиг.3.
Хотя указанный выше вариант выполнения изобретения был изложен с целью иллюстрации настоящего изобретения, специалистам ясно, что возможны разные модификации, добавления и замены, не выходящие из объема и смысла настоящего изобретения, раскрытого в прилагаемой формуле изобретения.