LLVM:How 在 Windows 上使用 LLVM Pass

1. 前言

Evently,我来到了这一步,which I can 决定使用 what’s like 环境。Before 由于项目需要学习了如何利用 LLVM Pass 来处理源码进行混淆操作,但对于环境的配置却不知道如何进行。那么这篇 Blog 就是对于 如何在 Windows 上搭建 LLVM 环境的探索

之前我看了前人的智慧 如何优雅的在 Windows 上使用 LLVM Pass 插件进行代码混淆,就承接了这套环境进行了代码设计。但里面有些内容让我感觉在 Windows 上感觉很不方便,所以 want 探索新的环境搭建方式,时隔 1 年我终于开始了。使用 Pass 时遇到重大困难,还好有 Windows下优雅使用LLVMPass 这位前人的智慧。

这种不方便是由于使用的是 GCC 标准,而非 MSVC,使得你编译出来的代码找不到动态链接库,需要自带 MinGW 环境 or 使用静态链接。不同点在于:

  • 符号名称粉碎方式不同。GCCMSVC 有着各自的链接符号标准。
  • 动态链接库不同。MSVC 更适配 WIndows 平台。

2. Problem

首先我们要明确问题是什么,这样才能有的放矢。具体的我们 hope 有这样一个 clang which can 使用命令加载我们编译好的 LLVM Pass on Windows,然而却没有预编译好的版本,即都是不能加载 Pass clang,所以我们要自己进行编译。

一个关键字段 LLVM_BUILD_LLVM_DYLIB:BOOL 需要是 ON 这样才能够加载 LLVM Pass
在这里插入图片描述
看到这里你的心是不是已经凉了一半了,官方文档定性了 not available on Windows。但是我们需要明白的是,这里的 not available 指的是 MSVC 编译器。

这个产生的原因好像是 MSVC 无法处理巨量的导出符号。In LLVM 的论坛中 How to create pass independently on Windows? 中有相关描述和解决方案 which I try 了 and 失败了。
在这里插入图片描述
那么来看看 山重水尽疑无路,柳暗花明又一村 是什么样的。 Clang: a C language family frontend for LLVM 中有这样的描述:clang 与 GCCMSVC 都有兼容的。
在这里插入图片描述
Depend on clang 的兼容特点,可以设计一个这样的方案:使用 pre-build 的 clang which 不能加载 Pass on Windows 去编译 LLVM 源码,将 LLVM_BUILD_LLVM_DYLIB 设置为 ON 这样就能够得到我们期望的 eastwest 了。

3. clang

这里我们主要关注的与 VS 配套的 clang 版本 。

Visual Studio 2022clang 18.1.8
Visual Studio 2019clang 12.0.0

这些是可以通过 Visual Studio Install 进行查看的:

pic1 pic1

3.1 clang18

这有一个 good message that 你可以直接下载能够加载 Pass 版本的 clang18 on LLVM 18.1.8
在这里插入图片描述
下载上图中划线的压缩包,解压后的 clang 是能够加载 Pass 的,所以如果你重新开始编写项目,建议直接从 clang18 的标准来写代码。

LLVM x86_64-pc-windows-msvc 帖子中有关于这方面的讨论。

3.2 clang12

由于之前我的项目使用 llvm 12.0.0 编写的,相关代码在 clang18 下无法成功通过编译,所以不得不进行新的探索。但是有一点可以确定的是,探索一定会成功,因为 clang18 已经有了相应的 pre-build 的 eastwest 了。

这里先来句 grass your mum,在 LLVM 12.0.0 的下载页面没有期待的 eastwest,这 your horse 给 Linux 配套了一吨也不给 Windows 留一个。
在这里插入图片描述

4. 步骤

4.1 下载

  • 你需要下载与 VS 版本对应的 clang,这样才能够利用 MSVC 的编译环境来进行编译。
  • 下载 Ninja
  • 下载 cmake。一般如果你下载了 VS 的话可以在 Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin 文件夹下找到 cmake。
  • 下载 LLVM 12 源码

4.2 cmake

下面是 cmake 的一些参数选项,这里主要 say 一下 -G 选项,which 是用来控制生成器的。由于 VScl 编译器,所以不能选择 Visual Studio 这套系统,比较好用的其它方案就是使用 Ninja 较为小巧。

Options
 -S <path-to-source>          = Explicitly specify a source directory.
 -B <path-to-build>           = Explicitly specify a build directory.
 -G <generator-name>          = Specify a build system generator.
Generators

The following generators are available on this platform (* marks default):
* Visual Studio 17 2022        = Generates Visual Studio 2022 project files.
                                 Use -A option to specify architecture.
  Visual Studio 16 2019        = Generates Visual Studio 2019 project files.
                                 Use -A option to specify architecture. 
  Ninja                        = Generates build.ninja files.
  1. 解压 LLVM 12.0.0 的源码,切换到其目录下创建 build 文件夹。然后创建如下 .bat 文件, 你需要自行设置其中的 CCCXXINSTALL 的值。然后运行得到相关内容。

