首页
社区
课程
招聘
[原创]让LLVM16在windows上再次优雅起来
发表于: 2023-11-30 16:35 15302

[原创]让LLVM16在windows上再次优雅起来

2023-11-30 16:35
15302

让LLVM16在windows上再次优雅起来

一、前言

​ 有许多文章介绍了可以在windows动态加载的pass插件的方式使用LLVM,但都是针对一些老版本的LLVM,譬如12、8等。本文以LLVM16进行动态编译适配VS2022 pro。

二、前提准备

​ 前提准备按照此https://bbs.kanxue.com/thread-272346.htm的前提准备即可,需要注意的是一切版本安装最新的即可。

三. 编译动态库版本 LLVM

3.1 下载并解压 LLVM 源码

​ 和前面文章的不同,我们下载的源码文件比他多了一个clang源码,注意此源码是必须的,经过教训得出这个结论,下面是我的源码目录结构

image-20231129153505358

af9K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6D9L8s2k6E0i4K6u0r3L8r3I4$3L8g2)9J5k6s2m8J5L8$3A6W2j5%4c8Q4x3V1k6J5k6h3I4W2j5i4y4W2M7#2)9J5c8X3c8G2N6$3&6D9L8$3q4V1i4K6u0r3L8r3I4$3L8h3!0J5k6#2)9J5k6o6p5$3i4K6u0W2x3q4)9J5k6e0c8Q4x3V1k6U0L8r3q4F1k6#2)9J5k6o6p5$3i4K6u0W2x3q4)9J5k6e0c8Q4x3X3g2K6M7X3y4Q4x3X3g2@1j5i4u0Q4x3X3g2^5P5R3`.`.
bb2K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6D9L8s2k6E0i4K6u0r3L8r3I4$3L8g2)9J5k6s2m8J5L8$3A6W2j5%4c8Q4x3V1k6J5k6h3I4W2j5i4y4W2M7#2)9J5c8X3c8G2N6$3&6D9L8$3q4V1i4K6u0r3L8r3I4$3L8h3!0J5k6#2)9J5k6o6p5$3i4K6u0W2x3q4)9J5k6e0c8Q4x3V1k6U0L8h3q4C8k6g2)9J5k6o6p5$3i4K6u0W2x3q4)9J5k6e0c8Q4x3X3g2K6M7X3y4Q4x3X3g2@1j5i4u0Q4x3X3g2^5P5R3`.`.
126K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6D9L8s2k6E0i4K6u0r3L8r3I4$3L8g2)9J5k6s2m8J5L8$3A6W2j5%4c8Q4x3V1k6J5k6h3I4W2j5i4y4W2M7#2)9J5c8X3c8G2N6$3&6D9L8$3q4V1i4K6u0r3L8r3I4$3L8h3!0J5k6#2)9J5k6o6p5$3i4K6u0W2x3q4)9J5k6e0c8Q4x3V1k6D9L8r3c8Q4x3X3b7I4y4W2)9J5k6e0m8Q4x3X3f1@1i4K6u0W2M7%4u0U0i4K6u0W2N6r3q4J5i4K6u0W2P5s2Z5`.
68cK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6D9L8s2k6E0i4K6u0r3L8r3I4$3L8g2)9J5k6s2m8J5L8$3A6W2j5%4c8Q4x3V1k6J5k6h3I4W2j5i4y4W2M7#2)9J5c8X3c8G2N6$3&6D9L8$3q4V1i4K6u0r3L8r3I4$3L8h3!0J5k6#2)9J5k6o6p5$3i4K6u0W2x3q4)9J5k6e0c8Q4x3V1k6D9L8s2k6E0i4K6u0V1x3e0k6Q4x3X3f1H3i4K6u0W2y4q4)9J5k6i4y4J5j5#2)9J5k6i4c8S2M7W2)9J5k6i4S2*7

3.2 修改 LLVM 源码

​ 这里需要修改一下LLVM的源码,首先是llvm\lib\CMakeLists.txt文件,因为本身在window上编译是没有Mac的环境,因此会报一些Mac的头文件错误,我们只需要MACRO的组件去掉就行了。

image-20231129163856609

​ 还有就是注释完此行之后会有一些地方在引用MACRO会产生一些报错,直接修改就完事了,策略就是哪里报错改哪里(因为我也记不住改的哪里了)。

