Contents

Make a skeleton spec file

We are going to create a package for triton. You don’t have to understand what triton does. The point is to show the steps needed to create an RPM package for a C++ project. In this case, it is actually a C++ project with a Python interface, which makes it a bit more interesting.

First, change to the directory where you want the RPM spec file to be created.

cd ~/rpmbuild/SPECS

One of the packages we had you install in setup is rpmdevtools, which contains several useful utilities for working with RPM packages. One of those tools is rpmdev-newspec, which creates a skeleton spec file for you. It has a number of templates which it uses to create the spec files; look in /etc/rpmdevtools to see the available templates. We will use the minimal template.

rpmdev-newspec triton

Now there is a file named triton.spec in your current directory. Let’s see what is inside.

Name:           triton
Version:
Release:        1%{?dist}
Summary:

License:
URL:
Source0:

BuildRequires:
Requires:

%description


%prep
%autosetup


%build
%configure
%make_build


%install
rm -rf $RPM_BUILD_ROOT
%make_install


%files
%license add-license-file-here
%doc add-docs-here



%changelog
* Mon Sep 13 2021 Jerry James
-

Determine the version

Let’s start filling in the spec file. First, what version are we packaging? At the time of this writing, the latest released version is 0.8.1, so add that to the Version: line. While most software, including triton, now carries version numbers of the form number.number[.number], that style is not universal. If you are making a package for an upstream that has an unusual version numbering style, see the versioning guidelines for help on determining what to put in the Version: field.

Use autochangelog

Autochangelog is a recent addition to Fedora. It lets you avoid dealing directly with the Release: field and the %changelog section. Change the Release: field to read %autorelease. At the end of the spec file, replace the lines after %changelog with %autochangelog.

Write a summary

Next we need a summary. What does this package do? The github page says:

Triton is a Dynamic Binary Analysis (DBA) framework.

It is considered bad form to include the name of the package on the Summary: line, so we’ll shorten that to “Dynamic binary analysis framework”. Notice that there is no period in the summary. It is not a complete sentence, but rather a short descriptive phrase.

Determine the license

If you haven’t yet downloaded the tarball for the version you are packaging, do so now. Unpack the sources, and run licensecheck on the source files, as described here. In the case of triton 0.8.1, the output looks like this:

