Skip to content

Example Programs #774

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 16 commits into
base: development
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,16 @@ jobs:
- name: Run Unittests
shell: bash
run: |
cd build
cmake --build . --target check-phasar-unittests
cmake --build ./build --target check-phasar-unittests

- name: Install PhASAR and Build Examples
if: matrix.build == 'DebugLibdeps' # Circumvent conflicting ASAn flags
env:
CXX: ${{ matrix.compiler[0] }}
CC: ${{ matrix.compiler[1] }}
shell: bash
run: |
cmake -DCMAKE_INSTALL_PREFIX=./INSTALL -P ./build/cmake_install.cmake
cd ./examples/how-to
cmake -S . -B build -Dphasar_ROOT=../../INSTALL
cmake --build ./build --target run_sample_programs
19 changes: 19 additions & 0 deletions examples/how-to/00-load-llvm-ir/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
cmake_minimum_required(VERSION 3.14...3.28)

project(load-llvm-ir)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

find_package(phasar REQUIRED CONFIG)

add_executable(load-llvm-ir main.cpp)
target_link_libraries(load-llvm-ir PRIVATE phasar::phasar)

if (TARGET run_sample_programs)
add_custom_target(run_load_llvm_ir
DEPENDS load-llvm-ir
COMMAND $<TARGET_FILE:load-llvm-ir> "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/simple.ll"
)

add_dependencies(run_sample_programs run_load_llvm_ir)
endif()
24 changes: 24 additions & 0 deletions examples/how-to/00-load-llvm-ir/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Load LLVM IR

Shows, how you can use PhASAR to load and manage a LLVM IR module.

## Build

This example program can be built using cmake.
It assumes, that you have installed PhASAR on your system. If you did not install PhASAR to a default location, you can specify `-Dphasar_ROOT=your/path/to/phasar` replacing "your/path/to/phasar" by the actual path where you have installed PhASAR.

```bash
# Invoked from the 00-load-llvm-ir root folder:
$ mkdir -p build && cd build
$ cmake ..
$ cmake --build .
```

## Test

You can test the example program on the target programs from [llvm-hello-world/target](../../llvm-hello-world/target/).

```bash
# Invoked from the 00-load-llvm-ir/build folder:
./load-llvm-ir ../../../llvm-hello-world/target/simple.ll
```
55 changes: 55 additions & 0 deletions examples/how-to/00-load-llvm-ir/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#include "phasar/PhasarLLVM/DB.h" // For LLVMProjectIRDB
#include "phasar/PhasarLLVM/Passes.h" // For GeneralStatisticsAnalysis
#include "phasar/PhasarLLVM/Utils.h" // For llvmIRToString()

#include "llvm/IR/InstIterator.h" // For llvm::instructions()

static void printIRStats(psr::LLVMProjectIRDB &IRDB);

int main(int Argc, char *Argv[]) {
if (Argc < 2) {
llvm::errs() << "USAGE: load-llvm-ir <LLVM-IR file>\n";
return 1;
}

// The LLVMProjectIRDB loads and manages an LLVM-IR module.
// You can load both .ll (human readable) and .bc (smaller, faster load-times)
// files.
// If you already have an llvm::Module*, you can also pass it here.
psr::LLVMProjectIRDB IRDB(Argv[1]);
if (!IRDB) {
// If phasar yould not load the IR, we should exit.
// Phasar has already printed an error message to the terminal.
return 1;
}

// ========
// Now, you can work with the module

printIRStats(IRDB);

// Inspect the module (see also llvm-hello-world)

auto *F = IRDB.getFunctionDefinition("main");
if (!F) {
llvm::errs() << "error: could not find function 'main'\n";
return 1;
}

llvm::outs() << "--------------- Instructions of 'main' ---------------\n";

for (const auto &Inst : llvm::instructions(F)) {
// Phasar annotates all instructions (and global variables) with IRDB-wide
// unique integer Ids:
auto InstId = IRDB.getInstructionId(&Inst);

llvm::outs() << '#' << InstId << ": " << psr::llvmIRToString(&Inst) << '\n';

// TODO: Analyze instruction 'Inst' here.
}
}

static void printIRStats(psr::LLVMProjectIRDB &IRDB) {
psr::GeneralStatisticsAnalysis Stats;
llvm::outs() << Stats.runOnModule(*IRDB.getModule()) << '\n';
}
19 changes: 19 additions & 0 deletions examples/how-to/01-build-type-hierarchy/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
cmake_minimum_required(VERSION 3.14...3.28)