​ 编译源码的时候可以按照前文的方式进行编译,编译的时候推荐加上-DCMAKE_VERBOSE_MAKEFILE=ON选项,这样可以输出详细的构建信息,方便修改源码。下面是我构建完成的LLVM。

image-20231129172008448image-20231129172118983

3.4 给 LLVM 打上补丁

​ 因为是使用gcc编译的会缺少一些dll,因此我们要把这些dll加入路径中。

image-20231129172348527

四、通过新通行证编写PASS插件

1、llvm的PASS介绍

​ 简单来说就是llvm在编译的时候加了一层IR,如下图所示,而LLVM又给我们提供了一些列的接口,可以通过Pass插件操控和分析LLVM IR的生成。因以vs的cl编译为例对llvm pass的介绍这里就不做过的的赘述了,详细的介绍可以参考此文文章302K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6C8K9i4m8J5k6i4W2Q4x3X3g2Y4K9i4c8Z5N6h3u0Q4x3X3g2A6L8#2)9J5c8U0t1H3x3U0m8Q4x3V1j5H3y4W2)9J5c8V1I4x3g2V1#2Q4x3X3c8u0f1W2)9J5k6s2m8S2M7%4y4Q4c8e0y4Q4z5o6m8Q4z5o6t1`.

image-20231129234842049

​ 在进行下面的知识以前有一些预热的知识关于,关于ll文件bc文件的含义参考:024K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6*7K9s2g2S2L8X3I4S2L8W2)9J5k6i4A6Z5K9h3S2#2i4K6u0W2j5$3!0E0i4K6u0r3M7q4)9J5c8U0f1&6y4U0f1%4z5e0x3^5z5g2)9K6c8Y4g2@1L8g2)9#2k6X3W2V1i4K6y4p5x3q4!0q4x3#2)9^5x3q4)9^5x3R3`.`.

1
2
3
4
5
# 生成ll 文件
clang++ -S -O0 -emit-llvm test.cpp
 
# 生成bc文件
opt  test.ll -o test.bc

2、新的通行证管理器下的Hello World!

​ 和老版本的Hello World!不同新通行证,这个pass的功能非常简单,就是打印函数名字和参数个数,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Support/raw_ostream.h"
 
using namespace llvm;
 
namespace {
     
    // 通过PassInfoMixin模板类简化注册过程
    struct HelloWorld : PassInfoMixin {
      // Pass插件主入口点,为回调函数,会把源码的函数IR对象和和函数函数管理对象传递此函数
      PreservedAnalyses run(Function &F, FunctionAnalysisManager &) {
         
        // 打印了函数名字和参数个数
        errs() << "Hello from: "<< F.getName() << "\n";
        errs() << "number of arguments: " << F.arg_size() << "\n";
           
        //表示该 Pass 不会改变任何分析结果。
        return PreservedAnalyses::all();
      }
      static bool isRequired() { return true; }
    };
}
 
// 导出函数返回LLVM 插件的信息,包括 LLVM插件API版本、插件名称、LLVM 版本字符串以及注册插件的回调函数
llvm::PassPluginLibraryInfo getHelloWorldPluginInfo() {
  return {LLVM_PLUGIN_API_VERSION, "HelloWorld", LLVM_VERSION_STRING,
          [](PassBuilder &PB) {
            PB.registerPipelineParsingCallback(
                [](StringRef Name, FunctionPassManager &FPM,
                   ArrayRef) {
                  if (Name == "hello-world") {
                    FPM.addPass(HelloWorld());
                    return true;
                  }
                  return false;
                });
          }};
}
 
extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo
llvmGetPassPluginInfo() {
  return getHelloWorldPluginInfo();
}

​ 编写完源码之后我们构建CMakeLists.txt,下面是我的CMakeLists可以进行参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#项目名称
project(hello-world)
 
#cmake最低版本
cmake_minimum_required(VERSION 3.16)
 
# 设置LLVM的安装路径
if(NOT DEFINED ENV{LLVM_HOME})
    # User must define the LLVM_HOME environment that point to the root installation dir of llvm
    message(FATAL_ERROR "Environment variable $LLVM_HOME is not defined, user should define it before running cmake!")
endif()
 
message(STATUS "LLVM_HOME = [$ENV{LLVM_HOME}]")
 