./.appveyor.yml: *No copyright* UNKNOWN
./.build_number: *No copyright* UNKNOWN
./.codecov.yml: *No copyright* UNKNOWN
./.editorconfig: *No copyright* UNKNOWN
./.travis.yml: *No copyright* UNKNOWN
./CMakeLists.txt: UNKNOWN
./Dockerfile: *No copyright* UNKNOWN
./LICENSE.txt: *No copyright* Apache License 2.0
./README.md: *No copyright* UNKNOWN
./CMakeModules/FindCAPSTONE.cmake: *No copyright* UNKNOWN
./CMakeModules/FindZ3.cmake: *No copyright* UNKNOWN
./CMakeModules/LibFindMacros.cmake: *No copyright* UNKNOWN
./doc/CMakeLists.txt: UNKNOWN
./doc/Doxyfile.in: *No copyright* UNKNOWN
./doc/DoxygenLayout.xml: *No copyright* UNKNOWN
./doc/customdoxygen.css: *No copyright* UNKNOWN
./doc/extract_doc.py: *No copyright* UNKNOWN
./doc/footer.html: *No copyright* UNKNOWN
./doc/header.html: *No copyright* UNKNOWN
./publications/CSAW2016-SOS-Virtual-Machine-Deobfuscation-RThomas_JSalwan.pdf: UNKNOWN
./publications/DIMVA2018-deobfuscation-salwan-bardin-potet.pdf: UNKNOWN
./publications/DIMVA2018-slide-deobfuscation-salwan-bardin-potet.pdf: *No copyright* UNKNOWN
./publications/MISC-82_French_Paper_How_Triton_may_help_to_analyse_obfuscated_binaries_RThomas_JSalwan.pdf: *No copyright* UNKNOWN
./publications/SSTIC2015_English_slide_detailed_version_Triton_Concolic_Execution_FrameWork_FSaudel_JSalwan.pdf: UNKNOWN
./publications/SSTIC2015_French_Paper_Triton_Framework_dexecution_Concolique_FSaudel_JSalwan.pdf: UNKNOWN
./publications/SSTIC2015_French_slide_light_version_Triton_Concolic_Execution_FrameWork_FSaudel_JSalwan.pdf: UNKNOWN
./publications/SSTIC2017-French-Article-desobfuscation_binaire_reconstruction_de_fonctions_virtualisees-salwan_potet_bardin.pdf: UNKNOWN
./publications/SSTIC2017_Deobfuscation_of_VM_based_software_protection.pdf: *No copyright* UNKNOWN
./publications/SecurityDay2015_dynamic_symbolic_execution_Jonathan_Salwan.pdf: UNKNOWN
./publications/StHack2015_Dynamic_Behavior_Analysis_using_Binary_Instrumentation_Jonathan_Salwan.pdf: UNKNOWN
./publications/StHack2016_Dynamic_Binary_Analysis_and_Obfuscated_Codes_RThomas_JSalwan.pdf: UNKNOWN
./src/CMakeLists.txt: *No copyright* UNKNOWN
./doc/autocomplete/example.py: *No copyright* UNKNOWN
./doc/autocomplete/function.py: *No copyright* UNKNOWN
./doc/autocomplete/generate_autocomplete.py: *No copyright* UNKNOWN
./doc/figures/figure.1.1.png: UNKNOWN
./doc/figures/figure.1.10.png: UNKNOWN
./doc/figures/figure.1.11.png: UNKNOWN
./doc/figures/figure.1.19.png: UNKNOWN
./doc/figures/figure.1.20.png: UNKNOWN
./doc/figures/figure.1.4.png: UNKNOWN
./doc/figures/figure.1.5.png: UNKNOWN
./doc/figures/figure.1.6.png: UNKNOWN
./doc/figures/figure.1.7.png: UNKNOWN
./doc/figures/figure.1.8.png: UNKNOWN
./doc/figures/figure.1.9.png: UNKNOWN
./src/examples/CMakeLists.txt: *No copyright* UNKNOWN
./src/libtriton/CMakeLists.txt: *No copyright* UNKNOWN
./src/scripts/extract_syscall.py: *No copyright* UNKNOWN
./src/scripts/run_linux_test.sh: *No copyright* UNKNOWN
./src/scripts/triton.in: *No copyright* UNKNOWN
./src/scripts/tritonAttach.in: *No copyright* UNKNOWN
./src/testers/CMakeLists.txt: *No copyright* UNKNOWN
./src/tracer/CMakeLists.txt: *No copyright* UNKNOWN
./src/examples/cpp/CMakeLists.txt: *No copyright* UNKNOWN
./src/examples/cpp/Makefile: *No copyright* UNKNOWN
./src/examples/cpp/constraint.cpp: *No copyright* UNKNOWN
./src/examples/cpp/ctest_api.cpp: *No copyright* UNKNOWN
./src/examples/cpp/info_reg.cpp: *No copyright* UNKNOWN
./src/examples/cpp/ir.cpp: *No copyright* UNKNOWN
./src/examples/cpp/simplification.cpp: *No copyright* UNKNOWN
./src/examples/cpp/taint_reg.cpp: *No copyright* UNKNOWN
./src/examples/pin/blacklist.py: *No copyright* UNKNOWN
./src/examples/pin/callback_concrete_needs.py: *No copyright* UNKNOWN
./src/examples/pin/callback_image.py: *No copyright* UNKNOWN
./src/examples/pin/callback_routine.py: *No copyright* UNKNOWN
./src/examples/pin/callback_signals.py: *No copyright* UNKNOWN
./src/examples/pin/callback_syscall.py: *No copyright* UNKNOWN
./src/examples/pin/count_inst.py: *No copyright* UNKNOWN
./src/examples/pin/crackme_hash_collision.py: *No copyright* UNKNOWN
./src/examples/pin/inject_model_with_snapshot.py: *No copyright* UNKNOWN
./src/examples/pin/ir.py: *No copyright* UNKNOWN
./src/examples/pin/looking_for_stack_base_and_main_addr.py: *No copyright* UNKNOWN
./src/examples/pin/path_constraints.py: *No copyright* UNKNOWN
./src/examples/pin/runtime_memory_tainting.py: *No copyright* UNKNOWN
./src/examples/pin/runtime_register_modification.py: *No copyright* UNKNOWN
./src/examples/pin/strlen.py: *No copyright* UNKNOWN
./src/examples/pin/sym_only_on_tainted.py: *No copyright* UNKNOWN
./src/examples/pin/symbolize_input_file.py: *No copyright* UNKNOWN
./src/examples/pin/trace_inst.py: *No copyright* UNKNOWN
./src/examples/python/backward_slicing.py: *No copyright* UNKNOWN
./src/examples/python/code_coverage_crackme_xor.py: *No copyright* UNKNOWN
./src/examples/python/constraints.py: *No copyright* UNKNOWN
./src/examples/python/disass.py: *No copyright* UNKNOWN
./src/examples/python/forward_tainting.py: *No copyright* UNKNOWN
./src/examples/python/hooking_libc.py: *No copyright* UNKNOWN
./src/examples/python/ir.py: *No copyright* UNKNOWN
./src/examples/python/proving_opaque_predicates.py: *No copyright* UNKNOWN
./src/examples/python/simplification.py: *No copyright* UNKNOWN
./src/examples/python/small_x86-64_symbolic_emulator.py: *No copyright* UNKNOWN
./src/examples/python/symbolic_emulation_1.py: *No copyright* UNKNOWN
./src/examples/python/symbolic_emulation_2.py: *No copyright* UNKNOWN
./src/examples/python/symbolic_emulation_crackme_xor.py: *No copyright* UNKNOWN
./src/examples/python/synthetizing_obfuscated_expressions.py: *No copyright* UNKNOWN
./src/libtriton/api/api.cpp: Apache License 2.0
./src/libtriton/arch/architecture.cpp: Apache License 2.0
./src/libtriton/arch/bitsVector.cpp: Apache License 2.0
./src/libtriton/arch/immediate.cpp: Apache License 2.0
./src/libtriton/arch/instruction.cpp: Apache License 2.0
./src/libtriton/arch/irBuilder.cpp: Apache License 2.0
./src/libtriton/arch/memoryAccess.cpp: Apache License 2.0
./src/libtriton/arch/operandWrapper.cpp: Apache License 2.0
./src/libtriton/arch/register.cpp: Apache License 2.0
./src/libtriton/ast/ast.cpp: Apache License 2.0
./src/libtriton/ast/astContext.cpp: Apache License 2.0
./src/libtriton/callbacks/callbacks.cpp: Apache License 2.0
./src/libtriton/modes/modes.cpp: Apache License 2.0
./src/libtriton/utils/coreUtils.cpp: Apache License 2.0
./src/samples/32bits/crackme_xor: *No copyright* UNKNOWN
./src/samples/32bits/crackme_xor.c: *No copyright* UNKNOWN
./src/samples/aarch64/crackme_hash: UNKNOWN
./src/samples/aarch64/crackme_hash.c: *No copyright* UNKNOWN
./src/samples/aarch64/crackme_xor: UNKNOWN
./src/samples/aarch64/crackme_xor.c: *No copyright* UNKNOWN
./src/samples/code_coverage/test_atoi: *No copyright* UNKNOWN
./src/samples/code_coverage/test_atoi.c: *No copyright* UNKNOWN
./src/samples/crackmes/crackme_hash: *No copyright* UNKNOWN
./src/samples/crackmes/crackme_hash.c: *No copyright* UNKNOWN
./src/samples/crackmes/crackme_regex_fsm: *No copyright* UNKNOWN
./src/samples/crackmes/crackme_regex_fsm.c: *No copyright* UNKNOWN
./src/samples/crackmes/crackme_regex_fsm_obfuscated: UNKNOWN
./src/samples/crackmes/crackme_regex_fsm_obfuscated.c: *No copyright* UNKNOWN
./src/samples/crackmes/crackme_sample: *No copyright* UNKNOWN
./src/samples/crackmes/crackme_sample.c: *No copyright* UNKNOWN
./src/samples/crackmes/crackme_xor: *No copyright* UNKNOWN
./src/samples/crackmes/crackme_xor.c: *No copyright* UNKNOWN
./src/samples/ir_test_suite/ir: UNKNOWN
./src/samples/ir_test_suite/ir.c: *No copyright* UNKNOWN
./src/samples/ir_test_suite/qemu-test-x86_64: UNKNOWN
./src/samples/others/read_from_file: UNKNOWN
./src/samples/others/read_from_file.c: *No copyright* UNKNOWN
./src/samples/others/sage_example: *No copyright* UNKNOWN
./src/samples/others/sage_example.c: *No copyright* UNKNOWN
./src/samples/others/signals: *No copyright* UNKNOWN
./src/samples/others/signals.c: *No copyright* UNKNOWN
./src/samples/others/strlen: *No copyright* UNKNOWN
./src/samples/others/strlen.c: *No copyright* UNKNOWN
./src/samples/others/thread: UNKNOWN
./src/samples/others/thread.c: *No copyright* UNKNOWN
./src/samples/smt/af.smt2: *No copyright* UNKNOWN
./src/samples/smt/cmp.smt2: *No copyright* UNKNOWN
./src/samples/smt/firstCharTest.smt2: *No copyright* UNKNOWN
./src/samples/smt/jbe.smt2: *No copyright* UNKNOWN
./src/samples/smt/jle.smt2: *No copyright* UNKNOWN
./src/samples/smt/jnbe.smt2: *No copyright* UNKNOWN
./src/samples/smt/jnle.smt2: *No copyright* UNKNOWN
./src/samples/smt/of.smt2: *No copyright* UNKNOWN
./src/samples/smt/pf.smt2: *No copyright* UNKNOWN
./src/samples/smt/sf.smt2: *No copyright* UNKNOWN
./src/samples/vulns/formatString: *No copyright* UNKNOWN
./src/samples/vulns/formatString.c: *No copyright* UNKNOWN
./src/samples/vulns/testSuite: *No copyright* UNKNOWN
./src/samples/vulns/testSuite.c: *No copyright* UNKNOWN
./src/testers/aarch64/unicorn_test_aarch64.py: *No copyright* UNKNOWN
./src/testers/arm32/unicorn_test_arm32_branch_arm_1.py: *No copyright* UNKNOWN
./src/testers/arm32/unicorn_test_arm32_branch_arm_2.py: *No copyright* UNKNOWN
./src/testers/arm32/unicorn_test_arm32_branch_pc_arm_1.py: *No copyright* UNKNOWN
./src/testers/arm32/unicorn_test_arm32_branch_pc_arm_2.py: *No copyright* UNKNOWN
./src/testers/arm32/unicorn_test_arm32_branch_thumb_1.py: *No copyright* UNKNOWN
./src/testers/arm32/unicorn_test_arm32_branch_thumb_2.py: *No copyright* UNKNOWN
./src/testers/arm32/unicorn_test_arm32_data_arm.py: *No copyright* UNKNOWN
./src/testers/arm32/unicorn_test_arm32_data_thumb.py: *No copyright* UNKNOWN
./src/testers/arm32/unicorn_test_arm32_interworking_arm.py: *No copyright* UNKNOWN
./src/testers/arm32/unicorn_test_arm32_interworking_thumb.py: *No copyright* UNKNOWN
./src/testers/arm32/unicorn_test_arm32_loadstore_arm_1.py: *No copyright* UNKNOWN
./src/testers/arm32/unicorn_test_arm32_loadstore_arm_2.py: *No copyright* UNKNOWN
./src/testers/arm32/unicorn_test_arm32_loadstore_arm_3.py: *No copyright* UNKNOWN
./src/testers/arm32/unicorn_test_arm32_loadstore_arm_4.py: *No copyright* UNKNOWN
./src/testers/arm32/unicorn_test_arm32_loadstore_thumb_1.py: *No copyright* UNKNOWN
./src/testers/arm32/unicorn_test_arm32_loadstore_thumb_2.py: *No copyright* UNKNOWN
./src/testers/arm32/unicorn_test_arm32_loadstore_thumb_3.py: *No copyright* UNKNOWN
./src/testers/pin/check_semantics.py: *No copyright* UNKNOWN
./src/testers/pin/qemu-test-x86_64.py: *No copyright* UNKNOWN
./src/testers/pin/unsuported_semantics.py: *No copyright* UNKNOWN
./src/testers/unittests/test_arch.py: *No copyright* UNKNOWN
./src/testers/unittests/test_ast_conversion.py: *No copyright* UNKNOWN
./src/testers/unittests/test_ast_deep.py: *No copyright* UNKNOWN
./src/testers/unittests/test_ast_duplication.py: *No copyright* UNKNOWN
./src/testers/unittests/test_ast_eval.py: *No copyright* UNKNOWN
./src/testers/unittests/test_ast_reference.py: *No copyright* UNKNOWN
./src/testers/unittests/test_ast_representation.py: *No copyright* UNKNOWN
./src/testers/unittests/test_ast_simplification.py: *No copyright* UNKNOWN
./src/testers/unittests/test_ast_utils.py: *No copyright* UNKNOWN
./src/testers/unittests/test_bitvector.py: *No copyright* UNKNOWN
./src/testers/unittests/test_callback.py: *No copyright* UNKNOWN
./src/testers/unittests/test_concrete_value.py: *No copyright* UNKNOWN
./src/testers/unittests/test_disass.py: *No copyright* UNKNOWN
./src/testers/unittests/test_doc.py: *No copyright* UNKNOWN
./src/testers/unittests/test_examples.py: *No copyright* UNKNOWN
./src/testers/unittests/test_flags.py: *No copyright* UNKNOWN
./src/testers/unittests/test_github_issues.py: *No copyright* UNKNOWN
./src/testers/unittests/test_im_callback.py: *No copyright* UNKNOWN
./src/testers/unittests/test_immediate.py: *No copyright* UNKNOWN
./src/testers/unittests/test_immutable_registers.py: *No copyright* UNKNOWN
./src/testers/unittests/test_instruction.py: *No copyright* UNKNOWN
./src/testers/unittests/test_memory.py: *No copyright* UNKNOWN
./src/testers/unittests/test_only_symbolized_mode.py: *No copyright* UNKNOWN
./src/testers/unittests/test_only_tainted_mode.py: *No copyright* UNKNOWN
./src/testers/unittests/test_path_constraint.py: *No copyright* UNKNOWN
./src/testers/unittests/test_registers.py: *No copyright* UNKNOWN
./src/testers/unittests/test_semantics.py: *No copyright* UNKNOWN
./src/testers/unittests/test_simulation.py: *No copyright* UNKNOWN
./src/testers/unittests/test_symbolic.py: *No copyright* UNKNOWN
./src/testers/unittests/test_symbolic_expression.py: *No copyright* UNKNOWN
./src/testers/unittests/test_symbolic_optimizations.py: *No copyright* UNKNOWN
./src/testers/unittests/test_symbolic_variable.py: *No copyright* UNKNOWN
./src/testers/unittests/test_taint.py: *No copyright* UNKNOWN
./src/testers/unittests/test_undefined_registers.py: *No copyright* UNKNOWN
./src/testers/unittests/utils.py: *No copyright* UNKNOWN
./src/testers/x86/unicorn_test_x86.py: *No copyright* UNKNOWN
./src/tracer/pin/CMakeLists.txt: *No copyright* UNKNOWN
./src/tracer/pin/api.cpp: Apache License 2.0
./src/tracer/pin/api.hpp: Apache License 2.0
./src/tracer/pin/bindings.cpp: Apache License 2.0
./src/tracer/pin/bindings.hpp: Apache License 2.0
./src/tracer/pin/callbacks.cpp: Apache License 2.0
./src/tracer/pin/context.cpp: Apache License 2.0
./src/tracer/pin/context.hpp: Apache License 2.0
./src/tracer/pin/init.cpp: Apache License 2.0
./src/tracer/pin/main.cpp: Apache License 2.0
./src/tracer/pin/snapshot.cpp: Apache License 2.0
./src/tracer/pin/snapshot.hpp: Apache License 2.0
./src/tracer/pin/trigger.cpp: Apache License 2.0
./src/tracer/pin/trigger.hpp: Apache License 2.0
./src/tracer/pin/utils.cpp: Apache License 2.0
./src/tracer/pin/utils.hpp: Apache License 2.0
./src/examples/python/samples/sample_1: *No copyright* UNKNOWN
./src/examples/python/samples/sample_1.c: *No copyright* UNKNOWN
./src/libtriton/arch/arm/armOperandProperties.cpp: Apache License 2.0
./src/libtriton/arch/x86/x8664Cpu.cpp: Apache License 2.0
./src/libtriton/arch/x86/x86Cpu.cpp: Apache License 2.0
./src/libtriton/arch/x86/x86Semantics.cpp: Apache License 2.0
./src/libtriton/arch/x86/x86Specifications.cpp: Apache License 2.0
./src/libtriton/ast/representations/astPythonRepresentation.cpp: Apache License 2.0
./src/libtriton/ast/representations/astRepresentation.cpp: Apache License 2.0
./src/libtriton/ast/representations/astSmtRepresentation.cpp: Apache License 2.0
./src/libtriton/ast/z3/tritonToZ3Ast.cpp: Apache License 2.0
./src/libtriton/ast/z3/z3ToTritonAst.cpp: Apache License 2.0
./src/libtriton/bindings/python/init.cpp: Apache License 2.0
./src/libtriton/bindings/python/pyXFunctions.cpp: Apache License 2.0
./src/libtriton/bindings/python/utils.cpp: Apache License 2.0
./src/libtriton/engines/solver/solverEngine.cpp: Apache License 2.0
./src/libtriton/engines/solver/solverModel.cpp: Apache License 2.0
./src/libtriton/engines/symbolic/pathConstraint.cpp: Apache License 2.0
./src/libtriton/engines/symbolic/pathManager.cpp: Apache License 2.0
./src/libtriton/engines/symbolic/symbolicEngine.cpp: Apache License 2.0
./src/libtriton/engines/symbolic/symbolicExpression.cpp: Apache License 2.0
./src/libtriton/engines/symbolic/symbolicSimplification.cpp: Apache License 2.0
./src/libtriton/engines/symbolic/symbolicVariable.cpp: Apache License 2.0
./src/libtriton/engines/taint/taintEngine.cpp: Apache License 2.0
./src/libtriton/includes/triton/aarch64.spec: *No copyright* UNKNOWN
./src/libtriton/includes/triton/aarch64Cpu.hpp: Apache License 2.0
./src/libtriton/includes/triton/aarch64Semantics.hpp: Apache License 2.0
./src/libtriton/includes/triton/aarch64Specifications.hpp: Apache License 2.0
./src/libtriton/includes/triton/api.hpp: Apache License 2.0
./src/libtriton/includes/triton/archEnums.hpp: Apache License 2.0
./src/libtriton/includes/triton/architecture.hpp: Apache License 2.0
./src/libtriton/includes/triton/arm32.spec: *No copyright* UNKNOWN
./src/libtriton/includes/triton/arm32Cpu.hpp: Apache License 2.0
./src/libtriton/includes/triton/arm32Semantics.hpp: Apache License 2.0
./src/libtriton/includes/triton/arm32Specifications.hpp: Apache License 2.0
./src/libtriton/includes/triton/armOperandProperties.hpp: Apache License 2.0
./src/libtriton/includes/triton/ast.hpp: Apache License 2.0
./src/libtriton/includes/triton/astContext.hpp: Apache License 2.0
./src/libtriton/includes/triton/astEnums.hpp: Apache License 2.0
./src/libtriton/includes/triton/astPythonRepresentation.hpp: Apache License 2.0
./src/libtriton/includes/triton/astRepresentation.hpp: Apache License 2.0
./src/libtriton/includes/triton/astRepresentationInterface.hpp: Apache License 2.0
./src/libtriton/includes/triton/astSmtRepresentation.hpp: Apache License 2.0
./src/libtriton/includes/triton/bitsVector.hpp: Apache License 2.0
./src/libtriton/includes/triton/callbacks.hpp: Apache License 2.0
./src/libtriton/includes/triton/callbacksEnums.hpp: Apache License 2.0
./src/libtriton/includes/triton/comparableFunctor.hpp: Apache License 2.0
./src/libtriton/includes/triton/config.hpp.in: Apache License 2.0
./src/libtriton/includes/triton/coreUtils.hpp: Apache License 2.0
./src/libtriton/includes/triton/cpuInterface.hpp: Apache License 2.0
./src/libtriton/includes/triton/cpuSize.hpp: Apache License 2.0
./src/libtriton/includes/triton/dllexport.hpp: Apache License 2.0
./src/libtriton/includes/triton/exceptions.hpp: Apache License 2.0
./src/libtriton/includes/triton/externalLibs.hpp: Apache License 2.0
./src/libtriton/includes/triton/immediate.hpp: Apache License 2.0
./src/libtriton/includes/triton/instruction.hpp: Apache License 2.0
./src/libtriton/includes/triton/irBuilder.hpp: Apache License 2.0
./src/libtriton/includes/triton/memoryAccess.hpp: Apache License 2.0
./src/libtriton/includes/triton/modes.hpp: Apache License 2.0
./src/libtriton/includes/triton/modesEnums.hpp: Apache License 2.0
./src/libtriton/includes/triton/operandWrapper.hpp: Apache License 2.0
./src/libtriton/includes/triton/pathConstraint.hpp: Apache License 2.0
./src/libtriton/includes/triton/pathManager.hpp: Apache License 2.0
./src/libtriton/includes/triton/py3c_compat.h: MIT License
./src/libtriton/includes/triton/pythonBindings.hpp: Apache License 2.0
./src/libtriton/includes/triton/pythonObjects.hpp: Apache License 2.0
./src/libtriton/includes/triton/pythonUtils.hpp: Apache License 2.0
./src/libtriton/includes/triton/pythonXFunctions.hpp: Apache License 2.0
./src/libtriton/includes/triton/register.hpp: Apache License 2.0
./src/libtriton/includes/triton/semanticsInterface.hpp: Apache License 2.0
./src/libtriton/includes/triton/shortcutRegister.hpp: Apache License 2.0
./src/libtriton/includes/triton/solverEngine.hpp: Apache License 2.0
./src/libtriton/includes/triton/solverEnums.hpp: Apache License 2.0
./src/libtriton/includes/triton/solverInterface.hpp: Apache License 2.0
./src/libtriton/includes/triton/solverModel.hpp: Apache License 2.0
./src/libtriton/includes/triton/symbolicEngine.hpp: Apache License 2.0
./src/libtriton/includes/triton/symbolicEnums.hpp: Apache License 2.0
./src/libtriton/includes/triton/symbolicExpression.hpp: Apache License 2.0
./src/libtriton/includes/triton/symbolicSimplification.hpp: Apache License 2.0
./src/libtriton/includes/triton/symbolicVariable.hpp: Apache License 2.0
./src/libtriton/includes/triton/syscalls.hpp: Apache License 2.0
./src/libtriton/includes/triton/taintEngine.hpp: Apache License 2.0
./src/libtriton/includes/triton/tritonToZ3Ast.hpp: Apache License 2.0
./src/libtriton/includes/triton/tritonTypes.hpp: Apache License 2.0
./src/libtriton/includes/triton/unix.hpp: Apache License 2.0
./src/libtriton/includes/triton/version.hpp.in: Apache License 2.0
./src/libtriton/includes/triton/x86.spec: *No copyright* UNKNOWN
./src/libtriton/includes/triton/x8664Cpu.hpp: Apache License 2.0
./src/libtriton/includes/triton/x86Cpu.hpp: Apache License 2.0
./src/libtriton/includes/triton/x86Semantics.hpp: Apache License 2.0
./src/libtriton/includes/triton/x86Specifications.hpp: Apache License 2.0
./src/libtriton/includes/triton/z3Solver.hpp: Apache License 2.0
./src/libtriton/includes/triton/z3ToTritonAst.hpp: Apache License 2.0
./src/libtriton/os/unix/syscallNumberToString.cpp: Apache License 2.0
./src/testers/arm32/crypto_test/crypto_test-nothumb-O0-run.py: *No copyright* UNKNOWN
./src/testers/arm32/crypto_test/crypto_test-nothumb-O1-run.py: *No copyright* UNKNOWN
./src/testers/arm32/crypto_test/crypto_test-nothumb-O2-run.py: *No copyright* UNKNOWN
./src/testers/arm32/crypto_test/crypto_test-nothumb-O3-run.py: *No copyright* UNKNOWN
./src/testers/arm32/crypto_test/crypto_test-nothumb-Os-run.py: *No copyright* UNKNOWN
./src/testers/arm32/crypto_test/crypto_test-nothumb-Oz-run.py: *No copyright* UNKNOWN
./src/testers/arm32/crypto_test/crypto_test-thumb-O0-run.py: *No copyright* UNKNOWN
./src/testers/arm32/crypto_test/crypto_test-thumb-O1-run.py: *No copyright* UNKNOWN
./src/testers/arm32/crypto_test/crypto_test-thumb-O2-run.py: *No copyright* UNKNOWN
./src/testers/arm32/crypto_test/crypto_test-thumb-O3-run.py: *No copyright* UNKNOWN
./src/testers/arm32/crypto_test/crypto_test-thumb-Os-run.py: *No copyright* UNKNOWN
./src/testers/arm32/crypto_test/crypto_test-thumb-Oz-run.py: *No copyright* UNKNOWN
./src/testers/unittests/misc/defcamp-2015-r100.bin: *No copyright* UNKNOWN
./src/testers/unittests/misc/emu_1.dump: UNKNOWN
./src/testers/unittests/misc/ir-test-suite.bin: *No copyright* UNKNOWN
./src/examples/python/ctf-writeups/defcamp-2015-r100/r100.bin: *No copyright* UNKNOWN
./src/examples/python/ctf-writeups/defcamp-2015-r100/solve.py: *No copyright* UNKNOWN
./src/examples/python/ctf-writeups/defcon-2016-baby-re/baby-re: UNKNOWN
./src/examples/python/ctf-writeups/defcon-2016-baby-re/baby-re.dump: UNKNOWN
./src/examples/python/ctf-writeups/defcon-2016-baby-re/gdb-peda-fulldump.patch: *No copyright* UNKNOWN
./src/examples/python/ctf-writeups/defcon-2016-baby-re/solve.py: *No copyright* UNKNOWN
./src/examples/python/ctf-writeups/google2016-unbreakable/solve.py: *No copyright* UNKNOWN
./src/examples/python/ctf-writeups/google2016-unbreakable/unbreakable-enterprise-product-activation: UNKNOWN
./src/examples/python/ctf-writeups/hackcon-2016-angry-reverser/solve.py: *No copyright* UNKNOWN
./src/examples/python/ctf-writeups/hackcon-2016-angry-reverser/yolomolo: UNKNOWN
./src/examples/python/ctf-writeups/hackover-ctf-2015-r150/rvs.bin: *No copyright* UNKNOWN
./src/examples/python/ctf-writeups/hackover-ctf-2015-r150/solve.py: *No copyright* UNKNOWN
./src/examples/python/ctf-writeups/mma-2015-howtouse/howtouse.dll: UNKNOWN
./src/examples/python/ctf-writeups/mma-2015-howtouse/solve.py: *No copyright* UNKNOWN
./src/libtriton/arch/arm/aarch64/aarch64Cpu.cpp: Apache License 2.0
./src/libtriton/arch/arm/aarch64/aarch64Semantics.cpp: Apache License 2.0
./src/libtriton/arch/arm/aarch64/aarch64Specifications.cpp: Apache License 2.0
./src/libtriton/arch/arm/arm32/arm32Cpu.cpp: Apache License 2.0
./src/libtriton/arch/arm/arm32/arm32Semantics.cpp: Apache License 2.0
./src/libtriton/arch/arm/arm32/arm32Specifications.cpp: Apache License 2.0
./src/libtriton/bindings/python/modules/tritonCallbacks.cpp: Apache License 2.0
./src/libtriton/bindings/python/namespaces/initArchNamespace.cpp: Apache License 2.0
./src/libtriton/bindings/python/namespaces/initAstNodeNamespace.cpp: Apache License 2.0
./src/libtriton/bindings/python/namespaces/initAstRepresentationNamespace.cpp: Apache License 2.0
./src/libtriton/bindings/python/namespaces/initCallbackNamespace.cpp: Apache License 2.0
./src/libtriton/bindings/python/namespaces/initConditionsNamespace.cpp: Apache License 2.0
./src/libtriton/bindings/python/namespaces/initCpuSizeNamespace.cpp: Apache License 2.0
./src/libtriton/bindings/python/namespaces/initExtendNamespace.cpp: Apache License 2.0
./src/libtriton/bindings/python/namespaces/initModeNamespace.cpp: Apache License 2.0
./src/libtriton/bindings/python/namespaces/initOpcodesNamespace.cpp: Apache License 2.0
./src/libtriton/bindings/python/namespaces/initOperandNamespace.cpp: Apache License 2.0
./src/libtriton/bindings/python/namespaces/initPrefixesNamespace.cpp: Apache License 2.0
./src/libtriton/bindings/python/namespaces/initRegNamespace.cpp: Apache License 2.0
./src/libtriton/bindings/python/namespaces/initShiftsNamespace.cpp: Apache License 2.0
./src/libtriton/bindings/python/namespaces/initSymbolicNamespace.cpp: Apache License 2.0
./src/libtriton/bindings/python/namespaces/initSyscallNamespace.cpp: Apache License 2.0
./src/libtriton/bindings/python/namespaces/initVersionNamespace.cpp: Apache License 2.0
./src/libtriton/bindings/python/objects/pyAstContext.cpp: Apache License 2.0
./src/libtriton/bindings/python/objects/pyAstNode.cpp: Apache License 2.0
./src/libtriton/bindings/python/objects/pyBitsVector.cpp: Apache License 2.0
./src/libtriton/bindings/python/objects/pyImmediate.cpp: Apache License 2.0
./src/libtriton/bindings/python/objects/pyInstruction.cpp: Apache License 2.0
./src/libtriton/bindings/python/objects/pyMemoryAccess.cpp: Apache License 2.0
./src/libtriton/bindings/python/objects/pyPathConstraint.cpp: Apache License 2.0
./src/libtriton/bindings/python/objects/pyRegister.cpp: Apache License 2.0
./src/libtriton/bindings/python/objects/pySolverModel.cpp: Apache License 2.0
./src/libtriton/bindings/python/objects/pySymbolicExpression.cpp: Apache License 2.0
./src/libtriton/bindings/python/objects/pySymbolicVariable.cpp: Apache License 2.0
./src/libtriton/bindings/python/objects/pyTritonContext.cpp: Apache License 2.0
./src/libtriton/engines/solver/z3/z3Solver.cpp: Apache License 2.0
./src/testers/arm32/crypto_test/bin/crypto_test-nothumb-O0.bin: UNKNOWN
./src/testers/arm32/crypto_test/bin/crypto_test-nothumb-O1.bin: UNKNOWN
./src/testers/arm32/crypto_test/bin/crypto_test-nothumb-O2.bin: UNKNOWN
./src/testers/arm32/crypto_test/bin/crypto_test-nothumb-O3.bin: UNKNOWN
./src/testers/arm32/crypto_test/bin/crypto_test-nothumb-Os.bin: UNKNOWN
./src/testers/arm32/crypto_test/bin/crypto_test-nothumb-Oz.bin: UNKNOWN
./src/testers/arm32/crypto_test/bin/crypto_test-thumb-O0.bin: UNKNOWN
./src/testers/arm32/crypto_test/bin/crypto_test-thumb-O1.bin: UNKNOWN
./src/testers/arm32/crypto_test/bin/crypto_test-thumb-O2.bin: UNKNOWN
./src/testers/arm32/crypto_test/bin/crypto_test-thumb-O3.bin: UNKNOWN
./src/testers/arm32/crypto_test/bin/crypto_test-thumb-Os.bin: UNKNOWN
./src/testers/arm32/crypto_test/bin/crypto_test-thumb-Oz.bin: UNKNOWN
./src/testers/unittests/misc/qemu/ir-test-suite-qemu.bin: UNKNOWN
./src/testers/unittests/misc/qemu/test-i386-muldiv.h: *No copyright* UNKNOWN
./src/testers/unittests/misc/qemu/test-i386-shift.h: *No copyright* UNKNOWN
./src/testers/unittests/misc/qemu/test-i386.c: GNU General Public License v2.0 or later
./src/testers/unittests/misc/qemu/test-i386.h: *No copyright* UNKNOWN
./src/examples/python/ctf-writeups/custom-crackmes/aarch64-hash/crackme_hash: UNKNOWN
./src/examples/python/ctf-writeups/custom-crackmes/aarch64-hash/crackme_hash.c: *No copyright* UNKNOWN
./src/examples/python/ctf-writeups/custom-crackmes/aarch64-hash/solve.py: *No copyright* UNKNOWN
./src/examples/python/ctf-writeups/custom-crackmes/arm32-hash/crackme_hash-arm: *No copyright* UNKNOWN
./src/examples/python/ctf-writeups/custom-crackmes/arm32-hash/crackme_hash-thumb: *No copyright* UNKNOWN
./src/examples/python/ctf-writeups/custom-crackmes/arm32-hash/crackme_hash.c: *No copyright* UNKNOWN
./src/examples/python/ctf-writeups/custom-crackmes/arm32-hash/solve-arm.py: *No copyright* UNKNOWN
./src/examples/python/ctf-writeups/custom-crackmes/arm32-hash/solve-thumb.py: *No copyright* UNKNOWN

