- PVSM.RU - https://www.pvsm.ru -
Перевод статьи подготовлен специально для студентов курса «Разработчик С++» [1].
→ Читать первую часть [2]
Если одни и те же исходные файлы скомпилированы в разных папках, иногда информация о папках передается в двоичные файлы. Это может произойти в основном по двум причинам:
__FILE__
.Продолжим наш пример hello world в MacOS, давайте разделим исходники, чтобы мы могли показать влияние расположения на финальные двоичные файлы. Структура проекта будет похожа на приведенную ниже.
.
├── run_build.sh
├── srcA
│ ├── CMakeLists.txt
│ ├── hello_world.cpp
│ ├── hello_world.hpp
│ └── main.cpp
└── srcB
├── CMakeLists.txt
├── hello_world.cpp
├── hello_world.hpp
└── main.cpp
Соберем наши двоичные файлы в режиме отладки.
cd srcA/build
cmake -DCMAKE_BUILD_TYPE=Debug ..
make
cd .. && cd ..
cd srcB/build
cmake -DCMAKE_BUILD_TYPE=Debug ..
make
cd .. && cd ..
md5sum srcA/build/hello
md5sum srcB/build/hello
md5sum srcA/build/CMakeFiles/HelloLib.dir/hello_world.cpp.o
md5sum srcB/build/CMakeFiles/HelloLib.dir/hello_world.cpp.o
md5sum srcA/build/libHelloLib.a
md5sum srcB/build/libHelloLib.a
Мы получим следующие контрольные суммы:
3572a95a8699f71803f3e967f92a5040 srcA/build/hello
7ca693295e62de03a1bba14853efa28c srcB/build/hello
76e0ae7c4ef79ec3be821ccf5752730f srcA/build/CMakeFiles/HelloLib.dir/hello_world.cpp.o
5ef044e6dcb73359f46d48f29f566ae5 srcB/build/CMakeFiles/HelloLib.dir/hello_world.cpp.o
dc941156608b578c91e38f8ecebfef6d srcA/build/libHelloLib.a
1f9697ef23bf70b41b39ef3469845f76 srcB/build/libHelloLib.a
Информация о папке передается из объектных файлов в конечные исполняемые файлы, что делает наши сборки невоспроизводимыми. Мы можем посмотреть различия между двоичными файлами, используя diffoscope, чтобы увидеть, куда встраивается информация о папке.
> diffoscope helloA helloB
--- srcA/build/hello
+++ srcB/build/hello
@@ -1282,20 +1282,20 @@
...
00005070: 5f77 6f72 6c64 5f64 6562 7567 2f73 7263 _world_debug/src
-00005080: 412f 006d 6169 6e2e 6370 7000 2f55 7365 A/.main.cpp./Use
+00005080: 422f 006d 6169 6e2e 6370 7000 2f55 7365 B/.main.cpp./Use
00005090: 7273 2f63 6172 6c6f 732f 446f 6375 6d65 rs/carlos/Docume
000050a0: 6e74 732f 6465 7665 6c6f 7065 722f 7265 nts/developer/re
000050b0: 7072 6f64 7563 6962 6c65 2d62 7569 6c64 producible-build
000050c0: 732f 7361 6e64 626f 782f 6865 6c6c 6f5f s/sandbox/hello_
-000050d0: 776f 726c 645f 6465 6275 672f 7372 6341 world_debug/srcA
+000050d0: 776f 726c 645f 6465 6275 672f 7372 6342 world_debug/srcB
000050e0: 2f62 7569 6c64 2f43 4d61 6b65 4669 6c65 /build/CMakeFile
000050f0: 732f 6865 6c6c 6f2e 6469 722f 6d61 696e s/hello.dir/main
00005100: 2e63 7070 2e6f 005f 6d61 696e 005f 5f5a .cpp.o._main.__Z
...
@@ -1336,15 +1336,15 @@
...
000053c0: 6962 6c65 2d62 7569 6c64 732f 7361 6e64 ible-builds/sand
000053d0: 626f 782f 6865 6c6c 6f5f 776f 726c 645f box/hello_world_
-000053e0: 6465 6275 672f 7372 6341 2f62 7569 6c64 debug/srcA/build
+000053e0: 6465 6275 672f 7372 6342 2f62 7569 6c64 debug/srcB/build
000053f0: 2f6c 6962 4865 6c6c 6f4c 6962 2e61 2868 /libHelloLib.a(h
00005400: 656c 6c6f 5f77 6f72 6c64 2e63 7070 2e6f ello_world.cpp.o
00005410: 2900 5f5f 5a4e 3130 4865 6c6c 6f57 6f72 ).__ZN10HelloWor
...
Опять же, решение будет зависеть от используемого компилятора:
gcc
есть три флага компилятора, чтобы обойти эту проблему:
-fdebug-prefix-map=OLD=NEW
может удалить префиксы каталога из отладочной информации.-fmacro-prefix-map=OLD=NEW
доступен начиная с gcc 8 и решает проблему невоспроизводимости из-за использования макроса __FILE__.-ffile-prefix-map=OLD=NEW
доступен начиная с gcc 8 и является объединением -fdebug-prefix-map и -fmacro-prefix-mapclang
поддерживает -fdebug-prefix-map=OLD=NEW
с версии 3.8 и работает над поддержкой двух других флагов для будущих версий.Лучший способ решить эту проблему — добавить флаги в параметры компилятора. При использовании CMake:
target_compile_options(target PUBLIC "-ffile-prefix-map=${CMAKE_SOURCE_DIR}=.")
Порядок файлов может представлять проблему, если каталоги считываются, чтобы составить список их файлов. Например, Unix не имеет детерминированного порядка, в котором readdir() и listdir() должны возвращать содержимое каталога, поэтому доверие к этим функциям для подачи в систему сборки может привести к недетерминированным сборкам.
Та же проблема возникает, например, если ваша система сборки хранит файлы для компоновщика в контейнере (например, в обычном python-словаре), который может возвращать элементы в недетерминированном порядке. Это приведет к тому, что каждый раз файлы будут связаны в другом порядке, и будут создаваться разные двоичные файлы.
Мы можем смоделировать эту проблему, изменяя порядок файлов в CMake. Если мы изменим предыдущий пример, чтобы иметь более одного исходного файла для библиотеки:
.
├── CMakeLists.txt
├── CMakeListsA.txt
├── CMakeListsB.txt
├── hello_world.cpp
├── hello_world.hpp
├── main.cpp
├── sources0.cpp
├── sources0.hpp
├── sources1.cpp
├── sources1.hpp
├── sources2.cpp
└── sources2.hpp
Мы можем увидеть, что результаты компиляции отличаются, если мы изменим порядок файлов в CMakeLists.txt
:
cmake_minimum_required(VERSION 3.0)
project(HelloWorld)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(HelloLib hello_world.cpp
sources0.cpp
sources1.cpp
sources2.cpp)
add_executable(hello main.cpp)
target_link_libraries(hello HelloLib)
Если мы сделаем две последовательные сборки с именами A и B, поменяв местами sources0.cpp
и sources1.cpp
в списке файлов, получим следующие контрольные суммы:
30ab264d6f8e1784282cd1a415c067f2 helloA
cdf3c9dd968f7363dc9e8b40918d83af helloB
707c71bc2a8def6885b96fb67b84d79c hello_worldA.cpp.o
707c71bc2a8def6885b96fb67b84d79c hello_worldB.cpp.o
694ff3765b688e6faeebf283052629a3 sources0A.cpp.o
694ff3765b688e6faeebf283052629a3 sources0B.cpp.o
0db24dc6a94da1d167c68b96ff319e56 sources1A.cpp.o
0db24dc6a94da1d167c68b96ff319e56 sources1B.cpp.o
fd0754d9a4a44b0fcc4e4f3c66ad187c sources2A.cpp.o
fd0754d9a4a44b0fcc4e4f3c66ad187c sources2B.cpp.o
baba9709d69c9e5fd51ad985ee328172 libHelloLibA.a
72641dc6fc4f4db04166255f62803353 libHelloLibB.a
Объектные .o
-файлы идентичны, но .а
-библиотеки и исполняемые файлы — нет. Это связано с тем, что порядок вставки в библиотеки зависит от порядка перечисления файлов.
Эта проблема возникает, например, в gcc, когда активирована оптимизация времени компоновки (Link-Time Optimizations [3]) (флагом -flto
). Эта опция вводит случайно сгенерированные имена в двоичные файлы. Единственный способ избежать этой проблемы — использовать флаг -frandom-seed
. Эта опция предоставляет начальное число, которое gcc использует вместо случайных чисел. Он используется для генерации определенных имен символов, которые должны быть разными в каждом скомпилированном файле. Он также используется для размещения уникальных штампов в файлах покрытия данных и объектных файлах, которые их производят. Этот параметр должен быть разным для каждого исходного файла. Один из вариантов — установить контрольную сумму файла, чтобы вероятность коллизии была очень низкой. Например, в CMake это можно сделать с помощью такой функции:
set(LIB_SOURCES
./src/source1.cpp
./src/source2.cpp
./src/source3.cpp)
foreach(_file ${LIB_SOURCES})
file(SHA1 ${_file} checksum)
string(SUBSTRING ${checksum} 0 8 checksum)
set_property(SOURCE ${_file} APPEND_STRING PROPERTY COMPILE_FLAGS "-frandom-seed=0x${checksum}")
endforeach()
Хуки [4] Conan могут помочь нам сделать наши сборки воспроизводимыми. Эта фича позволяет настроить поведение клиента в определенных точках.
Одним из способов использования хуков может быть установка переменных окружения на этапе pre_build
. В приведенном ниже примере вызывается функция set_environment
, а затем восстанавливается среда на шаге post_build
с помощью reset_environment
.
def set_environment(self):
if self._os == "Linux":
self._old_source_date_epoch = os.environ.get("SOURCE_DATE_EPOCH")
timestamp = "1564483496"
os.environ["SOURCE_DATE_EPOCH"] = timestamp
self._output.info(
"set SOURCE_DATE_EPOCH: {}".format(timestamp))
elif self._os == "Macos":
os.environ["ZERO_AR_DATE"] = "1"
self._output.info(
"set ZERO_AR_DATE: {}".format(timestamp))
def reset_environment(self):
if self._os == "Linux":
if self._old_source_date_epoch is None:
del os.environ["SOURCE_DATE_EPOCH"]
else:
os.environ["SOURCE_DATE_EPOCH"] = self._old_source_date_epoch
elif self._os == "Macos":
del os.environ["ZERO_AR_DATE"]
Хуки также могут быть полезны для исправления двоичных файлов на этапе post_build
. Существуют различные инструменты анализа и исправления бинарных файлов, такие как ducible
, pefile
, pe-parse
или strip-nondeterminism
. Пример хука для исправления двоичного файла PE с использованием ducible
может быть таким:
class Patcher(object):
...
def patch(self):
if self._os == "Windows" and self._compiler == "Visual Studio":
for root, _, filenames in os.walk(self._conanfile.build_folder):
for filename in filenames:
filename = os.path.join(root, filename)
if ".exe" in filename or ".dll" in filename:
self._patch_pe(filename)
def _patch_pe(self, filename):
patch_tool_location = "C:/ducible/ducible.exe"
if os.path.isfile(patch_tool_location):
self._output.info("Patching {} with md5sum: {}".format(filename,md5sum(filename)))
self._conanfile.run("{} {}".format(patch_tool_location, filename))
self._output.info("Patched file: {} with md5sum: {}".format(filename,md5sum(filename)))
...
def pre_build(output, conanfile, **kwargs):
lib_patcher.init(output, conanfile)
lib_patcher.set_environment()
def post_build(output, conanfile, **kwargs):
lib_patcher.patch()
lib_patcher.reset_environment()
Детерминированные сборки являются сложной задачей, тесно связанной с используемой операционной системой и набором инструментов. Это введение должно было помочь понять наиболее распространенные причины отсутствия детерминированности и способы их устранения.
Инструменты для сравнения двоичных файлов
Инструменты для исправления файлов
Инструменты для анализа файлов
→ Читать первую часть [2]
Автор: MaxRokatansky
Источник [24]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/331006
Ссылки в тексте:
[1] «Разработчик С++»: https://otus.pw/idLU/
[2] Читать первую часть: https://habr.com/ru/company/otus/blog/467741/
[3] Link-Time Optimizations: https://gcc.gnu.org/wiki/LinkTimeOptimization
[4] Хуки: https://docs.conan.io/en/latest/extending/hooks.html
[5] www.chromium.org/developers/testing/isolated-testing/deterministic-builds: https://www.chromium.org/developers/testing/isolated-testing/deterministic-builds
[6] reproducible-builds.org: https://reproducible-builds.org/
[7] wiki.yoctoproject.org/wiki/Reproducible_Builds: https://wiki.yoctoproject.org/wiki/Reproducible_Builds
[8] stackoverflow.com/questions/1180852/deterministic-builds-under-windows: https://stackoverflow.com/questions/1180852/deterministic-builds-under-windows
[9] docs.microsoft.com/en-us/windows/win32/debug/pe-format#archive-library-file-format: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#archive-library-file-format
[10] devblogs.microsoft.com/oldnewthing/20180103-00/?p=97705: https://devblogs.microsoft.com/oldnewthing/20180103-00/?p=97705
[11] www.geoffchappell.com/studies/msvc/link/link/options/brepro.htm?tx=37&ts=0: https://www.geoffchappell.com/studies/msvc/link/link/options/brepro.htm?tx=37&ts=0
[12] diffoscope.org: https://diffoscope.org/
[13] docs.microsoft.com/en-us/windows-server/administration/windows-commands/fc: https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/fc
[14] salsa.debian.org/reproducible-builds/strip-nondeterminism: https://salsa.debian.org/reproducible-builds/strip-nondeterminism
[15] github.com/erocarrera/pefile: https://github.com/erocarrera/pefile
[16] github.com/trailofbits/pe-parse: https://github.com/trailofbits/pe-parse
[17] github.com/smarttechnologies/peparser: https://github.com/smarttechnologies/peparser
[18] github.com/google/syzygy: https://github.com/google/syzygy
[19] github.com/nh2/ar-timestamp-wiper: https://github.com/nh2/ar-timestamp-wiper
[20] docs.microsoft.com/en-us/cpp/build/reference/dumpbin-reference?view=vs-2019: https://docs.microsoft.com/en-us/cpp/build/reference/dumpbin-reference?view=vs-2019
[21] sourceware.org/binutils/docs/binutils/readelf.html: https://sourceware.org/binutils/docs/binutils/readelf.html
[22] github.com/llvm-mirror/llvm/tree/master/tools: https://github.com/llvm-mirror/llvm/tree/master/tools
[23] github.com/lief-project/LIEF: https://github.com/lief-project/LIEF
[24] Источник: https://habr.com/ru/post/468555/?utm_campaign=468555&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.