2013-02-08 3 views
3

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

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

Он прошел через несколько этапов, поскольку я пытался ускорить его. Я начал просто размещать INT3 в начале каждого базового блока, прикрепляя его как отладчик и регистрируя хиты. Затем я попытался повысить производительность за счет исправления в счетчике любого блока размером более 5 байт (размер JMP REL32). Я написал небольшую заглушку («mov [blah], 1/jmp backToTheBasicBlockWeCameFrom») в пространстве памяти процесса и исправил JMP. Это значительно ускоряет работу, поскольку нет исключения и нет отладки, но я хотел бы ускорить работу.

Я имею в виду одно из следующих действий:

1) Предварительно инструмент целевой бинарной с моими исправленных счетчики (на данный момент я делаю это во время выполнения). Я мог бы создать новый раздел в PE, бросить в него свои счетчики, заплатить все нужные мне крючки, а затем просто прочитать данные из того же раздела с моим отладчиком после каждого выполнения. Это даст мне некоторую скорость (около 16%, по моей оценке), но есть еще те досадные INT3, которые мне нужно иметь в меньших блоках, которые действительно будут калечить производительность.

2) Приведите двоичный код в свой собственный UnhandledExceptionFilter и обработайте его собственные int3 в сочетании с вышесказанным. Это означало бы, что нет процесса перехода от debuggee к моему инструменту покрытия на каждом int3, но все равно будет возникать исключение точки останова и последующий переход ядра. Правильно ли я думаю, что это не принесло бы мне большой производительности?

3) Попытайтесь сделать что-нибудь умное с помощью инструкций профилирования отраслевой аппаратуры Intel. Это звучит довольно устрашающе, но я не понимаю, как бы я это сделал - возможно ли это в приложении windows usermode? Я мог бы зайти так далеко, чтобы написать драйвер режима ядра, если он довольно прост, но я не кодер ядра (я немного пошатнулся) и, вероятно, вызвал бы у меня много головных болей. Существуют ли какие-либо другие проекты, использующие этот подход? Я вижу, что ядро ​​Linux имеет контроль над самим ядром, что заставляет меня думать, что мониторинг конкретного приложения usermode будет затруднен.

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

5) Что-то еще. Я работаю в VMWare в Windows XP, на довольно старом оборудовании (Pentium 4-ish) - есть ли что-то, что я пропустил, или какие-либо выводы, которые я должен прочитать? Могу ли я получить JMP REL32 до менее 5 байтов (и уловить меньшие блоки без необходимости в int3)?

Спасибо.

+0

Голосовать против закрытия как «неконструктивный»? Меня поражает, как многие из них проголосовали за закрытие совершенно законных вопросов, с вполне разумными ответами. Вы, ребята, так плохо. –

+1

Мне любопытно. Если вы делаете это без источника или символов, как вы изучаете результаты? Вы просто вычисляете процентное значение? Если процент низкий, как вы выясните, в каких разделах ваши тесты отсутствуют? –

+0

Я на самом деле делаю чёрный ящик, чтобы найти дыры в безопасности. Идея состоит в том, что я немного мутирую входной файл, наблюдаю за любыми новыми блоками, которые открываются, а затем «спускаюсь», чтобы мутировать этот ввод больше, если он открывает блоки, которые мы не видели раньше.(Если у вас есть час или около того, чтобы сэкономить, я обнаружил, что презентация Трэвиса Орманди «делает программное обеспечение глупее» увлекательным - http://www.youtube.com/watch?v=YqZRuvdbR64) – randomdude

ответ

1

Если вы настаиваете на использовании двоичных файлов инструментов, в значительной степени ваш самый быстрый охват - это 5-байтовый прыжок с отскоком. (Вы используете стандартное заземление для инструментов для двоичных инструментов).

Решение INT 3 всегда будет включать в себя ловушку. Да, вы могли бы обрабатывать ловушку в своем пространстве вместо пространства отладчика, и это ускорит ее, но она никогда не будет близка к конкурентоспособной по сравнению с перепрыгиванием/назад. В любом случае, вам может понадобиться это резервное копирование, если функция, которую вы используете, короче 5 байт (например,, "inc eax/ret"), потому что тогда у вас нет 5 байтов, которые вы можете исправить.

Что вы можете сделать, чтобы немного оптимизировать ситуацию, это изучить исправленный код. Без такой проверки, с оригинальным кодом:

  instrn 1 
     instrn 2 
     instrn N 
    next: 

заплатах, в общем выглядеть следующим образом:

  jmp patch 
     xxx 
    next: 

должен, как правило, патч:

patch: pushf 
      inc count 
      popf 
      instrn1 
      instrn2 
      instrnN 
      jmp back 

Если все, что вы хотите, охват, вам не нужно увеличивать и не нужно сохранять флаги:

patch: mov byte ptr covered,1 
      instrn1 
      instrn2 
      instrnN 
      jmp back 

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

Если вы настаиваете на подсчете, вы можете проанализировать instrn1/2/N, чтобы посмотреть, не заботятся ли они о флагах, в которые «inc» дурачит, и только pushf/popf, если это необходимо, или вы можете вставить приращение между двумя инструкции в патче, которые не заботятся. Вы должны анализировать их до некоторой степени, чтобы справляться с такими осложнениями, как instn, ret в любом случае; вы можете создать лучший патч (например, не «jmp назад»).

Вы можете обнаружить, что с помощью счета добавления, 1 быстрее, чем инка рассчитывать, поскольку это позволяет избежать частичных обновлений состояния кода и последующие блокировки трубопровода. Это немного повлияет на ваш cc-impact-analysis, поскольку inc не устанавливает бит переноса, а добавляет.

Другая возможность - выборка ПК. Не используйте код вообще; просто прерывайте поток периодически и принимайте примерное значение ПК. Если вы знаете, где находятся базовые блоки, образец ПК в любом месте базового блока свидетельствует о том, что весь блок был выполнен. Это не обязательно даст точные данные о покрытии (вы можете пропустить критические значения ПК), но накладные расходы довольно низки.

Если вы готовы запланировать код источника, вы можете сделать лучше: просто вставьте «cover [i] = true;» в начале i-го базового блока, и пусть компилятор позаботится обо всех различных оптимизациях. Никаких исправлений не требуется. По-настоящему классная часть этого заключается в том, что если у вас есть базовые блоки внутри вложенных циклов, и вы вставляете исходные пробники, подобные этому, компилятор заметит, что назначения зонда являются идемпотентными относительно цикла и вытаскивают зонд из цикла , Виола, нулевой зонд над головой внутри петли. Что еще вам нужно?

+0

Спасибо за указатели (heeh). В настоящий момент я использую «mybte ptr, 1» в своих заглушках, но я вообще не думал о выравнивании по строке кэша. – randomdude

+0

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

Смежные вопросы