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

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

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

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

Пока что я нашел и нейтрализовал эти вещи:

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

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

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

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

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

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

Итак, вот вопрос из трех частей (резюмированный как «что теперь?»):

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

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

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

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

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

ОБНОВЛЕНИЕ: мы недавно открыли источник этого инструмента на GitHub. См. Раздел Сравнить в документации.


person Eugene    schedule 25.07.2009    source источник
comment
(+1) Спасибо за --compare вариант peparser. Но эта часть PDB ... file path, похоже, работает не во всех случаях. Если я перестрою проект VC ++ 2015 после добавления _ 3_ в командную строку компоновщика (что приводит к удалению фактического пути из двоичного образа), затем peparse сообщает его как not equivalent исходной сборке.   -  person dxiv    schedule 09.05.2019
comment
@dxiv можете ли вы открыть ошибку на github с прикрепленными двоичными файлами, если это возможно?   -  person Eugene    schedule 09.05.2019
comment
Готово, спасибо за внимание.   -  person dxiv    schedule 10.05.2019


Ответы (5)


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

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

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

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

ОБНОВЛЕНИЕ: мы недавно открыли источник этого инструмента на GitHub. См. Раздел Сравнить в документации.

person Eugene    schedule 15.05.2012
comment
Вот простой способ обхода временной метки TLB (проверено только на msvs_2015 + MIDL версии 7.00.0555): peparser_with_tlb - person Smalti; 29.06.2017

Стандартизируйте пути сборки

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

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 Service Pack 2

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

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

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

person Slacker    schedule 25.07.2009
comment
К сожалению, исходный путь не хранится в виде обычного текста, и я не смог найти никакой информации о том, что на самом деле затронуто этим, и могу ли я спокойно его игнорировать. (в конце концов, ложноотрицательные результаты намного хуже, чем положительные). - person Eugene; 25.07.2009

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

person Ori Pessach    schedule 25.07.2009
comment
Не пробовал, нет. Даже если это сработает, это не может быть надежно автоматизировано ... Хотя это может пролить свет на то, что именно отличается. Я попробую, спасибо. - person Eugene; 25.07.2009
comment
Я уверен, что вы можете автоматизировать дизассемблирование программного обеспечения. Запускать из командной строки ... Это может быть хорошим решением в зависимости от того, какие препятствия вы встретите при выводе дизассемблера;) - person Kieveli; 25.07.2009

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

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

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

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

person hythlodayr    schedule 25.07.2009
comment
Я бы предложил то же самое, но с использованием символических ссылок в общем каталоге, таком как C: \ BUILD \ XXX. - person Preet Sangha; 25.07.2009
comment
Прит, а как в Windows создать символическую ссылку? - person Rob Kennedy; 25.07.2009
comment
NTFS поддерживает точки соединения. Но вам нужно скачать утилиту ИЛИ быть на Vista +. Windows технически обрабатывает точки соединения по-разному, поэтому, как и в subst.exe, это может работать, а может и не работать. - person hythlodayr; 25.07.2009
comment
Соединения будут работать, за исключением требования одного и того же пути, указывающего на разные места для 2 процессов, работающих одновременно. Думаю, они упростили бы уборку ... - person Eugene; 25.07.2009
comment
Я не заметил, что ваше требование при этом спрятано в конце там. Почему бы не упростить задачу, построив последовательно? - person hythlodayr; 25.07.2009
comment
Каждую ночь нужно строить множество вещей, каждое из которых может занять несколько часов (в том числе и на неплохой машине). Также много сборок в течение дня. (и это тоже чистые сборки, а не CC) - person Eugene; 25.07.2009
comment
Разве нет билдов, которые можно было бы безопасно запускать одновременно; т.е. нечего различать? В любом случае, если вы не возражаете против сложности (а вам следует ...), вы можете установить точку соединения на более детальном этапе. Скажем, на этапе проекта, а не в начале сборки. Пока проект сохраняет одно и то же имя ссылки, целевые и объектные файлы каждого проекта будут последовательно использовать один и тот же путь. Но вам понадобится какой-то мьютекс, чтобы два процесса сборки не пытались построить один и тот же проект одновременно. - person hythlodayr; 25.07.2009
comment
Это не выглядело так ясно. Пока проект последовательно создает одно и то же имя ссылки, двоичные файлы для этого проекта должны ссылаться на один и тот же путь. Вам просто нужно справиться с ситуацией, когда несколько процессов сборки пытаются создать один и тот же проект в одно и то же время ... - person hythlodayr; 25.07.2009

Я нашел дополнительный инструмент, который поможет решить эту проблему: Ducible на GitHub

Это инструмент, позволяющий воспроизводить сборки переносимых исполняемых файлов (PE) и PDB.

Он изменяет предоставленные файлы * .exe, * .dll и * .pdb на месте, заменяя недетерминированные данные детерминированными данными.

person Jason    schedule 20.04.2021