# Default llvm config file path
set(ENV{LLVM_DIR} "$ENV{LLVM_HOME}\\lib\\cmake\\llvm")
message(STATUS "LLVM_DIR  : ${LLVM_DIR}")
 
 
# Check the path
if (NOT EXISTS $ENV{LLVM_DIR})
    message(STATUS "Path ($ENV{LLVM_DIR}) not found!")
 
    # If default llvm config path not found, try this one,
    # which is config with [-DLLVM_LIBDIR_SUFFIX=64] before building llvm
    set(ENV{LLVM_DIR} $ENV{LLVM_HOME}/lib64/cmake/llvm)
    if (NOT EXISTS $ENV{LLVM_DIR})
        message(FATAL_ERROR "Path ($ENV{LLVM_DIR}) not found!")
    else()
        message(STATUS "LLVM_DIR ($ENV{LLVM_DIR}) found!")
    endif()
else()
    message(STATUS "LLVM_DIR ($ENV{LLVM_DIR}) found!")
endif()
 
# 查找并加载LLVM的CMake模块
find_package(LLVM REQUIRED CONFIG)
 
# 将LLVM的CMake模块路径添加到CMake模块路径中
list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}")
 
# 添加LLVM的头文件路径
include_directories(${LLVM_INCLUDE_DIRS})
 
# 添加LLVM的库路径
link_directories(${LLVM_LIBRARY_DIRS})
 
# 添加LLVM的依赖库
add_definitions(${LLVM_DEFINITIONS})
 
# Debug
message(STATUS "CMAKE_MODULE_PATH : ${LLVM_CMAKE_DIR}")
message(STATUS "LLVM_DEFINITIONS  : ${LLVM_DEFINITIONS}")
message(STATUS "LLVM_INCLUDE_DIRS : ${LLVM_INCLUDE_DIRS}")
message(STATUS "LLVM_LIBRARY_DIRS : ${LLVM_LIBRARY_DIRS}")
 
# 添加项目文件
add_library( hello-world SHARED
    hello-world.cpp
  )
 
# Use C++11 to compile your pass (i.e., supply -std=c++11).
target_compile_features(hello-world PRIVATE cxx_range_for cxx_auto_type)
 
include_directories(./)
 
# LLVM is (typically) built with no C++ RTTI. We need to match that;
# otherwise, we'll get linker errors about missing RTTI data.
set_target_properties(hello-world PROPERTIES COMPILE_FLAGS "-fno-rtti")
 
# Get proper shared-library behavior (where symbols are not necessarily
# resolved when the shared library is linked) on OS X.
if(APPLE)
    set_target_properties(hello-world PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
endif(APPLE)
 
# 添加多线程支持
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(hello-world Threads::Threads)
 
# 添加链接库
target_link_libraries(hello-world
    libLLVMCore.dll.a
    libLLVMSupport.dll.a
    libLLVMipo.dll.a
    libLLVMPasses.dll.a
    libLLVMTransformUtils.dll.a
    libclang.dll.a
    libclangAnalysis.dll.a
    libclangAnalysisFlowSensitive.dll.a
    libclangAnalysisFlowSensitiveModels.dll.a
    libclangAPINotes.dll.a
    libclangARCMigrate.dll.a
    libclangAST.dll.a
    libclangASTMatchers.dll.a
    libclangBasic.dll.a
    libclangCodeGen.dll.a
    libclangCrossTU.dll.a
    libclangDependencyScanning.dll.a
    libclangDirectoryWatcher.dll.a
    libclangDriver.dll.a
    libclangDynamicASTMatchers.dll.a
    libclangEdit.dll.a
    libclangExtractAPI.dll.a
    libclangFormat.dll.a
    libclangFrontend.dll.a
    libclangFrontendTool.dll.a
    libclangHandleCXX.dll.a
    libclangHandleLLVM.dll.a
    libclangIndex.dll.a
    libclangIndexSerialization.dll.a
    libclangInterpreter.dll.a
)

​ 写完cmake之后构建编译脚本,下面是我的编译脚本,其中路径信息根据自己的情况进行修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
:: Set MSYS2 env
set PATH=%PATH%;C:\msys64\mingw64\bin
 
:: Set LLVM_HOME
set LLVM_HOME=C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\Llvm\x64
set CC=%LLVM_HOME%/bin/clang.exe
set CXX=%LLVM_HOME%/bin/clang++.exe
 
rd /Q /S .\build
 
:: Build release version
cmake -S ./ -B ./build -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=ON  -G "MinGW Makefiles" -DCMAKE_VERBOSE_MAKEFILE=ON
:: Build debug version
:: cmake -S ./ -B ./build -DCMAKE_BUILD_TYPE=Debug -DLLVM_ENABLE_ASSERTIONS=ON
 
:: Build  project
cmake --build ./build -j 4
 
pause

image-20231129190546641

3、运行自己的Hello-World通行证

​ 编译好 LLVMPass 插件之后,我们就可以使用这个插件了,下面我以test.cpp为例:

#include 
void fun1(const char* log)
{
    puts(log);
}
void fun2(int n1, int n2)
{
    printf("%d:::%d", n1, n2);
}
 
int main(int argc, char* argv[]){
    fun1("hello Pass!");
    fun2(5, 6);
    return 0;
}

首先我们要用clang编译器生成我们的ll文件,会生成如下的代码,此时已经生成了LLVM IR代码。

1
clang -O1 -S -emit-llvm test.cpp -o test.ll

image-20231129185038407

​ 生成LLVM IR代码之后,我们要使用我们的pass插件修改LLVM IR代码,用以下命令,可以看到已经成功调用我们的插件了,成功的输出了我们的函数名称和参数。

1
opt -load-pass-plugin libhello-world.dll  --passes="hello-world" test.ll -o test.bc

image-20231129190828541

​ 上面生成的是bc文件,我们也可以把bc文件再次翻译成ll文件使用以下命令

1
llvm-dis test.bc -o test2.ll

​ 当生成bc文件之后,我们就可以使用llc把bc文件翻译.s文件了,或者直接编译成obj文件,通过一下命令进行编译:

1
llc test.bc -o test.s/test.obj

image-20231129200133752


[注意]看雪招聘,专注安全领域的专业人才平台!

最后于 2023-11-30 17:09 被l140w4n9编辑 ,原因:
收藏
免费 11
支持
分享
最新回复 (12)
雪    币: 12862
活跃值: (9282)
能力值: ( LV9,RANK:280 )
在线值:
发帖
回帖
粉丝
2
不是很懂为什么都喜欢在LLVM上面整字符串加密
2023-11-30 23:39
0
雪    币: 371
活跃值: (653)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
主要练手用,字符串加密原理更容易理解,学会这个可以对llvm IR的模块、函数、基本块和指令的操作有初步的理解,这个帖子算是llvm入门贴。
2023-12-1 09:16
0
雪    币: 338
活跃值: (500)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
llvm是啥啊
2023-12-1 10:16
0
雪    币: 593
活跃值: (2372)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
5
2023-12-1 10:37
0
雪    币: 2494
活跃值: (5639)
能力值: ( LV10,RANK:160 )
在线值:
发帖
回帖
粉丝
6
感谢分享
2023-12-1 11:05
0
雪    币: 2193
活跃值: (2295)
能力值: ( LV12,RANK:243 )
在线值:
发帖
回帖
粉丝
7
非常好的文章,不过 https://bbs.kanxue.com/thread-272346.htm 这篇文章里说不能在Windows上用VS来编译LLVM,我等会抽空发一篇文章说一下怎么用VS2022的MSVC编译LLVM16,不需要对clone下来的源码进行任何改动
2023-12-1 11:25
0
雪    币: 7916
活跃值: (2891)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
8
学习了。。。
2023-12-1 14:05
0
雪    币: 74
活跃值: (898)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
2023-12-1 14:14
0
雪    币: 58
活跃值: (1220)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
古月浪子 非常好的文章,不过 https://bbs.kanxue.com/thread-272346.htm 这篇文章里说不能在Windows上用VS来编译LLVM,我等会抽空发一篇文章说一下怎么用VS202 ...
这个一直都可以啊,clone下来直接build就行。
2023-12-4 21:31
0
雪    币: 3836
活跃值: (4142)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
感谢分享
2024-1-12 01:00
0
雪    币: 1701
活跃值: (215907)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
12
优雅永不过时
2024-1-12 09:51
0
雪    币: 2053
活跃值: (3596)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
13
字符串加密代码中,处理结构体中字符串的代码判断条件好像存在一点问题,该条件“if (Initializer->getNumOperands() > 1) continue;”使得只能处理成员变量为1的结构体,是否可以将该条件去掉
2024-6-19 11:36
0
游客
登录 | 注册 方可回帖
返回