Repository: GrammaTech/gtirb Branch: master Commit: eb6a7af1bb97 Files: 357 Total size: 2.2 MB Directory structure: gitextract_07t2irr6/ ├── .ci/ │ ├── Dockerfile.static │ ├── Dockerfile.ubuntu20 │ ├── Dockerfile.ubuntu22 │ ├── Dockerfile.ubuntu24 │ ├── PKGBUILD │ ├── adjust-coverage-paths.py │ ├── build.py │ ├── gitlab-ci.yml │ ├── pre-commit │ ├── test-install-all.sh │ ├── test-install-static.sh │ ├── test-install.cpp │ ├── test-install.lisp │ ├── test-install.py │ └── test-interop.sh ├── .clang-format ├── .clang-tidy ├── .cmake-format.yaml ├── .dockerignore ├── .flake8 ├── .github/ │ └── workflows/ │ └── actions.yml ├── .gitignore ├── .gtirb.tex ├── .isort.cfg ├── .lisp-format ├── .pre-commit-config.yaml ├── AlignOf.cmake ├── AuxData.md ├── CHANGELOG.md ├── CMakeLists.googletest ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── FAQ.md ├── LICENSE.txt ├── Macros.cmake ├── PROTOBUF.md ├── README.md ├── cl/ │ ├── CMakeLists.txt │ ├── README.md │ ├── dot.lisp │ ├── gtirb.asd │ ├── gtirb.lisp │ ├── package.lisp │ ├── ranged.lisp │ ├── test.lisp │ ├── update.lisp │ ├── utility.lisp │ ├── validate.lisp │ └── version.lisp ├── conanfile.py ├── cpack-config.cmake ├── doc/ │ ├── CFG-Edges.md │ ├── CMakeLists.txt │ ├── binary-representation.md │ ├── cl/ │ │ ├── CMakeLists.txt │ │ └── write-documentation.lisp │ ├── cpp/ │ │ ├── CMakeLists.txt │ │ ├── Doxyfile.in │ │ ├── DoxygenLayout.xml │ │ └── README.md │ ├── dot/ │ │ └── gtirb.dot │ ├── examples/ │ │ ├── CMakeLists.txt │ │ ├── api-walkthrough.cpp │ │ ├── cfg-paths.cpp │ │ ├── cfg-paths.lisp │ │ ├── cfg-paths.py │ │ ├── cfgpaths.java │ │ ├── data-symbols.cpp │ │ ├── data-symbols.lisp │ │ ├── data-symbols.py │ │ ├── datasymbols.java │ │ ├── functions.cpp │ │ ├── jumps.cpp │ │ ├── show-cfg.lisp │ │ ├── show-cfg.py │ │ └── stack-stamp.md │ ├── general/ │ │ ├── AuxDataContainer.md │ │ ├── Block.md │ │ ├── ByteBlock.md │ │ ├── ByteInterval.md │ │ ├── CFG.md │ │ ├── CMakeLists.txt │ │ ├── COMPONENTS.md │ │ ├── CfgEdge.md │ │ ├── CfgEdgeLabel.md │ │ ├── CfgNode.md │ │ ├── CodeBlock.md │ │ ├── DataBlock.md │ │ ├── Doxyfile.in │ │ ├── DoxygenLayout.xml │ │ ├── IR.md │ │ ├── Module.md │ │ ├── Node.md │ │ ├── ProxyBlock.md │ │ ├── Section.md │ │ ├── SymAddrAddr.md │ │ ├── SymAddrConst.md │ │ ├── Symbol.md │ │ ├── SymbolicExpression.md │ │ ├── Version.md │ │ ├── examples.dox │ │ └── images.dox │ ├── java/ │ │ └── CMakeLists.txt │ ├── preprocmd.py │ └── python/ │ ├── CMakeLists.txt │ └── conf.py ├── gtirbConfig.cmake.in ├── include/ │ └── gtirb/ │ ├── Addr.hpp │ ├── Allocator.hpp │ ├── AuxData.hpp │ ├── AuxDataContainer.hpp │ ├── AuxDataSchema.hpp │ ├── ByteInterval.hpp │ ├── CFG.hpp │ ├── Casting.hpp │ ├── CfgNode.hpp │ ├── CodeBlock.hpp │ ├── Context.hpp │ ├── DataBlock.hpp │ ├── DecodeMode.hpp │ ├── ErrorOr.hpp │ ├── Export.hpp │ ├── IR.hpp │ ├── Module.hpp │ ├── Node.hpp │ ├── Observer.hpp │ ├── Offset.hpp │ ├── ProxyBlock.hpp │ ├── Section.hpp │ ├── Symbol.hpp │ ├── SymbolicExpression.hpp │ ├── Utility.hpp │ ├── gtirb.hpp │ └── version.h.in ├── java/ │ ├── .gitignore │ ├── CMakeLists.txt │ ├── Version.java.in │ ├── build.gradle │ ├── com/ │ │ └── grammatech/ │ │ └── gtirb/ │ │ ├── AuxDataContainer.java │ │ ├── AuxDataSchema.java │ │ ├── AuxDataSchemas.java │ │ ├── ByteBlock.java │ │ ├── ByteInterval.java │ │ ├── CFG.java │ │ ├── CfiDirective.java │ │ ├── CodeBlock.java │ │ ├── DataBlock.java │ │ ├── Edge.java │ │ ├── ElfSymbolInfoTuple.java │ │ ├── ElfSymbolVersionsTable.java │ │ ├── IR.java │ │ ├── Module.java │ │ ├── Node.java │ │ ├── Offset.java │ │ ├── PeExportEntry.java │ │ ├── PeImportEntry.java │ │ ├── PeResourceEntry.java │ │ ├── ProbFuncName.java │ │ ├── ProxyBlock.java │ │ ├── Section.java │ │ ├── SectionPropertyTuple.java │ │ ├── SymAddrAddr.java │ │ ├── SymAddrConst.java │ │ ├── Symbol.java │ │ ├── SymbolicExpression.java │ │ ├── TreeListItem.java │ │ ├── TreeListUtils.java │ │ ├── TypeTableEntry.java │ │ ├── Util.java │ │ ├── auxdatacodec/ │ │ │ ├── BoolCodec.java │ │ │ ├── ByteCodec.java │ │ │ ├── Codec.java │ │ │ ├── FloatCodec.java │ │ │ ├── IntegerCodec.java │ │ │ ├── ListCodec.java │ │ │ ├── LongCodec.java │ │ │ ├── MapCodec.java │ │ │ ├── OffsetCodec.java │ │ │ ├── SetCodec.java │ │ │ ├── ShortCodec.java │ │ │ ├── StringCodec.java │ │ │ ├── Tuple1Codec.java │ │ │ ├── Tuple2Codec.java │ │ │ ├── Tuple3Codec.java │ │ │ ├── Tuple4Codec.java │ │ │ ├── Tuple5Codec.java │ │ │ ├── UuidCodec.java │ │ │ ├── Variant11Codec.java │ │ │ ├── Variant2Codec.java │ │ │ └── Variant3Codec.java │ │ ├── tuple/ │ │ │ ├── Tuple1.java │ │ │ ├── Tuple2.java │ │ │ ├── Tuple3.java │ │ │ ├── Tuple4.java │ │ │ └── Tuple5.java │ │ └── variant/ │ │ ├── Token.java │ │ ├── Variant11.java │ │ ├── Variant2.java │ │ └── Variant3.java │ ├── pom.xml.in │ ├── settings.gradle │ └── tests/ │ ├── TestAuxData.java │ ├── TestByteIntervals.java │ ├── TestIrSanity.java │ ├── TestModules.java │ ├── TestSections.java │ ├── TestSymbolicExpressions.java │ ├── TestSymbols.java │ ├── TestTuple.java │ └── TestVariant.java ├── proto/ │ ├── AuxData.proto │ ├── ByteInterval.proto │ ├── CFG.proto │ ├── CMakeLists.txt │ ├── CodeBlock.proto │ ├── DataBlock.proto │ ├── IR.proto │ ├── Module.proto │ ├── Offset.proto │ ├── ProxyBlock.proto │ ├── Section.proto │ ├── Symbol.proto │ ├── SymbolicExpression.proto │ └── v0/ │ ├── AuxData.proto │ ├── AuxDataContainer.proto │ ├── Block.proto │ ├── ByteMap.proto │ ├── CFG.proto │ ├── CMakeLists.txt │ ├── DataObject.proto │ ├── IR.proto │ ├── ImageByteMap.proto │ ├── Module.proto │ ├── Offset.proto │ ├── ProxyBlock.proto │ ├── Section.proto │ ├── Symbol.proto │ └── SymbolicExpression.proto ├── python/ │ ├── CMakeLists.txt │ ├── README.md │ ├── gtirb/ │ │ ├── __init__.py │ │ ├── auxdata.py │ │ ├── block.py │ │ ├── byteinterval.py │ │ ├── cfg.py │ │ ├── ir.py │ │ ├── lazyintervaltree.py │ │ ├── module.py │ │ ├── node.py │ │ ├── offset.py │ │ ├── proto/ │ │ │ └── __init__.py │ │ ├── section.py │ │ ├── serialization.py │ │ ├── symbol.py │ │ ├── symbolicexpression.py │ │ └── util.py │ ├── mypy.ini.in │ ├── pyproject.toml.in │ ├── requirements-dev.txt │ ├── requirements-mypy.txt │ ├── stubs/ │ │ ├── README.md │ │ ├── intervaltree/ │ │ │ ├── __init__.pyi │ │ │ ├── interval.pyi │ │ │ └── intervaltree.pyi │ │ ├── networkx/ │ │ │ ├── __init__.pyi │ │ │ └── classes/ │ │ │ ├── __init__.pyi │ │ │ └── multidigraph.pyi │ │ └── sortedcontainers/ │ │ ├── __init__.pyi │ │ └── sorteddict.pyi │ ├── tests/ │ │ ├── hello.gtirb │ │ ├── helpers.py │ │ ├── test_auxdata.py │ │ ├── test_block.py │ │ ├── test_blocks_at.py │ │ ├── test_blocks_at_offset.py │ │ ├── test_blocks_on.py │ │ ├── test_blocks_on_offset.py │ │ ├── test_byte_intervals_at.py │ │ ├── test_byte_intervals_on.py │ │ ├── test_cfg.py │ │ ├── test_deep_eq.py │ │ ├── test_ir.py │ │ ├── test_module.py │ │ ├── test_node_from_uuid.py │ │ ├── test_properties.py │ │ ├── test_repr.py │ │ ├── test_section.py │ │ ├── test_serialization.py │ │ ├── test_symbolic_expression.py │ │ ├── test_symbolic_expressions_at.py │ │ └── test_wrapper.py │ ├── tox.ini │ └── version.py.in ├── resources/ │ └── windows_version_resource.rc.in ├── src/ │ ├── AuxData.cpp │ ├── AuxDataContainer.cpp │ ├── ByteInterval.cpp │ ├── CFG.cpp │ ├── CFGSerialization.hpp │ ├── CMakeLists.txt │ ├── CodeBlock.cpp │ ├── Context.cpp │ ├── DataBlock.cpp │ ├── ErrorOr.cpp │ ├── IR.cpp │ ├── Module.cpp │ ├── Node.cpp │ ├── Offset.cpp │ ├── ProxyBlock.cpp │ ├── Section.cpp │ ├── Serialization.cpp │ ├── Serialization.hpp │ ├── Symbol.cpp │ ├── SymbolicExpression.cpp │ ├── SymbolicExpressionSerialization.hpp │ ├── Utility.cpp │ ├── gtirb/ │ │ └── proto/ │ │ └── CMakeLists.txt │ └── test/ │ ├── Addr.test.cpp │ ├── Allocator.test.cpp │ ├── AuxData.test.cpp │ ├── AuxDataContainer.test.cpp │ ├── AuxDataContainerSchema.hpp │ ├── AuxDataSchemaRegistration.test.cpp │ ├── ByteInterval.test.cpp │ ├── CFG.test.cpp │ ├── CMakeLists.txt │ ├── CodeBlock.test.cpp │ ├── DataBlock.test.cpp │ ├── IR.test.cpp │ ├── Main.test.cpp │ ├── Main.test.hpp │ ├── MergeSortedIterator.test.cpp │ ├── Module.test.cpp │ ├── Node.test.cpp │ ├── Offset.test.cpp │ ├── PrepDeathTest.hpp │ ├── PrepTestGTIRB.cpp │ ├── ProxyBlock.test.cpp │ ├── Section.test.cpp │ ├── SerializationTestHarness.hpp │ ├── Symbol.test.cpp │ ├── SymbolicExpression.test.cpp │ ├── TestHelpers.hpp │ ├── TypedNodeTest.cpp │ ├── UtilsDeprecatedGlobals.test.cpp │ ├── UtilsUsingGtirbNamespace.test.cpp │ ├── config-test.h.in │ ├── runtests.cmake │ ├── testInputBinary/ │ │ ├── CMakeLists.txt │ │ └── TestInputBinary.cpp │ └── testInterop/ │ ├── CMakeLists.txt │ ├── test_floats.cpp │ ├── test_floats.py │ ├── test_variants.cpp │ └── test_variants.py └── version.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: .ci/Dockerfile.static ================================================ FROM ubuntu:20.04 ARG BOOST_VERSION=1_68_0 SHELL ["/bin/bash", "-c"] # Install apt packages RUN export DEBIAN_FRONTEND=noninteractive RUN ln -fs /usr/share/zoneinfo/America/New_York /etc/localtime RUN apt-get -y update && \ apt-get -y install \ autoconf \ build-essential \ clang \ clang-format \ cmake \ curl \ git \ libprotobuf-dev \ libprotoc-dev \ libtool \ protobuf-compiler \ unzip \ wget \ software-properties-common RUN TARBALL=boost_${BOOST_VERSION}.tar.bz2 && \ curl -L https://archives.boost.io/release/1.68.0/source/${TARBALL} \ -o /tmp/${TARBALL} --fail && \ tar xf /tmp/${TARBALL} --one-top-level=/tmp && \ cd /tmp/boost_${BOOST_VERSION} && \ ./bootstrap.sh --prefix=/usr/local && \ ./b2 include=/usr/include/python3.8 link=static install && \ cd / && \ rm -rf /tmp/${TARBALL} /tmp/boost_${BOOST_VERSION} ================================================ FILE: .ci/Dockerfile.ubuntu20 ================================================ FROM ubuntu:20.04 # Install apt packages ENV VIRTUAL_ENV=/opt/venv ENV PATH="$VIRTUAL_ENV/bin:$PATH" RUN apt-get -y update && \ DEBIAN_FRONTEND=noninteractive \ apt-get -y install \ autoconf \ build-essential \ clang \ clang-format \ cmake \ curl \ default-jdk \ doxygen \ elpa-paredit \ emacs-nox \ git \ graphviz \ libprotobuf-dev \ libprotoc-dev \ libtool \ libboost-dev \ maven \ protobuf-compiler \ python3 \ python3-pip \ python3-setuptools \ python3-venv \ wget \ software-properties-common \ sbcl \ slime && \ python3 -m venv $VIRTUAL_ENV && \ python3 -m pip install --upgrade pip # The default version of maven from the ubuntu repositories contains a bug that # causes warnings about illegal reflective accesses. The build on apache's # website fixes this bug, so we use that build instead. RUN wget https://archive.apache.org/dist/maven/maven-3/3.9.3/binaries/apache-maven-3.9.3-bin.tar.gz -P /tmp RUN tar xf /tmp/apache-maven-*.tar.gz -C /opt RUN update-alternatives --install /usr/bin/mvn mvn /opt/apache-maven-3.9.3/bin/mvn 392 # Install python dependencies COPY python/requirements-dev.txt /tmp/requirements-dev.txt RUN pip3 install -r /tmp/requirements-dev.txt ================================================ FILE: .ci/Dockerfile.ubuntu22 ================================================ FROM ubuntu:22.04 ENV VIRTUAL_ENV=/opt/venv ENV PATH="$VIRTUAL_ENV/bin:$PATH" RUN apt-get -y update && \ DEBIAN_FRONTEND=noninteractive \ apt-get -y install \ autoconf \ build-essential \ clang \ clang-format \ cmake \ curl \ default-jdk \ doxygen \ elpa-paredit \ emacs-nox \ git \ graphviz \ libprotobuf-dev \ libprotoc-dev \ libtool \ libboost-dev \ maven \ protobuf-compiler \ python3 \ python3-pip \ python3-setuptools \ python3-venv \ wget \ software-properties-common \ sbcl \ slime && \ python3 -m venv $VIRTUAL_ENV && \ python3 -m pip install --upgrade pip COPY python/requirements-dev.txt /tmp/requirements-dev.txt RUN pip3 install -r /tmp/requirements-dev.txt ================================================ FILE: .ci/Dockerfile.ubuntu24 ================================================ FROM ubuntu:24.04 ENV VIRTUAL_ENV=/opt/venv ENV PATH="$VIRTUAL_ENV/bin:$PATH" RUN apt-get -y update && \ DEBIAN_FRONTEND=noninteractive \ apt-get -y install \ autoconf \ build-essential \ clang \ clang-format \ cmake \ curl \ default-jdk \ doxygen \ elpa-paredit \ emacs-nox \ git \ graphviz \ libprotobuf-dev \ libprotoc-dev \ libtool \ libboost-dev \ # As of 24.04.1 LTS, the clang package depends on libstdc++-13-dev but # appears to be compiled for libstdc++-14-dev, which we need to install # explicitly here. libstdc++-14-dev \ maven \ protobuf-compiler \ python3 \ python3-pip \ python3-setuptools \ python3-venv \ wget \ software-properties-common \ sbcl \ slime && \ python3 -m venv $VIRTUAL_ENV && \ python3 -m pip install --upgrade pip COPY python/requirements-dev.txt /tmp/requirements-dev.txt RUN pip3 install -r /tmp/requirements-dev.txt ================================================ FILE: .ci/PKGBUILD ================================================ # Contributor: Eric Schulte # Maintainer: Eric Schulte _srcname=gtirb pkgname=gtirb-git pkgver=v1.4.7.r0.gb3094954 pkgrel=1 pkgdesc="GrammaTech Intermediate Representation for Binaries" arch=('x86_64') url="https://github.com/grammatech/gtirb" license=('MIT') depends=('protobuf' 'python-networkx') makedepends=('git' 'cmake' 'python' 'doxygen' 'graphviz' 'boost') provides=('gtirb') source=('git://github.com/grammatech/gtirb.git') sha512sums=('SKIP') pkgver() { cd "$_srcname" git describe --long --tags --exclude "gt/*" | sed 's/\([^-]*-g\)/r\1/;s/[-:/ ]/./g' } build() { cd "$_srcname/" cmake . -Bbuild -DCMAKE_INSTALL_PREFIX=/usr -DGTIRB_CL_API=OFF \ -DCMAKE_BUILD_TYPE=${BUILD_TYPE-RelWithDebInfo} \ -DCMAKE_CXX_COMPILER=${COMPILER-g++} cmake --build build --target all doc } package() { cd "$_srcname/" make -Cbuild DESTDIR="$pkgdir" install mkdir -p "$pkgdir"/usr/share/doc/$_srcname cp -R build/doc/html/ "$pkgdir"/usr/share/doc/$_srcname } ================================================ FILE: .ci/adjust-coverage-paths.py ================================================ #!/usr/bin/env python3 import os from argparse import ArgumentParser from pathlib import Path from xml.etree import ElementTree parser = ArgumentParser( description=""" Adjusts the source paths in coverage.xml to satisfy GitLab's expectations. Specifically, it removes the build tree prefix from the sources and replaces it with the source tree prefix. These prefixes are determined via command-line arguments or CI environment variables. """ ) parser.add_argument( "coverage", metavar="coverage.xml", type=Path, help="path of cobertura coverage file to fix", ) group = parser.add_mutually_exclusive_group(required=True) group.add_argument( "-i", "--in-place", action="store_true", help="rewrite coverage in place" ) group.add_argument( "-o", "--output", metavar="file", help="write modified coverage to file" ) parser.add_argument( "--source-dir", metavar="path", default=os.environ.get("CI_PROJECT_DIR"), help="root of the GTIRB repository", ) parser.add_argument( "--build-dir", metavar="path", type=Path, help="path where coverage was run (default coverage.xml parent directory)", ) args = parser.parse_args() if args.source_dir is None: parser.error("either --source-dir or CI_PROJECT_DIR is required") args.source_dir = Path(args.source_dir).resolve() if args.build_dir is None: args.build_dir = args.coverage.parent args.build_dir = args.build_dir.resolve() et = ElementTree.parse(args.coverage) for source in et.iter("source"): if source.text: relpath = Path(source.text).relative_to(args.build_dir) fixed = str(Path(args.source_dir, relpath)) print("mapping", source.text, "to", fixed) source.text = fixed if args.output: et.write(args.output) else: et.write(args.coverage) ================================================ FILE: .ci/build.py ================================================ #!/usr/bin/env python import subprocess import sys import conanfile def run_conan(args): cmd = ["conan"] + args print("running: %s" % " ".join(cmd)) sys.stdout.flush() subprocess.check_call(cmd) def build(argv): props = conanfile.Properties() run_conan(["create", ".", props.conan_ref] + argv) archived_channels = props.archived_channels if props.conan_channel in archived_channels: run_conan( ["upload", props.conan_recipe, "--all", "--remote", "gitlab"] ) else: print( "Conan channel not archived. Update archived_branches in " "conanfile.py to get archival." ) print("archived channels: ") print(*archived_channels, sep=", ") print("channel built: " + props.conan_channel) if __name__ == "__main__": build(sys.argv[1:]) ================================================ FILE: .ci/gitlab-ci.yml ================================================ variables: # The IMAGE_TAG is derived from the branch name so that if a branch modifies # the CI images, it builds and runs using the new images without conflicting # with master. IMAGE_TAG: "$CI_COMMIT_REF_SLUG" GIT_SUBMODULE_STRATEGY: recursive EXTRA_INDEX_URL: https://__token__:$GL_PKG_API_TOKEN@git.grammatech.com/api/v4/projects/1587/packages/pypi/simple # The follow two variables are used by the package-uploader. PROJECT_ID: $CI_PROJECT_ID PIPELINE_ID: $CI_PIPELINE_ID CONAN_PASSWORD: $CI_JOB_TOKEN CONAN_VERSION: "1.59" # Limit build parallelism to avoid overwhelming CI servers. MAKE_JOBS: 8 stages: - build-images - prebuild - build - build-installers - test-setup - test-packages1 - test-packages2 - deploy - deploy-apt - upload default: tags: [shared] .build-ci-image: &build-ci-image stage: build-images image: name: quay.io/buildah/stable script: # Configure authentication credentials for GitLab - buildah login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY" - buildah login -u "$DOCKERHUB_USERNAME" -p "$DOCKERHUB_PASSWORD" https://index.docker.io/v1/ # Build our image (using a cache if available) - >- buildah build -f "$CI_PROJECT_DIR/$DOCKERFILE_PATH" --cache-from "$DOCKER_REGISTRY/$CI_PROJECT_PATH/$IMAGE_NAME/cache" --cache-to "$DOCKER_REGISTRY/$CI_PROJECT_PATH/$IMAGE_NAME/cache" --layers --tag "$DOCKER_REGISTRY/$CI_PROJECT_PATH/$IMAGE_NAME:$IMAGE_TAG" "$CI_PROJECT_DIR" # Push to our internal registry - buildah push "$DOCKER_REGISTRY/$CI_PROJECT_PATH/$IMAGE_NAME:$IMAGE_TAG" # If we're on the master branch, also push the latest tag. - >- if [ "$CI_COMMIT_BRANCH" = "$CI_DEFAULT_BRANCH" ]; then buildah push "$DOCKER_REGISTRY/$CI_PROJECT_PATH/$IMAGE_NAME:$IMAGE_TAG" \ "$DOCKER_REGISTRY/$CI_PROJECT_PATH/$IMAGE_NAME:latest" fi build-ci-image-ubuntu20: <<: *build-ci-image variables: DOCKERFILE_PATH: .ci/Dockerfile.ubuntu20 IMAGE_NAME: ubuntu20 build-ci-image-ubuntu22: <<: *build-ci-image variables: DOCKERFILE_PATH: .ci/Dockerfile.ubuntu22 IMAGE_NAME: ubuntu22 build-ci-image-ubuntu24: <<: *build-ci-image variables: DOCKERFILE_PATH: .ci/Dockerfile.ubuntu24 IMAGE_NAME: ubuntu24 build-ci-image-static: <<: *build-ci-image variables: DOCKERFILE_PATH: .ci/Dockerfile.static IMAGE_NAME: static check-format: stage: prebuild image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu20:$IMAGE_TAG script: - |+ pre-commit run --all-files --show-diff-on-failure || ( (cat < cmake ../ -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_COMPILER=${CXX_COMPILER:-g++} -DCPACK_DEBIAN_PACKAGE_RELEASE="$(lsb_release -sc)" -DENABLE_CODE_COVERAGE=${ENABLE_CODE_COVERAGE:-OFF} -DGTIRB_BUILD_SHARED_LIBS=${GTIRB_BUILD_SHARED_LIBS:-ON} -DGTIRB_PACKAGE_POLICY=$PACKAGE_POLICY -DGTIRB_RELEASE_VERSION=$RELEASE_VERSION -DGTIRB_STRIP_DEBUG_SYMBOLS=On -DGTIRB_CL_API=${GTIRB_CL_API:-OFF} -DGTIRB_CXX_API=${GTIRB_CXX_API:-OFF} -DGTIRB_JAVA_API=${GTIRB_JAVA_API:-OFF} -DGTIRB_PY_API=${GTIRB_PY_API:-OFF} - make -j${MAKE_JOBS} - ctest --output-on-failure build-ubuntu20-gcc: variables: IMAGE_NAME: 'ubuntu20' GTIRB_CXX_API: 'ON' CXX_COMPILER: 'g++' CPACK_GENERATOR: 'DEB' BUILD_TYPE: 'RelWithDebInfo' PACKAGE_POLICY: "unix" <<: *build build-ubuntu22-gcc: variables: IMAGE_NAME: 'ubuntu22' GTIRB_CXX_API: 'ON' CXX_COMPILER: 'g++' CPACK_GENERATOR: 'DEB' BUILD_TYPE: 'RelWithDebInfo' PACKAGE_POLICY: "unix" <<: *build build-ubuntu24-gcc: variables: IMAGE_NAME: 'ubuntu24' GTIRB_CXX_API: 'ON' CXX_COMPILER: 'g++' CPACK_GENERATOR: 'DEB' BUILD_TYPE: 'RelWithDebInfo' PACKAGE_POLICY: "unix" <<: *build build-ubuntu24-gcc-debug: variables: IMAGE_NAME: 'ubuntu24' GTIRB_CXX_API: 'ON' CXX_COMPILER: 'g++' BUILD_TYPE: 'Debug' PACKAGE_POLICY: "unix" <<: *build build-ubuntu24-clang-debug: variables: IMAGE_NAME: 'ubuntu24' GTIRB_CXX_API: 'ON' CXX_COMPILER: 'clang++' BUILD_TYPE: 'Debug' PACKAGE_POLICY: "unix" <<: *build build-static: variables: IMAGE_NAME: 'static' GTIRB_CXX_API: 'ON' CXX_COMPILER: 'g++' BUILD_TYPE: 'RelWithDebInfo' GTIRB_BUILD_SHARED_LIBS: 'OFF' <<: *build build-java: variables: IMAGE_NAME: 'ubuntu24' GTIRB_JAVA_API: 'ON' <<: *build build-lisp: variables: IMAGE_NAME: 'ubuntu20' GTIRB_CL_API: 'ON' <<: *build build-ubuntu20-python: variables: IMAGE_NAME: 'ubuntu20' GTIRB_PY_API: 'ON' <<: *build build-ubuntu24-python: variables: IMAGE_NAME: 'ubuntu24' GTIRB_PY_API: 'ON' <<: *build generate-coverage: variables: IMAGE_NAME: 'ubuntu24' BUILD_TYPE: 'Debug' CXX_COMPILER: 'g++' ENABLE_CODE_COVERAGE: 'ON' GTIRB_CXX_API: 'ON' GTIRB_PY_API: 'ON' artifacts: # Upload coverage reports and source for report-coverage-* jobs to use. paths: - build/**/*.gcno - build/**/*.gcda - build/python/.coverage* - build/**/*.py - build/**/*.h - build/**/*.cc - build/**/*.cpp before_script: - pip install "gcovr>=8.6" coverage <<: *build # > If there is more than one matched line in the job output, the last line is used # https://docs.gitlab.com/ee/ci/yaml/index.html#coverage # We output the two coverage numbers in separate jobs(report-coverage-*) so # that both can be reported to GitLab. report-coverage-cpp: stage: build needs: [generate-coverage] image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu24:$IMAGE_TAG coverage: '/^TOTAL.*\s+(\d+\%)$/' script: - pip install "gcovr>=8.6" - cd build - gcovr --exclude=googletest-src --exclude=doc --exclude=..*/proto --exclude=..*/test --root .. report-coverage-py: stage: build needs: [generate-coverage] image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu24:$IMAGE_TAG coverage: '/^TOTAL.*\s+(\d+\%)$/' artifacts: reports: coverage_report: coverage_format: cobertura path: build/python/coverage.xml script: - pip install -r python/requirements-dev.txt - cd build/python - tox run -e report - ../../.ci/adjust-coverage-paths.py --in-place coverage.xml --build-dir .. # The build artifact timestamps may be out-of-date relative to the newly cloned # repository for later jobs. These commands will update the timestamps to bring # them up to date without actually building much. This is significantly faster # than rebuilding out-of-date files in the later jobs. .update-artifact-timestamps: &update-artifact-timestamps - make -C build --touch python-wheel: stage: build-installers needs: [build-ubuntu24-python] image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu24:$IMAGE_TAG artifacts: name: "$CI_COMMIT_REF_NAME-$CI_JOB_NAME" paths: - gtirb-*-py*-none-any.whl script: - *update-artifact-timestamps - pip3 wheel --no-deps build/python python-wheel-unstable: stage: build-installers needs: [python-wheel] image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu24:$IMAGE_TAG rules: - if: '$CI_COMMIT_BRANCH == "master"' artifacts: name: "$CI_COMMIT_REF_NAME-$CI_JOB_NAME" paths: - gtirb-unstable-py3-none-any.whl script: - cp gtirb-*-py*-none-any.whl ./gtirb-unstable-py3-none-any.whl test-capstone-example: stage: test-packages1 image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu24:$IMAGE_TAG needs: ['build-ubuntu24-gcc'] script: - *update-artifact-timestamps - cd build - '[[ ! -f bin/ex-jumps ]]' - apt-get install -y libcapstone-dev - cmake .. - make -j${MAKE_JOBS} - '[[ -f bin/ex-jumps ]]' test-default-install: stage: test-packages1 image: $DOCKER_REGISTRY/rewriting/gtirb/$IMAGE_NAME:$IMAGE_TAG variables: IMAGE_NAME: 'ubuntu24' COMPILER: 'g++-9' needs: ['build-ubuntu24-gcc'] script: - *update-artifact-timestamps - make -C build install && rm -rf build - LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH .ci/test-install-all.sh test-default-install-static: stage: test-packages1 image: $DOCKER_REGISTRY/rewriting/gtirb/static:$IMAGE_TAG needs: ['build-static'] script: - *update-artifact-timestamps - '[ -e build/lib/libgtirb.a ]' - '[ ! -e build/lib/libgtirb.so ]' - make -C build install - rm -rf build - LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH .ci/test-install-static.sh test-install-different-prefix: stage: test-packages1 image: $DOCKER_REGISTRY/rewriting/gtirb/$IMAGE_NAME:$IMAGE_TAG variables: IMAGE_NAME: 'ubuntu24' COMPILER: 'g++-9' needs: ['build-ubuntu24-gcc'] script: - *update-artifact-timestamps - cmake . -B build -DCMAKE_INSTALL_PREFIX=/tmp/prefix - make -C build install - rm -rf build - > CPPFLAGS=-I/tmp/prefix/include LDFLAGS=-L/tmp/prefix/lib LD_LIBRARY_PATH=/tmp/prefix/lib .ci/test-install-all.sh test-install-with-destdir: stage: test-packages1 image: $DOCKER_REGISTRY/rewriting/gtirb/$IMAGE_NAME:$IMAGE_TAG variables: IMAGE_NAME: 'ubuntu24' COMPILER: 'g++-9' needs: ['build-ubuntu24-gcc'] script: - *update-artifact-timestamps - DESTDIR=/tmp/destdir make -C ./build install - > CPPFLAGS=-I/tmp/destdir/usr/local/include LDFLAGS=-L/tmp/destdir/usr/local/lib LD_LIBRARY_PATH=/tmp/destdir/usr/local/lib .ci/test-install-all.sh test-interop: stage: test-packages1 needs: ['build-ubuntu24-gcc', 'python-wheel'] image: ${DOCKER_REGISTRY}/rewriting/gtirb/ubuntu24:$IMAGE_TAG script: - *update-artifact-timestamps - pip3 install gtirb-*-py*-none-any.whl - .ci/test-interop.sh .conan-linux: &conan-linux stage: deploy needs: [check-format] image: $DOCKER_REGISTRY/rewriting/gtirb/$IMAGE_NAME:$IMAGE_TAG script: - python3 -m pip install --upgrade conan~=$CONAN_VERSION - conan profile new default --detect - conan profile update settings.compiler.libcxx=libstdc++11 default - conan remote add gitlab ${CI_API_V4_URL}/packages/conan - conan user ci_user -r gitlab -p - export PYTHONPATH="$(pwd)" - python3 .ci/build.py conan-linux-gcc9: variables: IMAGE_NAME: ubuntu20 <<: *conan-linux conan-linux-gcc11: variables: IMAGE_NAME: ubuntu22 <<: *conan-linux .conan-windows: &conan-windows stage: deploy needs: [check-format] tags: [ddisasm-windows] artifacts: name: "$CI_COMMIT_REF_NAME-$CI_JOB_NAME" paths: - gtirb*.zip script: # Setting CI_PROJECT_DIR to $(pwd) because CI doesn't properly set CI_PROJECT_DIR with VirtualBox runners. - export CI_PROJECT_DIR=`cygpath -w $(pwd)` # Initialize - systeminfo - python -m pip install --upgrade conan~=$CONAN_VERSION - export PYTHONPATH=$CI_PROJECT_DIR # Setup Remote and Authenticate - conan remote add gitlab ${CI_API_V4_URL}/packages/conan - conan user ci_user -r gitlab -p # Build - export GTIRB_DISABLE_PARALLEL_BUILD=1 - python .ci/build.py -s build_type=$BUILD_TYPE # Install # Python print() on Windows returns CRLF and will cause issues in CI - use "print('string', end='')" when outputting from python. - export PKG_INSTALL_NAME=`python -c "import conanfile; print(conanfile.Properties().conan_recipe, end='')"` - conan install $PKG_INSTALL_NAME -g deploy --build=missing --install-folder="./packages" # Package - export PKG_NAME=`conan inspect . --raw name` - export PKG_VERSION=`conan inspect . --raw version` - export PKG_ARCH=`uname -m` - cd ./packages # Library Package - export PKG_FILENAME="${CI_PROJECT_DIR}\\${PKG_NAME}-${PKG_VERSION}.win10.${PKG_ARCH}.zip" - export PKG_MANIFEST=( "gtirb/bin/gtirb.dll" "gtirb/licenses/LICENSE.txt" ) - zip -r $PKG_FILENAME ${PKG_MANIFEST[@]} # Development Package - export PKG_FILENAME_DEV="${CI_PROJECT_DIR}\\${PKG_NAME}-dev-${PKG_VERSION}.win10.${PKG_ARCH}.zip" - export PKG_MANIFEST_DEV=( "${PKG_MANIFEST[@]}" "gtirb/lib/gtirb.lib" "gtirb/include/gtirb" ) - zip -r $PKG_FILENAME_DEV ${PKG_MANIFEST_DEV[@]} conan-windows-debug: variables: BUILD_TYPE: Debug <<: *conan-windows conan-windows-release: variables: BUILD_TYPE: Release <<: *conan-windows conan-windows-32: stage: deploy tags: [ddisasm-windows] needs: [] variables: ARCHITECTURE: x64 script: - systeminfo - export PATH="C:\\Program Files\\Python38;$PATH" - export PYTHONPATH="$(cygpath -w $(pwd))" # Install conan - python -m pip install --upgrade conan~=$CONAN_VERSION # Setup Remote and Authenticate - conan remote add gitlab ${CI_API_V4_URL}/packages/conan - conan user ci_user -r gitlab -p # The boost options disable building boost with libiconv. This is important, because we can't get the package to build with --build=libiconv. - CI_PROJECT_DIR=$(cygpath -w $(pwd)) python .ci/build.py -s arch=x86 -s compiler.runtime=MT --build=protobuf -o protobuf:with_zlib=False --build=gtirb --build=boost -o boost:zlib=False -o boost:bzip2=False -o boost:without_locale=True -o boost:without_log=True --build=missing external-pypi: stage: deploy needs: [build-ubuntu24-gcc, python-wheel] image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu24:$IMAGE_TAG rules: - if: '$CI_COMMIT_REF_NAME =~ /^release-.*/' script: - pip3 install twine wheel setuptools pkginfo --upgrade - pip3 install gtirb-*-py*-none-any.whl - GTIRB_VERSION=$(python3 -c "import gtirb; print(gtirb.__version__)") # We won't be releasing our dev packages externally, so fail if this is a # .dev package. - if [[ "$GTIRB_VERSION" =~ \.dev[[:digit:]]*(\+.*)?$ ]]; then exit 1; fi - twine check gtirb-*-py*-none-any.whl - twine upload gtirb-*-py*-none-any.whl -u __token__ -p $PYPI_API_KEY # On master, we only upload and overwrite `.dev` versions of the python # package. Non-.dev versions should only be uploaded on versioned # release-.* branches. This is so the versioning of our python packages # coincides with that of our conan packages, where dev packages are # produced from master, and stable, versioned packages are produced # on release-.* branches. internal-pypi: stage: deploy needs: [build-ubuntu24-gcc, python-wheel] image: python:3.9 rules: - if: '$CI_COMMIT_BRANCH == "master"' - if: '$CI_COMMIT_REF_NAME =~ /^release-.*/' script: - pip3 install twine wheel setuptools pkginfo --upgrade - wget https://git.grammatech.com/research/templates/python-module/raw/master/.pypirc - sed "s/password = /password = $GL_PKG_API_TOKEN/" .pypirc > ~/.pypirc - pip3 install gtirb-*-py*-none-any.whl - NEW_VERSION=$(python3 -c "import gtirb; print(gtirb.__version__)") - if [[ "$NEW_VERSION" =~ \.dev[[:digit:]]*(\+.*)?$ && "$CI_COMMIT_REF_NAME" =~ ^release-.* ]]; then exit 1; fi - if [[ "$CI_COMMIT_BRANCH" == "master" ]]; then if [[ ! "$NEW_VERSION" =~ \.dev[[:digit:]]*$ ]]; then echo "[ERROR] Only .dev versions can be uploaded from the master branch."; exit 1; fi; if pip3 install --extra-index-url=$EXTRA_INDEX_URL "gtirb>$NEW_VERSION" 2>/dev/null; then echo "[ERROR] The package version being published on master should always be >= the version in the repository."; exit 1; fi; wget https://git.grammatech.com/research/templates/python-module/raw/master/delete_remote_packages.py; python3 delete_remote_packages.py $GL_PKG_API_TOKEN gtirb-*-py*-none-any.whl; fi - twine check gtirb-*-py*-none-any.whl - twine upload --verbose --repository repypi gtirb-*-py*-none-any.whl # Apt packaging related jobs .build-ubuntu-packages: &build-ubuntu-packages stage: build-installers artifacts: name: "$CI_COMMIT_REF_NAME-$CI_JOB_NAME" paths: - build script: - pip3 install setuptools wheel --upgrade - cd build - cpack -G "DEB" -D CPACK_GTIRB_PACKAGE=debian-lib; - cpack -G "DEB" -D CPACK_GTIRB_PACKAGE=debian-dev; - cpack -G "DEB" -D CPACK_GTIRB_PACKAGE=debian-debug; build-ubuntu20-packages: needs: [build-ubuntu20-gcc] image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu20:$IMAGE_TAG <<: *build-ubuntu-packages build-ubuntu22-packages: needs: [build-ubuntu22-gcc] image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu22:$IMAGE_TAG <<: *build-ubuntu-packages build-ubuntu24-packages: needs: [build-ubuntu24-gcc] image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu24:$IMAGE_TAG <<: *build-ubuntu-packages .setup-test-apt-repo: &setup-test-apt-repo stage: test-setup artifacts: name: "$CI_COMMIT_REF_NAME-$CI_JOB_NAME" paths: - apt-repo script: - mkdir apt-repo - cp build/*.deb apt-repo - cd apt-repo && dpkg-scanpackages . /dev/null > Packages .test-ubuntu-libgtirb-dev: &test-ubuntu-libgtirb-dev stage: test-packages1 artifacts: name: "$CI_COMMIT_REF_NAME-$CI_JOB_NAME" paths: - test-install script: - echo -e "\ndeb [trusted=yes] file:$(pwd)/apt-repo ./\n" >> /etc/apt/sources.list - cat /etc/apt/sources.list - 'TEST_PKG_NAME=$(dpkg --info apt-repo/libgtirb-dev_*.deb | sed -n "s/Package: //p")' - apt-get update -y && apt-get install -y --allow-unauthenticated $TEST_PKG_NAME - cp .ci/test-install.cpp ./ - g++ test-install.cpp -std=c++17 -o test-install -lgtirb -lstdc++ - ./test-install .test-ubuntu-libgtirb: &test-ubuntu-libgtirb stage: test-packages2 script: - echo -e "\ndeb [trusted=yes] file:$(pwd)/apt-repo ./\n" >> /etc/apt/sources.list - 'TEST_PKG_NAME=$(dpkg --info apt-repo/libgtirb_*.deb | sed -n "s/Package: //p")' - apt-get update -y && apt-get install -y --allow-unauthenticated $TEST_PKG_NAME - ./test-install .test-ubuntu-libgtirb-dbg: &test-ubuntu-libgtirb-dbg stage: test-packages1 script: - echo -e "\ndeb [trusted=yes] file:$(pwd)/apt-repo ./\n" >> /etc/apt/sources.list - 'TEST_PKG_NAME=$(dpkg --info apt-repo/libgtirb-dbg_*.deb | sed -n "s/Package: //p")' - apt-get update -y && apt-get install -y --allow-unauthenticated $TEST_PKG_NAME - '[ -f /usr/lib/debug/.build-id/$(readelf -n /usr/lib/libgtirb.so | grep ''Build ID: '' | cut -d":" -f2 | sed -E ''s/ ([a-f0-9]{2,})([a-f0-9]{30,})/\1\/\2/g'').debug ];' setup-ubuntu20-test-apt-repo: image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu20:$IMAGE_TAG needs: [build-ubuntu20-packages] <<: *setup-test-apt-repo setup-ubuntu22-test-apt-repo: image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu22:$IMAGE_TAG needs: [build-ubuntu22-packages] <<: *setup-test-apt-repo setup-ubuntu24-test-apt-repo: image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu24:$IMAGE_TAG needs: [build-ubuntu24-packages] <<: *setup-test-apt-repo test-ubuntu20-libgtirb-dev: image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu20:$IMAGE_TAG needs: [setup-ubuntu20-test-apt-repo] <<: *test-ubuntu-libgtirb-dev test-ubuntu22-libgtirb-dev: image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu22:$IMAGE_TAG needs: [setup-ubuntu22-test-apt-repo] <<: *test-ubuntu-libgtirb-dev test-ubuntu24-libgtirb-dev: image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu24:$IMAGE_TAG needs: [setup-ubuntu24-test-apt-repo] <<: *test-ubuntu-libgtirb-dev test-ubuntu20-libgtirb-dbg: image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu20:$IMAGE_TAG needs: [setup-ubuntu20-test-apt-repo] <<: *test-ubuntu-libgtirb-dbg test-ubuntu22-libgtirb-dbg: image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu22:$IMAGE_TAG needs: [setup-ubuntu22-test-apt-repo] <<: *test-ubuntu-libgtirb-dbg test-ubuntu24-libgtirb-dbg: image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu24:$IMAGE_TAG needs: [setup-ubuntu24-test-apt-repo] <<: *test-ubuntu-libgtirb-dbg test-ubuntu20-libgtirb: image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu20:$IMAGE_TAG needs: [setup-ubuntu20-test-apt-repo,test-ubuntu20-libgtirb-dev] <<: *test-ubuntu-libgtirb test-ubuntu22-libgtirb: image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu22:$IMAGE_TAG needs: [setup-ubuntu22-test-apt-repo,test-ubuntu22-libgtirb-dev] <<: *test-ubuntu-libgtirb test-ubuntu24-libgtirb: image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu24:$IMAGE_TAG needs: [setup-ubuntu24-test-apt-repo,test-ubuntu24-libgtirb-dev] <<: *test-ubuntu-libgtirb .debian-installer: &debian-installer stage: deploy artifacts: name: "$CI_COMMIT_REF_NAME-$CI_JOB_NAME" paths: - '*gtirb*.deb' script: - cp build/*gtirb*.deb ./ debian-installer-ubuntu20: image: $DOCKER_REGISTRY/rewriting/gtirb/ubuntu20:$IMAGE_TAG needs: [test-ubuntu20-libgtirb, build-ubuntu20-packages] <<: *debian-installer .apt-upload: &apt-upload stage: deploy-apt trigger: project: rewriting/utility/package-uploader strategy: depend apt-public-ubuntu20-unstable: <<: *apt-upload rules: - if: '$CI_COMMIT_BRANCH == "master"' variables: JOB_NAME: debian-installer-ubuntu20 APT_REPO: public APT_REPO_CODENAMES: focal APT_REPO_COMPONENT: unstable apt-public-ubuntu20-stable: <<: *apt-upload rules: - if: '$CI_COMMIT_REF_NAME =~ /^release-.*/' variables: JOB_NAME: debian-installer-ubuntu20 APT_REPO: public APT_REPO_CODENAMES: focal APT_REPO_COMPONENT: stable apt-internal-ubuntu20-unstable: <<: *apt-upload rules: - if: '$CI_COMMIT_BRANCH == "master"' variables: JOB_NAME: debian-installer-ubuntu20 APT_REPO: internal APT_REPO_CODENAMES: focal APT_REPO_COMPONENT: unstable apt-internal-ubuntu20-stable: <<: *apt-upload rules: - if: '$CI_COMMIT_REF_NAME =~ /^release-.*/' variables: JOB_NAME: debian-installer-ubuntu20 APT_REPO: internal APT_REPO_CODENAMES: focal APT_REPO_COMPONENT: stable maven-central-upload: stage: deploy needs: [build-java] trigger: project: rewriting/utility/package-uploader strategy: depend rules: - if: '$CI_COMMIT_REF_NAME == "master"' - if: '$CI_COMMIT_REF_NAME =~ /^release-.*/' variables: JOB_NAME: build-java JAVA_POM_SUBDIR: ./build/java .windows-upload: &windows-upload stage: upload trigger: project: rewriting/utility/package-uploader strategy: depend needs: [conan-windows-release] windows-upload-public: <<: *windows-upload rules: - if: '$CI_COMMIT_REF_NAME =~ /^release-.*/' - if: '$CI_COMMIT_REF_NAME == "master"' variables: JOB_NAME: conan-windows-release FILESERVER: public FILESERVER_SUBDIR_NAME: windows-release windows-upload-internal: <<: *windows-upload rules: - if: '$CI_COMMIT_REF_NAME =~ /^release-.*/' - if: '$CI_COMMIT_REF_NAME == "master"' variables: JOB_NAME: conan-windows-release FILESERVER: internal FILESERVER_SUBDIR_NAME: windows-release wheel-upload-public: stage: upload trigger: project: rewriting/utility/package-uploader strategy: depend needs: [python-wheel-unstable] rules: - if: '$CI_COMMIT_REF_NAME == "master"' variables: JOB_NAME: python-wheel-unstable FILESERVER: public FILESERVER_SUBDIR_NAME: python ================================================ FILE: .ci/pre-commit ================================================ #!/bin/bash OUTPUT=$(git clang-format --diff) if [ "${OUTPUT}" == "no modified files to format" ] || [ "${OUTPUT}" == "clang-format did not modify any files" ];then exit 0 else echo "Run git clang-format, then commit." exit 1 fi ================================================ FILE: .ci/test-install-all.sh ================================================ #!/bin/sh -e set -o xtrace set -o nounset set -o errexit builddir=$(pwd) workdir=`mktemp -d` trap 'cd / ; rm -rf $workdir' EXIT cd $workdir # Compile and run a C++ file that links to libgtirb cp $builddir/.ci/test-install.cpp ./ make CXXFLAGS=-std=c++17 LDLIBS=-lgtirb test-install ./test-install ================================================ FILE: .ci/test-install-static.sh ================================================ #!/bin/sh -e set -o xtrace set -o nounset set -o errexit builddir=$(pwd) workdir=`mktemp -d` trap 'cd / ; rm -rf $workdir' EXIT cd $workdir # Compile and run a C++ file that links to libgtirb statically cp $builddir/.ci/test-install.cpp ./ make 'CXXFLAGS=-std=c++17' 'LDLIBS=-lgtirb -lgtirb_proto -lprotobuf -lpthread' test-install ./test-install ================================================ FILE: .ci/test-install.cpp ================================================ #include #include #include int main() { auto filename = std::tmpnam(nullptr); std::ofstream ofs{filename, std::ios_base::binary}; auto ctx1 = gtirb::Context(); auto ir1 = gtirb::IR::Create(ctx1); ir1->save(ofs); ofs.close(); std::ifstream ifs{filename, std::ios_base::binary}; auto ctx2 = gtirb::Context(); if (auto ir2 = gtirb::IR::load(ctx2, ifs)) { return ir2.get()->modules().empty() ? 0 : 1; }; return 1; } ================================================ FILE: .ci/test-install.lisp ================================================ (in-package :gtirb) (uiop/stream:with-temporary-file (:pathname path :keep nil) (let ((it (make-instance 'gtirb)) (test-string "Something.")) (push (cons "test" (make-instance 'aux-data)) (aux-data it)) (setf (aux-data-type (cdar (aux-data it))) :string (aux-data-data (cdar (aux-data it))) test-string) (write-gtirb it path) (assert (string= (aux-data-data (cdar (aux-data (read-gtirb path)))) test-string) (path) "AuxData in GTIRB at ~s holds ~s." path test-string))) ================================================ FILE: .ci/test-install.py ================================================ import sys import tempfile import gtirb filename = tempfile.mktemp() ir = gtirb.IR() ir.save_protobuf(filename) ir = gtirb.IR.load_protobuf(filename) sys.exit(len(ir.modules)) ================================================ FILE: .ci/test-interop.sh ================================================ #!/bin/bash set -e cd build failures=0 check() { local creator=$1 local auxdata=$2 local consumer=$3 shift 3 if "$@" ; then echo $creator $auxdata AuxData work in $consumer else failures=$(( failures + 1)) fi } ### floating-point compatiblity test bin/test_floats -w floats_cpp.gtirb python3 src/test/testInterop/test_floats.py -w floats_py.gtirb check python float c++ \ bin/test_floats -r floats_py.gtirb check c++ float python \ python3 src/test/testInterop/test_floats.py -r floats_cpp.gtirb rm floats_{cpp,py}.gtirb ### variant compatibility test bin/test_variants -w variants_cpp.gtirb python3 src/test/testInterop/test_variants.py -w variants_py.gtirb check c++ variant python \ python3 src/test/testInterop/test_variants.py -r variants_cpp.gtirb check python variant c++ \ bin/test_variants -r variants_py.gtirb rm variants_{cpp,py}.gtirb test $failures = 0 ================================================ FILE: .clang-format ================================================ --- Language: Cpp # BasedOnStyle: LLVM AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlinesLeft: false AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: false BinPackArguments: true BinPackParameters: true BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false BeforeCatch: false BeforeElse: false IndentBraces: false BreakBeforeBinaryOperators: None BreakBeforeBraces: Attach BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 80 CommentPragmas: '^ IWYU pragma:' ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] # IncludeBlocks: Regroup IncludeCategories: - Regex: '^".*"' Priority: 1 - Regex: '^.container" field, despite # what the Context Availability documentation says. See: # https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability - id: vars run: | export IMAGE_TAG=$([ "${{ github.ref_name }}" == "master" ] && echo latest || echo ${{ github.ref_name }} | sed -e "s/\//-/g") echo "image_tag=$IMAGE_TAG" >> $GITHUB_ENV echo "image_tag=$IMAGE_TAG" >> $GITHUB_OUTPUT export IMAGE_PATH=$(echo ghcr.io/${{ github.repository }}/ | awk '{print tolower($0)}') echo "image_path=$IMAGE_PATH" >> $GITHUB_ENV echo "image_path=$IMAGE_PATH" >> $GITHUB_OUTPUT - uses: actions/checkout@master - name: Build image env: image_reference: ${{ env.image_path }}${{ matrix.os }} run: | buildah login -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} ghcr.io buildah build \ -f .ci/Dockerfile.${{ matrix.file_suffix }} \ --cache-from ${{ env.image_reference }}-cache \ --cache-to ${{ env.image_reference }}-cache \ --layers \ --tag ${{ env.image_reference }}:${{ env.image_tag }} buildah push ${{ env.image_reference }}:${{ env.image_tag }} docs: runs-on: ubuntu-latest permissions: packages: read strategy: matrix: os: [focal] needs: docker container: ${{ needs.docker.outputs.image_path }}${{ matrix.os }}:${{ needs.docker.outputs.image_tag }} steps: - name: Checkout uses: actions/checkout@v3 - name: Generate documentation run: | cmake -DGTIRB_ENABLE_TESTS=OFF -B build . cd build/python pip install -e '.[doc]' cd .. cmake .. make doc mv doc/html ../public - name: Upload GitHub Pages artifact uses: actions/upload-pages-artifact@v3 with: path: public deploy-pages: needs: docs if: github.ref == 'refs/heads/master' permissions: pages: write id-token: write environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 ================================================ FILE: .gitignore ================================================ *~ *.fasl *.o *.out *.pc out.gtir CMakeCache.txt CMakeFiles CMakeScripts Testing Makefile cmake_install.cmake install_manifest.txt compile_commands.json CTestTestfile.cmake build/ build-doc/ bin/ doc/html/ googletest-download/ googletest-src/ gsl-download/ gsl-src/ include/gsl/ include/proto/ wiki/ scratch.lisp /include/gtirb/version.h /java/com/grammatech/gtirb/proto/ .mypy_cache/ __pycache__/ .vscode/ .gtirb.pdf ================================================ FILE: .gtirb.tex ================================================ \begin{tikzpicture}%% Caption: GrammaTech IR for Binaries is a data structure that implicitly represents binary structures required for modification. Elements postifxed with "(1)" are singleton in any IR instance every other structure can appear in multiple. Every elements is assigned a Universal Unique Identifier (UUID) which can be used to reference the element from an AuxData table. AuxData tables hold open-ended analysis results. %% #fig:gtirb [ object/.style = {draw}, owner/.style = {thick}, owner-many/.style = {-triangle 90 reversed,thick}, reference/.style = {owner, dashed, ->}, ] \node[object, ] (ir) {IR (1)}; \node[object, below right=1em and 2em of ir, text width=6.25em] (ad0) {AuxData}; \node[below=0.25em of ad0.south] (ad1) {ID1 \& DATA1}; \node[below=0em of ad1] (ad2) {ID2 \& DATA2}; \node[below=0em of ad2] (ad3) {ID3 \& DATA3}; \node[below=0em of ad3] (ad4) {\ldots{}}; \node[fit={(ad1) (ad4)}, draw, thick, dotted, inner sep=0.25em] (auxdata) {}; \node[right=2em of auxdata] (anything) {{\em any UUID}}; \node[object, above right=4.5em and 2em of ir] (cfg) {CFG (1)}; \node[object, right=4em of cfg, text width=5em] (edges) {Edges}; \node[object, right=4em of ir, text width=6em] (modules) {Modules}; \node[object, below right=1em and 4em of modules, text width=6em] (symbols) {Symbols}; \node[object, right=4em of modules, text width=6em] (sections) {Sections}; \node[object, right=2em of sections, text width=8em] (byteintervals) {ByteIntervals}; \node[object, above=2em of byteintervals, text width=8em] (proxies) {Proxy blocks}; \node[object, right=2.5em of proxies, text width=7em] (codeblocks) {Code Blocks}; \node[object, below=0.5em of codeblocks, text width=7em] (datablocks) {Data Blocks}; \node[fit={(proxies) (codeblocks)}, draw, thick, dotted] (edgeblocks) {}; \node[] at (edges -| proxies) (eblocks) {Edge Blocks}; \node[fit={(codeblocks) (datablocks)}, draw, thick, dotted, inner sep=0.75em] (byteblocks) {}; \node[] at (eblocks -| byteblocks) (bblocks) {Byte Blocks}; \node[object, below right=1em and 2.5em of byteintervals, text width=10.5em] (symexpr) {SymbolicExpressions}; %% References \draw[reference] (edges.east) |- (eblocks); \draw[reference] (edges.east) -| ++(2,1) -| (bblocks.north); \draw[reference] (symbols.east) -| ++(2,-1) -| (symexpr.south); \draw[reference] (auxdata) -- (anything); %% Ownership \draw[owner-many] (ir) -- (modules); \draw[owner] (ir) -- ++(1.0,0) |- (cfg); \draw[owner-many] (ir) -- ++(1.0,0) |- (ad0); \draw[owner-many] (cfg) -- (edges); \draw[owner-many] (modules) -- ++(1.5,0) |- (symbols); \draw[owner-many] (modules) -- (sections); \draw[owner-many] (modules) -- ++(1.5,0) |- (edgeblocks); \draw[owner-many] (modules.south) -- (ad0.north-|modules.south); \draw[owner-many] (sections) -- (byteintervals); \draw[owner-many] (byteintervals) -- ++(1.75,0) |- (symexpr); \draw[owner-many] (byteintervals) -- ++(1.75,0) |- (bblocks); %% Local Variables: %% mode: latex %% end: \end{tikzpicture} ================================================ FILE: .isort.cfg ================================================ # Settings are chosen to be compatible with black: # https://black.readthedocs.io/en/stable/the_black_code_style.html#how-black-wraps-lines # For details on each setting: # https://github.com/timothycrosley/isort/wiki/isort-Settings [settings] line_length=79 multi_line_output=3 include_trailing_comma=true force_grid_wrap=0 ================================================ FILE: .lisp-format ================================================ ;;;; -*- emacs-lisp -*- ;;;; ;;;; For information about how to use lisp-format see it's ;;;; documentation, which is available in a comment at the top of the ;;;; lisp-format script or, equivalently, in the README available at: ;;;; ;;;; https://github.com/eschulte/lisp-format ;;;; (mapc (lambda (dir) (add-to-list 'load-path dir)) (apply #'append (mapcar (lambda (pkg-glob) (cl-loop for path in (directory-files ;; Use quicklisp if the user has it setup. (if (getenv "QUICK_LISP") (concat (getenv "QUICK_LISP") "/dists/quicklisp/software/") ;; Search for the site-lisp path in load-path (add-to-list 'load-path "/usr/share/emacs25/site-lisp/elpa/") (add-to-list 'load-path "/usr/share/emacs/site-lisp/elpa/") (cl-loop for path in load-path if (and (string-match-p "site-lisp" path) (file-expand-wildcards (concat (file-name-as-directory path) "slime*"))) do (cl-return path))) t pkg-glob) if (file-directory-p path) collect path)) (list "slime*" "paredit*")))) (defun verbose-require (package) (unless (ignore-errors (require package)) (message "Failed to load the package '%S'." package) (message "Ensure %s is installed locally, and then edit your" package) (message "\"~/.lisp-formatrc\" file adding %s to your load path.\n" package) (message " (add-to-list 'load-path )\n" package) ;; After printing the messages require again to trigger the error. (require package))) (verbose-require 'slime) (verbose-require 'paredit) (set-default 'indent-tabs-mode nil) (pushnew 'untabify *lisp-format-fixers*) (defun fix-trailing-parens (start end &optional _arg) "Use `paredit-close-parenthesis' to fix trailing parens." (interactive (if current-prefix-arg (list (point-min) (point-max) current-prefix-arg) (list (region-beginning) (region-end) nil))) (let ((c (current-column))) (save-excursion (save-restriction (narrow-to-region (point-min) end) (goto-char start) (while (re-search-forward "^ *)" nil t) (forward-char -1) (paredit-close-parenthesis)))) (move-to-column c))) (pushnew 'fix-trailing-parens *lisp-format-fixers*) ;;; Syntax table extension for curry-compose-reader-macros (modify-syntax-entry ?\[ "(]" lisp-mode-syntax-table) (modify-syntax-entry ?\] ")[" lisp-mode-syntax-table) (modify-syntax-entry ?\{ "(}" lisp-mode-syntax-table) (modify-syntax-entry ?\} "){" lisp-mode-syntax-table) (modify-syntax-entry ?\« "(»" lisp-mode-syntax-table) (modify-syntax-entry ?\» ")«" lisp-mode-syntax-table) ;;; Specify indentation levels for specific functions. (mapc (lambda (pair) (put (first pair) 'lisp-indent-function (second pair))) '((make-instance 1) (if-let 1) (if-let* 1) (when-let 1) (when-let* 1) (defixture 1) (lambda-bind 1) (signals 1) (match 1) (start-case 1) (define-proto-backed-class 4) (register-groups-bind 2))) (defun define-feature-lisp-indent (path state indent-point sexp-column normal-indent) "Indentation function called by `lisp-indent-function' for define-feature." ;; (message "CALLED: %S" ;; (list 'define-feature-lisp-indent ;; path state indent-point sexp-column normal-indent)) (cond ((equalp path '(2)) 2) ; Doc string for enclosing define-feature. ((equalp path '(3)) 2) ; Extractor function definition. ((equalp path '(3 2)) 4) ; Doc string for extractor. ((equalp path '(4)) 2) ; Merge function definition. (t nil))) ; Otherwise do the default. (put 'define-feature 'lisp-indent-function 'define-feature-lisp-indent) ================================================ FILE: .pre-commit-config.yaml ================================================ repos: - repo: https://github.com/psf/black rev: 22.3.0 hooks: - id: black args: ["--line-length", "79"] - id: black name: black (.pyi) args: ["--line-length", "79"] types: [pyi] - repo: local hooks: - id: clang-format name: clang-format language: system files: \.(c|h|cpp|hpp|proto|java)$ entry: clang-format -i - repo: https://github.com/eschulte/lisp-format rev: master hooks: - id: lisp-format name: lisp-format args: [-style=file] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.4.0 hooks: - id: end-of-file-fixer - id: trailing-whitespace args: ["--chars"," \t"] - id: check-merge-conflict - id: check-yaml args: [--allow-multiple-documents] - id: debug-statements - id: mixed-line-ending - repo: https://github.com/iconmaster5326/cmake-format-pre-commit-hook rev: v0.6.2 hooks: - id: cmake-format exclude: build - repo: https://github.com/timothycrosley/isort rev: 5.13.2 hooks: - id: isort files: \.py$ - repo: https://github.com/PyCQA/flake8 rev: 7.1.0 hooks: - id: flake8 ================================================ FILE: AlignOf.cmake ================================================ macro(ALIGNOF TYPE LANG NAME) if(NOT ALIGNOF_${NAME}) # # Try to compile and run a foo grogram. The alignment result will be stored # in ALIGNOF_${CHECK_TYPE} # set(INCLUDE_HEADERS "#include #include #include " ) foreach(File ${CMAKE_REQUIRED_INCLUDES}) set(INCLUDE_HEADERS "${INCLUDE_HEADERS}\n#include <${File}>\n") endforeach() set(INCLUDE_HEADERS "${INCLUDE_HEADERS}\n#include \n") file( WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/c_get_${NAME}_alignment.${LANG}" "${INCLUDE_HEADERS} int main(){ char diff; struct foo {char a; ${TYPE} b;}; struct foo *p = (struct foo *) malloc(sizeof(struct foo)); diff = ((char *)&p->b) - ((char *)&p->a); return diff; }" ) try_run( ALIGNOF_${NAME} COMPILE_RESULT "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/" "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/c_get_${NAME}_alignment.${LANG}" COMPILE_OUTPUT_VARIABLE "ALIGNOF_${NAME}_COMPILE_VAR" ) if(NOT COMPILE_RESULT) message( FATAL_ERROR "Check alignment of ${TYPE} in ${LANG}: compilation failed: ${ALIGNOF_${NAME}_COMPILE_VAR}" ) else() message( STATUS "Check alignment of ${TYPE} in ${LANG}: ${ALIGNOF_${NAME}}" ) endif() endif() endmacro() ================================================ FILE: AuxData.md ================================================ Standard AuxData Schemata ========================= The \ref AUXDATA_GROUP class provides generic storage for application-specific data. This allows data to be attached to either the IR or Module classes in GTIRB. We specify a small number of standard gtirb::AuxData schemata to support interoperability. These are listed below, in two sets: - [Sanctioned](#sanctioned-auxdata-tables) Recommended for GTIRB users. Individual schemata are unlikely to change in future, although the set of Sanctioned schemata may grow. - [Provisional](#provisional-auxdata-tables) Under consideration for 'sanctioned' status. For example, if you want to store alignment requirements for blocks and data objects, you can use an [alignment](#alignment) table. ```c++ // Leverage definitions for the sanctioned AuxData tables. #include // Define your own custom AuxData tables. // By convention, we put these in the namespace gtirb::schema. // // Note that if the custom type requires custom serialization, a // specialization of the auxdata_traits template also has to be // provided. We provide default specializations for many standard // types. namespace gtirb { namespace schema { struct MyAuxDataFoo { static constexpr const char* Name = "foo"; typedef Foo Type; }; } } using namespace gtirb; using namespace schema; // Register AuxData types before using GTIRB. void call_me_from_main() { AuxDataContainer::registerAuxDataType(); AuxDataContainer::registerAuxDataType(); } void do_stuff_with_gtirb() { Context C; IR& ir = *IR::Create(C); ir.addModule(Module::Create(C)); Module& module = *ir.modules_begin(); // Attach an empty alignment table to the internal representation module.addAuxData(std::map{}); //... // Create a new block Section* section = module.addSection(C, ".text"); ByteInterval* interval = section->addByteInterval(C, Addr(400), 1000); CodeBlock* b1 = interval->addBlock(C, 64, 6); // Record that the block should be aligned to 8-byte boundaries. // First fetch the map AuxData. auto* align_map = module.getAuxData(); // Check for null if you don't know that the module definitely has // an existing Alignment AuxData attached. if (align_map) (*align_map)[b1->getUUID()] = 8; // Attach a custom "Foo" object. // Note that AuxData uses a move reference Foo my_foo = BuildAFoo(); module.addAuxData(std::move(my_foo)); // Subsequently access the Foo table through the AuxData interface. module.getAuxData()->some_member_function(); } ``` ## Sanctioned AuxData Tables The following are the sanctioned AuxData table schemata. | Label | Type | |-------------------------------------------|----------------------------------------------------| | [`"elfDynamicInit"`](#elfDynamicInit) | ```gtirb::UUID``` | | [`"elfDynamicFini"`](#elfDynamicFini) | ```gtirb::UUID``` | | [`"elfSoname"`](#elfSoname) | ```std::string``` | | [`"elfStackExec"`](#elfStackExec) | ```bool``` | | [`"elfStackSize"`](#elfStackSize) | ```uint64_t``` | | [`"functionBlocks"`](#functionblocks) | ```std::map>``` | | [`"functionEntries"`](#functionentries) | ```std::map>``` | | [`"functionNames"`](#functionnames) | ```std::map``` | | [`"types"`](#types) | ```std::map``` | | [`"alignment"`](#alignment) | ```std::map``` | | [`"comments"`](#comments) | ```std::map``` | | [`"symbolForwarding"`](#symbolforwarding) | ```std::map``` | | [`"padding"`](#padding) | ```std::map``` | ### elfDynamicInit | | | |----------|----------------------------------------------------| | Label | ```"elfDynamicInit"``` | | Type | ```gtirb::UUID``` | | Value | CodeBlock UUID | | AttachedTo | gtirb::Module | | Note | The CodeBlock to which a DT_INIT entry in an ELF file's .dynamic section refers. | ### elfDynamicFini | | | |----------|----------------------------------------------------| | Label | ```"elfDynamicFini"``` | | Type | ```gtirb::UUID``` | | Value | CodeBlock UUID | | AttachedTo | gtirb::Module | | Note | The CodeBlock to which a DT_FINI entry in an ELF file's .dynamic section refers. | ### elfSoname | | | |----------|----------------------------------------------------| | Label | ```"elfSoname"``` | | Type | ```std::string``` | | Value | The SONAME of a library. | | AttachedTo | gtirb::Module | | Note | The string value which the DT_SONAME entry in an ELF file's .dynamic section contains. | ### elfStackExec | | | |----------|----------------------------------------------------| | Label | ```"elfStackExec"``` | | Type | ```bool``` | | Value | Stack executable flag specified by PT_GNU_STACK segment in ELF files. | | AttachedTo | gtirb::Module | ### elfStackSize | | | |----------|----------------------------------------------------| | Label | ```"elfStackSize"``` | | Type | ```uint64_t``` | | Value | The size of the PT_GNU_STACK segment in ELF files, which may influence the runtime stack size in certain environments. | | AttachedTo | gtirb::Module | ### functionBlocks | | | |----------|----------------------------------------------------| | Label | ```"functionBlocks"``` | | Type | ```std::map>``` | | Key | Function UUID. | | Value | The set of UUIDs of all the blocks (gtirb::CodeBlock) in the function. | | AttachedTo | gtirb::Module | | Note | This table identifies all of the gtirb::CodeBlocks that belong to each function. These do not necessarily have to be contiguous in the address space. Note that there is no function notion in the core GTIRB IR. A function's UUID is just a unique identifier that is consistently used across all function-related AuxData tables. | ### functionEntries | | | |----------|----------------------------------------------------| | Label | ```"functionEntries"``` | | Type | ```std::map>``` | | Key | Function UUID. | | Value | The set of UUIDs of all the entry blocks (gtirb::CodeBlock) for the function. | | AttachedTo | gtirb::Module | | Note | This table identifies all gtirb::CodeBlocks that represent entry points to each function. A single function may have more than one entry point. Note that there is no function notion in the core GTIRB IR. A function's UUID is just a unique identifier that is consistently used across all function-related AuxData tables. | ### functionNames | | | |----------|---------------------------------------------------------------------| | Label | ```"functionNames"``` | | Type | ```std::map``` | | Key | Function UUID. | | Value | The UUID of a gtrb::Symbol whose `name` field contains the name of the function. | | AttachedTo | gtirb::Module | | Note | There may be more than one gtirb::Symbol associated with the address(es) corresponding to the entry point(s) of a function. This table identifies a canonical gtirb::Symbol to be used for each function. Note that there is no function notion in the core GTIRB IR. A function's UUID is just a unique identifier that is consistently used across all function-related AuxData tables. | ### types | | | |----------|-----------------------------------------| | Label | ```"types"``` | | Type | ```std::map``` | | Key | The gtirb::UUID of a gtirb::DataBlock. | | Value | The type of the data, expressed as a std::string containing a C++ type specifier. | | AttachedTo | gtirb::Module | | Note | An entry in this table indicates that the given gtirb::DataBlock contains content that exhibits the given C++ type. | ### alignment | | | |----------|-----------------------------------------------------------| | Label | ```"alignment"``` | | Type | ```std::map``` | | Key | The gtirb::UUID of a gtirb::CodeBlock, gtirb::DataBlock, or gtirb::Section. | | Value | Alignment requirements for the block/data object/section. | | AttachedTo | gtirb::Module | | Note | An entry in this table indicates that the given object's address is required to be evenly divisible by the alignment value. Typically the alignment value is a power of 2. | ### comments | | | |----------|--------------------------------------------| | Label | ```"comments"``` | | Type | ```std::map``` | | Key | The gtirb::Offset of a comment. | | Value | A comment string relevant to the specified offset in the specified GTIRB entry. | | AttachedTo | gtirb::Module | | Note | The gtirb::Offset refers to the UUID of an entity in memory and a byte offset within that entity to indicate the point at which the comment applies. Comments can contain arbitrary content and are likely generated by analysis tools. They often do not (but may) represent comments present in the original source code of the binary. | ### symbolForwarding | | | |----------|----------------------------------------------| | Label | ```"symbolForwarding"``` | | Type | ```std::map``` | | Key | The gtirb::UUID of the "from" gtirb::Symbol. | | Value | The gtirb::UUID of the "to" gtirb::Symbol. | | AttachedTo | gtirb::Module | | Note | This table is intended to support cross-module references. A "from" symbol in one gtirb::Module may be dynamically bound at runtime to the "to" symbol in another gtirb::Module, thereby modeling dynamic library runtime linkage. | ### padding | | | |----------|------------------------------------------------| | Label | ```"padding"``` | | Type | ```std::map``` | | Key | The gtirb::Offset at which padding is present. | | Value | The length of the padding, in bytes. | | AttachedTo | gtirb::Module | | Note | Padding here may be 0's or it may be valid instructions. An entry in this table indicates that an analysis has determined that at the given gtirb::Offset (UUID of an entity in memory and byte offset into that entity) and length of bytes indicated constitute content that is unused by the program and is only present to ensure alignment of neighboring objects. Note: some disassemblers may still create a gtirb::CodeBlock or gtirb::DataBlock for the same portion of address space that a padding entry covers. | ## Provisional AuxData Tables The following are the provisional AuxData table schemata. | Label | Type | |-------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------| | [`"binaryType"`](#binarytype) | ```std::vector``` | | [`"cfiDirectives"`](#cfidirectives) | ```std::map, gtirb::UUID>>>``` | | [`"elfSymbolInfo"`](#elfsymbolinfo) | ```std::map>``` | | [`"elfSymbolVersions"`](#elfsymbolversions) | ```std::tuple, uint16_t>>, std::map>, std::map>>``` | | [`"encodings"`](#encodings) | ```std::map``` | | [`"functionNameProbabilities"`](#functionnameprobabilities) | ```std::map>>>``` | | [`"includedLibraryNames"`](#includedlibrarynames) | ```std::map``` | | [`"includedLibraryVersions"`](#includedlibraryversions) | ```std::map``` | | [`"libraries"`](#libraries) | ```std::vector``` | | [`"libraryPaths"`](#librarypaths) | ```std::vector``` | | [`"peExportEntries"`](#peexportentries) | ```std::vector>``` | | [`"peExportedSymbols"`](#peexportedsymbols) | ```std::vector``` | | [`"peImportEntries"`](#peimportentries) | ```std::vector>``` | | [`"peImportedSymbols"`](#peimportedsymbols) | ```std::vector``` | | [`"peResource"`](#peresource) | ```std::vector, gtirb::Offset, uint64_t>>``` | | [`"profile"`](#profile) | ```std::map``` | | [`"prototypeTable"`](#prototypetable) | ```std::map``` | | [`"sccs"`](#sccs) | ```std::map``` | | [`"sectionProperties"`](#sectionproperties) | ```std::map>>>``` | | [`"symbolicExpressionSizes"`](#symbolicexpressionsizes) | ```std::map``` | | [`"typeTable"`](#typetable) | ```std::map, std::tuple, uint64_t, uint64_t, std::tuple>, gtirb::UUID, std::tuple, tuple>>, std::tuple, gtirb::UUID>>``` | ### encodings | | | |----------|------------------------------------------------| | Label | ```"encodings"``` | | Type | ```std::map``` | | Key | The gtirb::UUID of a data object. | | Value | The encoding of the data object. | | AttachedTo | gtirb::Module | | Note | Map from (typed) data objects to the encoding of the data, expressed as a std::string containing an assembler encoding specifier: "string", "uleb128" or "sleb128". | ### elfSymbolVersions | | | |----------|------------------------------------------------| | Label | ```"elfSymbolVersions"``` | | Type | ```std::tuple, uint16_t>>, std::map>, std::map>>``` | | Key | The gtirb::UUID of a section. | | Value | The tuple with the ELF section types and flag. | | AttachedTo | gtirb::Module | | Note | Tuple with symbol version definitions, needed symbol versions, and a mapping of symbol UUIDs to symbol versions. Symbol version definitions are `ElfSymDefs = std::map>, uint16_t>`, a map from symbol version identifiers version definitions. These correspond to `ELFxx_Verdef` entries in the ELF section `.gnu.version_d`. The values in the map are tuples containing the list of versions strings and the verdef flags. The verdef flag may be `VER_FLG_BASE` (0x1), which indicates that the given version definiton is the file itself, and must not be used for matching a symbol. The first element of the list is the version itself, the subsequent elements are predecessor versions. The needed symbol versions are `ElfSymVerNeeded = std::map>`, a map from dynamic library names to the symbol versions that they need. For each library, we have a map from version identifiers to version strings. Finally, symbol UUIDs are mapped to symbol versions as `ElfSymbolVersionsEntries = std::map>`, where the `bool` represents the `HIDDEN` attribute. Symbol version identifiers are `SymbolVersionId = uint16_t` integers. | ### cfiDirectives | | | |----------|------------------------------------------------| | Label | ```"cfiDirectives"``` | | Type | ```std::map, gtirb::UUID>>>``` | | Key | The gtirb::Offset of a cfi directive. | | Value | cfi directive contains: a string describing the directive, a vector of numeric arguments, and an optional symbolic argument (represented with the UUID of the symbol | | AttachedTo | gtirb::Module | | Note | Map from Offsets to vector of cfi directives. A cfi directive contains: a string describing the directive, a vector of numeric arguments, and an optional symbolic argument (represented with the UUID of the symbol). | ### elfSymbolInfo | | | |----------|------------------------------------------------| | Label | ```"elfSymbolInfo"``` | | Type | ```std::map>``` | | Key | The gtirb::UUID of a symbol. | | Value | The type, binding, and visibility categories of the symbol. | | AttachedTo | gtirb::Module | | Note | On ELF targets only: Map from symbols to their type, binding, and visibility categories. | ### libraries | | | |----------|------------------------------------------------| | Label | ```"libraries"``` | | Type | ```std::vector``` | | Value | The name of a library. | | AttachedTo | gtirb::Module | | Note | Names of the external libraries that are needed dynamically at run time. | ### libraryPaths | | | |----------|------------------------------------------------| | Label | ```"libraryPaths"``` | | Type | ```std::vector``` | | Value | A path contained in the rpath of the binary. | | AttachedTo | gtirb::Module | | Note | Paths contained in the rpath of the binary. | ### binaryType | | | |----------|------------------------------------------------| | Label | ```"binaryType"``` | | Type | ```std::vector``` | | Value | A binary type descriptor. | | AttachedTo | gtirb::Module | | Note | A set of binary type descriptors e.g. for ELF whether the binary is PIE "DYN" or not, "EXEC". PE binaries have additional descriptors, "DLL" or "EXE, and subsystem descriptor, e.g. WINDOWS_GUI or WINDOWS_CUI. | ### SCCs | | | |----------|------------------------------------------------| | Label | ```"SCCs"``` | | Type | ```std::map``` | | Key | The gtirb::UUID of a block | | Value | The intra-procedural SCC identifier of the block. | | AttachedTo | gtirb::Module | | Note | The intra-procedural SCC identifier of each block. | ### symbolicExpressionSizes | | | |----------|------------------------------------------------| | Label | ```"symbolicExpressionSizes"``` | | Type | ```std::map``` | | Key | The gtirb::Offset of a symbolic expression. | | Value | The size of the expression, in bytes. | | AttachedTo | gtirb::Module | | Note | Map from an Offset of a symbolic expression in a ByteInterval to its extent, a size in bytes. | ### peImportEntries | | | |----------|------------------------------------------------| | Label | ```"peImportEntries"``` | | Type | ```std::vector>``` | | Value | A tuples containing details of an imported function. | | AttachedTo | gtirb::Module | | Note | List of tuples detailing an imported function address, ordinal, function name, and library names for PE. | ### peExportEntries | | | |----------|------------------------------------------------| | Label | ```"peExportEntries"``` | | Type | ```std::vector>``` | | Value | A tuples containing details of an exported function. | | AttachedTo | gtirb::Module | | Note | List of tuples detailing an exported address, ordinal, and name for PE. | ### peImportedSymbols | | | |----------|------------------------------------------------| | Label | ```"peImportedSymbols"``` | | Type | ```std::vector``` | | Value | gtirb::UUID of an imported symbol. | | AttachedTo | gtirb::Module | | Note | UUIDs of the imported symbols for PE. | ### peExportedSymbols | | | |----------|------------------------------------------------| | Label | ```"peExportedSymbols"``` | | Type | ```std::vector``` | | Value | gtirb::UUID of an exported symbol. | | AttachedTo | gtirb::Module | | Note | UUIDs of the exported symbols for PE. | ### peResource | | | |----------|------------------------------------------------| | Label | ```"peResource"``` | | Type | ```std::vector, gtirb::Offset, uint64_t>>``` | | Value | A resource header, data length, and data pointer. | | AttachedTo | gtirb::Module | | Note | List of PE resources. A resource header, data length, and data pointer. | ### profile | | | |----------|----------------------------------------------------------| | Label | ```"profile"``` | | Type | ```std::map``` | | Key | The gtirb::UUID of a gtirb::CodeBlock. | | Value | The number of times that block was executed. | | AttachedTo | gtirb::Module | | Notes | An entry in this table describes how many times a code block was executed. Blocks that are not present in this aux data table should be assumed to have a value of 0, indicating that they were not executed. | ### functionNameProbabilities | | | |------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Label | ```"functionNameProbabilities"``` | | Type | ```std::map>>>``` | | Key | Name of the tool that made the predictions. | | Value | Map from function UUID to a list of weighted predictions. Each prediction is a tuple of where the score's meaning is tool dependent. Several existing tools use the convention that higher score is a better match and values ranging from 0.0 to 1.0. | | AttachedTo | gtirb::Module | | Notes | Used to collect results from tools that identify functions and their source libraries. Source library information is tracked in [includedLibraryNames](#includedlibrarynames) and [includedlibraryVersions](#includedlibraryversions). | ### includedLibraryNames | | | |------------|------------------------------------------| | Label | ```"includedLibraryNames"``` | | Type | ```std::map``` | | Key | Included library UUID. | | Value | The name of the library. | | AttachedTo | gtirb::Module | | Notes | Names of libraries that are included in an executable (i.e., their code is intermingled with the executable code). | ### includedlibraryVersions | | | |------------|-----------------------------------------------------------------------------------------------------------------------| | Label | ```"includedLibraryVersions"``` | | Type | ```std::map``` | | Key | Included library UUID. | | Value | Version string for the included library. | | AttachedTo | gtirb::Module | | Notes | Versions of libraries that are included in an executable (i.e., their code is intermingled with the executable code). | ### sectionProperties | | | |----------|------------------------------------------------| | Label | ```"sectionProperties"``` | | Type | ```std::map>>>``` | | Key | The gtirb::UUID of a section. | | Value | The tuple with the ELF section types and flag. | | AttachedTo | gtirb::Module | | Note | Map from section UUIDs to tuples with the ELF section types and flags. | ### typeTable | | | |------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Label | ```"typeTable"``` | | Type | ```std::map, std::tuple, uint64_t, uint64_t, std::tuple>, gtirb::UUID, std::tuple, tuple>>, std::tuple, gtirb::UUID>>``` | | Key | UUID of the type object | | Value | Variant of the object type, where the variants field each represent: Unknown, Bool, Int, Char, Float, Function, Pointer, Array, Struct, Void, Alias, in that order. | | AttachedTo | gtirb::Module | | Notes | Contains structured type information about objects in the variant. Some empty tuples have been replaced with ```std::tuple``` which is default-initialized to zero since some GTIRB implementations cannot store a 0-length tuple. The corresponding semantics of each type's variant field is the following:
- **Unknown**: ```uint64_t``` - Size of the unknown type
- **Bool**: ```std::tuple``` - default initialized to zero
- **Int**: ```std::tuple``` - A tuple of signedness (1 for signed, 0 for unsigned) and width of int
- **Char**: ```uint64_t``` - Size of the character
- **Float**: ```uint64_t``` - size of the floating point number
- **Function**: ```std::tuple>``` - A tuple of return type UUID, and a list of parameter type UUIDs
- **Pointer**: ```gtirb::UUID``` - UUID of pointed-to type
- **Array**: ```std::tuple``` - A tuple of UUID of the elements of the array, and the number of element sin that array
- **Alias**: ```gtirb::UUID``` - The type being aliased (note this is effectively a `typedef`)
- **Struct**: ```tuple>>``` - A tuple of the size of the structure in total, and a vector of its fields. Each field is represented as a tuple of the offset at which the field is located, and the UUID of the type of that field.
- **Void**: ```std::tuple``` - default initialized to zero | ### prototypeTable | | | |------------|--------------------------------------------| | Label | ```"prototypeTable"``` | | Type | ```std::map``` | | Key | UUID of the function | | Value | UUID of the function type in the typeTable | | AttachedTo | gtirb::Module | | Notes | Maps functions' UUIDs to their associated typeTable entry for the purpose of giving them prototypes. NOTE: The associated type table entry **must** be a Function type object. | ================================================ FILE: CHANGELOG.md ================================================ # 2.3.2 (Unreleased) # 2.3.1 * Fix auxdata table `elfSectionProperties` to `sectionProperties` in the java api to match the cpp and python api * Switched to use tox for testing the python API. # 2.3.0 * Fixed a compatibility problem in the Python API that prevented using recent releases of protobuf. The Python API on PyPI now requires protobuf 4.21.1 or newer. * Building the Python API now requires protobuf compiler version 3.19 or later for the generated definitions to be compatible with recent protobuf packages. The API can still be built with older protobuf compilers, but the result will not be compatible with recent protobuf packages. * Added support in the C++ API for deserializing set AuxData into unordered_sets. * Fixed issue causing compile errors when building with Boost 1.86+. * Building the C++ API now requires Boost 1.68 or later. * Fixed an obscure build error caused by a missing protobuf compiler. CMake now explicitly checks for the protobuf compiler before generating build files. # 2.2.0 * Move the following utility functions into the `gtirb` namespace: `alignAddr`, `alignmentAdjustment`, `BumpPtrAllocator`, `isPowerOf2_64`, `SpecificBumpPtrAllocator`, `cast`, `cast_or_null`, `dyn_cast`, `dyn_cast_or_null`, `isa`. For backwards compatibility, these functions remain available, but deprecated, in the global namespace unless `GTIRB_WRAP_UTILS_IN_NAMESPACE` is defined. # 2.1.0 * Stop generating debian metapackages and packages with the version attached to the package name. Updates in the apt-repository now support multiple package versions and upgrading `gtirb` with `apt-get upgrade`. * Fix performance issue when checking references of ProxyBlocks in Python API. * Add elfSoname AuxData definition * ByteInterval's blocks' sort order is now thoroughly defined: offset, size, kind, decode mode, and UUID. Adjusting these properties during iteration may cause blocks to be skipped or visited twice. * Added ByteInterval methods to the Python API to look up blocks by offset on. # 2.0.0 * The Java API has been substantially reworked. Including: * Most of the core API classes now have a more polished interface. * The API for handling AuxData has been completely redesigned. * New testing infrastructure based on JUnit5 has been added. * More extensive test cases have been added. * Add elfStackExec and elfStackSize AuxData definitions * Add `IR.modules_named` helper method to Python API. * Add `IR.findModules(String name)` helper method to Java API. # 1.12.0 * Add elfDynamicInit and elfDynamicFini AuxData definitions # 1.11.0 Note that this release, due to changes to the protobuf definitions of symbolic expression attributes, is backwards-incompatible with previous GTIRB files. * Replace symbolic expression attributes with composable labels. # 1.10.9 * Added support for bool values in AuxData. * Added elfSymbolVersions provisional AuxData definition. * The GTIRB file format has changed to include a prefix containing a "magic" identifier and the GTIRB protobuf version number to allow easier id of GTIRB files. The change is not backwards compatible. Newer versions of GTIRB will not be able to load older GTIRB files. * Converted the decode mode from an arbitrary integer into a ProtoBuf enum. This obviously breaks compatibility with older GTIRB files. # 1.10.8 * Ubuntu 18 and gcc7 are no longer supported. # 1.10.7 * Added support for floating-point numbers in AuxData. * Disabled testing the Python API using `setup.py test`. The tests can still be run using ctest or Python's unittest module. * Improved the performance of the Python API's ByteBlock.references property. * Fixed a pair of bugs in C++ support for variants in AuxData # 1.10.6 * Added type annotations to python API and made them available in package. * Removed install-python target. # 1.10.5 * Added various symbolic expression attributes. * Updated Java API # 1.10.4 * Removed SymStackConst support * Added variant (union) support for AuxData * Removed address and size from modules * Modified Module::findSections(string) to return a range instead of iterator * Added IR::findSection(string) to C++ API # 1.10.3 * Added offset helpers to Python API * Better support for `std::byte` when working with `ByteInterval` * Fixed a bug which manifested when serializing a big-endian IR # 1.10.2 * Updated "address" iteration order to compare size if addresses are the same and UUIDs if addresses and sizes are the same so that it can be used to store objects in ordered containers. * Fixed some bugs where modifying an object's address or size would cause findNodeOn to return incorrect results. * Improved performance of findNodeOn and findNodeAt queries. # 1.10.1 * Added module-level endianess flag, for use in archtectures with multiple possible endians for its code blocks. * Introduce iteration helpers cfgPredecessors and cfgSuccessors. # 1.9.0 * Introduce attributes for symbolic expressions to the core IR. * Reduce asymptotic complexity of iterating over blocks in a large number of byte intervals. * Fixed bug where searching for blocks by address could return incorrect matches. * Fixed bug where `ByteInterval::addBlock` would refuse to move an existing block to a new offset if it was already present. # 1.8.5 * Make Python Offset objects immutable and make Offsets equivalent when they refer to the same displacement from the same element. # 1.8.4 * Fix bug where Symbol iteration could get out of order when symbols refer to blocks in byte intervals that are relocated. # 1.8.3 * Fix bug that didn't add CodeBlocks to the CFG if the CodeBlocks were added to a ByteInterval before it was added to the IR. # 1.8.2 * The C++ API build no longer generates a `libgtirb.so.1` symlink. This has the effect of requiring clients to link against the full version number (e.g., `libgtirb.so.1.8.2`) to ensure ABI compatibility while we continue to make rapid improvements to the library. # 1.5.0 * In the Python API: * Removed `Node.from_uuid` and added `get_by_uuid` to `IR`s. This changes UUID lookup from a global cache to a per-IR cache; this means you can now have two IRs exist that share UUIDs but have different contents, for example. * Added convienience properties to all node types to find the parent nodes they belong to. # 1.4.6 * Implement std::hash for Addr objects. # 1.4.5 * Explicitly disable copy and move constructors for the Node class hierarchy in C++. This avoids a class of errors where nodes cannot be found by Node::getByUUID. # 1.4.4 * Build/install libgtirb.so.1 symlink on linux. # 1.4.3 * Remove the python-egg cmake target, add the python-wheel cmake target # 1.4.2 * Don't use __declspec(dllimport) on Windows. # 1.4.1 * Add ISA enums for PPC64, ARM64, MIPS32, and MIPS64. # 1.3.2 * Access functions for converting to/from protobuf are no longer public in the C++ API. * The proto library is no longer dllexported. * GTIRB_EXPORT_API no longer uses dllimport on the client side. # 1.3.1 * No longer installs Python files by default. Added a new 'install-python' target to install Python files. # 1.3.0 * Added a new field to symbols, `at_end`, which allows symbols to point to the end of their referents as well as the beginning. # 1.2.1 * Moved protobuf definitions into gtirb.proto package (gtirb::proto namespace in C++). * Installing the Python API now respects CMAKE_INSTALL_PREFIX and DESTDIR with their usual semantics. # 1.2.0 * AuxData and AuxDataContainer in the C++ API have been reworked to provide cleaner type safety. * AuxData is now retrieved directly from an AuxDataContainer using a schema class that specifies both the name of the AuxData object as well as its type. * Schemata for AuxData types must be registered at process startup before GTIRB objects are constructed or unserialized. # 1.1.1 * Fixed a bug where changing the address of a block caused lookups of symbol by address to fail in some cases. # 1.1.0 * Added a new API for accessing GTIRB, written in Java. This API is not yet released, and as such, has missing features, is not yet documented, and may change at any time. For more information, look at the contents of the `java` directory. # 1.0.0 This is a major backwards-incompatible release. The protobuf specification has changed significantly resulting in protobuf version 1 which is now tracked in `version.txt` in the base of this repository. (The original protobuf version was version 0.) The changes in this release are primarily intended to enable *binary rewriting* use cases on GTIRB. Other changes to the protobuf specification are for more general cleanup, simplification, and clarification. In addition, a new Common Lisp GTIRB API is now included along with the C++ and Python APIs. A list of specific changes follows. Complete documentation of all new objects and structures is provided in the GTIRB manual. * A `version` field is now present on GTIRB IR instances. The value of this field is now `1`. The old value of `0` is the protobuf default for a missing field. * The control flow graph (CFG) is now a child of the IR instead of living under a specific module. This means that a multi-module IR now has a single pan-module CFG. * The `Block` object has been renamed to `CodeBlock` and the `DataObject` to `DataBlock`. * A new object has been added to the GTIRB `Section`s named `ByteInterval`s. This replaces the `ByteMap` in the previous GTIRB version. A `ByteInterval` has: * An *optional* fixed address indicating its location in memory. Without an address the location of the `ByteInterval` is not specified allowing it to float to enable easier binary rewriting. * A `size` specifying the extend of the `ByteInterval` in memory. If this size is larger than the contents of the `ByteInterval` then the extension of the `ByteInterval` in memory beyond the end of the contents is un-allocated. * A byte vector named `contents` holding the contents of the `ByteInterval`. * A map from offsets to symbolic expressions. * A list of blocks holding `CodeBlock`s and `DataBlock`s. * The `address` field has been removed from `Block`s. * The `address` and `size` fields have been removed from `Section`s. * An offset from the start of their `ByteInterval` have been added to blocks. * The following fields have been removed from `Module`s: * `image_byte_map` * `symbolic_operands` * `blocks` and * `data`. * An entry point stored as a `CodeBlock` has been added to `Module`s. * A list of `ByteIntervals` has been added to `Section`s. * The `ISAID` enumeration on module is renamed to `ISA`. * Instead of an `AuxDataContainer` object we now hold a `map` on modules and IRs. * The following GTIRB enumerations are modified: `ISA`, `FileFormat`, `SymbolKind`, and `SectionFlag`. The goals of these modifications is to simplify the enumerations and ensure that all included options are both necessary and orthogonal. * Sections now include have `SectionFlag`s to store common properties such as `readable`, `writeable`, or `executable`. # 0.3.0 * You can now enable and disable the building of certain APIs when calling CMake, via the following flags: * `GTIRB_CXX_API` to control the building of the C++ API, on by default * `GTIRB_PY_API` to control the building of the Python API, on by default if `python3` is installed on your system * The following changes have been made to the Python API: * `Serialization.decode` can now take a `bytes` object in addition to a `BytesIO` object. * If an unknwon type is encountered while decoding `AuxData`, it will be placed in `data` as a `bytes`-like object instead of throwing a `DecodeError`. Unknown data decoded this way can be then encoded again. It is still an error to encode unknown types of auxdata not in the manner described above. * ImageByteMap::setData() has been extended to support arbitrary iterator types. * We now build documentation for the Python API using [Sphinx](https://www.sphinx-doc.org/en/master/). To generate all documentation locally, call `make doc` after calling `cmake`; this will generate both C++ and Python API documentation. To only make one or the other, call `make doxy` or `make sphinx`, respectively. * Making the Sphinx documentation will require the following Python packages: ```bash pip3 install sphinx sphinx-autodoc-typehints ``` # 0.2.0 * Added a new Python API, meant to be a high-level wrapper over the Protobuf-generated one. To make use of it, add the `python` folder from your build directory to your `PYTHONPATH`. The package is named `gtirb`. * CMake now won't automatically download and install its dependencies, so that the user has control over which versions are in use. The version requirements for Boost and Protobuf are listed in `README.md`. * Updated the sanctioned AuxData definitions. * Fix for build issue when using Boost 1.71.0. # 0.1.1 * Initial public release. ================================================ FILE: CMakeLists.googletest ================================================ cmake_minimum_required(VERSION 2.8.2) project(googletest-download NONE) include(ExternalProject) externalproject_add( googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG v1.15.2 SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src" BINARY_DIR "${CMAKE_BINARY_DIR}/googletest-build" CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" TEST_COMMAND "" ) ================================================ FILE: CMakeLists.txt ================================================ # # Cmake Configuration # # Need 3.10 to support CXX_STANDARD=17 and protobuf::protoc cmake_minimum_required(VERSION 3.10.0) # The version.txt file is the official record of the version number. We use the # contents of that file to set the project version for use in other CMake files. file(READ "${CMAKE_CURRENT_SOURCE_DIR}/version.txt" ver) string(REGEX MATCH "VERSION_MAJOR ([0-9]*)" _ ${ver}) set(GTIRB_MAJOR_VERSION ${CMAKE_MATCH_1}) string(REGEX MATCH "VERSION_MINOR ([0-9]*)" _ ${ver}) set(GTIRB_MINOR_VERSION ${CMAKE_MATCH_1}) string(REGEX MATCH "VERSION_PATCH ([0-9]*)" _ ${ver}) set(GTIRB_PATCH_VERSION ${CMAKE_MATCH_1}) string(REGEX MATCH "VERSION_PROTOBUF ([0-9]*)" _ ${ver}) set(GTIRB_PROTOBUF_VERSION ${CMAKE_MATCH_1}) cmake_policy(SET CMP0048 NEW) project( GTIRB VERSION "${GTIRB_MAJOR_VERSION}.${GTIRB_MINOR_VERSION}.${GTIRB_PATCH_VERSION}" ) set(PACKAGE_BRANCH master) include(CheckFunctionExists) include(CheckCXXSourceCompiles) include(CheckIncludeFile) include(Macros.cmake) include(AlignOf.cmake) include(CMakePackageConfigHelpers) option(ENABLE_CONAN "Use Conan to inject dependencies" OFF) if(ENABLE_CONAN) set(CONAN_SYSTEM_INCLUDES ON) include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) conan_basic_setup() endif() # --------------------------------------------------------------------------- # Build options # --------------------------------------------------------------------------- set(GTIRB_MSVC_PARALLEL_COMPILE_JOBS "0" CACHE STRING "Number of parallel compiler jobs used for Visual Studio compiles. 0 means use all processors. Default is 0." ) option(GTIRB_ENABLE_TESTS "Enable building and running unit tests." ON) option(GTIRB_ENABLE_MYPY "Enable checking python types with mypy." ON) # This just sets the builtin BUILD_SHARED_LIBS, but if defaults to ON instead of # OFF. option(GTIRB_BUILD_SHARED_LIBS "Build shared libraries." ON) if(GTIRB_BUILD_SHARED_LIBS) set(BUILD_SHARED_LIBS ON) else() set(BUILD_SHARED_LIBS OFF) endif() if(UNIX AND NOT BUILD_SHARED_LIBS) # Find only static libraries set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") add_compile_options(-static) endif() enable_testing() # Set ENABEL_CODE_COVERAGE to default off, unless you want to test c++ coverage option(ENABLE_CODE_COVERAGE "Build with instrumentation for collecting code coverage" OFF ) if(ENABLE_CODE_COVERAGE) if(${CMAKE_CXX_COMPILER_ID} STREQUAL GNU OR ${CMAKE_CXX_COMPILER_ID} STREQUAL Clang ) add_compile_options(--coverage) link_libraries(--coverage) else() message(FATAL_ERROR "no support for code coverage on this target") endif() endif() # Whether or not to run clang-tidy (if present) option(GTIRB_RUN_CLANG_TIDY "Enable running of clang-tidy." ON) # Define the cache variables for the API options. option(GTIRB_CXX_API "Whether or not the C++ API is built." ON) option(GTIRB_PY_API "Whether or not the Python API is built." ON) option(GTIRB_CL_API "Whether or not the Common Lisp API is built." ON) option(GTIRB_JAVA_API "Whether or not the Java API is built." ON) # Determine whether or not to strip debug symbols and set the build-id. This is # only really needed when we are building ubuntu *-dbg packages option(GTIRB_STRIP_DEBUG_SYMBOLS "Whether or not to strip debug symbols and set the build-id." OFF ) option( GTIRB_RELEASE_VERSION "Whether or not to build package versions without dev/SNAPSHOT suffixes. Applies to the python and java APIs." OFF ) # Determine whether or not the APIs are REALLY built or not. # === C++ === set(CXX_API ${GTIRB_CXX_API}) # === Python === set(PY_API ${GTIRB_PY_API}) if(GTIRB_PY_API) gtirb_find_python() if(PYTHON) set(PYTHON_MINIMUM_VERSION "3.6") if("${Python3_VERSION}" VERSION_LESS "${PYTHON_MINIMUM_VERSION}") message( WARNING "${PYTHON} --version is ${Python3_VERSION}, which is less than the minimum required, ${PYTHON_MINIMUM_VERSION}; disabling building of API." ) set(PY_API OFF) endif() else() message( WARNING "Python interpreter not found; disabling building of Python API. If this is in error, try giving -DPYTHON=... to CMake to specify what program to use." ) set(PY_API OFF) endif() endif() # === Common Lisp === # TODO: test the CL API on other CL interpreters and search for those in # addition to SBCL when looking for a default CL interpeter set(CL_API ${GTIRB_CL_API}) if(GTIRB_CL_API) find_program(LISP "sbcl") set(QUICKLISP "$ENV{HOME}/quicklisp" CACHE STRING "The Quicklisp installation to use." ) set(LISP_MINIMUM_VERSION "1.4.5") if(NOT LISP) message( WARNING "Lisp interpreter not found; disabling building of Lisp API. If this is in error, try giving -DLISP=... to CMake to specify what program to use." ) set(CL_API OFF) elseif(NOT EXISTS "${QUICKLISP}") message( WARNING "Quicklisp installation not found; disabling building of Lisp API. If this is in error, try giving -DQUICKLISP=... to CMake to specify what directory to use." ) set(CL_API OFF) else() execute_process(COMMAND "${LISP}" "--version" OUTPUT_VARIABLE LISP_VERSION) string(REPLACE "SBCL" "" LISP_VERSION "${LISP_VERSION}") string(REPLACE ".debian" "" LISP_VERSION "${LISP_VERSION}") if("${LISP_VERSION}" VERSION_LESS "${LISP_VERSION}") message( WARNING "${LISP} --version is ${LISP_VERSION}, which is less then the minimum required, ${LISP_MINIMUM_VERSION}; disabling building of API." ) set(CL_API OFF) endif() endif() endif() # === Java === set(JAVA_API ${GTIRB_JAVA_API}) if(GTIRB_JAVA_API) find_package(Java 1.8.0 COMPONENTS Development) if(NOT JAVA_FOUND) message(WARNING "Java 8 compiler not found; disabling building of Java API. If this is in error, try setting the environment variable $JAVA_HOME." ) set(JAVA_API OFF) else() find_program(MVN mvn) if(NOT MVN) message( WARNING "Maven not found; disabling building of Java API. If this is in " "error, try setting -DMVN= on the CMake command-line" ) set(JAVA_API OFF) endif() endif() endif() # Documentation options. option(GTIRB_DOCUMENTATION "Whether or not documentation is built." ON) # --------------------------------------------------------------------------- # Global settings # --------------------------------------------------------------------------- set_property(GLOBAL PROPERTY USE_FOLDERS ON) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/src) if(WIN32) set(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "add a postfix, usually d on windows" ) endif() set(CMAKE_RELEASE_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows" ) set(CMAKE_RELWITHDEBINFO_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows" ) set(CMAKE_MINSIZEREL_POSTFIX "" CACHE STRING "add a postfix, usually empty on windows" ) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) if(CXX_API) # Use C++17 set(CMAKE_CXX_STANDARD 17) # Error if it's not available set(CMAKE_CXX_STANDARD_REQUIRED ON) # Specifically check for gcc-7 or later. gcc-5 is installed on many systems # and will accept -std=c++17, but does not fully support the standard. if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "7.0.0") message(FATAL_ERROR "gcc 7 or later is required to build gtirb") endif() endif() set(CMAKE_CXX_VISIBILITY_PRESET hidden) # # Global Options (Compile / Link) # add_compile_options(-DBOOST_MULTI_INDEX_DISABLE_SERIALIZATION) # MSVC-specific Options if(${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC) if(NOT GTIRB_MSVC_PARALLEL_COMPILE_JOBS STREQUAL "1") if(GTIRB_MSVC_PARALLEL_COMPILE_JOBS STREQUAL "0") add_compile_options(-MP) message(STATUS "Parallel compilation enabled") else() add_compile_options(-MP${GTIRB_MSVC_PARALLEL_COMPILE_JOBS}) message( STATUS "Parallel compilation with ${GTIRB_MSVC_PARALLEL_COMPILE_JOBS} jobs" ) endif() else() message(STATUS "Parallel compilation disabled") endif() add_compile_options(-D_CRT_SECURE_NO_WARNINGS) add_compile_options(-D_MBCS) add_compile_options(-D_SCL_SECURE_NO_WARNINGS) # We need to add both so that there is not a mismatch between Win32 SDK # headers (which use UNICODE) and C Standard Library headers (which use # _UNICODE). add_compile_options(-D_UNICODE) add_compile_options(-DUNICODE) add_compile_options(-D_WIN32) # Disable macro definitions for min and max that conflict with the STL. add_compile_options(-DNOMINMAX) # Enable RTTI. FIXME: stop using typeid so we can disable this and add -fno- # rtti to the Clang/GCC compiler options. add_compile_options(-GR) # Enable exceptions, which are basically required because of our reliance on # boost. add_compile_options(-EHsc) # Enabled a sensible warning level and treat all warnings as errors. add_compile_options(-W4) add_compile_options(-WX) # Enable bigobj support, otherwise IR.cpp and Module.cpp will refuse to # compile due to execeeding the number of sections allowed in an object # file. FIXME: we should not have that many template instantiations. add_compile_options(-bigobj) add_compile_options(-sdl) # Enable extra security checks add_compile_options(-permissive-) # Disable permissive mode add_compile_options(-wd4996) # VC8: Deprecated libc functions. # This is a warning about a change in behavior from old versions of visual # c++. We want the new (standard-compliant) behavior, so we don't want the # warning. The warning is that using an array in a class initializer list # will cause its elements to be default initialized. add_compile_options(-wd4351) add_compile_options(-wd4146) # unary minus operator applied to unsigned # type, result still unsigned # C4505: 'google::protobuf::internal::MapField<...>::ContainsMapKey': # unreferenced local function has been removed add_compile_options(-wd4505) # C4267: protobuf-generated headers, at least w/ protobuf 3.9.1, trigger # MSVC's "conversion from 'size_t' to 'int', possible loss of data" warning. add_compile_options(-wd4267) # Release target options add_compile_options($<$:-GL>) # Enable whole program # optimization add_link_options($<$:-ltcg>) # Enable link-time code # generation elseif((${CMAKE_CXX_COMPILER_ID} STREQUAL GNU) OR (${CMAKE_CXX_COMPILER_ID} STREQUAL Clang) ) add_compile_options(-Wall -Wextra -Wpointer-arith -Wshadow -Werror) add_compile_options(-fPIC) endif() endif() # --------------------------------------------------------------------------- # Boost # --------------------------------------------------------------------------- if(CXX_API) find_package(Boost 1.68 REQUIRED) add_compile_options(-DBOOST_CONFIG_SUPPRESS_OUTDATED_MESSAGE) add_compile_options(-DBOOST_SYSTEM_NO_DEPRECATED) # Boost versions 1.70.0+ may use Boost's provided CMake support rather than # CMake's internal Boost support. The former uses "Boost::boost" and so on, # while the latter uses "Boost_BOOST" and so on. This normalizes the two cases # to use Boost_INCLUDE_DIRS and Boost_LIBRARIES. if(TARGET Boost::headers) get_target_property( Boost_INCLUDE_DIRS Boost::headers INTERFACE_INCLUDE_DIRECTORIES ) endif() include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) endif() # --------------------------------------------------------------------------- # Google Test Application # --------------------------------------------------------------------------- if(GTIRB_ENABLE_TESTS AND CXX_API) # Pull in Google Test # https://github.com/google/googletest/tree/master/googletest#incorporating- # into-an-existing-cmake-project # Download and unpack googletest at configure time configure_file(CMakeLists.googletest googletest-download/CMakeLists.txt) execute_process( COMMAND "${CMAKE_COMMAND}" -G "${CMAKE_GENERATOR}" . RESULT_VARIABLE result WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/googletest-download" ) if(result) message(WARNING "CMake step for googletest failed: ${result}") endif() execute_process( COMMAND "${CMAKE_COMMAND}" --build . RESULT_VARIABLE result WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/googletest-download" ) if(result) message(WARNING "Build step for googletest failed: ${result}") endif() # Prevent overriding the parent project's compiler/linker settings on Windows set(gtest_force_shared_crt ON CACHE BOOL "" FORCE ) # Add googletest directly to our build. This defines the gtest and gtest_main # targets. add_subdirectory( "${CMAKE_BINARY_DIR}/googletest-src" "${CMAKE_BINARY_DIR}/googletest-build" EXCLUDE_FROM_ALL ) include_directories("${gtest_SOURCE_DIR}/include") endif() # --------------------------------------------------------------------------- # JUnit Test Application # --------------------------------------------------------------------------- if(GTIRB_ENABLE_TESTS AND JAVA_API) include(ExternalProject) externalproject_add( junit PREFIX ${CMAKE_BINARY_DIR}/junit URL "https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/1.10.0/junit-platform-console-standalone-1.10.0.jar" CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" TEST_COMMAND "" DOWNLOAD_NO_EXTRACT ON ) set(JUNIT_STANDALONE_JAR ${CMAKE_BINARY_DIR}/junit/src/junit-platform-console-standalone-1.10.0.jar ) endif() # --------------------------------------------------------------------------- # protobuf # --------------------------------------------------------------------------- if(CL_API) find_package(Protobuf 3.7.0 REQUIRED) else() find_package(Protobuf 3.0.0 REQUIRED) endif() if(NOT Protobuf_PROTOC_EXECUTABLE) # find_package only fails if the protobuf libraries or headers cannot be # found. It does not treat failing to find the protobuf compiler as an error, # so we do that explicitly here. message( FATAL_ERROR "Could not find Protobuf compiler 'protoc'. Please make sure the " "Protobuf compiler is installed." ) endif() if(Protobuf_VERSION VERSION_LESS 3.2) add_definitions(-DPROTOBUF_SET_BYTES_LIMIT) endif() if(NOT BUILD_SHARED_LIBS) set(Protobuf_USE_STATIC_LIBS ON) endif() include_directories(SYSTEM ${PROTOBUF_INCLUDE_DIRS}) add_subdirectory(proto) # --------------------------------------------------------------------------- # gtirb sources # --------------------------------------------------------------------------- if(CXX_API) add_subdirectory(src) endif() if(PY_API) add_subdirectory(python) endif() if(CL_API) add_subdirectory(cl) endif() if(JAVA_API) add_subdirectory(java) endif() if(GTIRB_DOCUMENTATION) add_subdirectory(doc) endif() # --------------------------------------------------------------------------- # Export config for use by other CMake projects # --------------------------------------------------------------------------- if(CXX_API) # --- For direct use from the build directory/cmake registry --- # This exports the targets export(TARGETS gtirb gtirb_proto FILE "${CMAKE_CURRENT_BINARY_DIR}/gtirbTargets.cmake" ) # This is the main config file that find_package will look for. configure_file( "${CMAKE_CURRENT_LIST_DIR}/gtirbConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/gtirbConfig.cmake" @ONLY ) # Add the build directory to the user CMake registry, so find_package can # locate it automatically. export(PACKAGE gtirb) # --- For the installed copy --- # Main config file for find_package, includes the targets file and defines the # check_gtirb_branch function. if(NOT DEFINED PACKAGE_BRANCH) set(PACKAGE_BRANCH "No package branch specified") endif() # FIXME: The installed version of gtirbConfig currently contains the # check_gtirb_branch function, which requires users to explicitly call it. We # ought to move this functionality to gtirbConfig-version, so that checking # the gtirb version also checks the branch, requiring users to opt-out of the # branch check, rather than opt-in by calling check_gtirb_branch. See: GitLab # issue #93 configure_file( "${CMAKE_CURRENT_LIST_DIR}/gtirbConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/export/gtirbConfig.cmake" @ONLY ) # In this mode, find_package also seems to require a version file set(version_file "${CMAKE_CURRENT_BINARY_DIR}/gtirbConfig-version.cmake") write_basic_package_version_file( ${version_file} VERSION ${GTIRB_VERSION} COMPATIBILITY AnyNewerVersion ) # Copy the config files to the install location install( FILES ${CMAKE_CURRENT_BINARY_DIR}/export/gtirbConfig.cmake ${version_file} DESTINATION lib/gtirb COMPONENT cmake_config ) # This exports the targets to the install location. install( EXPORT gtirbTargets COMPONENT cmake_target DESTINATION lib/gtirb ) endif() # --------------------------------------------------------------------------- # Package policy enforcement # --------------------------------------------------------------------------- if(GTIRB_PACKAGE_POLICY) set(PACKAGE_POLICY ${GTIRB_PACKAGE_POLICY}) elseif(ENABLE_CONAN OR WIN32) set(PACKAGE_POLICY conan) else() set(PACKAGE_POLICY unix) endif() if(PACKAGE_POLICY STREQUAL "unix") # Provides copyright file for Unix packages. install( FILES ${CMAKE_SOURCE_DIR}/LICENSE.txt COMPONENT license DESTINATION share/doc/gtirb RENAME copyright ) elseif(PACKAGE_POLICY STREQUAL "conan") # Provides LICENSE.txt for Conan packages install( FILES ${CMAKE_SOURCE_DIR}/LICENSE.txt COMPONENT license DESTINATION licenses ) endif() # --------------------------------------------------------------------------- # Package generation with cpack # --------------------------------------------------------------------------- set(CPACK_PROJECT_CONFIG_FILE ${CMAKE_CURRENT_SOURCE_DIR}/cpack-config.cmake) set(CMAKE_PROJECT_HOMEPAGE_URL https://github.com/GrammaTech/gtirb) set(CPACK_PACKAGE_VERSION_MAJOR ${GTIRB_MAJOR_VERSION}) set(CPACK_PACKAGE_VERSION_MINOR ${GTIRB_MINOR_VERSION}) set(CPACK_PACKAGE_VERSION_PATCH ${GTIRB_PATCH_VERSION}) set(CPACK_PACKAGE_VENDOR "GrammaTech Inc.") set(CPACK_PACKAGE_CONTACT gtirb@grammatech.com) set(CPACK_PACKAGE_DESCRIPTION_FILE ${CMAKE_CURRENT_SOURCE_DIR}/README.md) set(CPACK_PACKAGE_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.md) set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "The GrammaTech Intermediate Representation for Binaries (GTIRB) is a machine code analysis and rewriting data structure." ) set(CPACK_DEBIAN_PACKAGE_SECTION devel) string(REGEX MATCH "([^\.]+)\.([^\.]+)\.([^\.]+)" PROTOBUF_VERSION_MATCH ${Protobuf_VERSION} ) set(PROTOBUF_MAJOR_VERSION ${CMAKE_MATCH_1}) set(PROTOBUF_MINOR_VERSION ${CMAKE_MATCH_2}) set(PROTOBUF_PATCH_VERSION ${CMAKE_MATCH_3}) math(EXPR NEXT_PROTOBUF_PATCH "${PROTOBUF_PATCH_VERSION}+1") set(CPACK_PROTOBUF_VERSION_UPPER_BOUND "${PROTOBUF_MAJOR_VERSION}.${PROTOBUF_MINOR_VERSION}.${NEXT_PROTOBUF_PATCH}" ) set(CPACK_PROTOBUF_VERSION_LOWER_BOUND "${Protobuf_VERSION}") set(CPACK_GTIRB_VERSION "${GTIRB_VERSION}") set(CPACK_SOURCE_DIR ${CMAKE_SOURCE_DIR}) include(CPack) # --------------------------------------------------------------------------- # Report APIs and features built # --------------------------------------------------------------------------- message("APIs to be built:") message(" C++ ${CXX_API}") message(" Python ${PY_API}") message(" Lisp ${CL_API}") message(" Java ${JAVA_API}") ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at gtirb-conduct@grammatech.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: CONTRIBUTING.md ================================================ Contributing ============ ## Code of Conduct Please read the [GTIRB Code of Conduct](CODE_OF_CONDUCT.md). ## General Guidelines - Text files may not have trailing whitespace. - Text files must end with a trailing newline. - All tests should be able to run and pass. This can be checked by running `make check` on your build directory after running `cmake`. - All CMake files shall be formatted with [cmake-format](https://pypi.org/project/cmake-format/). A `.cmake-format` file is provided in the root directory for the project, and a pass through this tool is included as part of our `pre-commit` configuration (see below for details). - The GTIRB version saved in `version.txt` uses [semantic versioning](https://semver.org) - We also track a Protobuf version number in `version.txt` (as `VERSION_PROTOBUF`). This number is incremented whenever our protobuf files (under `proto/` in the GTIRB repository) are changed in any way that affects the serialized protobuf -- even if the change could potentially be backwards compatible because of the way protobuf gracefully handles unknown new fields. - We ask that all contributors complete our Contributor License Agreement (CLA), which can be found at [GrammaTech-CLA-GTIRB.pdf](./GrammaTech-CLA-GTIRB.pdf), and email the completed form to `CLA@GrammaTech.com`. Under this agreement contributors retain the copyright to their work but grants GrammaTech unlimited license to the work. ### pre-commit In general, code must follow a unified format. To make compliance with this format easier, we recommend the use of [`pre-commit`](https://pre-commit.com/) with the provided configuration file, `.pre-commit-config.yaml`, to manage formatting. To use `pre-commit`: 1. If `pre-commit` is not already installed on your system, install it now with [`pip`](https://pypi.org/project/pip/). ```shell pip3 install pre-commit ``` 2. If [`clang-format`](https://clang.llvm.org/docs/ClangFormat.html) is not already installed on your system, install it now. 3. Install the formatters as a pre-commit hook. In the gtirb root directory: ```shell pre-commit install ``` If you prefer to run `pre-commit` manually instead, run this before all commits: ```shell pre-commit run ``` ## C++ Code Requirements - All code shall be formatted with [clang-format](https://clang.llvm.org/docs/ClangFormat.html). A `.clang-format` is provided in the root directory for the project, and a pass through this tool is included as part of our `pre-commit` configuration. - Code should generally follow the C++ Core Guidelines recommendations. - Code should generally allow for thread safety. - No static variables. - No globals - Free functions should not maintain state. - Use caution when using iterators to guard against invalidation. - Maintain const-correctness. - Use UpperCamelCase for type names. - Use UpperCamelCase for enum members. - Use UpperCamelCase for variable and class members. - Use lowerCamelCase for function and method names. - Avoid `using namespace std` - Use `auto` when the deduced type is explicitly spelled out in the initialization or if the deduced type is an abstract type alias. Always explicitly specify type qualifiers, pointers, and references. E.g., ```cpp const auto *Ptr = dynamic_cast(SomePtr); auto Val = static_cast(SomeValue); for (auto Iter = SomeContainer.begin(), End = SomeContainer.end(); Iter != End; ++Iter) {} ``` - Use `auto` to make code more readable, but prefer `auto &` or `auto *` to avoid unexpected copies. - `#include` as little as possible to reduce compile times. Use forward declarations of classes when possible to avoid including their definitions. - Do not introduce variables to the code that would require a client to dllimport them. Export.hpp does not setup dllimport declarations for clients. For example, do not add static function-local variables in inline functions in header files. ## Build Performance Tips Some tips to keep in mind to not needlessly regress build performance when working with GTIRB: - Do not include a protobuf header from within a .hpp file unless you need the enum values from protobuf. A forward declare + include in the .cpp file is sufficient and cuts out 100s of transitive headers to parse. - CFG.hpp and ByteInterval.hpp are the most expensive headers you can possibly include. Avoid including these whenever humanly possible (getting rid of 2 includes of CFG.hpp cut out ~2500 transitive header includes). - Boost headers are extremely heavy to parse, try to push as much of their inclusions down into a .cpp file as possible. For instance, including boost's endian header requires ~200 transitive headers by itself. Things like the boost containers are considerably more expensive. This is what contributes to ByteInterval and CFG being so expensive to include. Be wary when adding includes to boost headers or adding new boost dependencies. - Do not blindly trust the output from tools like include what you use; they sometimes do silly things like include a header file when a forward declare is sufficient. When adding an include to a .hpp file, try hard to avoid adding the include. ### Testing Development - All code you care about should be tested. - Any code you don't care about should be removed. - C++ code should be tested on Linux using GCC and Clang, and on Windows using Visual Studio. - Code testing is done via Google Test. - Test names are prefixed with the type of test they are (`Unit_`, `System_`, `Integration_`). - No unit test should take more than 0.5 seconds. ## Building under Windows Most of the build issues on Windows arise from having to define the location of many dependencies. ### Troubleshooting - When using a batch file to call `cmake`, make sure to quote all paths and escape all backshlases in paths. i.e. `-DCMAKE_PREFIX_PATH="c:\\Program Files\\capstone"` and do not leave a trailing backslash on paths. - Use `-Dprotobuf_DEBUG=ON` for protobuf related build issues in general. - `'../src/gtirb/proto/protobuf::protoc', needed by 'src/gtirb/proto/AuxData.pb.h', missing and no known rule to make it` - due to missing or unusable protoc protobuf compiler. You may need to define `-Dprotobuf_EXECUTABLE=""`, or check that the CMAKE_PREFIX_PATH has a path to the protobuf dir (resulting from `ninja install` after building). - `CMAKE_PREFIX_PATH` is not additive. If you set it again, it will silently overwrite prior settings. Add to the one definition, separtaing with semi-colons. ## Python Code Requirements - Code must be [PEP8](https://www.python.org/dev/peps/pep-0008/) compliant. To check for PEP8 compliance, [flake8](https://pypi.org/project/flake8/) is recommended, and included as part of our `pre-commit` configuration. - All code must be formatted with [Black](https://pypi.org/project/black/) (set to line lengths of 79, for PEP8 compliance). A pass through this tool is included as part of our `pre-commit` configuration. - The Python API should be made to run on all versions of Python 3. - Use `UpperCamelCase` for type names, `UPPER_CASE` for constant names, and `snake_case` for other identifier names. ### Testing Development - All code you care about should be tested. - Any code you don't care about should be removed. - Code testing is done via the built-in `unittest` framework. - Code testing uses [`tox`](https://tox.wiki/en/stable/) and [`pytest`](https://docs.pytest.org/en/stable/) to simplify testing supported configurations. - No unit test should take more than 0.5 seconds. ## Documentation The GTIRB documentation consists of complete documentation for all components of the GTIRB API, along with examples and other usage information. ### Building Documentation At minimum, you will need [CMake](https://cmake.org/) and [Doxygen](http://www.doxygen.nl/). To build the documentation: 1. Create and change to a temporary build directory. We will refer to this directory as `build`. ```bash > mkdir build > cd build ``` 2. Build the documentation. ```bash build> cmake [] build> cmake --build . --target doc ``` 3. Open the documentation home page `build/doc/html/index.html` in your browser. The `` are as follows - `-DGTIRB_CXX_API=OFF` : do not generate C++ API documentation. If this option is not specified, `cmake` will attempt to generate C++ API documentation, failing (along with the documentation build as a whole) if [Doxygen](http://www.doxygen.nl/) is not available. - `-DGTIRB_CL_API=OFF` : do not generate Common Lisp API documentation. If this option is not specified, `cmake` will attempt to generate Common Lisp API documentation if and only if it can locate a SBCL/Quicklisp installation, failing if [simpler-documentation-template (SDT)](https://github.com/eschulte/simpler-documentation-template) is not available. - `-DGTIRB_PY_API=OFF` : do not generate Python API documentation. If this option is not specified, `cmake` will attempt to generate Python API documentation if and only if it can locate a Python installation, failing if [Sphinx](https://www.sphinx-doc.org/en/master/), [sphinx-autodoc-typehints](https://pypi.org/project/sphinx-autodoc-typehints/), or the Python API dependencies are not available. ### Contributing Markdown Documentation To add a new markdown document to the documentation: 1. Create the new document as a child of `/doc`. - File names start with `gtirb`. - File extension is `.md`. - Use github markdown syntax. - Wrap your markdown documents at 80 columns. 2. Edit `/doc/general/Doxyfile.in` to add the basename of your new markdown document to the `INPUT` rule setting. 3. Edit `/doc/general/CMakeLists.txt` to add your new markdown document to `MDFILES_IN`. Ordering is not important. 4. Integrate your new markdown document into the documentation, either by linking to it from an existing page or by updating `/doc/general/DoxygenLayout.xml` to add an entry to the **More Information** tab. 5. [Build the documentation](#building-documentation) and check that your new page is present and rendered correctly. - If it is not rendered correctly, you may need to add a new preprocessing step to `doc/general/preprocmd.py` to rewrite the corresponding github-style markdown into something Doxygen can handle correctly. ### Graphviz - File names start with `gtirb`. - The color palette is `black`, `lightblue`, `cornflowerblue`, and `coral`. - Render `.dot` files to the same file name with a `.png` extension. * Example: `dot -Tpng gtirbScope.dot > gtirbScope.png` - Use the `arial` font. ### Python For the Python API, [Sphinx](https://www.sphinx-doc.org/en/master/) and [related plugins](https://pypi.org/project/sphinx-autodoc-typehints/) are required. To install these via [pip](https://pip.pypa.io/en/stable/), run: ```bash pip3 install sphinx sphinx-autodoc-typehints ``` You will also need all the dependencies of the GTIRB Python API itself. If you haven't already installed the Python API (and don't want to do so now) you can install just its dependencies as follows. ```bash cd /build/python python3 setup.py egg_info pip3 install -r gtirb.egg-info/requires.txt ``` ### Common Lisp For the Common Lisp API, [simpler-documentation-template (SDT)](https://github.com/eschulte/simpler-documentation-template) is required. This package should automatically be downloaded via the build process; see `cl/README.md` for details on how to prepare the Common Lisp API. ================================================ FILE: FAQ.md ================================================ Frequently Asked Questions -------------------------- - [I get compiler errors when I try to compile programs using GTIRB. How can I make them go away?](#compiler-errors) - [Linking error with "undefined reference" to gtirb::](#linking-error-with-undefined-reference-to-gtirb) #### Compiler Errors __Q: I get compiler errors when I try to compile programs using GTIRB. How can I make them go away?__ A: GTIRB requires C++17, including the C++17 standard library. If your compiler does not use C++17 by default, you will need to explicitly specify it when compiling programs that use GTIRB. For example: ``` g++ --std=c++17 my_gtirb_program.cpp -lgtirb -o my_gtirb_program ``` #### Linking error with "undefined reference" to gtirb __Q: I get linker errors when I try to compile and link programs using GTIRB. How can I make them go away?__ A: This isn't GTIRB specific, but if you place the `-lgtirb` on your compilation line *before* the source file the linker will sometimes throw away the symbols from the GTIRB library which it doesn't think it needs (and if it hasn't read your source yet it won't think it needs much). So if for example, ``` g++ --std=c++17 -lgtirb my_gtirb_program.cpp ``` doesn't work for you, then try this instead. ``` g++ --std=c++17 my_gtirb_program.cpp -lgtirb ``` ================================================ FILE: LICENSE.txt ================================================ ============================================================================== GTIRB is under the MIT License: ============================================================================== MIT License Copyright (c) 2018 GrammaTech, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================================================================== Software from third parties included in GTIRB: ============================================================================== GTIRB contains third party software which is under different license terms. All such code will be identified clearly using at least one of two mechanisms: 1) It will be in a separate directory tree with its own `LICENSE.txt` or `LICENSE` file at the top containing the specific license and restrictions which apply to that software, or 2) It will contain specific license and restriction terms at the top of every file. ============================================================================== Apache License v2.0 with LLVM Exceptions: ============================================================================== Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ---- LLVM Exceptions to the Apache 2.0 License ---- As an exception, if, as a result of your compiling your source code, portions of this Software are embedded into an Object form of such source code, you may redistribute such embedded portions in such Object form without complying with the conditions of Sections 4(a), 4(b) and 4(d) of the License. In addition, if you combine or link compiled forms of this Software with software that is licensed under the GPLv2 ("Combined Software") and if a court of competent jurisdiction determines that the patent provision (Section 3), the indemnity provision (Section 9) or other Section of the License conflicts with the conditions of the GPLv2, you may retroactively and prospectively choose to deem waived or otherwise exclude such Section(s) of the License, but only in their entirety and only with respect to the Combined Software. ============================================================================== University of Illinois Open Source License: ============================================================================== University of Illinois/NCSA Open Source License Copyright (c) 2003-2019 University of Illinois at Urbana-Champaign. All rights reserved. Developed by: LLVM Team University of Illinois at Urbana-Champaign http://llvm.org Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal with the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimers. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimers in the documentation and/or other materials provided with the distribution. * Neither the names of the LLVM Team, University of Illinois at Urbana-Champaign, nor the names of its contributors may be used to endorse or promote products derived from this Software without specific prior written permission. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE. ================================================ FILE: Macros.cmake ================================================ macro(IMPL_GTIRB_ADD_LINKER_FLAG flag) if(NOT ${CMAKE_EXE_LINKER_FLAGS} MATCHES "(${flag}.*)") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${flag}" CACHE STRING "Linker Flags" FORCE ) endif() if(NOT ${CMAKE_SHARED_LINKER_FLAGS} MATCHES "(${flag}.*)") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${flag}" CACHE STRING "Linker Flags" FORCE ) endif() endmacro() macro(GTIRB_ADD_MSVC_LINKER_FLAG flag) if(MSVC) impl_gtirb_add_linker_flag(${flag}) endif() endmacro() macro(GTIRB_ADD_GCC_LINKER_FLAG flag) if(CMAKE_COMPILER_IS_GNUCXX) impl_gtirb_add_linker_flag(${flag}) endif() endmacro() macro(GTIRB_ADD_CLANG_LINKER_FLAG flag) if(${CMAKE_CXX_COMPILER_ID} STREQUAL Clang) impl_gtirb_add_linker_flag(${flag}) endif() endmacro() macro(GTIRB_ADD_LIBRARY) add_library( ${PROJECT_NAME} ${${PROJECT_NAME}_H} ${${PROJECT_NAME}_SRC} ${${PROJECT_NAME}_PROTO} ) set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "gtirb") endmacro() macro(GTIRB_ADD_LIBRARY_STATIC) add_library( ${PROJECT_NAME} STATIC ${${PROJECT_NAME}_H} ${${PROJECT_NAME}_SRC} ) set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "gtirb") endmacro() macro(GTIRB_ADD_LIBRARY_HEADERONLY) add_library(${PROJECT_NAME} INTERFACE) target_include_directories( ${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} ) endmacro() macro(GTIRB_ADD_EXECUTABLE) add_executable(${PROJECT_NAME} ${${PROJECT_NAME}_H} ${${PROJECT_NAME}_SRC}) set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "gtirb/applications") endmacro() macro(GTIRB_ADD_EXECUTABLE_GTEST) add_executable(${PROJECT_NAME} ${${PROJECT_NAME}_H} ${${PROJECT_NAME}_SRC}) add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME}) set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "gtirb/test") endmacro() macro(GTIRB_GET_ALL_SUBDIRS result curdir) file( GLOB children RELATIVE ${curdir} ${curdir}/* ) set(dirlist "") foreach(child ${children}) if(IS_DIRECTORY ${curdir}/${child}) list(APPEND dirlist ${child}) endif() endforeach() set(${result} ${dirlist}) endmacro() macro(GTIRB_ADD_ALL_SUBDIRS) gtirb_get_all_subdirs(SUBDIRS ${CMAKE_CURRENT_SOURCE_DIR}) foreach(subdir ${SUBDIRS}) add_subdirectory(${subdir}) endforeach() endmacro() # Provide a vaguely consistent interface to find a Python 3 interpreter. Just # use FindPython3 if it exists, but fall back to looking for the interpreter # program if we have to. if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.12") macro(GTIRB_FIND_PYTHON) find_package(Python3 REQUIRED COMPONENTS Interpreter) if(Python3_EXECUTABLE) set(PYTHON ${Python3_EXECUTABLE}) endif() endmacro() else() macro(GTIRB_FIND_PYTHON) find_program(PYTHON NAMES python3 python py) if(PYTHON) execute_process( COMMAND "${PYTHON}" --version OUTPUT_VARIABLE Python3_VERSION ) string(REPLACE "Python " "" Python3_VERSION "${Python3_VERSION}") if("${Python3_VERSION}" VERSION_LESS 3) unset(PYTHON) unset(Python3_VERSION) endif() endif() endmacro() endif() ================================================ FILE: PROTOBUF.md ================================================ Using Serialized GTIRB Data =========================== > Note that for language with GTIRB APIs (at least C++, Python, Common > Lisp) using the GTIRB API is preferrable to manipulating protobuf > directly. GTIRB uses a serialized format that consists of an 8-byte signature followed by serialized [protobuf](https://github.com/google/protobuf/wiki) data. The protobuf data allows for exploration and manipulation in the language of your choice. The [Google protocol buffers](https://developers.google.com/protocol-buffers/) homepage lists the languages in which protocol buffers can be used directly; users of other languages can convert the protobuf-formatted data to JSON format and then use the JSON data in their applications. In the future we intend to define a standard JSON schema for GTIRB. The 8-byte signature that prefixes the protobuf data includes both GTIRB's magic number and the version fot GTIRB's protobuf specification that is in use. The layout is as follows: - Bytes 0-4 contain the ASCII characters: `GTIRB`. - Bytes 5-6 are considered reserved for future use and should be 0. - Byte 7 contains the GTIRB protobuf spec version in use. Directory `gtirb/src/proto` contains the protocol buffer message type definitions for GTIRB. You can inspect these `.proto` files to determine the structure of the various GTIRB message types. The top-level message type is `IR`. - [General Guidelines](#general-guidelines) - [Python Applications](#python-applications) - [Java Applications](#java-applications) # General Guidelines If you have not used protocol buffers before, there are several useful resources available at https://developers.google.com/protocol-buffers/, including an installation guide and a tutorial. In general, writing an application to use GTIRB data in protocol buffer format will involve the following steps. 1. Install the protocol buffer compiler (`protoc`) from https://github.com/protocolbuffers/protobuf/releases, if you haven't already done so. 2. Install any required protocol buffer library or libraries for the programming language you are using. 3. Invoke the protocol buffer compiler on the `.proto` files in `gtirb/src/proto/` to generate code in the language you wish to use. 4. Write your application, importing/including the file or files you generated in step 3. The [Protocol Buffers API Reference](https://developers.google.com/protocol-buffers/docs/reference/overview) provides language-specific instructions for the various supported programming languages, along with links to information for cases where support is provided by third-party plug-ins. # Python Applications To create a Python application that uses serialized GTIRB data, do the following. 1. Install the protocol buffer compiler (`protoc`). 2. Install the Python protobuf library, if you haven't already done so. $ pip install protobuf 3. Generate Python message definitions in a dedicated directory (for example, `python/`). $ mkdir -p python $ for f in src/proto/*.proto; do protoc -Isrc/proto --python_out=python $f done This will create a number of files with names of the form `_pb2.py` in the `python/` subdirectory of your working directory: one for each `.proto` in src/proto/, including `IR_pb2.py`. 4. Write your application. Make sure that it imports `IR_pb2`, or the parts of it that you require. 5. Run your application, making sure that the directory containing your message definitions is in the `PYTHONPATH`. ## Python Examples Directory `gtirb/doc/examples` contains several example Python scripts that use protocol buffers to explore serialized GTIRB data. - [cfg-paths.py](doc/examples/cfg-paths.py) - [data-symbols.py](doc/examples/data-symbols.py) # Java Applications To create a Java application that uses serialized GTIRB data, do the following. 1. Install the protocol buffer compiler (`protoc`). 2. Download the `protobuf` Java runtime from [https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java](https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java) and save it somewhere suitable. 3. Generate Java message definitions in a dedicated directory (for example, `java/`). $ mkdir -p java $ for f in src/proto/*.proto; do protoc -Isrc/proto --java_out=java $f done This will create a subdirectory `java/proto/', containing a number of files with names of the form `OuterClass.java`: one for each `.proto` in `src/proto/`. 4. Compile the Java message definitions, making sure the `protobuf` Java runtime `.jar` file is in your `CLASSPATH`. $ mkdir -p java/classfiles $ CLASSPATH= \ javac -d java/classfiles java/proto/*.java (If you want to build a `.jar` file to combine all these `.class` files, do so at this stage.) 5. Write your application. Make sure that it imports all the classes you need from the `proto` package. 6. Compile and run your application, making sure that your CLASSPATH contains both the `protobuf` Java runtime `.jar` file and the location of the your compiled message definition classes. ## Java Examples Directory `gtirb/doc/examples` contains several example Java programs that use protocol buffers to explore serialized GTIRB data. - [datasymbols.java](doc/examples/datasymbols.java) ================================================ FILE: README.md ================================================ # GTIRB The GrammaTech Intermediate Representation for Binaries (GTIRB) is a machine code analysis and rewriting data structure. It is intended to facilitate the communication of binary IR between programs performing binary disassembly, analysis, transformation, and pretty printing. GTIRB is modeled on LLVM-IR, and seeks to serve a similar functionality of encouraging communication and interoperability between tools. The remainder of this file describes various aspects of GTIRB: - [Structure](#structure) - [Installing](#installing) - [Building](#building) - [Usage](#usage) # Structure GTIRB has the following structure. Solid lines denote inheritance. Dotted lines denote reference by UUID. ![GTIRB Data Structure](.gtirb.svg) ## IR An instance of GTIRB may include multiple modules (`Module`) which represent loadable objects such as executables or libraries, an inter-procedural control flow graph (`IPCFG`), and Auxiliary Data tables (`AuxData`) which can hold arbitrary analysis results in user-defined formats which can easily reference other elements of the IR. Each module holds information such as symbols (`Symbol`) and sections which themselves hold the actual bytes and data and code blocks of the module. The CFG consists of basic blocks (`Block`) and control flow edges between these blocks. Each data or code block references a range of bytes in a byte interval (`ByteInterval`). A section may hold one large byte interval holding all blocks---if the relative positions of blocks in that section are defined---or may hold one byte interval per block---if the relative positions of blocks is not defined, e.g. for the code blocks in the `.text` section during program rewriting. Each symbol holds a pointer to the block or datum it references. ## Instructions GTIRB explicitly does NOT represent instructions or instruction semantics but does provide symbolic operand information and access to the bytes. There are many *intermediate languages* (IL)s for representation of instruction semantics (e.g., [BAP][]'s [BIL][], [Angr][]'s [Vex][], or [Ghidra][]'s P-code). GTIRB works with these or any other IL by storing instructions generally and efficiently as *raw machine-code bytes* and separately storing the symbolic and control flow information. The popular [Capstone][]/[Keystone][] decoder/encoder provide an excellent option to read and write instructions from/to GTIRB's machine-code byte representation without committing to any particular semantic IL. By supporting multiple ILs and separate storage of analysis results in auxiliary data tables GTIRB enables collaboration between independent binary analysis and rewriting teams and tools. [BAP]: https://github.com/BinaryAnalysisPlatform/bap [BIL]: https://github.com/BinaryAnalysisPlatform/bil/releases/download/v0.1/bil.pdf [Angr]: http://angr.io [Vex]: https://github.com/angr/pyvex [Ghidra]: https://www.nsa.gov/resources/everyone/ghidra/ [Capstone]: https://www.capstone-engine.org [Keystone]: https://www.keystone-engine.org ## Auxiliary Data GTIRB provides for the sharing of additional information, e.g. analysis results, in the form of `AuxData` objects. These can store maps and vectors of basic GTIRB types in a portable way. The [GTIRB manual][] describes the structure for common types of auxiliary data such as function boundary information, type information, or results of common analyses in [Standard AuxData Schemata][]. [GTIRB manual]: https://grammatech.github.io/gtirb/ [Standard AuxData Schemata]: https://grammatech.github.io/gtirb/md__aux_data.html ## UUIDs Every element of GTIRB---e.g., modules (`Module`), symbols (`Symbol`), and blocks (`Block`)---has a universally unique identifier (UUID). UUIDs allow both first-class IR components and AuxData tables to reference elements of the IR. Instructions and symbolic operands can be addressed by the class `Offset` which encapsulates a UUID (that refers to the instruction's block) and an offset. # Installing Packages currently exist for easily installing GTIRB (and attendant tooling including the [ddisasm][] disassembler and [gtirb-pprinter][] pretty printer) on Windows, and Ubuntu 20. See below for instructions. Additionally, a public Docker image exists at [grammatech/ddisasm][] with all of these tools installed. GTIRB is versioned with Major.Minor.Patch versioning where Major version increments will require significant source changes but should be very rare, Minor version increments may require small source changes, and Patch version increments shouldn't break any downstream builds. We do not yet provide ABI compatibility across any version changes. [ddisasm]: https://github.com/GrammaTech/ddisasm [gtirb-pprinter]: https://github.com/GrammaTech/gtirb-pprinter [grammatech/ddisasm]: https://hub.docker.com/r/grammatech/ddisasm ## Python API The latest stable GTIRB Python API may be installed from PyPI using pip: ```sh pip install gtirb ``` The latest unstable version of the Python API can be installed from a prebuilt wheel: ```sh pip install https://download.grammatech.com/gtirb/files/python/gtirb-0.dev-py3-none-any.whl ``` It is critical that the choice of a `stable` or `unstable` package matches the installed ddisasm and gtirb-pprinter packages. ## Windows Windows releases are packaged as .zip files and are available at https://download.grammatech.com/gtirb/files/windows-release/. ## Ubuntu Packages for Ubuntu 20 are available in the GTIRB apt repository and may be installed per the following instructions. First, add GrammaTech's APT key. ```sh wget -O - https://download.grammatech.com/gtirb/files/apt-repo/conf/apt.gpg.key | apt-key add - ``` Next update your sources.list file. ```sh echo "deb [arch=amd64] https://download.grammatech.com/gtirb/files/apt-repo [distribution] [component]"| sudo tee -a /etc/apt/sources.list ``` Where: - `[distribution]` is `focal` (currently, only Ubuntu 20 packages are available) - `[component]` is either `stable`, which holds the last versioned release, or `unstable`, which holds the HEAD of the repository. Finally update your package database and install the core GTIRB tools: ```sh sudo apt-get update sudo apt-get install gtirb-pprinter ddisasm ``` **Warning**: Stable versions gtirb-2.0.0, gtirb-pprinter-2.1.0, ddisasm-1.8.0 and OLDER rely on metapackages which cause conflicts if you try `apt-get upgrade` (see https://github.com/GrammaTech/gtirb/issues/63). In this case, uninstall and reinstall the packages you got from the GTIRB repository. You may need to use `dpkg --remove` to remove the metapackages (e.g. `ddisasm`) before removing the concrete versioned packages (e.g. `ddisasm-1.5.1`). NEWER stable versions no longer rely on metapackages and can be upgraded without problems. # Building GTIRB's C++ API should successfully build in 64-bits with GCC, Clang, and Visual Studio compilers supporting at least C++17. GTIRB uses CMake which must be installed with at least version 3.10. The common build process looks like this: ```sh mkdir build cd build # Note: You may wish to add some -D arguments to the next command. See below. cmake cmake --build . # Run the test suite. ctest ``` For customizing the GTIRB build, you can get a list of customization options by navigating to your build directory and running: ```sh cmake -LH ``` ## Requirements To build and install GTIRB, the following requirements should be installed: - [CMake][], version 3.10.0 or higher. - Ubuntu 18 provides this version via the APT package `cmake`. - Ubuntu 16 and earlier provide out of date versions; build from source on those versions. - [Protobuf][], version 3.0.0 or later. - Ubuntu 18 provides this version via the APT packages `libprotobuf-dev` and `protobuf-compiler`. - Ubuntu 16 and earlier provide out of date versions; build from source on those versions. - Boost [(non-standard Ubuntu package from launchpad.net)][], version 1.68 or later. - Ubuntu 18 only has version 1.65 in the standard repository. See Ubuntu instructions above. [CMake]: https://cmake.org/ [Protobuf]: https://developers.google.com/protocol-buffers/ [(non-standard Ubuntu package from launchpad.net)]: https://launchpad.net/~mhier/+archive/ubuntu/libboost-latest # Usage GTIRB is designed to be serialized using [Google protocol buffers][] (i.e., [protobuf][]), enabling [easy and efficient use from any programming language](#using-serialized-gtirb-data). GTIRB may also be used through a dedicated API implemented in multiple languages. The APIs provide efficient data structures suitable for use by binary analysis and rewriting applications; see [below](#gtirb-api-implementations) for details. [Google protocol buffers]: https://developers.google.com/protocol-buffers/ [protobuf]: https://github.com/google/protobuf/wiki ## Using Serialized GTIRB Data GTIRB uses a serialized format that consists of an 8-byte signature followed by serialized [protobuf][] data. The protobuf data allows for exploration and manipulation in the language of your choice. The [Google protocol buffers][] homepage lists the languages in which protocol buffers can be used directly; users of other languages can convert the protobuf-formatted data to JSON format and then use the JSON data in their applications. The `proto` directory in this repository contains the protocol buffer message type definitions for GTIRB. You can inspect these `.proto` files to determine the structure of the various GTIRB message types. The top-level message type is `IR`. For more details, see [Using Serialized GTIRB Data](PROTOBUF.md). ## GTIRB API Implementations The GTIRB API is currently available in C++, Python, and Common Lisp. There is a *partial* Java API which is not ready for external use. For language-independent API information, see [GTIRB Components](doc/general/ComponentsIndex.md). For information about the different API implementations, see: - [C++ API](doc/cpp/README.md) - [Python API](python/README.md) - [Common Lisp API](cl/README.md) - Java API **incomplete** ================================================ FILE: cl/CMakeLists.txt ================================================ file(GLOB CL_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.lisp ${CMAKE_CURRENT_SOURCE_DIR}/*.asd ) set(CL_EXECUTABLE_STEMS dot update) foreach(CL_EXECUTABLE_STEM ${CL_EXECUTABLE_STEMS}) set(CL_EXECUTABLE ${CMAKE_CURRENT_BINARY_DIR}/gtirb-${CL_EXECUTABLE_STEM}) list(APPEND CL_EXECUTABLES ${CL_EXECUTABLE}) add_custom_command( OUTPUT ${CL_EXECUTABLE} DEPENDS ${CL_SOURCES} ${PROTO_FILES} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${LISP} --noinform --dynamic-space-size 16384 --no-userinit --no-sysinit --disable-debugger --load ${QUICKLISP}/setup.lisp --eval "(asdf:initialize-source-registry `(:source-registry (:tree \"${CMAKE_CURRENT_SOURCE_DIR}\") :inherit-configuration))" --eval "(ql:quickload :gtirb/${CL_EXECUTABLE_STEM})" --eval "(setf uiop/image::*lisp-interaction* nil)" --eval "(asdf:make :gtirb/run-${CL_EXECUTABLE_STEM} :type :program :monolithic t)" --eval "(uiop/image:quit)" COMMAND ${CMAKE_COMMAND} -E copy gtirb-${CL_EXECUTABLE_STEM} ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${CMAKE_COMMAND} -E remove gtirb-${CL_EXECUTABLE_STEM} VERBATIM ) endforeach(CL_EXECUTABLE_STEM) add_custom_target(clgtirb ALL DEPENDS ${CL_EXECUTABLES}) if(GTIRB_ENABLE_TESTS) add_test( NAME testgtirbcl COMMAND ${LISP} --noinform --dynamic-space-size 16384 --no-userinit --no-sysinit --disable-debugger --load "${QUICKLISP}/setup.lisp" --eval "(asdf:initialize-source-registry `(:source-registry (:tree \"${CMAKE_CURRENT_SOURCE_DIR}\") :inherit-configuration))" --eval "(ql:quickload :gtirb/test)" --eval "(gtirb/test:batch-test)" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/" ) endif() ================================================ FILE: cl/README.md ================================================ Common Lisp library for GTIRB ============================= The Common Lisp API for GrammaTech's IR for Binaries (GTIRB). GTIRB is a data structure designed to support the analysis and rewriting of binary executables. There are a number of tools that produce, process and consume GTIRB. See the following for more information on GTIRB: - [https://github.com/grammatech/gtirb](https://github.com/grammatech/gtirb) the main GTIRB source repository. - [https://grammatech.github.io/gtirb/](https://grammatech.github.io/gtirb/) the GTIRB manual repository. - [https://arxiv.org/abs/1907.02859](https://arxiv.org/abs/1907.02859) a white-paper describing the design goals of GTIRB. - [https://github.com/grammatech/ddisasm](https://github.com/grammatech/ddisasm) a very high performance reassembleable disassembler producing GTIRB. - [https://github.com/grammatech/gtirb-pprinter](https://github.com/grammatech/gtirb-pprinter) a pretty printer from GTIRB to assembler. ## Requirements and Installation Hopefully, eventually, it will be possible to install everything by (1) installing [Protobuf](https://developers.google.com/protocol-buffers/), version 3.7.0 or later, and then (2) installing this Common Lisp GTIRB library with QuickLisp `(ql:quickload :gtirb)`. We're a ways away from that currently. So after you've installed Protobuf, you should clone and install the Common Lisp `PROTOBUF` package manually according to the instructions at [https://github.com/brown/protobuf](https://github.com/brown/protobuf) ensuring that the `protoc-gen-lisp` executable has been built and is on your path. At that point you should be able to load the GTIRB package. ## Usage The Common Lisp API attempts to provide access to the underlying GTIRB data-structure described above in idiomatic common lisp. The main Protobuf data structures are wrapped in CLOS objects. All fields are modifiable with `setf`. Invariant are maintained automatically by the API, e.g. using `:around` methods. In some cases accessors are provided beyond the fields directly present in the Protobuf. For example, every GTIRB element has a UUID (which supports referencing elements from AuxData tables). The Common Lisp API provides uniform access to any element through the `get-uuid` method which operates similarly to `gethash` only it may be called on any top-level GTIRB `IR` object (which itself maintains a hash of every contained element by UUID). The GTIRB CFG is represented as a graph using the Common Lisp graph library from [https://github.com/eschulte/graph](https://github.com/eschulte/graph). This simple representation should promote easy exploration and modification of the control flow graph, and the many graph analysis functions defined in that library may be directly applied to the CFG. Every node of the graph holds the UUID for a code block. The bytes of any code and data block may be accessed by calling the `bytes` method, which provides directly access to the bytes of the block's `byte-interval`. ### Example Usage See the test suite for a large number of basic usage examples. However, the following gives a simple usage example. 1. From the command-line. Use the datalog disassembler `ddisasm` to disassemble the `ls` executable into a GTIRB instance. ddisasm --ir $(which ls) /tmp/ls.gtirb 2. From the Common Lisp REPL. Load the GTIRB API, and then load the GTIRB instance created in step (1) into a common lisp GTIRB object. (ql:quickload :gtirb) (use-package :gtirb) (defparameter ls (read-gtirb "/tmp/ls.gtirb")) 3. At this point you can explore the CFG, perform analyses, or even modify the contents of the GTIRB object. Results of analyses may be saved into new AuxData tables which become part of the GTIRB object for later use by other sessions or by other tools potentially written in other languages. ;; Do stuff with the GTIRB, maybe make changes. 4. Finally, the resulting GTIRB object may be written back to the file system. (write-gtirb ls "/tmp/ls-modified.gtirb") 5. At the command line. A new executable may be created from the modified gtirb file using the `gtirb-pprinter`. gtirb-pprinter --ir /tmp/ls-modified.gtirb --binary /tmp/ls-modified ================================================ FILE: cl/dot.lisp ================================================ (defpackage :gtirb/dot (:use :common-lisp :alexandria :graph :graph/dot :gtirb :command-line-arguments :named-readtables :curry-compose-reader-macros) (:import-from :uiop :nest) (:import-from :uiop/image :quit) (:shadowing-import-from :proto-v0 :ir) (:shadowing-import-from :gtirb :symbol) (:export :to-dot :to-dot-file)) (in-package :gtirb/dot) (in-readtable :curry-compose-reader-macros) (defun dot-edge-label (graph edge) (let ((obj (edge-value graph edge))) (format nil "\"~a[~:[U~;C~]:~:[I~;D~]]\"" (edge-type obj) (conditional obj) (direct obj)))) (defmethod to-dot ((obj gtirb) &rest rest) "Write the CFG of MODULE to the Graphviz graphing language." (apply #'to-dot (cfg obj) :edge-attrs (list (cons :label {dot-edge-label (cfg obj)})) rest)) (eval-when (:compile-toplevel :load-toplevel :execute) (defparameter +udpate-args+ '((("help" #\h #\?) :type boolean :optional t :documentation "display help output")))) ;;; NOTE: When Quicklisp updates graph to the latest this can be removed. (defmethod to-dot-file ((object t) path &key attributes node-attrs edge-attrs subgraphs ranks) (with-open-file (out path :direction :output :if-exists :supersede) (to-dot object :stream out :attributes attributes :node-attrs node-attrs :edge-attrs edge-attrs :subgraphs subgraphs :ranks ranks))) (define-command dot (gtirb-file dot-file &spec +udpate-args+) "Write first GTIRB module in GTIRB-FILE to DOT-FILE." "" (when help (show-help-for-dot) (quit)) (to-dot-file (read-gtirb gtirb-file) dot-file)) ================================================ FILE: cl/gtirb.asd ================================================ (defsystem "gtirb" :name "gtirb" :author "GrammaTech" :licence "MIT" :description "Common Lisp library for GTIRB" :long-description "A Common Lisp front end to the GrammaTech Intermediate Representation for Bianries (GTIRB). GTIRB is serialized using Google's protocol buffers. This library wraps the raw protocol buffer serialization with a more Lispy interface." :depends-on (:gtirb/gtirb) :class :package-inferred-system :defsystem-depends-on (:asdf-package-system :protobuf) :in-order-to ((test-op (load-op "gtirb/test"))) :perform (test-op (o c) (symbol-call :gtirb/test '#:test))) (defsystem "proto-v0" :name "proto-v0" :description "Common Lisp interface to (old V0) GTIRB protobuf files" :author "GrammaTech" :license "MIT" :defsystem-depends-on (:protobuf) :components ((:static-file "README.md") ;; See the protobuf defsystem extension for how the gtirb.proto ;; file is loaded into Lisp. https://github.com/brown/protobuf (:module proto :pathname "../proto/v0/" :components ((:protobuf-source-file "AuxDataContainer") (:protobuf-source-file "CFG") (:protobuf-source-file "Section") (:protobuf-source-file "Offset") (:protobuf-source-file "IR") (:protobuf-source-file "ByteMap") (:protobuf-source-file "ProxyBlock") (:protobuf-source-file "AuxData") (:protobuf-source-file "Module") (:protobuf-source-file "DataObject") (:protobuf-source-file "ImageByteMap") (:protobuf-source-file "SymbolicExpression") (:protobuf-source-file "Symbol") (:protobuf-source-file "Block"))))) (defsystem "proto" :name "proto" :description "Common Lisp interface to GTIRB protobuf files" :author "GrammaTech" :license "MIT" :defsystem-depends-on (:protobuf) :components ((:static-file "README.md") ;; See the protobuf defsystem extension for how the gtirb.proto ;; file is loaded into Lisp. https://github.com/brown/protobuf (:module proto :pathname "../proto" :components ((:protobuf-source-file "AuxData") (:protobuf-source-file "ByteInterval") (:protobuf-source-file "CFG") (:protobuf-source-file "CodeBlock") (:protobuf-source-file "Offset") (:protobuf-source-file "DataBlock") (:protobuf-source-file "IR") (:protobuf-source-file "Module") (:protobuf-source-file "ProxyBlock") (:protobuf-source-file "Section") (:protobuf-source-file "Symbol") (:protobuf-source-file "SymbolicExpression"))))) (defsystem "gtirb/run-validate" :author "GrammaTech" :licence "MIT" :description "Validate a GTIRB instance." :depends-on (gtirb/validate) :build-operation "asdf:program-op" :build-pathname "gtirb-validate" :entry-point "gtirb/validate::run-validate-file") (defsystem "gtirb/run-update" :author "GrammaTech" :licence "MIT" :description "Convert between GTIRB protobuf versions." :depends-on (gtirb/update) :build-operation "asdf:program-op" :build-pathname "gtirb-update" :entry-point "gtirb/update::run-update") (defsystem "gtirb/run-dot" :author "GrammaTech" :licence "MIT" :description "Write GTIRB to a dot graph." :depends-on (gtirb/dot) :build-operation "asdf:program-op" :build-pathname "gtirb-dot" :entry-point "gtirb/dot::run-dot") (register-system-packages "proto" '(:gtirb.proto)) (register-system-packages "cl-interval" '(:interval)) ================================================ FILE: cl/gtirb.lisp ================================================ (defpackage :gtirb/gtirb (:nicknames :gtirb) (:use :common-lisp :alexandria :cl-ppcre :graph :trivia :trivial-utf-8 :ieee-floats :gtirb/ranged :gtirb/utility :gtirb/version :named-readtables :curry-compose-reader-macros) (:shadow :symbol) (:import-from :gtirb.proto) (:import-from :trivial-package-local-nicknames :add-package-local-nickname) (:import-from :uiop :nest) (:import-from :cl-intbytes :int->octets :octets->int :octets->uint) (:export :read-gtirb :write-gtirb :is-equal-p :*is-equal-p-verbose-p* :gtirb-node :get-uuid :remove-uuid :at-address :on-address :address-range :uuid :update-proto ;;; Classes and fields. :gtirb :ir :cfg :version ;; Module :module :name :binary-path :isa :file-format :byte-order :preferred-addr :rebase-delta :symbols :proxies :sections :aux-data :entry-point ;; Symbol :symbol :value :payload :at-end ;; Section :section :byte-intervals :flags ;; Byte-Interval :byte-interval :blocks :addressp :address :contents :size :truncate-contents :ignore ;; Symbolic expressions :symbolic-expressions :sym-addr-const :sym-addr-addr :scale :*preserve-symbolic-expressions* ;; Block :gtirb-block :gtirb-byte-block :code-block :data-block :decode-mode :bytes :offset ;; Edge-Label :edge-label :conditional :direct :edge-type ;; Aux-Data :aux-data-type :aux-data-data ;; gtirb :modules)) (in-package :gtirb/gtirb) (in-readtable :curry-compose-reader-macros) (eval-when (:compile-toplevel :load-toplevel :execute) (add-package-local-nickname :proto :gtirb.proto)) (defgeneric read-gtirb (source) (:documentation "Read a protobuf serialized GTIRB instance from SOURCE.") (:method ((path t)) (make-instance 'gtirb :proto (read-proto 'proto:ir path)))) (defmethod read-gtirb :around (source) "Check the protobuf version." (let ((gtirb (call-next-method))) (unless (= protobuf-version (proto:version (proto gtirb))) (warn "Protobuf version mismatch version ~a from ~a isn't expected ~a" (proto:version (proto gtirb)) source protobuf-version)) gtirb)) (defun write-gtirb (gtirb path) "Write a GTIRB IR object to PATH." (update-proto gtirb) (write-proto (proto gtirb) path)) ;;;; Class utilities. (defvar *is-equal-p-verbose-p* nil "Compare equality verbosely in the `is-equal-p' function. This may be useful to print contextual information when an equality comparison fails for a large object with many nested objects.") (defvar *is-equal-p-verbose-output-buffer* nil "Buffer to hold output of is-equal-p verbose failure messages.") (defvar *is-equal-p-verbose-output-length* 10 "Maximum length of output to show of `*is-equal-p-verbose-output-buffer*'.") (defmacro compare-or-verbose (comparison left right &rest flags) `(or (,comparison ,left ,right ,@flags) (prog1 nil (when *is-equal-p-verbose-p* (push (format nil "NOT ~S" (list ',comparison ,left ,right)) *is-equal-p-verbose-output-buffer*))))) (defun is-equal-p (left right) "Return t if LEFT and RIGHT are equal. Recursively descend into any sub-structure. Custom recursive equality predicates are defined for common Common Lisp data structures as well as all GTIRB structures." (let ((*is-equal-p-verbose-output-buffer* nil)) (let ((equalp (is-equal-p-internal left right))) (prog1 equalp (when (and (not equalp) *is-equal-p-verbose-p*) (format t "~{~S~%~}" (subseq *is-equal-p-verbose-output-buffer* 0 *is-equal-p-verbose-output-length*))))))) (defmethod is-equal-p-internal :around ((left t) (right t)) (let ((equalp (call-next-method))) (when equalp (setf *is-equal-p-verbose-output-buffer* nil)) equalp)) (defgeneric is-equal-p-internal (left right) (:documentation "Internal function called by `is-equal-p'.") (:method ((left t) (right t)) (compare-or-verbose equalp left right)) (:method ((left number) (right number)) (compare-or-verbose = left right)) (:method ((left cl:symbol) (right cl:symbol)) (compare-or-verbose eql left right)) (:method ((left string) (right string)) (compare-or-verbose string= left right)) (:method ((left cons) (right cons)) (if (and (proper-list-p left) (proper-list-p right)) (compare-or-verbose set-equal left right :test #'is-equal-p-internal) (and (compare-or-verbose is-equal-p-internal (car left) (car right)) (compare-or-verbose is-equal-p-internal (cdr left) (cdr right))))) (:method ((left hash-table) (right hash-table)) (compare-or-verbose set-equal (hash-table-alist left) (hash-table-alist right) :test #'is-equal-p-internal)) (:method ((left graph:digraph) (right graph:digraph)) (and (compare-or-verbose set-equal (graph:nodes left) (graph:nodes right) :test #'is-equal-p-internal) (compare-or-verbose set-equal (graph:edges-w-values left) (graph:edges-w-values right) :test #'is-equal-p-internal)))) (defclass gtirb-node () () (:documentation "Objects with a UUID contained in a GTIRB instance.")) (defclass proto-backed (gtirb-node) () (:documentation "Objects which may be serialized to/from protobuf.")) (defgeneric uuid (object) (:documentation "Return the UUID for OBJECT as an integer.") (:method ((obj proto-backed)) (uuid-to-integer (proto:uuid (proto obj))))) (defgeneric get-uuid (uuid object) (:documentation "Get the referent of UUID in OBJECT.")) (defgeneric remove-uuid (uuid object) (:documentation "Remove the entry for UUID from OBJECT.")) (defgeneric (setf get-uuid) (new uuid object) (:documentation "Register REFERENT behind UUID in OBJECT.")) (defgeneric insert-address (object item start-address &optional end-address) (:documentation "Insert ITEM into OBJECT between START-ADDRESS and END-ADDRESS.")) (defgeneric delete-address (object item start-address &optional end-address) (:documentation "Delete ITEM from OBJECT between START-ADDRESS and END-ADDRESS.")) (defgeneric at-address (object address) (:documentation "Find all objects in OBJECT starting at ADDRESS.")) (defgeneric on-address (object start-address &optional end-address) (:documentation "Find all objects in OBJECT between START-ADDRESS and END-ADDRESS.")) (defgeneric set-parent-uuid (new uuid object) (:documentation "Set UUID to NEW in OBJECT's parent.")) (defgeneric update-proto (proto-backed-object) (:documentation "Update and return the `proto' field of PROTO-BACKED-OBJECT. This will ensure that any changes made to PROTO-BACKED-OBJECT outside of its protocol buffer, e.g. any slots initialized using the :from-proto option to `define-proto-backed-class', are synchronized against the object's protocol buffer.") (:method ((proto-backed-object proto-backed)) (proto proto-backed-object))) (defgeneric address-range (proto-backed-object) (:documentation "Return any address range of the PROTO-BACKED-OBJECT GTIRB object.")) (defmacro define-proto-backed-class ((class proto-class) super-classes slot-specifiers proto-fields &rest options) "Define a Common Lisp class backed by a protobuf class. SLOT-SPECIFIERS is as in `defclass' with the addition of optional :to-proto and :from-proto fields, which may take protobuf serialization functions, and :skip-equal-p field which causes `is-equal-p' to skip that field. PROTO-FIELDS may hold a list of fields which pass through directly to the backing protobuf class. The :parent option names the field holding the containing protobuf element. The :address-range option holds the logic to calculate an address range for instances of the object." (nest (flet ((plist-get (item list) (second (member item list))) (plist-drop (item list) (if-let ((location (position item list))) (append (subseq list 0 location) (subseq list (+ 2 location))) list)))) (let ((from-proto-slots (remove-if-not {find :from-proto} slot-specifiers)) (to-proto-slots (remove-if-not {find :to-proto} slot-specifiers)) (parent (second (assoc :parent options))) (address-range (cdr (assoc :address-range options))))) `(progn (defclass ,class (proto-backed ,@super-classes) ;; Accessors for normal lisp classes ((proto :initarg :proto :accessor proto :type ,proto-class :initform ,(if parent `(let ((it (make-instance ',proto-class))) (setf (proto::uuid it) (new-uuid)) it) `(make-instance ',proto-class)) :documentation "Backing protobuf object. Should not need to be manipulated by client code.") (ir :accessor ir :type (or null gtirb) :initarg :ir :initform nil :documentation ,(format nil "Access the top-level IR of this ~a." class)) ;; TODO: Consider throwing warnings in a `setf :around' ;; defmethod on the parents of objects with parents if ;; the objects are set to something that already has a ;; current parent. This could avoid surprising ;; inconsistencies. Alternately this could throw an ;; error with the option to copy the object with a new ;; corrected parent or to set the parent directly. ,@(when parent `((,parent :accessor ,parent :type (or null ,parent) :initarg ,(make-keyword parent) :initform nil :documentation ,(format nil "Access the ~a of this ~a." parent class)))) ,@(mapcar [{plist-drop :to-proto} {plist-drop :from-proto} {plist-drop :proto-field} {plist-drop :skip-equal-p}] slot-specifiers)) ,@(remove-if [«or {eql :parent} {eql :address-range}» #'car] options)) ,@(when parent `((defmethod get-uuid (uuid (object ,class)) (assert (or (ir object) (,parent object)) (object) ,(format nil "`get-uuid' failed on a ~a without a ~a" class parent)) (get-uuid uuid (or (ir object) (,parent object)))) (defmethod set-parent-uuid (new uuid (object ,class)) (assert (or (ir object) (,parent object)) (object) ,(format nil "`set-parent-uuid' failed on a ~a without a ~a" class parent)) (setf (get-uuid uuid (or (ir object) (,parent object))) new)) (defmethod (setf get-uuid) (new uuid (object ,class)) (assert (or (ir object) (,parent object)) (object) ,(format nil "`get-uuid' failed on ~a without a ~a" class parent)) (set-parent-uuid new uuid object)) (defmethod remove-uuid (uuid (object ,class)) (assert (or (ir object) (,parent object)) (object) ,(format nil "`remove-uuid' failed on ~a without a ~a" class parent)) (remove-uuid uuid (or (ir object) (,parent object)))) (defmethod at-address ((object ,class) address) (assert (or (ir object) (,parent object)) (object) ,(format nil "`at-address' failed on ~a without a ~a" class parent)) (at-address (or (ir object) (,parent object)) address)) (defmethod on-address ((object ,class) start &optional end) (assert (or (ir object) (,parent object)) (object) ,(format nil "`on-address' failed on ~a without a ~a" class parent)) (on-address (or (ir object) (,parent object)) start end)))) (defmethod address-range ((self ,class)) ,@address-range) (defmethod initialize-instance :after ((self ,class) &key) ,@(when parent `((when (,parent self) (setf (get-uuid (uuid-to-integer (proto:uuid (proto self))) self) self)))) (with-slots (proto ,@(mapcar #'car from-proto-slots)) self ,@(mapcar (lambda (spec) (destructuring-bind (slot &key from-proto &allow-other-keys) spec `(setf ,slot (funcall ,from-proto proto)))) from-proto-slots))) (defmethod update-proto ((self ,class)) ,@(mapcar (lambda (spec) (destructuring-bind (slot &key to-proto (proto-field slot) &allow-other-keys) spec `(setf (,(intern (symbol-name proto-field) 'proto) (proto self)) (funcall ,to-proto (,slot self))))) to-proto-slots) (proto self)) ;; Equality check on class. ;; ;; NOTE: For this to work we might need to add an optional :only ;; field to both slot-specifiers and proto-fields. This ;; would mean that the equality of this field is only ;; checked with this form returns true. E.g., on ;; byte-intervals we could say: ;; (address :type unsigned-byte-64 :only #'addressp) (defmethod is-equal-p-internal ((left ,class) (right ,class)) (and ,@(mapcar (lambda (accessor) `(compare-or-verbose is-equal-p-internal (,accessor left) (,accessor right))) (append (mapcar {plist-get :accessor} (remove-if {plist-get :skip-equal-p} slot-specifiers)) (mapcar #'car proto-fields))))) ;; Pass-through accessors for protobuf fields so they operate ;; directly on the backing protobuf object. ,@(apply #'append (mapcar (nest (lambda (pair)) (destructuring-bind (name &key type documentation enumeration (proto-field name) &allow-other-keys) pair) (let ((base `(,(intern (symbol-name proto-field) 'proto) (proto obj))))) `((defmethod ,name ((obj ,class)) ,@(when documentation (list documentation)) ,(ecase type ((unsigned-byte-64 boolean bytes) base) (enumeration `(cdr (assoc ,base ,enumeration))) (uuid `(uuid-to-integer ,base)) (string `(pb:string-value ,base)))) (defmethod (setf ,name) (new (obj ,class)) ,@(when documentation (list documentation)) ,(ecase type ((unsigned-byte-64 boolean) `(setf ,base new)) (bytes `(setf ,base (force-byte-array new))) (enumeration `(setf ,base (car (rassoc new ,enumeration)))) (uuid `(setf ,base (integer-to-uuid new))) (string `(setf ,base (pb:string-field new))))))) proto-fields))))) (eval-when (:compile-toplevel :load-toplevel :execute) (defvar aux-data-slot-definition "A-list of auxiliary data objects keyed by string name. Aux-Data tables may hold structured or unstructured data. This data may refer to elements of the GTIRB IR through uuids. Information relevant to a particular module will be stored in Aux-Data tables accessible from the specific module. Aux-Data tables only exist on modules and on GTIRB IR instances.")) ;;;; Classes. (define-proto-backed-class (gtirb proto:ir) () ((modules :initarg modules :accessor modules :type list :initform nil :from-proto [{mapcar {make-instance 'module :ir self :gtirb self :proto}} {coerce _ 'list} #'proto:modules] :to-proto {map 'vector #'update-proto} :documentation "List of the modules on a top-level GTIRB IR instance.") (cfg :accessor cfg :type digraph :from-proto (lambda (proto) (let ((p-cfg (proto:cfg proto))) (populate (make-instance 'digraph) :edges-w-values (mapcar (lambda (edge) (cons (list (uuid-to-integer (proto:source-uuid edge)) (uuid-to-integer (proto:target-uuid edge))) (make-instance 'edge-label :proto (proto:label edge)))) (coerce (proto:edges p-cfg) 'list)) :nodes (map 'list #'uuid-to-integer (proto:vertices p-cfg))))) :to-proto (lambda (cfg &aux (p-cfg (make-instance 'proto:cfg))) (setf (proto:vertices p-cfg) (map 'vector #'integer-to-uuid (nodes cfg)) (proto:edges p-cfg) (map 'vector (lambda (edge) (destructuring-bind ((source target) . label) edge (let ((p-edge (make-instance 'proto:edge))) (setf (proto:source-uuid p-edge) (integer-to-uuid source) (proto:target-uuid p-edge) (integer-to-uuid target) (proto:label p-edge) (proto label)) p-edge))) (edges-w-values cfg))) p-cfg) :documentation "Control flow graph (CFG) represented as a `graph:digraph'. Nodes in the graph hold the UUIDs of code blocks which may be looked up using `get-uuid'. Edges on the graph are labeled with `edge-label' objects which provide information on the nature of the control flow of the graph.") (aux-data :accessor aux-data :type list :from-proto #'aux-data-from-proto :to-proto #'aux-data-to-proto :documentation #.aux-data-slot-definition) (by-uuid :accessor by-uuid :initform (make-hash-table) :type hash-table :skip-equal-p t :documentation "Internal cache for UUID-based lookup.") (by-address :accessor by-address :initform (make-ranged) :skip-equal-p t :documentation "Internal cache for Address-based lookup.") (aux-data-w-offsets :accessor aux-data-w-offsets :initform nil :skip-equal-p t :documentation "Cache for fast offset updates.")) ((version :type unsigned-byte-64 :documentation "Protobuf version.")) (:documentation "Base class of an instance of GTIRB IR.")) (defmethod initialize-instance :around ((self gtirb) &key) (call-next-method) ;; Populate the aux-data tables with offsets. (setf (aux-data-w-offsets self) (get-aux-data-w-offsets self))) (define-condition ir (error) ((message :initarg :message :initform nil :reader message) (object :initarg :object :initform nil :reader object)) (:report (lambda (condition stream) (format stream "GTIRB error ~S on ~S." (message condition) (object condition)))) (:documentation "Condition raised on GTIRB data structure violations.")) (defmethod print-object ((obj gtirb) stream) (print-unreadable-object (obj stream :type t :identity t) (format stream "~a" (modules obj)))) (defmethod ir ((obj gtirb)) obj) (defmethod get-uuid (uuid (obj gtirb)) (gethash uuid (by-uuid obj))) (defmethod (setf get-uuid) (new uuid (obj gtirb)) (when (zerop uuid) (warn "Saving object ~a without a UUID into ~a." new obj)) (when-let ((range (address-range new))) #+debug (format t "(range ~S) ;; => ~S~%" new range) (apply #'insert-address obj new range)) (setf (gethash uuid (by-uuid obj)) new)) (defmethod remove-uuid (uuid (obj gtirb)) (remhash uuid (by-uuid obj))) (defmethod insert-address ((gtirb gtirb) item start &optional end) (ranged-insert (by-address gtirb) (uuid item) start end)) (defmethod delete-address ((gtirb gtirb) item start &optional end) (ranged-delete (by-address gtirb) (uuid item) start end)) (defmethod at-address ((gtirb gtirb) address) (mapcar {get-uuid _ gtirb} (ranged-find-at (by-address gtirb) address))) (defmethod on-address ((gtirb gtirb) start &optional (end start)) (mapcar {get-uuid _ gtirb} (ranged-find (by-address gtirb) start end))) (define-constant +module-isa-map+ '((#.proto:+isa-isa-undefined+ . :undefined) (#.proto:+isa-ia32+ . :ia32) (#.proto:+isa-ppc32+ . :ppc32) (#.proto:+isa-x64+ . :x64) (#.proto:+isa-arm+ . :arm) (#.proto:+isa-valid-but-unsupported+ . :valid-but-unsupported) (#.proto:+isa-ppc64+ . :ppc64) (#.proto:+isa-arm64+ . :arm64) (#.proto:+isa-mips32+ . :mips32) (#.proto:+isa-mips64+ . :mips64)) :test #'equal) (define-constant +module-file-format-map+ '((#.proto:+file-format-coff+ . :coff) (#.proto:+file-format-elf+ . :elf) (#.proto:+file-format-ida-pro-db32+ . :ida-pro-db32) (#.proto:+file-format-ida-pro-db64+ . :ida-pro-db64) (#.proto:+file-format-macho+ . :macho) (#.proto:+file-format-pe+ . :pe) (#.proto:+file-format-raw+ . :raw) (#.proto:+file-format-xcoff+ . :xcoff) (#.proto:+file-format-format-undefined+ . :format-undefined)) :test #'equal) (define-constant +module-byte-order-map+ `((#.proto:+byte-order-byte-order-undefined+ . :undefined) (#.proto:+byte-order-big-endian+ . :big-endian) (#.proto:+byte-order-little-endian+ . :little-endian)) :test #'equal) (define-proto-backed-class (module proto:module) () ((proxies :accessor proxies :type hash-table :initform (make-hash-table) :from-proto (lambda (proto &aux (table (make-hash-table))) (let ((proto-proxies (proto:proxies proto))) (dotimes (n (length proto-proxies) table) (let ((it (aref proto-proxies n))) (setf (gethash (uuid-to-integer (proto:uuid it)) table) (make-instance 'proxy-block :ir (ir self) :module self :proto it)))))) :to-proto [{map 'vector (lambda (uuid) (let ((it (make-instance 'proto:proxy-block))) (setf (proto:uuid it) (integer-to-uuid uuid)) it))} {mapcar #'car} #'hash-table-alist] :documentation "Hash-table of proxy-blocks keyed by UUID. Proxy-blocks in GTIRB are used to represent cross-module linkages. For example when code in a module calls to a function defined in an external library, the CFG for that IR instance may represent this call with a call edge to a proxy block representing the external called function.") (symbols :accessor symbols :type list :initform nil :from-proto [{map 'list {make-instance 'symbol :ir (ir self) :module self :proto}} #'proto:symbols] :to-proto {map 'vector #'update-proto} :documentation "Hash-table of symbols keyed by UUID.") (sections :accessor sections :type list :from-proto [{map 'list {make-instance 'section :ir (ir self) :module self :proto}} #'proto:sections] :to-proto {map 'vector #'update-proto} :documentation "List of the sections comprising this module.") (aux-data :accessor aux-data :type list :from-proto #'aux-data-from-proto :to-proto #'aux-data-to-proto :documentation #.aux-data-slot-definition)) ((name :type string :documentation "An optional human-readable name for this module.") (binary-path :type string :documentation "The path or filename for this module. E.g, the name of a dynamically loaded library or of the main executable.") (preferred-addr :type unsigned-byte-64 :documentation "Some systems specify a preferred address in memory. On those systems this field may be used to capture this address.") (rebase-delta :type unsigned-byte-64 :documentation "The difference between this module's and `preferred-addr' and the address at which it was actually loaded.") (isa :type enumeration :enumeration +module-isa-map+ :documentation "The instruction set architecture (ISA) of the code in this module.") (file-format :type enumeration :enumeration +module-file-format-map+ :documentation "The binary file format of the original file this module represents.") (byte-order :type enumeration :enumeration +module-byte-order-map+ :documentation "The byte-order of the bytes in this module.")) (:documentation "Module of a GTIRB IR instance.") (:parent gtirb)) (defmethod make-instance :around ((class (eql 'module)) &rest initargs &key &allow-other-keys) (let ((proto (or (getf initargs :proto) (let ((new-proto (make-instance 'proto:module))) (if-let ((name (getf initargs :name))) (setf (proto:name new-proto) (pb:string-field name)) (warn "Modules created without a name")) new-proto)))) (apply #'call-next-method class :proto proto initargs))) (defmethod print-object ((obj module) stream) (print-unreadable-object (obj stream :type t :identity t) (format stream "~a ~a ~s" (file-format obj) (isa obj) (name obj)))) (defgeneric get-aux-data-w-offsets (object) (:documentation "Collect all Aux-Data tables with offsets in their types.") (:method ((list list)) (remove-if-not [{find :offset} #'flatten #'aux-data-type #'cdr] list)) (:method ((self module)) (get-aux-data-w-offsets (aux-data self))) (:method ((self gtirb)) (apply #'append (get-aux-data-w-offsets (aux-data self)) (mapcar #'get-aux-data-w-offsets (modules self))))) (defmethod (setf aux-data) :after (new-value (self gtirb)) (declare (ignorable new-value)) (setf (aux-data-w-offsets self) (get-aux-data-w-offsets self))) (defmethod (setf aux-data) :after (new-value (self module)) (declare (ignorable new-value)) (setf (aux-data-w-offsets (ir self)) (get-aux-data-w-offsets (ir self)))) (define-constant +edge-label-type-map+ '((#.proto:+edge-type-type-branch+ . :branch) (#.proto:+edge-type-type-call+ . :call) (#.proto:+edge-type-type-fallthrough+ . :fallthrough) (#.proto:+edge-type-type-return+ . :return) (#.proto:+edge-type-type-syscall+ . :syscall) (#.proto:+edge-type-type-sysret+ . :sysret)) :test #'equal) (define-proto-backed-class (edge-label proto:edge-label) () () ((conditional :type boolean :documentation "This is true if this edge is due to a conditional instruction.") (direct :type boolean :documentation "Is this a direct (as opposed to indirect) control flow edge.") (edge-type :type enumeration :enumeration +edge-label-type-map+ :proto-field type :documentation "The type of an edge indicates the nature of the control flow along it. E.g., \"branch,\" \"call,\" \"fallthrough,\" and \"return\" are examples.")) (:documentation "Label on a CFG edge. This indicates the type of control flow along this edge.")) (defmethod print-object ((obj edge-label) stream) (print-unreadable-object (obj stream :type t :identity t) (format stream "~a ~:[unconditional~;conditional~] ~:[undirect~;direct~]" (edge-type obj) (conditional obj) (direct obj)))) (define-proto-backed-class (symbol proto:symbol) () () ((name :type string) (value :type unsigned-byte-64) (referent-uuid :type uuid) (at-end :type boolean)) (:documentation "Symbol with it's NAME and an optional VALUE or REFERENT.") (:parent module)) (defgeneric payload (symbol) (:documentation "Provide access to the referent or value of SYMBOL.") (:method ((symbol symbol)) (cond ((proto:has-value (proto symbol)) (value symbol)) ((proto:has-referent-uuid (proto symbol)) (get-uuid (referent-uuid symbol) symbol))))) (defmethod (setf payload) ((new proto-backed) (symbol symbol)) "Save GTIRB object NEW into the `referent-uuid' of SYMBOL." (proto:clear-value (proto symbol)) (setf (referent-uuid symbol) (uuid new))) (defmethod (setf payload) ((new integer) (symbol symbol)) "Save INTEGER value NEW into the `value' of SYMBOL." (proto:clear-referent-uuid (proto symbol)) (setf (value symbol) new)) (defmethod (setf payload) ((new t) (symbol symbol)) (error "Symbol payload ~S must be either a GTIRB element or an integer." new)) (defmethod print-object ((obj symbol) stream) (print-unreadable-object (obj stream :type t :identity t) (format stream "~a ~a~:[~;|~]" (name obj) (or (value obj) (referent-uuid obj)) (at-end obj)))) (define-constant +section-flags-map+ '((#.proto:+section-flag-section-undefined+ . :flag-undefined) (#.proto:+section-flag-readable+ . :readable) (#.proto:+section-flag-writable+ . :writable) (#.proto:+section-flag-executable+ . :executable) (#.proto:+section-flag-loaded+ . :loaded) (#.proto:+section-flag-initialized+ . :initialized) (#.proto:+section-flag-thread-local+ . :thread-local)) :test #'equal) (define-proto-backed-class (section proto:section) () ((byte-intervals :accessor byte-intervals :type list :from-proto [{map 'list {make-instance 'byte-interval :ir (ir self) :section self :proto}} #'proto:byte-intervals] :to-proto {map 'vector #'update-proto} :documentation "Byte-intervals holding all of the section's bytes.")) ((name :type string :documentation "Name of this section.") (flags :type enumeration :enumeration +section-flags-map+ :proto-field section-flags :documentation "Flags holding common properties of this section. These flags only hold those section properties which are relatively universal including read, write, execute permissions, whether the section is loaded into memory at run-time or not, whether the section is zero initialized, and whether the section is thread-local.")) (:documentation "Section in a GTIRB IR instance.") (:parent module)) (defmethod print-object ((obj section) stream) (print-unreadable-object (obj stream :type t :identity t) (format stream "~a ~a" (name obj) (length (byte-intervals obj))))) (defmethod address ((obj section)) (extremum (mapcar #'address (byte-intervals obj)) #'<)) (defmethod size ((it section)) (- (extremum (mapcar «+ #'address #'size» (byte-intervals it)) #'> :key #'car) (address it))) (defgeneric blocks (obj) (:documentation "List of gtirb-byte-block objects in this object. Primitive accessor for byte-interval.") (:method ((obj gtirb)) (mappend #'blocks (modules obj))) (:method ((obj module)) (mappend #'blocks (sections obj))) (:method ((obj section)) (mappend #'blocks (byte-intervals obj)))) (define-proto-backed-class (offset proto:offset) () () ((element-id :type uuid) (displacement :type unsigned-byte-64)) (:documentation "Offset into a GTIRB object.")) (define-constant +se-attribute-flag-map+ '( (#.proto:+se-attribute-flag-got+ . :got) (#.proto:+se-attribute-flag-gotpc+ . :gotpc) (#.proto:+se-attribute-flag-gotoff+ . :gotoff) (#.proto:+se-attribute-flag-gotrel+ . :gotrel) (#.proto:+se-attribute-flag-plt+ . :plt) (#.proto:+se-attribute-flag-pltoff+ . :pltoff) (#.proto:+se-attribute-flag-pcrel+ . :pcrel) (#.proto:+se-attribute-flag-secrel+ . :secrel) (#.proto:+se-attribute-flag-tls+ . :tls) (#.proto:+se-attribute-flag-tlsgd+ . :tlsgd) (#.proto:+se-attribute-flag-tlsld+ . :tlsld) (#.proto:+se-attribute-flag-tlsldm+ . :tlsldm) (#.proto:+se-attribute-flag-tlscall+ . :tlscall) (#.proto:+se-attribute-flag-tlsdesc+ . :tlsdesc) (#.proto:+se-attribute-flag-tprel+ . :tprel) (#.proto:+se-attribute-flag-tpoff+ . :tpoff) (#.proto:+se-attribute-flag-dtprel+ . :dtprel) (#.proto:+se-attribute-flag-dtpoff+ . :dtpoff) (#.proto:+se-attribute-flag-dtpmod+ . :dtpmod) (#.proto:+se-attribute-flag-ntpoff+ . :ntpoff) (#.proto:+se-attribute-flag-page+ . :page) (#.proto:+se-attribute-flag-pageoff+ . :pageoff) (#.proto:+se-attribute-flag-call+ . :call) (#.proto:+se-attribute-flag-lo+ . :lo) (#.proto:+se-attribute-flag-hi+ . :hi) (#.proto:+se-attribute-flag-higher+ . :higher) (#.proto:+se-attribute-flag-highest+ . :highest) (#.proto:+se-attribute-flag-gotntpoff+ . :gotntpoff) (#.proto:+se-attribute-flag-indntpoff+ . :indntpoff) (#.proto:+se-attribute-flag-g0+ . :g0) (#.proto:+se-attribute-flag-g1+ . :g1) (#.proto:+se-attribute-flag-g2+ . :g2) (#.proto:+se-attribute-flag-g3+ . :g3) (#.proto:+se-attribute-flag-upper16+ . :upper16) (#.proto:+se-attribute-flag-lower16+ . :lower16) (#.proto:+se-attribute-flag-lo12+ . :lo12) (#.proto:+se-attribute-flag-lo15+ . :lo15) (#.proto:+se-attribute-flag-lo14+ . :lo14) (#.proto:+se-attribute-flag-hi12+ . :hi12) (#.proto:+se-attribute-flag-hi21+ . :hi21) (#.proto:+se-attribute-flag-s+ . :s) (#.proto:+se-attribute-flag-pg+ . :pg) (#.proto:+se-attribute-flag-nc+ . :nc) (#.proto:+se-attribute-flag-abs+ . :abs) (#.proto:+se-attribute-flag-prel+ . :prel) (#.proto:+se-attribute-flag-prel31+ . :prel31) (#.proto:+se-attribute-flag-target1+ . :target1) (#.proto:+se-attribute-flag-target2+ . :target2) (#.proto:+se-attribute-flag-sbrel+ . :sbrel) (#.proto:+se-attribute-flag-tlsldo+ . :tlsldo) (#.proto:+se-attribute-flag-hi16+ . :hi16) (#.proto:+se-attribute-flag-lo16+ . :lo16) (#.proto:+se-attribute-flag-gprel+ . :gprel) (#.proto:+se-attribute-flag-disp+ . :disp) (#.proto:+se-attribute-flag-ofst+ . :ofst) (#.proto:+se-attribute-flag-h+ . :h) (#.proto:+se-attribute-flag-l+ . :l) (#.proto:+se-attribute-flag-ha+ . :ha) (#.proto:+se-attribute-flag-high+ . :high) (#.proto:+se-attribute-flag-higha+ . :higha) (#.proto:+se-attribute-flag-highera+ . :highera) (#.proto:+se-attribute-flag-highesta+ . :highesta) (#.proto:+se-attribute-flag-tocbase+ . :tocbase) (#.proto:+se-attribute-flag-toc+ . :toc) (#.proto:+se-attribute-flag-notoc+ . :notoc)) :test #'equal :documentation "See doc/general/SymbolicExpression.md for more details.") (define-proto-backed-class (byte-interval proto:byte-interval) () ((blocks :initarg :blocks :accessor blocks :type list :from-proto [{map 'list (lambda (proto-block) (let ((it (cond ((not (emptyp (proto:uuid (proto:data proto-block)))) (make-instance 'data-block :ir (ir self) :byte-interval self :offset (proto:offset proto-block) :proto (proto:data proto-block))) ((not (emptyp (proto:uuid (proto:code proto-block)))) (make-instance 'code-block :ir (ir self) :byte-interval self :offset (proto:offset proto-block) :proto (proto:code proto-block)))))) #+debug (when (emptyp (proto:uuid (proto it))) (warn "BAD BLOCK ~a with empty uuid from ~a.~%~A~%" it (name (section self)) proto-block)) it))} #'proto:blocks] :to-proto {map 'vector (lambda (gtirb-block) (let ((it (make-instance 'proto:block))) (setf (proto:offset it) (offset gtirb-block)) (etypecase gtirb-block (code-block (setf (proto:code it) (update-proto gtirb-block))) (data-block (setf (proto:data it) (update-proto gtirb-block)))) it))} :documentation "Blocks in this byte-interval. This list could include `code-block' or `data-block' elements (which both subclass the `gtirb-byte-block' class) but not `proxy-block' elements as proxy blocks do not hold bytes.") (symbolic-expressions :accessor symbolic-expressions :type hash-table :initarg :symbolic-expressions :from-proto (lambda (proto &aux (table (make-hash-table))) (flet ((process-symbols (&rest symbols) (mappend (lambda (uuid) (if-let ((sym (get-uuid (uuid-to-integer uuid) (ir self)))) (list sym) (unless (emptyp uuid) (warn "Symbol UUID ~S not found" uuid)))) symbols))) (dotimes (n (length (proto:symbolic-expressions proto)) table) (let* ((proto (aref (proto:symbolic-expressions proto) n)) (offset (proto:key proto)) (attribute-flags (map 'list [#'cdr {assoc _ +se-attribute-flag-map+}] (proto:attribute-flags (proto:value proto)))) (symbolic-expression (proto:value proto))) (setf (gethash offset table) (cond ((proto:has-addr-const symbolic-expression) (make-instance 'sym-addr-const :ir (ir self) :attribute-flags attribute-flags :symbols (process-symbols (proto:symbol-uuid (proto:addr-const symbolic-expression))) :proto (proto:addr-const symbolic-expression))) ((proto:has-addr-addr symbolic-expression) (make-instance 'sym-addr-addr :ir (ir self) :attribute-flags attribute-flags :symbols (process-symbols (proto:symbol1-uuid (proto:addr-addr symbolic-expression)) (proto:symbol2-uuid (proto:addr-addr symbolic-expression))) :proto (proto:addr-addr symbolic-expression))) (t (assert "Symbolic expression of unknown kind.")))))))) :to-proto [{map 'vector (lambda (pair) (destructuring-bind (offset . symbolic-expression) pair (flet ((force-attribute-flags-array (array) (declare (type (simple-array) array)) (make-array (length array) :element-type `(mod ,(length +se-attribute-flag-map+)) :initial-contents array))) (let ((it (make-instance 'proto:byte-interval-symbolic-expressions-entry))) (setf (proto:key it) offset (proto:value it) (let ((it (make-instance 'proto:symbolic-expression))) (when (attribute-flags symbolic-expression) (setf (proto:attribute-flags it) (force-attribute-flags-array (map 'vector [#'car {rassoc _ +se-attribute-flag-map+}] (attribute-flags symbolic-expression))))) (etypecase symbolic-expression (sym-addr-const (setf (proto:addr-const it) (update-proto symbolic-expression))) (sym-addr-addr (setf (proto:addr-addr it) (update-proto symbolic-expression)))) it)) it))))} #'hash-table-alist] :documentation "Hash of symbolic-expressions keyed by offset.")) ((addressp :type boolean :proto-field has-address :documentation "Does this byte-interval have an address.") (address :type unsigned-byte-64 :documentation "Optionally specify the address in memory at which this ~ byte-interval should start. Byte-intervals without address could exist anywhere in memory.") (size :type unsigned-byte-64 :documentation "The size of this byte-interval. It is possible for the size of a byte-interval to be larger than the number of bytes in the byte interval's `contents' if portions of the byte-interval are not represented statically but are zero-initialized at runtime.") (contents :type bytes :documentation "A vector holding the actual bytes of this byte interval.")) (:documentation "Byte-interval in a GTIRB instance.") (:parent section) (:address-range (when (addressp self) (list (address self) (+ (address self) (size self)))))) (defmethod (setf size) :before (new (obj byte-interval)) (restart-case (when (> (length (contents obj)) new) (error (make-condition 'ir :message "size smaller than contents" :object obj))) (truncate-contents () :report "Truncate the contents of the byte-interval to the new size." (setf (contents obj) (subseq (contents obj) 0 new))) (ignore () :report "Ignore and leave the byte-interval in an inconsistent state."))) (defmethod print-object ((obj byte-interval) stream) (print-unreadable-object (obj stream :type t :identity t) (format stream "~a ~a" (if (addressp obj) (address obj) "?") (size obj)))) (defmethod address ((obj byte-interval)) (when (addressp obj) (proto:address (proto obj)))) (defclass symbolic-expression () ((symbols :accessor symbols :initarg :symbols :initform nil :type list :documentation "Symbol(s) appearing in this symbolic expression.") (attribute-flags :accessor attribute-flags :initarg :attribute-flags :initform nil :type list :documentation "Attributes holding the relocation type."))) (defmethod print-object ((obj symbolic-expression) stream) (print-unreadable-object (obj stream :type t :identity t) (format stream "~a ~{~a~^, ~}" (offset obj) (symbols obj)))) ;;; TODO: If we get symbolic expressions for these, then add the ;;; following to each `symbolic-expression' class: ;;; ;;; (:parent byte-interval) ;;; (:address-range (when-let ((range (addressp (byte-interval self)))) ;;; (+ (offset self) (first range)))) ;;; ;;; and then go back up and add ":byte-interval self" to their ;;; `make-instance' calls in byte-interval. (define-proto-backed-class (sym-addr-const proto:sym-addr-const) (symbolic-expression) () ((offset :type unsigned-byte-64)) (:address-range (when (addressp (byte-interval self)) (let ((address (+ (address (byte-interval self)) (offset self)))) (list address address))))) (define-proto-backed-class (sym-addr-addr proto:sym-addr-addr) (symbolic-expression) () ((offset :type unsigned-byte-64) (scale :type unsigned-byte-64)) (:address-range (when (addressp (byte-interval self)) (let ((address (+ (address (byte-interval self)) (offset self)))) (list address address))))) (defmethod update-proto :before ((sym sym-addr-const)) (setf (proto:symbol-uuid (proto sym)) (integer-to-uuid (uuid (first (symbols sym)))))) (defmethod update-proto :before ((sym sym-addr-addr)) (setf (proto:symbol1-uuid (proto sym)) (integer-to-uuid (uuid (first (symbols sym)))) (proto:symbol2-uuid (proto sym)) (integer-to-uuid (uuid (second (symbols sym)))))) (defmethod update-proto :before ((sym sym-addr-const)) (setf (proto:symbol-uuid (proto sym)) (integer-to-uuid (uuid (first (symbols sym)))))) (defmethod update-proto :before ((sym sym-addr-addr)) (setf (proto:symbol1-uuid (proto sym)) (integer-to-uuid (uuid (first (symbols sym)))) (proto:symbol2-uuid (proto sym)) (integer-to-uuid (uuid (second (symbols sym)))))) (defclass gtirb-block () ()) (defclass gtirb-byte-block (gtirb-block) () (:documentation "Super-class of the `code-block' and `data-block' classes. This class abstracts over all GTIRB blocks which are able to hold bytes.")) (defmethod symbolic-expressions ((bb gtirb-byte-block)) (nest (alist-hash-table) (remove-if-not [«and {< (offset bb)} {>= (+ (offset bb) (size bb))}» #'car]) (hash-table-alist) (symbolic-expressions) (byte-interval bb))) (defmethod address ((obj gtirb-byte-block)) (when-let ((base-address (address (byte-interval obj)))) (+ base-address (offset obj)))) (defgeneric bytes (object &optional start end) (:documentation "Return the bytes held by OBJECT.") (:method ((obj byte-interval) &optional (start 0) end) (if end (subseq (contents obj) start end) (subseq (contents obj) start))) (:method ((obj gtirb-byte-block) &optional (start 0) (end (size obj))) #+debug (format t "[~S] ~S:[~S:~S]<-[~S:~S]~%" (proto:uuid (proto obj)) (name (section (byte-interval obj))) (or (and (addressp (byte-interval obj)) (address (byte-interval obj))) "?") (size (byte-interval obj)) (offset obj) (size obj)) (let* ((start (+ (offset obj) start)) (end (+ (offset obj) end))) (assert (<= end (size (byte-interval obj))) (obj) "Block's end ~d exceeds size of containing byte-interval ~d." end (size (byte-interval obj))) (let ((real-end (length (contents (byte-interval obj))))) (cond ((<= end real-end) ; Allocated bytes. (subseq (contents (byte-interval obj)) start end)) ((<= start real-end) ; Both allocated and un-allocated bytes. (concatenate 'vector (subseq (contents (byte-interval obj)) start) (make-array (- end real-end) :initial-element 0))) (t ; Un-allocated bytes, zero-fill. (make-array (size obj) :initial-element 0))))))) (defun shift-subseq (sequence start end) "Return a copy of SEQUENCE bounded by START and END." (subseq sequence start end)) (defparameter *preserve-symbolic-expressions* nil "When true, (setf bytes) preserves symbolic expressions intersecting the assigned part of the object.") (define-setf-expander shift-subseq (sequence start end &environment env) "Update the subseq of SEQUENCE bounded by START and END." (multiple-value-bind (dummies vals newval setter getter) (get-setf-expansion sequence env) (declare (ignorable newval setter)) (let ((store (gensym))) (values dummies ; Temporary variables vals ; Value forms. (list store) ; Store variables. `(progn (cond ((zerop ,start) (setf ,getter (concatenate 'vector ,store (subseq ,getter ,end)))) ((= (length ,store) (- ,end ,start)) (setf (subseq ,getter ,start ,end) ,store)) ((>= ,end (length ,getter)) (setf ,getter (concatenate 'vector (subseq ,getter 0 ,start) ,store))) (t (setf ,getter (concatenate 'vector (subseq ,getter 0 ,start) ,store (subseq ,getter ,end))))) ,store) ; Storing form. `(shift-subseq ,getter))))) ; Accessing form. (define-setf-expander bytes (sequence &optional (start 0) (end nil end-p) &environment env) (multiple-value-bind (dummies vals newval setter getter) (get-setf-expansion sequence env) (declare (ignorable newval setter)) (let ((store (gensym)) (end (if end-p end `(length (bytes ,getter))))) (values dummies ; Temporary variables vals ; Value forms. (list store) ; Store variables. `(progn (etypecase ,getter (gtirb::byte-interval (setf (shift-subseq (contents ,getter) ,start ,end) ,store)) (gtirb::gtirb-byte-block (with-slots (offset) ,getter (let ((original-offset offset)) (setf (shift-subseq (contents (byte-interval ,getter)) (+ offset ,start) (+ offset ,end)) ,store) (setf-bytes-after ,store (byte-interval ,getter) (+ offset ,start) (+ offset ,end)) ;; Ensure the offset for THIS block isn't pushed back change. (setf offset original-offset))))) (setf-bytes-after ,store ,getter ,start ,end) ,store) ; Storing form. `(bytes ,getter))))) ; Accessing form. (defvar *update-aux-data-offsets* nil "Are offsets in AuxData tables updated as bytes are modified.") (defgeneric setf-bytes-after (new object &optional start end) (:documentation "Update the offsets into BYTE-INTERVAL due to saving NEW into START END.") (:method (new (byte-interval byte-interval) &optional (start 0) (end (size byte-interval))) (setf (size byte-interval) (+ start (length new) (- (size byte-interval) end))) (let ((difference (- (length new) (- end start)))) ;; Symbolic expressions. (nest (setf (symbolic-expressions byte-interval)) (alist-hash-table) (mappend (lambda (pair) (destructuring-bind (offset . sym-expr) pair (cond ((< offset start) (list (cons offset sym-expr))) ((>= offset end) (list (cons (+ offset difference) sym-expr))) ;; Clear symbolic expressions in the modified range, ;; unless *preserve-symbolic-expressions* is true. (t (if *preserve-symbolic-expressions* (list pair) nil)))))) (hash-table-alist (symbolic-expressions byte-interval))) (when *update-aux-data-offsets* ;; Update offsets in AuxData tables. (labels ((update-offset (data type) (cond ((and (listp type) (eql :mapping (car type))) (nest (alist-hash-table) (mapcar «cons [{update-offset _ (second type)} #'car] [{update-offset _ (third type)} #'cdr]») (hash-table-alist data))) ((and (listp type) (eql :sequence (car type))) (mapcar {update-offset _ (cdr type)} data)) ((eql :offset type) ;; NOTE: Since we are already adjusting all ;; other code blocks and offsets are ;; currently stored by code block we only ;; need to update offsets in the current ;; code block. ;; NOTE: Offsets are relative to the base of the ;; code block but START is relative to the ;; base of the byte-interval. ;; NOTE: Only update offsets in the current ;; block. (let* ((obj (get-uuid (first data) byte-interval)) (obj-start (if (typep obj 'gtirb-byte-block) (offset obj) most-positive-fixnum)) (obj-end (+ obj-start (size obj))) (off (second data))) (when (and ;; Changes bytes intersect this block (or (and (>= obj-start start) (<= obj-start end)) (and (>= obj-end start) (<= obj-end end))) ;; This offset is after the start ;; of the changed bytes (>= (+ (offset obj) off) start)) (incf (second data) difference))) data) (t data)))) (mapcar (lambda (pair) (let ((table (cdr pair))) (setf (aux-data-data table) (update-offset (aux-data-data table) (aux-data-type table))))) (aux-data-w-offsets (ir byte-interval))))) ;; Byte-Blocks. (mapc (lambda (bb) (with-slots (offset) bb (when (>= offset end) (incf offset difference)))) (blocks byte-interval))) new) (:method (new (bb gtirb-byte-block) &optional (start 0) (end (size bb))) ;; Update the size of the byte-block (setf (size bb) (+ start (length new) (- (size bb) end))) new)) (define-proto-backed-class (code-block proto:code-block) (gtirb-byte-block) ((offset :initarg :offset :accessor offset :type number :documentation "Offset into this block's bytes in the block's byte-interval.")) ((size :type unsigned-byte-64 :documentation "The length of the bytes held by this code block.") (decode-mode :type unsigned-byte-64 :documentation "Only present on architecture with multiple decode-modes.")) (:documentation "Code-block in a GTIRB IR instance.") (:parent byte-interval) (:address-range (when-let ((range (address-range (byte-interval self)))) (list (+ (offset self) (first range)) (+ (offset self) (size self) (first range)))))) (defmethod print-object ((obj code-block) stream) (print-unreadable-object (obj stream :type t :identity t) (format stream "~a ~a" (size obj) (decode-mode obj)))) (defgeneric entry-point (module) (:documentation "The code-block which is the entry point of MODULE.") (:method ((obj module)) (get-uuid (uuid-to-integer (proto:entry-point (proto obj))) obj))) (defmethod (setf entry-point) ((new code-block) (obj module)) (proto:clear-entry-point (proto obj)) (setf (proto:entry-point (proto obj)) (integer-to-uuid (uuid new)))) (define-proto-backed-class (data-block proto:data-block) (gtirb-byte-block) ((offset :initarg :offset :accessor offset :type number :documentation "Offset into this block's bytes in the block's byte-interval.")) ((size :type unsigned-byte-64 :documentation "The length of the bytes held by this data block.")) (:documentation "Data-block in a GTIRB IR instance.") (:parent byte-interval) (:address-range (when-let ((range (address-range (byte-interval self)))) (list (+ (offset self) (first range)) (+ (offset self) (size self) (first range)))))) (defmethod print-object ((obj data-block) stream) (print-unreadable-object (obj stream :type t :identity t) (format stream "~a" (size obj)))) (define-proto-backed-class (proxy-block proto:proxy-block) (gtirb-block) () () (:documentation "Proxy-block in a GTIRB IR instance.") (:parent module)) (defmethod print-object ((obj proxy-block) stream) (print-unreadable-object (obj stream :type t :identity t))) ;;;; AuxData type and data handling. (define-proto-backed-class (aux-data proto:aux-data) () () ()) (defmethod print-object ((obj aux-data) stream) (print-unreadable-object (obj stream :type t :identity t) (format stream "~a" (aux-data-type obj)))) (defun aux-data-from-proto (proto) (let ((p-aux-data (proto:aux-data proto)) (aux-data '())) (dotimes (n (length p-aux-data)) (push (cons (pb:string-value (proto:key (aref p-aux-data n))) (make-instance 'aux-data :proto (proto:value (aref p-aux-data n)))) aux-data)) aux-data)) (defun aux-data-to-proto (aux-data) (map 'vector (lambda (pair) (destructuring-bind (name . aux-data) pair (let ((entry (make-instance 'proto:module-aux-data-entry))) (setf (proto:key entry) (pb:string-field name) (proto:value entry) (proto aux-data)) entry))) aux-data)) (defmacro start-case (string &body body) `(progn ;; (declare (type string ,string)) (assert (stringp ,string) (,string) "Argument ~s is not a string." ,string) (cond ,@(mapcar (lambda (form) (destructuring-bind (prefix . body) form (if (stringp prefix) `((eql (search ,prefix ,string) 0) (let ((,string (subseq ,string ,(length prefix)))) ,@body)) (cons prefix body)))) body)))) (defparameter +integer-typename-regex+ (create-scanner "(U?INT)([0-9]+)-T") "Regex for deconstructing an integer typename.") (defun matching (open-char close-char string) "Return the first balanced offset of CLOSE-CHAR in STRING. STRING is assumed to already have one extant OPEN-CHAR which needs to be matched. New instances of OPEN-CHAR must be closed by balanced CLOSE-CHARs before the CLOSE-CHAR matching the implicit extant OPEN-CHAR." (let ((offset 1)) (dotimes (n (length string)) (cond ((eql (aref string n) open-char) (incf offset)) ((eql (aref string n) close-char) (decf offset))) (when (zerop offset) (return-from matching n)))) (error "Can't close (~a ~a) in ~s." open-char close-char string)) (defun aux-data-type-read (type-string) (when (and type-string (not (emptyp type-string))) (start-case type-string ("mapping<" (let ((close (matching #\< #\> type-string))) (cons (cons :mapping (aux-data-type-read (subseq type-string 0 close))) (aux-data-type-read (subseq type-string close))))) ("set<" (let ((close (matching #\< #\> type-string))) (cons (cons :set (aux-data-type-read (subseq type-string 0 close))) (aux-data-type-read (subseq type-string close))))) ("sequence<" (let ((close (matching #\< #\> type-string))) (cons (cons :sequence (aux-data-type-read (subseq type-string 0 close))) (aux-data-type-read (subseq type-string close))))) ("tuple<" (let ((close (matching #\< #\> type-string))) (cons (cons :tuple (aux-data-type-read (subseq type-string 0 close))) (aux-data-type-read (subseq type-string close))))) ("variant<" (let ((close (matching #\< #\> type-string))) (cons (cons :variant (aux-data-type-read (subseq type-string 0 close))) (aux-data-type-read (subseq type-string close))))) ("," (aux-data-type-read type-string)) (">" (aux-data-type-read type-string)) ("UUID" (cons :uuid (aux-data-type-read type-string))) ("Addr" (cons :addr (aux-data-type-read type-string))) ("Offset" (cons :offset (aux-data-type-read type-string))) ("string" (cons :string (aux-data-type-read type-string))) ("bool" (cons :bool (aux-data-type-read type-string))) ("uint8_t" (cons :uint8-t (aux-data-type-read type-string))) ("uint16_t" (cons :uint16-t (aux-data-type-read type-string))) ("uint32_t" (cons :uint32-t (aux-data-type-read type-string))) ("uint64_t" (cons :uint64-t (aux-data-type-read type-string))) ("int8_t" (cons :int8-t (aux-data-type-read type-string))) ("int16_t" (cons :int16-t (aux-data-type-read type-string))) ("int32_t" (cons :int32-t (aux-data-type-read type-string))) ("int64_t" (cons :int64-t (aux-data-type-read type-string))) ("float" (cons :float (aux-data-type-read type-string))) ("double" (cons :double (aux-data-type-read type-string))) (t (error "Junk in type string ~a" type-string))))) (defgeneric aux-data-type (aux-data) (:documentation "Access the structured type of AUX-DATA.") (:method ((obj aux-data)) (first (aux-data-type-read (pb:string-value (proto:type-name (proto obj))))))) (defun aux-data-type-print (stream type &optional colon-modifier-supplied-p at-sign-modifier-supplied-p (repetitions 1)) (declare (type (or null (eql t) stream string) stream)) (declare (ignorable colon-modifier-supplied-p at-sign-modifier-supplied-p repetitions)) (when type (if (listp type) (case (first type) (:mapping (format stream "mapping<~/gtirb:aux-data-type-print/,~/gtirb:aux-data-type-print/>" (second type) (third type))) (:set (format stream "set<~/gtirb:aux-data-type-print/>" (second type))) (:sequence (format stream "sequence<~/gtirb:aux-data-type-print/>" (second type))) (:tuple (format stream "tuple<~{~/gtirb:aux-data-type-print/~^,~}>" (cdr type))) (:variant (format stream "variant<~{~/gtirb:aux-data-type-print/~^,~}>" (cdr type)))) (format stream (ecase type (:uuid "UUID") (:addr "Addr") (:offset "Offset") (:string "string") (:bool "bool") (:uint8-t "uint8_t") (:uint16-t "uint16_t") (:uint32-t "uint32_t") (:uint64-t "uint64_t") (:int8-t "int8_t") (:int16-t "int16_t") (:int32-t "int32_t") (:int64-t "int64_t") (:float "float") (:double "double")))))) (defmethod (setf aux-data-type) (new (obj aux-data)) (setf (proto:type-name (proto obj)) (pb:string-field (format nil "~/gtirb::aux-data-type-print/" new)))) (defgeneric aux-data-data (aux-data) (:documentation "Access the structured representation of AUX-DATAs data.") (:method ((obj aux-data)) (aux-data-decode (aux-data-type obj) (proto:data (proto obj))))) (defmethod (setf aux-data-data) (new (obj aux-data)) (setf (proto:data (proto obj)) (force-byte-array (aux-data-encode (aux-data-type obj) new)))) (defun parse-num-bytes (num-bits-string) (/ (parse-integer num-bits-string) 8)) (declaim (special *decode-data*)) (defun decode (type) (labels ((advance (n) (setf *decode-data* (subseq *decode-data* n))) (decode-int (type) (register-groups-bind (type-signage (#'parse-num-bytes num-bytes)) (+integer-typename-regex+ (symbol-name type)) (prog1 (if (string= type-signage "UINT") (octets->uint (subseq *decode-data* 0 num-bytes) num-bytes) (octets->int (subseq *decode-data* 0 num-bytes) num-bytes)) (advance num-bytes))))) (declare (inline advance)) (match type ((or :uint8-t :int8-t :uint16-t :int16-t :uint32-t :int32-t :uint64-t :int64-t) (decode-int type)) (:float (decode-float32 (decode-int :uint32-t))) (:double (decode-float64 (decode-int :uint64-t))) (:addr (prog1 (octets->uint (subseq *decode-data* 0 8) 8) (advance 8))) (:bool (prog1 (not (zerop (octets->uint (subseq *decode-data* 0 1) 1))) (advance 1))) (:uuid (prog1 (uuid-to-integer (subseq *decode-data* 0 16)) (advance 16))) (:offset (handler-bind ((error (lambda (e) (declare (ignorable e)) (let* ((offset (make-instance 'proto:offset)) (size (pb:octet-size offset))) (pb:merge-from-array offset *decode-data* 0 size) (prog1 offset (advance size)))))) (list (decode :uuid) (decode :uint64-t)))) (:string (let ((size (decode :uint64-t))) (prog1 (utf-8-bytes-to-string (subseq *decode-data* 0 size)) (advance size)))) ((list :mapping key-t value-t) (let ((result (make-hash-table :test #'equal))) (dotimes (n (decode :uint64-t) result) (declare (ignorable n)) (let* ((key (decode key-t)) (value (decode value-t))) (setf (gethash key result) value))))) ((list (or :sequence :set) type) (let (result) (reverse (dotimes (n (decode :uint64-t) result) (declare (ignorable n)) (push (decode type) result))))) ((list* :tuple types) (mapcar #'decode types)) ((list* :variant types) (let ((index (decode :uint64-t))) (cons index (decode (nth index types)))))))) (defun aux-data-decode (type data) (let ((*decode-data* data)) (decode type))) (defun encode (type data) (labels ((extend (it) (push it *decode-data*)) (encode-int (type data) (register-groups-bind (_ (#'parse-num-bytes num-bytes)) (+integer-typename-regex+ (symbol-name type)) (declare (ignorable _)) (extend (int->octets data num-bytes))))) (declare (inline extend)) (match type ((or :uint8-t :int8-t :uint16-t :int16-t :uint32-t :int32-t :uint64-t :int64-t) (encode-int type data)) (:float (encode-int :uint32-t (encode-float32 data))) (:double (encode-int :uint64-t (encode-float64 data))) (:addr (extend (int->octets data 8))) (:bool (extend (int->octets (if data 1 0) 1))) (:uuid (extend (integer-to-uuid data))) (:offset (etypecase data (proto:offset (let* ((size (pb:octet-size data)) (buffer (make-array size :element-type '(unsigned-byte 8)))) (pb:serialize data buffer 0 size) (extend buffer))) (list (progn (encode :uuid (first data)) (encode :uint64-t (second data)))))) (:string (let ((string-bytes (string-to-utf-8-bytes data))) (encode :uint64-t (length string-bytes)) (extend string-bytes))) ((list :mapping key-t value-t) (encode :uint64-t (hash-table-count data)) (maphash (lambda (key value) (encode key-t key) (encode value-t value)) data)) ((list (or :sequence :set) type) (let ((size (length data))) (encode :uint64-t size) (dotimes (n size) (encode type (elt data n))))) ((list* :tuple types) (mapc (lambda (type datum) (encode type datum)) types data)) ((list* :variant types) (encode :uint64-t (car data)) (encode (nth (car data) types) (cdr data)))))) (defun aux-data-encode (type data) (let ((*decode-data* nil)) (encode type data) (reduce {concatenate 'vector} (reverse *decode-data*)))) ================================================ FILE: cl/package.lisp ================================================ (defpackage :gtirb (use-package :common-lisp)) ================================================ FILE: cl/ranged.lisp ================================================ (defpackage :gtirb/ranged (:use :common-lisp) (:import-from :interval) (:export :make-ranged :ranged-insert :ranged-delete :ranged-find :ranged-find-at)) (in-package :gtirb/ranged) #-debug (declaim (optimize (speed 3) (safety 0) (debug 0))) #+debug (declaim (optimize (speed 0) (safety 3) (debug 3))) (defun make-ranged () (interval:make-tree)) (defstruct (uuid-interval (:include interval:interval)) (uuid 0 :type integer)) (defun uuid-interval= (i1 i2) (= (uuid-interval-uuid i1) (uuid-interval-uuid i2))) (defun ranged-insert (tree uuid start end) (interval:insert tree (make-uuid-interval :uuid uuid :start start :end end))) (defun ranged-delete (tree uuid start end) (interval:delete tree (make-uuid-interval :uuid uuid :start start :end end))) (defun ranged-find-at (tree address) (mapcar #'uuid-interval-uuid (remove-if-not (lambda (i) (= address (interval:interval-start i))) (interval:find-all tree address)))) (defun ranged-find (tree start &optional (end start)) (mapcar #'uuid-interval-uuid (interval:find-all tree (cons start end)))) ================================================ FILE: cl/test.lisp ================================================ (defpackage :gtirb/test (:use :common-lisp :alexandria :flexi-streams :stefil :gtirb :gtirb/dot :gtirb/utility :gtirb/ranged :gtirb/version :graph :named-readtables :curry-compose-reader-macros) (:import-from :trivial-package-local-nicknames :add-package-local-nickname) (:import-from :gtirb.proto) (:import-from :gtirb/utility :check-magic-header :write-magic-header) (:import-from :md5 :md5sum-file :md5sum-sequence) (:import-from :uiop :nest :run-program :with-temporary-file :quit) (:import-from :asdf/system :system-relative-pathname) (:shadowing-import-from :gtirb :symbol) (:export :test :batch-test)) (in-package :gtirb/test) (in-readtable :curry-compose-reader-macros) (eval-when (:compile-toplevel :load-toplevel :execute) (add-package-local-nickname :proto :gtirb.proto)) (defvar *proto-path* nil "Path to protobuf.") (defun batch-test (&optional args) "Run tests in 'batch' mode printing results to STDERR then quit. The ERRNO used when exiting lisp indicates success or failure." (declare (ignorable args)) (let* ((stefil::*test-progress-print-right-margin* (expt 2 20)) (failures (coerce (stefil::failure-descriptions-of (without-debugging (test))) 'list))) (if failures (format *error-output* "FAILURES~%~{ ~a~~%~}" (mapc [#'stefil::name-of #'stefil::test-of #'car #'stefil::test-context-backtrace-of] failures)) (format *error-output* "SUCCESS~%")) (quit (if failures 2 0)))) (defvar *gtirb-dir* (system-relative-pathname "gtirb" "../")) ;;;; Fixtures. (defixture hello (:setup (progn #+live-w-ddisasm (with-temporary-file (:pathname bin-path) (setf *proto-path* (with-temporary-file (:pathname p :keep t) p)) (run-program (format nil "echo 'main(){puts(\"hello world\");}'~ |gcc -x c - -o ~a" bin-path) :force-shell t) (run-program (format nil "ddisasm --ir ~a ~a" *proto-path* bin-path))) #-live-w-ddisasm (setf *proto-path* (merge-pathnames "python/tests/hello.gtirb" *gtirb-dir*)))) (:teardown (progn #+live-w-ddisasm (delete-file *proto-path*) (setf *proto-path* nil)))) ;;;; Main test suite. (defsuite test) (in-suite test) (deftest we-have-versions () (is (stringp gtirb-version)) (is (integerp protobuf-version))) (deftest simple-read () (with-fixture hello (is (eql 'proto:ir (class-name (class-of (read-proto 'proto:ir *proto-path*))))))) (deftest simple-write () (with-fixture hello (with-temporary-file (:pathname path) (gtirb::write-proto (read-proto 'proto:ir *proto-path*) path) (is (probe-file path))))) (deftest idempotent-read-write () (with-fixture hello (with-temporary-file (:pathname path) (gtirb::write-proto (read-proto 'proto:ir *proto-path*) path) ;; Protobuf provides multiple options for serializing repeated ;; enums, so this check can fail even with valid serialization. #+inhibited (is (equalp (md5sum-file *proto-path*) (md5sum-file path)))))) (deftest read-gtirb-from-streams-and-files () (with-fixture hello (is (typep (read-gtirb *proto-path*) 'gtirb)) (is (typep (read-gtirb (namestring *proto-path*)) 'gtirb)) (is (typep (with-open-file (input *proto-path*) (read-gtirb input)) 'gtirb)))) (deftest idempotent-read-write-w-class () (nest (with-fixture hello) (with-temporary-file (:pathname path)) (let ((hello1 (read-gtirb *proto-path*))) (write-gtirb hello1 path) (is (is-equal-p hello1 (read-gtirb path)))))) (deftest idempotent-aux-data-type () (with-fixture hello (let ((it (read-gtirb *proto-path*))) (is (tree-equal (mapcar [#'pb:string-value #'proto:type-name #'gtirb::proto #'cdr] (aux-data (first (modules it)))) (mapcar [{format nil "~/gtirb::aux-data-type-print/"} #'aux-data-type #'cdr] (aux-data (first (modules it)))) :test #'string=))))) (deftest idempotent-aux-data-decode-encode () (let ((type1 '(:tuple :uuid :uint64-t :int64-t :uint64-t)) (type2 '(:sequence :uuid)) (type3 '(:sequence (:variant :uuid :string))) (data '(1 2 3 4)) (data2 '((0 . 1) (1 . "foo") (0 . 3) (1 . "bar")))) (is (equalp (gtirb::aux-data-decode type1 (gtirb::aux-data-encode type1 data)) data)) (is (equalp (gtirb::aux-data-decode type2 (gtirb::aux-data-encode type2 data)) data)) (is (equalp (gtirb::aux-data-decode type3 (gtirb::aux-data-encode type3 data2)) data2))) (with-fixture hello (let ((hello (read-gtirb *proto-path*))) (mapc (lambda (pair) (destructuring-bind (name . aux-data) pair (let* ((orig (proto:data (gtirb::proto aux-data))) (type (aux-data-type aux-data)) (new (gtirb::aux-data-encode type (aux-data-data aux-data)))) (is (equalp (gtirb::aux-data-decode type new) (gtirb::aux-data-decode type orig)) "~s with type ~a encodes/decodes is not idempotent~%~s" name (aux-data-type aux-data) (list (gtirb::aux-data-decode type orig) (gtirb::aux-data-decode type new)))))) (aux-data (first (modules hello))))))) (deftest test-check-magic-header () (is (signals gtirb-magic-error (check-magic-header #()))) (is (signals gtirb-magic-error (check-magic-header #(0 0 0 0 0 0 0 0)))) (is (signals gtirb-magic-error (check-magic-header #(71 84 73 82 66 0 0 0)))) (is (null (check-magic-header (vector 71 84 73 82 66 0 0 protobuf-version))))) (deftest test-write-magic-header () (with-output-to-sequence (out) (write-magic-header out) (is (equalp (get-output-stream-sequence out) (vector 71 84 73 82 66 0 0 protobuf-version))))) (deftest update-proto-to-disk-and-back () (nest (with-fixture hello) (with-temporary-file (:pathname path)) (let ((test-string "this is a test") (hello (read-gtirb *proto-path*)) (aux (make-instance 'aux-data))) (setf (aux-data-type aux) :string) (setf (aux-data-data aux) test-string) (push (cons "test" aux) (aux-data (first (modules hello)))) (write-gtirb hello path) (let* ((next (read-gtirb path)) (proto (gtirb::proto (first (modules next))))) ;; Test for non-empty protobuf elements. (is (not (zerop (length (proto:byte-intervals (aref (proto:sections proto) 0)))))) ;; Test for the aux-data table created earlier in the test. (is (string= (aux-data-data (cdr (assoc "test" (aux-data (first (modules next))) :test #'string=))) test-string)))))) (deftest create-module-with-a-name () (is (string= "foo" (nest (pb:string-value) (proto:name) (gtirb::proto) (make-instance 'module :name "foo" :allow-other-keys t))))) ;; FIXME: create-module-without-a-name should verify that the appropriate ;; warning is emitted, but for some reason the warning doesn't percolate up ;; to the point where the signals testing macro is able to catch it. (deftest create-module-without-a-name () (is (emptyp (nest (pb:string-value) (proto:name) (gtirb::proto) (make-instance 'module))))) (deftest entry-points-on-modules () (with-fixture hello (let* ((hello (read-gtirb *proto-path*)) (module (first (modules hello))) (code-block (nest (first) (remove-if-not {typep _ 'code-block}) (blocks hello)))) ;; Entry-point is read as a code block. (is (typep (entry-point module) 'code-block)) ;; Newly saved entry-point has the UUID of the code block. (setf (entry-point module) code-block) (is (= (uuid code-block) (uuid-to-integer (proto:entry-point (gtirb::proto module)))))))) (deftest back-pointers-work () (with-fixture hello (let ((hello (read-gtirb *proto-path*))) ;; Modules point to IR. (is (every [{eql hello} #'gtirb] (modules hello))) ;; Sections point to modules. (mapc (lambda (module) (is (every [{eql module} #'module] (sections module))) ;; Byte-intervals point to sections. (mapc (lambda (section) (is (every [{eql section} #'section] (byte-intervals section))) ;; Blocks point to byte-intervals. (mapc (lambda (byte-interval) (is (every [{eql byte-interval} #'byte-interval] (blocks byte-interval)))) (byte-intervals section))) (sections module))) (modules hello))))) (deftest access-block-bytes () (with-fixture hello (is (every [#'not #'null #'bytes] (blocks (read-gtirb *proto-path*)))))) (deftest find-every-block-in-the-module () (nest (with-fixture hello) (let ((it (read-gtirb *proto-path*)))) (is) (every {get-uuid _ it}) (mapcar #'uuid) (blocks it))) (deftest get-blocks-and-bytes-from-cfg-nodes () (nest (with-fixture hello) (let ((it (read-gtirb *proto-path*)))) (is) (every {get-uuid _ it}) (nodes) (cfg it))) (deftest shift-subseq-adds-and-removes-as-expected () (let ((it #(1 2 3 4 5))) (setf (gtirb::shift-subseq it 2 3) #(9 9 9 9)) (is (equalp it #(1 2 9 9 9 9 4 5)))) (let ((it #(1 2 3 4 5))) (setf (gtirb::shift-subseq it 2 3) #()) (is (equalp it #(1 2 4 5))))) (deftest set-block-bytes-to-the-same-size () (with-fixture hello (let* ((it (read-gtirb *proto-path*)) (original-byte-intervals (nest (mappend #'byte-intervals) (mappend #'sections) (modules it))) (original-byte-interval-md5sum (nest (md5sum-sequence) (force-byte-array) (apply #'concatenate 'vector) (mapcar #'contents original-byte-intervals)))) (let ((target (first (mappend #'blocks original-byte-intervals)))) (setf (bytes target) (make-array (length (bytes target)) :initial-element 9)) (is (= (length original-byte-intervals) ; No new byte intervals. (length (nest (mappend #'byte-intervals) (mappend #'sections) (modules it))))) (is (not (equalp original-byte-interval-md5sum ; New contents. (nest (md5sum-sequence) (force-byte-array) (apply #'concatenate 'vector) (mapcar #'contents) (mappend #'byte-intervals) (mappend #'sections) (modules it))))))))) (deftest set-block-bytes-to-the-different-size () (with-fixture hello (let* ((it (read-gtirb *proto-path*)) (original-byte-intervals (nest (mappend #'byte-intervals) (mappend #'sections) (modules it))) (target (first (mappend #'blocks original-byte-intervals))) (original-bi-size (size (byte-interval target))) (original-bi-bytes (bytes (byte-interval target))) (original-size (length (bytes target))) (rest-block-bytes (cdr (mapcar #'bytes (blocks (byte-interval target)))))) (setf (bytes target) (make-array (* 2 original-size) :initial-element 9)) ;; First block bytes are updated. (is (equalp (bytes (first (mappend #'blocks original-byte-intervals))) (make-array (* 2 original-size) :initial-element 9))) ;; Block size is updated. (is (equalp (size target) (* 2 original-size))) ;; Byte-interval size is updated. (is (equalp (size (byte-interval target)) (+ original-bi-size original-size))) ;; Remainder of the byte-interval's bytes are the same. (is (equalp (subseq (bytes (byte-interval target)) (* 2 original-size)) (subseq original-bi-bytes original-size))) ;; Remaining blocks bytes are the same. (is (every #'equalp rest-block-bytes (cdr (mapcar #'bytes (blocks (byte-interval target))))))))) (deftest address-range-block-lookup () (with-fixture hello (let* ((it (read-gtirb *proto-path*)) (a-block (first (blocks it)))) (is (= 2 (length (address-range a-block)))) (is (member a-block (nest (mappend #'blocks) (on-address it) (first) (address-range a-block)))) (is (member a-block (nest (mappend #'blocks) (at-address it) (first) (address-range a-block)))) (is (not (member a-block (nest (mappend #'blocks) (at-address it) (second) (address-range a-block)))))))) (deftest symbolic-expressions-pushed-back () (with-fixture hello ;; Collect a byte-interval with offset symbolic expressions. (let* ((bi (nest (find-if [{some [#'not #'zerop #'car]} #'hash-table-alist #'symbolic-expressions]) (cdr) (mappend #'byte-intervals) (sections) (first) (modules) (read-gtirb *proto-path*))) (original-length (length (bytes bi))) (original-offsets (hash-table-keys (symbolic-expressions bi))) (largest-offset (extremum original-offsets #'>)) (smallest-offset (extremum original-offsets #'<))) ;; Add bytes after the last symbol. (setf (bytes bi largest-offset largest-offset) (make-array 2 :initial-element 9)) ;; Ensure bytes have been altered. (is (> (length (bytes bi)) original-length)) ;; Ensure symbolic expressions before last have not moved. (is (set-equal (remove (+ largest-offset 2) (hash-table-keys (symbolic-expressions bi))) (remove largest-offset original-offsets))) ;; Remove those added bytes (setf (bytes bi largest-offset (+ largest-offset 2)) #()) ;; Add bytes at the beginning. (setf (bytes bi 0 0) (make-array 1 :initial-element 9)) ;; Ensure symbolic expressions have actually been pushed back. (is (set-equal (mapcar #'1+ original-offsets) (hash-table-keys (symbolic-expressions bi)))) ;; Drop bytes at the beginning. (setf (bytes bi 0 (1+ smallest-offset)) #()) ;; Ensure this symbolic expression now starts at the beginning. (is (zerop (extremum (hash-table-keys (symbolic-expressions bi)) #'<)))))) (defvar *listing-comments*) (defun get-listing-comments (it) (when-let ((table (assoc "comments" (gtirb::aux-data-w-offsets (ir it)) :test #'equalp))) (hash-table-alist (aux-data-data (cdr table))))) (defmethod listing :around (object &key stream comments) (declare (ignorable stream comments)) (let ((*listing-comments* (if (boundp '*listing-comments*) *listing-comments* (get-listing-comments object)))) (call-next-method))) (defgeneric listing (OBJECT &key stream comments) (:documentation "Print a listing of OBJECT.") (:method ((self gtirb) &key (stream t) comments) (format stream "~&IR~%") (mapc {listing _ :stream stream :comments comments} (modules self))) (:method ((self module) &key (stream t) comments) (format stream "~&Module: ~S~%" (name self)) (mapc {listing _ :stream stream :comments comments} (sections self))) (:method ((self section) &key (stream t) comments) (format stream "~&Section: ~S" (name self)) (mapc {listing _ :stream stream :comments comments} (byte-intervals self))) (:method ((self byte-interval) &key (stream t) comments) (format stream "~:[ (no address)~; at ~a~]~%" (addressp self) (address self)) (mapc {listing _ :stream stream :comments comments} (blocks self))) (:method ((self gtirb-byte-block) &key (stream t) comments) (let ((bytes (bytes self)) (comments (when comments (remove-if-not [{= (uuid self)} #'caar] *listing-comments*)))) (dotimes (step (size self)) (format stream (if (zerop step) "~&>" " ")) (if-let ((comment (and comments (find-if [{= step} #'cadar] comments)))) (format stream "~4X ; ~A~%" (aref bytes step) (cdr comment)) (format stream "~4X" (aref bytes step))) (when (= 7 (mod step 8)) (format stream "~&")))))) (defun block-comments (data-block it) (sort (nest (remove-if-not [{= (uuid data-block)} #'caar]) (hash-table-alist) (aux-data-data) (cdr) (assoc "comments" (gtirb::aux-data-w-offsets (ir it)) :test #'equalp)) #'< :key [#'second #'car])) (deftest offsets-pushed-back () (nest (let ((gtirb::*update-aux-data-offsets* t))) (with-fixture hello) (let* ((it (read-gtirb *proto-path*)) (text (find-if [{string= ".text"} #'name] (sections (first (modules it))))) (commented-block (find-if {block-comments _ it} (blocks text))) (comment (first (block-comments commented-block it))) (starting-offset (cadar comment))) (is (gtirb::get-aux-data-w-offsets it) "We're finding aux-data tables with offsets. ~ Should be at least comments and cfiDirectives.") (is (gtirb::aux-data-w-offsets it) "The `aux-data-w-offsets' was populated.") #+nil (listing commented-block :comments t) (setf (bytes commented-block 0 0) #(0 0 0 0)) #+nil (listing commented-block :comments t) (is (= (+ 4 starting-offset) (cadar (first (block-comments commented-block it)))))))) (deftest block-symbolic-expressions () (with-fixture hello (is (nest (mappend [#'hash-table-values #'symbolic-expressions]) (mappend #'blocks) (mappend #'byte-intervals) (sections) (first) (modules) (read-gtirb *proto-path*))))) (deftest symbolic-expressions-maintained () (nest (with-fixture hello) (mapc (lambda (db) (let ((o-offset (offset db)) (o-bi-size (size (byte-interval db))) (o-db-size (size db)) (o-bi-se-size (hash-table-size (symbolic-expressions (byte-interval db)))) (o-se-size (hash-table-size (symbolic-expressions db)))) (setf (bytes db 0 0) #(#x90 #x90 #x90 #x90)) (is (= o-offset (offset db))) (is (= (+ 4 o-bi-size) (size (byte-interval db)))) (is (= (+ 4 o-db-size) (size db))) (is (= o-bi-se-size (hash-table-size (symbolic-expressions (byte-interval db))))) (is (= o-se-size (hash-table-size (symbolic-expressions db))))))) (blocks (read-gtirb *proto-path*)))) (deftest payload-can-be-read-and-set () (with-fixture hello (let* ((it (read-gtirb *proto-path*)) (symbols (mappend #'symbols (modules it))) (referent-symbol (find-if [#'proto:has-referent-uuid #'gtirb::proto] symbols))) ;; Reading gives the right type of payload. (is (subtypep (type-of (payload referent-symbol)) 'gtirb::proto-backed)) ;; Setting a payload has the right effect. (setf (payload referent-symbol) 42) ; Referent to value. (is (subtypep (type-of (payload referent-symbol)) 'number)) (is (not (proto:has-referent-uuid (gtirb::proto referent-symbol))))))) (deftest can-create-without-parents () (is (typep (make-instance 'module) 'module)) (is (typep (make-instance 'section) 'section)) (is (typep (make-instance 'byte-interval) 'byte-interval))) (deftest direct-ir-access () (with-fixture hello (let* ((it (read-gtirb *proto-path*))) (is (typep (ir (setf it (first (modules it)))) 'gtirb)) (is (typep (ir (setf it (first (sections it)))) 'gtirb)) (is (typep (ir (setf it (first (byte-intervals it)))) 'gtirb)) (is (typep (ir (setf it (first (blocks it)))) 'gtirb))))) (deftest every-symbolic-expression-has-symbols () (flet ((every-symbolic-expression-has-symbols-proto (path) (every (lambda (se) (cond ((proto:has-addr-const se) (proto:symbol-uuid (proto:addr-const se))) ((proto:has-addr-addr se) (proto:symbol1-uuid (proto:addr-addr se))))) (nest (mapcar #'proto:value) (mappend [{coerce _ 'list} #'proto:symbolic-expressions]) (mappend [{coerce _ 'list} #'proto:byte-intervals]) (coerce (proto:sections (aref (proto:modules (read-proto 'proto:ir path)) 0)) 'list)))) (every-symbolic-expression-has-symbols-gtirb (path) (nest (every #'symbols) (mappend [#'hash-table-values #'symbolic-expressions]) (mappend #'byte-intervals) (mappend #'sections) (modules (read-gtirb path))))) (with-fixture hello ;; First confirm for the protobuf (is (every-symbolic-expression-has-symbols-proto *proto-path*)) ;; Second confirm for the GTIRB representation. (is (every-symbolic-expression-has-symbols-gtirb *proto-path*)) ;; Then re-confirm both for a rewritten protobuf file. (uiop:with-temporary-file (:pathname temporary-file) (write-gtirb (read-gtirb *proto-path*) temporary-file) (is (every-symbolic-expression-has-symbols-proto temporary-file)) (is (every-symbolic-expression-has-symbols-gtirb temporary-file)))))) #+ignore-expected-failure (deftest every-block-is-found-at-its-address () (with-fixture hello (let ((it (read-gtirb *proto-path*))) (nest (is) (every «member #'identity [{at-address it} #'address]») (mappend #'blocks) (mappend #'byte-intervals) (mappend #'sections) (modules it))))) (deftest truncating-size-on-byte-interval-errors-and-truncates () (with-fixture hello (let ((byte-intervals (nest (mappend #'byte-intervals) (mappend #'sections) (modules) (read-gtirb *proto-path*)))) ;; Truncation signals an error. (signals ir (setf (size (first byte-intervals)) (1- (length (contents (first byte-intervals)))))) ;; Truncate restart works. (let* ((bi (second byte-intervals)) (original-length (length (contents bi)))) (handler-bind ((ir (lambda (e) (if (find-restart 'truncate-contents) (invoke-restart 'truncate-contents) (error e))))) (setf (size bi) (1- original-length))) (is (= original-length (+ 1 (size bi)) (+ 1 (length (contents bi)))))) ;; Ignore restart works. (let* ((bi (third byte-intervals)) (original-length (length (contents bi)))) (handler-bind ((ir (lambda (e) (if (find-restart 'ignore) (invoke-restart 'ignore) (error e))))) (setf (size bi) (1- original-length))) (is (= original-length (length (contents bi)))) (is (> original-length (size bi))))))) ;;;; Dot test suite (deftest write-dot-to-file () (with-fixture hello (with-temporary-file (:pathname path) (to-dot-file (read-gtirb *proto-path*) path)))) ================================================ FILE: cl/update.lisp ================================================ (defpackage :gtirb/update (:use :common-lisp :alexandria :gtirb/utility :named-readtables :curry-compose-reader-macros :command-line-arguments) (:import-from :serapeum :take-while) (:import-from :gtirb.proto) (:import-from :trivial-package-local-nicknames :add-package-local-nickname) (:import-from :proto-v0) (:import-from :uiop :nest) (:import-from :uiop/image :quit) (:import-from :gtirb :aux-data :aux-data-type :aux-data-data :+module-file-format-map+) (:export :update :upgrade :read-proto :write-proto)) (in-package :gtirb/update) (in-readtable :curry-compose-reader-macros) (eval-when (:compile-toplevel :load-toplevel :execute) (add-package-local-nickname :proto :gtirb.proto) (defparameter +udpate-args+ '((("help" #\h #\?) :type boolean :optional t :documentation "display help output")))) (defun module-bytes-subseq (module start end &aux (results #())) (let ((regions (proto-v0:regions (proto-v0:byte-map (proto-v0:image-byte-map module))))) (force-byte-array (dotimes (n (length regions) results) (let* ((region (aref regions n)) (address (proto-v0:address region)) (size (length (proto-v0:data region)))) (cond ((and (<= address start) (< start (+ address size))) (setf results (concatenate 'vector results (subseq (proto-v0:data region) (- start address) (min size (- end address))))) (setf start (min end (+ address size)))) ((= start end) (return results)))))))) (defun byte-interval (module section &aux (new (make-instance 'proto:byte-interval))) (let ((address (proto-v0:address section)) (size (proto-v0:size section))) (setf (proto:uuid new) (new-uuid) (proto:address new) address (proto:has-address new) (proto-v0:has-address section) (proto:size new) size (proto:contents new) (module-bytes-subseq module address (+ address size)) (proto:symbolic-expressions new) (map 'vector {upgrade _ :base address} (remove-if-not [«and {<= address} {>= (+ address size)}» #'proto-v0:key] (proto-v0:symbolic-operands module))) (proto:blocks new) (map 'vector (lambda (block) (etypecase block (proto-v0:block (let ((it (make-instance 'proto:block))) (setf (proto:code it) (upgrade block) (proto:offset it) (- (proto-v0:address block) address)) it)) (proto-v0:data-object (let ((it (make-instance 'proto:block))) (setf (proto:data it) (upgrade block :base address) (proto:offset it) (- (proto-v0:address block) address)) it)))) (remove-if-not (lambda (block) (let ((addr (proto-v0:address block))) (and (<= address addr) (< addr (+ address size))))) (concatenate 'vector (proto-v0:blocks module) (proto-v0:data module)))))) (coerce (list new) 'vector)) (defun entry-point (module) (when-let ((address (proto-v0:entry-point-address (proto-v0:image-byte-map module)))) (if-let ((gtirb-block (find-if «and [{< address} «+ #'proto-v0:address #'proto-v0:size»] [{>= address} #'proto-v0:address]» (proto-v0:blocks module)))) (proto-v0:uuid gtirb-block) (if (zerop address) (warn "Zero address found in module ~S, assuming not an entry point." (pb:string-value (proto-v0:name module))) (error "No block found holding module ~S entry point ~a." (pb:string-value (proto-v0:name module)) address))))) (defmacro transfer-fields (new old &rest fields) `(progn ,@(mapcar (lambda (field) `(setf (,(intern (symbol-name field) 'proto) ,new) (upgrade (,(intern (symbol-name field) 'proto-v0) ,old)))) fields))) #+debug (defun serial (it) "Useful to ensure yourself of what protobuf serialization is producing." (let* ((size (pb:octet-size it)) (buffer (make-array size :element-type '(unsigned-byte 8)))) (pb:serialize it buffer 0 size) buffer)) #+debug (defun deserial (class bytes &aux (it (make-instance class))) "Useful to ensure yourself of what protobuf deserialization is producing." (pb:merge-from-array it bytes 0 (length bytes)) it) (defun combine-cfgs (cfgs) (let ((it (make-instance 'proto:cfg))) (setf (proto:edges it) (coerce (mappend [{coerce _ 'list} #'proto:edges] cfgs) 'vector)) (setf (proto:vertices it) (coerce (mappend [{coerce _ 'list} #'proto:vertices] cfgs) 'vector)) it)) (define-constant +symbol-storage-kind+ '((#.proto-v0:+storage-kind-storage-undefined+ . :undefined) (#.proto-v0:+storage-kind-storage-normal+ . :normal) (#.proto-v0:+storage-kind-storage-static+ . :static) (#.proto-v0:+storage-kind-storage-extern+ . :extern) (#.proto-v0:+storage-kind-storage-local+ . :local)) :test #'equal) (defvar *code-block-uuids* (make-hash-table)) (defvar *data-block-uuids* (make-hash-table)) (defun storage-kind (symbol) (cdr (assoc (proto-v0:storage-kind symbol) +symbol-storage-kind+))) (defun elf-symbol-info (symbol) "Return an elfSymbolInfo entry for SYMBOL using its `proto-v0:storage-kind'." (list 0 ;; Point to data "OBJ," if it points to a code "FUNC," else "NOTYPE." (cond ((gethash (proto-v0:uuid symbol) *data-block-uuids*) "OBJ") ((gethash (proto-v0:uuid symbol) *code-block-uuids*) "FUNC") (t "NOTYPE")) (ecase (storage-kind symbol) ((:normal :static) "GLOBAL") (:local "LOCAL")) (ecase (storage-kind symbol) (:normal "DEFAULT") ((:local :static) "HIDDEN")) 0)) (defun update-padding-table (data offset-bases) "Convert DATA which is keyed by addresses to be keyed by offset. Use OFFSET-BASES, an alist of base address and byte-interval, to do this conversion." (let* ((it (make-instance 'aux-data :proto data)) (data (aux-data-data it))) (nest (setf (aux-data-type it) '(:mapping :offset :uint64-t) (aux-data-data it)) (alist-hash-table) (mapcar (lambda (pair) (destructuring-bind (addr . value) pair (let ((base (nest (lastcar) (take-while [{> addr} #'car]) offset-bases))) (destructuring-bind (base . id) base (cons (let ((it (make-instance 'proto:offset))) (setf (proto:element-id it) id) (setf (proto:displacement it) (- addr base)) it) value)))))) (hash-table-alist) data) (gtirb::proto it))) (defun offset-bases (new-sections) (nest (apply #'append) (map 'list (lambda (section) (map 'list (lambda (bi) (assert (proto:has-address bi)) (cons (proto:address bi) (proto:uuid bi))) (proto:byte-intervals section)))) new-sections)) (defgeneric upgrade (object &key &allow-other-keys) (:documentation "Upgrade OBJECT to the next protobuf version.") (:method ((old t) &key &allow-other-keys) old) (:method ((old array) &key &allow-other-keys) (if (every #'numberp old) (force-byte-array old) (map 'vector #'upgrade old))) (:method ((old proto-v0:ir) &key &allow-other-keys &aux (new (make-instance 'proto:ir))) (setf (proto:uuid new) (proto-v0:uuid old) (proto:version new) 1 (proto:aux-data new) (upgrade (proto-v0:aux-data-container old) :new-class 'proto:ir-aux-data-entry) (proto:modules new) (upgrade (proto-v0:modules old)) (proto:cfg new) (combine-cfgs (map 'list [#'upgrade #'proto-v0:cfg] (proto-v0:modules old)))) new) (:method ((old proto-v0:module) &key &allow-other-keys &aux (new (make-instance 'proto:module))) (transfer-fields new old uuid binary-path preferred-addr rebase-delta file-format name symbols proxies name) (let ((new-sections (map 'vector {upgrade _ :module old} (proto-v0:sections old)))) (setf (proto:isa new) (proto-v0:isa-id old) (proto:aux-data new) (upgrade (proto-v0:aux-data-container old) :new-class 'proto:module-aux-data-entry :offset-bases (offset-bases new-sections)) (proto:sections new) new-sections)) (if-let ((entry-point (entry-point old))) (setf (proto:entry-point new) entry-point)) ;; Add a symbolType AuxData table of type mapping to ;; track the storage kinds of all symbols. (ecase (cdr (assoc (proto-v0:file-format old) +module-file-format-map+)) (:elf (let ((ad (make-instance 'aux-data)) (ade (make-instance 'proto:module-aux-data-entry))) (setf (aux-data-type ad) '(:mapping :uuid (:tuple :uint64-t :string :string :string :uint64-t)) (aux-data-data ad) (alist-hash-table (map 'list «cons [#'uuid-to-integer #'proto-v0:uuid] #'elf-symbol-info» (remove-if [{eql :extern} #'storage-kind] (proto-v0:symbols old))))) (setf (proto:key ade) (pb:string-field "elfSymbolInfo") (proto:value ade) (gtirb::proto ad)) (setf (proto:aux-data new) (coerce (append (list ade) (coerce (proto:aux-data new) 'list)) 'vector)))) (:pe (flet ((symbols-to-aux-data (name symbols) (let ((ad (make-instance 'aux-data)) (ade (make-instance 'proto:module-aux-data-entry))) (setf (aux-data-type ad) '(:set :uuid) (aux-data-data ad) symbols (proto:key ade) (pb:string-field name) (proto:value ade) (gtirb::proto ad)) ade))) (let ((in (symbols-to-aux-data "peImportedSymbols" (nest (mapcar [#'uuid-to-integer #'proto-v0:uuid]) (remove-if-not [{member _ '(:local :static)} #'storage-kind]) (proto-v0:symbols old)))) (out (symbols-to-aux-data "peExportedSymbols" (nest (mapcar [#'uuid-to-integer #'proto-v0:uuid]) (remove-if-not [{member _ '(:normal :extern)} #'storage-kind]) (proto-v0:symbols old))))) (setf (proto:aux-data new) (coerce (append (list in out) (coerce (proto:aux-data new) 'list)) 'vector)))))) new) (:method ((old proto-v0:aux-data-container) &key new-class offset-bases &allow-other-keys) (map 'vector (lambda (entry) (let ((it (make-instance new-class))) (let ((new-value (upgrade (proto-v0:value entry)))) (when (string= "padding" (pb:string-value (proto-v0:key entry))) (setf new-value (update-padding-table new-value offset-bases))) (setf (proto:key it) (proto-v0:key entry) (proto:value it) new-value)) it)) (proto-v0:aux-data old))) (:method ((old proto-v0:aux-data) &key &allow-other-keys &aux (new (make-instance 'proto:aux-data))) (transfer-fields new old type-name data) new) (:method ((old proto-v0:section) &key module &allow-other-keys &aux (new (make-instance 'proto:section))) (transfer-fields new old uuid name) (setf (proto:byte-intervals new) (byte-interval module old)) new) (:method ((old proto-v0:edge-label) &key &allow-other-keys &aux (new (make-instance 'proto:edge-label))) (transfer-fields new old conditional direct type) new) (:method ((old proto-v0:edge) &key &allow-other-keys &aux (new (make-instance 'proto:edge))) (transfer-fields new old source-uuid target-uuid label) new) (:method ((old proto-v0:cfg) &key &allow-other-keys &aux (new (make-instance 'proto:cfg))) (transfer-fields new old vertices edges) new) (:method ((old proto-v0:module-symbolic-operands-entry) &key base &allow-other-keys &aux (new (make-instance 'proto:byte-interval-symbolic-expressions-entry))) (setf (proto:key new) (- (proto-v0:key old) base) (proto:value new) (upgrade (proto-v0:value old))) new) (:method ((old proto-v0:symbol) &key &allow-other-keys &aux (new (make-instance 'proto:symbol))) (transfer-fields new old uuid name) (cond ; Variant "oneof" 'value' or 'referent_uuid'. ((proto-v0:has-value old) (setf (proto:value new) (proto-v0:value old))) ((proto-v0:has-referent-uuid old) (setf (proto:referent-uuid new) (upgrade (proto-v0:referent-uuid old)) ;; This field was added after GTIRB-V.0. (proto:at-end new) nil))) new) (:method ((old proto-v0:symbolic-expression) &key &allow-other-keys &aux (new (make-instance 'proto:symbolic-expression))) (cond ; Variant "oneof" field. ((proto-v0:has-addr-const old) (setf (proto:addr-const new) (upgrade (proto-v0:addr-const old)))) ((proto-v0:has-addr-addr old) (setf (proto:addr-addr new) (upgrade (proto-v0:addr-addr old)))) (t (warn "Symbolic expressions ~s has no value." old))) #+debug (progn ; Potentially useful debug pattern to inspect protobuf. (format t "~%~%~%OLD:~S~%" (serial old)) (describe old) (format t "~%NEW:~S~%" (serial new)) (describe new)) new) (:method ((old proto-v0:sym-addr-const) &key &allow-other-keys &aux (new (make-instance 'proto:sym-addr-const))) (transfer-fields new old symbol-uuid) new) (:method ((old proto-v0:sym-addr-addr) &key &allow-other-keys &aux (new (make-instance 'proto:sym-addr-addr))) (transfer-fields new old scale offset symbol1-uuid symbol2-uuid) new) (:method ((old proto-v0:block) &key &allow-other-keys &aux (new (make-instance 'proto:code-block))) (setf (gethash (proto-v0:uuid old) *code-block-uuids*) t) (transfer-fields new old uuid size decode-mode) new) (:method ((old proto-v0:data-object) &key &allow-other-keys &aux (new (make-instance 'proto:data-block))) (setf (gethash (proto-v0:uuid old) *data-block-uuids*) t) (transfer-fields new old uuid size) new)) (define-command update (input-file output-file &spec +udpate-args+) "Update GTIRB protobuf from INPUT-FILE to OUTPUT-FILE." "" (when help (show-help-for-update) (quit)) (setf *random-state* (make-random-state t)) (write-proto (upgrade (read-proto 'proto-v0:ir input-file)) output-file)) ================================================ FILE: cl/utility.lisp ================================================ (defpackage :gtirb/utility (:use :common-lisp :gtirb/version) (:import-from :cl-intbytes :int->octets :octets->int) (:import-from :alexandria :define-constant :read-stream-content-into-byte-vector :read-file-into-byte-vector :starts-with-subseq) (:import-from :uiop :nest) (:import-from :uiop/stream :file-stream-p) (:export :read-proto :write-proto :gtirb-magic-error :new-uuid :force-byte-array :uuid-to-integer :integer-to-uuid)) (in-package :gtirb/utility) (declaim (optimize (speed 3) (safety 0) (debug 0))) (define-constant gtirb-magic-octets #(71 84 73 82 66) ;; "GTIRB" :test #'equalp :documentation "GTIRB file magic bytes at beginning of magic header.") (define-constant gtirb-magic-length 8 :test #'equal :documentation "Number of bytes in the GTIRB file magic header.") (define-condition gtirb-magic-error (error) ((message :initarg :message :initform nil :reader message)) (:report (lambda (condition stream) (format stream "~S" (message condition)))) (:documentation "Condition raised if GTIRB header is invalid.")) (defgeneric read-proto (class source) (:documentation "Read protobuf object of class CLASS from SOURCE.") (:method :before (class (path pathname)) (declare (ignorable class)) (assert (probe-file path) (path) "Can't read Protobuf from ~s, because the file doesn't exist." path)) (:method (class (path string)) (read-proto class (pathname path))) (:method (class (path pathname)) (with-open-file (input path :direction :input :element-type 'unsigned-byte) (read-proto class input))) (:method (class (input stream)) (read-proto class (if (file-stream-p input) (read-file-into-byte-vector input) (read-stream-content-into-byte-vector input)))) (:method (class (buffer array) &aux (gtirb (make-instance class))) (check-magic-header buffer) (pb:merge-from-array gtirb buffer gtirb-magic-length (length buffer)) gtirb)) (defun write-proto (object path) "Write OBJECT to PATH." (let* ((size (pb:octet-size object)) (buffer (make-array size :element-type '(unsigned-byte 8)))) (pb:serialize object buffer 0 size) (with-open-file (output path :direction :output :if-exists :supersede :element-type 'unsigned-byte) (write-magic-header output) (write-sequence buffer output))) (values)) (defun new-uuid (&aux (it (make-array 16 :element-type '(unsigned-byte 8)))) "Return a new random UUID." (dotimes (n 16 it) (setf (aref it n) (random 256)))) (defun force-byte-array (array) "Force ARRAY into a byte array." (declare (type (simple-array) array)) (make-array (length array) :element-type '(unsigned-byte 8) :initial-contents array)) (defun uuid-to-integer (uuid) (declare (type (simple-array) uuid)) (if (zerop (length uuid)) (prog1 0 #+debug (warn "Bad null UUID.")) (octets->int (force-byte-array uuid) 16))) (defun integer-to-uuid (number) (int->octets number 16)) (defun check-magic-header (bytes) "Check if the GTIRB magic header bytes are present in at the start of BYTES, throwing a GTIRB-MAGIC-ERROR if the header is not present." (when (or (< (length bytes) gtirb-magic-length) (not (starts-with-subseq gtirb-magic-octets bytes))) (error (make-condition 'gtirb-magic-error :message "File missing GTIRB magic - not a GTIRB file?"))) (when (not (equal protobuf-version (aref bytes (1- gtirb-magic-length)))) (error (nest (make-condition 'gtirb-magic-error :message) (format nil "Attempt to decode IR of version ~d (expected version ~d)" (aref bytes (1- gtirb-magic-length)) protobuf-version))))) (defun write-magic-header (stream) "Write the GTIRB magic header bytes to STREAM." (write-sequence gtirb-magic-octets stream) (write-byte 0 stream) (write-byte 0 stream) (write-byte protobuf-version stream)) ================================================ FILE: cl/validate.lisp ================================================ (defpackage :gtirb/validate (:use :gt/full :gtirb :graph :command-line-arguments) (:import-from :gtirb.proto) (:shadowing-import-from :gt/full :size :copy) (:shadowing-import-from :gtirb :symbol) (:export :validate)) (in-package :gtirb/validate) (in-readtable :curry-compose-reader-macros) (defmethod size ((gtirb-node gtirb-node)) (gtirb:size gtirb-node)) (defmethod copy ((graph graph) &key &allow-other-keys) (graph:copy graph)) ;;;; Interface (defvar *requirements* nil "A-list of default requirements keyed by object type.") (defvar *failed-checks*) (defgeneric validate (object &key requirements) (:documentation "Validate that OBJECT satisfies the requirements in .") (:method ((object t) &key (requirements *requirements*) &aux (*failed-checks* nil)) (values (every {check object} (cdr (assoc (type-of object) requirements))) *failed-checks*))) (defclass check () ((action :reader action :initarg :action :type (or symbol function) :documentation "Action to run the check.") (object :reader object :initarg :object :type symbol :documentation "Type of object the check applies to.") (name :reader name :initarg :name :type string :documentation "Name of the check.")) (:documentation "Check objects hold validation checks for gtirb instances.")) (defmethod initialize-instance :after ((check check) &key (requirements *requirements*) &allow-other-keys) (unless (assoc (object check) requirements) (push (list (object check)) requirements)) (pushnew check (cdr (assoc (object check) requirements)) :key #'name)) (defgeneric check (object requirement) (:documentation "Check that OBJECT satisfies REQUIREMENT.") (:method ((obj t) (requirement symbol)) (funcall requirement obj)) (:method ((obj t) (requirement function)) (funcall requirement obj)) (:method ((obj t) (requirement check)) (funcall (action requirement) obj))) (defmethod check :around ((object t) (requirement function)) (or (call-next-method) (push requirement *failed-checks*))) (defmethod check :around ((object t) (requirement check)) (or (call-next-method) (push (name requirement) *failed-checks*))) ;;;; GTIRB Checks (defmacro define-check-generic (name (object) &body methods) `(progn (defgeneric ,name (,object) ,@methods) (make-instance 'check :name ',name :action ',name :object ',(second (first (second (first methods))))))) (define-check-generic size-matches-contents (object) (:method ((obj gtirb)) (every #'size-matches-contents (modules obj))) (:method ((obj module)) (every #'size-matches-contents (sections obj))) (:method ((obj section)) (every #'size-matches-contents (byte-intervals obj))) (:method ((obj byte-interval)) (= (size obj) (length (contents obj))))) (flet ((nothing-overlaps- (things &aux (min 0)) (and (every (lambda (pair) (destructuring-bind (address . size) pair (prog1 (>= address min) (setf min (max min (+ address size)))))) (sort (mapcar «cons #'address #'size» things) #'< :key #'car)) (every #'nothing-overlaps things)))) (define-check-generic nothing-overlaps (object) (:method ((obj gtirb)) (every #'nothing-overlaps (modules obj))) (:method ((obj module)) (nothing-overlaps- (sections obj))) (:method ((obj section)) (nothing-overlaps- (byte-intervals obj))) (:method ((obj byte-interval)) (nothing-overlaps- (remove-if-not {typep _ 'code-block} (blocks obj)))) (:method ((obj code-block)) t))) (define-check-generic all-referents-exist (object) (:method ((obj gtirb)) (and (every #'all-referents-exist (modules obj)) (every {get-uuid _ obj} (nodes (cfg obj))))) (:method ((obj module)) (every #'all-referents-exist (symbols obj))) (:method ((obj symbol)) (if (gtirb.proto:has-referent-uuid (gtirb::proto obj)) (payload obj) t))) (define-check-generic symbolic-expression-size-well-formed (object) (:method ((obj gtirb)) (every [{every «member #'second [#'hash-table-keys #'symbolic-expressions {get-uuid _ obj} #'car]»} #'hash-table-keys #'aux-data-data #'cdr (lambda (el) (assoc "symbolicExpressionSizes" el :test #'string=)) #'aux-data] (modules obj)))) ;;;; Command-line interface (eval-when (:compile-toplevel :load-toplevel :execute) (defparameter +validate-args+ '((("help" #\h #\?) :type boolean :optional t :documentation "display help output")))) (define-command validate-file (gtirb-file &spec +validate-args+) "Validate GTIRB-FILE." "" (flet ((exit (code) (if *lisp-interaction* (return-from validate-file (zerop code)) (quit code)))) (when help (show-help-for-validate-file) (quit)) (if (validate (read-gtirb gtirb-file)) (exit 0) (exit 2)))) ================================================ FILE: cl/version.lisp ================================================ (defpackage :gtirb/version (:use :common-lisp) (:import-from :alexandria :define-constant) (:import-from :asdf/system :system-relative-pathname) (:import-from :uiop :nest) (:export :gtirb-version :protobuf-version)) (in-package :gtirb/version) (eval-when (:compile-toplevel :load-toplevel :execute) (defvar version.txt `#.(nest (let ((version-path (system-relative-pathname "gtirb" "../version.txt")))) (with-open-file (in version-path)) (loop for line = (read-line in nil :eof) until (eql line :eof) collect (let ((delim (position #\Space line))) (cons (intern (subseq line 0 delim)) (parse-integer (subseq line (1+ delim))))))))) (define-constant gtirb-version (format nil "~d.~d.~d" (cdr (assoc 'VERSION_MAJOR version.txt)) (cdr (assoc 'VERSION_MINOR version.txt)) (cdr (assoc 'VERSION_PATCH version.txt))) :test #'string= :documentation "GTIRB Version as a string of \"MAJOR.MINOR.PATCH\".") (define-constant protobuf-version (cdr (assoc 'VERSION_PROTOBUF version.txt)) :test #'= :documentation "GTIRB Protobuf Version as a non-negative integer.") ================================================ FILE: conanfile.py ================================================ import os import re from conans import CMake, ConanFile, tools from conans.errors import ConanInvalidConfiguration from conans.model.version import Version def get_gtirb_version(): if "CI_COMMIT_REF_NAME" in os.environ: branch = os.environ["CI_COMMIT_REF_NAME"] if branch == "master": return "dev" try: with open("version.txt") as f: s = f.read() match = re.search( r"VERSION_MAJOR(\s+)(\S+)(\s+)" r"VERSION_MINOR(\s+)(\S+)(\s+)" r"VERSION_PATCH(\s+)(\S+)(\s+)", s, ) if match: major = match.group(2) minor = match.group(5) patch = match.group(8) return major + "." + minor + "." + patch else: return "" except Exception: return "" def branch_to_channel(branch): if re.match(r"^release-.*", branch): return "stable" else: return branch.replace("/", "+") class Properties: name = "gtirb" rel_url = "rewriting/gtirb" exports_sources = "*", "!*_CPack_Packages*", "!*java*.class" @property def version(self): if not hasattr(self, "_version"): self._version = get_gtirb_version() return self._version @version.setter def version(self, ver): self._version = ver @property def description(self): return "%s library" % self.name @property def url(self): return "https://git.grammatech.com/%s" % self.rel_url @property def conan_channel(self): channel = "local" if "CI_COMMIT_REF_NAME" in os.environ: branch = os.environ["CI_COMMIT_REF_NAME"] channel = branch_to_channel(branch) return channel @property def archived_channels(self): # Add to this list branch names to have conan packages for # branches archived in gitlab. archived_branches = ["master"] # Also, archive the 'stable' channel, where all stable versions # will be uploaded archived_channels = ["stable"] return archived_channels + list( map(branch_to_channel, archived_branches) ) @property def conan_ref(self): channel = self.conan_channel return "%s/%s" % (self.rel_url.replace("/", "+"), channel) @property def conan_recipe(self): return "%s/%s@%s" % (self.name, self.version, self.conan_ref) class GtirbConan(Properties, ConanFile): boost_version = "1.69.0" protobuf_version = "3.15.5" requires = ( "boost/{0}".format(boost_version), "protobuf/{0}".format(protobuf_version), ) settings = "os", "build_type", "compiler", "arch" generators = "cmake" def configure(self): if ( self.settings.compiler == "gcc" and self.settings.compiler.libcxx != "libstdc++11" ): raise ConanInvalidConfiguration( "gtirb requires libstdc++11 ABI, update your conan profile" ) def build_requirements(self): if self.settings.os == "Windows": self.build_requires("ninja/1.10.2") def build(self): if self.settings.os == "Windows": with tools.vcvars( self.settings, force=True, filter_known_paths=False ): self.build_cmake() else: self.build_cmake() def build_cmake(self): # Note: Only build the C++ API defs = { "CMAKE_VERBOSE_MAKEFILE:BOOL": "ON", "ENABLE_CONAN:BOOL": "ON", "GTIRB_CXX_API:BOOL": "ON", "GTIRB_PY_API:BOOL": "OFF", "GTIRB_CL_API:BOOL": "OFF", "GTIRB_JAVA_API:BOOL": "OFF", } disable_parallel_build = ( int(os.environ.get("GTIRB_DISABLE_PARALLEL_BUILD", "0")) != 0 ) if self.settings.os == "Windows": cmake = CMake(self, generator="Ninja") defs.update( { k: os.environ.get(k) for k in ["BOOST_ROOT", "CMAKE_PREFIX_PATH", "PYTHON"] } ) defs.update({"Protobuf_USE_STATIC_LIBS": "ON"}) if disable_parallel_build: defs["GTIRB_MSVC_PARALLEL_COMPILE_JOBS"] = "1" else: cmake = CMake(self, generator=None) defs.update({"GTIRB_STRIP_DEBUG_SYMBOLS:BOOL": "ON"}) cmake.configure( source_folder=".", defs=defs, ) if disable_parallel_build: cmake.parallel = False cmake.build() cmake.test(output_on_failure=True) cmake.install() # The patch_config_paths() function will change absolute paths in the # exported cmake config files to use the appropriate conan variables # instead. # It is an experimental feature of conan, however, so if you're having # trouble with paths in the cmake of the conan package, it could that # this function is no longer doing what we want. cmake.patch_config_paths() def package(self): pass def package_info(self): self.cpp_info.libs = ["gtirb"] def package_id(self): v = Version(str(self.settings.compiler.version)) if self.settings.compiler == "Visual Studio" and v in ["15", "16"]: self.info.settings.compiler.version = "Visual Studio 15 and 16" ================================================ FILE: cpack-config.cmake ================================================ # Global properties set(CMAKE_PROJECT_HOMEPAGE_URL https://github.com/GrammaTech/gtirb) set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE) set(CPACK_DEB_COMPONENT_INSTALL ON) # Reusable lists of components set(LIB_COMPONENTS library license) set(DEV_COMPONENTS headers proto_library cmake_config cmake_target) # Debian packages if("${CPACK_GTIRB_PACKAGE}" STREQUAL "debian-lib") set(CPACK_DEBIAN_PACKAGE_NAME "libgtirb") set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) set(CPACK_COMPONENTS_ALL ${LIB_COMPONENTS}) if("${CPACK_DEBIAN_PACKAGE_RELEASE}" STREQUAL "focal") set(CPACK_DEBIAN_PACKAGE_DEPENDS "libstdc++6, libc6, libgcc1, libprotobuf17" ) elseif("${CPACK_DEBIAN_PACKAGE_RELEASE}" STREQUAL "jammy") set(CPACK_DEBIAN_PACKAGE_DEPENDS "libstdc++6, libc6, libgcc-s1, libprotobuf23" ) elseif("${CPACK_DEBIAN_PACKAGE_RELEASE}" STREQUAL "noble") set(CPACK_DEBIAN_PACKAGE_DEPENDS "libstdc++6, libc6, libgcc-s1, libprotobuf32t64" ) else() message( SEND_ERROR "Unknown / missing value for CPACK_DEBIAN_PACKAGE_RELEASE." ) endif() elseif("${CPACK_GTIRB_PACKAGE}" STREQUAL "debian-dev") set(CPACK_DEBIAN_PACKAGE_NAME "libgtirb-dev") set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) set(CPACK_COMPONENTS_ALL ${DEV_COMPONENTS}) set(CPACK_DEBIAN_PACKAGE_DEPENDS "libgtirb (=${CPACK_GTIRB_VERSION}-${CPACK_DEBIAN_PACKAGE_RELEASE}), libboost-dev (>=1.68) | libboost1.68-dev, libprotobuf-dev (>=${CPACK_PROTOBUF_VERSION_LOWER_BOUND}~), libprotobuf-dev (<<${CPACK_PROTOBUF_VERSION_UPPER_BOUND})" ) elseif("${CPACK_GTIRB_PACKAGE}" STREQUAL "debian-debug") set(CPACK_DEBIAN_PACKAGE_NAME "libgtirb-dbg") set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) set(CPACK_COMPONENTS_ALL debug-file) set(CPACK_DEBIAN_PACKAGE_DEPENDS "libgtirb (=${CPACK_GTIRB_VERSION}-${CPACK_DEBIAN_PACKAGE_RELEASE})" ) endif() ================================================ FILE: doc/CFG-Edges.md ================================================ Advice on when to Place ICFG Edges ================================== It is not always straightforward when to construct ICFG edges. For example, should there be an edge from a function's return to every caller of that function? While GTIRB does not preclude or enforce any particular ICFG connection strategy, we do propose the following suggestions for how to handle many common situations. ## Undefined/extern functions. How to handle edges leaving call instructions to undefined or extern functions? In this case the function is not included in the module and thus is not a possible edge target in the ICFG. > ???? ## Fall through edges from call sites. Since we're working with an interprocedural CFG, we have edges from call instructions to their targets. Do we also have edges from a call instruction to the instruction immediately after it in memory? > Yes unless we know the callee does not return (e.g., exit). ### Non-return functions. When generating code around non-return functions (like exit): sometimes compilers omit any following code if they know a call will not return. For example in a situation like this (except in machine code): ```C void foo() { if (something) { bar_that_always_calls_exit(); // stack cleanup return; } } ``` In some cases, the compiler will know that `bar_that_always_calls_exit()` will never return. In some cases, the compiler doesn't figure that out. In the former case, the compiler may omit the "stack cleanup" and "return" code. In the latter case, it will not. In some cases, your disassembly engine will know that `bar_that_always_calls_exit()` will never return. In some cases, your disassembly engine doesn't figure that out. In the former case, you can choose to omit the "fall-through" edge in your IR. In the latter case, you won't, because you think the execution flow is possible. So, if you get the combination: the compiler figured it out and you did *not*. Then you will add the CFG edge from `bar_that_always_calls_exit()` to whatever follows. But whatever follows will not necessarily be code in the function `foo()` or code at all really. It will be whatever random chunk of bytes the compiler happened to plop down next. We bring this up because it has bearing on your stance for adding edges from callsites to their follows. If you don't do that, you'll never make this mistake. However, we suggest that in the balance, having the edges when your disassembly is correct outweighs the presence of this error. ## Edges from returns back to call sites. What about from a return instruction to corresponding call sites (plural) (or rather the instructions that follow them)? > Yes. ## Edges from indirect calls to targets. Indirect calls: we won't know who the targets are always. And we won't know to add edges from corresponding return instructions. > We put in edges (to and from) as we're able to identify them. We > don't conservatively put edges between every indirect call and every > function. ## Tail calls. Tail calls don't have return instructions or a place for a callee to return to. > Hook up the eventual return of a tail called function to the places > it could lead (i.e., non tail-call callers and callers of tail-call > callers). ================================================ FILE: doc/CMakeLists.txt ================================================ add_custom_target(doc) file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/html") if(CXX_API) set(CPP_TAGFILE "${CMAKE_CURRENT_BINARY_DIR}/CPP_DOXY.tag") set(CPP_TAGFILE_RULE "${CPP_TAGFILE}=cpp") set(CPP_DOX_ENABLED "CPP_ONLY") set(CPP_API_VISIBLE "yes") add_subdirectory(cpp) else() set(CPP_TAGFILE_RULE "") set(CPP_DOX_ENABLED "") set(CPP_API_VISIBLE "no") endif() if(PY_API) add_subdirectory(python) set(PY_DOX_ENABLED "PY_ONLY") set(PY_API_VISIBLE "yes") else() set(PY_DOX_ENABLED "") set(PY_API_VISIBLE "no") endif() if(CL_API) add_subdirectory(cl) set(CL_DOX_ENABLED "CL_ONLY") set(CL_API_VISIBLE "yes") else() set(CL_DOX_ENABLED "") set(CL_API_VISIBLE "no") endif() if(JAVA_API) add_subdirectory(java) set(JAVA_DOX_ENABLED "JAVA_ONLY") set(JAVA_API_VISIBLE "yes") else() set(JAVA_DOX_ENABLED "") set(JAVA_API_VISIBLE "no") endif() add_subdirectory(examples) add_subdirectory(general) ================================================ FILE: doc/binary-representation.md ================================================ Binary Representation with GTIRB ================================ - [Representing Binaries](#representing-binaries) - [Sections](#sections) - [Symbols](#symbols) - [Byte Intervals](#byte-intervals) - [Symbolic Expressions](#symbolic-expressions) GTIRB portably encodes binaries from a range of standard executable and linkable formats, such as [ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format), [PE](https://en.wikipedia.org/wiki/Portable_Executable), and [Mach-O](https://en.wikipedia.org/wiki/Mach-O), allowing conversion to and from these formats to GTIRB. In ensure all information from the original binary is retained despite standard GTIRB data structures--which are intended to be general across all binary representations--being lossy for many aspects of particular representation, encoders are encouraged to include all raw bytes of the original file in the GTIRB encoding. In addition, GTIRB encodes information above and beyond what these formats store; it stores control flow, symbolization (reference) information, and other analysis results, with the goal of providing all essential information to support subsequent binary analysis and rewriting. Finally, GTIRB allows user-extensible data to be included in the form of AuxData tables which can easily reference other GTIRB elements--letting tools communicate with each other in a single standard in-file format. ## Representing Binaries Although executable file formats differ in many ways, they typically tend to have a similar structure. The bytes of the image are divided into sections, which contain the bytes consisting of the code and data along with information about how to load and adjust them at run-time. To facilitate linking with shared libraries, they have a symbol table, which specifies a list of names of entities this file provides or requires. To facilitate relocation in memory these files often contains a relocation table. GTIRB contains all this information in standard forms. In GTIRB, a single executable or library is encoded as a *module*. A GTIRB file may have multiple modules, enclosed in a single *IR*. GTIRB encodes the standard features of all binary formats in the following structures: ### Sections Modules in GTIRB contain multiple *sections*. A section has a name reflecting any name given in the original file (e.g., `.text`), a set of properties, and a set of contents stored in [byte intervals](#byte-intervals). ### Symbols Rather than storing a symbol table as a section, GTIRB stores a set of *symbols* associated with every module. These symbols have a name, a set of properties, and a *referent*. A referent may be an integer, indicating that the symbol is a numeric constant or fixed address, or a reference to a *block*. A block may be one of: | Block Kind | Description | |---------------|---------------------------------------------------------| | *code block* | a series of executable instructions | | *data block* | a series of data bytes | | *proxy block* | indicating that the symbol is defined in another module | ### Byte Intervals The bytes of a section are subdivided into chunks of bytes called *byte intervals*. This indirection layer serves two purposes: - Indicate what blocks can be moved independently of each other. It is guaranteed that you can shuffle around two byte intervals in a section, and doing so will preserve the program's semantics. - Support the generation of blocks with no original address. Byte intervals may have a fixed address, but they may also be unfixed, likely indicating that the byte interval was generated by a binary rewriting tool or is freely movable to any address. Two byte intervals in the same section may not overlap in addresses (although sections can overlap with each other in some cases, such as in object code). Byte intervals contain code blocks or data blocks. The blocks within a byte interval *can* overlap. Examples of overlapping blocks include: - Overlapping data blocks are common. - One data block may representing an array may overlap many data blocks representing elements of the array. - Compilers often overlap strings with shared suffixes. The data blocks representing these strings will similarly overlap. * Overlapping code blocks are rare, however particularly clever or malicious code blocks in variable-width ISAs may overlap when two different sequences of instructions serialize to machine-code bytes which share common subsequences. Byte intervals also hold [symbolic expressions](#symbolic-expressions) which indicate symbolic contents of code or data blocks. ### Symbolic Expressions To encode relocations, GTIRB associates *symbolic expressions* with byte-intervals. These specify that certain bytes in the binary refer to the address. This allows these bytes to be recalculated when the referent is moved in the binary image. GTIRB does not specify exactly how symbolic expressions are transformed into bytes. This depends on where the symbolic expression is located; inside a code block, it depends on what part of an instruction it is of, while inside a data block, it depends on the size of the data block. There are currently three kinds of symbolic expressions: | Kind | Description | |-----------------|-------------------| | *SymAddrConst* | the address of the referent of a symbol, plus or minus a fixed offset | | *SymAddrAddr* | the difference between two symbols, divided by a scale and plus an offset | ================================================ FILE: doc/cl/CMakeLists.txt ================================================ set(SDT_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/write-documentation.lisp) set(README ${CMAKE_SOURCE_DIR}/cl/README.md) set(HTML_INDEX ${CMAKE_BINARY_DIR}/doc/html/cl/index.html) file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/doc/html/cl") file(GLOB CL_SOURCES ${CMAKE_SOURCE_DIR}/cl/*.lisp ${CMAKE_SOURCE_DIR}/cl/*.asd) add_custom_command( OUTPUT ${HTML_INDEX} DEPENDS ${SDT_SCRIPT} ${README} ${CL_SOURCES} COMMAND ${LISP} --noinform --dynamic-space-size 16384 --no-userinit --no-sysinit --disable-debugger --load ${QUICKLISP}/setup.lisp --eval "(asdf:initialize-source-registry `(:source-registry (:tree \"${CMAKE_SOURCE_DIR}/cl\") :inherit-configuration))" --script ${SDT_SCRIPT} gtirb ${README} ${HTML_INDEX} VERBATIM COMMENT "Generating API documentation with SDT" ) add_custom_target(sdt ALL DEPENDS ${HTML_INDEX}) add_dependencies(doc sdt) ================================================ FILE: doc/cl/write-documentation.lisp ================================================ ;; -*- lisp -*- ;; ;; Usage: write-documentation PKG ABSTRACT OUTPUT-HTML ;; Write the automatically generated documentation for PKG to ;; OUTPUT-HTML. The ABSTRACT should be a file in Markdown format to ;; place at the top of the generated documentation. ;; (load "~/.sbclrc" :if-does-not-exist nil) (ql:quickload :alexandria) (ql:quickload :simpler-documentation-template) (ql:quickload :markdown.cl) (unless (= (length sb-ext:*posix-argv*) 5) (format t "Usage: write-documentation PKG ABSTRACT OUTPUT-HTML")) (let ((package (alexandria:make-keyword (string-upcase (second sb-ext:*posix-argv*)))) (abstract-path (third sb-ext:*posix-argv*)) (output-path (fourth sb-ext:*posix-argv*))) (ql:quickload package) #+debug (format t "Writing: ~S~%" (list package abstract-path output-path)) (simpler-documentation-template:create-template package :maybe-skip-methods-p t :target output-path :abstract-html (markdown.cl:parse (with-open-file (in abstract-path) (let* ((file-bytes (file-length in)) (seq (make-string file-bytes)) (file-chars (read-sequence seq in))) (subseq seq 0 file-chars)))))) ================================================ FILE: doc/cpp/CMakeLists.txt ================================================ # based on sample in https://majewsky.wordpress.com/2010/08/14/tip-of-the-day- # cmake-and-doxygen/ add a target to generate API documentation with Doxygen cmake_minimum_required(VERSION 3.3) find_package(Doxygen) if(DOXYGEN_FOUND) set(ROOTDIR "${CMAKE_CURRENT_SOURCE_DIR}/../..") set(DOTDIR "${CMAKE_CURRENT_SOURCE_DIR}/../dot") set(BUILDFILES_IN ../preprocmd.py DoxygenLayout.xml) foreach(_inf ${BUILDFILES_IN}) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/${_inf} ${CMAKE_CURRENT_BINARY_DIR}/${_inf} @ONLY ) endforeach() # Handle version initialization file(READ "${ROOTDIR}/version.txt" ver) string(REGEX MATCH "VERSION_MAJOR ([0-9]*)" _ ${ver}) set(GTIRB_MAJOR_VERSION ${CMAKE_MATCH_1}) string(REGEX MATCH "VERSION_MINOR ([0-9]*)" _ ${ver}) set(GTIRB_MINOR_VERSION ${CMAKE_MATCH_1}) string(REGEX MATCH "VERSION_PATCH ([0-9]*)" _ ${ver}) set(GTIRB_PATCH_VERSION ${CMAKE_MATCH_1}) configure_file( "${ROOTDIR}/include/gtirb/version.h.in" "${ROOTDIR}/include/gtirb/version.h" @ONLY ) configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in" "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile" @ONLY ) # adapted from https://gist.github.com/abravalheri/11214134 macro(move_filename file_list newdir) foreach(src_file ${${file_list}}) get_filename_component(src_file_name "${src_file}" NAME) list(REMOVE_ITEM ${file_list} "${src_file}") list(APPEND ${file_list} "${newdir}/${src_file_name}") endforeach() endmacro() # ---------------------------------------------------------------------- # copy dot files into a subdir of the working directory # ---------------------------------------------------------------------- file(GLOB DOTFILES_IN "${DOTDIR}/*") set(DOTFILES ${DOTFILES_IN}) move_filename(DOTFILES ${CMAKE_CURRENT_BINARY_DIR}) # message("DOTFILES = ${DOTFILES}") add_custom_command( OUTPUT ${DOTFILES} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${DOTFILES_IN} COMMAND mkdir -p dot COMMAND ${CMAKE_COMMAND} -E copy_directory ${DOTDIR} dot COMMENT "copying dot dir" VERBATIM ) # ---------------------------------------------------------------------- # copy md files into the working directory # ---------------------------------------------------------------------- set(MDFILES_IN "${CMAKE_CURRENT_SOURCE_DIR}/README.md") set(MDFILES ${MDFILES_IN}) move_filename(MDFILES ${CMAKE_CURRENT_BINARY_DIR}) # message("MDFILES = ${MDFILES}") gtirb_find_python() foreach(_inmd ${MDFILES_IN}) get_filename_component(_outmd "${_inmd}" NAME) add_custom_command( OUTPUT ${_outmd} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${_inmd} COMMAND ${CMAKE_COMMAND} -E copy ${_inmd} "${_outmd}.in" COMMAND ${PYTHON} ../preprocmd.py "${_outmd}.in" ${_outmd} COMMENT "processing ${_outmd}" VERBATIM ) endforeach() # ---------------------------------------------------------------------- # Main target # ---------------------------------------------------------------------- add_custom_target( cpp_doxyout WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${DOTFILES} DEPENDS ${MDFILES} COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile COMMENT "Generating C++ API documentation with Doxygen" VERBATIM ) add_dependencies(doc cpp_doxyout) endif(DOXYGEN_FOUND) ================================================ FILE: doc/cpp/Doxyfile.in ================================================ # Doxyfile 1.8.11 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all text # before the first occurrence of this tag. Doxygen uses libiconv (or the iconv # built into libc) for the transcoding. See http://www.gnu.org/software/libiconv # for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = GTIRB # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = v@GTIRB_MAJOR_VERSION@.@GTIRB_MINOR_VERSION@.@GTIRB_PATCH_VERSION@ # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = "GrammaTech Intermediate Representation for Binaries: C++ API" # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = @CMAKE_BINARY_DIR@/doc # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and # will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes # performance problems for the file system. # The default value is: NO. CREATE_SUBDIRS = NO # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, # Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), # Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, # Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, # Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, # Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, # Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = "The $name class" \ "The $name widget" \ "The $name file" \ is \ provides \ specifies \ contains \ represents \ a \ an \ the # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = NO # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = . # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the # first line (until the first dot) of a Javadoc-style comment as the brief # description. If set to NO, the Javadoc-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief # description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new # page for each member. If set to NO, the documentation of a member will be part # of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:\n" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines. ALIASES = # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding "class=itcl::class" # will allow you to use the command class in the itcl::class meaning. TCL_SUBST = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, Javascript, # C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: # FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: # Fortran. In the later case the parser tries to guess whether the code is fixed # or free formatted code, this is the default for Fortran type files), VHDL. For # instance to make doxygen treat .inc files as Fortran files (default is PHP), # and .f files as C (default is Fortran), use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See http://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or # globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = YES # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # If one adds a struct or class to a group and this option is enabled, then also # any nested class or struct is added to the same group. By default this option # is disabled and one has to add nested compounds explicitly via \ingroup. # The default value is: NO. GROUP_NESTED_COMPOUNDS = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = YES # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small # doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES, all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined # locally in source files will be included in the documentation. If set to NO, # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. If set to YES, local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO, only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = YES # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option # has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend # (class|struct|union) declarations. If set to NO, these declarations will be # included in the documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any # documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file # names in lower-case letters. If set to YES, upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. # The default value is: system dependent. CASE_SENSE_NAMES = NO # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will # append additional text to a page's title, such as Class Reference. If set to # YES the compound reference will be hidden. # The default value is: NO. HIDE_COMPOUND_REFERENCE= NO # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = YES # If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = YES # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = YES # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = YES # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = NO # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo # list. This list is created by putting \todo commands in the documentation. # The default value is: YES. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test # list. This list is created by putting \test commands in the documentation. # The default value is: YES. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES, the # list will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = YES # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error (stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = YES # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some parameters # in a documented function, or documenting parameters that don't exist or using # markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO, doxygen will only warn about wrong or incomplete # parameter documentation, but not about the absence of documentation. # The default value is: NO. WARN_NO_PARAMDOC = NO # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when # a warning is encountered. # The default value is: NO. WARN_AS_ERROR = NO # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). WARN_LOGFILE = doxygen_warnings.txt #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. INPUT = @CMAKE_CURRENT_SOURCE_DIR@/../../include @CMAKE_CURRENT_SOURCE_DIR@/../dot \ README.md # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: http://www.gnu.org/software/libiconv) for the list of # possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by doxygen. # # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, # *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f, *.for, *.tcl, # *.vhd, *.vhdl, *.ucf, *.qsf, *.as and *.js. FILE_PATTERNS = *.c \ *.cc \ *.cxx \ *.cpp \ *.c++ \ *.java \ *.ii \ *.ixx \ *.ipp \ *.i++ \ *.inl \ *.idl \ *.ddl \ *.odl \ *.h \ *.hh \ *.hxx \ *.hpp \ *.h++ \ *.cs \ *.d \ *.php \ *.php4 \ *.php5 \ *.phtml \ *.inc \ *.m \ *.markdown \ *.md \ *.mm \ *.dox \ *.py \ *.pyw \ *.f90 \ *.f \ *.for \ *.tcl \ *.vhd \ *.vhdl \ *.ucf \ *.qsf \ *.as \ *.js # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = */include/proto/*.h # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = proto # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = * # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. USE_MDFILE_AS_MAINPAGE = README.md #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # function all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see http://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the config file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = YES # If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the # clang parser (see: http://clang.llvm.org/) for more accurate parsing at the # cost of reduced performance. This can be particularly helpful with template # rich C++ code for which doxygen's built-in parser lacks the necessary type # information. # Note: The availability of this option depends on whether or not doxygen was # generated with the -Duse-libclang=ON option for CMake. # The default value is: NO. CLANG_ASSISTED_PARSING = NO # If clang assisted parsing is enabled you can provide the compiler with command # line options that you would normally use when invoking the compiler. Note that # the include paths will already be set by doxygen for the files and directories # specified with INPUT and INCLUDE_PATH. # This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. CLANG_OPTIONS = #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = YES # The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in # which the alphabetical index list will be split. # Minimum value: 1, maximum value: 20, default value: 5. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html/cpp # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see # http://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use grayscales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this # to YES can help to show when doxygen was last run and thus if the # documentation is up to date. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: http://developer.apple.com/tools/xcode/), introduced with # OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a # Makefile in the HTML output directory. Running make will produce the docset in # that directory and running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on # Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, # doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated # (YES) or that it should be included in the master .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated # (YES) or a normal table of contents (NO) in the .chm file. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- # folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location of Qt's # qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the # generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can # further fine-tune the look of the index. As an example, the default style # sheet generated by doxygen has an example that shows how to put an image at # the root of the tree instead of the PROJECT_NAME. Since the tree basically has # the same information as the tab index, you could consider setting # DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # # Note that when changing this option you need to delete any form_*.png files in # the HTML output directory before the changes have effect. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # http://www.mathjax.org) which uses client side Javascript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: # http://docs.mathjax.org/en/latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from http://www.mathjax.org before deployment. # The default value is: http://cdn.mathjax.org/mathjax/latest. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the javascript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /