diff --git a/.dockerignore b/.dockerignore
index 9560e2e41..1617fbf89 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,6 +1,6 @@
build
Dockerfile*
.travis.yml
-.git*
+.github*
LICENSE
README.md
diff --git a/.gitignore b/.gitignore
index 4abb0448e..a4faf9b0f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -69,3 +69,9 @@ package
# Ignore local binary ninja installations
binaryninja
+
+#ignore vscode dir
+.vscode
+
+#ignore generated version files
+VERSION
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 20ec7940f..74b5556f0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -96,6 +96,9 @@ set(REMILL_LLVM_VERSION "${LLVM_MAJOR_VERSION}.${LLVM_MINOR_VERSION}")
set(MCSEMA_LIFT mcsema-lift-${REMILL_LLVM_VERSION})
+# for version information
+add_subdirectory(mcsema/Version)
+
add_executable(${MCSEMA_LIFT}
${PROJECT_PROTOBUFSOURCEFILES}
@@ -200,7 +203,7 @@ list(APPEND PROJECT_LIBRARIES gflags)
# target settings
#
-target_link_libraries(${MCSEMA_LIFT} PRIVATE ${PROJECT_LIBRARIES})
+target_link_libraries(${MCSEMA_LIFT} PRIVATE ${PROJECT_LIBRARIES} McSemaVersion)
target_include_directories(${MCSEMA_LIFT} SYSTEM PUBLIC ${PROJECT_INCLUDEDIRECTORIES})
target_compile_definitions(${MCSEMA_LIFT} PUBLIC ${PROJECT_DEFINITIONS})
target_compile_options(${MCSEMA_LIFT} PRIVATE ${PROJECT_CXXFLAGS})
diff --git a/cmake/git_watcher.cmake b/cmake/git_watcher.cmake
new file mode 100644
index 000000000..ab4b23a3b
--- /dev/null
+++ b/cmake/git_watcher.cmake
@@ -0,0 +1,310 @@
+# git_watcher.cmake
+# https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/git_watcher.cmake
+#
+# Released under the MIT License.
+# https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/LICENSE
+
+
+# This file defines a target that monitors the state of a git repo.
+# If the state changes (e.g. a commit is made), then a file gets reconfigured.
+# Here are the primary variables that control script behavior:
+#
+# PRE_CONFIGURE_FILE (REQUIRED)
+# -- The path to the file that'll be configured.
+#
+# POST_CONFIGURE_FILE (REQUIRED)
+# -- The path to the configured PRE_CONFIGURE_FILE.
+#
+# GIT_STATE_FILE (OPTIONAL)
+# -- The path to the file used to store the previous build's git state.
+# Defaults to the current binary directory.
+#
+# GIT_WORKING_DIR (OPTIONAL)
+# -- The directory from which git commands will be run.
+# Defaults to the directory with the top level CMakeLists.txt.
+#
+# GIT_EXECUTABLE (OPTIONAL)
+# -- The path to the git executable. It'll automatically be set if the
+# user doesn't supply a path.
+#
+# DESIGN
+# - This script was designed similar to a Python application
+# with a Main() function. I wanted to keep it compact to
+# simplify "copy + paste" usage.
+#
+# - This script is invoked under two CMake contexts:
+# 1. Configure time (when build files are created).
+# 2. Build time (called via CMake -P).
+# The first invocation is what registers the script to
+# be executed at build time.
+#
+# MODIFICATIONS
+# You may wish to track other git properties like when the last
+# commit was made. There are two sections you need to modify,
+# and they're tagged with a ">>>" header.
+
+# Short hand for converting paths to absolute.
+macro(PATH_TO_ABSOLUTE var_name)
+ get_filename_component(${var_name} "${${var_name}}" ABSOLUTE)
+endmacro()
+
+# Check that a required variable is set.
+macro(CHECK_REQUIRED_VARIABLE var_name)
+ if(NOT DEFINED ${var_name})
+ message(FATAL_ERROR "The \"${var_name}\" variable must be defined.")
+ endif()
+ PATH_TO_ABSOLUTE(${var_name})
+endmacro()
+
+# Check that an optional variable is set, or, set it to a default value.
+macro(CHECK_OPTIONAL_VARIABLE var_name default_value)
+ if(NOT DEFINED ${var_name})
+ set(${var_name} ${default_value})
+ endif()
+ PATH_TO_ABSOLUTE(${var_name})
+endmacro()
+
+CHECK_REQUIRED_VARIABLE(PRE_CONFIGURE_FILE)
+CHECK_REQUIRED_VARIABLE(POST_CONFIGURE_FILE)
+CHECK_OPTIONAL_VARIABLE(GIT_STATE_FILE "${CMAKE_BINARY_DIR}/git-state-hash")
+CHECK_OPTIONAL_VARIABLE(GIT_WORKING_DIR "${CMAKE_SOURCE_DIR}")
+
+# Check the optional git variable.
+# If it's not set, we'll try to find it using the CMake packaging system.
+if(NOT DEFINED GIT_EXECUTABLE)
+ find_package(Git QUIET REQUIRED)
+endif()
+CHECK_REQUIRED_VARIABLE(GIT_EXECUTABLE)
+
+
+set(_state_variable_names
+ GIT_RETRIEVED_STATE
+ GIT_HEAD_SHA1
+ GIT_IS_DIRTY
+ GIT_AUTHOR_NAME
+ GIT_AUTHOR_EMAIL
+ GIT_COMMIT_DATE_ISO8601
+ GIT_COMMIT_SUBJECT
+ GIT_COMMIT_BODY
+ VERSION_STRING
+ # >>>
+ # 1. Add the name of the additional git variable you're interested in monitoring
+ # to this list.
+)
+
+
+
+# Macro: RunGitCommand
+# Description: short-hand macro for calling a git function. Outputs are the
+# "exit_code" and "output" variables.
+macro(RunGitCommand)
+ execute_process(COMMAND
+ "${GIT_EXECUTABLE}" ${ARGV}
+ WORKING_DIRECTORY "${_working_dir}"
+ RESULT_VARIABLE exit_code
+ OUTPUT_VARIABLE output
+ ERROR_QUIET
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if(NOT exit_code EQUAL 0)
+ set(ENV{GIT_RETRIEVED_STATE} "false")
+ endif()
+endmacro()
+
+
+
+# Function: GetGitState
+# Description: gets the current state of the git repo.
+# Args:
+# _working_dir (in) string; the directory from which git commands will be executed.
+function(GetGitState _working_dir)
+
+ # This is an error code that'll be set to FALSE if the
+ # RunGitCommand ever returns a non-zero exit code.
+ set(ENV{GIT_RETRIEVED_STATE} "true")
+
+ # Get whether or not the working tree is dirty.
+ RunGitCommand(status --porcelain)
+ if(NOT exit_code EQUAL 0)
+ set(ENV{GIT_IS_DIRTY} "false")
+ else()
+ if(NOT "${output}" STREQUAL "")
+ set(ENV{GIT_IS_DIRTY} "true")
+ else()
+ set(ENV{GIT_IS_DIRTY} "false")
+ endif()
+ endif()
+
+ # There's a long list of attributes grabbed from git show.
+ set(object HEAD)
+ RunGitCommand(show -s "--format=%H" ${object})
+ if(exit_code EQUAL 0)
+ set(ENV{GIT_HEAD_SHA1} ${output})
+ endif()
+
+ RunGitCommand(show -s "--format=%an" ${object})
+ if(exit_code EQUAL 0)
+ set(ENV{GIT_AUTHOR_NAME} "${output}")
+ endif()
+
+ RunGitCommand(show -s "--format=%ae" ${object})
+ if(exit_code EQUAL 0)
+ set(ENV{GIT_AUTHOR_EMAIL} "${output}")
+ endif()
+
+ RunGitCommand(show -s "--format=%ci" ${object})
+ if(exit_code EQUAL 0)
+ set(ENV{GIT_COMMIT_DATE_ISO8601} "${output}")
+ endif()
+
+ RunGitCommand(show -s "--format=%s" ${object})
+ if(exit_code EQUAL 0)
+ set(ENV{GIT_COMMIT_SUBJECT} "${output}")
+ endif()
+
+ RunGitCommand(show -s "--format=%b" ${object})
+ if(exit_code EQUAL 0)
+ if(output)
+ # Escape line breaks in the commit message.
+ string(REPLACE "\r\n" "\\r\\n\\\r\n" safe ${output})
+ if(safe STREQUAL output)
+ # Didn't have windows lines - try unix lines.
+ string(REPLACE "\n" "\\n\\\n" safe ${output})
+ endif()
+ else()
+ # There was no commit body - set the safe string to empty.
+ set(safe "")
+ endif()
+ set(ENV{GIT_COMMIT_BODY} "\"${safe}\"")
+ else()
+ set(ENV{GIT_COMMIT_BODY} "\"\"") # empty string.
+ endif()
+
+ # >>>
+ # 2. Additional git properties can be added here via the
+ # "execute_process()" command. Be sure to set them in
+ # the environment using the same variable name you added
+ # to the "_state_variable_names" list.
+
+ if(EXISTS "${GIT_WORKING_DIR}/VERSION")
+ file(READ "${GIT_WORKING_DIR}/VERSION" version_output_raw)
+ string(STRIP "${version_output_raw}" version_output)
+ set(ENV{VERSION_STRING} "${version_output}")
+ else()
+ set(ENV{VERSION_STRING} "")
+ endif()
+
+endfunction()
+
+
+
+# Function: GitStateChangedAction
+# Description: this function is executed when the state of the git
+# repository changes (e.g. a commit is made).
+function(GitStateChangedAction)
+ foreach(var_name ${_state_variable_names})
+ set(${var_name} $ENV{${var_name}})
+ endforeach()
+ configure_file("${PRE_CONFIGURE_FILE}" "${POST_CONFIGURE_FILE}" @ONLY)
+endfunction()
+
+
+
+# Function: HashGitState
+# Description: loop through the git state variables and compute a unique hash.
+# Args:
+# _state (out) string; a hash computed from the current git state.
+function(HashGitState _state)
+ set(ans "")
+ foreach(var_name ${_state_variable_names})
+ string(SHA256 ans "${ans}$ENV{${var_name}}")
+ endforeach()
+ set(${_state} ${ans} PARENT_SCOPE)
+endfunction()
+
+
+
+# Function: CheckGit
+# Description: check if the git repo has changed. If so, update the state file.
+# Args:
+# _working_dir (in) string; the directory from which git commands will be ran.
+# _state_changed (out) bool; whether or no the state of the repo has changed.
+function(CheckGit _working_dir _state_changed)
+
+ # Get the current state of the repo.
+ GetGitState("${_working_dir}")
+
+ # Convert that state into a hash that we can compare against
+ # the hash stored on-disk.
+ HashGitState(state)
+
+ # Issue 14: post-configure file isn't being regenerated.
+ #
+ # Update the state to include the SHA256 for the pre-configure file.
+ # This forces the post-configure file to be regenerated if the
+ # pre-configure file has changed.
+ file(SHA256 ${PRE_CONFIGURE_FILE} preconfig_hash)
+ string(SHA256 state "${preconfig_hash}${state}")
+
+ # Check if the state has changed compared to the backup on disk.
+ if(EXISTS "${GIT_STATE_FILE}")
+ file(READ "${GIT_STATE_FILE}" OLD_HEAD_CONTENTS)
+ if(OLD_HEAD_CONTENTS STREQUAL "${state}")
+ # State didn't change.
+ set(${_state_changed} "false" PARENT_SCOPE)
+ return()
+ endif()
+ endif()
+
+ # The state has changed.
+ # We need to update the state file on disk.
+ # Future builds will compare their state to this file.
+ file(WRITE "${GIT_STATE_FILE}" "${state}")
+ set(${_state_changed} "true" PARENT_SCOPE)
+endfunction()
+
+
+
+# Function: SetupGitMonitoring
+# Description: this function sets up custom commands that make the build system
+# check the state of git before every build. If the state has
+# changed, then a file is configured.
+function(SetupGitMonitoring)
+ add_custom_target(check_git
+ ALL
+ DEPENDS ${PRE_CONFIGURE_FILE}
+ BYPRODUCTS
+ ${POST_CONFIGURE_FILE}
+ ${GIT_STATE_FILE}
+ COMMENT "Checking the git repository for changes..."
+ COMMAND
+ ${CMAKE_COMMAND}
+ -D_BUILD_TIME_CHECK_GIT=TRUE
+ -DGIT_WORKING_DIR=${GIT_WORKING_DIR}
+ -DGIT_EXECUTABLE=${GIT_EXECUTABLE}
+ -DGIT_STATE_FILE=${GIT_STATE_FILE}
+ -DPRE_CONFIGURE_FILE=${PRE_CONFIGURE_FILE}
+ -DPOST_CONFIGURE_FILE=${POST_CONFIGURE_FILE}
+ -P "${CMAKE_CURRENT_LIST_FILE}")
+endfunction()
+
+
+
+# Function: Main
+# Description: primary entry-point to the script. Functions are selected based
+# on whether it's configure or build time.
+function(Main)
+ if(_BUILD_TIME_CHECK_GIT)
+ # Check if the repo has changed.
+ # If so, run the change action.
+ CheckGit("${GIT_WORKING_DIR}" changed)
+ if(changed OR NOT EXISTS "${POST_CONFIGURE_FILE}")
+ GitStateChangedAction()
+ endif()
+ else()
+ # >> Executes at configure time.
+ SetupGitMonitoring()
+ endif()
+endfunction()
+
+# And off we go...
+Main()
diff --git a/mcsema/Version.h.in b/mcsema/Version.h.in
deleted file mode 100644
index 522446e16..000000000
--- a/mcsema/Version.h.in
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (c) 2020 Trail of Bits, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-#define MCSEMA_VERSION_STRING "@MCSEMA_VERSION_STRING@"
-#define MCSEMA_BRANCH_NAME "@MCSEMA_BRANCH_NAME@"
diff --git a/mcsema/Version/CMakeLists.txt b/mcsema/Version/CMakeLists.txt
new file mode 100644
index 000000000..2554b4259
--- /dev/null
+++ b/mcsema/Version/CMakeLists.txt
@@ -0,0 +1,16 @@
+# Based on: https://github.com/andrew-hardin/cmake-git-version-tracking/blob/master/better-example/CMakeLists.txt
+# By Andrew Hardin
+# Released under the MIT License.
+# https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/LICENSE
+#
+# Define the two required variables before including
+# the source code for watching a git repository.
+set(PRE_CONFIGURE_FILE "Version.cpp.in")
+set(POST_CONFIGURE_FILE "${CMAKE_CURRENT_BINARY_DIR}/Version.cpp")
+set(GIT_WORKING_DIR "${MCSEMA_SOURCE_DIR}")
+include("${MCSEMA_SOURCE_DIR}/cmake/git_watcher.cmake")
+
+# Create a library out of the compiled post-configure file.
+add_library(McSemaVersion STATIC ${POST_CONFIGURE_FILE})
+target_include_directories(McSemaVersion PUBLIC ${MCSEMA_SOURCE_DIR})
+add_dependencies(McSemaVersion check_git)
diff --git a/mcsema/Version/Version.cpp.in b/mcsema/Version/Version.cpp.in
new file mode 100644
index 000000000..e91333479
--- /dev/null
+++ b/mcsema/Version/Version.cpp.in
@@ -0,0 +1,61 @@
+// Copyright (C) 2020 Trail of Bits
+// Based on: https://github.com/andrew-hardin/cmake-git-version-tracking/blob/master/better-example/git.cc.in
+// Which is (C) 2020 Andrew Hardin
+//
+// MIT License
+// Copyright (c) 2020 Andrew Hardin
+//
+// 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.
+//
+#include "mcsema/Version/Version.h"
+
+namespace mcsema {
+namespace Version {
+
+ bool HasVersionData() {
+ return @GIT_RETRIEVED_STATE@;
+ }
+ bool HasUncommittedChanges() {
+ return @GIT_IS_DIRTY@;
+ }
+ std::string GetAuthorName() {
+ return "@GIT_AUTHOR_NAME@";
+ }
+ std::string GetAuthorEmail() {
+ return "@GIT_AUTHOR_EMAIL@";
+ }
+ std::string GetCommitHash() {
+ return "@GIT_HEAD_SHA1@";
+ }
+ std::string GetCommitDate() {
+ return "@GIT_COMMIT_DATE_ISO8601@";
+ }
+ std::string GetCommitSubject() {
+ return "@GIT_COMMIT_SUBJECT@";
+ }
+ std::string GetCommitBody() {
+ return @GIT_COMMIT_BODY@;
+ }
+ std::string GetVersionString() {
+ return "@VERSION_STRING@";
+ }
+
+
+} // namespace Version
+} // namespace mcsema
diff --git a/mcsema/Version/Version.h b/mcsema/Version/Version.h
new file mode 100644
index 000000000..d4e481cda
--- /dev/null
+++ b/mcsema/Version/Version.h
@@ -0,0 +1,44 @@
+#pragma once
+// Copyright (C) 2020 Trail of Bits
+// Based on: https://github.com/andrew-hardin/cmake-git-version-tracking/blob/master/better-example/git.h
+// Which is (C) 2020 Andrew Hardin
+//
+// MIT License
+// Copyright (c) 2020 Andrew Hardin
+//
+// 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.
+//
+
+#include
+
+namespace mcsema {
+namespace Version {
+
+bool HasVersionData();
+bool HasUncommittedChanges();
+std::string GetAuthorName();
+std::string GetAuthorEmail();
+std::string GetCommitHash();
+std::string GetCommitDate();
+std::string GetCommitSubject();
+std::string GetCommitBody();
+std::string GetVersionString();
+
+} // namespace Version
+} // namespace mcsema
\ No newline at end of file
diff --git a/tools/mcsema_lift/Lift.cpp b/tools/mcsema_lift/Lift.cpp
index 5e2617fee..cee47a84c 100644
--- a/tools/mcsema_lift/Lift.cpp
+++ b/tools/mcsema_lift/Lift.cpp
@@ -24,6 +24,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -42,14 +43,6 @@
# define LLVM_VERSION_STRING LLVM_VERSION_MAJOR << "." << LLVM_VERSION_MINOR
#endif
-#ifndef MCSEMA_VERSION_STRING
-# define MCSEMA_VERSION_STRING "unknown"
-#endif // MCSEMA_VERSION_STRING
-
-#ifndef MCSEMA_BRANCH_NAME
-# define MCSEMA_BRANCH_NAME "unknown"
-#endif // MCSEMA_BRANCH_NAME
-
DECLARE_string(arch);
DECLARE_string(os);
@@ -82,11 +75,29 @@ DEFINE_bool(legacy_mode, false,
namespace {
-static void PrintVersion(void) {
- std::cout << "This is mcsema-lift version: " << MCSEMA_VERSION_STRING
- << std::endl
- << "Built from branch: " << MCSEMA_BRANCH_NAME << std::endl
- << "Using LLVM " << LLVM_VERSION_STRING << std::endl;
+static void SetVersion(void) {
+ std::stringstream ss;
+ auto vs = mcsema::Version::GetVersionString();
+ if (0 == vs.size()) {
+ vs = "unknown";
+ }
+ ss << vs << "\n";
+ if (!mcsema::Version::HasVersionData()) {
+ ss << "No extended version information found!\n";
+ } else {
+ ss << "Commit Hash: " << mcsema::Version::GetCommitHash() << "\n";
+ ss << "Commit Date: " << mcsema::Version::GetCommitDate() << "\n";
+ ss << "Last commit by: " << mcsema::Version::GetAuthorName() << " ["
+ << mcsema::Version::GetAuthorEmail() << "]\n";
+ ss << "Commit Subject: [" << mcsema::Version::GetCommitSubject() << "]\n";
+ ss << "\n";
+ if (mcsema::Version::HasUncommittedChanges()) {
+ ss << "Uncommitted changes were present during build.\n";
+ } else {
+ ss << "All changes were committed prior to building.\n";
+ }
+ }
+ google::SetVersionString(ss.str());
}
// Print a list of instructions that Remill can lift.
@@ -643,6 +654,8 @@ int main(int argc, char *argv[]) {
llvm::cl::ParseCommandLineOptions(1, llvm_argv);
google::SetUsageMessage(ss.str());
+
+ SetVersion();
google::ParseCommandLineFlags(&argc, &argv, true);
if (FLAGS_log.empty()) {
@@ -653,11 +666,6 @@ int main(int argc, char *argv[]) {
FLAGS_minloglevel = FLAGS_loglevel;
- if (FLAGS_version) {
- PrintVersion();
- return EXIT_SUCCESS;
- }
-
if (FLAGS_os.empty() || FLAGS_arch.empty() || FLAGS_cfg.empty()) {
std::cout << google::ProgramUsage() << std::endl;
return EXIT_FAILURE;