Many files are identified as Apache License 2.0. The rest are either UNKNOWN or are testing files. There is a file named LICENSE.txt, which contains the text of the Apache License, version 2.0, further confirmation that licensecheck correctly identified the license. If source code files are identified as UNKNOWN, it is a good idea to look at them to see if a license is mentioned. In the case of triton, all such files appear to be infrastructure, testing, or example files, so we conclude that the license is indeed Apache License 2.0.

Now we have to translate that into the name used by Fedora packages in the License: field. The Fedora project maintains a list of licenses. The “Short Name” column is what goes into the License: field of an RPM spec file. For the triton package, ASL 2.0 is the correct value.

The URL field

Next, put the project URL into the URL: field of the spec file. This should be some sort of home page for the project. In the triton case, the value of the field is https://triton.quarkslab.com/. If the project is on github, the project URL may be the main repository page for the project, which will be a URL of the form https://github.com/owner/project. In that case, check whether either of the pages https://owner.github.io/project or https://owner.github.io/ exist and contain useful content. Use one of them as the URL if so.

The Source field

The Source fields in the spec file identify those files that must be present in the build root in order to build the software. See the Source URL guidelines for help determining the appropriate URL for this field.

The name of the package should be replaced with %{name} in this URL, and the version number should be replaced with %{version}. The intent is to avoid changing the URL field every time a new version is released.