project(build-type-hierarchy)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

find_package(phasar REQUIRED CONFIG)

add_executable(build-type-hierarchy main.cpp)
target_link_libraries(build-type-hierarchy PRIVATE phasar::phasar)

if (TARGET run_sample_programs)
add_custom_target(run_build_type_hierarchy
DEPENDS build-type-hierarchy
COMMAND $<TARGET_FILE:build-type-hierarchy> "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/class_hierarchy.ll"
)

add_dependencies(run_sample_programs run_build_type_hierarchy)
endif()
24 changes: 24 additions & 0 deletions examples/how-to/01-build-type-hierarchy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Build Type Hierarchy

Shows, how you can use PhASAR to build and use a type hierarchy from a LLVM IR module.

## Build

This example program can be built using cmake.
It assumes, that you have installed PhASAR on your system. If you did not install PhASAR to a default location, you can specify `-Dphasar_ROOT=your/path/to/phasar` replacing "your/path/to/phasar" by the actual path where you have installed PhASAR.

```bash
# Invoked from the 01-build-type-hierarchy root folder:
$ mkdir -p build && cd build
$ cmake ..
$ cmake --build .
```

## Test

You can test the example program on the target programs from [llvm-hello-world/target](../../llvm-hello-world/target/).

```bash
# Invoked from the 01-build-type-hierarchy/build folder:
./build-type-hierarchy ../../../llvm-hello-world/target/class_hierarchy.ll
```
47 changes: 47 additions & 0 deletions examples/how-to/01-build-type-hierarchy/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include "phasar/PhasarLLVM/DB.h"
#include "phasar/PhasarLLVM/TypeHierarchy.h"

#include "llvm/Demangle/Demangle.h"

int main(int Argc, char *Argv[]) {
if (Argc < 2) {
llvm::errs() << "USAGE: build-type-hierarchy <LLVM-IR file>\n";
return 1;
}

// Load the IR
psr::LLVMProjectIRDB IRDB(Argv[1]);
if (!IRDB) {
return 1;
}

// Build the type hierarchy.
// Note that this DIBasedTypeHierarchy requires debug information (DI) to be
// embedded into the LLVM IR. You can achieve this by passing -g to clang.
psr::DIBasedTypeHierarchy TH(IRDB);

for (const auto *ClassTy : TH.getAllTypes()) {
llvm::outs() << "Found class type " << ClassTy->getName() << " ("
<< TH.getTypeName(ClassTy) << ")\n";
llvm::outs() << "> demangled name: "
<< llvm::demangle(TH.getTypeName(ClassTy).str()) << '\n';
}
llvm::outs() << '\n';

// Try to find class 'A'
const auto *ClassA = TH.getType("_ZTS1A");

// If TH does not find, it returns nullptr.

if (ClassA != nullptr) {
// Get the (transitive) sub-types of ClassA
for (const auto *ClassTy : TH.subTypesOf(ClassA)) {
llvm::outs() << "Class " << ClassTy->getName()
<< " is a (transitive) sub-type of A\n";

// You can also check, whether a type is a (transitive) sub-type of
// another type:
assert(TH.isSubType(ClassA, ClassTy));
}
}
}
23 changes: 23 additions & 0 deletions examples/how-to/02-build-call-graph/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
cmake_minimum_required(VERSION 3.14...3.28)

project(build-call-graph)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

find_package(phasar REQUIRED CONFIG)

add_executable(build-llvm-based-icfg build_llvm_based_icfg.cpp)
target_link_libraries(build-llvm-based-icfg PRIVATE phasar::phasar)

add_executable(build-llvm-based-call-graph build_llvm_based_call_graph.cpp)
target_link_libraries(build-llvm-based-call-graph PRIVATE phasar::phasar)

if (TARGET run_sample_programs)
add_custom_target(run_build_call_graph
DEPENDS build-llvm-based-icfg build-llvm-based-call-graph
COMMAND $<TARGET_FILE:build-llvm-based-icfg> "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/class_hierarchy.ll"
COMMAND $<TARGET_FILE:build-llvm-based-call-graph> "${CMAKE_CURRENT_LIST_DIR}/../../llvm-hello-world/target/class_hierarchy.ll"
)

add_dependencies(run_sample_programs run_build_call_graph)
endif()
38 changes: 38 additions & 0 deletions examples/how-to/02-build-call-graph/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Build CallGraph

Shows several ways, how you can use PhASAR to build and use a call graph from a LLVM IR module.

