LLVM pass:迭代模块函数列表时出错

Posted

技术标签:

【中文标题】LLVM pass:迭代模块函数列表时出错【英文标题】:LLVM pass: Error when iterating over Module functions list 【发布时间】:2016-03-30 12:29:08 【问题描述】:

我正在尝试在 LLVM 传递中使用 llvm::Module::getFunctionList() 返回的列表迭代模块函数列表。我使用这样的循环:

    for (auto curFref = M->getFunctionList().begin(), 
              endFref = M->getFunctionList().end(); 
              curFref != endFref; ++curFref) 
        errs() << "found function: " << curFref->getName() << "\n";
    

此循环的第一次迭代按预期检索一个函数,但它没有检测到列表的结尾,并继续在后续迭代中获取其他不是函数的对象(如其getName() 报告的那样),比如那个函数参数。经过几次迭代后,它可能会遇到一些垃圾(或 NULL)并在引用当前“函数”引用时崩溃。 例如,对于这个程序:

int foo(int k) 
    int i, s = 0;
    for (i = 0; i < k; ++i)
        s += i;
    return s;

这变成了这个 IR 代码:

...
; Function Attrs: nounwind uwtable
define i32 @foo(i32 %k) #0 
entry:
...

输出如下所示:

found function: foo
found function: k
found function: #0 0x00007f481f77c46e llvm::sys::PrintStackTrace(llvm::raw_ostream&) /home/me/work/llvm-3.8.0/lib/Support/Unix/Signals.inc:322:0
...

因此您可以看到,在正确迭代foo 之后,它继续到参数k 等对象。

我在模块传递(runOnModule())和函数传递(使用 F.getParent() 查询包含的模块)中都尝试了这个,并得到了相同的结果。

这个问题在 LLVM 3.8.0 和 LLVM 3.5.2 上也同样存在。

知道我没有正确迭代返回的函数列表吗?

=====

编辑:

请注意,在模块的函数上使用替代迭代时会显示相同的行为,例如在使用 M.begin()/end() 作为迭代器时,甚至在使用基于 C++11 范围的 for 循环时:for (Function &amp;curF: M) ... .

此外,M.getFunctionList().size() 在尝试迭代列表项时会导致分段错误。因此,功能列表似乎确实已损坏。但这是我在runOnModule() 入口点开始时得到的列表。所以我的代码似乎没有破坏它。

======

编辑2:

我不知道这是否重要,但我的 LLVM 通行证是从 LLVM 源树外部构建为动态可加载库,然后使用 -load=foo.so 命令行选项加载到 opt 中。

【问题讨论】:

@Chandler Carruth 的回答解决了这个问题。在他回答的核心中,当我使用 make/configure 构建 LLVM 时,他建议使用 ninja/cmake 构建 LLVM。使用 ninja/cmake 重建消除了这个问题。虽然 LLVM 3.8.0 发行说明表明仍然支持使用 make/configure 进行构建,但这个问题表明它以某种方式被破坏了。 【参考方案1】:

我是核心 LLVM 开发人员之一。

这几乎可以肯定是对 LLVM 的错误检出、错误编译的 LLVM,或者您的通行证是作为单独的 DSO 构建的问题(尽管我不明白这是怎么回事)。

LLVM 的许多部分使用M-&gt;functions()(或M-&gt;begin()M-&gt;end())仅迭代函数。如果行为被破坏,这些部分将始终失败。这些机制与像M-&gt;getFunctionList() 那样直接访问函数列表之间的唯一区别是列表是否是可变的。对于您展示的用例,它们应该完全具有相同的行为。

我建议尝试运行 LLVM 测试套件以确保它正常工作。如果你使用 Ninja CMake 生成器(我会推荐)来构建 LLVM:

% ninja check-llvm

如果这失败了,特别是如果它在类似区域崩溃,这似乎是一个非常好的迹象,表明问题出在你的 LLVM 副本中。

无论如何,SO 并不是在这里获得进一步帮助的好地方。问题的答案是“实际上应该可以工作”,我检查了 LLVM 中的代码,但没有看到任何明显的解释。所以我的建议是尝试上述方法,尝试 LLVM 的新副本(可能是树顶,或最新版本)。确保你有一个足够现代的主机工具链来构建 LLVM。有关详细信息,请参阅本节: http://llvm.org/docs/GettingStarted.html#host-c-toolchain-both-compiler-and-standard-library

如果一切都失败了,请尝试联系邮件列表中的开发人员: http://lists.llvm.org/mailman/listinfo/llvm-dev

或者试试 IRC 频道:http://llvm.org/docs/#irc