For the triton package, we will use this as the Source0 URL:

https://github.com/JonathanSalwan/Triton/archive/v%{version}/%{name}-%{version}.tar.gz

BuildRequires

Each BuildRequires tag in a spec file identifies some package that must be installed prior to building the target package. Let’s examine the triton package.

At the top level of the source directory, we see a file named CMakeLists.txt. That tells us that cmake is used to build this project, so we add BuildRequires: cmake to the spec file.

Looking into the src subdirectory, we see that this project contains files with a .cpp suffix; i.e., they are C++ source files. We need a C++ compiler to build, then, so add BuildRequires: gcc-c++ to the spec file.

Now let’s look through CMakeLists.txt to see what else we might need. We see that this package contains python bindings, so add BuildRequires: python3-devel to the spec file.

Further down in CMakeLists.txt, we see find_package(Z3 REQUIRED). Fedora has a z3 package, so add BuildRequires: z3-devel to the spec file, right? Almost. The use of find_package means that cmake is going to use pkgconfig to find what it needs. According to the packaging guidelines, if the z3-devel package provides a capability of the form pkgconfig(x), then our BuildRequires should refer to that capability instead. Let’s check:

$ dnf repoquery --provides z3-devel
cmake(Z3) = 4.8.12.0
cmake(z3) = 4.8.12.0
pkgconfig(z3) = 4.8.12.0
z3-devel = 4.8.12-1.fc34
z3-devel(x86-32) = 4.8.12-1.fc34
z3-devel(x86-64) = 4.8.12-1.fc34

Indeed it does, so we should add BuildRequires: pkgconfig(z3) to the spec file instead. In this case, it would also be acceptable to add BuildRequires: cmake(z3).

Further down, we see this: find_package(CAPSTONE REQUIRED). Since the capstone package has a pkgconfig capability, add BuildRequires: pkgconfig(capstone) to the spec file.

Finally, we see find_package(Boost 1.55.0 REQUIRED). Checking with repoquery shows that this package has no pkgconfig or cmake capabilities, so we add BuildRequires: boost-devel to the spec file. The spec file should now look like this:

Name:           triton
Version:        0.8.1
Release:        %autorelease
Summary:        Dynamic binary analysis framework

License:        ASL 2.0
URL:            https://triton.quarkslab.com/
Source0:        https://github.com/JonathanSalwan/Triton/archive/v%{version}/%{name}-%{version}.tar.gz

BuildRequires:  boost-devel
BuildRequires:  cmake
BuildRequires:  cmake(z3)
BuildRequires:  gcc-c++
BuildRequires:  pkgconfig(capstone)
BuildRequires:  python3-devel

%description

%prep
%autosetup


%build
%configure
%make_build


%install
rm -rf $RPM_BUILD_ROOT
%make_install


%files
%license add-license-file-here
%doc add-docs-here



%changelog
%autochangelog

Description

The %description section is typically filled in with text from a README file. Failing that, do your best to write a paragraph describing what this package does. You want a person who has never heard of the software before to be able to get some idea of the purpose of the package from reading the description. As noted in the packaging guidelines, keep lines to 80 characters or less. Longer lines can be displayed in strange ways in the various graphical software managers. I usually limit description lines to 72 characters to be on the safe side.

Prep

The %prep section is where we do anything that must be done prior to building the software. Typically, the first step is to unpack a tarball or zip file. The %autosetup macro is often sufficient. It knows how to unpack a variety of files and compression types. This macro assumes that, after unpacking, it will find the source files in a directory named %{name}-%{version}. If that is not the case, we need to tell it so. In the triton case, the name of the directory is Triton-0.8.1. That starts with an upper case T, where the package name starts with a lower case t, so they do not match. Change the %autosetup macro to read:

%autosetup -n Triton-%{version}

Build

The %build section contains instructions on how to build the softare. The %configure and %make_build macros placed there by rpmdev-newspec are intended for use with software that supplies an autoconf-generated configure script. In the case of triton, we want to build with cmake instead. Replace %configure with %cmake ., which invokes cmake with Fedora-specific flags. Replace %make_build with %cmake_build.

Install

The %install section installs the software into a directory identified by the macro %buildroot. RPM uses the files placed there to build the final binary package.

Our %install section starts with the line rm -rf $RPM_BUILD_ROOT. That line was inserted by rpmdev-newspec, which aims to be compatible with all Linux distributions that use RPM. Fedora, however, does not want that line in spec files; see Tags and Section in the packaging guidelines. Remove it now.