这里需要注意对于 clang 版本的选择,要与 3.clang 中描述的对应,即如果是 VS 2022 则要下载 clang18.1.8,而 VS 2019 则要下载 clang12.0.0。这里建议 clang12 的源码用 clang12 编译,不要使用 clang18,会导致后面编译 Pass 时出现链接错误。

set CC=E:/Tools/tmp/bin/clang.exe
set CXX=E:/Tools/tmp/bin/clang++.exe
set INSTALL=E:\Tools\clang12

cmake -G "Ninja" -DLLVM_HOST_TRIPLE=x86_64-pc-windows-msvc -DCMAKE_C_COMPILER="%CC%" -DCMAKE_CXX_COMPILER="%CXX%" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_RTTI=ON -DCMAKE_INSTALL_PREFIX="%INSTALL%" -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TARGETS_TO_BUILD="X86" -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_BUILD_LLVM_DYLIB=ON -DLLVM_INCLUDE_TESTS=OFF -DLLVM_ENABLE_WERROR=OFF -DCMAKE_CXX_FLAGS="-Wno-error -Wno-language-extension-token -Wno-deprecated-declarations -Wno-microsoft-enum-value -Wno-unused-parameter -Wno-deprecated-copy -Wno-overlength-strings -Wno-invalid-offsetof" ../llvm

下面是对上述长 cmake 命令的逐个解释:

cmake -G "Ninja"							::generate
 -DLLVM_HOST_TRIPLE=x86_64-pc-windows-msvc 	::target platform
 -DCMAKE_C_COMPILER="%CC%" 					::C compiler path
 -DCMAKE_CXX_COMPILER="%CXX%" 				::C++ compiler path
 -DCMAKE_BUILD_TYPE=Release 				
 -DLLVM_ENABLE_RTTI=ON 						::open Runtime Type
 -DCMAKE_INSTALL_PREFIX="%INSTALL%" 		::install position
 -DLLVM_ENABLE_PROJECTS="clang;lld" 
 -DLLVM_TARGETS_TO_BUILD="X86" 
 -DLLVM_ENABLE_TERMINFO=OFF 
 -DLLVM_BUILD_LLVM_DYLIB=ON 				::important, it must be on
 -DLLVM_INCLUDE_TESTS=OFF 
 -DLLVM_ENABLE_WERROR=OFF 
 -DCMAKE_CXX_FLAGS="-Wno-error -Wno-language-extension-token -Wno-deprecated-declarations -Wno-microsoft-enum-value -Wno-unused-parameter -Wno-deprecated-copy -Wno-overlength-strings -Wno-invalid-offsetof" 
 ../llvm									::path

你需要在 VS 控制台中运行上面的 .bat 脚本,因为需要有相应的 VS 编译环境。
在这里插入图片描述

  1. 运行完成之后会生成一个 build.ninja 文件需要对这个文件进行一点小小的修改,不然会后续进行链接时出现问题。
    在这里插入图片描述
    使用这样的关键字进行搜索 .def -fuse-ld=lld-link,在其行中添加 -Xlinker /DEF:,一般不会太多个,我的里面只有 9 处需要手动添加的。
    在这里插入图片描述
    下图就是不进行上述修改产生的报错信息:
    在这里插入图片描述
  2. 运行下面命令进行编译,-j 后面的参数是线程数量,根据你 CPU 的性能进行调整。
cmake --build . --target install -j 16

经过上面的过程之后你就可以得到一份可以加载 Passclang 了。

4.3 编译 Pass

创建一个如下的 CMakeLists.txt 文件在你想要编译 Pass 的位置。特别我我要指出 -frtti 选项,之前是 -fno-rttiMinGW 环境中,但切换到 MSVC 中必须设置。然后就是add_library中添加 Pass 源码所在文件。

  • CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(design)

# Enable C++17 (required for LLVM 14+)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

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!")
endif()
 
message(STATUS "LLVM_HOME = [$ENV{LLVM_HOME}]")

if(NOT DEFINED ENV{LLVM_DIR})
    # Default llvm config file path
    set(ENV{LLVM_DIR} $ENV{LLVM_HOME}/lib/cmake/llvm)
endif()

# 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 "Path ($ENV{LLVM_DIR}) found!")
    endif()
else()
    message(STATUS "Path ($ENV{LLVM_DIR}) found!")
endif()

find_package(LLVM REQUIRED CONFIG)
add_definitions(${LLVM_DEFINITIONS})
include_directories(${LLVM_INCLUDE_DIRS})
link_directories(${LLVM_LIBRARY_DIRS})

