├── bin ├── magnifier-ui │ ├── www │ │ ├── config.js │ │ ├── static │ │ │ └── favicon.ico │ │ ├── .editorconfig │ │ ├── jsconfig.json │ │ ├── tailwind.config.js │ │ ├── .eslintrc.js │ │ ├── components │ │ │ ├── FunctionList.vue │ │ │ ├── GlComponent.vue │ │ │ ├── Upload.vue │ │ │ ├── Terminal.vue │ │ │ ├── IRView.vue │ │ │ ├── CodeView.vue │ │ │ ├── CommandView.vue │ │ │ └── Glayout.vue │ │ ├── assets │ │ │ ├── css │ │ │ │ └── main.css │ │ │ └── layout │ │ │ │ └── config.json │ │ ├── package.json │ │ ├── pages │ │ │ └── index.vue │ │ ├── .gitignore │ │ ├── nuxt.config.js │ │ ├── plugins │ │ │ └── socket.js │ │ └── store │ │ │ └── index.js │ ├── Printer.h │ ├── README.md │ └── main.cpp └── repl │ └── main.cpp ├── .gitignore ├── lib ├── ISubstitutionObserver.cpp ├── IdCommentWriter.h ├── IdCommentWriter.cpp └── BitcodeExplorer.cpp ├── cmake ├── options.cmake ├── llvm.cmake ├── vcpkg_helper.cmake └── FindFilesystem.cmake ├── magnifierConfig.cmake.in ├── include └── magnifier │ ├── IFunctionResolver.h │ ├── ISubstitutionObserver.h │ ├── Result.h │ └── BitcodeExplorer.h ├── scripts ├── fetch-cxx-common.sh └── build-preset.sh ├── README.md ├── .github └── workflows │ └── ci.yml ├── CMakeLists.txt ├── CMakePresets.json └── LICENSE /bin/magnifier-ui/www/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | webSocketAddr: 'ws://localhost:9001/ws' 3 | } 4 | -------------------------------------------------------------------------------- /bin/magnifier-ui/www/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trailofbits/magnifier/HEAD/bin/magnifier-ui/www/static/favicon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *build*/ 3 | CMakeFiles/ 4 | downloads/ 5 | 6 | *.bc 7 | .DS_Store 8 | 9 | CMakeCache.txt 10 | cmake_install.cmake 11 | Makefile 12 | *.log 13 | -------------------------------------------------------------------------------- /bin/magnifier-ui/www/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /bin/magnifier-ui/www/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "~/*": ["./*"], 6 | "@/*": ["./*"], 7 | "~~/*": ["./*"], 8 | "@@/*": ["./*"] 9 | } 10 | }, 11 | "exclude": ["node_modules", ".nuxt", "dist"] 12 | } 13 | -------------------------------------------------------------------------------- /bin/magnifier-ui/www/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: [ 3 | './components/**/*.{js,vue,ts}', 4 | './layouts/**/*.vue', 5 | './pages/**/*.vue', 6 | './plugins/**/*.{js,ts}', 7 | './nuxt.config.{js,ts}' 8 | ], 9 | theme: { 10 | extend: {} 11 | }, 12 | plugins: [] 13 | } 14 | -------------------------------------------------------------------------------- /lib/ISubstitutionObserver.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace magnifier { 4 | 5 | llvm::Value *NullSubstitutionObserver::PerformSubstitution( 6 | llvm::Instruction *instr, llvm::Value *old_val, llvm::Value *new_val, 7 | SubstitutionKind kind) { 8 | return new_val; 9 | } 10 | 11 | } // namespace magnifier -------------------------------------------------------------------------------- /bin/magnifier-ui/www/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true 6 | }, 7 | parserOptions: { 8 | parser: '@babel/eslint-parser', 9 | requireConfigFile: false 10 | }, 11 | extends: [ 12 | '@nuxtjs', 13 | 'plugin:nuxt/recommended' 14 | ], 15 | plugins: [ 16 | ], 17 | // add your custom rules here 18 | rules: {} 19 | } 20 | -------------------------------------------------------------------------------- /cmake/options.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2021-present, Trail of Bits, Inc. 3 | # All rights reserved. 4 | # 5 | # This source code is licensed in accordance with the terms specified in 6 | # the LICENSE file found in the root directory of this source tree. 7 | # 8 | 9 | 10 | # test options 11 | option(RELLIC_ENABLE_TESTING "Enable Test Builds" ON) 12 | option(RELLIC_ENABLE_INSTALL "Set to true to enable the install target" ON) 13 | -------------------------------------------------------------------------------- /magnifierConfig.cmake.in: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2019-present, Trail of Bits, Inc. 3 | # All rights reserved. 4 | # 5 | # This source code is licensed in accordance with the terms specified in 6 | # the LICENSE file found in the root directory of this source tree. 7 | # 8 | 9 | @PACKAGE_INIT@ 10 | 11 | cmake_minimum_required(VERSION 3.16) 12 | 13 | if(NOT TARGET magnifier) 14 | 15 | include(CMakeFindDependencyMacro) 16 | find_dependency(LLVM) 17 | # Exported Targets 18 | include("${CMAKE_CURRENT_LIST_DIR}/magnifierTargets.cmake") 19 | 20 | endif() -------------------------------------------------------------------------------- /bin/magnifier-ui/www/components/FunctionList.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 17 | -------------------------------------------------------------------------------- /include/magnifier/IFunctionResolver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-present, Trail of Bits, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed in accordance with the terms specified in 6 | * the LICENSE file found in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | namespace llvm { 12 | class Function; 13 | class CallBase; 14 | } 15 | 16 | namespace magnifier { 17 | 18 | class IFunctionResolver { 19 | public: 20 | virtual llvm::Function *ResolveCallSite(llvm::CallBase *call_base, llvm::Function *called_function) = 0; 21 | }; 22 | 23 | } -------------------------------------------------------------------------------- /bin/magnifier-ui/www/assets/css/main.css: -------------------------------------------------------------------------------- 1 | @import "golden-layout/dist/css/goldenlayout-base.css"; 2 | @import "golden-layout/dist/css/themes/goldenlayout-dark-theme.css"; 3 | 4 | @tailwind base; 5 | @tailwind components; 6 | @tailwind utilities; 7 | 8 | #main-window { 9 | color: #fff; 10 | font-family: "Fira Code"; 11 | font-weight: 500; 12 | font-size: 14px; 13 | } 14 | 15 | .hover { 16 | background-color: rgb(167 243 208); 17 | color: #000; 18 | } 19 | 20 | .main-label { 21 | color: #b4dfff; 22 | font-weight: 500; 23 | } 24 | 25 | .sub-window { 26 | background: #2f4858; 27 | 28 | 29 | } 30 | 31 | .active { 32 | background-color: #68D391; 33 | } 34 | 35 | .selected { 36 | background-color: #b4dfff; 37 | color: #000; 38 | } 39 | 40 | .highlight-color { 41 | background-color: #b4dfff; 42 | color: #000; 43 | } -------------------------------------------------------------------------------- /bin/magnifier-ui/www/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "magnifier-frontend", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "nuxt", 7 | "build": "nuxt build", 8 | "start": "nuxt start", 9 | "generate": "nuxt generate", 10 | "lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .", 11 | "lint": "npm run lint:js" 12 | }, 13 | "dependencies": { 14 | "@nuxtjs/axios": "^5.13.6", 15 | "@vue/composition-api": "^1.4.9", 16 | "core-js": "^3.15.1", 17 | "golden-layout": "^2.5.0", 18 | "nuxt": "^2.15.7" 19 | }, 20 | "devDependencies": { 21 | "@babel/eslint-parser": "^7.14.7", 22 | "@nuxt/postcss8": "^1.1.3", 23 | "@nuxtjs/eslint-config": "^6.0.1", 24 | "@nuxtjs/eslint-module": "^3.0.2", 25 | "autoprefixer": "^10.4.4", 26 | "eslint": "^7.29.0", 27 | "eslint-plugin-nuxt": "^2.0.0", 28 | "eslint-plugin-vue": "^7.12.1", 29 | "postcss": "^8.4.12", 30 | "tailwindcss": "^3.0.23" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /bin/magnifier-ui/www/components/GlComponent.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 45 | -------------------------------------------------------------------------------- /lib/IdCommentWriter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-present, Trail of Bits, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed in accordance with the terms specified in 6 | * the LICENSE file found in the root directory of this source tree. 7 | */ 8 | 9 | 10 | #pragma once 11 | 12 | #include 13 | 14 | namespace llvm { 15 | class Instruction; 16 | class formatted_raw_ostream; 17 | class Function; 18 | } // namespace llvm 19 | 20 | namespace magnifier { 21 | class BitcodeExplorer; 22 | class IdCommentWriter : public llvm::AssemblyAnnotationWriter { 23 | private: 24 | BitcodeExplorer &explorer; 25 | public: 26 | explicit IdCommentWriter(BitcodeExplorer &explorer); 27 | 28 | void emitInstructionAnnot(const llvm::Instruction *instruction, llvm::formatted_raw_ostream &os) override; 29 | 30 | void emitFunctionAnnot(const llvm::Function *function, llvm::formatted_raw_ostream &os) override; 31 | 32 | void emitBasicBlockStartAnnot(const llvm::BasicBlock *block, llvm::formatted_raw_ostream &os) override; 33 | 34 | void emitBasicBlockEndAnnot(const llvm::BasicBlock *block, llvm::formatted_raw_ostream &os) override; 35 | }; 36 | } -------------------------------------------------------------------------------- /cmake/llvm.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2021-present, Trail of Bits, Inc. 3 | # All rights reserved. 4 | # 5 | # This source code is licensed in accordance with the terms specified in 6 | # the LICENSE file found in the root directory of this source tree. 7 | # 8 | 9 | function(find_llvm target_name) 10 | 11 | find_package(LLVM CONFIG REQUIRED) 12 | 13 | add_library("${target_name}" INTERFACE) 14 | target_include_directories("${target_name}" SYSTEM INTERFACE 15 | $ 16 | ) 17 | target_compile_definitions("${target_name}" INTERFACE 18 | ${LLVM_DEFINITIONS} 19 | ) 20 | 21 | # Go find only the static libraries of LLVM, and link against those. 22 | foreach(LLVM_LIB IN LISTS LLVM_AVAILABLE_LIBS) 23 | get_target_property(LLVM_LIB_TYPE ${LLVM_LIB} TYPE) 24 | if(LLVM_LIB_TYPE STREQUAL "STATIC_LIBRARY") 25 | list(APPEND LLVM_LIBRARIES "${LLVM_LIB}") 26 | endif() 27 | endforeach() 28 | 29 | # These are out-of-order in `LLVM_AVAILABLE_LIBS` and should always be last. 30 | list(REMOVE_ITEM LLVM_LIBRARIES LLVMMC LLVMCore LLVMSupport) 31 | list(APPEND LLVM_LIBRARIES LLVMMC LLVMCore LLVMSupport) 32 | 33 | target_link_libraries("${target_name}" INTERFACE 34 | ${LLVM_LIBRARIES} 35 | ) 36 | 37 | endfunction(find_llvm) -------------------------------------------------------------------------------- /include/magnifier/ISubstitutionObserver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-present, Trail of Bits, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed in accordance with the terms specified in 6 | * the LICENSE file found in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | namespace llvm { 12 | class Value; 13 | class Instruction; 14 | } // namespace llvm 15 | 16 | namespace magnifier { 17 | 18 | enum class SubstitutionKind { 19 | kReturnValue = 1, 20 | kArgument, 21 | kConstantFolding, 22 | kValueSubstitution, 23 | kFunctionDevirtualization, 24 | }; 25 | 26 | class ISubstitutionObserver { 27 | public: 28 | virtual llvm::Value *PerformSubstitution(llvm::Instruction *instr, 29 | llvm::Value *old_val, 30 | llvm::Value *new_val, 31 | SubstitutionKind kind) = 0; 32 | }; 33 | 34 | class NullSubstitutionObserver : public ISubstitutionObserver { 35 | virtual llvm::Value *PerformSubstitution(llvm::Instruction *instr, 36 | llvm::Value *old_val, 37 | llvm::Value *new_val, 38 | SubstitutionKind kind) override; 39 | }; 40 | 41 | } // namespace magnifier -------------------------------------------------------------------------------- /bin/magnifier-ui/www/components/Upload.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 56 | -------------------------------------------------------------------------------- /bin/magnifier-ui/www/pages/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 36 | 37 | 58 | -------------------------------------------------------------------------------- /bin/magnifier-ui/www/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | /logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # parcel-bundler cache (https://parceljs.org/) 63 | .cache 64 | 65 | # next.js build output 66 | .next 67 | 68 | # nuxt.js build output 69 | .nuxt 70 | 71 | # Nuxt generate 72 | dist 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless 79 | 80 | # IDE / Editor 81 | .idea 82 | 83 | # Service worker 84 | sw.* 85 | 86 | # macOS 87 | .DS_Store 88 | .dccache 89 | 90 | # Vim swap files 91 | *.swp 92 | -------------------------------------------------------------------------------- /bin/magnifier-ui/www/components/Terminal.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 51 | 52 | 66 | -------------------------------------------------------------------------------- /bin/magnifier-ui/www/nuxt.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // Target: https://go.nuxtjs.dev/config-target 3 | target: 'static', 4 | 5 | // Global page headers: https://go.nuxtjs.dev/config-head 6 | head: { 7 | title: 'magnifier-frontend', 8 | htmlAttrs: { 9 | lang: 'en' 10 | }, 11 | meta: [ 12 | { charset: 'utf-8' }, 13 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 14 | { hid: 'description', name: 'description', content: '' }, 15 | { name: 'format-detection', content: 'telephone=no' } 16 | ], 17 | link: [ 18 | { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, 19 | { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500;600;700;800;900&display=swap' } 20 | ] 21 | }, 22 | 23 | // Global CSS: https://go.nuxtjs.dev/config-css 24 | css: [ 25 | '@/assets/css/main.css' 26 | ], 27 | 28 | // Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins 29 | plugins: [ 30 | 'plugins/socket.js' 31 | ], 32 | 33 | // Auto import components: https://go.nuxtjs.dev/config-components 34 | components: true, 35 | 36 | // Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules 37 | buildModules: [ 38 | // https://go.nuxtjs.dev/eslint 39 | '@nuxtjs/eslint-module', 40 | '@nuxt/postcss8' 41 | ], 42 | 43 | // Modules: https://go.nuxtjs.dev/config-modules 44 | modules: [ 45 | // https://go.nuxtjs.dev/axios 46 | '@nuxtjs/axios' 47 | ], 48 | 49 | // Axios module configuration: https://go.nuxtjs.dev/config-axios 50 | axios: {}, 51 | 52 | // Build Configuration: https://go.nuxtjs.dev/config-build 53 | build: { 54 | postcss: { 55 | plugins: { 56 | tailwindcss: {}, 57 | autoprefixer: {} 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /bin/magnifier-ui/www/plugins/socket.js: -------------------------------------------------------------------------------- 1 | import config from '~/config' 2 | 3 | export default ({ app }, inject) => { 4 | let socket 5 | 6 | inject('socket', { 7 | init () { 8 | this._socket = new WebSocket(config.webSocketAddr) 9 | // Connection opened 10 | this._socket.addEventListener('open', function (event) { 11 | console.log('socket opened!') 12 | this.connectionResolvers.forEach(r => r.resolve()) 13 | }.bind(this)) 14 | 15 | // Listen for messages 16 | this._socket.addEventListener('message', function (event) { 17 | const data = JSON.parse(event.data) 18 | // console.log(data) 19 | 20 | if (data.id && this.receiveResolvers[data.id]) { 21 | this.receiveResolvers[data.id].resolve(data) 22 | delete this.receiveResolvers[data.id] 23 | return 24 | } 25 | 26 | if (data.message) { 27 | console.log('Message from server ', data.message) 28 | return 29 | } 30 | app.store.dispatch('parseWsData', data) 31 | }.bind(this)) 32 | }, 33 | 34 | checkConnection () { 35 | return new Promise((resolve, reject) => { 36 | if (this._socket.readyState === WebSocket.OPEN) { 37 | resolve() 38 | } else { 39 | this.connectionResolvers.push({ resolve, reject }) 40 | } 41 | }) 42 | }, 43 | 44 | async send (payload) { 45 | await this.checkConnection() 46 | const id = this.packetId 47 | this.packetId += 1 48 | 49 | this._socket.send(JSON.stringify({ 50 | id, 51 | ...payload 52 | })) 53 | 54 | return new Promise((resolve, reject) => { 55 | this.receiveResolvers[id] = { resolve, reject } 56 | }) 57 | }, 58 | 59 | _socket: socket, 60 | connectionResolvers: [], 61 | receiveResolvers: {}, 62 | packetId: 1 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /scripts/fetch-cxx-common.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eE 3 | set -uo pipefail 4 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 5 | ROOT_DIR="$(realpath ${DIR}/..)" 6 | LOG_FILE=${ROOT_DIR}/cxx-common.log 7 | PLATFORM=$(uname -m) 8 | LLVM=${LLVM:-14} 9 | 10 | function log_msg { 11 | echo "${1}" 12 | echo "${1}" >> "${LOG_FILE}" 13 | } 14 | 15 | function setup_env { 16 | if [[ "${PLATFORM}" == "x86_64" ]] 17 | then 18 | export ARCH=amd64 19 | elif [[ "${PLATFORM}" == "arm64" || "${PLATFORM}" == "aarch64" ]] 20 | then 21 | export ARCH=arm64 22 | else 23 | log_msg "Unsupported platform: ${PLATFORM}!" 24 | exit 1 25 | fi 26 | 27 | if [[ "${OSTYPE}" == "darwin"* ]] 28 | then 29 | XCODE="_xcode-13.0"; 30 | OS="macos-11" 31 | elif [[ ${OSTYPE} == "linux"* ]] 32 | then 33 | XCODE="" 34 | OS="ubuntu-20.04" 35 | else 36 | log_msg "Unsupported OS: ${OSTYPE}" 37 | exit 1 38 | fi 39 | 40 | export CXX_COMMON_NAME=vcpkg_${OS}_llvm-${LLVM}${XCODE}_${ARCH} 41 | 42 | log_msg "Arch: ${ARCH}" 43 | log_msg "CXX_COMMON_NAME: ${CXX_COMMON_NAME}" 44 | } 45 | 46 | function install_cxx_common { 47 | local base_dir=${1} 48 | if [[ ! -f "${base_dir}/cxx-common.tar.xz" ]] 49 | then 50 | log_msg "Fetching cxx-common from Github" 51 | URL="https://github.com/lifting-bits/cxx-common/releases/latest/download/${CXX_COMMON_NAME}.tar.xz" 52 | GITHUB_LIBS="cxx-common.tar.xz" 53 | pushd "${base_dir}" >/dev/null 54 | log_msg "Fetching: ${URL} to [$(pwd)/${GITHUB_LIBS}]" 55 | curl -sS -o "${GITHUB_LIBS}" -L "${URL}" \ 56 | >> ${LOG_FILE} 2>&1 57 | tar -xJf "${GITHUB_LIBS}" 58 | # do not archive cxx-common zip for export mode 59 | popd >/dev/null 60 | fi 61 | 62 | export VCPKG_ROOT="${base_dir}/${CXX_COMMON_NAME}" 63 | } 64 | 65 | setup_env 66 | mkdir -p "${ROOT_DIR}/../downloads" 67 | install_cxx_common "${ROOT_DIR}/../downloads" 68 | -------------------------------------------------------------------------------- /bin/magnifier-ui/Printer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022-present, Trail of Bits, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed in accordance with the terms specified in 6 | * the LICENSE file found in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | void PrintDecl(clang::Decl* Decl, const clang::PrintingPolicy& Policy, 20 | int Indentation, llvm::raw_ostream& Out); 21 | void PrintDeclGroup(clang::Decl** Begin, unsigned NumDecls, 22 | llvm::raw_ostream& Out, const clang::PrintingPolicy& Policy, 23 | unsigned Indentation); 24 | void PrintStmt(clang::Stmt* Stmt, llvm::raw_ostream& Out, 25 | const clang::PrintingPolicy& Policy, int Indentation = 0, 26 | const clang::ASTContext* Context = nullptr, 27 | clang::PrinterHelper* Helper = nullptr); 28 | void PrintType(clang::QualType Type, llvm::raw_ostream& Out, 29 | const clang::PrintingPolicy& Policy, 30 | const llvm::Twine& PlaceHolder = llvm::Twine(), 31 | unsigned Indentation = 0); 32 | std::string GetQualTypeAsString(clang::QualType Type, 33 | const clang::PrintingPolicy& Policy); 34 | std::string GetTypeAsString(const clang::Type* Ty, 35 | const clang::PrintingPolicy& Policy); 36 | void PrintQualifiers(const clang::Qualifiers& Qualifiers, llvm::raw_ostream& OS, 37 | const clang::PrintingPolicy& Policy, 38 | bool appendSpaceIfNonEmpty = false); 39 | std::string GetQualifiersAsString(const clang::Qualifiers& Qualifiers); 40 | std::string GetQualifiersAsString(const clang::Qualifiers& Qualifiers, 41 | const clang::PrintingPolicy& Policy); -------------------------------------------------------------------------------- /bin/magnifier-ui/www/components/IRView.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 55 | -------------------------------------------------------------------------------- /bin/magnifier-ui/www/components/CodeView.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 59 | -------------------------------------------------------------------------------- /bin/magnifier-ui/README.md: -------------------------------------------------------------------------------- 1 | # MagnifierUI 2 | 3 | This is a Web UI built to provide a more user-friendly interface for Magnifier. 4 | It consists of a Vue.js frontend and a C++ backend, and it uses multi-session websockets to facilitate communication between the two. 5 | The MagnifierUI not only exposes most of the features Magnifier has to offer, but it also integrates [rellic](https://github.com/lifting-bits/rellic), the LLVM IR to C code decompiler, to show side-by-side C code decompilation results. 6 | 7 | ## Building 8 | 9 | ### Backend 10 | 11 | To build the C++ backend server, the `MAGNIFIER_ENABLE_UI` option needs to be enabled in CMake. 12 | This can be achieved by either adding `-DMAGNIFIER_ENABLE_UI=ON` to the CMake command line or directly editing the `CMakeList.txt` file. 13 | 14 | The backend server also depends on `uwebsockets`, `usockets`, and [rellic](https://github.com/lifting-bits/rellic). 15 | Both `uwebsockets` and `usockets` can be installed with vcpkg. 16 | 17 | The `magnifier-ui` target can then be compiled and executed. It will expose a websocket server on port 9001 by default. 18 | 19 | ### Frontend 20 | 21 | The Vue.js frontend relies on `node.js` and `npm` for the build process. 22 | It communicates with the C++ backend and displays the correct visuals. 23 | By default, it will try to connect to the websocket on `localhost:9001/ws`. 24 | This can be changed by editing the `www/config.js` file. 25 | 26 | During development, quick code reload is a very nice feature to have. 27 | To start a development server, the following commands can be used: 28 | 29 | ```bash 30 | # enter the www/ directory 31 | $ cd www/ 32 | 33 | # install dependencies 34 | $ npm install 35 | 36 | # serve with hot reload for development 37 | $ npm run dev 38 | ``` 39 | 40 | When it's time to deploy the project, a full static build can be generated: 41 | 42 | ```bash 43 | # enter the www/ directory 44 | $ cd www/ 45 | 46 | # install dependencies 47 | $ npm install 48 | 49 | # generate static project 50 | $ npm run generate 51 | ``` 52 | 53 | The content generated in `www/dist/` can then be copied and served just like any other static page websites (through something like github pages) while retaining its full functionality. -------------------------------------------------------------------------------- /lib/IdCommentWriter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-present, Trail of Bits, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed in accordance with the terms specified in 6 | * the LICENSE file found in the root directory of this source tree. 7 | */ 8 | 9 | #include "IdCommentWriter.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | namespace magnifier { 19 | IdCommentWriter::IdCommentWriter(BitcodeExplorer &explorer) : explorer(explorer) {} 20 | 21 | void IdCommentWriter::emitInstructionAnnot(const llvm::Instruction *instruction, llvm::formatted_raw_ostream &os) { 22 | ValueId instruction_id = explorer.GetId(*instruction, ValueIdKind::kDerived); 23 | ValueId source_id = explorer.GetId(*instruction, ValueIdKind::kOriginal); 24 | os << instruction_id << "|" << source_id; 25 | } 26 | 27 | void IdCommentWriter::emitFunctionAnnot(const llvm::Function *function, llvm::formatted_raw_ostream &os) { 28 | ValueId function_id = explorer.GetId(*function, ValueIdKind::kDerived); 29 | ValueId source_id = explorer.GetId(*function, ValueIdKind::kOriginal); 30 | 31 | if (!function->arg_empty()) { 32 | os << "Function argument ids: "; 33 | for (const llvm::Argument &argument : function->args()) { 34 | os << "(%" << argument.getName().str() << " = " << (function_id+argument.getArgNo()+1) << ") "; 35 | } 36 | os << "\n"; 37 | } 38 | 39 | os << function_id << "|" << source_id; 40 | } 41 | 42 | void IdCommentWriter::emitBasicBlockStartAnnot(const llvm::BasicBlock *block, llvm::formatted_raw_ostream &os) { 43 | const llvm::Instruction *terminator = block->getTerminator(); 44 | if (!terminator) { return; } 45 | os << "--- start block: " << explorer.GetId(*terminator, ValueIdKind::kBlock) << " ---\n"; 46 | } 47 | 48 | void IdCommentWriter::emitBasicBlockEndAnnot(const llvm::BasicBlock *block, llvm::formatted_raw_ostream &os) { 49 | const llvm::Instruction *terminator = block->getTerminator(); 50 | if (!terminator) { return; } 51 | os << "--- end block: " << explorer.GetId(*terminator, ValueIdKind::kBlock) << " ---\n"; 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Magnifier 2 | 3 | Magnifier is an experimental reverse engineering user interface. Magnifier asks, “What if, as an alternative to taking handwritten notes, reverse engineering researchers could interactively reshape a decompiled program to reflect what they would normally record?” With Magnifier, the decompiled C code isn’t the end—it’s the beginning. Read more about how Magnifier can be used in the [blog post announcing its release](https://blog.trailofbits.com/2022/08/25/magnifier-an-experiment-with-interactive-decompilation/). 4 | 5 | ## Building Magnifier using Presets and cxx-common 6 | 7 | Step 1: get cxx-common. 8 | 9 | ```sh 10 | scripts/fetch-cxx-common.sh 11 | # This script will set VCPKG_DIR for you 12 | ``` 13 | 14 | Step 2: Set `INSTALL_DIR` and Build 15 | 16 | ```sh 17 | export INSTALL_DIR=~/magnifier 18 | # you also need to set VCPKG_DIR if not using the fetch script 19 | scripts/build-preset.sh 20 | ``` 21 | 22 | ### Example Build Output 23 | 24 | ```sh 25 | artem@beefy:~/git/magnifier$ export INSTALL_DIR=~/magnifier 26 | artem@beefy:~/git/magnifier$ export VCPKG_ROOT=/opt/trailofbits/irene/downloads/vcpkg_ubuntu-20.04_llvm-13_amd64/ 27 | artem@beefy:~/git/magnifier$ scripts/build-preset.sh debug 28 | Building against VCPKG: [/opt/trailofbits/irene/downloads/vcpkg_ubuntu-20.04_llvm-13_amd64/] 29 | Installing to: [/home/artem/magnifier] 30 | Checking for clang/clang++ in [/opt/trailofbits/irene/downloads/vcpkg_ubuntu-20.04_llvm-13_amd64/] [x64-linux-rel]: 31 | Found a clang [/opt/trailofbits/irene/downloads/vcpkg_ubuntu-20.04_llvm-13_amd64//installed/x64-linux-rel/tools/llvm/clang]: 32 | clang version 13.0.1 (https://github.com/microsoft/vcpkg.git 7e7dad5fe20cdc085731343e0e197a7ae655555b) 33 | Target: x86_64-unknown-linux-gnu 34 | Thread model: posix 35 | InstalledDir: /opt/trailofbits/irene/downloads/vcpkg_ubuntu-20.04_llvm-13_amd64//installed/x64-linux-rel/tools/llvm 36 | Found a clang [/opt/trailofbits/irene/downloads/vcpkg_ubuntu-20.04_llvm-13_amd64//installed/x64-linux-rel/tools/llvm/clang++]: 37 | clang version 13.0.1 (https://github.com/microsoft/vcpkg.git 7e7dad5fe20cdc085731343e0e197a7ae655555b) 38 | Target: x86_64-unknown-linux-gnu 39 | Thread model: posix 40 | InstalledDir: /opt/trailofbits/irene/downloads/vcpkg_ubuntu-20.04_llvm-13_amd64//installed/x64-linux-rel/tools/llvm 41 | 42 | Configuring [dbg] [x64] against vcpkg [x64-linux-rel]... 43 | Configure success! 44 | Building [dbg] [x64]... 45 | Build success! 46 | Installing [dbg] [x64]... 47 | Install success! 48 | ``` 49 | 50 | ## Running Magnifier UI 51 | 52 | See the instructions [here](bin/magnifier-ui#magnifierui). 53 | -------------------------------------------------------------------------------- /bin/magnifier-ui/www/components/CommandView.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 83 | -------------------------------------------------------------------------------- /cmake/vcpkg_helper.cmake: -------------------------------------------------------------------------------- 1 | set(USE_SYSTEM_DEPENDENCIES OFF CACHE BOOL "Use system dependencies instead of trying to find vcpkg") 2 | 3 | if (NOT USE_SYSTEM_DEPENDENCIES) 4 | set(VCPKG_ROOT "" CACHE FILEPATH "Root directory to use for vcpkg-managed dependencies") 5 | if (VCPKG_ROOT) 6 | if (NOT EXISTS "${VCPKG_ROOT}") 7 | message(FATAL_ERROR "VCPKG_ROOT directory does not exist: '${VCPKG_ROOT}'") 8 | endif() 9 | 10 | set(VCPKG_ROOT_INSTALL_DIR "${VCPKG_ROOT}/installed") 11 | if (NOT EXISTS "${VCPKG_ROOT_INSTALL_DIR}") 12 | message(FATAL_ERROR "VCPKG_ROOT installation directory does not exist: '${VCPKG_ROOT_INSTALL_DIR}'") 13 | endif() 14 | 15 | set(CMAKE_TOOLCHAIN_FILE "${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE FILEPATH "" FORCE) 16 | else() 17 | message(FATAL_ERROR "Please define a path to VCPKG_ROOT. See https://github.com/lifting-bits/cxx-common for more details. Or if you don't want to use vcpkg dependencies, add '-DUSE_SYSTEM_DEPENDENCIES=ON'") 18 | endif() 19 | 20 | # Set default triplet to Release VCPKG build unless we can't find it 21 | if (NOT DEFINED VCPKG_TARGET_TRIPLET) 22 | set(_project_arch "x64") 23 | if (UNIX) 24 | execute_process(COMMAND uname -m 25 | OUTPUT_VARIABLE _SYSTEM_ARCH 26 | OUTPUT_STRIP_TRAILING_WHITESPACE) 27 | else() 28 | message(WARNING "No detection of architecture for this platform. Assuming x64") 29 | endif() 30 | if (_SYSTEM_ARCH MATCHES "^[Aa][Aa][Rr][Cc][Hh]64$") 31 | set(_project_arch "arm64") 32 | endif() 33 | 34 | if (APPLE) 35 | set(_project_vcpkg_triplet "${_project_arch}-osx-rel") 36 | elseif(UNIX) 37 | set(_project_vcpkg_triplet "${_project_arch}-linux-rel") 38 | elseif(WIN32) 39 | set(_project_vcpkg_triplet "${_project_arch}-windows-static-md-rel") 40 | else() 41 | message(FATAL_ERROR "Could not detect default release triplet") 42 | endif() 43 | 44 | if (NOT EXISTS "${VCPKG_ROOT_INSTALL_DIR}/${_project_vcpkg_triplet}") 45 | message(STATUS "Could not find installed project-default triplet '${_project_vcpkg_triplet}' using vcpkg-default for your system") 46 | else() 47 | set(VCPKG_TARGET_TRIPLET "${_project_vcpkg_triplet}" CACHE STRING "") 48 | message(STATUS "Setting default vcpkg triplet to release-only libraries: ${VCPKG_TARGET_TRIPLET}") 49 | endif() 50 | endif() 51 | 52 | if (DEFINED VCPKG_TARGET_TRIPLET AND NOT EXISTS "${VCPKG_ROOT_INSTALL_DIR}/${VCPKG_TARGET_TRIPLET}") 53 | message(FATAL_ERROR "Could not find vcpkg triplet (${VCPKG_TARGET_TRIPLET}) installation libraries '${VCPKG_ROOT_INSTALL_DIR}/${VCPKG_TARGET_TRIPLET}'.") 54 | endif() 55 | 56 | message(STATUS "Using vcpkg installation directory at '${VCPKG_ROOT_INSTALL_DIR}/${VCPKG_TARGET_TRIPLET}'") 57 | endif() 58 | -------------------------------------------------------------------------------- /bin/magnifier-ui/www/assets/layout/config.json: -------------------------------------------------------------------------------- 1 | {"root":{"type":"column","content":[{"type":"row","content":[{"type":"stack","content":[{"type":"component","content":[],"width":50,"minWidth":0,"height":50,"minHeight":0,"id":"","maximised":false,"isClosable":true,"reorderEnabled":true,"title":"Function List","componentType":"FunctionList","componentState":{"refId":0}}],"width":22.84851340289813,"minWidth":0,"height":50,"minHeight":0,"id":"","isClosable":true,"maximised":false,"activeItemIndex":0},{"type":"stack","content":[{"type":"component","content":[],"width":50,"minWidth":0,"height":50,"minHeight":0,"id":"","maximised":false,"isClosable":true,"reorderEnabled":true,"title":"IR View","componentType":"IRView","componentState":{"refId":1}}],"width":40.2431897797587,"minWidth":0,"height":50,"minHeight":0,"id":"","isClosable":true,"maximised":false,"activeItemIndex":0},{"type":"stack","content":[{"type":"component","content":[],"width":50,"minWidth":0,"height":50,"minHeight":0,"id":"","maximised":false,"isClosable":true,"reorderEnabled":true,"title":"Code View","componentType":"CodeView","componentState":{"refId":2}}],"width":36.90829681734317,"minWidth":0,"height":50,"minHeight":0,"id":"","isClosable":true,"maximised":false,"activeItemIndex":0}],"width":50,"minWidth":50,"height":75.21432522123894,"minHeight":75.21432522123894,"id":"","isClosable":true},{"type":"row","content":[{"type":"stack","content":[{"type":"component","content":[],"width":50,"minWidth":0,"height":50,"minHeight":0,"id":"","maximised":false,"isClosable":true,"reorderEnabled":true,"title":"Terminal","componentType":"Terminal","componentState":{"refId":3}}],"width":82.73387132879655,"minWidth":0,"height":24.785674778761056,"minHeight":0,"id":"","isClosable":true,"maximised":false,"activeItemIndex":0},{"type":"stack","content":[{"type":"component","content":[],"width":50,"minWidth":0,"height":50,"minHeight":0,"id":"","maximised":false,"isClosable":true,"reorderEnabled":true,"title":"CommandView","componentType":"CommandView","componentState":{"refId":4}}],"width":17.26612867120344,"minWidth":0,"height":50,"minHeight":0,"id":"","isClosable":true,"maximised":false,"activeItemIndex":0}],"width":50,"minWidth":50,"height":24.785674778761056,"minHeight":24.785674778761056,"id":"","isClosable":true}],"width":50,"minWidth":50,"height":50,"minHeight":50,"id":"","isClosable":true},"openPopouts":[],"settings":{"constrainDragToContainer":true,"reorderEnabled":true,"popoutWholeStack":false,"blockedPopoutsThrowError":true,"closePopoutsOnUnload":true,"responsiveMode":"none","tabOverlapAllowance":0,"reorderOnTabMenuClick":true,"tabControlOffset":10,"popInOnClose":false},"dimensions":{"borderWidth":5,"borderGrabWidth":5,"minItemHeight":10,"minItemWidth":10,"headerHeight":20,"dragProxyWidth":300,"dragProxyHeight":200},"header":{"show":"top","popout":"open in new window","dock":"dock","close":"close","maximise":"maximise","minimise":"minimise","tabDropdown":"additional tabs"},"resolved":true} -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: VCPKG Continuous Integration 2 | 3 | on: 4 | # Run this workflow once every 6 hours against the master branch 5 | #schedule: 6 | # - cron: "0 */6 * * *" 7 | 8 | push: 9 | branches: 10 | - 'master' 11 | 12 | tags: 13 | - '*' 14 | 15 | pull_request: 16 | branches: 17 | - '*' 18 | 19 | jobs: 20 | build_linux: 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | image: 25 | - { name: 'ubuntu', tag: '20.04' } 26 | llvm: [ '14' ] 27 | 28 | runs-on: ubuntu-20.04 29 | container: 30 | image: docker.pkg.github.com/lifting-bits/cxx-common/vcpkg-builder-${{ matrix.image.name }}:${{ matrix.image.tag }} 31 | credentials: 32 | username: ${{ github.actor }} 33 | password: ${{ secrets.GITHUB_TOKEN }} 34 | 35 | steps: 36 | - uses: actions/checkout@v2 37 | with: 38 | fetch-depth: 0 39 | submodules: true 40 | 41 | - name: Install utility tools 42 | shell: bash 43 | run: | 44 | # TODO some of these should probably live in the Docker build image 45 | #wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - 46 | #echo "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-13 main" >> /etc/apt/sources.list 47 | #echo "deb-src http://apt.llvm.org/focal/ llvm-toolchain-focal-13 main" >> /etc/apt/sources.list 48 | apt-get update 49 | apt-get install curl pixz xz-utils 50 | #apt-get install -y pixz xz-utils make rpm python3.8 51 | #update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 100 52 | 53 | - name: Build with CMake Presets 54 | shell: bash 55 | run: | 56 | export INSTALL_DIR=${GITHUB_WORKSPACE}/${INSTALL_DIR_PART} 57 | export VCPKG_ROOT=${GITHUB_WORKSPACE}/${VCPKG_ROOT_PART} 58 | scripts/fetch-cxx-common.sh 59 | scripts/build-preset.sh debug 60 | env: 61 | LLVM: ${{ matrix.llvm }} 62 | INSTALL_DIR_PART: ../magnifier-install 63 | VCPKG_ROOT_PART: ../downloads/vcpkg_${{ matrix.image.name }}-${{ matrix.image.tag }}_llvm-${{ matrix.llvm }}_amd64 64 | 65 | build_mac: 66 | strategy: 67 | fail-fast: false 68 | matrix: 69 | os: [ 70 | 'macos-11' 71 | ] 72 | llvm: [ '14' ] 73 | 74 | runs-on: ${{ matrix.os }} 75 | 76 | steps: 77 | - uses: actions/checkout@v2 78 | with: 79 | fetch-depth: 0 80 | submodules: true 81 | 82 | - name: Install utility tools 83 | shell: bash 84 | run: | 85 | brew install coreutils ninja 86 | 87 | - name: Build with CMake Presets 88 | shell: bash 89 | run: | 90 | export INSTALL_DIR=${GITHUB_WORKSPACE}/${INSTALL_DIR_PART} 91 | export VCPKG_ROOT=${GITHUB_WORKSPACE}/${VCPKG_ROOT_PART} 92 | scripts/fetch-cxx-common.sh 93 | scripts/build-preset.sh debug -- -DCMAKE_C_COMPILER=$(which clang) -DCMAKE_CXX_COMPILER=$(which clang++) 94 | env: 95 | LLVM: ${{ matrix.llvm }} 96 | INSTALL_DIR_PART: ../magnifier-install 97 | VCPKG_ROOT_PART: ../downloads/vcpkg_${{ matrix.os }}_llvm-${{ matrix.llvm }}_xcode-13.0_amd64 98 | -------------------------------------------------------------------------------- /bin/magnifier-ui/www/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export const state = () => ({ 4 | counter: 0, 5 | terminalOutput: '', 6 | funcs: {}, 7 | currentFuncId: 1, 8 | currentFuncIRContent: '', 9 | currentFuncDecompiledContent: '', 10 | currentIRSelection: undefined, 11 | currentProvenanceMap: {} 12 | }) 13 | 14 | export const mutations = { 15 | increment (state) { 16 | state.counter++ 17 | }, 18 | setFuncs (state, { funcs }) { 19 | Vue.set(state, 'funcs', funcs) 20 | }, 21 | setCurrentFuncIRContent (state, { content }) { 22 | state.currentFuncIRContent = content 23 | }, 24 | setCurrentFuncDecompiledContent (state, { content }) { 25 | state.currentFuncDecompiledContent = content 26 | }, 27 | setCurrentFuncId (state, { id }) { 28 | state.currentFuncId = id 29 | }, 30 | appendTerminalOutput (state, { text }) { 31 | state.terminalOutput += text 32 | }, 33 | clearTerminalInput (state) { 34 | state.terminalOutput = '' 35 | }, 36 | setCurrentIRSelection (state, { id }) { 37 | state.currentIRSelection = id 38 | }, 39 | setCurrentProvenanceMap (state, { provenance }) { 40 | const newMap = {} 41 | for (const map in provenance) { 42 | for (const [from, to] of provenance[map]) { 43 | if (!from || !to) { 44 | continue 45 | } 46 | 47 | const fromHex = from.toString(16) 48 | const toHex = to.toString(16) 49 | if (!newMap[fromHex]) { 50 | newMap[fromHex] = [] 51 | } 52 | newMap[fromHex].push(toHex) 53 | 54 | if (!newMap[toHex]) { 55 | newMap[toHex] = [] 56 | } 57 | newMap[toHex].push(fromHex) 58 | } 59 | } 60 | Vue.set(state, 'currentProvenanceMap', newMap) 61 | } 62 | } 63 | 64 | export const actions = { 65 | async updateFuncs ({ commit, state, dispatch }) { 66 | const { output } = await this.$socket.send({ 67 | cmd: 'lfa' 68 | }) 69 | 70 | if (output.trim().length <= 0) { 71 | await commit('setFuncs', { funcs: {} }) 72 | await commit('setCurrentFuncIRContent', { content: '' }) 73 | await commit('setCurrentFuncDecompiledContent', { content: '' }) 74 | return 75 | } 76 | 77 | const updateFuncList = {} 78 | const newFunctions = [] 79 | let funcPrecedingCurr = 1 80 | output.trim().split('\n').forEach((l) => { 81 | const [idStr, name] = l.trim().split(' ') 82 | const id = parseInt(idStr) 83 | updateFuncList[id] = name 84 | if (!state.funcs[id]) { newFunctions.push(id) } 85 | if (id <= state.currentFuncId) { funcPrecedingCurr = id } 86 | }) 87 | 88 | await commit('setFuncs', { funcs: updateFuncList }) 89 | 90 | if (newFunctions.length > 0) { 91 | await commit('setCurrentFuncId', { id: newFunctions[0] }) 92 | await dispatch('updateFuncContent') 93 | } else if (!updateFuncList[state.currentFuncId]) { 94 | await commit('setCurrentFuncId', { id: funcPrecedingCurr }) 95 | await dispatch('updateFuncContent') 96 | } 97 | }, 98 | async updateFuncContent ({ state, commit }) { 99 | const { output } = await this.$socket.send({ 100 | cmd: `dec ${state.currentFuncId}` 101 | }) 102 | 103 | if (typeof output !== 'object') { 104 | // need better error handling 105 | return 106 | } 107 | 108 | const { ir, code, provenance } = output 109 | 110 | await commit('setCurrentFuncIRContent', { 111 | content: ir 112 | }) 113 | 114 | await commit('setCurrentFuncDecompiledContent', { 115 | content: code 116 | }) 117 | 118 | await commit('setCurrentProvenanceMap', { 119 | provenance 120 | }) 121 | }, 122 | async focusFunc ({ commit, dispatch }, { id }) { 123 | await commit('setCurrentFuncId', { id }) 124 | await dispatch('updateFuncContent') 125 | }, 126 | async evalCommand ({ commit, dispatch }, { cmd }) { 127 | await commit('appendTerminalOutput', { text: `> ${cmd}\n` }) 128 | const { output } = await this.$socket.send({ 129 | cmd 130 | }) 131 | 132 | await commit('appendTerminalOutput', { text: output }) 133 | // Update Function List UI 134 | await dispatch('updateFuncs') 135 | }, 136 | parseWsData ({ commit, dispatch }, { cmd, output }) { 137 | // handle unknown data here 138 | }, 139 | async uploadBitcode ({ dispatch }, { file }) { 140 | await this.$socket.send({ 141 | cmd: 'upload', 142 | file 143 | }) 144 | await dispatch('updateFuncs') 145 | await dispatch('updateFuncContent') 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /scripts/build-preset.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 3 | PROJECT=magnifier 4 | 5 | BUILDLOG=${PROJECT}-build.log 6 | CONFIGLOG=${PROJECT}-configure.log 7 | rm -f ${BUILDLOG} ${CONFIGLOG} 8 | BUILD_TYPE=dbg 9 | VCPKG_SUFFIX="-rel" 10 | 11 | set -o pipefail 12 | 13 | function sanity_check { 14 | if [ -z "${VCPKG_ROOT}" ]; then 15 | echo "Please set the VCPKG_ROOT environment variable to the VCPKG root to build against" 16 | exit 1 17 | else 18 | echo "Building against VCPKG: [${VCPKG_ROOT}]" 19 | fi 20 | 21 | if [ -z "${INSTALL_DIR}" ]; then 22 | echo "Please set the INSTALL_DIR environment variable to the desired installation directory" 23 | exit 1 24 | else 25 | echo "Installing to: [${INSTALL_DIR}]" 26 | fi 27 | } 28 | 29 | function show_usage { 30 | 31 | printf "${0}: Build ${PROJECT} [-- extra arguments to CMake]" 32 | printf "\n" 33 | printf "\t--help: this screen\n" 34 | printf "\t--debug-vcpkg: build against a debug vcpkg (default OFF)\n" 35 | printf "\t: the type of build to do (debug or release or asan+debug)\n" 36 | printf "\tArguments after '--' are passed to CMake during configuration (e.g. -DCMAKE_C_COMPILER=foo)\n" 37 | printf "\n" 38 | printf "INSTALL_DIR set to [${INSTALL_DIR}]\n" 39 | printf "VCPKG_ROOT set to [${VCPKG_ROOT}]\n" 40 | 41 | return 0 42 | } 43 | 44 | function compiler_check { 45 | printf "Checking for clang/clang++ in [${VCPKG_ROOT}] [${VCPKG_TARGET_TRIPLET}]:\n" 46 | for c in ${VCPKG_ROOT}/installed/${VCPKG_TARGET_TRIPLET}/tools/llvm/{clang,clang++} 47 | do 48 | ver=$(${c} --version) 49 | printf "Found a clang [${c}]:\n" 50 | printf "${ver}\n" 51 | done 52 | printf "\n" 53 | } 54 | 55 | function set_arch { 56 | local arch=$(uname -m) 57 | case ${arch} in 58 | aarch64 | arm64) 59 | echo "arm64" 60 | ;; 61 | x86_64) 62 | echo "x64" 63 | ;; 64 | *) 65 | echo "Unknown architecture: ${arch}" 66 | exit 1 67 | esac 68 | } 69 | 70 | function set_os { 71 | local os=$(uname -s) 72 | case ${os} in 73 | Darwin) 74 | echo "osx" 75 | ;; 76 | Linux) 77 | echo "linux" 78 | ;; 79 | *) 80 | echo "Unknown OS: ${os}" 81 | exit 1 82 | esac 83 | } 84 | 85 | 86 | # Make the user specify which build type 87 | if [[ $# -eq 0 ]]; then 88 | show_usage ${0} 89 | exit 0 90 | fi 91 | 92 | # check if proper env vars are set 93 | sanity_check 94 | 95 | # Look for help or set the build type 96 | while [[ $# -gt 0 ]] 97 | do 98 | key="$1" 99 | case $key in 100 | --help | -h | "-?") 101 | show_usage ${0} 102 | exit 0 103 | ;; 104 | --debug-vcpkg) 105 | VCPKG_SUFFIX="" 106 | shift 107 | ;; 108 | debug) 109 | BUILD_TYPE="dbg" 110 | shift 111 | ;; 112 | release) 113 | BUILD_TYPE="rel" 114 | shift 115 | ;; 116 | asan) 117 | BUILD_TYPE="asan" 118 | VCPKG_SUFFIX="-asan" 119 | shift 120 | ;; 121 | "--") 122 | shift 123 | break 124 | ;; 125 | *) # unknown option 126 | echo "UNKNOWN OPTION: ${1}" 127 | echo "Usage:" 128 | show_usage ${0} 129 | exit 1 130 | ;; 131 | esac 132 | done 133 | 134 | ARCH=$(set_arch) 135 | OS=$(set_os) 136 | export VCPKG_TARGET_TRIPLET=${ARCH}-${OS}${VCPKG_SUFFIX} 137 | 138 | compiler_check 139 | 140 | 141 | echo "Configuring [${BUILD_TYPE}] [${ARCH}] against vcpkg [${VCPKG_TARGET_TRIPLET}]..." 142 | if [[ "${@}" != "" ]] 143 | then 144 | echo "Passing extra arguments to CMake: ${@}" 145 | fi 146 | 147 | cmake --preset vcpkg-${ARCH}-${BUILD_TYPE} ${@} &>${CONFIGLOG} 148 | if [ "$?" != "0" ]; then 149 | echo "Configuration failed. See ${CONFIGLOG}" 150 | cat "${CONFIGLOG}" 151 | exit 1 152 | else 153 | echo "Configure success!" 154 | fi 155 | 156 | echo "Building [${BUILD_TYPE}] [${ARCH}]..." 157 | cmake --build --preset ${ARCH}-${BUILD_TYPE} --parallel &>${BUILDLOG} 158 | if [ "$?" != "0" ]; then 159 | echo "Build failed. See ${BUILDLOG}" 160 | cat "${BUILDLOG}" 161 | exit 1 162 | else 163 | echo "Build success!" 164 | fi 165 | 166 | echo "Installing [${BUILD_TYPE}] [${ARCH}]..." 167 | # re-use build log since its mostly a part of build process 168 | cmake --build --preset ${ARCH}-${BUILD_TYPE} --target install --parallel >>${BUILDLOG} 2>&1 169 | if [ "$?" != "0" ]; then 170 | echo "Install failed. See ${BUILDLOG}" 171 | cat "${BUILDLOG}" 172 | exit 1 173 | else 174 | echo "Install success!" 175 | fi 176 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2021-present, Trail of Bits, Inc. 3 | # All rights reserved. 4 | # 5 | # This source code is licensed in accordance with the terms specified in 6 | # the LICENSE file found in the root directory of this source tree. 7 | # 8 | 9 | cmake_minimum_required(VERSION 3.16) 10 | include("cmake/options.cmake") 11 | include("cmake/vcpkg_helper.cmake") 12 | 13 | project(magnifier) 14 | 15 | include(GNUInstallDirs) 16 | 17 | if(CMAKE_SYSTEM_NAME STREQUAL "Linux") 18 | set(PLATFORM_LINUX true) 19 | 20 | elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") 21 | set(PLATFORM_MACOS true) 22 | 23 | else() 24 | message(FATAL_ERROR "Unsupported platform") 25 | endif() 26 | 27 | 28 | include("cmake/llvm.cmake") 29 | 30 | option(MAGNIFIER_ENABLE_INSTALL "Set to true to enable the install target" true) 31 | option(MAGNIFIER_ENABLE_UI "Set to true to enable the magnifier-ui target" OFF) 32 | 33 | list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 34 | find_package(Filesystem REQUIRED) 35 | 36 | 37 | find_llvm(llvm) 38 | 39 | add_library(magnifier STATIC 40 | lib/BitcodeExplorer.cpp 41 | lib/ISubstitutionObserver.cpp 42 | lib/IdCommentWriter.cpp 43 | lib/IdCommentWriter.h 44 | ) 45 | 46 | target_compile_features(magnifier PUBLIC cxx_std_20) 47 | set(magnifier_PUBLIC_HEADER_DIR "${PROJECT_SOURCE_DIR}/include/magnifier") 48 | set(magnifier_PUBLIC_HEADERS 49 | "${magnifier_PUBLIC_HEADER_DIR}/BitcodeExplorer.h" 50 | "${magnifier_PUBLIC_HEADER_DIR}/Result.h" 51 | "${magnifier_PUBLIC_HEADER_DIR}/IFunctionResolver.h" 52 | "${magnifier_PUBLIC_HEADER_DIR}/ISubstitutionObserver.h" 53 | ) 54 | set_target_properties(magnifier 55 | PROPERTIES 56 | PUBLIC_HEADER "${magnifier_PUBLIC_HEADERS}" 57 | LINKER_LANGUAGE CXX 58 | ) 59 | 60 | find_package(LLVM REQUIRED CONFIG) 61 | target_link_libraries(magnifier PUBLIC llvm) 62 | target_compile_definitions(magnifier PUBLIC ${LLVM_DEFINITIONS}) 63 | target_include_directories(magnifier SYSTEM PUBLIC ${LLVM_INCLUDE_DIRS}) 64 | target_link_directories(magnifier PUBLIC ${LLVM_LIBRARY_DIRS}) 65 | target_include_directories(magnifier 66 | PUBLIC 67 | $ 68 | $ 69 | $ 70 | PRIVATE 71 | "${CMAKE_CURRENT_SOURCE_DIR}" 72 | ) 73 | 74 | add_executable(repl) 75 | target_sources(repl PRIVATE bin/repl/main.cpp) 76 | target_link_libraries(repl PRIVATE magnifier std::filesystem) 77 | target_include_directories(repl 78 | PUBLIC 79 | $ 80 | $ 81 | $ 82 | PRIVATE 83 | "${CMAKE_CURRENT_SOURCE_DIR}" 84 | ) 85 | 86 | if(MAGNIFIER_ENABLE_UI) 87 | add_executable(magnifier-ui) 88 | target_sources(magnifier-ui PRIVATE 89 | bin/magnifier-ui/main.cpp 90 | bin/magnifier-ui/DeclPrinter.cpp 91 | bin/magnifier-ui/StmtPrinter.cpp 92 | bin/magnifier-ui/TypePrinter.cpp) 93 | target_link_libraries(magnifier-ui PRIVATE magnifier) 94 | target_include_directories(magnifier-ui 95 | PUBLIC 96 | $ 97 | $ 98 | $ 99 | PRIVATE 100 | "${CMAKE_CURRENT_SOURCE_DIR}" 101 | ) 102 | 103 | find_path(UWEBSOCKETS_INCLUDE_DIRS "uwebsockets/App.h") 104 | target_include_directories(magnifier-ui PRIVATE ${UWEBSOCKETS_INCLUDE_DIRS}) 105 | 106 | find_library(USOCKETS_LIB uSockets) 107 | target_link_libraries(magnifier-ui PRIVATE ${USOCKETS_LIB}) 108 | 109 | find_library(UV_LIB uv) 110 | target_link_libraries(magnifier-ui PRIVATE ${UV_LIB}) 111 | 112 | find_package(rellic CONFIG REQUIRED) 113 | target_link_libraries(magnifier-ui PRIVATE rellic::rellic) 114 | endif(MAGNIFIER_ENABLE_UI) 115 | 116 | if(MAGNIFIER_ENABLE_INSTALL) 117 | export(PACKAGE "${PROJECT_NAME}") 118 | 119 | set(cmake_install_dir "lib/cmake/${PROJECT_NAME}") 120 | 121 | include(CMakePackageConfigHelpers) 122 | configure_package_config_file("${PROJECT_NAME}Config.cmake.in" 123 | "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" 124 | INSTALL_DESTINATION "${cmake_install_dir}" 125 | ) 126 | 127 | install( 128 | FILES 129 | "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" 130 | DESTINATION "${cmake_install_dir}" 131 | ) 132 | install(EXPORT "${PROJECT_NAME}Targets" 133 | DESTINATION "${cmake_install_dir}" 134 | NAMESPACE "${PROJECT_NAME}::" 135 | ) 136 | install( 137 | TARGETS 138 | "llvm" 139 | EXPORT 140 | "${PROJECT_NAME}Targets" 141 | ) 142 | 143 | install( 144 | TARGETS 145 | magnifier 146 | repl 147 | 148 | EXPORT 149 | magnifierTargets 150 | 151 | RUNTIME DESTINATION 152 | bin 153 | 154 | LIBRARY DESTINATION 155 | lib 156 | 157 | ARCHIVE DESTINATION 158 | lib 159 | 160 | INCLUDES DESTINATION 161 | include 162 | 163 | PUBLIC_HEADER DESTINATION 164 | "${CMAKE_INSTALL_INCLUDEDIR}/magnifier" 165 | ) 166 | endif(MAGNIFIER_ENABLE_INSTALL) 167 | -------------------------------------------------------------------------------- /include/magnifier/Result.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-present, Trail of Bits, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed in accordance with the terms specified in 6 | * the LICENSE file found in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | namespace magnifier { 15 | 16 | template 17 | class Result final { 18 | private: 19 | bool destroyed{true}; 20 | mutable bool checked{false}; 21 | std::variant data; 22 | 23 | public: 24 | Result(void); 25 | ~Result(void) = default; 26 | 27 | bool Succeeded(void) const; 28 | 29 | const ErrorType &Error(void) const; 30 | ErrorType TakeError(void); 31 | 32 | const ValueType &Value(void) const; 33 | ValueType TakeValue(void); 34 | 35 | const ValueType *operator->(void) const; 36 | 37 | Result(const ValueType &value); 38 | Result(ValueType &&value): destroyed(false), data(std::move(value)) {} 39 | 40 | Result(const ErrorType &error); 41 | Result(ErrorType &&error): destroyed(false), data(std::move(error)) {} 42 | 43 | Result(Result &&other) noexcept; 44 | Result &operator=(Result &&other) noexcept; 45 | 46 | Result(const Result &) = delete; 47 | Result &operator=(const Result &) = delete; 48 | 49 | private: 50 | void VerifyState(void) const; 51 | void VerifyChecked(void) const; 52 | void VerifyFailed(void) const; 53 | void VerifySucceeded(void) const; 54 | }; 55 | 56 | template 57 | Result::Result(void) { 58 | checked = true; 59 | data = ErrorType(); 60 | } 61 | 62 | template 63 | bool Result::Succeeded(void) const { 64 | VerifyState(); 65 | 66 | checked = true; 67 | return std::holds_alternative(data); 68 | } 69 | 70 | template 71 | const ErrorType &Result::Error(void) const { 72 | VerifyState(); 73 | VerifyChecked(); 74 | VerifyFailed(); 75 | 76 | return std::get(data); 77 | } 78 | 79 | template 80 | ErrorType Result::TakeError(void) { 81 | VerifyState(); 82 | VerifyChecked(); 83 | VerifyFailed(); 84 | 85 | auto error = std::move(std::get(data)); 86 | destroyed = true; 87 | 88 | return error; 89 | } 90 | 91 | template 92 | const ValueType &Result::Value(void) const { 93 | VerifyState(); 94 | VerifyChecked(); 95 | VerifySucceeded(); 96 | 97 | return std::get(data); 98 | } 99 | 100 | template 101 | ValueType Result::TakeValue(void) { 102 | VerifyState(); 103 | VerifyChecked(); 104 | VerifySucceeded(); 105 | 106 | auto value = std::move(std::get(data)); 107 | destroyed = true; 108 | 109 | return value; 110 | } 111 | 112 | template 113 | const ValueType *Result::operator->(void) const { 114 | return &Value(); 115 | } 116 | 117 | template 118 | Result::Result(const ValueType &value) { 119 | data = value; 120 | destroyed = false; 121 | } 122 | 123 | 124 | template 125 | Result::Result(const ErrorType &error) { 126 | data = error; 127 | destroyed = false; 128 | } 129 | 130 | template 131 | Result::Result(Result &&other) noexcept { 132 | data = std::exchange(other.data, ErrorType()); 133 | checked = std::exchange(other.checked, true); 134 | destroyed = std::exchange(other.destroyed, false); 135 | } 136 | 137 | template 138 | Result & 139 | Result::operator=(Result &&other) noexcept { 140 | if (this != &other) { 141 | data = std::exchange(other.data, ErrorType()); 142 | checked = std::exchange(other.checked, true); 143 | destroyed = std::exchange(other.destroyed, false); 144 | } 145 | 146 | return *this; 147 | } 148 | 149 | template 150 | void Result::VerifyState(void) const { 151 | if (!destroyed) { 152 | return; 153 | } 154 | 155 | throw std::logic_error( 156 | "The Result object no longer contains its internal data because it has been moved with TakeError/TakeValue"); 157 | } 158 | 159 | template 160 | void Result::VerifyChecked(void) const { 161 | if (checked) { 162 | return; 163 | } 164 | 165 | throw std::logic_error( 166 | "The Result object was not checked for success"); 167 | } 168 | 169 | template 170 | void Result::VerifySucceeded(void) const { 171 | if (std::holds_alternative(data)) { 172 | return; 173 | } 174 | 175 | throw std::logic_error( 176 | "The Result object has not succeeded"); 177 | } 178 | 179 | template 180 | void Result::VerifyFailed(void) const { 181 | if (std::holds_alternative(data)) { 182 | return; 183 | } 184 | 185 | throw std::logic_error( 186 | "The Result object has not failed"); 187 | } 188 | 189 | } -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 19, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "debug-flags", 11 | "hidden": true, 12 | "cacheVariables": { 13 | "CMAKE_BUILD_TYPE": "Debug", 14 | "CMAKE_C_FLAGS": "-fno-omit-frame-pointer -fno-optimize-sibling-calls -gfull -O0", 15 | "CMAKE_CXX_FLAGS": "-fno-omit-frame-pointer -fno-optimize-sibling-calls -gfull -O0" 16 | } 17 | }, 18 | { 19 | "name": "asan-flags", 20 | "hidden": true, 21 | "inherits": ["debug-flags"], 22 | "cacheVariables": { 23 | "CMAKE_C_FLAGS": "-fno-omit-frame-pointer -fno-optimize-sibling-calls -gfull -O1 -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls -ffunction-sections -fdata-sections", 24 | "CMAKE_CXX_FLAGS": "-fno-omit-frame-pointer -fno-optimize-sibling-calls -gfull -O1 -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls -ffunction-sections -fdata-sections" 25 | } 26 | }, 27 | { 28 | "name": "release-flags", 29 | "hidden": true, 30 | "cacheVariables": { 31 | "CMAKE_BUILD_TYPE": "Release" 32 | } 33 | }, 34 | { 35 | "name": "arm64", 36 | "hidden": true, 37 | "environment": { 38 | "VCPKG_ARCH": "arm64" 39 | }, 40 | "architecture": { 41 | "value": "arm64", 42 | "strategy": "external" 43 | } 44 | }, 45 | { 46 | "name": "x86_64", 47 | "hidden": true, 48 | "environment": { 49 | "VCPKG_ARCH": "x64" 50 | }, 51 | "architecture": { 52 | "value": "x64", 53 | "strategy": "external" 54 | } 55 | }, 56 | { 57 | "name": "vcpkg-common", 58 | "hidden": true, 59 | "binaryDir": "$env{INSTALL_DIR}/build/magnifier", 60 | "generator": "Ninja", 61 | "cacheVariables": { 62 | "VCPKG_TARGET_TRIPLET": "$env{VCPKG_TARGET_TRIPLET}", 63 | "VCPKG_ROOT": "$env{VCPKG_ROOT}", 64 | "CMAKE_INSTALL_PREFIX": "$env{INSTALL_DIR}/install", 65 | "CMAKE_C_COMPILER": "$env{VCPKG_ROOT}/installed/$env{VCPKG_TARGET_TRIPLET}/tools/llvm/clang", 66 | "CMAKE_CXX_COMPILER": "$env{VCPKG_ROOT}/installed/$env{VCPKG_TARGET_TRIPLET}/tools/llvm/clang++" 67 | } 68 | }, 69 | { 70 | "name": "vcpkg-debug", 71 | "hidden": true, 72 | "inherits": ["debug-flags", "vcpkg-common"] 73 | }, 74 | { 75 | "name": "vcpkg-release", 76 | "hidden": true, 77 | "inherits": ["release-flags", "vcpkg-common"] 78 | }, 79 | { 80 | "name": "vcpkg-asan", 81 | "hidden": true, 82 | "inherits": ["asan-flags", "vcpkg-common"] 83 | }, 84 | { 85 | "name": "vcpkg-x64-dbg", 86 | "inherits": ["vcpkg-debug", "x86_64"], 87 | "displayName": "Debug Build (vcpkg) (x64)", 88 | "description": "Build a Debug version against a VCPKG installation. Define 'VCPKG_ROOT', 'INSTALL_DIR', 'VCPKG_TARGET_TRIPLET' env vars!" 89 | }, 90 | { 91 | "name": "vcpkg-x64-rel", 92 | "inherits": ["vcpkg-release", "x86_64"], 93 | "displayName": "Release Build (vcpkg) (x64)", 94 | "description": "Build a Release version against a VCPKG installation. Define 'VCPKG_ROOT', 'INSTALL_DIR', 'VCPKG_TARGET_TRIPLET' env vars!" 95 | }, 96 | { 97 | "name": "vcpkg-x64-asan", 98 | "inherits": ["vcpkg-asan", "x86_64"], 99 | "displayName": "Debug ASAN Build (vcpkg) (x64)", 100 | "description": "Build a Debug ASAN version against a VCPKG installation. Define 'VCPKG_ROOT', 'INSTALL_DIR', 'VCPKG_TARGET_TRIPLET' env vars!" 101 | }, 102 | { 103 | "name": "vcpkg-arm64-dbg", 104 | "inherits": ["vcpkg-debug", "arm64"], 105 | "displayName": "Debug Build (vcpkg) (arm64)", 106 | "description": "Build a Debug version against a VCPKG installation. Define 'VCPKG_ROOT', 'INSTALL_DIR', 'VCPKG_TARGET_TRIPLET' env vars!" 107 | }, 108 | { 109 | "name": "vcpkg-arm64-rel", 110 | "inherits": ["vcpkg-release", "arm64"], 111 | "displayName": "Release Build (vcpkg) (arm64)", 112 | "description": "Build a Release version against a VCPKG installation. Define 'VCPKG_ROOT', 'INSTALL_DIR', 'VCPKG_TARGET_TRIPLET' env vars!" 113 | }, 114 | { 115 | "name": "vcpkg-arm64-asan", 116 | "inherits": ["vcpkg-asan", "arm64"], 117 | "displayName": "Debug ASAN Build (vcpkg) (arm64)", 118 | "description": "Build a Debug ASAN version against a VCPKG installation. Define 'VCPKG_ROOT', 'INSTALL_DIR', 'VCPKG_TARGET_TRIPLET' env vars!" 119 | } 120 | ], 121 | "buildPresets": [ 122 | { 123 | "name": "build-base-debug", 124 | "hidden": true, 125 | "description": "Build in Debug mode", 126 | "configuration": "Debug" 127 | }, 128 | { 129 | "name": "build-base-release", 130 | "hidden": true, 131 | "description": "Build in Release mode", 132 | "configuration": "Release" 133 | }, 134 | { 135 | "name": "build-base-asan", 136 | "hidden": true, 137 | "description": "Build in Debug Asan mode", 138 | "configuration": "Debug" 139 | }, 140 | { 141 | "name": "x64-dbg", 142 | "configurePreset": "vcpkg-x64-dbg", 143 | "displayName": "Build (debug) (x64)", 144 | "inherits": ["build-base-debug"] 145 | }, 146 | { 147 | "name": "x64-asan", 148 | "configurePreset": "vcpkg-x64-asan", 149 | "displayName": "Build (debug) (ASAN) (x64)", 150 | "inherits": ["build-base-asan"] 151 | }, 152 | { 153 | "name": "x64-rel", 154 | "configurePreset": "vcpkg-x64-rel", 155 | "displayName": "Build (release) (x64)", 156 | "inherits": ["build-base-release"] 157 | }, 158 | { 159 | "name": "arm64-dbg", 160 | "configurePreset": "vcpkg-arm64-dbg", 161 | "displayName": "Build (debug) (arm64)", 162 | "inherits": ["build-base-debug"] 163 | }, 164 | { 165 | "name": "arm64-asan", 166 | "configurePreset": "vcpkg-arm64-asan", 167 | "displayName": "Build (debug) (ASAN) (arm64)", 168 | "inherits": ["build-base-asan"] 169 | }, 170 | { 171 | "name": "arm64-rel", 172 | "configurePreset": "vcpkg-arm64-rel", 173 | "displayName": "Build (release) (arm64)", 174 | "inherits": ["build-base-release"] 175 | } 176 | ] 177 | } 178 | -------------------------------------------------------------------------------- /bin/magnifier-ui/www/components/Glayout.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 253 | -------------------------------------------------------------------------------- /cmake/FindFilesystem.cmake: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved BSD 3-Clause License. See accompanying 2 | # file Copyright.txt or https://cmake.org/licensing for details. 3 | 4 | # Copied from https://github.com/vector-of-bool/CMakeCM/blob/master/modules/FindFilesystem.cmake 5 | 6 | #[=======================================================================[.rst: 7 | 8 | FindFilesystem 9 | ############## 10 | 11 | This module supports the C++17 standard library's filesystem utilities. Use the 12 | :imp-target:`std::filesystem` imported target to 13 | 14 | Options 15 | ******* 16 | 17 | The ``COMPONENTS`` argument to this module supports the following values: 18 | 19 | .. find-component:: Experimental 20 | :name: fs.Experimental 21 | 22 | Allows the module to find the "experimental" Filesystem TS version of the 23 | Filesystem library. This is the library that should be used with the 24 | ``std::experimental::filesystem`` namespace. 25 | 26 | .. find-component:: Final 27 | :name: fs.Final 28 | 29 | Finds the final C++17 standard version of the filesystem library. 30 | 31 | If no components are provided, behaves as if the 32 | :find-component:`fs.Final` component was specified. 33 | 34 | If both :find-component:`fs.Experimental` and :find-component:`fs.Final` are 35 | provided, first looks for ``Final``, and falls back to ``Experimental`` in case 36 | of failure. If ``Final`` is found, :imp-target:`std::filesystem` and all 37 | :ref:`variables ` will refer to the ``Final`` version. 38 | 39 | 40 | Imported Targets 41 | **************** 42 | 43 | .. imp-target:: std::filesystem 44 | 45 | The ``std::filesystem`` imported target is defined when any requested 46 | version of the C++ filesystem library has been found, whether it is 47 | *Experimental* or *Final*. 48 | 49 | If no version of the filesystem library is available, this target will not 50 | be defined. 51 | 52 | .. note:: 53 | This target has ``cxx_std_17`` as an ``INTERFACE`` 54 | :ref:`compile language standard feature `. Linking 55 | to this target will automatically enable C++17 if no later standard 56 | version is already required on the linking target. 57 | 58 | 59 | .. _fs.variables: 60 | 61 | Variables 62 | ********* 63 | 64 | .. variable:: CXX_FILESYSTEM_IS_EXPERIMENTAL 65 | 66 | Set to ``TRUE`` when the :find-component:`fs.Experimental` version of C++ 67 | filesystem library was found, otherwise ``FALSE``. 68 | 69 | .. variable:: CXX_FILESYSTEM_HAVE_FS 70 | 71 | Set to ``TRUE`` when a filesystem header was found. 72 | 73 | .. variable:: CXX_FILESYSTEM_HEADER 74 | 75 | Set to either ``filesystem`` or ``experimental/filesystem`` depending on 76 | whether :find-component:`fs.Final` or :find-component:`fs.Experimental` was 77 | found. 78 | 79 | .. variable:: CXX_FILESYSTEM_NAMESPACE 80 | 81 | Set to either ``std::filesystem`` or ``std::experimental::filesystem`` 82 | depending on whether :find-component:`fs.Final` or 83 | :find-component:`fs.Experimental` was found. 84 | 85 | 86 | Examples 87 | ******** 88 | 89 | Using `find_package(Filesystem)` with no component arguments: 90 | 91 | .. code-block:: cmake 92 | 93 | find_package(Filesystem REQUIRED) 94 | 95 | add_executable(my-program main.cpp) 96 | target_link_libraries(my-program PRIVATE std::filesystem) 97 | 98 | 99 | #]=======================================================================] 100 | 101 | 102 | if(TARGET std::filesystem) 103 | # This module has already been processed. Don't do it again. 104 | return() 105 | endif() 106 | 107 | cmake_minimum_required(VERSION 3.10) 108 | 109 | include(CMakePushCheckState) 110 | include(CheckIncludeFileCXX) 111 | 112 | # If we're not cross-compiling, try to run test executables. 113 | # Otherwise, assume that compile + link is a sufficient check. 114 | if(CMAKE_CROSSCOMPILING) 115 | include(CheckCXXSourceCompiles) 116 | macro(_cmcm_check_cxx_source code var) 117 | check_cxx_source_compiles("${code}" ${var}) 118 | endmacro() 119 | else() 120 | include(CheckCXXSourceRuns) 121 | macro(_cmcm_check_cxx_source code var) 122 | check_cxx_source_runs("${code}" ${var}) 123 | endmacro() 124 | endif() 125 | 126 | cmake_push_check_state() 127 | 128 | set(CMAKE_REQUIRED_QUIET ${Filesystem_FIND_QUIETLY}) 129 | 130 | # All of our tests required C++17 or later 131 | set(CMAKE_CXX_STANDARD 17) 132 | 133 | # Normalize and check the component list we were given 134 | set(want_components ${Filesystem_FIND_COMPONENTS}) 135 | if(Filesystem_FIND_COMPONENTS STREQUAL "") 136 | set(want_components Final) 137 | endif() 138 | 139 | # Warn on any unrecognized components 140 | set(extra_components ${want_components}) 141 | list(REMOVE_ITEM extra_components Final Experimental) 142 | foreach(component IN LISTS extra_components) 143 | message(WARNING "Extraneous find_package component for Filesystem: ${component}") 144 | endforeach() 145 | 146 | # Detect which of Experimental and Final we should look for 147 | set(find_experimental TRUE) 148 | set(find_final TRUE) 149 | if(NOT "Final" IN_LIST want_components) 150 | set(find_final FALSE) 151 | endif() 152 | if(NOT "Experimental" IN_LIST want_components) 153 | set(find_experimental FALSE) 154 | endif() 155 | 156 | if(find_final) 157 | check_include_file_cxx("filesystem" _CXX_FILESYSTEM_HAVE_HEADER) 158 | mark_as_advanced(_CXX_FILESYSTEM_HAVE_HEADER) 159 | if(_CXX_FILESYSTEM_HAVE_HEADER) 160 | # We found the non-experimental header. Don't bother looking for the 161 | # experimental one. 162 | set(find_experimental FALSE) 163 | endif() 164 | else() 165 | set(_CXX_FILESYSTEM_HAVE_HEADER FALSE) 166 | endif() 167 | 168 | if(find_experimental) 169 | check_include_file_cxx("experimental/filesystem" _CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER) 170 | mark_as_advanced(_CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER) 171 | else() 172 | set(_CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER FALSE) 173 | endif() 174 | 175 | if(_CXX_FILESYSTEM_HAVE_HEADER) 176 | set(_have_fs TRUE) 177 | set(_fs_header filesystem) 178 | set(_fs_namespace std::filesystem) 179 | set(_is_experimental FALSE) 180 | elseif(_CXX_FILESYSTEM_HAVE_EXPERIMENTAL_HEADER) 181 | set(_have_fs TRUE) 182 | set(_fs_header experimental/filesystem) 183 | set(_fs_namespace std::experimental::filesystem) 184 | set(_is_experimental TRUE) 185 | else() 186 | set(_have_fs FALSE) 187 | endif() 188 | 189 | set(CXX_FILESYSTEM_HAVE_FS ${_have_fs} CACHE BOOL "TRUE if we have the C++ filesystem headers") 190 | set(CXX_FILESYSTEM_HEADER ${_fs_header} CACHE STRING "The header that should be included to obtain the filesystem APIs") 191 | set(CXX_FILESYSTEM_NAMESPACE ${_fs_namespace} CACHE STRING "The C++ namespace that contains the filesystem APIs") 192 | set(CXX_FILESYSTEM_IS_EXPERIMENTAL ${_is_experimental} CACHE BOOL "TRUE if the C++ filesystem library is the experimental version") 193 | 194 | set(_found FALSE) 195 | 196 | if(CXX_FILESYSTEM_HAVE_FS) 197 | # We have some filesystem library available. Do link checks 198 | string(CONFIGURE [[ 199 | #include 200 | #include <@CXX_FILESYSTEM_HEADER@> 201 | 202 | int main() { 203 | auto cwd = @CXX_FILESYSTEM_NAMESPACE@::current_path(); 204 | printf("%s", cwd.c_str()); 205 | return EXIT_SUCCESS; 206 | } 207 | ]] code @ONLY) 208 | 209 | # Check a simple filesystem program without any linker flags 210 | _cmcm_check_cxx_source("${code}" CXX_FILESYSTEM_NO_LINK_NEEDED) 211 | 212 | set(can_link ${CXX_FILESYSTEM_NO_LINK_NEEDED}) 213 | 214 | if(NOT CXX_FILESYSTEM_NO_LINK_NEEDED) 215 | set(prev_libraries ${CMAKE_REQUIRED_LIBRARIES}) 216 | # Add the libstdc++ flag 217 | set(CMAKE_REQUIRED_LIBRARIES ${prev_libraries} -lstdc++fs) 218 | _cmcm_check_cxx_source("${code}" CXX_FILESYSTEM_STDCPPFS_NEEDED) 219 | set(can_link ${CXX_FILESYSTEM_STDCPPFS_NEEDED}) 220 | if(NOT CXX_FILESYSTEM_STDCPPFS_NEEDED) 221 | # Try the libc++ flag 222 | set(CMAKE_REQUIRED_LIBRARIES ${prev_libraries} -lc++fs) 223 | _cmcm_check_cxx_source("${code}" CXX_FILESYSTEM_CPPFS_NEEDED) 224 | set(can_link ${CXX_FILESYSTEM_CPPFS_NEEDED}) 225 | endif() 226 | endif() 227 | 228 | if(can_link) 229 | add_library(std::filesystem INTERFACE IMPORTED) 230 | set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_COMPILE_FEATURES cxx_std_17) 231 | set(_found TRUE) 232 | 233 | if(CXX_FILESYSTEM_NO_LINK_NEEDED) 234 | # Nothing to add... 235 | elseif(CXX_FILESYSTEM_STDCPPFS_NEEDED) 236 | set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_LINK_LIBRARIES -lstdc++fs) 237 | elseif(CXX_FILESYSTEM_CPPFS_NEEDED) 238 | set_property(TARGET std::filesystem APPEND PROPERTY INTERFACE_LINK_LIBRARIES -lc++fs) 239 | endif() 240 | endif() 241 | endif() 242 | 243 | cmake_pop_check_state() 244 | 245 | set(Filesystem_FOUND ${_found} CACHE BOOL "TRUE if we can run a program using std::filesystem" FORCE) 246 | 247 | if(Filesystem_FIND_REQUIRED AND NOT Filesystem_FOUND) 248 | message(FATAL_ERROR "Cannot run simple program using std::filesystem") 249 | endif() -------------------------------------------------------------------------------- /include/magnifier/BitcodeExplorer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-present, Trail of Bits, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed in accordance with the terms specified in 6 | * the LICENSE file found in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | namespace llvm { 22 | class AssemblyAnnotationWriter; 23 | class Function; 24 | class Instruction; 25 | class LLVMContext; 26 | class Module; 27 | class raw_ostream; 28 | class WeakVH; 29 | class Value; 30 | class Use; 31 | class FunctionCallee; 32 | class Type; 33 | class BasicBlock; 34 | class CallInst; 35 | } // namespace llvm 36 | 37 | namespace magnifier { 38 | class IdCommentWriter; 39 | class IFunctionResolver; 40 | 41 | using ValueId = uint64_t; 42 | static constexpr ValueId kInvalidValueId = 0; 43 | enum class ValueIdKind { 44 | kOriginal, // Corresponds with `!explorer.source_id`. 45 | kDerived, // Corresponds with `!explorer.id`. 46 | kBlock, // Corresponds with `!explorer.block_id`. 47 | kSubstitution, // Corresponds with `!explorer.substitution_kind_id`. 48 | }; 49 | 50 | enum class FunctionKind { 51 | kOriginal, // Functions directly loaded from llvm bitcode files 52 | kGenerated, // Functions generated after an operation (inlining, etc) 53 | }; 54 | 55 | enum class InlineError { 56 | kNotACallBaseInstruction, // Not a CallBase instruction 57 | kInstructionNotFound, // Instruction not found 58 | kCannotResolveFunction, // Cannot resolve function 59 | kInlineOperationFailed, // Inline operation failed 60 | kVariadicFunction, // Inlining variadic function is yet to be supported 61 | kResolveFunctionTypeMismatch, // Resolve function type mismatch 62 | }; 63 | 64 | enum class SubstitutionError { 65 | kIdNotFound, // ValueId not found 66 | kIncorrectType, // Instruction is not of the desired type 67 | kCannotUseFunctionId, // Expecting an instruction id instead of a function id 68 | }; 69 | 70 | enum class OptimizationError { 71 | kInvalidOptimizationLevel, // The provided optimization level is not allowed 72 | kIdNotFound, // Function id not found 73 | }; 74 | 75 | enum class DeletionError { 76 | kIdNotFound, // Function id not found 77 | kFunctionInUse, // Function is still in use 78 | }; 79 | 80 | enum class DevirtualizeError { 81 | kInstructionNotFound, // Instruction not found 82 | kNotACallBaseInstruction, // Not a CallBase instruction 83 | kFunctionNotFound, // Function not found 84 | kNotAIndirectCall, // Instruction do not refer to an indirect call 85 | kArgNumMismatch, // Function takes a different number of parameter 86 | }; 87 | 88 | class BitcodeExplorer { 89 | private: 90 | // Reference to the llvm context 91 | llvm::LLVMContext &llvm_context; 92 | // ID of the `!explorer.id` metadata. This metadata holds the unique ID of 93 | // this value. 94 | const unsigned md_explorer_id; 95 | // ID of the `!explorer.source_id` metadata. This metadata tracks the 96 | // provenance of a value. If a value, e.g. an instruction, is from a function, 97 | // then usually the `!explorer.id` and `!explorer.source_id` match. However, 98 | // if the value has been subject to mutation or inlining, then the 99 | // `!explorer.source_id` will stay constant while the `!explorer.id` will 100 | // change. 101 | const unsigned md_explorer_source_id; 102 | // ID of the `!explorer.block_id` metadata. This metadata should only be 103 | // attached to terminator instructions of basic blocks. It serves a similar 104 | // purpose as `!explorer.id` but for uniquely identifying `BasicBlock` values. 105 | const unsigned md_explorer_block_id; 106 | // ID of the `!explorer.substitution_kind_id` metadata. This metadata should 107 | // only be attached to instructions that are going to be substituted by 108 | // `ElideSubstitutionHooks`. It helps determine the `SubstitutionKind` of that 109 | // instruction. It's most commonly applied to `CallInst` values calling the 110 | // substitute hook function. 111 | const unsigned md_explorer_substitution_kind_id; 112 | // Annotator object used for annotating function disassembly. It prints the 113 | // various metadata attached to each value. 114 | std::unique_ptr annotator; 115 | // This is a vector of all the llvm `Module` objects ingested using 116 | // `TakeModule`. 117 | std::vector> opened_modules; 118 | // A map between unique `ValueId`s and their corresponding functions. 119 | std::map function_map; 120 | // A map between unique `ValueId`s and their corresponding instructions. 121 | std::map instruction_map; 122 | // A map between unique `ValueId`s and their corresponding basic blocks. 123 | std::map block_map; 124 | // A map between unique `ValueId`s and their corresponding function arguments. 125 | std::map argument_map; 126 | // An increment only counter used for assigning unique ids to values. 127 | ValueId value_id_counter; 128 | // A temporary map between types and their corresponding substitute hook 129 | // functions. This map helps keep track of hooks that needs to be elided 130 | // during an operation. It should be cleared at the end of any high-level 131 | // operation. 132 | std::map hook_functions; 133 | 134 | // Elide all substitute hooks present in `function`. Uses 135 | // `substitute_value_func` to guide the substitution process 136 | void ElideSubstitutionHooks(llvm::Function &function, 137 | ISubstitutionObserver &substitution_observer); 138 | 139 | // Convert from opaque type `ValueIdKind` to actual llvm `kind_id`. 140 | [[nodiscard]] unsigned ValueIdKindToKindId(ValueIdKind kind) const; 141 | 142 | // Get a `FunctionCallee` object for the given `type`. Create the function in 143 | // `func_module` if it doesn't exist. In addition, the object is added to the 144 | // `hook_functions` map. 145 | llvm::FunctionCallee GetHookFunction(llvm::Type *type, 146 | llvm::Module *func_module); 147 | 148 | // Create and return a `CallInst` for calling the substitution hook. 149 | // It also attaches the correct `SubstitutionKind` metadata to the 150 | // instruction. 151 | llvm::CallInst *CreateHookCallInst(llvm::Type *type, 152 | llvm::Module *func_module, 153 | SubstitutionKind hook_kind, 154 | llvm::Value *old_val, 155 | llvm::Value *new_val); 156 | 157 | // Update/index a function by adding various metadata to function, 158 | // instruction, and block values. Also update `function_map`, 159 | // `instruction_map`, and `block_map` to reflect the changes. 160 | void UpdateMetadata(llvm::Function &function); 161 | 162 | public: 163 | explicit BitcodeExplorer(llvm::LLVMContext &llvm_context); 164 | 165 | BitcodeExplorer(BitcodeExplorer &&) = default; 166 | 167 | ~BitcodeExplorer(); 168 | 169 | // Ingest `module` and take ownership. 170 | // It updates `opened_modules` and indexes all the functions inside the 171 | // module. 172 | void TakeModule(std::unique_ptr module); 173 | 174 | // Invoke `callback` on every indexed function while providing its `ValueID` 175 | // and `FunctionKind`. 176 | void ForEachFunction(const std::function &callback); 178 | 179 | // Given the function id, print function disassembly to `output_stream` 180 | bool PrintFunction(ValueId function_id, llvm::raw_ostream &output_stream); 181 | 182 | // Inline a call instruction 183 | Result InlineFunctionCall( 184 | ValueId instruction_id, IFunctionResolver &resolver, 185 | ISubstitutionObserver &substitution_observer); 186 | 187 | // Substitute an instruction with integer value 188 | Result SubstituteInstructionWithValue( 189 | ValueId instruction_id, uint64_t value, ISubstitutionObserver &observer); 190 | 191 | // Substitute an argument with integer value 192 | Result SubstituteArgumentWithValue( 193 | ValueId argument_id, uint64_t value, ISubstitutionObserver &observer); 194 | 195 | // Optimize a function using a certain `optimization_level` 196 | Result OptimizeFunction( 197 | ValueId function_id, 198 | const llvm::OptimizationLevel &optimization_level); 199 | 200 | // Delete a function that is not in use 201 | std::optional DeleteFunction(ValueId function_id); 202 | 203 | // Devirtualize an indirect function call into a direct one 204 | Result DevirtualizeFunction( 205 | ValueId instruction_id, ValueId function_id, 206 | ISubstitutionObserver &substitution_observer); 207 | 208 | // Returns the value ID for `function`, or `kInvalidValueId` if no ID is 209 | // found. 210 | [[nodiscard]] ValueId GetId(const llvm::Function &function, 211 | ValueIdKind kind) const; 212 | 213 | // Returns the value ID for `instruction`, or `kInvalidValueId` if no ID is 214 | // found. 215 | [[nodiscard]] ValueId GetId(const llvm::Instruction &instruction, 216 | ValueIdKind kind) const; 217 | 218 | // Set an id inside the metadata of a function. 219 | void SetId(llvm::Function &function, ValueId value, ValueIdKind kind) const; 220 | 221 | // Set an id inside the metadata of an instruction. 222 | void SetId(llvm::Instruction &instruction, ValueId value, 223 | ValueIdKind kind) const; 224 | 225 | // Remove id of `kind` from `function` metadata. 226 | void RemoveId(llvm::Function &function, ValueIdKind kind); 227 | 228 | // Remove id of `kind` from `instruction` metadata. 229 | void RemoveId(llvm::Instruction &instruction, ValueIdKind kind); 230 | 231 | ValueId MaxCurrentID(); 232 | 233 | std::optional GetFunctionById(ValueId); 234 | 235 | // Either gets the current id for a function or indexes the function. 236 | ValueId IndexFunction(llvm::Function &function); 237 | }; 238 | } // namespace magnifier 239 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /bin/repl/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-present, Trail of Bits, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed in accordance with the terms specified in 6 | * the LICENSE file found in the root directory of this source tree. 7 | */ 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | std::vector split(const std::string &input, char delimiter) { 32 | std::stringstream ss(input); 33 | std::vector results; 34 | std::string token; 35 | while (std::getline(ss, token, delimiter)) { 36 | results.push_back(token); 37 | } 38 | return results; 39 | } 40 | 41 | class FunctionResolver : public magnifier::IFunctionResolver { 42 | llvm::Function *ResolveCallSite(llvm::CallBase *call_base, llvm::Function *called_function) override { 43 | return called_function; 44 | } 45 | }; 46 | 47 | class SubstitutionObserver : public magnifier::ISubstitutionObserver { 48 | private: 49 | llvm::ToolOutputFile &tool_output; 50 | public: 51 | explicit SubstitutionObserver(llvm::ToolOutputFile &tool_output): tool_output(tool_output) {}; 52 | 53 | llvm::Value *PerformSubstitution(llvm::Instruction *instr, llvm::Value *old_val, llvm::Value *new_val, magnifier::SubstitutionKind kind) override { 54 | static const std::unordered_map substitution_kind_map = { 55 | {magnifier::SubstitutionKind::kReturnValue, "Return value"}, 56 | {magnifier::SubstitutionKind::kArgument, "Argument"}, 57 | {magnifier::SubstitutionKind::kConstantFolding, "Constant folding"}, 58 | {magnifier::SubstitutionKind::kValueSubstitution, "Value substitution"}, 59 | {magnifier::SubstitutionKind::kFunctionDevirtualization, "Function devirtualization"}, 60 | }; 61 | tool_output.os() << "perform substitution: "; 62 | instr->print(tool_output.os()); 63 | tool_output.os() << " : " << substitution_kind_map.at(kind) << "\n"; 64 | return new_val; 65 | } 66 | }; 67 | 68 | void RunOptimization(magnifier::BitcodeExplorer &explorer, llvm::ToolOutputFile &tool_output, magnifier::ValueId function_id, llvm::OptimizationLevel level) { 69 | static const std::unordered_map optimization_error_map = { 70 | {magnifier::OptimizationError::kInvalidOptimizationLevel, "The provided optimization level is not allowed"}, 71 | {magnifier::OptimizationError::kIdNotFound, "Function id not found"}, 72 | }; 73 | 74 | magnifier::Result result = explorer.OptimizeFunction(function_id, level); 75 | if (result.Succeeded()) { 76 | explorer.PrintFunction(result.Value(), tool_output.os()); 77 | } else { 78 | tool_output.os() << "Optimize function failed for id: " << function_id << " (error: " << optimization_error_map.at(result.Error()) << ")\n"; 79 | } 80 | } 81 | 82 | int main(int argc, char **argv) { 83 | llvm::InitLLVM x(argc, argv); 84 | llvm::LLVMContext llvm_context; 85 | llvm::ExitOnError llvm_exit_on_err; 86 | llvm_exit_on_err.setBanner("llvm error: "); 87 | 88 | magnifier::BitcodeExplorer explorer(llvm_context); 89 | 90 | 91 | std::error_code error_code; 92 | llvm::ToolOutputFile tool_output("-", error_code,llvm::sys::fs::OF_Text); 93 | if (error_code) { 94 | std::cerr << error_code.message() << std::endl; 95 | return -1; 96 | } 97 | 98 | FunctionResolver resolver{}; 99 | SubstitutionObserver substitution_observer(tool_output); 100 | 101 | std::unordered_map &)>> cmd_map = { 102 | // Load module: `lm ` 103 | {"lm", [&explorer, &llvm_exit_on_err, &llvm_context, &tool_output](const std::vector &args) -> void { 104 | if (args.size() != 2) { 105 | tool_output.os() << "Usage: lm - Load/open an LLVM .bc or .ll module\n"; 106 | return; 107 | } 108 | const std::string &filename = args[1]; 109 | const std::filesystem::path file_path = std::filesystem::path(filename); 110 | 111 | if (!std::filesystem::exists(file_path) || !std::filesystem::is_regular_file(file_path)) { 112 | tool_output.os() << "Unable to open file: " << filename << "\n"; 113 | return; 114 | } 115 | 116 | std::unique_ptr llvm_memory_buffer = llvm_exit_on_err( 117 | errorOrToExpected(llvm::MemoryBuffer::getFileOrSTDIN(filename))); 118 | llvm::BitcodeFileContents llvm_bitcode_contents = llvm_exit_on_err( 119 | llvm::getBitcodeFileContents(*llvm_memory_buffer)); 120 | 121 | for (auto &llvm_mod: llvm_bitcode_contents.Mods) { 122 | std::unique_ptr mod = llvm_exit_on_err(llvm_mod.parseModule(llvm_context)); 123 | explorer.TakeModule(std::move(mod)); 124 | } 125 | }}, 126 | // List functions: `lf` 127 | {"lf", [&explorer, &tool_output](const std::vector &args) -> void { 128 | if (args.size() != 1) { 129 | tool_output.os() << "Usage: lf - List all functions in all open modules\n"; 130 | return; 131 | } 132 | 133 | explorer.ForEachFunction([&tool_output](magnifier::ValueId function_id, llvm::Function &function, magnifier::FunctionKind kind) -> void { 134 | if (function.hasName() && kind == magnifier::FunctionKind::kOriginal) { 135 | tool_output.os() << function_id << " " << function.getName().str() << "\n"; 136 | } 137 | }); 138 | }}, 139 | // Print function: `pf ` 140 | {"pf", [&explorer, &tool_output](const std::vector &args) -> void { 141 | if (args.size() != 2) { 142 | tool_output.os() << "Usage: pf - Print function\n"; 143 | return; 144 | } 145 | 146 | magnifier::ValueId function_id = std::stoul(args[1], nullptr, 10); 147 | if (!explorer.PrintFunction(function_id, tool_output.os())) { 148 | tool_output.os() << "Function not found: " << function_id << "\n"; 149 | } 150 | }}, 151 | // Devirtualize function: `dc ` 152 | {"dc", [&explorer, &tool_output, &substitution_observer](const std::vector &args) -> void { 153 | static const std::unordered_map devirtualize_error_map = { 154 | {magnifier::DevirtualizeError::kNotACallBaseInstruction, "Not a CallBase instruction"}, 155 | {magnifier::DevirtualizeError::kInstructionNotFound, "Instruction not found"}, 156 | {magnifier::DevirtualizeError::kFunctionNotFound, "Function not found"}, 157 | {magnifier::DevirtualizeError::kNotAIndirectCall, "Can only devirtualize indirect call"}, 158 | {magnifier::DevirtualizeError::kArgNumMismatch, "Function takes a different number of parameter"} 159 | }; 160 | 161 | if (args.size() != 3) { 162 | tool_output.os() << "Usage: dc - Devirtualize function\n"; 163 | return; 164 | } 165 | 166 | magnifier::ValueId instruction_id = std::stoul(args[1], nullptr, 10); 167 | magnifier::ValueId function_id = std::stoul(args[2], nullptr, 10); 168 | magnifier::Result result = explorer.DevirtualizeFunction(instruction_id, function_id, substitution_observer); 169 | 170 | 171 | if (result.Succeeded()) { 172 | explorer.PrintFunction(result.Value(), tool_output.os()); 173 | } else { 174 | tool_output.os() << "Devirtualize function call failed for id: " << instruction_id << " (error: " << devirtualize_error_map.at(result.Error()) << ")\n"; 175 | } 176 | }}, 177 | // Delete function: `df! ` 178 | {"df!", [&explorer, &tool_output](const std::vector &args) -> void { 179 | static const std::unordered_map deletion_error_map = { 180 | {magnifier::DeletionError::kIdNotFound, "Function id not found"}, 181 | {magnifier::DeletionError::kFunctionInUse, "Function is still in use"}, 182 | }; 183 | 184 | if (args.size() != 2) { 185 | tool_output.os() << "Usage: df! - Delete function\n"; 186 | return; 187 | } 188 | 189 | magnifier::ValueId function_id = std::stoul(args[1], nullptr, 10); 190 | std::optional result = explorer.DeleteFunction(function_id); 191 | if (!result) { 192 | tool_output.os() << "Deleted function with id: " << function_id << "\n"; 193 | } else { 194 | tool_output.os() << "Delete function failed for id: " << function_id << " (error: " << deletion_error_map.at(result.value()) << ")\n"; 195 | } 196 | }}, 197 | // Inline function call: `ic ` 198 | {"ic", [&explorer, &tool_output, &resolver, &substitution_observer](const std::vector &args) -> void { 199 | static const std::unordered_map inline_error_map = { 200 | {magnifier::InlineError::kNotACallBaseInstruction, "Not a CallBase instruction"}, 201 | {magnifier::InlineError::kInstructionNotFound, "Instruction not found"}, 202 | {magnifier::InlineError::kCannotResolveFunction, "Cannot resolve function"}, 203 | {magnifier::InlineError::kInlineOperationFailed, "Inline operation failed"}, 204 | {magnifier::InlineError::kVariadicFunction, "Inlining variadic function is yet to be supported"}, 205 | {magnifier::InlineError::kResolveFunctionTypeMismatch, "Resolve function type mismatch"}, 206 | }; 207 | 208 | if (args.size() != 2) { 209 | tool_output.os() << "Usage: ic - Inline function call\n"; 210 | return; 211 | } 212 | 213 | magnifier::ValueId instruction_id = std::stoul(args[1], nullptr, 10); 214 | magnifier::Result result = explorer.InlineFunctionCall(instruction_id, resolver, substitution_observer); 215 | 216 | if (result.Succeeded()) { 217 | explorer.PrintFunction(result.Value(), tool_output.os()); 218 | } else { 219 | tool_output.os() << "Inline function call failed for id: " << instruction_id << " (error: " << inline_error_map.at(result.Error()) << ")\n"; 220 | } 221 | }}, 222 | // Substitute with value: `sv ` 223 | {"sv", [&explorer, &tool_output, &substitution_observer](const std::vector &args) -> void { 224 | static const std::unordered_map substitution_error_map = { 225 | {magnifier::SubstitutionError::kIdNotFound, "Instruction not found"}, 226 | {magnifier::SubstitutionError::kIncorrectType, "Instruction has non-integer type"}, 227 | {magnifier::SubstitutionError::kCannotUseFunctionId, "Expecting an instruction id instead of a function id"}, 228 | }; 229 | 230 | if (args.size() != 3) { 231 | std::cout << "Usage: sv - Substitute with value" << std::endl; 232 | return; 233 | } 234 | 235 | magnifier::ValueId value_id = std::stoul(args[1], nullptr, 10); 236 | uint64_t value = std::stoul(args[2], nullptr, 10); 237 | 238 | // Try treating `value_id` as an instruction id 239 | magnifier::Result result = explorer.SubstituteInstructionWithValue(value_id, value, substitution_observer); 240 | 241 | if (result.Succeeded()) { 242 | explorer.PrintFunction(result.Value(), tool_output.os()); 243 | return; 244 | } 245 | 246 | if (result.Error() != magnifier::SubstitutionError::kIdNotFound) { 247 | tool_output.os() << "Substitute value failed for id: " << value_id << " (error: " << substitution_error_map.at(result.Error()) << ")\n"; 248 | return; 249 | } 250 | 251 | // Try treating `value_id` as an argument id 252 | result = explorer.SubstituteArgumentWithValue(value_id, value, substitution_observer); 253 | 254 | if (result.Succeeded()) { 255 | explorer.PrintFunction(result.Value(), tool_output.os()); 256 | } else { 257 | tool_output.os() << "Substitute value failed for id: " << value_id << " (error: " << substitution_error_map.at(result.Error()) << ")\n"; 258 | } 259 | }}, 260 | // Optimize function bitcode using optimization level -O1: `o1 ` 261 | {"o1", [&explorer, &tool_output, &substitution_observer](const std::vector &args) -> void { 262 | if (args.size() != 2) { 263 | std::cout << "Usage: o1 - Optimize function bitcode using optimization level -O1" << std::endl; 264 | return; 265 | } 266 | 267 | magnifier::ValueId function_id = std::stoul(args[1], nullptr, 10); 268 | RunOptimization(explorer, tool_output, function_id, llvm::OptimizationLevel::O1); 269 | }}, 270 | // Optimize function bitcode using optimization level -O2: `o2 ` 271 | {"o2", [&explorer, &tool_output, &substitution_observer](const std::vector &args) -> void { 272 | if (args.size() != 2) { 273 | std::cout << "Usage: o2 - Optimize function bitcode using optimization level -O2" << std::endl; 274 | return; 275 | } 276 | 277 | magnifier::ValueId function_id = std::stoul(args[1], nullptr, 10); 278 | RunOptimization(explorer, tool_output, function_id, llvm::OptimizationLevel::O2); 279 | }}, 280 | // Optimize function bitcode using optimization level -O3: `o3 ` 281 | {"o3", [&explorer, &tool_output, &substitution_observer](const std::vector &args) -> void { 282 | if (args.size() != 2) { 283 | std::cout << "Usage: o3 - Optimize function bitcode using optimization level -O3" << std::endl; 284 | return; 285 | } 286 | 287 | magnifier::ValueId function_id = std::stoul(args[1], nullptr, 10); 288 | RunOptimization(explorer, tool_output, function_id, llvm::OptimizationLevel::O3); 289 | }}, 290 | }; 291 | 292 | // cmd_map["lm"](split("lm ../test.bc", ' ')); 293 | while (true) { 294 | std::string input; 295 | tool_output.os() << ">> "; 296 | std::getline(std::cin, input); 297 | 298 | std::vector tokenized_input = split(input, ' '); 299 | if (tokenized_input.empty()) { 300 | tool_output.os() << "Invalid Command\n"; 301 | continue; 302 | } 303 | 304 | if (tokenized_input[0] == "exit") { break; } 305 | 306 | auto cmd = cmd_map.find(tokenized_input[0]); 307 | if (cmd != cmd_map.end()) { 308 | cmd->second(tokenized_input); 309 | } else { 310 | tool_output.os() << "Invalid Command: " << tokenized_input[0] << "\n"; 311 | } 312 | } 313 | return 0; 314 | } 315 | -------------------------------------------------------------------------------- /bin/magnifier-ui/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-present, Trail of Bits, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed in accordance with the terms specified in 6 | * the LICENSE file found in the root directory of this source tree. 7 | */ 8 | 9 | 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include "Printer.h" 44 | 45 | std::vector split(const std::string &input, char delimiter) { 46 | std::stringstream ss(input); 47 | std::vector results; 48 | std::string token; 49 | while (std::getline(ss, token, delimiter)) { 50 | results.push_back(token); 51 | } 52 | return results; 53 | } 54 | 55 | class FunctionResolver : public magnifier::IFunctionResolver { 56 | llvm::Function *ResolveCallSite(llvm::CallBase *call_base, llvm::Function *called_function) override { 57 | return called_function; 58 | } 59 | }; 60 | 61 | class SubstitutionObserver : public magnifier::ISubstitutionObserver { 62 | private: 63 | llvm::raw_ostream &output_stream; 64 | public: 65 | explicit SubstitutionObserver(llvm::raw_ostream &output_stream): output_stream(output_stream) {}; 66 | 67 | llvm::Value *PerformSubstitution(llvm::Instruction *instr, llvm::Value *old_val, llvm::Value *new_val, magnifier::SubstitutionKind kind) override { 68 | static const std::unordered_map substitution_kind_map = { 69 | {magnifier::SubstitutionKind::kReturnValue, "Return value"}, 70 | {magnifier::SubstitutionKind::kArgument, "Argument"}, 71 | {magnifier::SubstitutionKind::kConstantFolding, "Constant folding"}, 72 | {magnifier::SubstitutionKind::kValueSubstitution, "Value substitution"}, 73 | {magnifier::SubstitutionKind::kFunctionDevirtualization, "Function devirtualization"}, 74 | }; 75 | output_stream << "perform substitution: "; 76 | instr->print(output_stream); 77 | output_stream << " : " << substitution_kind_map.at(kind) << "\n"; 78 | return new_val; 79 | } 80 | }; 81 | 82 | class AAW : public llvm::AssemblyAnnotationWriter { 83 | private: 84 | magnifier::BitcodeExplorer &explorer; 85 | public: 86 | explicit AAW(magnifier::BitcodeExplorer &explorer) : explorer(explorer) {} 87 | 88 | void emitInstructionAnnot(const llvm::Instruction *instruction, llvm::formatted_raw_ostream &os) override { 89 | os << ""; 92 | 93 | magnifier::ValueId instruction_id = explorer.GetId(*instruction, magnifier::ValueIdKind::kDerived); 94 | magnifier::ValueId source_id = explorer.GetId(*instruction, magnifier::ValueIdKind::kOriginal); 95 | 96 | os << instruction_id << "|" << source_id; 97 | } 98 | 99 | void emitFunctionAnnot(const llvm::Function *function, llvm::formatted_raw_ostream &os) override { 100 | os << ""; 103 | 104 | magnifier::ValueId function_id = explorer.GetId(*function, magnifier::ValueIdKind::kDerived); 105 | magnifier::ValueId source_id = explorer.GetId(*function, magnifier::ValueIdKind::kOriginal); 106 | 107 | if (!function->arg_empty()) { 108 | os << "Function argument ids: "; 109 | for (const llvm::Argument &argument : function->args()) { 110 | os << "(%" << argument.getName().str() << " = " << (function_id+argument.getArgNo()+1) << ") "; 111 | } 112 | os << "\n"; 113 | } 114 | 115 | os << function_id << "|" << source_id; 116 | } 117 | 118 | void emitBasicBlockStartAnnot(const llvm::BasicBlock *block, llvm::formatted_raw_ostream &os) override { 119 | os << ""; 120 | 121 | const llvm::Instruction *terminator = block->getTerminator(); 122 | if (!terminator) { return; } 123 | os << "--- start block: " << explorer.GetId(*terminator, magnifier::ValueIdKind::kBlock) << " ---\n"; 124 | } 125 | 126 | void emitBasicBlockEndAnnot(const llvm::BasicBlock *block, llvm::formatted_raw_ostream &os) override { 127 | const llvm::Instruction *terminator = block->getTerminator(); 128 | if (!terminator) { return; } 129 | os << "--- end block: " << explorer.GetId(*terminator, magnifier::ValueIdKind::kBlock) << " ---\n"; 130 | 131 | os << ""; 132 | } 133 | 134 | void printInfoComment(const llvm::Value&, llvm::formatted_raw_ostream& os) override { 135 | os << ""; 136 | } 137 | }; 138 | 139 | struct UserData { 140 | std::unique_ptr llvm_context; 141 | std::unique_ptr explorer; 142 | std::unique_ptr rellic_result; 143 | }; 144 | 145 | 146 | std::string JsonToString(llvm::json::Object &&value) { 147 | std::string s; 148 | llvm::raw_string_ostream os(s); 149 | os << llvm::json::Value(std::move(value)); 150 | os.flush(); 151 | return s; 152 | } 153 | 154 | llvm::json::Value GetRellicProvenance(rellic::DecompilationResult &result) { 155 | llvm::json::Array stmt_provenance; 156 | for (auto elem : result.stmt_provenance_map) { 157 | stmt_provenance.push_back(llvm::json::Array( 158 | {(unsigned long long)elem.first, (unsigned long long)elem.second})); 159 | } 160 | 161 | llvm::json::Array type_decls; 162 | for (auto elem : result.type_to_decl_map) { 163 | type_decls.push_back(llvm::json::Array( 164 | {(unsigned long long)elem.first, (unsigned long long)elem.second})); 165 | } 166 | 167 | llvm::json::Array value_decls; 168 | for (auto elem : result.value_to_decl_map) { 169 | value_decls.push_back(llvm::json::Array( 170 | {(unsigned long long)elem.first, (unsigned long long)elem.second})); 171 | } 172 | 173 | llvm::json::Array use_provenance; 174 | for (auto elem : result.use_expr_map) { 175 | if (!elem.second) { 176 | continue; 177 | } 178 | use_provenance.push_back( 179 | llvm::json::Array({(unsigned long long)elem.first, 180 | (unsigned long long)elem.second})); 181 | } 182 | 183 | llvm::json::Object msg{{"stmt_provenance", std::move(stmt_provenance)}, 184 | {"type_decls", std::move(type_decls)}, 185 | {"value_decls", std::move(value_decls)}, 186 | {"use_provenance", std::move(use_provenance)}}; 187 | return msg; 188 | } 189 | 190 | void RunOptimization(magnifier::BitcodeExplorer &explorer, llvm::raw_ostream &tool_output, magnifier::ValueId function_id, llvm::OptimizationLevel level) { 191 | static const std::unordered_map optimization_error_map = { 192 | {magnifier::OptimizationError::kInvalidOptimizationLevel, "The provided optimization level is not allowed"}, 193 | {magnifier::OptimizationError::kIdNotFound, "Function id not found"}, 194 | }; 195 | 196 | magnifier::Result result = explorer.OptimizeFunction(function_id, level); 197 | if (result.Succeeded()) { 198 | explorer.PrintFunction(result.Value(), tool_output); 199 | } else { 200 | tool_output << "Optimize function failed for id: " << function_id << " (error: " << optimization_error_map.at(result.Error()) << ")\n"; 201 | } 202 | } 203 | 204 | 205 | 206 | llvm::json::Object HandleRequest(UserData *data, const llvm::json::Object &json) { 207 | static std::unordered_map &)>> cmd_map = { 208 | // Load module: `lm ` 209 | // {"lm", [](UserData *data, const llvm::json::Object &json, const std::vector &args) -> llvm::json::Value { 210 | // llvm::ExitOnError llvm_exit_on_err; 211 | // llvm_exit_on_err.setBanner("llvm error: "); 212 | 213 | // if (args.size() != 2) { 214 | // return "Usage: lm - Load/open an LLVM .bc or .ll module\n"; 215 | // } 216 | // const std::string &filename = args[1]; 217 | // const std::filesystem::path file_path = std::filesystem::path(filename); 218 | 219 | // if (!std::filesystem::exists(file_path) || !std::filesystem::is_regular_file(file_path)) { 220 | // return "Unable to open file: " + filename + "\n"; 221 | // } 222 | 223 | // std::unique_ptr llvm_memory_buffer = llvm_exit_on_err( 224 | // errorOrToExpected(llvm::MemoryBuffer::getFileOrSTDIN(filename))); 225 | // llvm::BitcodeFileContents llvm_bitcode_contents = llvm_exit_on_err( 226 | // llvm::getBitcodeFileContents(*llvm_memory_buffer)); 227 | 228 | // for (auto &llvm_mod: llvm_bitcode_contents.Mods) { 229 | // std::unique_ptr mod = llvm_exit_on_err(llvm_mod.parseModule(*data->llvm_context)); 230 | // data->explorer->TakeModule(std::move(mod)); 231 | // } 232 | // return "Successfully loaded: " + filename + "\n"; 233 | // }}, 234 | // List functions: `lf` 235 | {"lf", [](UserData *data, const llvm::json::Object &json, const std::vector &args) -> llvm::json::Value { 236 | if (args.size() != 1) { 237 | return "Usage: lf - List all functions in all open modules\n"; 238 | } 239 | 240 | std::string tool_str; 241 | llvm::raw_string_ostream tool_output(tool_str); 242 | 243 | data->explorer->ForEachFunction([&tool_output](magnifier::ValueId function_id, llvm::Function &function, magnifier::FunctionKind kind) -> void { 244 | if (function.hasName() && kind == magnifier::FunctionKind::kOriginal) { 245 | tool_output << function_id << " " << function.getName().str() << "\n"; 246 | } 247 | }); 248 | 249 | tool_output.flush(); 250 | return tool_str; 251 | }}, 252 | // List all functions including generated ones: `lfa` 253 | {"lfa", [](UserData *data, const llvm::json::Object &json, const std::vector &args) -> llvm::json::Value { 254 | if (args.size() != 1) { 255 | return "Usage: lf - List all functions in all open modules\n"; 256 | } 257 | 258 | std::string tool_str; 259 | llvm::raw_string_ostream tool_output(tool_str); 260 | 261 | data->explorer->ForEachFunction([&tool_output](magnifier::ValueId function_id, llvm::Function &function, magnifier::FunctionKind kind) -> void { 262 | if (function.hasName()) { 263 | tool_output << function_id << " " << function.getName().str() << "\n"; 264 | } 265 | }); 266 | 267 | tool_output.flush(); 268 | return tool_str; 269 | }}, 270 | // Print function: `pf ` 271 | {"pf", [](UserData *data, const llvm::json::Object &json, const std::vector &args) -> llvm::json::Value { 272 | if (args.size() != 2) { 273 | return "Usage: pf - Print function\n"; 274 | } 275 | 276 | std::string tool_str; 277 | llvm::raw_string_ostream tool_output(tool_str); 278 | 279 | magnifier::ValueId function_id; 280 | try { 281 | function_id = std::stoul(args[1], nullptr, 10); 282 | } catch (...) { 283 | return "Invalid args"; 284 | } 285 | 286 | if (data->explorer->PrintFunction(function_id, tool_output)) { 287 | tool_output.flush(); 288 | return tool_str; 289 | } else { 290 | return "Function not found: " + std::to_string(function_id) + "\n"; 291 | } 292 | }}, 293 | // Devirtualize function: `dc ` 294 | {"dc", [](UserData *data, const llvm::json::Object &json, const std::vector &args) -> llvm::json::Value { 295 | static const std::unordered_map devirtualize_error_map = { 296 | {magnifier::DevirtualizeError::kNotACallBaseInstruction, "Not a CallBase instruction"}, 297 | {magnifier::DevirtualizeError::kInstructionNotFound, "Instruction not found"}, 298 | {magnifier::DevirtualizeError::kFunctionNotFound, "Function not found"}, 299 | {magnifier::DevirtualizeError::kNotAIndirectCall, "Can only devirtualize indirect call"}, 300 | {magnifier::DevirtualizeError::kArgNumMismatch, "Function takes a different number of parameter"} 301 | }; 302 | 303 | if (args.size() != 3) { 304 | return "Usage: dc - Devirtualize function\n"; 305 | } 306 | 307 | std::string tool_str; 308 | llvm::raw_string_ostream tool_output(tool_str); 309 | SubstitutionObserver substitution_observer(tool_output); 310 | 311 | magnifier::ValueId instruction_id; 312 | magnifier::ValueId function_id; 313 | try { 314 | instruction_id = std::stoul(args[1], nullptr, 10); 315 | function_id = std::stoul(args[2], nullptr, 10); 316 | } catch (...) { 317 | return "Invalid args"; 318 | } 319 | 320 | 321 | magnifier::Result result = data->explorer->DevirtualizeFunction(instruction_id, function_id, substitution_observer); 322 | 323 | 324 | if (result.Succeeded()) { 325 | data->explorer->PrintFunction(result.Value(), tool_output); 326 | } else { 327 | tool_output << "Devirtualize function call failed for id: " << instruction_id << " (error: " << devirtualize_error_map.at(result.Error()) << ")\n"; 328 | } 329 | 330 | tool_output.flush(); 331 | return tool_str; 332 | }}, 333 | // Delete function: `df! ` 334 | {"df!", [](UserData *data, const llvm::json::Object &json, const std::vector &args) -> llvm::json::Value { 335 | static const std::unordered_map deletion_error_map = { 336 | {magnifier::DeletionError::kIdNotFound, "Function id not found"}, 337 | {magnifier::DeletionError::kFunctionInUse, "Function is still in use"}, 338 | }; 339 | 340 | if (args.size() != 2) { 341 | return "Usage: df! - Delete function\n"; 342 | } 343 | 344 | std::string tool_str; 345 | llvm::raw_string_ostream tool_output(tool_str); 346 | 347 | magnifier::ValueId function_id; 348 | try { 349 | function_id = std::stoul(args[1], nullptr, 10); 350 | } catch (...) { 351 | return "Invalid args"; 352 | } 353 | std::optional result = data->explorer->DeleteFunction(function_id); 354 | if (!result) { 355 | tool_output << "Deleted function with id: " << function_id << "\n"; 356 | } else { 357 | tool_output << "Delete function failed for id: " << function_id << " (error: " << deletion_error_map.at(result.value()) << ")\n"; 358 | } 359 | 360 | tool_output.flush(); 361 | return tool_str; 362 | }}, 363 | // Inline function call: `ic ` 364 | {"ic", [](UserData *data, const llvm::json::Object &json, const std::vector &args) -> llvm::json::Value { 365 | static const std::unordered_map inline_error_map = { 366 | {magnifier::InlineError::kNotACallBaseInstruction, "Not a CallBase instruction"}, 367 | {magnifier::InlineError::kInstructionNotFound, "Instruction not found"}, 368 | {magnifier::InlineError::kCannotResolveFunction, "Cannot resolve function"}, 369 | {magnifier::InlineError::kInlineOperationFailed, "Inline operation failed"}, 370 | {magnifier::InlineError::kVariadicFunction, "Inlining variadic function is yet to be supported"}, 371 | {magnifier::InlineError::kResolveFunctionTypeMismatch, "Resolve function type mismatch"}, 372 | }; 373 | 374 | if (args.size() != 2) { 375 | return "Usage: ic - Inline function call\n"; 376 | } 377 | 378 | std::string tool_str; 379 | llvm::raw_string_ostream tool_output(tool_str); 380 | FunctionResolver resolver{}; 381 | SubstitutionObserver substitution_observer(tool_output); 382 | 383 | magnifier::ValueId instruction_id; 384 | try { 385 | instruction_id = std::stoul(args[1], nullptr, 10); 386 | } catch (...) { 387 | return "Invalid args"; 388 | } 389 | 390 | magnifier::Result result = data->explorer->InlineFunctionCall(instruction_id, resolver, substitution_observer); 391 | 392 | if (result.Succeeded()) { 393 | data->explorer->PrintFunction(result.Value(), tool_output); 394 | } else { 395 | tool_output << "Inline function call failed for id: " << instruction_id << " (error: " << inline_error_map.at(result.Error()) << ")\n"; 396 | } 397 | 398 | tool_output.flush(); 399 | return tool_str; 400 | }}, 401 | // Substitute with value: `sv ` 402 | {"sv", [](UserData *data, const llvm::json::Object &json, const std::vector &args) -> llvm::json::Value { 403 | static const std::unordered_map substitution_error_map = { 404 | {magnifier::SubstitutionError::kIdNotFound, "Instruction not found"}, 405 | {magnifier::SubstitutionError::kIncorrectType, "Instruction has non-integer type"}, 406 | {magnifier::SubstitutionError::kCannotUseFunctionId, "Expecting an instruction id instead of a function id"}, 407 | }; 408 | 409 | if (args.size() != 3) { 410 | return "Usage: sv - Substitute with value\n"; 411 | } 412 | 413 | std::string tool_str; 414 | llvm::raw_string_ostream tool_output(tool_str); 415 | SubstitutionObserver substitution_observer(tool_output); 416 | 417 | magnifier::ValueId value_id; 418 | uint64_t value; 419 | try { 420 | value_id = std::stoul(args[1], nullptr, 10); 421 | value = std::stoul(args[2], nullptr, 10); 422 | } catch (...) { 423 | return "Invalid args"; 424 | } 425 | 426 | // Try treating `value_id` as an instruction id 427 | magnifier::Result result = data->explorer->SubstituteInstructionWithValue(value_id, value, substitution_observer); 428 | 429 | if (result.Succeeded()) { 430 | data->explorer->PrintFunction(result.Value(), tool_output); 431 | tool_output.flush(); 432 | return tool_str; 433 | } 434 | 435 | if (result.Error() != magnifier::SubstitutionError::kIdNotFound) { 436 | return "Substitute value failed for id: " + std::to_string(value_id) + " (error: " + substitution_error_map.at(result.Error()) + ")\n"; 437 | } 438 | 439 | // Try treating `value_id` as an argument id 440 | result = data->explorer->SubstituteArgumentWithValue(value_id, value, substitution_observer); 441 | 442 | if (result.Succeeded()) { 443 | data->explorer->PrintFunction(result.Value(), tool_output); 444 | tool_output.flush(); 445 | return tool_str; 446 | } else { 447 | return "Substitute value failed for id: " + std::to_string(value_id) + " (error: " + substitution_error_map.at(result.Error()) + ")\n"; 448 | } 449 | }}, 450 | // Optimize function bitcode using optimization level -O1: `o1 ` 451 | {"o1", [](UserData *data, const llvm::json::Object &json, const std::vector &args) -> llvm::json::Value { 452 | if (args.size() != 2) { 453 | return "Usage: o1 - Optimize function bitcode using optimization level -O1\n"; 454 | } 455 | 456 | std::string tool_str; 457 | llvm::raw_string_ostream tool_output(tool_str); 458 | 459 | magnifier::ValueId function_id; 460 | try { 461 | function_id = std::stoul(args[1], nullptr, 10); 462 | } catch (...) { 463 | return "Invalid args"; 464 | } 465 | RunOptimization(*data->explorer, tool_output, function_id, llvm::OptimizationLevel::O1); 466 | 467 | tool_output.flush(); 468 | return tool_str; 469 | }}, 470 | // Optimize function bitcode using optimization level -O2: `o2 ` 471 | {"o2", [](UserData *data, const llvm::json::Object &json, const std::vector &args) -> llvm::json::Value { 472 | if (args.size() != 2) { 473 | return "Usage: o2 - Optimize function bitcode using optimization level -O2\n"; 474 | } 475 | 476 | std::string tool_str; 477 | llvm::raw_string_ostream tool_output(tool_str); 478 | 479 | magnifier::ValueId function_id; 480 | try { 481 | function_id = std::stoul(args[1], nullptr, 10); 482 | } catch (...) { 483 | return "Invalid args"; 484 | } 485 | RunOptimization(*data->explorer, tool_output, function_id, llvm::OptimizationLevel::O2); 486 | 487 | tool_output.flush(); 488 | return tool_str; 489 | }}, 490 | // Optimize function bitcode using optimization level -O3: `o3 ` 491 | {"o3", [](UserData *data, const llvm::json::Object &json, const std::vector &args) -> llvm::json::Value { 492 | if (args.size() != 2) { 493 | return "Usage: o3 - Optimize function bitcode using optimization level -O3\n"; 494 | } 495 | 496 | std::string tool_str; 497 | llvm::raw_string_ostream tool_output(tool_str); 498 | 499 | magnifier::ValueId function_id; 500 | try { 501 | function_id = std::stoul(args[1], nullptr, 10); 502 | } catch (...) { 503 | return "Invalid args"; 504 | } 505 | RunOptimization(*data->explorer, tool_output, function_id, llvm::OptimizationLevel::O3); 506 | 507 | tool_output.flush(); 508 | return tool_str; 509 | }}, 510 | // Decompile function 511 | {"dec", [](UserData *data, const llvm::json::Object &json, const std::vector &args) -> llvm::json::Value { 512 | if (args.size() != 2) { 513 | return "Usage: dec - Decompile function with id\n"; 514 | } 515 | magnifier::ValueId function_id; 516 | try { 517 | function_id = std::stoul(args[1], nullptr, 10); 518 | } catch (...) { 519 | return "Invalid args"; 520 | } 521 | 522 | std::string ir_output_str; 523 | llvm::raw_string_ostream ir_output_stream(ir_output_str); 524 | std::string c_output_str; 525 | llvm::raw_string_ostream c_output_stream(c_output_str); 526 | 527 | magnifier::BitcodeExplorer &explorer = *data->explorer; 528 | AAW aaw(explorer); 529 | 530 | std::optional target_function_opt = explorer.GetFunctionById(function_id); 531 | 532 | if (!target_function_opt) { 533 | return "No function with id found"; 534 | } 535 | 536 | std::unique_ptr module = llvm::CloneModule(*(*target_function_opt)->getParent());; 537 | 538 | llvm::Function *selected_function = nullptr; 539 | for (auto &function : module->functions()) { 540 | if (explorer.GetId(function, magnifier::ValueIdKind::kDerived) == function_id) { 541 | function.print(ir_output_stream, &aaw); 542 | ir_output_stream.flush(); 543 | selected_function = &function; 544 | } 545 | } 546 | assert(selected_function); 547 | 548 | rellic::Result r = rellic::Decompile(std::move(module)); 549 | if (!r.Succeeded()) { 550 | auto error = r.TakeError(); 551 | return error.message+"\n"; 552 | } 553 | auto result = r.TakeValue(); 554 | 555 | auto selected_function_decl = result.value_to_decl_map.at((llvm::Value *)selected_function); 556 | 557 | PrintDecl((clang::Decl *) selected_function_decl, 558 | result.ast->getASTContext().getPrintingPolicy(), 0, c_output_stream); 559 | 560 | c_output_stream.flush(); 561 | return llvm::json::Object{{ 562 | {"ir", ir_output_str}, 563 | {"code", c_output_str}, 564 | {"provenance", GetRellicProvenance(result)} 565 | }}; 566 | }}, 567 | {"upload", [](UserData *data, const llvm::json::Object &json, const std::vector &args) -> llvm::json::Value { 568 | auto file_hex_str = json.getString("file"); 569 | if (!file_hex_str) { 570 | return "invalid upload file"; 571 | } 572 | 573 | std::string file_str; 574 | bool r = llvm::tryGetFromHex(*file_hex_str, file_str); 575 | if (!r) { 576 | return "invalid upload file"; 577 | } 578 | 579 | std::unique_ptr mod{rellic::LoadModuleFromMemory(&(*data->llvm_context), file_str, true)}; 580 | if (!mod) { 581 | return "invalid upload file"; 582 | } 583 | 584 | // Only support having one module at a time due to design limitations 585 | // data->explorer = std::make_unique(*data->llvm_context); 586 | 587 | data->explorer->TakeModule(std::move(mod)); 588 | return "module uploaded"; 589 | }} 590 | }; 591 | 592 | 593 | auto cmd_str = json.getString("cmd"); 594 | auto packet_id = json.getInteger("id"); 595 | if (!cmd_str || !packet_id) { 596 | return llvm::json::Object { 597 | {"message","required fields not found"}}; 598 | } 599 | 600 | auto tokenized_input = split(cmd_str->str(), ' '); 601 | if (tokenized_input.empty()) { 602 | return llvm::json::Object { 603 | {"cmd", cmd_str}, 604 | {"id", packet_id}, 605 | {"output","Invalid Command\n"}}; 606 | } 607 | 608 | auto cmd = cmd_map.find(tokenized_input[0]); 609 | if (cmd == cmd_map.end()) { 610 | return llvm::json::Object { 611 | {"cmd", cmd_str}, 612 | {"id", packet_id}, 613 | {"output","Invalid Command: " + tokenized_input[0] + "\n"}}; 614 | } 615 | 616 | return llvm::json::Object { 617 | {"cmd", cmd_str}, 618 | {"id", packet_id}, 619 | {"output",cmd->second(data, json, tokenized_input)} 620 | }; 621 | 622 | } 623 | 624 | us_listen_socket_t *listen_socket = nullptr; 625 | 626 | int main(int argc, char **argv) { 627 | llvm::InitLLVM x(argc, argv); 628 | 629 | 630 | uWS::App().ws("/ws", { 631 | .maxPayloadLength = 50 * 1024 * 1024, 632 | .open = [](auto *ws){ 633 | UserData *data = ws->getUserData(); 634 | data->llvm_context = std::make_unique(); 635 | data->explorer = std::make_unique(*data->llvm_context); 636 | }, 637 | 638 | .message = [](auto *ws, std::string_view message, uWS::OpCode opCode) { 639 | // Only deal with text 640 | if (opCode != uWS::TEXT) {return;} 641 | 642 | 643 | auto json{llvm::json::parse(message)}; 644 | if (!json || json->kind() != llvm::json::Value::Object) { 645 | ws->send(JsonToString(llvm::json::Object { 646 | {"message","Invalid JSON message"} 647 | }), opCode); 648 | return; 649 | } 650 | 651 | if (json->getAsObject()->getString("cmd")->str() == "exit") { 652 | std::cout << "exiting" << std::endl; 653 | if (listen_socket) { 654 | us_listen_socket_close(0, listen_socket); 655 | listen_socket = nullptr; 656 | } 657 | return; 658 | } 659 | 660 | ws->send(JsonToString(HandleRequest(ws->getUserData(), *json->getAsObject())), opCode); 661 | } 662 | }).listen(9001, [](auto *socket) { 663 | if (socket) { 664 | std::cout << "Listening on port " << 9001 << std::endl; 665 | listen_socket = socket; 666 | } 667 | }).run(); 668 | return 0; 669 | } 670 | 671 | -------------------------------------------------------------------------------- /lib/BitcodeExplorer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-present, Trail of Bits, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed in accordance with the terms specified in 6 | * the LICENSE file found in the root directory of this source tree. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | #include "IdCommentWriter.h" 33 | 34 | #if 1 35 | #define MAG_DEBUG(...) __VA_ARGS__ 36 | #else 37 | #define MAG_DEBUG(...) 38 | #endif 39 | 40 | namespace magnifier { 41 | namespace { 42 | 43 | [[nodiscard]] static std::string GetSubstituteHookName(llvm::Type *type) { 44 | return llvm::formatv("substitute_hook_{0}", reinterpret_cast(type)) 45 | .str(); 46 | } 47 | 48 | // Try to verify a module. 49 | static bool VerifyModule(llvm::Module *module, llvm::Function *function) { 50 | std::string error; 51 | llvm::raw_string_ostream error_stream(error); 52 | if (llvm::verifyModule(*module, &error_stream)) { 53 | function->print(error_stream); 54 | error_stream.flush(); 55 | std::cerr << "Error verifying module: " << error; 56 | assert(false); 57 | return false; 58 | } else { 59 | return true; 60 | } 61 | } 62 | 63 | bool ShouldAddAssumption(SubstitutionKind substitution_kind) { 64 | return (substitution_kind == SubstitutionKind::kValueSubstitution || 65 | substitution_kind == SubstitutionKind::kFunctionDevirtualization); 66 | } 67 | 68 | } // namespace 69 | 70 | BitcodeExplorer::BitcodeExplorer(llvm::LLVMContext &llvm_context) 71 | : llvm_context(llvm_context), 72 | md_explorer_id(llvm_context.getMDKindID("explorer.id")), 73 | md_explorer_source_id(llvm_context.getMDKindID("explorer.source_id")), 74 | md_explorer_block_id(llvm_context.getMDKindID("explorer.block_id")), 75 | md_explorer_substitution_kind_id( 76 | llvm_context.getMDKindID("explorer.substitution_kind_id")), 77 | annotator(std::make_unique(*this)), 78 | function_map(), 79 | instruction_map(), 80 | block_map(), 81 | argument_map(), 82 | value_id_counter(1), 83 | hook_functions() {} 84 | 85 | void BitcodeExplorer::TakeModule(std::unique_ptr module) { 86 | llvm::LLVMContext &module_context = module->getContext(); 87 | assert(std::addressof(module_context) == std::addressof(llvm_context)); 88 | 89 | for (auto &function : module->functions()) { 90 | if (function.isDeclaration() || function.isIntrinsic()) { 91 | continue; 92 | } 93 | UpdateMetadata(function); 94 | } 95 | opened_modules.push_back(std::move(module)); 96 | } 97 | 98 | void BitcodeExplorer::ForEachFunction( 99 | const std::function 100 | &callback) { 101 | for (const auto &[function_id, weak_vh] : function_map) { 102 | if (llvm::Function *function = 103 | llvm::cast_or_null(weak_vh)) { 104 | FunctionKind kind = GetId(*function, ValueIdKind::kOriginal) == 105 | GetId(*function, ValueIdKind::kDerived) 106 | ? FunctionKind::kOriginal 107 | : FunctionKind::kGenerated; 108 | callback(function_id, *function, kind); 109 | } 110 | } 111 | } 112 | 113 | bool BitcodeExplorer::PrintFunction(ValueId function_id, 114 | llvm::raw_ostream &output_stream) { 115 | auto function_pair = function_map.find(function_id); 116 | if (function_pair == function_map.end()) { 117 | return false; 118 | } 119 | 120 | llvm::Function *function = 121 | llvm::cast_or_null(function_pair->second); 122 | if (!function) { 123 | return false; 124 | } 125 | 126 | function->print(output_stream, annotator.get()); 127 | return true; 128 | } 129 | 130 | Result BitcodeExplorer::InlineFunctionCall( 131 | ValueId instruction_id, IFunctionResolver &resolver, 132 | ISubstitutionObserver &substitution_observer) { 133 | auto instruction_pair = instruction_map.find(instruction_id); 134 | if (instruction_pair == instruction_map.end()) { 135 | return InlineError::kInstructionNotFound; 136 | } 137 | 138 | llvm::Instruction *instruction = 139 | llvm::cast_or_null(instruction_pair->second); 140 | if (!instruction) { 141 | return InlineError::kInstructionNotFound; 142 | } 143 | 144 | auto call_base = llvm::dyn_cast(instruction); 145 | if (!call_base) { 146 | return InlineError::kNotACallBaseInstruction; 147 | } 148 | 149 | llvm::Function *called_function = call_base->getCalledFunction(); 150 | llvm::Module *func_module = called_function->getParent(); 151 | 152 | // try to resolve declarations 153 | llvm::FunctionType *original_callee_type = called_function->getFunctionType(); 154 | called_function = resolver.ResolveCallSite(call_base, called_function); 155 | if (!called_function) { 156 | return InlineError::kCannotResolveFunction; 157 | } else if (called_function->isDeclaration()) { 158 | return InlineError::kCannotResolveFunction; 159 | } else if (called_function->isVarArg()) { 160 | return InlineError::kVariadicFunction; 161 | } else if (called_function->getFunctionType() != original_callee_type) { 162 | return InlineError::kResolveFunctionTypeMismatch; 163 | } 164 | 165 | // Need to index the newly resolved function if it's the first time 166 | // encountering it 167 | if (GetId(*called_function, ValueIdKind::kDerived) == kInvalidValueId) { 168 | UpdateMetadata(*called_function); 169 | } 170 | 171 | llvm::Function *caller_function = call_base->getFunction(); 172 | assert(caller_function != nullptr); 173 | 174 | // clone and modify the called function prior to inlining 175 | llvm::ValueToValueMapTy called_value_map; 176 | llvm::Function *cloned_called_function = 177 | llvm::CloneFunction(called_function, called_value_map); 178 | 179 | // Add hook for each argument 180 | 181 | // As an example, given function: 182 | // 183 | // foo(x, y) { 184 | // ... 185 | // z = x + y 186 | // ... 187 | // } 188 | // 189 | // We will append two calls to the substitute hook at the start of the 190 | // function First, argument `x` will be hooked. After the first loop 191 | // iteration, the function becomes: 192 | // 193 | // foo(x, y) { 194 | // temp_val = substitute_hook(x, x) 195 | // ... 196 | // z = temp_val + y 197 | // ... 198 | // } 199 | // 200 | // The `substitute_hook` takes two parameters: the old value and the new 201 | // value. It's more useful in the case of value substitution. Here we just use 202 | // the same value `x` for both. Then, the same process is applied again for 203 | // `y`: 204 | // 205 | // foo(x, y) { 206 | // temp_val = substitute_hook(x, x) 207 | // temp_val2 = substitute_hook(y, y) 208 | // ... 209 | // z = temp_val + temp_val2 210 | // ... 211 | // } 212 | 213 | // This hooking process helps us observe and control the substitution of 214 | // arguments during the inlining process. Now, when inlining the function 215 | // `foo`: 216 | // 217 | // bar() { 218 | // ... 219 | // foo(1,2) 220 | // ... 221 | // } 222 | // 223 | // There's a new intermediate stage: 224 | // 225 | // bar() { 226 | // ... 227 | // temp_val = substitute_hook(1, 1) 228 | // temp_val2 = substitute_hook(2, 2) 229 | // ... 230 | // z = temp_val + temp_val2 231 | // ... 232 | // } 233 | // 234 | // Before the final result is obtained by calling `ElideSubstitutionHooks`: 235 | // 236 | // bar() { 237 | // ... 238 | // z = 1 + 2 239 | // ... 240 | // } 241 | 242 | llvm::BasicBlock &entry = cloned_called_function->getEntryBlock(); 243 | llvm::IRBuilder<> builder(&entry.front()); 244 | for (llvm::Argument &arg : cloned_called_function->args()) { 245 | llvm::CallInst *call_inst = CreateHookCallInst( 246 | arg.getType(), func_module, SubstitutionKind::kArgument, &arg, &arg); 247 | builder.Insert(call_inst); 248 | 249 | arg.replaceUsesWithIf(call_inst, [call_inst](const llvm::Use &use) -> bool { 250 | return use.getUser() != call_inst; 251 | }); 252 | } 253 | 254 | // clone and modify the caller function 255 | llvm::ValueToValueMapTy caller_value_map; 256 | llvm::Function *cloned_caller_function = 257 | llvm::CloneFunction(caller_function, caller_value_map); 258 | 259 | llvm::CallBase *cloned_call_base = nullptr; 260 | for (auto &cloned_instruction : llvm::instructions(cloned_caller_function)) { 261 | if (this->GetId(cloned_instruction, ValueIdKind::kDerived) == 262 | instruction_id) { 263 | cloned_call_base = llvm::dyn_cast(&cloned_instruction); 264 | } 265 | } 266 | assert(cloned_call_base != nullptr); 267 | 268 | // hook the function call if the return type is not void 269 | 270 | // As an example, given functions: 271 | // 272 | // foo(x, y) { 273 | // return 10 274 | // } 275 | // 276 | // bar() { 277 | // ... 278 | // a = foo(1,2) 279 | // ... 280 | // b = a + 1 281 | // ... 282 | // } 283 | // 284 | // We are going to transform `bar` to become: 285 | // 286 | // bar() { 287 | // ... 288 | // temp_val = foo(1,2) 289 | // a = substitute_hook(temp_val, temp_val) 290 | // ... 291 | // b = a + 1 292 | // ... 293 | // } 294 | // 295 | // The `substitute_hook` takes two parameters: the old value and the new 296 | // value. It's more useful in the case of value substitution. Here we just use 297 | // the same value `temp_val` for both. With this transformation in place, 298 | // inlining the function will result in: 299 | // 300 | // bar() { 301 | // ... 302 | // a = substitute_hook(10, 10) 303 | // ... 304 | // b = a + 1 305 | // ... 306 | // } 307 | // 308 | // And `ElideSubstitutionHooks` will explicitly substitute in the return 309 | // value: 310 | // 311 | // bar() { 312 | // ... 313 | // b = 10 + 1 314 | // ... 315 | // } 316 | // 317 | // This offers us better insight into the substitution of the return value. 318 | 319 | if (!cloned_call_base->getType()->isVoidTy()) { 320 | auto *dup_call_base = 321 | llvm::dyn_cast(cloned_call_base->clone()); 322 | dup_call_base->setName("temp_val"); 323 | dup_call_base->setCalledFunction(cloned_called_function); 324 | dup_call_base->insertBefore(cloned_call_base); 325 | 326 | std::string original_name = cloned_call_base->getName().str(); 327 | cloned_call_base->setName("to_delete"); 328 | 329 | llvm::CallInst *substituted_call = CreateHookCallInst( 330 | cloned_call_base->getType(), func_module, 331 | SubstitutionKind::kReturnValue, dup_call_base, dup_call_base); 332 | substituted_call->insertBefore(cloned_call_base); 333 | substituted_call->setName(original_name); 334 | 335 | cloned_call_base->replaceAllUsesWith(substituted_call); 336 | cloned_call_base->eraseFromParent(); 337 | 338 | cloned_call_base = dup_call_base; 339 | } 340 | 341 | MAG_DEBUG(VerifyModule(func_module, cloned_caller_function)); 342 | 343 | // Do the inlining 344 | llvm::InlineFunctionInfo info; 345 | llvm::InlineResult inline_result = 346 | llvm::InlineFunction(*cloned_call_base, info); 347 | if (!inline_result.isSuccess()) { 348 | // clean up resources 349 | cloned_called_function->eraseFromParent(); 350 | cloned_caller_function->eraseFromParent(); 351 | 352 | return InlineError::kInlineOperationFailed; 353 | } 354 | 355 | // delete the cloned copy of the called_function after inlining it 356 | cloned_called_function->eraseFromParent(); 357 | 358 | MAG_DEBUG(VerifyModule(func_module, cloned_caller_function)); 359 | 360 | // elide the hooks 361 | ElideSubstitutionHooks(*cloned_caller_function, substitution_observer); 362 | 363 | // update all metadata 364 | UpdateMetadata(*cloned_caller_function); 365 | 366 | MAG_DEBUG(VerifyModule(func_module, cloned_caller_function)); 367 | 368 | return GetId(*cloned_caller_function, ValueIdKind::kDerived); 369 | } 370 | 371 | void BitcodeExplorer::UpdateMetadata(llvm::Function &function) { 372 | ValueId function_id = value_id_counter++; 373 | SetId(function, function_id, ValueIdKind::kDerived); 374 | if (GetId(function, ValueIdKind::kOriginal) == kInvalidValueId) { 375 | SetId(function, function_id, ValueIdKind::kOriginal); 376 | } 377 | function_map.emplace(function_id, &function); 378 | 379 | // Assign ids to function arguments. The assertion should always hold. 380 | for (llvm::Argument &argument : function.args()) { 381 | ValueId argument_id = value_id_counter++; 382 | argument_map.emplace(argument_id, &argument); 383 | assert(argument_id == (function_id + argument.getArgNo() + 1)); 384 | } 385 | 386 | for (auto &instruction : llvm::instructions(function)) { 387 | ValueId new_instruction_id = value_id_counter++; 388 | SetId(instruction, new_instruction_id, ValueIdKind::kDerived); 389 | 390 | // for an instruction without a source, set itself to be the self 391 | if (GetId(instruction, ValueIdKind::kOriginal) == kInvalidValueId) { 392 | SetId(instruction, new_instruction_id, ValueIdKind::kOriginal); 393 | } 394 | 395 | if (GetId(instruction, ValueIdKind::kBlock) != kInvalidValueId) { 396 | RemoveId(instruction, ValueIdKind::kBlock); 397 | } 398 | 399 | instruction_map.emplace(new_instruction_id, &instruction); 400 | } 401 | 402 | for (llvm::BasicBlock &block : function) { 403 | ValueId new_block_id = value_id_counter++; 404 | llvm::Instruction *terminator_instr = block.getTerminator(); 405 | if (!terminator_instr) { 406 | continue; 407 | } 408 | SetId(*terminator_instr, new_block_id, ValueIdKind::kBlock); 409 | 410 | block_map.emplace(new_block_id, &block); 411 | } 412 | } 413 | 414 | void BitcodeExplorer::ElideSubstitutionHooks( 415 | llvm::Function &function, ISubstitutionObserver &substitution_observer) { 416 | // Get a const reference to the module data layout later used for constant 417 | // folding 418 | llvm::Module *func_module = function.getParent(); 419 | const llvm::DataLayout &module_data_layout = func_module->getDataLayout(); 420 | 421 | // The tuple contains: 422 | // 1. The instruction we are substituting 423 | // This is usually in the form of `%abc = call i32 424 | // @substitute_hook_4949385960(i32 %old_val, i32 %new_val)`. But in the case 425 | // of constant folding, it can also be an instruction with only constant 426 | // operands similar to `%add.i = add nsw i32 31, 30`. 427 | // 2. The old value before the substitution 428 | // 3. The new value after the substitution 429 | std::vector> 430 | subs; 431 | 432 | // Find all uses of the hook functions 433 | for (auto [type, function_callee_obj] : hook_functions) { 434 | auto *hook_func = 435 | llvm::dyn_cast(function_callee_obj.getCallee()); 436 | if (!hook_func) { 437 | continue; 438 | } 439 | 440 | for (llvm::Use &use : hook_func->uses()) { 441 | // only enqueue hook calls inside the given function 442 | auto *call_base = llvm::dyn_cast(use.getUser()); 443 | if (call_base && call_base->getFunction() == &function) { 444 | subs.emplace_back(call_base, call_base->getArgOperand(0), 445 | call_base->getArgOperand(1)); 446 | } 447 | } 448 | } 449 | 450 | // substitute the values 451 | while (!subs.empty()) { 452 | auto [inst, old_val, new_val] = subs.back(); 453 | subs.pop_back(); 454 | 455 | ValueId substitution_id = GetId(*inst, ValueIdKind::kSubstitution); 456 | assert(substitution_id != kInvalidValueId); 457 | 458 | auto substitution_kind = static_cast(substitution_id); 459 | llvm::Value *updated_sub_val = substitution_observer.PerformSubstitution( 460 | inst, old_val, new_val, substitution_kind); 461 | 462 | // Assume equivalence for value substitution and function devirtualization 463 | if (ShouldAddAssumption(substitution_kind) && updated_sub_val != old_val) { 464 | llvm::IRBuilder builder(inst); 465 | builder.CreateAssumption( 466 | builder.CreateCmp(llvm::CmpInst::ICMP_EQ, old_val, updated_sub_val)); 467 | } 468 | 469 | // Check we are not replacing the value with itself 470 | if (updated_sub_val != inst) { 471 | // Iterate through and replace each occurrence of the value. 472 | // Attempt constant folding and add additional substitution hooks to the 473 | // `subs` list. 474 | while (!inst->use_empty()) { 475 | llvm::Use &substitute_location = *inst->use_begin(); 476 | substitute_location.set(updated_sub_val); 477 | 478 | auto *target_instr = 479 | llvm::cast(substitute_location.getUser()); 480 | llvm::Constant *fold_result = 481 | llvm::ConstantFoldInstruction(target_instr, module_data_layout); 482 | if (fold_result) { 483 | SetId(*target_instr, 484 | static_cast>( 485 | SubstitutionKind::kConstantFolding), 486 | ValueIdKind::kSubstitution); 487 | subs.emplace_back(target_instr, target_instr, fold_result); 488 | } 489 | } 490 | 491 | // Remove the substitution hook 492 | inst->eraseFromParent(); 493 | } 494 | 495 | // Remove `old_val` if it's an instruction and no longer in use. 496 | // Have to make sure it's not the same as `inst`. 497 | if (old_val != inst) { 498 | auto old_instr = llvm::dyn_cast(old_val); 499 | if (old_instr && old_instr->getParent() && old_instr->use_empty()) { 500 | old_instr->eraseFromParent(); 501 | } 502 | } 503 | } 504 | 505 | // remove all the hook functions after eliding them 506 | for (auto [type, function_callee_obj] : hook_functions) { 507 | if (auto *hook_func = 508 | llvm::dyn_cast(function_callee_obj.getCallee())) { 509 | assert(hook_func->use_empty()); 510 | hook_func->eraseFromParent(); 511 | } 512 | } 513 | 514 | // clear the hook function map after each high-level operation 515 | hook_functions.clear(); 516 | } 517 | 518 | // Convert from opaque type `ValueIdKind` to actual llvm `kind_id`. 519 | unsigned BitcodeExplorer::ValueIdKindToKindId(ValueIdKind kind) const { 520 | switch (kind) { 521 | case ValueIdKind::kOriginal: 522 | return md_explorer_source_id; 523 | case ValueIdKind::kDerived: 524 | return md_explorer_id; 525 | case ValueIdKind::kBlock: 526 | return md_explorer_block_id; 527 | case ValueIdKind::kSubstitution: 528 | return md_explorer_substitution_kind_id; 529 | } 530 | assert(false); 531 | return 0; // An invalid metadata id. 532 | } 533 | 534 | // Returns the value ID for `function`, or `kInvalidValueId` if no ID is found. 535 | ValueId BitcodeExplorer::GetId(const llvm::Function &function, 536 | ValueIdKind kind) const { 537 | llvm::MDNode *mdnode = function.getMetadata(ValueIdKindToKindId(kind)); 538 | if (mdnode == nullptr) { 539 | return kInvalidValueId; 540 | } 541 | return llvm::cast( 542 | llvm::cast(mdnode->getOperand(0)) 543 | ->getValue()) 544 | ->getZExtValue(); 545 | } 546 | 547 | // Returns the value ID for `instruction`, or `kInvalidValueId` if no ID is 548 | // found. 549 | ValueId BitcodeExplorer::GetId(const llvm::Instruction &instruction, 550 | ValueIdKind kind) const { 551 | llvm::MDNode *mdnode = instruction.getMetadata(ValueIdKindToKindId(kind)); 552 | if (mdnode == nullptr) { 553 | return kInvalidValueId; 554 | } 555 | return llvm::cast( 556 | llvm::cast(mdnode->getOperand(0)) 557 | ->getValue()) 558 | ->getZExtValue(); 559 | } 560 | 561 | // Set an id inside the metadata of a function. 562 | void BitcodeExplorer::SetId(llvm::Function &function, ValueId value, 563 | ValueIdKind kind) const { 564 | llvm::MDNode *mdnode = llvm::MDNode::get( 565 | llvm_context, llvm::ConstantAsMetadata::get(llvm::ConstantInt::get( 566 | llvm_context, llvm::APInt(64, value, false)))); 567 | function.setMetadata(ValueIdKindToKindId(kind), mdnode); 568 | } 569 | 570 | // Set an id inside the metadata of an instruction. 571 | void BitcodeExplorer::SetId(llvm::Instruction &instruction, ValueId value, 572 | ValueIdKind kind) const { 573 | llvm::MDNode *mdnode = llvm::MDNode::get( 574 | llvm_context, llvm::ConstantAsMetadata::get(llvm::ConstantInt::get( 575 | llvm_context, llvm::APInt(64, value, false)))); 576 | instruction.setMetadata(ValueIdKindToKindId(kind), mdnode); 577 | } 578 | 579 | // Remove id of `kind` from `function` metadata. 580 | void BitcodeExplorer::RemoveId(llvm::Function &function, ValueIdKind kind) { 581 | function.setMetadata(ValueIdKindToKindId(kind), nullptr); 582 | } 583 | 584 | // Remove id of `kind` from `instruction` metadata. 585 | void BitcodeExplorer::RemoveId(llvm::Instruction &instruction, 586 | ValueIdKind kind) { 587 | instruction.setMetadata(ValueIdKindToKindId(kind), nullptr); 588 | } 589 | 590 | // Get a `FunctionCallee` object for the given `type`. Create the function in 591 | // `func_module` if it doesn't exist. In addition, the object is added to the 592 | // `hook_functions` map. 593 | llvm::FunctionCallee BitcodeExplorer::GetHookFunction( 594 | llvm::Type *type, llvm::Module *func_module) { 595 | llvm::FunctionCallee &hook_func = hook_functions[type]; 596 | if (hook_func) { 597 | return hook_functions[type]; 598 | } 599 | 600 | llvm::FunctionType *func_type = 601 | llvm::FunctionType::get(type, {type, type}, false); 602 | hook_func = 603 | func_module->getOrInsertFunction(GetSubstituteHookName(type), func_type); 604 | return hook_func; 605 | } 606 | 607 | llvm::CallInst *BitcodeExplorer::CreateHookCallInst(llvm::Type *type, 608 | llvm::Module *func_module, 609 | SubstitutionKind hook_kind, 610 | llvm::Value *old_val, 611 | llvm::Value *new_val) { 612 | llvm::CallInst *call_instr = llvm::CallInst::Create( 613 | GetHookFunction(type, func_module), {old_val, new_val}, "temp_val"); 614 | SetId(*call_instr, 615 | static_cast>(hook_kind), 616 | ValueIdKind::kSubstitution); 617 | return call_instr; 618 | } 619 | 620 | Result 621 | BitcodeExplorer::SubstituteInstructionWithValue( 622 | ValueId instruction_id, uint64_t value, ISubstitutionObserver &observer) { 623 | auto instruction_pair = instruction_map.find(instruction_id); 624 | if (instruction_pair == instruction_map.end()) { 625 | return SubstitutionError::kIdNotFound; 626 | } 627 | 628 | llvm::Instruction *instruction = 629 | llvm::cast_or_null(instruction_pair->second); 630 | if (!instruction) { 631 | return SubstitutionError::kIdNotFound; 632 | } 633 | if (!instruction->getType()->isIntegerTy()) { 634 | return SubstitutionError::kIncorrectType; 635 | } 636 | 637 | llvm::Function *function = instruction->getFunction(); 638 | assert(function != nullptr); 639 | llvm::Module *func_module = function->getParent(); 640 | 641 | // clone and modify the function 642 | llvm::ValueToValueMapTy value_map; 643 | llvm::Function *cloned_function = llvm::CloneFunction(function, value_map); 644 | 645 | llvm::Instruction *cloned_instr = nullptr; 646 | for (auto &cloned_instruction : llvm::instructions(cloned_function)) { 647 | if (GetId(cloned_instruction, ValueIdKind::kDerived) == instruction_id) { 648 | cloned_instr = &cloned_instruction; 649 | } 650 | } 651 | assert(cloned_instr != nullptr); 652 | 653 | // Here we want to add in a substitution hook to facilitate the substitution 654 | // process. The real value replacement really happens inside 655 | // `ElideSubstitutionHooks`. For example, given a function: 656 | // 657 | // foo(x, y) { 658 | // ... 659 | // a = x + y 660 | // b = a + 1 661 | // ... 662 | // } 663 | // 664 | // And we want to replace `a` with `10`. Then the function should be 665 | // transformed into: 666 | // 667 | // foo(x, y) { 668 | // ... 669 | // temp_val = x + y 670 | // a = substitute_hook(temp_val, 10) 671 | // b = a + 1 672 | // ... 673 | // } 674 | // 675 | // The first parameter being the old value and the second one being the new 676 | // value. 677 | 678 | llvm::ConstantInt *const_val = llvm::ConstantInt::get( 679 | llvm_context, 680 | llvm::APInt(cloned_instr->getType()->getIntegerBitWidth(), value, false)); 681 | llvm::CallInst *substituted_call = CreateHookCallInst( 682 | cloned_instr->getType(), func_module, 683 | SubstitutionKind::kValueSubstitution, cloned_instr, const_val); 684 | substituted_call->insertAfter(cloned_instr); 685 | 686 | cloned_instr->replaceUsesWithIf( 687 | substituted_call, [substituted_call](const llvm::Use &use) -> bool { 688 | return use.getUser() != substituted_call; 689 | }); 690 | 691 | MAG_DEBUG(VerifyModule(func_module, cloned_function)); 692 | 693 | ElideSubstitutionHooks(*cloned_function, observer); 694 | 695 | UpdateMetadata(*cloned_function); 696 | 697 | MAG_DEBUG(VerifyModule(func_module, cloned_function)); 698 | 699 | return GetId(*cloned_function, ValueIdKind::kDerived); 700 | } 701 | 702 | Result BitcodeExplorer::SubstituteArgumentWithValue( 703 | ValueId argument_id, uint64_t value, ISubstitutionObserver &observer) { 704 | auto argument_pair = argument_map.find(argument_id); 705 | if (argument_pair == argument_map.end()) { 706 | if (function_map.find(argument_id) != function_map.end()) { 707 | return SubstitutionError::kCannotUseFunctionId; 708 | } 709 | return SubstitutionError::kIdNotFound; 710 | } 711 | 712 | llvm::Argument *argument = 713 | llvm::cast_or_null(argument_pair->second); 714 | if (!argument) { 715 | return SubstitutionError::kIdNotFound; 716 | } 717 | if (!argument->getType()->isIntegerTy()) { 718 | return SubstitutionError::kIncorrectType; 719 | } 720 | 721 | llvm::Function *function = argument->getParent(); 722 | assert(function != nullptr); 723 | llvm::Module *func_module = function->getParent(); 724 | 725 | // clone and modify the function 726 | llvm::ValueToValueMapTy value_map; 727 | llvm::Function *cloned_function = llvm::CloneFunction(function, value_map); 728 | 729 | llvm::Argument *cloned_argument = 730 | cloned_function->getArg(argument->getArgNo()); 731 | 732 | // Here we want to add in a substitution hook to facilitate the substitution 733 | // process. The real value replacement really happens inside 734 | // `ElideSubstitutionHooks`. For example, given a function: 735 | // 736 | // foo(x, y) { 737 | // ... 738 | // a = x + y 739 | // ... 740 | // } 741 | // 742 | // And we want to replace `y` with `10`. Then the function should be 743 | // transformed into: 744 | // 745 | // foo(x, y) { 746 | // temp_val = substitute_hook(y, 10) 747 | // ... 748 | // a = x + temp_val 749 | // ... 750 | // } 751 | // 752 | // The first parameter being the old value (`y`) and the second one being the 753 | // new value (`10`). 754 | 755 | llvm::ConstantInt *const_val = llvm::ConstantInt::get( 756 | llvm_context, 757 | llvm::APInt(cloned_argument->getType()->getIntegerBitWidth(), value, 758 | false)); 759 | llvm::CallInst *substitute_hook_call = CreateHookCallInst( 760 | cloned_argument->getType(), func_module, 761 | SubstitutionKind::kValueSubstitution, cloned_argument, const_val); 762 | substitute_hook_call->insertBefore(&cloned_function->getEntryBlock().front()); 763 | 764 | cloned_argument->replaceUsesWithIf( 765 | substitute_hook_call, 766 | [substitute_hook_call](const llvm::Use &use) -> bool { 767 | return use.getUser() != substitute_hook_call; 768 | }); 769 | 770 | MAG_DEBUG(VerifyModule(func_module, cloned_function)); 771 | 772 | ElideSubstitutionHooks(*cloned_function, observer); 773 | 774 | UpdateMetadata(*cloned_function); 775 | 776 | MAG_DEBUG(VerifyModule(func_module, cloned_function)); 777 | 778 | return GetId(*cloned_function, ValueIdKind::kDerived); 779 | } 780 | 781 | Result BitcodeExplorer::OptimizeFunction( 782 | ValueId function_id, 783 | const llvm::OptimizationLevel &optimization_level) { 784 | if (optimization_level == llvm::OptimizationLevel::O0) { 785 | return OptimizationError::kInvalidOptimizationLevel; 786 | } 787 | 788 | auto function_pair = function_map.find(function_id); 789 | if (function_pair == function_map.end()) { 790 | return OptimizationError::kIdNotFound; 791 | } 792 | 793 | llvm::Function *function = 794 | llvm::cast_or_null(function_pair->second); 795 | if (!function) { 796 | return OptimizationError::kIdNotFound; 797 | } 798 | 799 | llvm::Module *func_module = function->getParent(); 800 | 801 | // Clone the function 802 | llvm::ValueToValueMapTy value_map; 803 | llvm::Function *cloned_function = llvm::CloneFunction(function, value_map); 804 | 805 | // Create the analysis managers 806 | llvm::LoopAnalysisManager loop_analysis_manager; 807 | llvm::FunctionAnalysisManager function_analysis_manager; 808 | llvm::CGSCCAnalysisManager cgscc_analysis_manager; 809 | llvm::ModuleAnalysisManager module_analysis_manager; 810 | 811 | // Create the new pass manager builder 812 | llvm::PassBuilder pass_builder; 813 | 814 | function_analysis_manager.registerPass( 815 | [&] { return pass_builder.buildDefaultAAPipeline(); }); 816 | 817 | pass_builder.registerModuleAnalyses(module_analysis_manager); 818 | pass_builder.registerCGSCCAnalyses(cgscc_analysis_manager); 819 | pass_builder.registerFunctionAnalyses(function_analysis_manager); 820 | pass_builder.registerLoopAnalyses(loop_analysis_manager); 821 | pass_builder.crossRegisterProxies( 822 | loop_analysis_manager, function_analysis_manager, cgscc_analysis_manager, 823 | module_analysis_manager); 824 | 825 | llvm::FunctionPassManager function_pass_manager = 826 | pass_builder.buildFunctionSimplificationPipeline( 827 | optimization_level, llvm::ThinOrFullLTOPhase::None); 828 | 829 | function_pass_manager.run(*cloned_function, function_analysis_manager); 830 | 831 | UpdateMetadata(*cloned_function); 832 | 833 | MAG_DEBUG(VerifyModule(func_module, cloned_function)); 834 | 835 | return GetId(*cloned_function, ValueIdKind::kDerived); 836 | } 837 | 838 | std::optional BitcodeExplorer::DeleteFunction( 839 | ValueId function_id) { 840 | auto function_pair = function_map.find(function_id); 841 | if (function_pair == function_map.end()) { 842 | return DeletionError::kIdNotFound; 843 | } 844 | 845 | llvm::Function *function = 846 | llvm::cast_or_null(function_pair->second); 847 | if (!function) { 848 | return DeletionError::kIdNotFound; 849 | } 850 | 851 | // Check if the function is referenced by another function 852 | bool function_used_by_other_function = false; 853 | for (llvm::Use &use : function->uses()) { 854 | auto *instr = llvm::dyn_cast(use.getUser()); 855 | if (instr && instr->getFunction() != function) { 856 | function_used_by_other_function = true; 857 | break; 858 | } 859 | } 860 | 861 | if (function_used_by_other_function) { 862 | return DeletionError::kFunctionInUse; 863 | } 864 | 865 | // Remove function from the various maps 866 | function_map.erase(function_id); 867 | 868 | for (llvm::Argument &argument : function->args()) { 869 | argument_map.erase(function_id + argument.getArgNo() + 1); 870 | } 871 | 872 | for (auto &instruction : llvm::instructions(function)) { 873 | instruction_map.erase(GetId(instruction, ValueIdKind::kDerived)); 874 | } 875 | 876 | for (llvm::BasicBlock &block : *function) { 877 | llvm::Instruction *terminator_instr = block.getTerminator(); 878 | if (!terminator_instr) { 879 | continue; 880 | } 881 | block_map.erase(GetId(*terminator_instr, ValueIdKind::kBlock)); 882 | } 883 | 884 | // Delete the function 885 | function->eraseFromParent(); 886 | 887 | return std::nullopt; 888 | } 889 | 890 | Result BitcodeExplorer::DevirtualizeFunction( 891 | ValueId instruction_id, ValueId function_id, 892 | ISubstitutionObserver &substitution_observer) { 893 | // Find and check `instruction_id` is correctly referencing a `CallBase` 894 | // instruction 895 | auto instruction_pair = instruction_map.find(instruction_id); 896 | if (instruction_pair == instruction_map.end()) { 897 | return DevirtualizeError::kInstructionNotFound; 898 | } 899 | 900 | llvm::Instruction *instruction = 901 | llvm::cast_or_null(instruction_pair->second); 902 | if (!instruction) { 903 | return DevirtualizeError::kInstructionNotFound; 904 | } 905 | 906 | auto call_base = llvm::dyn_cast(instruction); 907 | 908 | if (call_base == nullptr) { 909 | return DevirtualizeError::kNotACallBaseInstruction; 910 | } 911 | 912 | if (!call_base->isIndirectCall()) { 913 | return DevirtualizeError::kNotAIndirectCall; 914 | } 915 | 916 | // Find the function with `function_id` 917 | auto function_pair = function_map.find(function_id); 918 | if (function_pair == function_map.end()) { 919 | return DevirtualizeError::kFunctionNotFound; 920 | } 921 | 922 | llvm::Function *direct_called_function = 923 | llvm::cast_or_null(function_pair->second); 924 | if (!direct_called_function) { 925 | return DevirtualizeError::kFunctionNotFound; 926 | } 927 | 928 | if (direct_called_function->arg_size() != call_base->arg_size()) { 929 | return DevirtualizeError::kArgNumMismatch; 930 | } 931 | 932 | // Clone and modify the caller function 933 | llvm::ValueToValueMapTy caller_value_map; 934 | llvm::Function *cloned_caller_function = 935 | llvm::CloneFunction(call_base->getFunction(), caller_value_map); 936 | 937 | llvm::CallBase *cloned_call_base = nullptr; 938 | for (auto &cloned_instruction : llvm::instructions(cloned_caller_function)) { 939 | if (this->GetId(cloned_instruction, ValueIdKind::kDerived) == 940 | instruction_id) { 941 | cloned_call_base = llvm::dyn_cast(&cloned_instruction); 942 | } 943 | } 944 | assert(cloned_call_base != nullptr); 945 | 946 | // Here we add in a hook function call to better observe the substitutions 947 | // that take place. The real value substitutions happen inside 948 | // `ElideSubstitutionHooks`. For example, given a function: 949 | // 950 | // foo() { 951 | // ... 952 | // a = ... 953 | // call a 954 | // ... 955 | // } 956 | // 957 | // We want to transform the it into: 958 | // 959 | // foo() { 960 | // ... 961 | // a = ... 962 | // temp_val = substitute_hook(a, direct_called_function) 963 | // call temp_val 964 | // ... 965 | // } 966 | // 967 | // The first parameter of the hook function is the old value (`a`) and the 968 | // second parameter is the new value - the function we are going to call 969 | // directly (`direct_called_function`). 970 | 971 | llvm::CallInst *substitute_hook_call = CreateHookCallInst( 972 | cloned_call_base->getCalledOperand()->getType(), 973 | cloned_call_base->getModule(), 974 | SubstitutionKind::kFunctionDevirtualization, 975 | cloned_call_base->getCalledOperand(), direct_called_function); 976 | substitute_hook_call->insertBefore(cloned_call_base); 977 | cloned_call_base->setCalledOperand(substitute_hook_call); 978 | 979 | MAG_DEBUG(VerifyModule(cloned_caller_function->getParent(), 980 | cloned_caller_function)); 981 | 982 | ElideSubstitutionHooks(*cloned_caller_function, substitution_observer); 983 | 984 | UpdateMetadata(*cloned_caller_function); 985 | 986 | MAG_DEBUG(VerifyModule(cloned_caller_function->getParent(), 987 | cloned_caller_function)); 988 | 989 | return GetId(*cloned_caller_function, ValueIdKind::kDerived); 990 | } 991 | 992 | std::optional BitcodeExplorer::GetFunctionById(ValueId id) { 993 | auto res = this->function_map.find(id); 994 | if (res != this->function_map.end()) { 995 | if (auto f = llvm::cast_or_null(res->second)) { 996 | return f; 997 | } else { 998 | return std::nullopt; 999 | } 1000 | } else { 1001 | return std::nullopt; 1002 | } 1003 | } 1004 | 1005 | ValueId BitcodeExplorer::IndexFunction(llvm::Function &function) { 1006 | auto res = GetId(function, ValueIdKind::kDerived); 1007 | if (res == kInvalidValueId) { 1008 | UpdateMetadata(function); 1009 | return GetId(function, ValueIdKind::kDerived); 1010 | } 1011 | 1012 | return res; 1013 | } 1014 | 1015 | ValueId BitcodeExplorer::MaxCurrentID() { return this->value_id_counter; } 1016 | 1017 | BitcodeExplorer::~BitcodeExplorer() = default; 1018 | 1019 | } // namespace magnifier 1020 | --------------------------------------------------------------------------------