Now we just have %make_install in the %install section. That macro runs make install with some flags that are commonly used to get the installed files to go into %buildroot. Like %configure and %make_build, it is intended for projects that use autoconf. For a cmake project like triton, we need to replace it with %cmake_install.

Files

The %files section lists all of the files that go into the binary RPM. We don’t have to guess; we’ll do a test build and install to help us figure this out. However, there are two things we can do right now. Since we see a file named LICENSE.txt in the source directory, change the %license line to refer to it. Also, there is a README.md file, so change the %doc line to refer to it. The spec file should now look something like this:

Name:           triton
Version:        0.8.1
Release:        %autorelease
Summary:        Dynamic binary analysis framework

License:        ASL 2.0
URL:            https://triton.quarkslab.com/
Source0:        https://github.com/JonathanSalwan/Triton/archive/v%{version}/%{name}-%{version}.tar.gz

BuildRequires:  boost-devel
BuildRequires:  cmake
BuildRequires:  cmake(z3)
BuildRequires:  gcc-c++
BuildRequires:  pkgconfig(capstone)
BuildRequires:  python3-devel

%description
Triton is a dynamic binary analysis (DBA) framework.  It provides
internal components like a Dynamic Symbolic Execution (DSE) engine, a
dynamic taint engine, AST representations of the x86, x86-64, ARM32 and
AArch64 Instruction Set Architectures (ISA), SMT simplification passes,
an SMT solver interface, and Python bindings.  Based on these components,
one can build program analysis tools, automate reverse engineering and
perform software verification.

%prep
%autosetup -n Triton-%{version}

%build
%cmake .
%cmake_build

%install
%cmake_install

%files
%license LICENSE.txt
%doc README.md

%changelog
%autochangelog

First test build

Make sure that your spec file is in ~/rpmbuild/SPECS, and that triton-0.8.1.tar.gz is in ~/rpmbuild/SOURCES. While in the ~/rpmbuild/SPECS directory, run rpmbuild -bs triton.spec. That command generates a source rpm in ~/rpmbuild/SRPMS.

Next, we will attempt to build the package with mock. Refer to building with mock for details. Briefly, run this command:

mock -r fedora-rawhide-x86_64 --rebuild ~/rpmbuild/SRPMS/triton-0.8.1-1.fc35.src.rpm

You may have to replace fc35 in that filename. Make it match the name of file file in your ~/rpmbuild/SRPMS directory.

The test build fails, complaining that it cannot find the z3 library. What’s up with that? We included BuildRequires: cmake(z3) in our spec file! A close look at the build log reveals the problem:

Z3_INCLUDE_DIR=Z3_INCLUDE_DIR-NOTFOUND
Z3_INCLUDE_DIRS=

This project looks for the z3 headers in a different place than where Fedora installs them. If we look at the z3-devel package, we see that the headers are installed in /usr/include/z3. Change the %cmake line in the spec file to read:

%cmake -DZ3_INCLUDE_DIRS=%{_includedir}/z3 -DZ3_LIBRARIES=%{_libdir}/libz3.so .

Regenerate the source rpm and run the mock build again. It fails with compiler complaints about boost problems. Now what?

Apply upstream patches

Let’s look at the sources on github to see if we are facing a known issue. In the list of recent commits, we see this one from 10 June 2021: “Fix #1016: Issue with boost 1.76.0 and bigint”. That sounds promising! Clicking through to the commit shows a handful of changes that we should apply to the sources to fix our issue.

We now have a choice to make. If we think that applying this one commit is all we need to do, we should make a patch out of it, name it Patch0 in the spec file, and move on. On the other hand, if the patch does not apply cleanly to the version we want to build, or if we see lots of bug-fixing commits after the most recent release, we may be better building a git snapshot.

After looking through the git history, I conclude that for triton we would be better off with a git snapshot until the next version is released. Fortunately, Fedora provides a set of %forge macros that make this easy. Change the spec file to look like this:

# There have been lots of bug fixes since the last release.  Build from git.
%global commit  0be77d3dc69b964c697a9ce0a1f9f13ad1388323
%global date    20210910
%global forgeurl https://github.com/JonathanSalwan/Triton

Name:           triton
Version:        0.8.1

%forgemeta

Release:        %autorelease
Summary:        Dynamic binary analysis framework

License:        ASL 2.0
URL:            https://triton.quarkslab.com/
Source0:        %{forgesource}

BuildRequires:  boost-devel
BuildRequires:  cmake
BuildRequires:  cmake(z3)
BuildRequires:  gcc-c++
BuildRequires:  pkgconfig(capstone)
BuildRequires:  python3-devel

%description
Triton is a dynamic binary analysis (DBA) framework.  It provides
internal components like a Dynamic Symbolic Execution (DSE) engine, a
dynamic taint engine, AST representations of the x86, x86-64, ARM32 and
AArch64 Instruction Set Architectures (ISA), SMT simplification passes,
an SMT solver interface, and Python bindings.  Based on these components,
one can build program analysis tools, automate reverse engineering and
perform software verification.

%prep
%forgesetup

%build
%cmake -DZ3_INCLUDE_DIRS=%{_includedir}/z3 -DZ3_LIBRARIES=%{_libdir}/libz3.so .
%cmake_build

%install
%cmake_install

%files
%license LICENSE.txt
%doc README.md

%changelog
%autochangelog

The new source tarball can be downloaded by running spectool -g triton.spec. Move it to the ~/rpmbuild/SOURCES directory, then generate a new source RPM and build it with mock.

Devel subpackage

The second test build ended with an error indicating that a bunch of files were installed into %buildroot without being mentioned in %files. Great! We can now figure out what the content of %files should be. But wait. Files were installed into /usr/include. That means that we need a devel package. Add this after %description:

%package        devel
Summary:        Header files and library links for libtriton
Requires:       %{name}%{?_isa} = %{version}-%{release}

%description    devel
Header files and library links for building applications that use
libtriton.

Down at the bottom, change the %files section to include files for both the main package and the devel subpackage like so:

%files
%license LICENSE.txt
%doc README.md
%{_libdir}/libtriton.so

%files          devel
%{_includedir}/%{name}/

There, now we’re done!

Library soname

Not so fast. According to the packaging guidelines section on devel packages, we should have a versioned library file in the main package, and an unversioned symbolic link in the devel subpackage. But all we have is an unversioned library file. What’s up with that? The problem is that the triton package does not set an soname on the library. Whether we should “fix” this or not is debatable. Many packagers will set an soname of the form “0.0.0” if upstream does not set one. That way, if the upstream ever gets around to setting an soname, it will presumably be higher. We can do that by patching src/libtriton/CMakeLists.txt. For single-line edits like this, I often use sed to make the change. Add this to the end of %prep:

# Set an soname
sed '/add_library.Triton/aset_target_properties(\${PROJECT_LIBTRITON} PROPERTIES SOVERSION 0 VERSION %{version})' \
    -i src/libtriton/CMakeLists.txt

Then change the %files sections to read:

%files
%license LICENSE.txt
%doc README.md
%{_libdir}/libtriton.so.0*

%files          devel
%{_includedir}/%{name}/
%{_libdir}/libtriton.so

Regenerate the source RPM and re-run the mock build. It fails, complaining about not finding the library file. Wait, it got installed into /usr/lib instead of /usr/lib64!

This is a common occurrence with cmake projects when upstream develops on a Debian-based system. If we look in src/libtriton/CMakeLists.txt, we see a line that reads

install (TARGETS ${PROJECT_LIBTRITON} DESTINATION lib)

There’s the problem. We need that “lib” to be “lib64” on 64-bit systems. Add this to the %prep section:

# Fix the install location on 64-bit systems
if [ "%{_lib}" != "lib" ]; then
  sed -i 's/\(DESTINATION \)lib/\1%{_lib}/' src/libtriton/CMakeLists.txt
fi

Regenerate the source RPM and re-run the mock build.

Python interface

The build fails again. Now the complaint is:

RPM build errors:
    Installed (but unpackaged) file(s) found:
   /usr/lib/python3.10/site-packages/triton.so

There are two problems here. First, we need to account for the python interface in files. Second, that file isn’t in the right place. It is in /usr/lib, not /usr/lib64 where architecture-specific files are supposed to go. Let’s fix the second problem first.

Looking in src/libtriton/CMakeLists.txt again, we see this:

    execute_process (COMMAND ${PYTHON_EXECUTABLE} -c "from __future__ import print_function; from distutils.sysconfig import get_python_lib; print(get_python_lib())" OUTPUT_VARIABLE PYTHON_SITE_PACKAGES OUTPUT_STRIP_TRAILING_WHITESPACE)

There’s the problem: get_python_lib called with no arguments yields the general library installation directory. We have to call it with a true argument to get the platform-dependent library installation directory. We should report this bug upstream, like this. Until it is fixed, let’s work around it by modifying src/libtriton/CMakeLists.txt again. Change the %prep section to look like this:

%prep
%forgesetup

# Set an soname and install the python library in the right place
sed -e '/add_library.Triton/aset_target_properties(\${PROJECT_LIBTRITON} PROPERTIES SOVERSION 0 VERSION %{version})' \
    -e 's/get_python_lib()/get_python_lib(1)/' \
    -i src/libtriton/CMakeLists.txt

# Fix the install location on 64-bit systems
if [ "%{_lib}" != "lib" ]; then
  sed -i 's/\(DESTINATION \)lib/\1%{_lib}/' src/libtriton/CMakeLists.txt
fi

Now for the issue that we don’t account for the python interface. We need a new subpackage for the python parts. Add this just above %prep:

%package     -n python3-%{name}
Summary:        Python 3 interface to triton
Requires:       %{name}%{?_isa} = %{version}-%{release}

%description -n python3-%{name}
Python 3 interface to triton.

and add a new %files section:

%files       -n python3-%{name}
%{python3_sitearch}/%{name}.so

Now it builds successfully. We’re done!

Build flags

Just kidding! Let’s look at that build log again. In particular, look at the build flags used for C++ source files. After the Fedora flags, you’ll see some flags inserted by triton upstream:

-DZ3_INTERFACE -Wall -Wextra -Wno-unused-parameter -Wno-unknown-pragmas -O3 -fno-stack-protector -fomit-frame-pointer -fno-strict-aliasing -fPIC -std=c++11 -fPIC -Wimplicit-fallthrough=0 -Wno-error=cast-function-type -DNDEBUG -fPIC

We don’t necessarily want all of upstream’s flags. In particular, Fedora chooses its own optimization flag (currently -O2), so we do not want the -O3. Fedora has also elected to build with the stack protector, so unless there is some reason why triton won’t work with it on, we also do not want -fno-stack-protector. The choice of omitting the frame pointer or not is also made by Fedora, so we should also remove -fomit-frame-ponter. The other flags should be fine.

Let’s change our sed invocation in %prep to read as follows:

# Do not override Fedora flags.
# Set an soname on the library.
# Install the python library in the right place
sed -e 's/-O3/-O2/g' \
    -e 's/ -fno-stack-protector -fomit-frame-pointer//' \
    -e '/add_library.Triton/aset_target_properties(\${PROJECT_LIBTRITON} PROPERTIES SOVERSION 0 VERSION %{version})' \
    -e 's/get_python_lib()/get_python_lib(1)/' \
    -i src/libtriton/CMakeLists.txt

Build again. The build is successful, but when we look in the results directory, we should notice something odd: there is a debuginfo package for the main triton package, but there isn’t one for the python3-triton package. That package contains a shared object; shouldn’t debuginfo be generated for it? Let’s see what is in that package:

$ rpm -qlpv /var/lib/mock/fedora-rawhide-x86_64/result/python3-triton-0.8.1-1.20210910git0be77d3.fc36.x86_64.rpm
-rw-r--r--    1 root     root                 54066864 Sep 14 11:23 /usr/lib64/python3.10/site-packages/triton.so

The debuginfo generator looks for ELF files with the executable bit set. This file does not have executable bits set. Add this to the %install section:

# Add missing executable bits
chmod 0755 %{buildroot}%{python3_sitearch}/%{name}.so

Run the build again and a python3-triton-debuginfo package is generated.

Requires and Provides

Now that we have reasonable looking packages, lets make sure that the final Requires and Provides look right.

$ rpm -q --requires -p /var/lib/mock/fedora-rawhide-x86_64/result/triton-0.8.1-1.20210910git0be77d3.fc36.x86_64.rpm
libc.so.6()(64bit)
libc.so.6(GLIBC_2.14)(64bit)
libc.so.6(GLIBC_2.2.5)(64bit)
libc.so.6(GLIBC_2.3.4)(64bit)
libc.so.6(GLIBC_2.32)(64bit)
libc.so.6(GLIBC_2.4)(64bit)
libcapstone.so.4()(64bit)
libgcc_s.so.1()(64bit)
libgcc_s.so.1(GCC_3.0)(64bit)
libgcc_s.so.1(GCC_3.3.1)(64bit)
libpython3.10.so.1.0()(64bit)
libstdc++.so.6()(64bit)
libstdc++.so.6(CXXABI_1.3)(64bit)
libstdc++.so.6(CXXABI_1.3.5)(64bit)
libstdc++.so.6(GLIBCXX_3.4)(64bit)
libstdc++.so.6(GLIBCXX_3.4.11)(64bit)
libstdc++.so.6(GLIBCXX_3.4.14)(64bit)
libstdc++.so.6(GLIBCXX_3.4.15)(64bit)
libstdc++.so.6(GLIBCXX_3.4.18)(64bit)
libstdc++.so.6(GLIBCXX_3.4.20)(64bit)
libstdc++.so.6(GLIBCXX_3.4.21)(64bit)
libstdc++.so.6(GLIBCXX_3.4.29)(64bit)
libstdc++.so.6(GLIBCXX_3.4.9)(64bit)
libz3.so.4.8()(64bit)
rpmlib(CompressedFileNames) <= 3.0.4-1
rpmlib(FileDigests) <= 4.6.0-1
rpmlib(PayloadFilesHavePrefix) <= 4.0-1
rpmlib(PayloadIsZstd) <= 5.4.18-1
rtld(GNU_HASH)

I see dependencies on the capstone and z3 libraries, which is good. The main library also depends on the python library, though. That seems odd. A little checking shows the reason: the files /usr/lib64/python3.10/site-packages/triton.so and /usr/lib64/libtriton.so.0.8.1 are the same.

$ rpm -q --provides -p /var/lib/mock/fedora-rawhide-x86_64/result/triton-0.8.1-1.20210910git0be77d3.fc36.x86_64.rpm
libtriton.so.0()(64bit)
triton = 0.8.1-1.20210910git0be77d3.fc36
triton(x86-64) = 0.8.1-1.20210910git0be77d3.fc36

That seems reasonable. How about the python3 subpackage?

$ rpm -q --provides -p /var/lib/mock/fedora-rawhide-x86_64/result/python3-triton-0.8.1-1.20210910git0be77d3.fc36.x86_64.rpm
libtriton.so.0()(64bit)
python-triton = 0.8.1-1.20210910git0be77d3.fc36
python3-triton = 0.8.1-1.20210910git0be77d3.fc36
python3-triton(x86-64) = 0.8.1-1.20210910git0be77d3.fc36
python3.10-triton = 0.8.1-1.20210910git0be77d3.fc36

Notice that both packages Provide libtriton.so.0()(64bit). That’s because they are the same file, as we noted above, so both have an soname.

$ rpm -q --requires -p /var/lib/mock/fedora-rawhide-x86_64/result/python3-triton-0.8.1-1.20210910git0be77d3.fc36.x86_64.rpm
libc.so.6()(64bit)
libc.so.6(GLIBC_2.14)(64bit)
libc.so.6(GLIBC_2.2.5)(64bit)
libc.so.6(GLIBC_2.3.4)(64bit)
libc.so.6(GLIBC_2.32)(64bit)
libc.so.6(GLIBC_2.4)(64bit)
libcapstone.so.4()(64bit)
libgcc_s.so.1()(64bit)
libgcc_s.so.1(GCC_3.0)(64bit)
libgcc_s.so.1(GCC_3.3.1)(64bit)
libpython3.10.so.1.0()(64bit)
libstdc++.so.6()(64bit)
libstdc++.so.6(CXXABI_1.3)(64bit)
libstdc++.so.6(CXXABI_1.3.5)(64bit)
libstdc++.so.6(GLIBCXX_3.4)(64bit)
libstdc++.so.6(GLIBCXX_3.4.11)(64bit)
libstdc++.so.6(GLIBCXX_3.4.14)(64bit)
libstdc++.so.6(GLIBCXX_3.4.15)(64bit)
libstdc++.so.6(GLIBCXX_3.4.18)(64bit)
libstdc++.so.6(GLIBCXX_3.4.20)(64bit)
libstdc++.so.6(GLIBCXX_3.4.21)(64bit)
libstdc++.so.6(GLIBCXX_3.4.29)(64bit)
libstdc++.so.6(GLIBCXX_3.4.9)(64bit)
libz3.so.4.8()(64bit)
python(abi) = 3.10
rpmlib(CompressedFileNames) <= 3.0.4-1
rpmlib(FileDigests) <= 4.6.0-1
rpmlib(PayloadFilesHavePrefix) <= 4.0-1
rpmlib(PayloadIsZstd) <= 5.4.18-1
rtld(GNU_HASH)
triton(x86-64) = 0.8.1-1.20210910git0be77d3.fc36

I don’t see the z3 python interface listed here; i.e., python3-z3. Is it needed? Digging through the source code shows that it is, indeed.

So we have two problems: we’ve got duplicate libraries, and the python interface lacks a Requires. The second problem is easy to fix. Just add this under %package -n python3-%{name}:

Requires: python3-z3

The first problem requires some thought. What do we want the final installed objects to look like? Ideally, the library in /usr/lib64 would have an soname, and no dependency on Python. It would be a library that C++ applications can link to. The library in /usr/lib64/python3.10/site-packages would not have an soname, would contain only the Python interface, and would be linked against the library in /usr/lib64. However, upstream has not given us that option. That means we have to choose. Do we want to hack upstream’s build scripts to get what we really want, or should we settle for what we can get from upstream’s build scripts?

There is actually a third option: let upstream build the way it wants to build, then relink the libraries the way we want. Take a look at this version of the spec file:

# There have been lots of bug fixes since the last release.  Build from git.
%global commit  0be77d3dc69b964c697a9ce0a1f9f13ad1388323
%global date    20210910
%global forgeurl https://github.com/JonathanSalwan/Triton

Name:           triton
Version:        0.8.1

%forgemeta

Release:        %autorelease
Summary:        Dynamic binary analysis framework

License:        ASL 2.0
URL:            https://triton.quarkslab.com/
Source0:        %{forgesource}

BuildRequires:  boost-devel
BuildRequires:  cmake
BuildRequires:  cmake(z3)
BuildRequires:  gcc-c++
BuildRequires:  pkgconfig(capstone)
BuildRequires:  python3-devel

%description
Triton is a dynamic binary analysis (DBA) framework.  It provides
internal components like a Dynamic Symbolic Execution (DSE) engine, a
dynamic taint engine, AST representations of the x86, x86-64, ARM32 and
AArch64 Instruction Set Architectures (ISA), SMT simplification passes,
an SMT solver interface, and Python bindings.  Based on these components,
one can build program analysis tools, automate reverse engineering and
perform software verification.

%package        devel
Summary:        Header files and library links for libtriton
Requires:       %{name}%{?_isa} = %{version}-%{release}

%description    devel
Header files and library links for building applications that use
libtriton.

%package     -n python3-%{name}
Summary:        Python 3 interface to triton
Requires:       %{name}%{?_isa} = %{version}-%{release}
Requires:       python3-z3

%description -n python3-%{name}
Python 3 interface to triton.

%prep
%forgesetup

# Do not override Fedora flags.
# Install the python library in the right place
sed -e 's/-O3/-O2/g' \
    -e 's/ -fno-stack-protector -fomit-frame-pointer//' \
    -e 's/get_python_lib()/get_python_lib(1)/' \
    -i src/libtriton/CMakeLists.txt

# Fix the install location on 64-bit systems
if [ "%{_lib}" != "lib" ]; then
  sed -i 's/\(DESTINATION \)lib/\1%{_lib}/' src/libtriton/CMakeLists.txt
fi

%build
%cmake -DZ3_INCLUDE_DIRS=%{_includedir}/z3 -DZ3_LIBRARIES=%{_libdir}/libz3.so .
%cmake_build

cd %{_vpath_builddir}/src/libtriton
# Relink the main library without the python bindings.
g++ %{build_cxxflags} %{build_ldflags} -shared -Wl,-soname,libtriton.so.0 \
  $(find CMakeFiles -path '*/bindings/python' -prune -o -name \*.o -print) \
  -o libtriton.so -lz3 -lcapstone
# Relink the python library and link it to the main library
g++ %{build_cxxflags} %{build_ldflags} -shared -o triton.so \
  $(find CMakeFiles/triton.dir/bindings/python -name \*.o) \
  -L. -ltriton
cd -

%install
%cmake_install

# Add ldconfig links
cd %{buildroot}%{_libdir}
mv libtriton.so libtriton.so.%{version}
ln -s libtriton.so.%{version} libtriton.so.0
ln -s libtriton.so.0 libtriton.so
cd -

# Add missing executable bits
chmod 0755 %{buildroot}%{python3_sitearch}/%{name}.so

%files
%license LICENSE.txt
%doc README.md
%{_libdir}/libtriton.so.0*

%files          devel
%{_includedir}/%{name}/
%{_libdir}/libtriton.so

%files       -n python3-%{name}
%{python3_sitearch}/%{name}.so

%changelog
%autochangelog

