├── .clang-format ├── CMakeLists.txt ├── LICENSE ├── README.md ├── lib ├── CMakeLists.txt ├── callcounter-inst │ ├── CMakeLists.txt │ ├── DynamicCallCounter.cpp │ ├── StaticCallCounter.cpp │ └── include │ │ ├── DynamicCallCounter.h │ │ └── StaticCallCounter.h └── callcounter-rt │ ├── CMakeLists.txt │ └── runtime.cpp ├── test └── simpletest.c └── tools ├── CMakeLists.txt └── callcounter ├── CMakeLists.txt ├── config.h.cmake └── main.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -2 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveAssignments: true 6 | AlignConsecutiveDeclarations: false 7 | AlignEscapedNewlinesLeft: true 8 | AlignOperands: true 9 | AlignTrailingComments: true 10 | AllowAllParametersOfDeclarationOnNextLine: false 11 | AllowShortBlocksOnASingleLine: true 12 | AllowShortCaseLabelsOnASingleLine: true 13 | AllowShortFunctionsOnASingleLine: Inline 14 | AllowShortIfStatementsOnASingleLine: false 15 | AllowShortLoopsOnASingleLine: false 16 | AlwaysBreakAfterReturnType: AllDefinitions 17 | AlwaysBreakBeforeMultilineStrings: false 18 | AlwaysBreakTemplateDeclarations: true 19 | BinPackArguments: false 20 | BinPackParameters: false 21 | BraceWrapping: 22 | AfterClass: true 23 | AfterControlStatement: false 24 | AfterEnum: true 25 | AfterFunction: true 26 | AfterNamespace: true 27 | AfterStruct: true 28 | AfterUnion: true 29 | BeforeCatch: false 30 | BeforeElse: false 31 | IndentBraces: false 32 | BreakBeforeBinaryOperators: NonAssignment 33 | BreakBeforeBraces: Attach 34 | BreakBeforeTernaryOperators: true 35 | BreakConstructorInitializersBeforeComma: false 36 | BreakStringLiterals: true 37 | ColumnLimit: 80 38 | CommentPragmas: '^ IWYU pragma:' 39 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 40 | ConstructorInitializerIndentWidth: 2 41 | ContinuationIndentWidth: 4 42 | Cpp11BracedListStyle: true 43 | DerivePointerAlignment: false 44 | DisableFormat: false 45 | ExperimentalAutoDetectBinPacking: false 46 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 47 | IncludeCategories: 48 | - Regex: '^<.*\.h>' 49 | Priority: 1 50 | - Regex: '^<.*' 51 | Priority: 2 52 | - Regex: '.*' 53 | Priority: 3 54 | IncludeIsMainRegex: '([-_](test|unittest))?$' 55 | IndentCaseLabels: true 56 | IndentWidth: 2 57 | IndentWrappedFunctionNames: false 58 | KeepEmptyLinesAtTheStartOfBlocks: false 59 | MacroBlockBegin: '' 60 | MacroBlockEnd: '' 61 | MaxEmptyLinesToKeep: 2 62 | NamespaceIndentation: None 63 | PenaltyBreakBeforeFirstCallParameter: 1 64 | PenaltyBreakComment: 300 65 | PenaltyBreakFirstLessLess: 120 66 | PenaltyBreakString: 1000 67 | PenaltyExcessCharacter: 1000000 68 | PenaltyReturnTypeOnItsOwnLine: 200 69 | PointerAlignment: Left 70 | ReflowComments: true 71 | SortIncludes: false 72 | SpaceAfterCStyleCast: false 73 | SpaceAfterTemplateKeyword: true 74 | SpaceBeforeAssignmentOperators: true 75 | SpaceBeforeParens: ControlStatements 76 | SpaceInEmptyParentheses: false 77 | SpacesBeforeTrailingComments: 2 78 | SpacesInAngles: false 79 | SpacesInContainerLiterals: true 80 | SpacesInCStyleCastParentheses: false 81 | SpacesInParentheses: false 82 | SpacesInSquareBrackets: false 83 | Standard: Auto 84 | TabWidth: 2 85 | UseTab: Never 86 | ... 87 | 88 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(callcounter) 3 | 4 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}") 5 | list(APPEND CMAKE_MODULE_PATH "${LLVM_DIR}") 6 | 7 | set(PACKAGE_NAME callcounter) 8 | set(PACKAGE_VERSION 0.1) 9 | set(PACKAGE_STRING "${PACKAGE_NAME} ${PACKAGE_VERSION}") 10 | set(PACKAGE_BUGREPORT "wsumner@sfu.ca") 11 | 12 | if (NOT CMAKE_BUILD_TYPE) 13 | set(CMAKE_BUILD_TYPE Debug CACHE 14 | STRING "Build type (default Debug):" FORCE) 15 | endif() 16 | 17 | add_compile_options( 18 | "$<$,$>:-fno-rtti;-Wall;-fno-omit-frame-pointer>" 19 | "$<$,$>:-fno-rtti;-Wall>" 20 | ) 21 | 22 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") 23 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") 24 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") 25 | 26 | ############## LLVM CONFIGURATION ################# 27 | 28 | # LLVM_DIR must be set to the prefix of /share/llvm/cmake via commandline 29 | find_package(LLVM REQUIRED CONFIG) 30 | message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") 31 | message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") 32 | 33 | # We incorporate the CMake features provided by LLVM: 34 | list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}") 35 | include(AddLLVM) 36 | 37 | message("LLVM STATUS: 38 | Definitions ${LLVM_DEFINITIONS} 39 | Includes ${LLVM_INCLUDE_DIRS} 40 | Libraries ${LLVM_LIBRARY_DIRS} 41 | Targets ${LLVM_TARGETS_TO_BUILD}" 42 | ) 43 | 44 | # Now set the LLVM header and library paths: 45 | include_directories(${LLVM_INCLUDE_DIRS}) 46 | link_directories(${LLVM_LIBRARY_DIRS}) 47 | add_definitions(${LLVM_DEFINITIONS}) 48 | 49 | ############## FINAL PROJECT CONFIG ################# 50 | 51 | set(CMAKE_TEMP_LIBRARY_PATH "${PROJECT_BINARY_DIR}/lib") 52 | 53 | add_subdirectory(lib) 54 | add_subdirectory(tools) 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 nsumner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | These programs are demonstrations of how LLVM can be used for (very simple) 2 | static and dynamic analyses. However, they use many of the initial building 3 | blocks necessary for solving more complex problems. 4 | 5 | The provided analyses count the number of direct invocations of a particular 6 | function are present within a program or within and execution depending on 7 | whether static or dynamic analysis is used. 8 | 9 | Building with CMake 10 | ============================================== 11 | 1. Clone the demo repository. 12 | 13 | git clone https://github.com/nsumner/llvm-demo.git 14 | 15 | 2. Create a new directory for building. 16 | 17 | mkdir demobuild 18 | 19 | 3. Change into the new directory. 20 | 21 | cd demobuild 22 | 23 | 4. Run CMake with the path to the LLVM source. 24 | 25 | cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=True \ 26 | -DLLVM_DIR=/lib/cmake/llvm/ ../llvm-demo 27 | 28 | 5. Run make inside the build directory: 29 | 30 | make 31 | 32 | This produces a callcounter tool called bin/callcounter and supporting 33 | libraries in lib/. 34 | 35 | Note, building with a tool like ninja can be done by adding `-G Ninja` to 36 | the cmake invocation and running ninja instead of make. 37 | 38 | Running 39 | ============================================== 40 | 41 | First suppose that you have a program compiled to bitcode: 42 | 43 | clang -g -c -emit-llvm ../llvm-demo/test/simpletest.c -o calls.bc 44 | 45 | Running the dynamic call printer: 46 | 47 | bin/callcounter -dynamic calls.bc -o calls 48 | ./calls 49 | 50 | Running the static call printer: 51 | 52 | bin/callcounter -static calls.bc 53 | 54 | or by loading the pass as a plugin for `opt`: 55 | 56 | opt -analyze -load lib/libcallcounter-lib.so -callcounter calls.bc 57 | -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(callcounter-inst) 2 | add_subdirectory(callcounter-rt) 3 | -------------------------------------------------------------------------------- /lib/callcounter-inst/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | llvm_map_components_to_libnames(REQ_LLVM_LIBRARIES 3 | core analysis support 4 | ) 5 | 6 | add_library(callcounter-inst 7 | StaticCallCounter.cpp 8 | DynamicCallCounter.cpp 9 | ) 10 | target_link_libraries(callcounter-inst 11 | INTERFACE 12 | ${REQ_LLVM_LIBRARIES} 13 | ) 14 | target_include_directories(callcounter-inst 15 | PUBLIC 16 | $ 17 | $ 18 | ) 19 | set_target_properties(callcounter-inst PROPERTIES 20 | LINKER_LANGUAGE CXX 21 | CXX_STANDARD 17 22 | ) 23 | 24 | add_library(callcounter-lib MODULE 25 | StaticCallCounter.cpp 26 | DynamicCallCounter.cpp 27 | ) 28 | target_link_libraries(callcounter-lib 29 | INTERFACE 30 | ${REQ_LLVM_LIBRARIES} 31 | ) 32 | target_include_directories(callcounter-lib 33 | PUBLIC 34 | $ 35 | $ 36 | ) 37 | set_target_properties(callcounter-lib PROPERTIES 38 | LINKER_LANGUAGE CXX 39 | CXX_STANDARD 17 40 | ) 41 | -------------------------------------------------------------------------------- /lib/callcounter-inst/DynamicCallCounter.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "llvm/IR/BasicBlock.h" 4 | #include "llvm/IR/DerivedTypes.h" 5 | #include "llvm/IR/IRBuilder.h" 6 | #include "llvm/Transforms/Utils/ModuleUtils.h" 7 | #include "DynamicCallCounter.h" 8 | 9 | 10 | using namespace llvm; 11 | using callcounter::DynamicCallCounter; 12 | 13 | 14 | namespace callcounter { 15 | 16 | char DynamicCallCounter::ID = 0; 17 | 18 | } 19 | 20 | 21 | // Returns a map (Function* -> uint64_t). 22 | static DenseMap 23 | computeFunctionIDs(llvm::ArrayRef functions) { 24 | DenseMap idMap; 25 | 26 | size_t nextID = 0; 27 | for (auto f : functions) { 28 | idMap[f] = nextID; 29 | ++nextID; 30 | } 31 | 32 | return idMap; 33 | } 34 | 35 | 36 | // Returns a set of all internal (defined) functions. 37 | static DenseSet 38 | computeInternal(llvm::ArrayRef functions) { 39 | DenseSet internal; 40 | 41 | for (auto f : functions) { 42 | if (!f->isDeclaration()) { 43 | internal.insert(f); 44 | } 45 | } 46 | 47 | return internal; 48 | } 49 | 50 | 51 | static llvm::Constant* 52 | createConstantString(llvm::Module& m, llvm::StringRef str) { 53 | auto& context = m.getContext(); 54 | 55 | auto* name = llvm::ConstantDataArray::getString(context, str, true); 56 | auto* int8Ty = llvm::Type::getInt8Ty(context); 57 | auto* arrayTy = llvm::ArrayType::get(int8Ty, str.size() + 1); 58 | auto* asStr = new llvm::GlobalVariable( 59 | m, arrayTy, true, llvm::GlobalValue::PrivateLinkage, name); 60 | 61 | auto* zero = llvm::ConstantInt::get(llvm::Type::getInt32Ty(context), 0); 62 | llvm::Value* indices[] = {zero, zero}; 63 | return llvm::ConstantExpr::getInBoundsGetElementPtr(arrayTy, asStr, indices); 64 | } 65 | 66 | 67 | // Create the CCOUNT(functionInfo) table used by the runtime library. 68 | static void 69 | createFunctionTable(Module& m, uint64_t numFunctions) { 70 | auto& context = m.getContext(); 71 | 72 | // Create the component types of the table 73 | auto* int64Ty = Type::getInt64Ty(context); 74 | auto* stringTy = Type::getInt8PtrTy(context); 75 | Type* fieldTys[] = {stringTy, int64Ty}; 76 | auto* structTy = StructType::get(context, fieldTys, false); 77 | auto* tableTy = ArrayType::get(structTy, numFunctions); 78 | auto* zero = ConstantInt::get(int64Ty, 0, false); 79 | 80 | // Compute and store an externally visible array of function information. 81 | std::vector values; 82 | std::transform( 83 | m.begin(), 84 | m.end(), 85 | std::back_inserter(values), 86 | [&m, zero, structTy](auto& f) { 87 | Constant* structFields[] = {createConstantString(m, f.getName()), zero}; 88 | return ConstantStruct::get(structTy, structFields); 89 | }); 90 | auto* functionTable = ConstantArray::get(tableTy, values); 91 | new GlobalVariable(m, 92 | tableTy, 93 | false, 94 | GlobalValue::ExternalLinkage, 95 | functionTable, 96 | "CaLlCoUnTeR_functionInfo"); 97 | } 98 | 99 | 100 | // For an analysis pass, runOnModule should perform the actual analysis and 101 | // compute the results. The actual output, however, is produced separately. 102 | bool 103 | DynamicCallCounter::runOnModule(Module& m) { 104 | auto& context = m.getContext(); 105 | 106 | // First identify the functions we wish to track 107 | std::vector toCount; 108 | for (auto& f : m) { 109 | toCount.push_back(&f); 110 | } 111 | 112 | ids = computeFunctionIDs(toCount); 113 | internal = computeInternal(toCount); 114 | auto const numFunctions = toCount.size(); 115 | 116 | // Store the number of functions into an externally visible variable. 117 | auto* int64Ty = Type::getInt64Ty(context); 118 | auto* numFunctionsGlobal = ConstantInt::get(int64Ty, numFunctions, false); 119 | new GlobalVariable(m, 120 | int64Ty, 121 | true, 122 | GlobalValue::ExternalLinkage, 123 | numFunctionsGlobal, 124 | "CaLlCoUnTeR_numFunctions"); 125 | 126 | createFunctionTable(m, numFunctions); 127 | 128 | // Install the result printing function so that it prints out the counts after 129 | // the entire program is finished executing. 130 | auto* voidTy = Type::getVoidTy(context); 131 | auto printer = m.getOrInsertFunction("CaLlCoUnTeR_print", voidTy); 132 | appendToGlobalDtors(m, llvm::cast(printer.getCallee()), 0); 133 | 134 | // Declare the counter function 135 | auto* helperTy = FunctionType::get(voidTy, int64Ty, false); 136 | auto counter = m.getOrInsertFunction("CaLlCoUnTeR_called", helperTy); 137 | 138 | for (auto f : toCount) { 139 | // We only want to instrument internally defined functions. 140 | if (f->isDeclaration()) { 141 | continue; 142 | } 143 | 144 | // Count each internal function as it executes. 145 | handleCalledFunction(*f, counter); 146 | 147 | // Count each external function as it is called. 148 | for (auto& bb : *f) { 149 | for (auto& i : bb) { 150 | if (CallBase *cb = dyn_cast(&i)) { 151 | handleInstruction(*cb, counter); 152 | } 153 | } 154 | } 155 | } 156 | 157 | return true; 158 | } 159 | 160 | 161 | void 162 | DynamicCallCounter::handleCalledFunction(Function& f, FunctionCallee counter) { 163 | IRBuilder<> builder(&*f.getEntryBlock().getFirstInsertionPt()); 164 | builder.CreateCall(counter, builder.getInt64(ids[&f])); 165 | } 166 | 167 | 168 | void 169 | DynamicCallCounter::handleInstruction(CallBase& cb, FunctionCallee counter) { 170 | // Check whether the called function is directly invoked 171 | auto called = dyn_cast(cb.getCalledOperand()->stripPointerCasts()); 172 | if (!called) { 173 | return; 174 | } 175 | 176 | // Check if the function is internal or blacklisted. 177 | if (internal.count(called) || !ids.count(called)) { 178 | // Internal functions are counted upon the entry of each function body. 179 | // Blacklisted functions are not counted. Neither should proceed. 180 | return; 181 | } 182 | 183 | // External functions are counted at their invocation sites. 184 | IRBuilder<> builder(&cb); 185 | builder.CreateCall(counter, builder.getInt64(ids[called])); 186 | } 187 | -------------------------------------------------------------------------------- /lib/callcounter-inst/StaticCallCounter.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "llvm/IR/BasicBlock.h" 4 | #include "llvm/IR/Instruction.h" 5 | 6 | #include "StaticCallCounter.h" 7 | 8 | // This program can be used by opt. After compilation, use: 9 | // opt -load buildpath/lib/callcounter-inst.so --callcounter -analyze bitcode.bc 10 | 11 | 12 | using namespace llvm; 13 | using callcounter::StaticCallCounter; 14 | 15 | 16 | namespace callcounter { 17 | 18 | char StaticCallCounter::ID = 0; 19 | 20 | RegisterPass X("callcounter", 21 | "Print the static count of direct calls"); 22 | 23 | } 24 | 25 | 26 | // For an analysis pass, runOnModule should perform the actual analysis and 27 | // compute the results. The actual output, however, is produced separately. 28 | bool 29 | StaticCallCounter::runOnModule(Module& m) { 30 | for (auto& f : m) { 31 | for (auto& bb : f) { 32 | for (auto& i : bb) { 33 | if (CallBase *cb = dyn_cast(&i)) { 34 | handleInstruction(*cb); 35 | } 36 | } 37 | } 38 | } 39 | 40 | return false; 41 | } 42 | 43 | 44 | void 45 | StaticCallCounter::handleInstruction(CallBase& cb) { 46 | // Check whether the called function is directly invoked 47 | auto called = dyn_cast(cb.getCalledOperand()->stripPointerCasts()); 48 | if (!called) { 49 | return; 50 | } 51 | 52 | // Update the count for the particular call 53 | auto count = counts.find(called); 54 | if (counts.end() == count) { 55 | count = counts.insert(std::make_pair(called, 0)).first; 56 | } 57 | ++count->second; 58 | } 59 | 60 | 61 | // Output for a pure analysis pass should happen in the print method. 62 | // It is called automatically after the analysis pass has finished collecting 63 | // its information. 64 | void 65 | StaticCallCounter::print(raw_ostream& out, Module const* /*m*/) const { 66 | out << "Function Counts\n" 67 | << "===============\n"; 68 | for (auto& kvPair : counts) { 69 | auto* function = kvPair.first; 70 | uint64_t count = kvPair.second; 71 | out << function->getName() << " : " << count << "\n"; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/callcounter-inst/include/DynamicCallCounter.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef DYNAMICCALLCOUNTER_H 4 | #define DYNAMICCALLCOUNTER_H 5 | 6 | 7 | #include "llvm/ADT/DenseMap.h" 8 | #include "llvm/ADT/DenseSet.h" 9 | #include "llvm/IR/InstrTypes.h" 10 | #include "llvm/IR/Module.h" 11 | #include "llvm/Pass.h" 12 | #include "llvm/Support/raw_ostream.h" 13 | 14 | 15 | namespace callcounter { 16 | 17 | 18 | struct DynamicCallCounter : public llvm::ModulePass { 19 | static char ID; 20 | 21 | llvm::DenseMap ids; 22 | llvm::DenseSet internal; 23 | 24 | DynamicCallCounter() : llvm::ModulePass(ID) {} 25 | 26 | bool runOnModule(llvm::Module& m) override; 27 | 28 | void handleCalledFunction(llvm::Function& f, llvm::FunctionCallee counter); 29 | void handleInstruction(llvm::CallBase& cb, llvm::FunctionCallee counter); 30 | }; 31 | 32 | 33 | } // namespace callcounter 34 | 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /lib/callcounter-inst/include/StaticCallCounter.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef STATICCALLCOUNTER_H 4 | #define STATICCALLCOUNTER_H 5 | 6 | 7 | #include "llvm/ADT/DenseMap.h" 8 | #include "llvm/IR/InstrTypes.h" 9 | #include "llvm/IR/Module.h" 10 | #include "llvm/Pass.h" 11 | #include "llvm/Support/raw_ostream.h" 12 | 13 | 14 | namespace callcounter { 15 | 16 | 17 | struct StaticCallCounter : public llvm::ModulePass { 18 | static char ID; 19 | 20 | llvm::DenseMap counts; 21 | 22 | StaticCallCounter() : llvm::ModulePass(ID) {} 23 | 24 | bool runOnModule(llvm::Module& m) override; 25 | 26 | void print(llvm::raw_ostream& out, llvm::Module const* m) const override; 27 | 28 | void handleInstruction(llvm::CallBase& cb); 29 | }; 30 | 31 | 32 | } // namespace callcounter 33 | 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /lib/callcounter-rt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(callcounter-rt 2 | runtime.cpp 3 | ) 4 | 5 | -------------------------------------------------------------------------------- /lib/callcounter-rt/runtime.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | 6 | extern "C" { 7 | 8 | 9 | // This macro allows us to prefix strings so that they are less likely to 10 | // conflict with existing symbol names in the examined programs. 11 | // e.g. CCOUNT(entry) yields CaLlCoUnTeR_entry 12 | #define CCOUNT(X) CaLlCoUnTeR_##X 13 | 14 | // The count of the number of functions is stored in a global variable inside 15 | // the instrumented module. 16 | extern uint64_t CCOUNT(numFunctions); 17 | 18 | // An array of information for each function ID is stored within the 19 | // instrumented module. 20 | extern struct { 21 | char* name; 22 | uint64_t count; 23 | } CCOUNT(functionInfo)[]; 24 | 25 | 26 | void 27 | CCOUNT(called)(uint64_t id) { 28 | ++CCOUNT(functionInfo)[id].count; 29 | } 30 | 31 | 32 | void 33 | CCOUNT(print)() { 34 | printf("=====================\n" 35 | "Direct Function Calls\n" 36 | "=====================\n"); 37 | for (size_t id = 0; id < CCOUNT(numFunctions); ++id) { 38 | auto& info = CCOUNT(functionInfo)[id]; 39 | printf("%s: %lu\n", info.name, info.count); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/simpletest.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | void a() { printf("Found a\n"); } 5 | void b() { printf("Found b\n"); } 6 | int main() { 7 | b(); 8 | a(); 9 | b(); 10 | printf("Done!\n"); 11 | return 0; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /tools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(callcounter) 2 | 3 | -------------------------------------------------------------------------------- /tools/callcounter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake" 3 | "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY 4 | ) 5 | add_executable(callcounter 6 | main.cpp 7 | ) 8 | target_include_directories(callcounter 9 | PRIVATE 10 | ${CMAKE_CURRENT_BINARY_DIR} 11 | ) 12 | 13 | 14 | llvm_map_components_to_libnames(REQ_LLVM_LIBRARIES 15 | ${LLVM_TARGETS_TO_BUILD} 16 | asmparser linker bitreader bitwriter irreader 17 | target mc support 18 | ) 19 | 20 | target_link_libraries(callcounter 21 | PRIVATE 22 | callcounter-inst 23 | ${REQ_LLVM_LIBRARIES} 24 | ) 25 | 26 | # Platform dependencies. 27 | if( WIN32 ) 28 | find_library(SHLWAPI_LIBRARY shlwapi) 29 | target_link_libraries(callcounter 30 | PRIVATE 31 | ${SHLWAPI_LIBRARY} 32 | ) 33 | else() 34 | find_package(Threads REQUIRED) 35 | find_package(Curses REQUIRED) 36 | target_link_libraries(callcounter 37 | PRIVATE 38 | ${CMAKE_THREAD_LIBS_INIT} 39 | ${CMAKE_DL_LIBS} 40 | ${CURSES_LIBRARIES} 41 | ) 42 | endif() 43 | 44 | set_target_properties(callcounter PROPERTIES 45 | LINKER_LANGUAGE CXX 46 | PREFIX "" 47 | CXX_STANDARD 17 48 | ) 49 | 50 | install(TARGETS callcounter 51 | RUNTIME DESTINATION bin 52 | ) 53 | 54 | -------------------------------------------------------------------------------- /tools/callcounter/config.h.cmake: -------------------------------------------------------------------------------- 1 | 2 | #ifndef CALLCOUNTER_CONFIG_H 3 | #define CALLCOUNTER_CONFIG_H 4 | 5 | #define RUNTIME_LIB "callcounter-rt" 6 | #cmakedefine CMAKE_TEMP_LIBRARY_PATH "@CMAKE_TEMP_LIBRARY_PATH@" 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /tools/callcounter/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "llvm/ADT/SmallString.h" 3 | #include "llvm/Analysis/TargetLibraryInfo.h" 4 | #include "llvm/AsmParser/Parser.h" 5 | #include "llvm/Bitcode/BitcodeWriter.h" 6 | #include "llvm/CodeGen/CommandFlags.h" 7 | #include "llvm/CodeGen/LinkAllAsmWriterComponents.h" 8 | #include "llvm/CodeGen/LinkAllCodegenComponents.h" 9 | #include "llvm/IR/DataLayout.h" 10 | #include "llvm/IR/IRPrintingPasses.h" 11 | #include "llvm/IR/LLVMContext.h" 12 | #include "llvm/IR/Module.h" 13 | #include "llvm/IR/Verifier.h" 14 | #include "llvm/IRReader/IRReader.h" 15 | #include "llvm/Linker/Linker.h" 16 | #include "llvm/MC/TargetRegistry.h" 17 | #include "llvm/IR/LegacyPassManager.h" 18 | #include "llvm/Pass.h" 19 | #include "llvm/Support/CodeGen.h" 20 | #include "llvm/Support/CommandLine.h" 21 | #include "llvm/Support/FileUtilities.h" 22 | #include "llvm/Support/FormattedStream.h" 23 | #include "llvm/Support/ManagedStatic.h" 24 | #include "llvm/Support/PrettyStackTrace.h" 25 | #include "llvm/Support/Path.h" 26 | #include "llvm/Support/Program.h" 27 | #include "llvm/Support/Signals.h" 28 | #include "llvm/Support/SourceMgr.h" 29 | #include "llvm/Support/TargetSelect.h" 30 | #include "llvm/Support/ToolOutputFile.h" 31 | #include "llvm/Support/raw_ostream.h" 32 | #include "llvm/Target/TargetMachine.h" 33 | #include "llvm/TargetParser/Host.h" 34 | #include "llvm/TargetParser/SubtargetFeature.h" 35 | #include "llvm/TargetParser/Triple.h" 36 | #include "llvm/Transforms/Scalar.h" 37 | 38 | #include 39 | #include 40 | 41 | #include "DynamicCallCounter.h" 42 | #include "StaticCallCounter.h" 43 | 44 | #include "config.h" 45 | 46 | using namespace llvm; 47 | using std::string; 48 | using std::unique_ptr; 49 | using std::vector; 50 | using llvm::sys::ExecuteAndWait; 51 | using llvm::sys::findProgramByName; 52 | using llvm::legacy::PassManager; 53 | 54 | 55 | enum class AnalysisType { 56 | STATIC, 57 | DYNAMIC, 58 | }; 59 | 60 | 61 | static cl::OptionCategory callCounterCategory{"call counter options"}; 62 | 63 | static cl::opt inPath{cl::Positional, 64 | cl::desc{""}, 65 | cl::value_desc{"bitcode filename"}, 66 | cl::init(""), 67 | cl::Required, 68 | cl::cat{callCounterCategory}}; 69 | 70 | static cl::opt analysisType{ 71 | cl::desc{"Select analyis type:"}, 72 | cl::values(clEnumValN(AnalysisType::STATIC, 73 | "static", 74 | "Count static direct calls."), 75 | clEnumValN(AnalysisType::DYNAMIC, 76 | "dynamic", 77 | "Count dynamic direct calls.") 78 | ), 79 | cl::Required, 80 | cl::cat{callCounterCategory}}; 81 | 82 | static cl::opt outFile{"o", 83 | cl::desc{"Filename of the instrumented program"}, 84 | cl::value_desc{"filename"}, 85 | cl::init(""), 86 | cl::cat{callCounterCategory}}; 87 | 88 | static cl::opt optLevel{ 89 | "O", 90 | cl::desc{"Optimization level. [-O0, -O1, -O2, or -O3] (default = '-O2')"}, 91 | cl::Prefix, 92 | cl::ZeroOrMore, 93 | cl::init('2'), 94 | cl::cat{callCounterCategory}}; 95 | 96 | static cl::list libPaths{"L", 97 | cl::Prefix, 98 | cl::desc{"Specify a library search path"}, 99 | cl::value_desc{"directory"}, 100 | cl::cat{callCounterCategory}}; 101 | 102 | static cl::list libraries{"l", 103 | cl::Prefix, 104 | cl::desc{"Specify libraries to link against"}, 105 | cl::value_desc{"library prefix"}, 106 | cl::cat{callCounterCategory}}; 107 | 108 | // Make sure that compilation options are enabled when the program loads. 109 | static codegen::RegisterCodeGenFlags cfg; 110 | 111 | 112 | static void 113 | compile(Module& m, StringRef outputPath) { 114 | string err; 115 | 116 | Triple triple = Triple(m.getTargetTriple()); 117 | Target const* target = TargetRegistry::lookupTarget(codegen::getMArch(), triple, err); 118 | if (!target) { 119 | report_fatal_error(Twine{"Unable to find target:\n " + err}); 120 | } 121 | 122 | CodeGenOpt::Level level = CodeGenOpt::Default; 123 | switch (optLevel) { 124 | default: 125 | report_fatal_error("Invalid optimization level.\n"); 126 | // No fall through 127 | case '0': level = CodeGenOpt::None; break; 128 | case '1': level = CodeGenOpt::Less; break; 129 | case '2': level = CodeGenOpt::Default; break; 130 | case '3': level = CodeGenOpt::Aggressive; break; 131 | } 132 | 133 | string FeaturesStr; 134 | TargetOptions options = llvm::codegen::InitTargetOptionsFromCodeGenFlags(triple); 135 | auto relocationModel = codegen::getExplicitRelocModel(); 136 | auto codeModel = llvm::codegen::getExplicitCodeModel(); 137 | if (!relocationModel) { 138 | // Modern distriutions default to PIC, so override if not set. 139 | relocationModel = llvm::Reloc::Model::PIC_; 140 | } 141 | 142 | unique_ptr machine( 143 | target->createTargetMachine(triple.getTriple(), 144 | codegen::getCPUStr(), 145 | codegen::getFeaturesStr(), 146 | options, 147 | relocationModel, 148 | codeModel, 149 | level)); 150 | assert(machine && "Could not allocate target machine!"); 151 | 152 | if (llvm::codegen::getFloatABIForCalls() != FloatABI::Default) { 153 | options.FloatABIType = llvm::codegen::getFloatABIForCalls(); 154 | } 155 | 156 | std::error_code errc; 157 | auto out = 158 | std::make_unique(outputPath, errc, sys::fs::OF_None); 159 | if (!out) { 160 | report_fatal_error(Twine{"Unable to create file:\n " + errc.message()}); 161 | } 162 | 163 | // Build up all of the passes that we want to do to the module. 164 | legacy::PassManager pm; 165 | 166 | // Add target specific info and transforms 167 | TargetLibraryInfoImpl tlii(triple); 168 | pm.add(new TargetLibraryInfoWrapperPass(tlii)); 169 | 170 | m.setDataLayout(machine->createDataLayout()); 171 | 172 | { // Bound this scope 173 | raw_pwrite_stream* os(&out->os()); 174 | 175 | std::unique_ptr bos; 176 | if (!out->os().supportsSeeking()) { 177 | bos = std::make_unique(*os); 178 | os = bos.get(); 179 | } 180 | 181 | // Ask the target to add backend passes as necessary. 182 | if (machine->addPassesToEmitFile(pm, *os, nullptr, CGFT_ObjectFile)) { 183 | report_fatal_error("target does not support generation " 184 | "of this file type!\n"); 185 | } 186 | 187 | // Before executing passes, print the final values of the LLVM options. 188 | cl::PrintOptionValues(); 189 | 190 | pm.run(m); 191 | } 192 | 193 | // Keep the output binary if we've been successful to this point. 194 | out->keep(); 195 | } 196 | 197 | 198 | static void 199 | link(StringRef objectFile, StringRef outputFile) { 200 | auto clang = findProgramByName("clang++"); 201 | string opt("-O"); 202 | opt += optLevel; 203 | 204 | if (!clang) { 205 | report_fatal_error("Unable to find clang."); 206 | } 207 | vector args{clang.get(), opt, "-o", outputFile.str(), objectFile.str()}; 208 | 209 | for (auto& libPath : libPaths) { 210 | args.push_back("-L" + libPath); 211 | } 212 | 213 | for (auto& library : libraries) { 214 | args.push_back("-l" + library); 215 | } 216 | 217 | vector charArgs; 218 | charArgs.reserve(args.size()); 219 | for (auto& arg : args) { 220 | charArgs.emplace_back(arg); 221 | } 222 | 223 | for (auto& arg : args) { 224 | outs() << arg.c_str() << " "; 225 | } 226 | outs() << "\n"; 227 | 228 | string err; 229 | auto result = ExecuteAndWait( 230 | clang.get(), 231 | charArgs, 232 | std::nullopt, 233 | {}, 234 | 0, 235 | 0, 236 | &err 237 | ); 238 | if (-1 == result) { 239 | report_fatal_error("Unable to link output file."); 240 | } 241 | } 242 | 243 | 244 | static void 245 | generateBinary(Module& m, StringRef outputFilename) { 246 | // Compiling to native should allow things to keep working even when the 247 | // version of clang on the system and the version of LLVM used to compile 248 | // the tool don't quite match up. 249 | string objectFile = outputFilename.str() + ".o"; 250 | compile(m, objectFile); 251 | link(objectFile, outputFilename); 252 | } 253 | 254 | 255 | static void 256 | saveModule(Module const& m, StringRef filename) { 257 | std::error_code errc; 258 | raw_fd_ostream out(filename.data(), errc, sys::fs::OF_None); 259 | 260 | if (errc) { 261 | report_fatal_error("error saving llvm module to '" + filename + "': \n" 262 | + errc.message()); 263 | } 264 | WriteBitcodeToFile(m, out); 265 | } 266 | 267 | 268 | static void 269 | prepareLinkingPaths(SmallString<32> invocationPath) { 270 | // First search the directory of the binary for the library, in case it is 271 | // all bundled together. 272 | sys::path::remove_filename(invocationPath); 273 | if (!invocationPath.empty()) { 274 | libPaths.push_back(invocationPath.str().str()); 275 | } 276 | // If the builder doesn't plan on installing it, we still need to get to the 277 | // runtime library somehow, so just build in the path to the temporary one. 278 | #ifdef CMAKE_INSTALL_PREFIX 279 | libPaths.push_back(CMAKE_INSTALL_PREFIX "/lib"); 280 | #elif defined(CMAKE_TEMP_LIBRARY_PATH) 281 | libPaths.push_back(CMAKE_TEMP_LIBRARY_PATH); 282 | #elif defined(TEMP_LIBRARY_PATH) 283 | // This is a bit of a hack 284 | libPaths.push_back(TEMP_LIBRARY_PATH "/Debug+Asserts/lib/"); 285 | libPaths.push_back(TEMP_LIBRARY_PATH "/Release+Asserts/lib/"); 286 | libPaths.push_back(TEMP_LIBRARY_PATH "/Debug/lib/"); 287 | libPaths.push_back(TEMP_LIBRARY_PATH "/Release/lib/"); 288 | #endif 289 | libraries.push_back(RUNTIME_LIB); 290 | libraries.push_back("rt"); 291 | } 292 | 293 | 294 | static void 295 | instrumentForDynamicCount(Module& m) { 296 | InitializeAllTargets(); 297 | InitializeAllTargetMCs(); 298 | InitializeAllAsmPrinters(); 299 | InitializeAllAsmParsers(); 300 | cl::AddExtraVersionPrinter(TargetRegistry::printRegisteredTargetsForVersion); 301 | 302 | if (outFile.getValue().empty()) { 303 | errs() << "-o command line option must be specified.\n"; 304 | exit(-1); 305 | } 306 | 307 | // Build up all of the passes that we want to run on the module. 308 | legacy::PassManager pm; 309 | pm.add(new callcounter::DynamicCallCounter()); 310 | pm.add(createVerifierPass()); 311 | pm.run(m); 312 | 313 | generateBinary(m, outFile); 314 | saveModule(m, outFile + ".callcounter.bc"); 315 | } 316 | 317 | 318 | struct StaticCountPrinter : public ModulePass { 319 | static char ID; 320 | raw_ostream& out; 321 | 322 | explicit StaticCountPrinter(raw_ostream& out) : ModulePass(ID), out(out) {} 323 | 324 | bool 325 | runOnModule(Module& m) override { 326 | getAnalysis().print(out, &m); 327 | return false; 328 | } 329 | 330 | void 331 | getAnalysisUsage(AnalysisUsage& au) const override { 332 | au.addRequired(); 333 | au.setPreservesAll(); 334 | } 335 | }; 336 | 337 | char StaticCountPrinter::ID = 0; 338 | 339 | 340 | static void 341 | countStaticCalls(Module& m) { 342 | // Build up all of the passes that we want to run on the module. 343 | legacy::PassManager pm; 344 | pm.add(new callcounter::StaticCallCounter()); 345 | pm.add(new StaticCountPrinter(outs())); 346 | pm.run(m); 347 | } 348 | 349 | 350 | int 351 | main(int argc, char** argv) { 352 | // This boilerplate provides convenient stack traces and clean LLVM exit 353 | // handling. It also initializes the built in support for convenient 354 | // command line option handling. 355 | sys::PrintStackTraceOnErrorSignal(argv[0]); 356 | llvm::PrettyStackTraceProgram X(argc, argv); 357 | llvm_shutdown_obj shutdown; 358 | cl::HideUnrelatedOptions(callCounterCategory); 359 | cl::ParseCommandLineOptions(argc, argv); 360 | 361 | // Construct an IR file from the filename passed on the command line. 362 | SMDiagnostic err; 363 | LLVMContext context; 364 | unique_ptr module = parseIRFile(inPath.getValue(), err, context); 365 | 366 | if (!module.get()) { 367 | errs() << "Error reading bitcode file: " << inPath << "\n"; 368 | err.print(argv[0], errs()); 369 | return -1; 370 | } 371 | 372 | if (AnalysisType::DYNAMIC == analysisType) { 373 | prepareLinkingPaths(StringRef(argv[0])); 374 | instrumentForDynamicCount(*module); 375 | } else { 376 | countStaticCalls(*module); 377 | } 378 | 379 | return 0; 380 | } 381 | --------------------------------------------------------------------------------