使用 SVF 分析某個開源項目
因為一些學習任務需要在本地使用 SVF 構建某個項目的 CallGraph,中間遇到一點問題
wLLVM
參考 https://blog.csdn.net/weixin_47778392/article/details/141107768
SVF 編譯時終端 exited with code 1
需要更新 CMake 版本,嘗試更新 CMake:參考 https://blog.csdn.net/qq_42176274/article/details/144149612
使用 SVF 分析一個簡單 demo
寫一個包含直接調用和間接調用的 c 語言 demo:
#include <stdio.h>
void foo() {
printf("foo\n");
}
void bar() {
printf("bar\n");
}
int main() {
void (*fp)() = bar;
fp(); // 間接調用
foo(); // 直接調用
return 0;
}
獲取 bitcode,編譯時注意關閉 optnone:
clang -O0 -g -emit-llvm -c demo.c -o demo.bc -Xclang -disable-O0-optnone
編譯復雜項目的時候用 wllvm 導出 bitcode,以 httpd 為例:
CC=wllvm CFLAGS="-O0 -g -save-temps=obj -fno-discard-value-names -w -Xclang -disable-O0-optnone" ./configure
make
extract-bc -l llvm-link httpd
使用 Anderson 指針分析構造 callgraph: wpa -ander -dump-callgraph demo.bc
callgraph_initial.dot
digraph "Call Graph" {
label="Call Graph";
Node0x55d3e7dd2620 [shape=record,shape=box,label="{CallGraphNode ID: 0 \{fun: foo\}|{<s0>1}}"];
Node0x55d3e7dd2620:s0 -> Node0x55d3e7da6830[color=black];
Node0x55d3e7da4ab0 [shape=record,shape=box,label="{CallGraphNode ID: 1 \{fun: bar\}|{<s0>2}}"];
Node0x55d3e7da4ab0:s0 -> Node0x55d3e7da6830[color=black];
Node0x55d3e7da4bf0 [shape=record,shape=box,label="{CallGraphNode ID: 2 \{fun: main\}|{<s0>3}}"];
Node0x55d3e7da4bf0:s0 -> Node0x55d3e7dd2620[color=black];
Node0x55d3e7da6830 [shape=record,shape=Mrecord,label="{CallGraphNode ID: 3 \{fun: printf\}}"];
Node0x55d3e7da6970 [shape=record,shape=Mrecord,label="{CallGraphNode ID: 4 \{fun: llvm.dbg.declare\}}"];
}
callgraph_final.dot
digraph "Call Graph" {
label="Call Graph";
Node0x55d3e7dd2620 [shape=record,shape=box,label="{CallGraphNode ID: 0 \{fun: foo\}|{<s0>1}}"];
Node0x55d3e7dd2620:s0 -> Node0x55d3e7da6830[color=black];
Node0x55d3e7da4ab0 [shape=record,shape=box,label="{CallGraphNode ID: 1 \{fun: bar\}|{<s0>2}}"];
Node0x55d3e7da4ab0:s0 -> Node0x55d3e7da6830[color=black];
Node0x55d3e7da4bf0 [shape=record,shape=box,label="{CallGraphNode ID: 2 \{fun: main\}|{<s0>3|<s1>4}}"];
Node0x55d3e7da4bf0:s0 -> Node0x55d3e7dd2620[color=black];
Node0x55d3e7da4bf0:s1 -> Node0x55d3e7da4ab0[color=red];
Node0x55d3e7da6830 [shape=record,shape=Mrecord,label="{CallGraphNode ID: 3 \{fun: printf\}}"];
Node0x55d3e7da6970 [shape=record,shape=Mrecord,label="{CallGraphNode ID: 4 \{fun: llvm.dbg.declare\}}"];
}
容易觀察到,通過指針分析,(通過函數指針的)間接調用被識別出來添加到 callgraph 里面了。接下來可以通過寫一個簡單的 llvm pass 從 bitcode 里面分析哪些是間接調用,從而分析 SVF 給出的 callgraph 中函數指針有沒有被分析出來(或者說被分析認為是哪些函數)
寫一個 IndirectCallPass
對于一個 CallBase,如果 getCalledFunction() == nullptr,可以認為它是一個間接調用。
#include "llvm/IR/PassManager.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Instructions.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Passes/PassBuilder.h"
#include <fstream>
using namespace llvm;
namespace {
struct IndirectCallPass : public PassInfoMixin<IndirectCallPass> {
PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM) {
static std::ofstream outfile("indirect_calls.jsonl", std::ios::app);
unsigned callsite_idx = 0;
for (auto &BB : F) {
for (auto &I : BB) {
if (auto *callInst = dyn_cast<CallBase>(&I)) {
auto *calledFunc = callInst->getCalledFunction();
if (calledFunc && calledFunc->isIntrinsic()){
continue;
}
if (!callInst->getCalledFunction()) {
outfile << F.getName().str() << "," << callsite_idx << "\n";
}
callsite_idx++;
}
}
}
return PreservedAnalyses::all();
}
};
} // namespace
// 注冊Pass(供opt工具使用)
llvm::PassPluginLibraryInfo getIndirectCallPassPluginInfo() {
return {LLVM_PLUGIN_API_VERSION, "find-indirect-calls", LLVM_VERSION_STRING,
[](PassBuilder &PB) {
PB.registerPipelineParsingCallback(
[](StringRef Name, FunctionPassManager &FPM,
ArrayRef<PassBuilder::PipelineElement>) {
if (Name == "find-indirect-calls") {
FPM.addPass(IndirectCallPass());
return true;
}
return false;
});
}};
}
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() {
return getIndirectCallPassPluginInfo();
}
通過 opt 工具使用這個 pass:
opt -load-pass-plugin=./IndirectCallPass.so -passes="find-indirect-calls" ./httpd.bc
對于通過 SVF 的 Anderson 導出的 callgraph,它不能精確到調用點位置信息,只能得到“某些函數調用了哪些函數”這樣的信息。導出的 ICFG 也暫時只包含分析前的結果。所以最好還是把 SVF 作為一個 library 導入并使用。
=== SVF 關于 callgraph 和間接調用的部分源碼分析
觀察 SVF 的 CallGraph.h 文件:
-
class CallGraphEdge:從函數 A 到函數 B 的多個調用會被合并成一個調用邊,每個邊有一組直接調用和一組間接調用CallInstSet indirectcalls,每個調用邊有一個CallSiteID csId(這個設計有點奇怪),有方法getIndirectCalls() -
class CallGraph:在多個指針分析中會被使用,每個調用圖有一個成員CallEdgeMap indirectCallMap維護某個圖上節點CallICFGNode到間接調用的函數的映射,有方法hasIndCSCallees和getIndCSCallees判別和獲取間接調用的函數信息,getIndCallSitesInvokingCallee獲取間接調用對應的調用點
感覺內容暫時夠用,可以先在官方提供的 SVF-example 上試驗一下,獲取所有的間接調用的 callsite 及其指針分析后結果(指向哪些函數)
=== 在項目中使用 SVF,并且獲取間接調用可能指向的函數
對于上面的 demo.c,可以在 SVF-example 里面寫:
CallGraph::CallEdgeMap &indCallMap = callgraph->getIndCallMap();
std::ofstream outfile("indirect_calls");
for (CallGraph::CallEdgeMap::iterator it = indCallMap.begin(), eit = indCallMap.end(); it != eit; ++it) {
const CallICFGNode *cs = it->first;
const CallGraph::FunctionSet &callees = it->second;
for (const FunObjVar *callee : callees) {
std::string calleeName = callee ? callee->getName() : "<unknown>";
outfile << cs->getSourceLoc() << " : " << calleeName << std::endl;
}
}
outfile.close();
輸出結果應當形如:
CallICFGNode: { "ln": 25, "cl": 5, "fl": "demo.c" } : baz
...
改進了一下 demo,用一個 array 來放函數指針,發現 anderson 給出的結果不夠精確。
使用 SVF-example 分析一下 httpd 項目,得到結果:
...
CallICFGNode: { "ln": 301, "cl": 34, "fl": "config.c" } : merge_core_dir_configs
...
去看源代碼,發現這里確實對應一個函數指針:
conf_vector[i] = (*df)(p, base_vector[i], new_vector[i]);
使用前面的 llvm pass 跑一下所有間接調用發生的位置,觀察 anderson 的推理結果(是否有忽略的函數指針):
config.c,85,977
config.c,88,963
config.c,93,1014
config.c,98,945
config.c,102,996
config.c,160,960
config.c,165,928
config.c,169,870
...
發現 llvm pass 識別出了一些特殊形式的東西,比如對于宏定義:
AP_IMPLEMENT_HOOK_RUN_ALL(int, header_parser,
(request_rec *r), (r), OK, DECLINED)
這個宏定義會被展開成一個包含間接調用的函數,anderson 靜態分析沒有實際做這個分析,暫時不知道是因為宏的問題還是 array 的問題。
===

浙公網安備 33010602011771號