编写 LLVM Pass(旧版 PM 版本)¶
简介 — 什么是 Pass?¶
警告
本文档介绍的是旧版 Pass 管理器。LLVM 为优化流水线(codegen 流水线仍使用旧版 Pass 管理器)使用了新的 Pass 管理器,它有自己定义 Pass 的方式。有关更多详细信息,请参阅编写 LLVM Pass和使用新的 Pass 管理器。
LLVM Pass 框架是 LLVM 系统的重要组成部分,因为 LLVM Pass 是编译器中最有趣的部分。Pass 执行构成编译器的转换和优化,它们构建供这些转换使用的分析结果,并且最重要的是,它们是编译器代码的结构化技术。
所有 LLVM Pass 都是 Pass 类的子类,它们通过重写从 Pass
继承的虚方法来实现功能。根据您的 Pass 的工作方式,您应该从 ModulePass、CallGraphSCCPass、FunctionPass 或 LoopPass 或 RegionPass 类继承,这为系统提供了有关您的 Pass 执行的操作以及如何与其他 Pass 组合的更多信息。LLVM Pass 框架的主要功能之一是,它根据您的 Pass 满足的约束(由它们派生自的类指示)以有效的方式安排 Pass 的运行。
Pass 类和要求¶
设计新的 Pass 时,您应该做的第一件事是决定您的 Pass 应该子类化哪个类。在这里,我们讨论可用的类,从最通用到最具体。
在为您的 Pass
选择超类时,您应该选择最具体的类,同时仍然能够满足列出的要求。这为 LLVM Pass 基础设施提供了优化 Pass 运行方式所需的信息,从而使生成的编译器不会不必要地缓慢。
ImmutablePass
类¶
最简单且最普通的 Pass 类型是 “ImmutablePass” 类。此 Pass 类型用于无需运行、不更改状态且永远不需要更新的 Pass。这不是一种常见的转换或分析类型,但可以提供有关当前编译器配置的信息。
尽管此 Pass 类很少使用,但它对于提供有关当前正在编译的目标机器的信息以及可能影响各种转换的其他静态信息非常重要。
ImmutablePass
从不使其他转换失效,永远不会失效,也永远不会 “运行”。
ModulePass
类¶
ModulePass 类是您可以使用的所有超类中最通用的。从 ModulePass
派生表明您的 Pass 将整个程序作为一个单元使用,以不可预测的顺序引用函数体,或添加和删除函数。由于对 ModulePass
子类的行为一无所知,因此无法对其执行进行优化。
模块 Pass 可以使用函数级 Pass(例如支配树分析)使用 getAnalysis
接口 getAnalysis<DominatorTree>(llvm::Function *)
来提供要检索分析结果的函数,如果函数 Pass 不需要任何模块或不可变 Pass。请注意,这只能对分析运行的函数执行,例如,在支配树分析的情况下,您应该仅请求函数定义的 DominatorTree
,而不是声明。
要编写正确的 ModulePass
子类,请从 ModulePass
派生并使用以下签名重写 runOnModule
方法
runOnModule
方法¶
virtual bool runOnModule(Module &M) = 0;
runOnModule
方法执行 Pass 的有趣工作。如果模块被转换修改,则应返回 true
,否则返回 false
。
CallGraphSCCPass
类¶
CallGraphSCCPass 由需要在调用图上自下而上(被调用者先于调用者)遍历程序的 Pass 使用。从 CallGraphSCCPass
派生提供了一些用于构建和遍历 CallGraph
的机制,但也允许系统优化 CallGraphSCCPass
的执行。如果您的 Pass 满足下面概述的要求,并且不满足 FunctionPass 的要求,则应从 CallGraphSCCPass
派生。
TODO
:简要解释 SCC、Tarjan 算法和 B-U 的含义。
明确地说,CallGraphSCCPass 子类
... 不允许检查或修改当前 SCC 中的
Function
以及 SCC 的直接调用者和直接被调用者之外的任何Function
。... 必须保留当前的
CallGraph
对象,并更新它以反映对程序所做的任何更改。... 不允许从当前模块添加或删除 SCC,但它们可以更改 SCC 的内容。
... 允许从当前模块添加或删除全局变量。
... 允许跨 runOnSCC 的调用维护状态(包括全局数据)。
在某些情况下,实现 CallGraphSCCPass
有点棘手,因为它必须处理包含多个节点的 SCC。如果虚拟方法修改了程序,则下面描述的所有虚拟方法都应返回 true
,否则返回 false
。
doInitialization(CallGraph &)
方法¶
virtual bool doInitialization(CallGraph &CG);
doInitialization
方法允许执行 CallGraphSCCPass
不允许执行的大多数操作。他们可以添加和删除函数,获取指向函数的指针等。doInitialization
方法旨在执行不依赖于正在处理的 SCC 的简单初始化类型的内容。doInitialization
方法调用未安排为与任何其他 Pass 执行重叠(因此它应该非常快)。
runOnSCC
方法¶
virtual bool runOnSCC(CallGraphSCC &SCC) = 0;
runOnSCC
方法执行 Pass 的有趣工作,如果模块被转换修改,则应返回 true
,否则返回 false
。
doFinalization(CallGraph &)
方法¶
virtual bool doFinalization(CallGraph &CG);
doFinalization
方法是一个不常用的方法,当 Pass 框架完成对正在编译的程序中的每个 SCC 调用 runOnSCC 时,将调用该方法。
FunctionPass
类¶
与 ModulePass
子类相反,FunctionPass 子类确实具有可预测的局部行为,系统可以预期。所有 FunctionPass
都在程序中的每个函数上独立于程序中的所有其他函数执行。FunctionPass
不需要以特定顺序执行,并且 FunctionPass
不会修改外部函数。
明确地说,FunctionPass
子类不允许
检查或修改当前正在处理的函数之外的
Function
。从当前
Module
添加或删除Function
。从当前
Module
添加或删除全局变量。跨 runOnFunction 的调用维护状态(包括全局数据)。
实现 FunctionPass
通常很简单。FunctionPass
可以重写三个虚方法来完成其工作。如果这些方法修改了程序,则所有这些方法都应返回 true
,否则返回 false
。
doInitialization(Module &)
方法¶
virtual bool doInitialization(Module &M);
doInitialization
方法允许执行 FunctionPass
不允许执行的大多数操作。他们可以添加和删除函数,获取指向函数的指针等。doInitialization
方法旨在执行不依赖于正在处理的函数的简单初始化类型的内容。doInitialization
方法调用未安排为与任何其他 Pass 执行重叠(因此它应该非常快)。
LowerAllocations Pass 是如何使用此方法的一个很好的例子。此 Pass 将 malloc
和 free
指令转换为平台相关的 malloc()
和 free()
函数调用。它使用 doInitialization
方法来获取它需要的 malloc
和 free
函数的引用,并在必要时向模块添加原型。
runOnFunction
方法¶
virtual bool runOnFunction(Function &F) = 0;
runOnFunction
方法必须由您的子类实现,以执行您的 Pass 的转换或分析工作。与往常一样,如果函数被修改,则应返回 true
值。
doFinalization(Module &)
方法¶
virtual bool doFinalization(Module &M);
doFinalization
方法是一个不常用的方法,当 Pass 框架完成对正在编译的程序中的每个函数调用 runOnFunction 时,将调用该方法。
LoopPass
类¶
所有 LoopPass
都在函数中的每个 循环 上独立于函数中的所有其他循环执行。LoopPass
以循环嵌套顺序处理循环,最外层循环最后处理。
LoopPass
子类允许使用 LPPassManager
接口更新循环嵌套。实现循环 Pass 通常很简单。LoopPass
可以重写三个虚方法来完成其工作。如果这些方法修改了程序,则所有这些方法都应返回 true
,否则返回 false
。
旨在作为主循环 Pass 流水线一部分运行的 LoopPass
子类需要保留其流水线中其他循环 Pass 所需的所有相同的函数分析。为了简化操作,LoopUtils.h
提供了 getLoopAnalysisUsage
函数。可以在子类的 getAnalysisUsage
重写中调用它,以获得一致且正确的行为。类似地,INITIALIZE_PASS_DEPENDENCY(LoopPass)
将初始化这组函数分析。
doInitialization(Loop *, LPPassManager &)
方法¶
virtual bool doInitialization(Loop *, LPPassManager &LPM);
doInitialization
方法旨在执行不依赖于正在处理的函数的简单初始化类型的内容。doInitialization
方法调用未安排为与任何其他 Pass 执行重叠(因此它应该非常快)。LPPassManager
接口应用于访问 Function
或 Module
级别的分析信息。
runOnLoop
方法¶
virtual bool runOnLoop(Loop *, LPPassManager &LPM) = 0;
runOnLoop
方法必须由您的子类实现,以执行您的 Pass 的转换或分析工作。与往常一样,如果函数被修改,则应返回 true
值。LPPassManager
接口应用于更新循环嵌套。
doFinalization()
方法¶
virtual bool doFinalization();
doFinalization
方法是一个不常用的方法,当 Pass 框架完成对正在编译的程序中的每个循环调用 runOnLoop 时,将调用该方法。
RegionPass
类¶
RegionPass
类似于 LoopPass,但在函数中的每个单入口单出口区域上执行。RegionPass
以嵌套顺序处理区域,最外层区域最后处理。
RegionPass
子类允许使用 RGPassManager
接口更新区域树。您可以重写 RegionPass
的三个虚方法来实现您自己的区域 Pass。如果这些方法修改了程序,则所有这些方法都应返回 true
,否则返回 false
。
doInitialization(Region *, RGPassManager &)
方法¶
virtual bool doInitialization(Region *, RGPassManager &RGM);
doInitialization
方法旨在执行不依赖于正在处理的函数的简单初始化类型的内容。doInitialization
方法调用未安排为与任何其他 Pass 执行重叠(因此它应该非常快)。RPPassManager
接口应用于访问 Function
或 Module
级别的分析信息。
runOnRegion
方法¶
virtual bool runOnRegion(Region *, RGPassManager &RGM) = 0;
runOnRegion
方法必须由您的子类实现,以执行您的 Pass 的转换或分析工作。与往常一样,如果区域被修改,则应返回 true 值。RGPassManager
接口应用于更新区域树。
doFinalization()
方法¶
virtual bool doFinalization();
doFinalization
方法是一个不常用的方法,当 Pass 框架完成对正在编译的程序中的每个区域调用 runOnRegion 时,将调用该方法。
MachineFunctionPass
类¶
MachineFunctionPass
是 LLVM 代码生成器的一部分,它在程序中每个 LLVM 函数的机器相关表示上执行。
代码生成器 Pass 由 TargetMachine::addPassesToEmitFile
和类似例程专门注册和初始化,因此它们通常不能从 opt 或 bugpoint 命令运行。
MachineFunctionPass
也是 FunctionPass
,因此应用于 FunctionPass
的所有限制也适用于它。MachineFunctionPass
也有额外的限制。特别是,MachineFunctionPass
不允许执行以下任何操作
修改或创建任何 LLVM IR
Instruction
、BasicBlock
、Argument
、Function
、GlobalVariable
、GlobalAlias
或Module
。修改当前正在处理的函数之外的
MachineFunction
。跨 runOnMachineFunction 的调用维护状态(包括全局数据)。
runOnMachineFunction(MachineFunction &MF)
方法¶
virtual bool runOnMachineFunction(MachineFunction &MF) = 0;
runOnMachineFunction
可以被认为是 MachineFunctionPass
的主要入口点;也就是说,您应该重写此方法来完成您的 MachineFunctionPass
的工作。
runOnMachineFunction
方法在 Module
中的每个 MachineFunction
上调用,以便 MachineFunctionPass
可以对函数的机器相关表示执行优化。如果您想获取您正在处理的 MachineFunction
的 LLVM Function
,请使用 MachineFunction
的 getFunction()
访问器方法 — 但请记住,您不得从 MachineFunctionPass
修改 LLVM Function
或其内容。
Pass 注册¶
Pass 使用 RegisterPass
模板注册。模板参数是要在命令行上使用的 Pass 的名称,以指定应将 Pass 添加到程序。第一个参数是 Pass 的名称,该名称将用于程序的 -help
输出,以及由 –debug-pass 选项生成的调试输出。
如果您希望您的 Pass 易于转储,您应该实现虚拟 print 方法
print
方法¶
virtual void print(llvm::raw_ostream &O, const Module *M) const;
print
方法必须由 “分析” 实现,以便打印分析结果的人类可读版本。这对于调试分析本身以及让其他人了解分析的工作原理非常有用。使用 opt -analyze
参数来调用此方法。
llvm::raw_ostream
参数指定将结果写入的流,Module
参数提供指向已分析程序的顶层模块的指针。但是请注意,此指针在某些情况下可能为 NULL
(例如从调试器调用 Pass::dump()
),因此它仅应用于增强调试输出,不应依赖于它。
指定 Pass 之间的交互¶
PassManager
的主要职责之一是确保 Pass 彼此正确交互。由于 PassManager
尝试优化 Pass 的执行,它必须知道 Pass 如何相互交互以及各种 Pass 之间存在哪些依赖关系。为了跟踪这一点,每个 Pass 都可以声明在当前 Pass 之前需要执行的 Pass 集合,以及当前 Pass 使哪些 Pass 失效。
通常,此功能用于要求在运行您的 Pass 之前计算分析结果。运行任意转换 Pass 可能会使计算出的分析结果无效,这就是失效集指定的内容。如果 Pass 未实现 getAnalysisUsage 方法,则默认情况下它没有任何先决条件 Pass,并且使所有其他 Pass 失效。
getAnalysisUsage
方法¶
virtual void getAnalysisUsage(AnalysisUsage &Info) const;
通过实现 getAnalysisUsage
方法,可以为您的转换指定必需集和失效集。实现应使用有关哪些 Pass 是必需的且未失效的信息填充 AnalysisUsage 对象。为此,Pass 可以在 AnalysisUsage
对象上调用以下任何方法
AnalysisUsage::addRequired<>
和 AnalysisUsage::addRequiredTransitive<>
方法¶
如果你的 Pass 需要先执行之前的 Pass(例如,一个分析),它可以使用这些方法之一来安排在其 Pass 之前运行。LLVM 有许多不同类型的分析和 Pass 可以被要求,范围从 DominatorSet
到 BreakCriticalEdges
。BreakCriticalEdges
,例如,要求保证当你的 Pass 运行后,CFG 中将不会有关键边。
一些分析会链式调用其他分析来完成它们的工作。例如,一个 AliasAnalysis 实现需要 链接 到其他别名分析 Pass。在分析链式调用的情况下,应该使用 addRequiredTransitive
方法而不是 addRequired
方法。这会通知 PassManager
,传递性要求的 Pass 应该与需要它的 Pass 一样长久地存活。
AnalysisUsage::addPreserved<>
方法¶
PassManager
的工作之一是优化分析的运行方式和时间。特别是,它尝试避免重新计算数据,除非必要。因此,允许 Pass 声明它们保留(即,它们不会使现有分析无效)现有的分析(如果可用)。例如,一个简单的常量折叠 Pass 不会修改 CFG,因此它不可能影响支配树分析的结果。默认情况下,所有 Pass 都被假定为使所有其他分析无效。
AnalysisUsage
类提供了几种在某些与 addPreserved
相关的情况下非常有用的方法。特别是,可以调用 setPreservesAll
方法来指示该 Pass 根本不修改 LLVM 程序(对于分析来说是正确的),并且 setPreservesCFG
方法可以被程序中更改指令但不修改 CFG 或终止符指令的转换使用。
addPreserved
对于像 BreakCriticalEdges
这样的转换特别有用。这个 Pass 知道如何在存在循环和支配关系分析的情况下更新一小部分,因此它可以保留它们,尽管它修改了 CFG。
getAnalysisUsage
的示例实现¶
// This example modifies the program, but does not modify the CFG
void LICM::getAnalysisUsage(AnalysisUsage &AU) const {
AU.setPreservesCFG();
AU.addRequired<LoopInfoWrapperPass>();
}
getAnalysis<>
和 getAnalysisIfAvailable<>
方法¶
Pass::getAnalysis<>
方法由你的类自动继承,为你提供访问你使用 getAnalysisUsage 方法声明为需要的 Pass 的权限。它接受一个模板参数,指定你想要的 Pass 类,并返回对该 Pass 的引用。例如
bool LICM::runOnFunction(Function &F) {
LoopInfo &LI = getAnalysis<LoopInfoWrapperPass>().getLoopInfo();
//...
}
此方法调用返回对所需 Pass 的引用。如果你尝试获取一个你未在你的 getAnalysisUsage 实现中声明为需要的分析,你可能会得到运行时断言失败。此方法可以由你的 run*
方法实现调用,或由你的 run*
方法调用的任何其他本地方法调用。
模块级 Pass 可以使用此接口使用函数级分析信息。例如
bool ModuleLevelPass::runOnModule(Module &M) {
//...
DominatorTree &DT = getAnalysis<DominatorTree>(Func);
//...
}
在上面的例子中,DominatorTree
的 runOnFunction
在返回对所需 Pass 的引用之前由 Pass 管理器调用。
如果你的 Pass 能够更新分析(如果它们存在)(例如,如上所述的 BreakCriticalEdges
),你可以使用 getAnalysisIfAvailable
方法,如果分析处于活动状态,则该方法返回指向分析的指针。例如
if (DominatorSet *DS = getAnalysisIfAvailable<DominatorSet>()) {
// A DominatorSet is active. This code will update it.
}
Pass 统计信息¶
Statistic 类旨在成为一种简单的方式,从 Pass 中公开各种成功指标。当在命令行上启用 -stats
命令行选项时,这些统计信息会在运行结束时打印。有关详细信息,请参阅程序员手册中的 统计信息部分。
PassManager 的作用¶
PassManager 类 接受一个 Pass 列表,确保它们的 先决条件 被正确设置,然后调度 Pass 以高效运行。所有运行 Pass 的 LLVM 工具都使用 PassManager 来执行这些 Pass。
PassManager 执行两件主要的事情来尝试减少一系列 Pass 的执行时间
共享分析结果。
PassManager
尝试尽可能避免重新计算分析结果。这意味着跟踪哪些分析已经可用,哪些分析变得无效,以及哪些分析需要为 Pass 运行。工作的一个重要部分是PassManager
跟踪所有分析结果的精确生命周期,允许它在不再需要分析结果后立即 释放内存 分配给保存分析结果的内存。流水线化程序上 Pass 的执行。
PassManager
尝试通过将 Pass 流水线化来获得更好的一系列 Pass 的缓存和内存使用行为。这意味着,给定一系列连续的 FunctionPass,它将在第一个函数上执行所有的 FunctionPass,然后在第二个函数上执行所有的 FunctionPasses,等等...直到整个程序都运行完这些 Pass。这改善了编译器的缓存行为,因为它一次只接触单个函数的 LLVM 程序表示,而不是遍历整个程序。它减少了编译器的内存消耗,因为,例如,一次只需要计算一个 DominatorSet。
PassManager
的有效性直接受到它拥有的关于它正在调度的 Pass 行为的信息量的影响。例如,在未实现的 getAnalysisUsage 方法面前,“保留”集是有意保守的。不实现应该实现的方法将导致不允许任何分析结果在你的 Pass 执行期间存活。
PassManager
类公开了一个 --debug-pass
命令行选项,该选项对于调试 Pass 执行、查看事物如何工作以及诊断何时应该保留比当前更多的分析非常有用。(要获取有关 --debug-pass
选项的所有变体的信息,只需键入 “llc -help-hidden
”)。
通过使用 –debug-pass=Structure 选项,例如,我们可以查看默认的优化流水线,例如(输出已被修剪)
$ llc -mtriple=arm64-- -O3 -debug-pass=Structure file.ll > /dev/null
(...)
ModulePass Manager
Pre-ISel Intrinsic Lowering
FunctionPass Manager
Expand large div/rem
Expand large fp convert
Expand Atomic instructions
SVE intrinsics optimizations
FunctionPass Manager
Dominator Tree Construction
FunctionPass Manager
Simplify the CFG
Dominator Tree Construction
Natural Loop Information
Canonicalize natural loops
(...)
releaseMemory
方法¶
virtual void releaseMemory();
PassManager
自动确定何时计算分析结果,以及保持它们存在多长时间。由于 Pass 对象本身的生命周期实际上是整个编译过程的持续时间,我们需要某种方法在分析结果不再有用时释放它们。releaseMemory
虚方法就是执行此操作的方式。
如果你正在编写一个分析或任何其他保留大量状态的 Pass(供另一个 “需要” 你的 Pass 并使用 getAnalysis 方法的 Pass 使用),你应该实现 releaseMemory
以,嗯,释放分配给维护此内部状态的内存。此方法在类的 run*
方法之后,在 Pass 中下一次调用 run*
之前调用。
注册动态加载的 Pass¶
当使用 LLVM 构建生产质量工具时,大小很重要,无论是为了分发目的,还是为了调节在目标系统上运行时常驻代码大小。因此,选择性地使用一些 Pass,同时省略其他 Pass,并保持稍后更改配置的灵活性变得非常理想。你希望能够做到这一切,并向用户提供反馈。这就是 Pass 注册发挥作用的地方。
Pass 注册的基本机制是 MachinePassRegistry
类和 MachinePassRegistryNode
的子类。
MachinePassRegistry
的实例用于维护 MachinePassRegistryNode
对象的列表。此实例维护列表并与命令行界面通信添加和删除操作。
MachinePassRegistryNode
子类的实例用于维护有关特定 Pass 提供的信息。此信息包括命令行名称、命令行帮助字符串和用于创建 Pass 实例的函数的地址。这些实例之一的全局静态构造函数向相应的 MachinePassRegistry
注册,静态析构函数取消注册。因此,静态链接到工具中的 Pass 将在启动时注册。动态加载的 Pass 将在加载时注册,并在卸载时取消注册。
使用现有注册表¶
有预定义的注册表来跟踪指令调度 (RegisterScheduler
) 和寄存器分配 (RegisterRegAlloc
) 机器 Pass。在这里,我们将描述如何注册一个寄存器分配器机器 Pass。
实现你的寄存器分配器机器 Pass。在你的寄存器分配器 .cpp
文件中添加以下包含
#include "llvm/CodeGen/RegAllocRegistry.h"
同样在你的寄存器分配器 .cpp
文件中,以以下形式定义一个创建器函数
FunctionPass *createMyRegisterAllocator() {
return new MyRegisterAllocator();
}
请注意,此函数的签名应与 RegisterRegAlloc::FunctionPassCtor
的类型匹配。在同一文件中添加 “安装” 声明,形式如下
static RegisterRegAlloc myRegAlloc("myregalloc",
"my register allocator help string",
createMyRegisterAllocator);
请注意,帮助字符串之前的两个空格在 -help
查询中产生整洁的结果。
$ llc -help
...
-regalloc - Register allocator to use (default=linearscan)
=linearscan - linear scan register allocator
=local - local register allocator
=simple - simple register allocator
=myregalloc - my register allocator help string
...
就是这样。用户现在可以自由地使用 -regalloc=myregalloc
作为选项。注册指令调度器是类似的,除了使用 RegisterScheduler
类。请注意,RegisterScheduler::FunctionPassCtor
与 RegisterRegAlloc::FunctionPassCtor
显着不同。
要强制将你的寄存器分配器加载/链接到 llc/lli 工具中,请将你的创建器函数的全局声明添加到 Passes.h
,并向 llvm/Codegen/LinkAllCodegenComponents.h
添加一个 “伪” 调用行。
创建新的注册表¶
最简单的入门方法是克隆现有注册表之一;我们推荐 llvm/CodeGen/RegAllocRegistry.h
。要修改的关键内容是类名称和 FunctionPassCtor
类型。
然后你需要声明注册表。示例:如果你的 Pass 注册表是 RegisterMyPasses
,则定义
MachinePassRegistry<RegisterMyPasses::FunctionPassCtor> RegisterMyPasses::Registry;
最后,声明你的 Pass 的命令行选项。示例
cl::opt<RegisterMyPasses::FunctionPassCtor, false,
RegisterPassParser<RegisterMyPasses> >
MyPassOpt("mypass",
cl::init(&createDefaultMyPass),
cl::desc("my pass option help"));
这里的命令行选项是 “mypass
”,以 createDefaultMyPass
作为默认创建器。
将 GDB 与动态加载的 Pass 一起使用¶
不幸的是,将 GDB 与动态加载的 Pass 一起使用并不像它应该的那样容易。首先,你不能在尚未加载的共享对象中设置断点,其次,共享对象中的内联函数存在问题。以下是一些使用 GDB 调试你的 Pass 的建议。
为了讨论方便,我假设你正在调试由 opt 调用的转换,尽管这里描述的任何内容都不依赖于此。
在你的 Pass 中设置断点¶
你要做的第一件事是在 opt 进程上启动 gdb
$ gdb opt
GNU gdb 5.0
Copyright 2000 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "sparc-sun-solaris2.6"...
(gdb)
请注意,opt 中有很多调试信息,因此加载需要时间。请耐心等待。由于我们还不能在我们的 Pass 中设置断点(共享对象在运行时才加载),我们必须执行该进程,并使其在调用我们的 Pass 之前停止,但在加载共享对象之后停止。最万无一失的方法是在 PassManager::run
中设置断点,然后使用你想要的参数运行该进程
$ (gdb) break llvm::PassManager::run
Breakpoint 1 at 0x2413bc: file Pass.cpp, line 70.
(gdb) run test.bc -load $(LLVMTOP)/llvm/Debug+Asserts/lib/[libname].so -[passoption]
Starting program: opt test.bc -load $(LLVMTOP)/llvm/Debug+Asserts/lib/[libname].so -[passoption]
Breakpoint 1, PassManager::run (this=0xffbef174, M=@0x70b298) at Pass.cpp:70
70 bool PassManager::run(Module &M) { return PM->run(M); }
(gdb)
一旦 opt 在 PassManager::run
方法中停止,你现在可以自由地在你的 Pass 中设置断点,以便你可以跟踪执行或执行其他标准调试操作。
杂项问题¶
一旦你掌握了基础知识,GDB 就会遇到一些问题,其中一些问题有解决方案,而另一些则没有。
内联函数具有虚假的堆栈信息。总的来说,GDB 在获取堆栈跟踪和单步执行内联函数方面做得相当好。但是,当动态加载 Pass 时,它会以某种方式完全失去此功能。我唯一知道的解决方案是将函数取消内联(将其从类的主体移动到
.cpp
文件)。重启程序会破坏断点。在遵循上述信息后,你已成功在你的 Pass 中植入了一些断点。接下来你知道的是,你重启程序(即,你再次键入 “
run
”),并且你开始收到有关断点无法设置的错误。我发现 “修复” 此问题的唯一方法是删除已在你的 Pass 中设置的断点,运行程序,并在执行在PassManager::run
中停止后重新设置断点。
希望这些提示对常见的调试情况有所帮助。如果你想贡献一些你自己的提示,请联系 Chris。