希望这会有所帮助!

【讨论】:

我毫不怀疑这个迭代器适用于 LLVM 项目中构建的模块。但在我的情况下,我将模块传递构建为共享对象,并使用“opt”的“-load”选项加载它。所以我怀疑这个用例可能有问题(即,当这个用例被破坏时,LLVM 中的所有东西都可以工作)。我还使用自己的 makefile 在外部构建 pass 共享对象,依赖于llvm-config --cppflags 提供的编译标志。因此,llvm-config --cppflags 输出可能与用于内部构建的标志不同。你知道这些用例是否经过测试? 看来您建议使用 ninja 进行构建具有“魔力”。我最初的 LLVM 构建使用了 configure/make。虽然官方在 3.8.0 中仍然支持它,但它似乎有问题。使用 ninja 重建 LLVM 并针对更新的(ninja)LLVM 使用相同的 makefile 构建我的 Module pass 解决了这个问题。我想 LLVM 的基于 make 的构建并不受支持,尽管发行说明表明它将仅从 3.9 停止支持。谢谢。【参考方案2】:

您是否尝试过使用M-&gt;begin()M-&gt;end() 而不是M-&gt;getFunctionList().begin()M-&gt;getFunctionList().end()?这对我有用。

【讨论】:

M-&gt;begin()/end() 只是 M-&gt;getFunctionsList().begin()/end() 的有限版本。无论如何,我也尝试了简单的迭代器,得到了同样不幸的结果。【参考方案3】:

您的输入是否可能以某种方式损坏?尝试先在上面运行llvm::verifyModule(来自Verifier.h),看看你得到了什么——或者在上面运行opt -verify,应该是一样的。此外,您可能对 opt 的 -verify-each 命令行选项感兴趣。

【讨论】:

在我测试的任何模块上都会重现该问题。我所有的输入模块都是使用以下命令从 C 代码生成的:clang -c -emit-llvm -o foo-preopt.bc foo.c,然后是 opt -mem2reg -licm -S -o foo.ll foo-preopt.bc。因此,如果任何模块损坏,它是clang 或标准opt 传递的结果。无论如何,-verify 没有报告任何问题。我使用 LLVM 的稳定版本。 @ElazarR 另一个想法:确保您使用的是与 opt 一起构建的 clang,而不是操作系统提供的 clang。 我使用的 LLVM 工具是一致的。实际上,我避免在我的操作系统中安装 clang/LLVM 包。我只使用从相应版本源代码构建的 clang/LLVM,并提供每个工具的显式路径。【参考方案4】:

如果您使用以下方式注册您的通行证:

 char SkeletonPass::ID = 0;

// Automatically enable the pass.
// This pass is taken from Adrian Samson Template project http://adriansampson.net/blog/clangpass.html
static void registerSkeletonPass(const PassManagerBuilder &,
                         legacy::PassManagerBase &PM) 
  PM.add(new SkeletonPass());

static RegisterStandardPasses
  RegisterMyPass(PassManagerBuilder::EP_EarlyAsPossible,
                 registerSkeletonPass);

这仅适用于 FunctionPass/BasicBlockPass,一种解决方案是像往常一样使用规范传递,而无需任何技巧,如下所示:-

char SkeletonPass::ID = 0;
static RegisterPass<SkeletonPass> X("passname","Pass Name Analysis");
static void registerPass(const PassManagerBuilder &,
                         legacy::PassManagerBase &PM) 
    PM.add(new SkeletonPass());

static RegisterStandardPasses
        RegisterMyPass(PassManagerBuilder::EP_EarlyAsPossible,
                       registerPass);

然后使用以下 cmd 运行您的通行证:

$ clang -c -O1 -emit-llvm  programs/hello.cpp   -o programs/hello.bc
$ opt   -load build/Debug/libCallgraph.so -passname programs/hello.bc

【讨论】:

很遗憾,这并不能解决问题。迭代 M.getFunctionList() 仍然返回以模块中的第一个函数开头并以非函数符号(对象)继续的项目,例如该函数的参数(正如我在问题中报告的那样)。实际上,isa() 将这些对象(函数参数)报告为 Function 对象。因此,给定的迭代器似乎完全错误。

以上是关于LLVM pass:迭代模块函数列表时出错的主要内容,如果未能解决你的问题,请参考以下文章

LLVM 之 IR 篇:如何编写内联 Pass

Clang前端使用LLVM Pass示例

LLVM使用其他Pass的结果

如何迭代函数列表

LLVM out of source pass build:不支持可加载模块(在 Linux 上)

llvm后端disassembler流程