Note that we no longer convince cmake to add an soname to the library in %prep. We relink the shared object anyway, so we just add an soname at that time. Notice also that we do not link the python interface to libpython like triton upstream does. See this bug for a discussion of the issues involved.

Rebuild with that spec file and notice that the Requires and Provides now

File cleanup

Now that we have the libraries linked the way we want, and the Requires and Provides look sane, let’s examine the contents of each package to see if they look okay.

$ rpm -qlp /var/lib/mock/fedora-rawhide-x86_64/result/triton-0.8.1-1.20210910git0be77d3.fc36.x86_64.rpm
/usr/lib/.build-id
/usr/lib/.build-id/c8
/usr/lib/.build-id/c8/4d4d21a8a1340fa22ad9e55c0b20d31e6af970
/usr/lib64/libtriton.so.0
/usr/lib64/libtriton.so.0.8.1
/usr/share/doc/triton
/usr/share/doc/triton/README.md
/usr/share/licenses/triton
/usr/share/licenses/triton/LICENSE.txt

That looks okay. How about the python interface?

$ rpm -qlp /var/lib/mock/fedora-rawhide-x86_64/result/python3-triton-0.8.1-1.20210910git0be77d3.fc36.x86_64.rpm
/usr/lib/.build-id
/usr/lib/.build-id/63
/usr/lib/.build-id/63/91bd11555af935b4d71b20d7e3923c57e6272a
/usr/lib64/python3.10/site-packages/triton.so

That looks okay, too. How about the devel subpackage?

$ rpm -qlp /var/lib/mock/fedora-rawhide-x86_64/result/triton-devel-0.8.1-1.20210910git0be77d3.fc36.x86_64.rpm
/usr/include/triton
/usr/include/triton/aarch64.spec
/usr/include/triton/aarch64Cpu.hpp
/usr/include/triton/aarch64Semantics.hpp
/usr/include/triton/aarch64Specifications.hpp
/usr/include/triton/api.hpp
/usr/include/triton/archEnums.hpp
/usr/include/triton/architecture.hpp
/usr/include/triton/arm32.spec
/usr/include/triton/arm32Cpu.hpp
/usr/include/triton/arm32Semantics.hpp
/usr/include/triton/arm32Specifications.hpp
/usr/include/triton/armOperandProperties.hpp
/usr/include/triton/ast.hpp
/usr/include/triton/astContext.hpp
/usr/include/triton/astEnums.hpp
/usr/include/triton/astPythonRepresentation.hpp
/usr/include/triton/astRepresentation.hpp
/usr/include/triton/astRepresentationInterface.hpp
/usr/include/triton/astSmtRepresentation.hpp
/usr/include/triton/bitsVector.hpp
/usr/include/triton/callbacks.hpp
/usr/include/triton/callbacksEnums.hpp
/usr/include/triton/comparableFunctor.hpp
/usr/include/triton/config.hpp
/usr/include/triton/config.hpp.in
/usr/include/triton/coreUtils.hpp
/usr/include/triton/cpuInterface.hpp
/usr/include/triton/cpuSize.hpp
/usr/include/triton/dllexport.hpp
/usr/include/triton/exceptions.hpp
/usr/include/triton/externalLibs.hpp
/usr/include/triton/immediate.hpp
/usr/include/triton/instruction.hpp
/usr/include/triton/irBuilder.hpp
/usr/include/triton/memoryAccess.hpp
/usr/include/triton/modes.hpp
/usr/include/triton/modesEnums.hpp
/usr/include/triton/operandWrapper.hpp
/usr/include/triton/pathConstraint.hpp
/usr/include/triton/pathManager.hpp
/usr/include/triton/py3c_compat.h
/usr/include/triton/pythonBindings.hpp
/usr/include/triton/pythonObjects.hpp
/usr/include/triton/pythonUtils.hpp
/usr/include/triton/pythonXFunctions.hpp
/usr/include/triton/register.hpp
/usr/include/triton/semanticsInterface.hpp
/usr/include/triton/shortcutRegister.hpp
/usr/include/triton/solverEngine.hpp
/usr/include/triton/solverEnums.hpp
/usr/include/triton/solverInterface.hpp
/usr/include/triton/solverModel.hpp
/usr/include/triton/symbolicEngine.hpp
/usr/include/triton/symbolicEnums.hpp
/usr/include/triton/symbolicExpression.hpp
/usr/include/triton/symbolicSimplification.hpp
/usr/include/triton/symbolicVariable.hpp
/usr/include/triton/syscalls.hpp
/usr/include/triton/taintEngine.hpp
/usr/include/triton/tritonToZ3Ast.hpp
/usr/include/triton/tritonTypes.hpp
/usr/include/triton/unix.hpp
/usr/include/triton/version.hpp
/usr/include/triton/version.hpp.in
/usr/include/triton/x86.spec
/usr/include/triton/x8664Cpu.hpp
/usr/include/triton/x86Cpu.hpp
/usr/include/triton/x86Semantics.hpp
/usr/include/triton/x86Specifications.hpp
/usr/include/triton/z3Solver.hpp
/usr/include/triton/z3ToTritonAst.hpp
/usr/lib64/libtriton.so

That mostly looks okay, except I see two files with names ending in .in in there. Such files usually contain templates that are filled in by the build system. We do, in fact, see the same filenames without the .in extension in the list, so we probably do not want the templated versions. Taking a diff of the files in question confirms this: the .in files are meant to be instantiated by cmake. Let’s remove them in the %install section:

# Remove unwanted files
rm %{buildroot}%{_includedir}/triton/*.in

Build documentation

The source directory has doc and publication subdirectories. We haven’t done anything with them. Let’s try building the documentation. What do we need? The doc subdirectory contains a file named Doxyfile.in, so we need doxygen. Add BuildRequires: doxygen to the other BuildRequires. In the %build section, add %make_build -C %{_vpath_builddir} doc below the %cmake_build line. With luck, that will build the documentation for us. (We’ll check in a moment.) But what are we going to do with the documentation once it is built?

Let’s add another subpackage, specifically to hold the documentation. Here’s a little trick: if you add %global _docdir_fmt %{name} to your spec file, then all of the documentation in subpackages will be installed to the documentation directory of the parent package, perhaps making it a bit easier to find. With that trick, our spec file now looks like this:

# There have been lots of bug fixes since the last release.  Build from git.
%global commit  0be77d3dc69b964c697a9ce0a1f9f13ad1388323
%global date    20210910
%global forgeurl https://github.com/JonathanSalwan/Triton

# Install documentation in the main doc directory
%global _docdir_fmt %{name}

Name:           triton
Version:        0.8.1

%forgemeta

Release:        %autorelease
Summary:        Dynamic binary analysis framework

License:        ASL 2.0
URL:            https://triton.quarkslab.com/
Source0:        %{forgesource}

BuildRequires:  boost-devel
BuildRequires:  cmake
BuildRequires:  cmake(z3)
BuildRequires:  doxygen
BuildRequires:  gcc-c++
BuildRequires:  pkgconfig(capstone)
BuildRequires:  python3-devel

%description
Triton is a dynamic binary analysis (DBA) framework.  It provides
internal components like a Dynamic Symbolic Execution (DSE) engine, a
dynamic taint engine, AST representations of the x86, x86-64, ARM32 and
AArch64 Instruction Set Architectures (ISA), SMT simplification passes,
an SMT solver interface, and Python bindings.  Based on these components,
one can build program analysis tools, automate reverse engineering and
perform software verification.

%package        devel
Summary:        Header files and library links for triton
Requires:       %{name}%{?_isa} = %{version}-%{release}

%description    devel
Header files and library links for building applications that use
libtriton.

%package        doc
Summary:        API documentation for libtriton
BuildArch:      noarch

%description    doc
API documentation for libtriton.

%package     -n python3-%{name}
Summary:        Python 3 interface to triton
Requires:       %{name}%{?_isa} = %{version}-%{release}
Requires:       python3-z3

%description -n python3-%{name}
Python 3 interface to triton.

%prep
%forgesetup

# Do not override Fedora flags.
# Install the python library in the right place
sed -e 's/-O3/-O2/g' \
    -e 's/ -fno-stack-protector -fomit-frame-pointer//' \
    -e 's/get_python_lib()/get_python_lib(1)/' \
    -i src/libtriton/CMakeLists.txt

# Fix the install location on 64-bit systems
if [ "%{_lib}" != "lib" ]; then
  sed -i 's/\(DESTINATION \)lib/\1%{_lib}/' src/libtriton/CMakeLists.txt
fi

%build
%cmake -DZ3_INCLUDE_DIRS=%{_includedir}/z3 -DZ3_LIBRARIES=%{_libdir}/libz3.so .
%cmake_build
%make_build -C %{_vpath_builddir} doc

cd %{_vpath_builddir}/src/libtriton
# Rebuild the main library without the python bindings.
g++ %{build_cxxflags} %{build_ldflags} -shared -Wl,-soname,libtriton.so.0 \
  $(find CMakeFiles -path '*/bindings/python' -prune -o -name \*.o -print) \
  -o libtriton.so -lz3 -lcapstone
# Rebuild the python library linked to the main library
g++ %{build_cxxflags} %{build_ldflags} -shared -o triton.so \
  $(find CMakeFiles/triton.dir/bindings/python -name \*.o) \
  -L. -ltriton
cd -

%install
%cmake_install