add_library(design SHARED design.cpp)

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(design PROPERTIES COMPILE_FLAGS "-frtti")

# 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(design PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
endif(APPLE)

target_link_libraries(design 
    PRIVATE LLVMCore LLVMSupport
)

接着创建一个 cmake_build.bat 文件,在 VS 控制台中运行该脚本即可完成 Pass 编译。

我的是 Visual Studio 2022,但 clang12 的配套环境是 2019,但 2022 是可以使用 2019 的工具链的,去 install 中下载相应的工具链。
在这里插入图片描述
下载完成后 Microsoft Visual Studio\2022\Community\VC\Tools\MSVC 中就会多出现一个文件夹记住 2019 的版本号。
在这里插入图片描述
为了配置相应的 MSVC 环境,可以打开一个 x86_64 的控制台,然后输入 echo %path%
在这里插入图片描述
echo 输出的结果全部复制出来,填入 cmake_build.batpath。同时将其中的 14.42.34433 替换为 14.29.30133 这样就配置好了 VS 2019 的环境变量而不用去单独下载 2019 了。
在这里插入图片描述

  • cmake_build.bat
set PATH=you copy in x86_64
set LLVM_HOME=E:\Tools\clang12
set CC=E:\Tools\clang12\bin\clang.exe
set CXX=E:\Tools\clang12\bin\clang++.exe

:: Remove build dir
rd /Q /S .\build
cmake -G "Ninja" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_RTTI=ON -S ./ -B ./build
cmake --build ./build --config Release
pause

双击 cmake_build.bat 即可完成对于 Pass 的编译。

4.4 使用 Pass

下面是一个失败的例子,我使用这样的命令之后没有得出想要的结果。探究其原因可能是使用的 LegacyPass 不能被在 Windows 很好的支持相关吧。不 want 进行深入探索了。

clang -emit-llvm test.cpp -o test.bc
opt --load-pass-plugin="me.dll" -passes=hello test.bc -o test.bc
Failed to load passes from 'me.dll'. Request ignored.
opt.exe: unknown pass name 'design'
#include <llvm/Pass.h>
#include <llvm/IR/Function.h>
#include <llvm/Support/raw_ostream.h>

using namespace llvm;

namespace {
  struct HelloPass : public FunctionPass {
    static char ID;
    HelloPass() : FunctionPass(ID) {}

    bool runOnFunction(Function &F) override {
      outs() << "Hello, Function: " << F.getName() << "!\n";
      return false; // No modification to the IR
    }
  };
}

char HelloPass::ID = 0;
static RegisterPass<HelloPass> X("hello", "Hello World Pass");

就在这时我找了这篇文章 Windows下优雅使用LLVMPass ,再次感谢这位大佬的分享。我需要使用 New Pass 去进行注册才能够被成功加载,那么新 Pass 的例子如下:

#include <llvm/Support/raw_ostream.h>
#include <llvm/IR/PassManager.h>
#include <llvm/Passes/PassPlugin.h>
#include <llvm/Passes/PassBuilder.h>

using namespace llvm;

#include <iostream>

namespace llvm{

  struct NewPassHelloWorld : public PassInfoMixin<NewPassHelloWorld> {

      PreservedAnalyses run(Module &F, ModuleAnalysisManager &AM) {
          std::cout << "NewPassHelloWorld Loaded" << std::endl;
          errs() << "MyPass:";
          errs() << F.getName() << "\n";
          return PreservedAnalyses::all();
      }
      bool isRequird(){ return true; }

  };

}
// This part is the new way of registering your pass
extern "C" ::llvm::PassPluginLibraryInfo
  LLVM_ATTRIBUTE_WEAK llvmGetPassPluginInfo() {
return {
  LLVM_PLUGIN_API_VERSION, "NewPassHelloWorld", "v0.1",
  [](PassBuilder &PB) {
    PB.registerPipelineParsingCallback(   //this callback register the function will be called after opt load the pass
      [](StringRef PassName, ModulePassManager &FPM, ...) {
        if(PassName == "NewPassHelloWorld"){
          FPM.addPass(NewPassHelloWorld());
          return true;
        }
        return false;
      }
    );
  }
};
}

这里使用新的类模板 PassInfoMixin 进行注册。在注册函数 PassPluginLibraryInfo 中使用了 lambda 表达式构造了临时函数。

最后运行下面的指令,成功的看到了测试 Pass 被成功调用。至此 fishwheel 的环境搭建 on Windows 算是大功告成了。

opt --load-pass-plugin=NewPassHelloWorld.dll --passes=NewPassHelloWorld aes.bc -o aes_d.bc
NewPassHelloWorld Loaded
MyPass:aes.bc

5. Bugs

这里记录一下一些 Bug 的实际截图:

  • clang18 编译的 clang12 编译 Pass
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值