You may look at the different C++ source files to see, how you can build a call graph using PhASAR.
You may want to start with [build_llvm_based_icfg.cpp](./build_llvm_based_icfg.cpp).

## Build

This example program can be built using cmake.
It assumes, that you have installed PhASAR on your system. If you did not install PhASAR to a default location, you can specify `-Dphasar_ROOT=your/path/to/phasar` replacing "your/path/to/phasar" by the actual path where you have installed PhASAR.

```bash
# Invoked from the 02-build-call-graph root folder:
$ mkdir -p build && cd build
$ cmake ..
$ cmake --build .
```

## Test

You can test the example program on the target programs from [llvm-hello-world/target](../../llvm-hello-world/target/).

```bash
# Invoked from the 02-build-call-graph/build folder:
./build-llvm-based-icfg ../../../llvm-hello-world/target/class_hierarchy.ll

./build-llvm-based-call-graph ../../../llvm-hello-world/target/class_hierarchy.ll
```

### Visualizing the CallGraph

The test programs show, how you can export a call-graph to a dot-graph.
You can use the `dot` command-line tool (get this by, e.g., invoking `apt install graphviz` or similar).

The call-graph obtained from the example program on the sample `class_hierarchy.ll` should look similar to this:

![Sample Call-Graph](./img/cg.svg)
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#include "phasar/PhasarLLVM/ControlFlow.h"
#include "phasar/PhasarLLVM/ControlFlow/LLVMBasedCallGraphBuilder.h"
#include "phasar/PhasarLLVM/DB.h"
#include "phasar/PhasarLLVM/TypeHierarchy.h"
#include "phasar/PhasarLLVM/Utils.h"

#include "llvm/Demangle/Demangle.h"

int main(int Argc, char *Argv[]) {
if (Argc < 2) {
llvm::errs() << "USAGE: build-llvm-based-call-graph <LLVM-IR file>\n";
return 1;
}

// Load the IR
psr::LLVMProjectIRDB IRDB(Argv[1]);
if (!IRDB) {
return 1;
}

if (!IRDB.getFunctionDefinition("main")) {
llvm::errs() << "Required function 'main' not found\n";
return 1;
}

// We may wish to use type-information for construcing the call-graph
psr::DIBasedTypeHierarchy TH(IRDB);

// Needed to resolve indirect calls to C++ virtual functions
psr::LLVMVFTableProvider VTP(IRDB);

// The resolver defines, how the call-graph construction algorithm resolves
// indirect calls.
// This comprises calls through a function pointer, as well as virtual
// functions. Here, we select the Rapid Type Analysis that requires a
// type-hierarchy as input.
//
// You can also write your own resolver by creating a class that inherits from
// the psr::Resolver interface.
psr::RTAResolver Resolver(&IRDB, &VTP, &TH);

// You must specify at least one function as entry-point. The
// LLVMBasedICFG will only consider those functions for the call-graph
// that are reachable from at least on eof the entry-points.
auto CG = psr::buildLLVMBasedCallGraph(IRDB, Resolver, {"main"});

// Iterate over all call-sites:
for (const auto *Call : CG.getAllVertexCallSites()) {
if (Call->isDebugOrPseudoInst()) {
// We may with to skip the auto-generated debug-intrinsics
continue;
}

llvm::outs() << "Found call-site: " << psr::llvmIRToString(Call) << '\n';

// The probably most important function: getCalleesOfCallAt()
for (const auto *CalleeFun : CG.getCalleesOfCallAt(Call)) {
llvm::outs() << "> calling "
<< llvm::demangle(CalleeFun->getName().str()) << '\n';
}
llvm::outs() << '\n';
}

llvm::outs() << "--------------------------\n";

// You can also go the other way around:
for (const auto *Fun : CG.getAllVertexFunctions()) {
llvm::outs() << "Found Function: " << llvm::demangle(Fun->getName().str())
<< '\n';

// The probably second-most important function: getCallersOf()
for (const auto *CallSite : CG.getCallersOf(Fun)) {
llvm::outs() << "> called from " << psr::llvmIRToString(CallSite)
<< '\n';
}
llvm::outs() << '\n';
}

// You can create an LLVMBasedICFG from an already existing call-graph:
psr::LLVMBasedICFG ICFG(std::move(CG), &IRDB);

llvm::outs() << "--------------------------\n";

// You can export the call-graph as dot, such that you can display it
// using a graphviz viewer:
ICFG.print();
}
Loading
Loading