# Remove unwanted files
rm %{buildroot}%{_includedir}/triton/*.in

# Add ldconfig links
cd %{buildroot}%{_libdir}
mv libtriton.so libtriton.so.%{version}
ln -s libtriton.so.%{version} libtriton.so.0
ln -s libtriton.so.0 libtriton.so
cd -

%install
%cmake_install

# Remove unwanted files
rm %{buildroot}%{_includedir}/triton/*.in

# Add ldconfig links
cd %{buildroot}%{_libdir}
mv libtriton.so libtriton.so.%{version}
ln -s libtriton.so.%{version} libtriton.so.0
ln -s libtriton.so.0 libtriton.so
cd -

# Add missing executable bits
chmod 0755 %{buildroot}%{python3_sitearch}/%{name}.so

%files
%license LICENSE.txt
%doc README.md
%{_libdir}/libtriton.so.0*

%files          devel
%{_includedir}/%{name}/
%{_libdir}/libtriton.so

%files          doc
%license LICENSE.txt
%doc publications %{_vpath_builddir}/doc/doc/html

%files       -n python3-%{name}
%{python3_sitearch}/%{name}.so

%changelog
%autochangelog

Notice that we added BuildArch: noarch to the doc subpackage. There are no architecture-specific files in that subpackage. Hopefully, exactly the same documentation will be built on all architectures. (That usually, but not always, works out.) Also notice that the doc subpackages does not have any Requires. You don’t need to have the main package installed to read the documentation. Finally, notice that we added the license file to the doc subpackage. The license file must be installed when any triton package is installed, and the doc subpackage does not depend on any of the others.

Run tests

We’re getting close now! Upstream has provided us with some tests. We should run them to make sure we haven’t somehow broken the whole thing. With a cmake project, the %ctest macro usually does what you want. Let’s add this to our spec file:

%check
%ctest

Run that again and … wait a minute! All of the tests failed! What gives?

There are three problems. First, all of the tests report:

ModuleNotFoundError: No module named 'triton'

We can fix that by setting PYTHONPATH to point to the python module in the buildroot. Add this between %check and %ctest:

export PYTHONPATH=%{buildroot}%{python3_sitearch}

Second, that module is linked with a library that only exists in the build root, so we have to tell the dynamic linker how to find it. Add this line just after the previous one:

export LD_LIBRARY_PATH=%{buildroot}%{_libdir}

Third, recall that we determined that the python interface needs python3-z3. Well, we didn’t list that as a BuildRequires, did we? Add BuildRequires: python3-z3 and try building again.

The tests still fail, now complaining about missing unicorn and lief modules. The python3-unicorn package is available from Fedora, but has issues. The lief package is not available from Fedora at all. At this point, we have to reluctantly decide that running upstream’s tests isn’t going to work. Instead we will leave a comment behind explaining what to do if unicorn is fixed and lief is packaged for Fedora, and use a simple “can the module be imported?” test instead. That looks like this:

%check
# This does not work due to missing lief package and a unicorn bug:
# (https://bugzilla.redhat.com/show_bug.cgi?id=2004320
#
# export PYTHONPATH=%%{buildroot}%%{python3_sitearch}
# export LD_LIBRARY_PATH=%%{buildroot}%%{_libdir}
# %%ctest
#
# For now, just check that we can import the module.

export LD_LIBRARY_PATH=%{buildroot}%{_libdir}
%py3_check_import %{name}

Reconsider the name

At this point, we should think about the name of the package again. We used the project name, triton, but is that the best choice? Since the main package contains only a library, libtriton, maybe libtriton would be a better choice. This could be argued either way, but if we were to decide to go with libtriton, the final spec file would look like this:

# There have been lots of bug fixes since the last release.  Build from git.
%global commit  0be77d3dc69b964c697a9ce0a1f9f13ad1388323
%global date    20210910
%global forgeurl https://github.com/JonathanSalwan/Triton

# Install documentation in the main doc directory
%global _docdir_fmt %{name}

Name:           libtriton
Version:        0.8.1

%forgemeta

Release:        %autorelease
Summary:        Dynamic binary analysis framework

License:        ASL 2.0
URL:            https://triton.quarkslab.com/
Source0:        %{forgesource}

BuildRequires:  boost-devel
BuildRequires:  cmake
BuildRequires:  cmake(z3)
BuildRequires:  doxygen
BuildRequires:  gcc-c++
BuildRequires:  pkgconfig(capstone)
BuildRequires:  python3-devel
BuildRequires:  python3-z3

%description
Triton is a dynamic binary analysis (DBA) framework.  It provides
internal components like a Dynamic Symbolic Execution (DSE) engine, a
dynamic taint engine, AST representations of the x86, x86-64, ARM32 and
AArch64 Instruction Set Architectures (ISA), SMT simplification passes,
an SMT solver interface, and Python bindings.  Based on these components,
one can build program analysis tools, automate reverse engineering and
perform software verification.

%package        devel
Summary:        Header files and library links for triton
Requires:       %{name}%{?_isa} = %{version}-%{release}

%description    devel
Header files and library links for building applications that use
libtriton.

%package        doc
Summary:        API documentation for libtriton
BuildArch:      noarch

%description    doc
API documentation for libtriton.

%package     -n python3-triton
Summary:        Python 3 interface to triton
Requires:       %{name}%{?_isa} = %{version}-%{release}
Requires:       python3-z3

%description -n python3-triton
Python 3 interface to triton.

%prep
%forgesetup

# Do not override Fedora flags.
# Install the python library in the right place
sed -e 's/-O3/-O2/g' \
    -e 's/ -fno-stack-protector -fomit-frame-pointer//' \
    -e 's/get_python_lib()/get_python_lib(1)/' \
    -i src/libtriton/CMakeLists.txt

# Fix the install location on 64-bit systems
if [ "%{_lib}" != "lib" ]; then
  sed -i 's/\(DESTINATION \)lib/\1%{_lib}/' src/libtriton/CMakeLists.txt
fi

%build
%cmake -DZ3_INCLUDE_DIRS=%{_includedir}/z3 -DZ3_LIBRARIES=%{_libdir}/libz3.so .
%cmake_build
%make_build -C %{_vpath_builddir} doc

cd %{_vpath_builddir}/src/libtriton
# Rebuild the main library without the python bindings.
g++ %{build_cxxflags} %{build_ldflags} -shared -Wl,-soname,libtriton.so.0 \
  $(find CMakeFiles -path '*/bindings/python' -prune -o -name \*.o -print) \
  -o libtriton.so -lz3 -lcapstone
# Rebuild the python library linked to the main library
g++ %{build_cxxflags} %{build_ldflags} -shared -o triton.so \
  $(find CMakeFiles/triton.dir/bindings/python -name \*.o) \
  -L. -ltriton
cd -

%install
%cmake_install

# Remove unwanted files
rm %{buildroot}%{_includedir}/triton/*.in

# Add ldconfig links
cd %{buildroot}%{_libdir}
mv %{name}.so %{name}.so.%{version}
ln -s %{name}.so.%{version} %{name}.so.0
ln -s %{name}.so.0 %{name}.so
cd -

# Add missing executable bits
chmod 0755 %{buildroot}%{python3_sitearch}/triton.so

%check
# This does not work due to missing lief package and a unicorn bug:
# (https://bugzilla.redhat.com/show_bug.cgi?id=2004320
#
# export PYTHONPATH=%%{buildroot}%%{python3_sitearch}
# export LD_LIBRARY_PATH=%%{buildroot}%%{_libdir}
# %%ctest
#
# For now, just check that we can import the module.

export LD_LIBRARY_PATH=%{buildroot}%{_libdir}
%py3_check_import triton

%files
%license LICENSE.txt
%doc README.md
%{_libdir}/libtriton.so.0*

%files          devel
%{_includedir}/triton/
%{_libdir}/libtriton.so

%files          doc
%license LICENSE.txt
%doc publications %{_vpath_builddir}/doc/doc/html

%files       -n python3-triton
%{python3_sitearch}/triton.so

%changelog
%autochangelog

Run rpmlint

We finally have a viable spec file! Let’s run an automated tool over it that can detect many common packaging problems.

mock -r fedora-rawhide-x86_64 --install /var/lib/mock/fedora-rawhide-x86_64/result/*.{noarch,x86_64}.rpm rpmlint python3-enchant

Afterwards, we enter a mock shell and run rpmlint:

mock --enable-network -r fedora-rawhide-x86_64 --shell

The --enable-network argument allows rpmlint to access the network in order to do some checks, such as URL validity. In the shell, run this command:

# rpmlint -i libtriton libtriton-devel libtriton-doc python3-triton
================================================ rpmlint session starts ================================================
rpmlint: 2.1.0
configuration:
    /usr/lib/python3.10/site-packages/rpmlint/configdefaults.toml
    /etc/xdg/rpmlint/fedora.toml
    /etc/xdg/rpmlint/licenses.toml
    /etc/xdg/rpmlint/scoring.toml
    /etc/xdg/rpmlint/users-groups.toml
    /etc/xdg/rpmlint/warn-on-functions.toml
checks: 31, packages: 4

python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyBool_Type	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so _Py_TrueStruct	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so _Py_NoneStruct	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyType_Type	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyType_Type	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyByteArray_Type	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyExc_RuntimeError	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so _Py_NotImplementedStruct	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyExc_TypeError	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyMethod_Type	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so _Py_FalseStruct	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyTuple_SetItem	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyByteArray_Size	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyLong_AsVoidPtr	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyObject_GetAttrString(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyType_IsSubtype	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so _PyLong_New	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyDict_SetItem	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyModule_AddObject	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyTuple_New	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyDict_SetItemString	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyUnicode_FromFormat	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyBytes_FromStringAndSize	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyArg_ParseTupleAndKeywords	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyList_Size	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so _PyObject_New	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyErr_Format	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyObject_CallObject	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so _Py_Dealloc	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyByteArray_AsString	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so Py_BuildValue	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyList_New	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyImport_ImportModule(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyUnicode_FromString	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyBytes_Size	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyList_GetItem	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyLong_FromVoidPtr	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyDict_New	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyArg_ParseTuple	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyUnicode_AsUTF8	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyObject_SetAttrString(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyBytes_AsString	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyType_Ready	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyLong_FromLong	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyObject_IsTrue	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyModule_Create2	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyObject_GenericGetAttr	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyList_SetItem	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyObject_CallFunctionObjArgs	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyCallable_Check	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so Py_Initialize	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyErr_Print	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyDict_Clear	(/usr/lib64/python3.10/site-packages/triton.so)
python3-triton.x86_64: W: undefined-non-weak-symbol /usr/lib64/python3.10/site-packages/triton.so PyBool_FromLong	(/usr/lib64/python3.10/site-packages/triton.so)
libtriton.x86_64: E: shlib-policy-name-error 0
python3-triton.x86_64: W: no-documentation
libtriton-devel.x86_64: W: missing-dependency-on libtriton*/libtriton-libs/liblibtriton* = 0.8.1
libtriton.x86_64: E: invalid-ldconfig-symlink /usr/lib64/libtriton.so.0.8.1 libtriton.so.0.8.1
libtriton-doc.noarch: W: files-duplicate /usr/share/doc/libtriton/html/search/typedefs_0.js /usr/share/doc/libtriton/html/search/all_0.js
libtriton-doc.noarch: W: files-duplicate /usr/share/doc/libtriton/html/search/variables_f.js /usr/share/doc/libtriton/html/search/all_10.js
libtriton-doc.noarch: W: files-duplicate /usr/share/doc/libtriton/html/search/variables_17.js /usr/share/doc/libtriton/html/search/all_18.js
libtriton-doc.noarch: W: files-duplicate /usr/share/doc/libtriton/html/search/functions_17.js /usr/share/doc/libtriton/html/search/all_1a.js
================ 4 packages and 0 specfiles checked; 2 errors, 60 warnings, 2 badness; has taken 2.5 s =================

Oh my, that’s a lot of output! Is something wrong? The undefined-non-weak-symbol warnings are because we deliberately did not link the Python module with libpython. Just verify that each symbol starts with Py or _Py and we can move on. The shlib-policy-name-error, missing-dependency-on, and invalid-ldconfig-symlink warnings are bogus; I get them on every package with a shared library, so it must be an rpmlint bug. Let’s ignore them for now. The python3-triton package indeed has no documentation; we put that into the doc subpackage, so that’s okay. The files-duplicate warnings are all due to actions by doxygen that we don’t want to countermand. Everything looks fine.

Conclusion

I hope you learned something from this case study. Please send suggestions for improvements to Jerry James. If you want to submit the libtriton package to Fedora, please feel free. Just make sure nobody else beat you to it.