Clang LibTooling官方给出的教程中给出了直接在LLVM/Clang代码目录下进行工具开发的示例,但这样对于代码管理不甚方便,为此,尝试独立于LLVM代码树开发(即Out-of-Tree)
省流:在编译Clang时,添加CMake选项:-DLLVM_ENABLE_RTTI=ON
官方Tutorial分析
官方给出的LibTooling Tutorial大体上已经满足了开发环境的配置
官方Tutorial:https://clang.llvm.org/docs/LibASTMatchersTutorial.html
如果使用官方的配置方法,尝试在其他路径下撰写项目,仅在编译时再链接LLVM/Clang库则会出现如下报错:
user@debian:~/my-tool/build$ make [ 50%] Linking CXX executable my_tool /usr/bin/ld: CMakeFiles/my_tool.dir/src/MyTool.cpp.o:(.data.rel.ro._ZTIZN5clang7tooling24newFrontendActionFactoryINS_12ast_matchers11MatchFinderEEESt10unique_ptrINS0_21FrontendActionFactoryESt14default_deleteIS5_EEPT_PNS0_19SourceFileCallbacksEEN28FrontendActionFactoryAdapter22ConsumerFactoryAdaptorE[_ZTIZN5clang7tooling24newFrontendActionFactoryINS_12ast_matchers11MatchFinderEEESt10unique_ptrINS0_21FrontendActionFactoryESt14default_deleteIS5_EEPT_PNS0_19SourceFileCallbacksEEN28FrontendActionFactoryAdapter22ConsumerFactoryAdaptorE]+0x10): undefined reference to `typeinfo for clang::ASTFrontendAction' /usr/bin/ld: CMakeFiles/my_tool.dir/src/MyTool.cpp.o:(.data.rel.ro._ZTIZN5clang7tooling24newFrontendActionFactoryINS_12ast_matchers11MatchFinderEEESt10unique_ptrINS0_21FrontendActionFactoryESt14default_deleteIS5_EEPT_PNS0_19SourceFileCallbacksEE28FrontendActionFactoryAdapter[_ZTIZN5clang7tooling24newFrontendActionFactoryINS_12ast_matchers11MatchFinderEEESt10unique_ptrINS0_21FrontendActionFactoryESt14default_deleteIS5_EEPT_PNS0_19SourceFileCallbacksEE28FrontendActionFactoryAdapter]+0x10): undefined reference to `typeinfo for clang::tooling::FrontendActionFactory' /usr/bin/ld: CMakeFiles/my_tool.dir/src/MyTool.cpp.o:(.data.rel.ro._ZTI15FunctionPrinter[_ZTI15FunctionPrinter]+0x10): undefined reference to `typeinfo for clang::ast_matchers::MatchFinder::MatchCallback' collect2: error: ld returned 1 exit status make[2]: *** [CMakeFiles/my_tool.dir/build.make:156: my_tool] Error 1 make[1]: *** [CMakeFiles/Makefile2:351: CMakeFiles/my_tool.dir/all] Error 2 make: *** [Makefile:91: all] Error 2
报错分析:
这是一个非常典型的C++编译选项不匹配导致的链接错误:
undefined reference to 'typeinfo for ...'
意味着代码和链接的库之间 RTTI (Run-Time Type Information) 设置不一致(from Gemini2.5 pro)Out-of-Tree开发环境配置
下面我们梳理Out-of-Tree开发环境配置
Step 0: Obtaining Clang
(与官方Tutorial相同)
As Clang is part of the LLVM project, you’ll need to download LLVM’s source code first. Both Clang and LLVM are in the same git repository, under different directories. For further information, see the getting started guide.
mkdir ~/clang-llvm && cd ~/clang-llvmgit clone https://github.com/llvm/llvm-project.git
Next, you need to obtain the CMake build system and Ninja build tool.
cd ~/clang-llvm
git clone https://github.com/martine/ninja.git
cd ninja
git checkout release
./configure.py --bootstrap
sudo cp ninja /usr/bin/
cd ~/clang-llvm
git clone https://gitlab.kitware.com/cmake/cmake.git
cd cmake
./bootstrap
make
sudo make install
Step 1: 编译构建Clang
(与官方不同,out-of-tree环境)
cd ~/clang-llvm
mkdir build && cd build
cmake -G Ninja ../llvm-project/llvm \-DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra" \-DCMAKE_BUILD_TYPE=Release \-DLLVM_BUILD_TESTS=ON \-DCMAKE_INSTALL_PREFIX=/usr/local \ #安装及库文件路径-DLLVM_ENABLE_RTTI=ON # out-of-tree关键选项
ninja
ninja check # Test LLVM only.
ninja clang-test # Test Clang only.
最后将编译好的clang及其库文件安装至系统
sudo ninja install
简单验证:
# 确认 libclangTooling.a 已被安装到指定位置
ls /usr/local/lib/libclangTooling.a
# 输出:/usr/local/lib/libclangTooling.a
至此,基础out-of-tree环境基础完成
创建独立的 Out-of-Tree LibTooling 项目
- 项目结构
my-tool/
├── build
├── CMakeLists.txt #项目CMakeLists
├── src
│ └── MyTool.cpp #项目源代码
└── test_samples #测试用例目录└── test_function_name.c
- CMakeLists.txt
可直接复制使用,注意修改地方已经标明
cmake_minimum_required(VERSION 3.14)
project(MyTool)set(CMAKE_CXX_STANDARD 17)
find_package(LLVM REQUIRED CONFIG)
find_package(Clang REQUIRED CONFIG)# 添加头文件路径
include_directories(${LLVM_INCLUDE_DIRS})
include_directories(${CLANG_INCLUDE_DIRS})
link_directories(${LLVM_LIBRARY_DIRS})# 定义可执行文件
# ==========注意修改此处1==========
add_executable(my_tool src/MyTool.cpp
)
# ==========注意修改此处2==========
# 链接 Clang 库
target_link_libraries(my_toolPRIVATEclangToolingclangASTclangASTMatchersclangBasicclangFrontendclangSerializationclangDriverLLVMSupport
)
- 撰写最小化的工具代码 (MyTool.cpp)
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/CommandLine.h"// 使用 namespaces 简化代码
using namespace clang;
using namespace clang::ast_matchers;
using namespace clang::tooling;
using namespace llvm;// 定义一个 Matcher 来查找所有函数定义(不仅仅是声明)
// .bind("func") 将匹配到的节点绑定到名字 "func" 上,方便后续在回调中获取
DeclarationMatcher FunctionMatcher = functionDecl(isDefinition()).bind("func");// 定义一个回调类,当 Matcher 找到匹配项时,它的 run 方法会被调用
class FunctionPrinter : public MatchFinder::MatchCallback {
public:// 重写 run 方法virtual void run(const MatchFinder::MatchResult &Result) {// 从匹配结果中获取绑定的 "func" 节点// 并将其转换为 FunctionDecl (函数声明) 类型if (const FunctionDecl *FD = Result.Nodes.getNodeAs<FunctionDecl>("func")) {// 获取函数名StringRef FuncName = FD->getName();// 获取源码位置信息SourceManager &SM = *Result.SourceManager;SourceLocation FuncLocation = FD->getLocation();// 使用 llvm::outs()(一个线程安全的cout)打印结果outs() << "Found function: " << FuncName << " at "<< FuncLocation.printToString(SM) << "\n";}}
};// 为我们的工具设置命令行选项分类
static cl::OptionCategory MyToolCategory("find-functions options");int main(int argc, const char **argv) {// 使用 CommonOptionsParser 解析命令行参数,它会自动处理 -- 和 compile_commands.jsonauto ExpectedParser = CommonOptionsParser::create(argc, argv, MyToolCategory);if (!ExpectedParser) {errs() << ExpectedParser.takeError();return 1;}CommonOptionsParser &OptionsParser = ExpectedParser.get();ClangTool Tool(OptionsParser.getCompilations(), OptionsParser.getSourcePathList());// 创建我们的回调实例FunctionPrinter Printer;MatchFinder Finder;// 将 Matcher 和回调实例注册到 MatchFinder 中Finder.addMatcher(FunctionMatcher, &Printer);// 运行 ClangTool,它会解析AST并触发我们的回调return Tool.run(newFrontendActionFactory(&Finder).get());
}
- 测试用例(test_function_name.c)
void helper_function() {// 函数体为空,无任何外部调用
}int add(int a, int b) {return a + b;
}int main(int argc, char **argv) {// 声明一个变量并调用内部函数int result = add(5, 3);helper_function();// (void)result; return 0;
}// 一个只有声明没有定义的函数
void forward_declared_function();
- 编译与运行
编译:
cd my-tool/build
cmake ..
make
运行工具:
./my_tool ../test_samples/test_function_name.c --
应当出现如下结果:
liu@debian:~/code/my-tool/build$ ./my_tool ../test_samples/test_function_name.c --
Found function: helper_function at /home/liu/code/my-tool/build/../test_samples/test_function_name.c:3:6
Found function: add at /home/liu/code/my-tool/build/../test_samples/test_function_name.c:7:5
Found function: main at /home/liu/code/my-tool/build/../test_samples/test_function_name.c:11:5
附录:
debug方法
make VERBOSE=1
: 查看CMake执行的完整、真实的编译和链接命令,检查是否有多余的编译标志(如-fno-rtti
)。llvm-config --cxxflags --libs
: 查看已安装的LLVM环境推荐外部应用使用的编译和链接参数。find / -name "libclangTooling.a"
: 在整个系统中查找文件,确认其真实位置和名称。