Подтвердить что ты не робот

Детерминированные сборки под Windows

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

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

MSVC в тандеме с форматом PE, естественно, делает это очень трудно.

До сих пор я нашел и нейтрализовал эти вещи:

  • Временная метка PE и контрольная сумма
  • Запись каталога цифровой подписи
  • Отметка времени отладки
  • подпись, возраст и путь к файлу PDB
  • Временная метка ресурсов
  • Все версии файлов/продуктов в ресурсе VS_VERSION_INFO
  • Раздел цифровой подписи

Я анализирую PE, находит смещения и размеры для всех этих вещей и игнорирует байтовые диапазоны при сравнении двоичных файлов. Работает как шарм (ну, для нескольких тестов я его запустил). Я могу сказать, что подписанный исполняемый файл с версией 1.0.2.0, построенный на Win Server 2008, равен unsigned one, версии 10.6.6.6, построенный на моем блоке разработчика Win XP, если версия компилятора и все источники и заголовки одинаковы. Это похоже на VC 7.1 - 9.0. (Для релизов)

С одной оговоркой.

Абсолютные пути для обеих строчек должны быть одинаковыми должны иметь одинаковую длину.

cl.exe преобразует относительные пути в абсолютные и помещает их в объекты вместе с флагами компилятора и так далее. Это оказывает непропорциональное воздействие на целые двоичные файлы. Изменение одного символа в пути приведет к тому, что один байт изменится здесь и там несколько раз на весь .text-раздел (хотя многие объекты были связаны, я подозреваю). Изменение длины пути приводит к значительно большим различиям. И в obj файлах, и в связанных двоичных файлах.

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

Итак, вот вопрос из трех частей (суммируется как "что теперь?" ):

  • Должен ли я отказаться от всего проекта и вернуться домой, потому что то, что я пытаюсь сделать, нарушает законы физики и корпоративную политику MS?

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

  • Есть ли способ заставить компилятор использовать относительные пути или обмануть его, думая, что путь не такой, какой он есть?

Причина для последней - красиво раздражающая файловая система Windows. Вы просто никогда не знаете, что при удалении нескольких файлов из источников и объектов и метаданных svn не удастся из-за блокировки файлов изгоев. По крайней мере, создание нового корня всегда преуспевает, пока остается свободное место. Проблема с одновременным запуском нескольких сборок. Запуск пучка виртуальных машин, в то время как решение, довольно тяжелое.

Интересно, есть ли способ настроить виртуальную файловую систему для процесса и его дочерних элементов, чтобы несколько деревьев процесса увидели разные "C:\build" dirs, частные для них только в одно и то же время.. Небольшая виртуализация сортов...

UPDATE:, мы недавно открыли инструмент GitHub. См. Раздел Сравнить в документации.

4b9b3361

Ответ 1

Я решил это до некоторой степени.

В настоящее время у нас есть система сборки, которая гарантирует, что все новые сборки находятся на пути постоянной длины (builds/001, builds/002 и т.д.), что позволяет избежать сдвигов в макете PE. После сборки инструмент сравнивает старые и новые двоичные файлы, игнорируя соответствующие поля PE и другие местоположения с известными поверхностными изменениями. Он также выполняет некоторые простые эвристики для обнаружения динамических игнорируемых изменений. Вот полный список вещей, которые нужно игнорировать:

  • Временная метка PE и контрольная сумма
  • Запись каталога цифровой подписи
  • Временная метка таблицы экспорта
  • Отметка времени отладки
  • подпись, возраст и путь к файлу PDB
  • Временная метка ресурсов
  • Все версии файлов/продуктов в ресурсе VS_VERSION_INFO
  • Раздел цифровой подписи
  • Задний трюк MIDL для встроенных библиотек типов (содержит строку timestamp)
  • __ FILE__, __DATE__ и __TIME__, когда они используются как литералы (могут быть широкими или узкими char)

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

UPDATE:, мы недавно открыли инструмент GitHub. См. Раздел Сравнить в документации.

Ответ 2

Стандартизировать пути сборки

Простым решением будет стандартизация ваших путей сборки, поэтому они всегда имеют форму, например:

c:\buildXXXX

Затем, когда вы сравниваете, скажем, build0434, build0398, просто препроцессор двоичного файла, чтобы изменить все вхождения build0434 на build0398. Выберите шаблон, который, как вы знаете, вряд ли появится в вашем фактическом источнике/данных, за исключением тех строк, которые компилятор/компоновщик встраивается в PE.

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

Утилита dumpbin

Другим советом является использование dumpbin.exe (поставляется с MSVC). Используйте dumpbin/all, чтобы сбрасывать все детали двоичного файла в текстовый/шестнадцатеричный дамп. Это может сделать более очевидным, чтобы увидеть, что/где меняется.

Например:

dumpbin /all program1.exe > program1.txt
dumpbin /all program2.exe > program2.txt
windiff program1.txt program2.txt

Или используйте свой любимый инструмент для обработки текста вместо Windiff.

Утилита Bindiff

Вы можете найти инструмент Microsoft bindiff.exe, который можно получить здесь:

Службы поддержки Windows XP с пакетом обновления 2 (SP2)

Он имеет опцию /v, чтобы указать ему игнорировать определенные двоичные поля, такие как метки времени, контрольные суммы и т.д.:

"BinDiff использует специальную процедуру сравнения для исполняемых файлов Win32, которые маскируют из разных полей времени создания времени оба файла при выполнении сравнить. Это позволяет выполнять два исполняемых файла файлы, помеченные как" Near Identical "когда файлы действительно идентичны, за исключением того времени, когда они были построены".

Однако, похоже, что вы уже делаете надмножество того, что делает bindiff.exe.

Ответ 3

Вы пытались разобрать исполняемый файл и сравнить разборку? Это должно удалить много отвлекающих деталей, которые вы упомянули, и сделать удаление других намного проще.

Ответ 4

Есть ли способ заставить компилятор использовать относительные пути или обмануть его, думая, что путь не что это такое?

У вас есть два способа сделать это:

  • Используйте команду subst.exe и сопоставьте букву диска с папкой сборки (это может быть ненадежным).
  • Если файл subst.exe не работает, создайте общие папки для каждой из ваших папок сборки и используйте команду "net use". Это почти наверняка должно работать.

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