Ollvm整合到XCode中的几种方法

我是去年10月左右开始研究Ollvm,现将一部分心得分享出来

基本概念

LLVM Pass按加载方式分类:

  • 静态Pass,即将Pass逻辑编译到clang/opt中,通过给clang/opt传递适当参数触发Pass。如果目标语言和编译环境不支持命令行方式指定Pass则可尝试此方式。目前大部分开源Ollvm项目都是此种方式,且一定程度修改了LLVM源码部分
  • 动态Pass,即将Pass逻辑编译为独立动态库,通过给clang/opt传递适当参数触发Pass。如果目标语言和编译环境不方便使用自定义clang/opt但支持命令行方式指定动态Pass则可尝试此方式,此方式在Pass开发编译调试时优势明显。

常见问题

  • 动态PassLLVM版本要匹配吗?是的,至少大版本要匹配否则大概率崩
  • 开发动态PassXCode使用以实现Ollvm混淆的思路可行吗?可行
  • 直接针对XCodeApple Clang开发动态Pass可行吗?可行

XCode使用Pass的方式(以下方式笔者均验证过可行)

  • 静态Pass方式1,编译为XCode Toolchain,也是目前大部分开源Ollvm项目所采用的方式。不过编译时间久一些且Toolchain占用的存储空间也大一些。
  • 静态Pass方式2,直接使用clang,在Xcode中指定CC变量为内嵌Passclang。如果isysroot不兼容可能需要在Other C Flags中覆盖
  • 动态Pass方式3,在Xcode中指定CC变量为开源clang(如brew install llvm@15),且指定Other C Flags指定-fpass-plugin为对应Pass路径
  • 动态Pass方式4,在Xcode中指定CC变量为编译脚本,脚本逻辑为"先用clang -emit-llvm参数生成bitcode,然后运行opt执行Pass,最后用clang -c生成原本要生成的obj文件"。此种方式可以直接使用Xcode自带的Apple clang,能比较好的兼容arm64e架构
  • 动态Pass方式5,直接针对Xcode自带的Apple clang开发动态Pass,在Xcode中指定Other C Flags指定-fpass-plugin为Pass路径。此种方式复杂度较高,只适合精通llvm编译的开发者。此种方式可以直接使用Xcode自带的Apple clang,能比较好的兼容arm64e架构

针对AppleClang开发动态Pass

现在来看看最难的部分,如果直接使用AppleClang对应版本的开源LLVM开发Pass给AppleClang使用是会崩溃的,因为AppleClang相对于开源LLVM魔改了很多地方。为了让AppleClang能运行起来一个最简单的Pass你至少需要如下hacker代码:

__attribute__((visibility("default"))) void Value::dump() const { 
    print(dbgs(), /*IsForDebug=*/true); dbgs() << '\n'; 
}
__attribute__((visibility("default"))) void Module::dump() const {
  print(llvm::errs(), 0, true);
}
class Ollvm : public PassInfoMixin<Ollvm> {
public:
    PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) {
        M.dump();
#ifdef APPLE_CLANG
        static PreservedAnalyses (*PreservedAnalyses__all)() = 0;
        static PreservedAnalyses (*PreservedAnalyses__none)() = 0;
        if (PreservedAnalyses__all == 0) {
            const char* image = _dyld_get_image_name(0);
            PreservedAnalyses__all = (__typeof(PreservedAnalyses__all)) DobbySymbolResolver(image, 
                "__ZN4llvm6detail9PassModelINS_6ModuleEN12_GLOBAL__N_114NoOpModulePassENS_17PreservedAnalysesENS_15AnalysisManagerIS2_JEEEJEE3runERS2_RS7_");
            PreservedAnalyses__none = (__typeof(PreservedAnalyses__none))DobbySymbolResolver(image, 
                "__ZN4llvm6detail9PassModelINS_6ModuleENS_25InvalidateAllAnalysesPassENS_17PreservedAnalysesENS_15AnalysisManagerIS2_JEEEJEE3runERS2_RS6_");
        }
        return (opc > 0) ? PreservedAnalyses__all() : PreservedAnalyses__none();
#else
        return (opc > 0) ? PreservedAnalyses::all() : PreservedAnalyses::none();
#endif
    };
    static bool isRequired() { return true; }
};

以上操作仅仅是让你“入门”,而下一步就是处理AppleClang魔改造成的冲突,包括但不限于补全符号/拆分静态库/自行实现冲突代码,也是个复杂且麻烦的工程了,有兴趣的可以接着研究下去

4 个赞

给大佬点赞了,大佬在Xcode16.x上成功了吗,我用llvm19一直没有搞成

我没用Xcode16用的Xcode15。本质都一样的

嗯呢,我也是用的Xcode15,不过没有大佬研究的这么深入

大佬请教下,ollvm只能编译oc吧,如果是混编工程如何处理呢?

理论上能编译为IR的语言如swift/rust/python等,都可以ollvm混淆,不过我没怎么用过,不确定兼容性如何

收到,谢谢。我理解ollvm原来是用于clang支持oc,现在swiftlang可能要结合swift源码改造一下,又设计到跟clang的结合,所以请教一下

请问下大佬cmake指令是怎么写的,我这样写cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_CREATE_XCODE_TOOLCHAIN=ON -DLLVM_ENABLE_PROJECTS=clang -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ - …/llvm,xcode上用编译出来的工具链编译时候显示clang-16: error: unknown argument: ‘-index-store-path’

这种编译报错自己稍微研究研究就能解决,你这个错误在xcode - build settings,关闭Enable-Index-While…即可

嗯嗯,这个解决了,现在是因为ld: warning: ignoring file ‘/Users/ryan/Library/Developer/Xcode/DerivedData/asd-bqgvdkdlposliqdubkcdgveszrsu/Build/Intermediates.noindex/asd.build/Debug-iphoneos/asd.build/Objects-normal/arm64e/main.o’: found architecture ‘arm64e.old’, required architecture ‘arm64e’ 不兼容导致没法编译,是不是要降级回去才行啊。

这个是因为开源llvm生成的arm64e二进制,和xcode的appleclang生成的arm64e格式不同
事实上开源llvm生成的arm64e二进制是没法使用的,即使强行修改格式也不能完美兼容,必须使用appleclang生成的才能完美兼容,比如本文中的方式4/方式5,我就提示到这,是否能搞定取决于自己能力了