LLVM 23.0.0git
MemoryTaggingSupport.cpp
Go to the documentation of this file.
1//== MemoryTaggingSupport.cpp - helpers for memory tagging implementations ===//
2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
3// See https://llvm.org/LICENSE.txt for license information.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7//
8// This file declares common infrastructure for HWAddressSanitizer and
9// Aarch64StackTagging.
10//
11//===----------------------------------------------------------------------===//
12
14
15#include "llvm/ADT/STLExtras.h"
16#include "llvm/Analysis/CFG.h"
21#include "llvm/IR/BasicBlock.h"
22#include "llvm/IR/IRBuilder.h"
24#include "llvm/IR/Intrinsics.h"
27#include <utility>
28
29namespace llvm {
30namespace memtag {
31
33 const LoopInfo &LI, const AllocaInfo &AInfo,
35 llvm::function_ref<void(Instruction *)> Callback) {
36 if (AInfo.LifetimeEnd.size() == 1 && AInfo.LifetimeStart.size() == 1 &&
37 PDT.dominates(AInfo.LifetimeEnd[0], AInfo.LifetimeStart[0])) {
38 Callback(AInfo.LifetimeEnd[0]);
39 return;
40 }
43 for (const auto &[BB, BBInfo] : AInfo.BBInfos) {
44 if (BBInfo.Last == Intrinsic::lifetime_end)
45 EndBlocks.insert(BB);
46 else
47 StartBlocks.push_back(BB);
48 }
49
50 if (!StartBlocks.empty()) {
51 for (auto *RI : RetVec) {
52 auto WL = StartBlocks;
53 // If the block with the return is an EndBlock (i.e. a block where the
54 // last relevant lifetime intrinsic is an end), we don't have to run a
55 // complicated algorithm to know that the RetInst is never reachable
56 // without going through an end.
57 if (!EndBlocks.contains(RI->getParent()) &&
58 isPotentiallyReachableFromMany(WL, RI->getParent(), &EndBlocks, &DT,
59 &LI)) {
60 Callback(RI);
61 }
62 }
63 }
64 for_each(AInfo.LifetimeEnd, Callback);
65}
66
67bool isSupportedLifetime(const AllocaInfo &AInfo, const DominatorTree *DT,
68 const LoopInfo *LI) {
69 if (AInfo.LifetimeStart.empty())
70 return false;
71 SmallVector<BasicBlock *, 2> LastEndBlocks;
74 for_each(AInfo.BBInfos, [&](const auto &It) {
75 const auto &[BB, BBI] = It;
76 if (BBI.Last == Intrinsic::lifetime_end)
77 LastEndBlocks.append(succ_begin(BB), succ_end(BB));
78 else
79 StartBlocks.insert(BB);
80 if (BBI.First == Intrinsic::lifetime_end)
81 FirstEndBlocks.insert(BB);
82 });
83 if (LastEndBlocks.empty() || FirstEndBlocks.empty())
84 return true;
85 return !isManyPotentiallyReachableFromMany(LastEndBlocks, FirstEndBlocks,
86 &StartBlocks, DT, LI);
87}
88
90 if (isa<ReturnInst>(Inst)) {
91 if (CallInst *CI = Inst.getParent()->getTerminatingMustTailCall())
92 return CI;
93 return &Inst;
94 }
96 return &Inst;
97 }
98 return nullptr;
99}
100
102 Instruction &Inst) {
103 // Visit non-intrinsic debug-info records attached to Inst.
105 auto AddIfInteresting = [&](Value *V) {
106 if (auto *AI = dyn_cast_or_null<AllocaInst>(V)) {
107 if (getAllocaInterestingness(*AI) !=
109 return;
110 AllocaInfo &AInfo = Info.AllocasToInstrument[AI];
111 auto &DVRVec = AInfo.DbgVariableRecords;
112 if (DVRVec.empty() || DVRVec.back() != &DVR)
113 DVRVec.push_back(&DVR);
114 }
115 };
116
117 for_each(DVR.location_ops(), AddIfInteresting);
118 if (DVR.isDbgAssign())
119 AddIfInteresting(DVR.getAddress());
120 }
121
122 if (CallInst *CI = dyn_cast<CallInst>(&Inst)) {
123 if (CI->canReturnTwice()) {
124 Info.CallsReturnTwice = true;
125 }
126 }
127 if (AllocaInst *AI = dyn_cast<AllocaInst>(&Inst)) {
128 switch (getAllocaInterestingness(*AI)) {
130 Info.AllocasToInstrument[AI].AI = AI;
131 ORE.emit([&]() {
132 return OptimizationRemarkMissed(DebugType, "safeAlloca", &Inst);
133 });
134 break;
136 ORE.emit(
137 [&]() { return OptimizationRemark(DebugType, "safeAlloca", &Inst); });
138 break;
140 break;
141 }
142 return;
143 }
144 if (auto *II = dyn_cast<LifetimeIntrinsic>(&Inst)) {
145 AllocaInst *AI = dyn_cast<AllocaInst>(II->getArgOperand(0));
146 if (!AI ||
148 return;
149 auto &AInfo = Info.AllocasToInstrument[AI];
150 auto &BBInfo = AInfo.BBInfos[II->getParent()];
151
152 if (II->getIntrinsicID() == Intrinsic::lifetime_start)
153 AInfo.LifetimeStart.push_back(II);
154 else if (BBInfo.Last != Intrinsic::lifetime_end)
155 AInfo.LifetimeEnd.push_back(II);
156
157 BBInfo.Last = II->getIntrinsicID();
158 if (BBInfo.First == Intrinsic::not_intrinsic)
159 BBInfo.First = II->getIntrinsicID();
160
161 return;
162 }
163
165 if (ExitUntag)
166 Info.RetVec.push_back(ExitUntag);
167}
168
171 std::optional<TypeSize> Size = AI.getAllocationSize(AI.getDataLayout());
172 if (Size &&
173 // FIXME: support vscale.
174 !Size->isScalable() &&
175 // FIXME: instrument dynamic allocas, too
176 AI.isStaticAlloca() &&
177 // alloca() may be called with 0 size, ignore it.
178 !Size->isZero() &&
179 // We are only interested in allocas not promotable to registers.
180 // Promotable allocas are common under -O0.
181 !isAllocaPromotable(&AI) &&
182 // inalloca allocas are not treated as static, and we don't want
183 // dynamic alloca instrumentation for them as well.
184 !AI.isUsedWithInAlloca() &&
185 // swifterror allocas are register promoted by ISel
186 !AI.isSwiftError()) {
187 if (!(SSI && SSI->isSafe(AI))) {
189 }
190 // safe allocas are not interesting
192 }
194}
195
197 auto DL = AI.getDataLayout();
198 return *AI.getAllocationSize(DL);
199}
200
202 const Align NewAlignment = std::max(Info.AI->getAlign(), Alignment);
203 Info.AI->setAlignment(NewAlignment);
204 auto &Ctx = Info.AI->getFunction()->getContext();
205
207 uint64_t AlignedSize = alignTo(Size, Alignment);
208 if (Size == AlignedSize)
209 return;
210
211 // Add padding to the alloca.
212 Type *AllocatedType =
213 Info.AI->isArrayAllocation()
215 Info.AI->getAllocatedType(),
216 cast<ConstantInt>(Info.AI->getArraySize())->getZExtValue())
217 : Info.AI->getAllocatedType();
218 Type *PaddingType = ArrayType::get(Type::getInt8Ty(Ctx), AlignedSize - Size);
219 Type *TypeWithPadding = StructType::get(AllocatedType, PaddingType);
220 auto *NewAI = new AllocaInst(TypeWithPadding, Info.AI->getAddressSpace(),
221 nullptr, "", Info.AI->getIterator());
222 NewAI->takeName(Info.AI);
223 NewAI->setAlignment(Info.AI->getAlign());
224 NewAI->setUsedWithInAlloca(Info.AI->isUsedWithInAlloca());
225 NewAI->setSwiftError(Info.AI->isSwiftError());
226 NewAI->copyMetadata(*Info.AI);
227
228 Info.AI->replaceAllUsesWith(NewAI);
229 Info.AI->eraseFromParent();
230 Info.AI = NewAI;
231}
232
234 Module *M = IRB.GetInsertBlock()->getParent()->getParent();
235 MDNode *MD =
236 MDNode::get(M->getContext(), {MDString::get(M->getContext(), Name)});
237 Value *Args[] = {MetadataAsValue::get(M->getContext(), MD)};
238 return IRB.CreateIntrinsic(Intrinsic::read_register,
239 IRB.getIntPtrTy(M->getDataLayout()), Args);
240}
241
242Value *getPC(const Triple &TargetTriple, IRBuilder<> &IRB) {
243 Module *M = IRB.GetInsertBlock()->getParent()->getParent();
244 if (TargetTriple.getArch() == Triple::aarch64)
245 return memtag::readRegister(IRB, "pc");
246 return IRB.CreatePtrToInt(IRB.GetInsertBlock()->getParent(),
247 IRB.getIntPtrTy(M->getDataLayout()));
248}
249
252 Module *M = F->getParent();
253 return IRB.CreatePtrToInt(
254 IRB.CreateIntrinsic(Intrinsic::frameaddress,
255 IRB.getPtrTy(M->getDataLayout().getAllocaAddrSpace()),
256 {Constant::getNullValue(IRB.getInt32Ty())}),
257 IRB.getIntPtrTy(M->getDataLayout()));
258}
259
261 Module *M = IRB.GetInsertBlock()->getParent()->getParent();
262 // FIXME: This should use the thread_pointer intrinsic. However, the
263 // intrinsic is not currently implemented on Darwin correctly. The
264 // TPIDRRO_EL0 register became part of the ABI to access TSD on recent
265 // versions of OS and so it is safe to use here
266 // Darwin provides a fixed slot for sanitizers at offset 231.
267 MDNode *MD = MDNode::get(M->getContext(),
268 {MDString::get(M->getContext(), "TPIDRRO_EL0")});
269 Value *Args[] = {MetadataAsValue::get(M->getContext(), MD)};
270 return IRB.CreateConstGEP1_32(
271 IRB.getInt8Ty(),
272 IRB.CreateIntToPtr(
273 IRB.CreateIntrinsic(Intrinsic::read_register,
274 {IRB.getIntPtrTy(M->getDataLayout())}, Args),
275 IRB.getPtrTy()),
276 8 * Slot);
277}
278
280 Module *M = IRB.GetInsertBlock()->getParent()->getParent();
281 // Android provides a fixed TLS slot for sanitizers. See TLS_SLOT_SANITIZER
282 // in Bionic's libc/private/bionic_tls.h.
283 Function *ThreadPointerFunc = Intrinsic::getOrInsertDeclaration(
284 M, Intrinsic::thread_pointer,
285 IRB.getPtrTy(M->getDataLayout().getDefaultGlobalsAddressSpace()));
286 return IRB.CreateConstGEP1_32(IRB.getInt8Ty(),
287 IRB.CreateCall(ThreadPointerFunc), 8 * Slot);
288}
289
291 return DVR->isDbgAssign() ? DVR : nullptr;
292}
293
294void annotateDebugRecords(AllocaInfo &Info, unsigned int Tag) {
295 auto AnnotateDbgRecord = [&](DbgVariableRecord *DPtr) {
296 // Prepend "tag_offset, N" to the dwarf expression.
297 // Tag offset logically applies to the alloca pointer, and it makes sense
298 // to put it at the beginning of the expression.
300 for (size_t LocNo = 0; LocNo < DPtr->getNumVariableLocationOps(); ++LocNo)
301 if (DPtr->getVariableLocationOp(LocNo) == Info.AI)
302 DPtr->setExpression(
303 DIExpression::appendOpsToArg(DPtr->getExpression(), NewOps, LocNo));
304 if (auto *DAI = DynCastToDbgAssign(DPtr)) {
305 if (DAI->getAddress() == Info.AI)
306 DAI->setAddressExpression(
307 DIExpression::prependOpcodes(DAI->getAddressExpression(), NewOps));
308 }
309 };
310
311 llvm::for_each(Info.DbgVariableRecords, AnnotateDbgRecord);
312}
313
315 unsigned int Inc, bool IsMemtagDarwin) {
316 // Update the ring buffer. Top byte of ThreadLong defines the size of the
317 // buffer in pages, it must be a power of two, and the start of the buffer
318 // must be aligned by twice that much. Therefore wrap around of the ring
319 // buffer is simply Addr &= ~((ThreadLong >> 56) << 12).
320 // The use of AShr instead of LShr is due to
321 // https://bugs.llvm.org/show_bug.cgi?id=39030
322 // Runtime library makes sure not to use the highest bit.
323 //
324 // Mechanical proof of this address calculation can be found at:
325 // https://github.com/google/sanitizers/blob/master/hwaddress-sanitizer/prove_hwasanwrap.smt2
326 //
327 // Example of the wrap case for N = 1
328 // Pointer: 0x01AAAAAAAAAAAFF8
329 // +
330 // 0x0000000000000008
331 // =
332 // 0x01AAAAAAAAAAB000
333 // &
334 // WrapMask: 0xFFFFFFFFFFFFF000
335 // =
336 // 0x01AAAAAAAAAAA000
337 //
338 // Then the WrapMask will be a no-op until the next wrap case.
339 //
340 // Darwin relies on bit 60-62 to store the size of the buffer in pages. This
341 // limits N to [0,2] while the rest of the proof remains unchanged. Bits
342 // 56-59 are avoided in order to prevent MTE Canonical Tag Faults while
343 // accessing the ring buffer. Bit 63 is avoided to prevent unintentional
344 // signed extension by AShr.
345 assert((4096 % Inc) == 0);
346 Value *WrapMask = IRB.CreateXor(
347 IRB.CreateShl(IRB.CreateAShr(ThreadLong, IsMemtagDarwin ? 60 : 56), 12,
348 "", true, true),
349 ConstantInt::get(ThreadLong->getType(), (uint64_t)-1));
350 return IRB.CreateAnd(
351 IRB.CreateAdd(ThreadLong, ConstantInt::get(ThreadLong->getType(), Inc)),
352 WrapMask);
353}
354
355} // namespace memtag
356} // namespace llvm
assert(UImm &&(UImm !=~static_cast< T >(0)) &&"Invalid immediate!")
MachineBasicBlock MachineBasicBlock::iterator DebugLoc DL
This file contains constants used for implementing Dwarf debug support.
#define F(x, y, z)
Definition MD5.cpp:54
uint64_t IntrinsicInst * II
This file contains some templates that are useful if you are working with the STL at all.
an instruction to allocate memory on the stack
bool isSwiftError() const
Return true if this alloca is used as a swifterror argument to a call.
LLVM_ABI bool isStaticAlloca() const
Return true if this alloca is in the entry block of the function and is a constant size.
bool isUsedWithInAlloca() const
Return true if this alloca is used as an inalloca argument to a call.
LLVM_ABI std::optional< TypeSize > getAllocationSize(const DataLayout &DL) const
Get allocation size in bytes.
static LLVM_ABI ArrayType * get(Type *ElementType, uint64_t NumElements)
This static method is the primary way to construct an ArrayType.
const Function * getParent() const
Return the enclosing method, or null if none.
Definition BasicBlock.h:213
This class represents a function call, abstracting a target machine's calling convention.
static LLVM_ABI DIExpression * appendOpsToArg(const DIExpression *Expr, ArrayRef< uint64_t > Ops, unsigned ArgNo, bool StackValue=false)
Create a copy of Expr by appending the given list of Ops to each instance of the operand DW_OP_LLVM_a...
static LLVM_ABI DIExpression * prependOpcodes(const DIExpression *Expr, SmallVectorImpl< uint64_t > &Ops, bool StackValue=false, bool EntryValue=false)
Prepend DIExpr with the given opcodes and optionally turn it into a stack value.
Record of a variable value-assignment, aka a non instruction representation of the dbg....
Concrete subclass of DominatorTreeBase that is used to compute a normal dominator tree.
Definition Dominators.h:164
Module * getParent()
Get the module that this global value is contained inside of...
Value * CreateConstGEP1_32(Type *Ty, Value *Ptr, unsigned Idx0, const Twine &Name="")
Definition IRBuilder.h:1957
Value * CreateIntToPtr(Value *V, Type *DestTy, const Twine &Name="")
Definition IRBuilder.h:2171
IntegerType * getIntPtrTy(const DataLayout &DL, unsigned AddrSpace=0)
Fetch the type of an integer with size at least as big as that of a pointer in the given address spac...
Definition IRBuilder.h:610
BasicBlock * GetInsertBlock() const
Definition IRBuilder.h:201
LLVM_ABI CallInst * CreateIntrinsic(Intrinsic::ID ID, ArrayRef< Type * > Types, ArrayRef< Value * > Args, FMFSource FMFSource={}, const Twine &Name="")
Create a call to intrinsic ID with Args, mangled using Types.
Value * CreateShl(Value *LHS, Value *RHS, const Twine &Name="", bool HasNUW=false, bool HasNSW=false)
Definition IRBuilder.h:1495
Value * CreateAnd(Value *LHS, Value *RHS, const Twine &Name="")
Definition IRBuilder.h:1554
Value * CreateAdd(Value *LHS, Value *RHS, const Twine &Name="", bool HasNUW=false, bool HasNSW=false)
Definition IRBuilder.h:1406
Value * CreatePtrToInt(Value *V, Type *DestTy, const Twine &Name="")
Definition IRBuilder.h:2166
CallInst * CreateCall(FunctionType *FTy, Value *Callee, ArrayRef< Value * > Args={}, const Twine &Name="", MDNode *FPMathTag=nullptr)
Definition IRBuilder.h:2487
PointerType * getPtrTy(unsigned AddrSpace=0)
Fetch the type representing a pointer.
Definition IRBuilder.h:604
Value * CreateAShr(Value *LHS, Value *RHS, const Twine &Name="", bool isExact=false)
Definition IRBuilder.h:1535
Value * CreateXor(Value *LHS, Value *RHS, const Twine &Name="")
Definition IRBuilder.h:1602
IntegerType * getInt8Ty()
Fetch the type representing an 8-bit integer.
Definition IRBuilder.h:551
This provides a uniform API for creating instructions and inserting them into a basic block: either a...
Definition IRBuilder.h:2788
iterator_range< simple_ilist< DbgRecord >::iterator > getDbgRecordRange() const
Return a range over the DbgRecords attached to this instruction.
LLVM_ABI const DataLayout & getDataLayout() const
Get the data layout of the module this instruction belongs to.
Metadata node.
Definition Metadata.h:1080
static MDTuple * get(LLVMContext &Context, ArrayRef< Metadata * > MDs)
Definition Metadata.h:1572
static LLVM_ABI MetadataAsValue * get(LLVMContext &Context, Metadata *MD)
Definition Metadata.cpp:110
A Module instance is used to store all the information related to an LLVM module.
Definition Module.h:67
The optimization diagnostic interface.
LLVM_ABI void emit(DiagnosticInfoOptimizationBase &OptDiag)
Output the remark via the diagnostic handler and to the optimization record file.
Diagnostic information for missed-optimization remarks.
Diagnostic information for applied optimization remarks.
PostDominatorTree Class - Concrete subclass of DominatorTree that is used to compute the post-dominat...
LLVM_ABI bool dominates(const Instruction *I1, const Instruction *I2) const
Return true if I1 dominates I2.
std::pair< iterator, bool > insert(PtrType Ptr)
Inserts Ptr if and only if there is no element in the container equal to Ptr.
bool contains(ConstPtrType Ptr) const
SmallPtrSet - This class implements a set which is optimized for holding SmallSize or less elements.
This class consists of common code factored out of the SmallVector class to reduce code duplication b...
void push_back(const T &Elt)
This is a 'vector' (really, a variable-sized array), optimized for the case when the array is small.
StringRef - Represent a constant reference to a string, i.e.
Definition StringRef.h:55
static LLVM_ABI StructType * get(LLVMContext &Context, ArrayRef< Type * > Elements, bool isPacked=false)
This static method is the primary way to create a literal StructType.
Definition Type.cpp:413
Triple - Helper class for working with autoconf configuration names.
Definition Triple.h:47
ArchType getArch() const
Get the parsed architecture type of this triple.
Definition Triple.h:420
The instances of the Type class are immutable: once they are created, they are never changed.
Definition Type.h:45
static LLVM_ABI IntegerType * getInt8Ty(LLVMContext &C)
Definition Type.cpp:294
LLVM Value Representation.
Definition Value.h:75
Type * getType() const
All values are typed, get the type of this value.
Definition Value.h:256
An efficient, type-erasing, non-owning reference to a callable.
const ParentTy * getParent() const
Definition ilist_node.h:34
void visit(OptimizationRemarkEmitter &ORE, Instruction &Inst)
AllocaInterestingness getAllocaInterestingness(const AllocaInst &AI)
LLVM_ABI Function * getOrInsertDeclaration(Module *M, ID id, ArrayRef< Type * > Tys={})
Look up the Function declaration of the intrinsic id in the Module M.
@ DW_OP_LLVM_tag_offset
Only used in LLVM metadata.
Definition Dwarf.h:146
Value * getFP(IRBuilder<> &IRB)
void forAllReachableExits(const DominatorTree &DT, const PostDominatorTree &PDT, const LoopInfo &LI, const AllocaInfo &AInfo, const SmallVectorImpl< Instruction * > &RetVec, llvm::function_ref< void(Instruction *)> Callback)
bool isSupportedLifetime(const AllocaInfo &AInfo, const DominatorTree *DT, const LoopInfo *LI)
uint64_t getAllocaSizeInBytes(const AllocaInst &AI)
Value * getDarwinSlotPtr(IRBuilder<> &IRB, int Slot)
Value * getAndroidSlotPtr(IRBuilder<> &IRB, int Slot)
static DbgVariableRecord * DynCastToDbgAssign(DbgVariableRecord *DVR)
Value * readRegister(IRBuilder<> &IRB, StringRef Name)
void annotateDebugRecords(AllocaInfo &Info, unsigned int Tag)
Instruction * getUntagLocationIfFunctionExit(Instruction &Inst)
void alignAndPadAlloca(memtag::AllocaInfo &Info, llvm::Align Align)
Value * getPC(const Triple &TargetTriple, IRBuilder<> &IRB)
Value * incrementThreadLong(IRBuilder<> &IRB, Value *ThreadLong, unsigned int Inc, bool IsMemtagDarwin=false)
This is an optimization pass for GlobalISel generic memory operations.
Definition Types.h:26
UnaryFunction for_each(R &&Range, UnaryFunction F)
Provide wrappers to std::for_each which take ranges instead of having to pass begin/end explicitly.
Definition STLExtras.h:1732
LLVM_ABI bool isPotentiallyReachableFromMany(SmallVectorImpl< BasicBlock * > &Worklist, const BasicBlock *StopBB, const SmallPtrSetImpl< BasicBlock * > *ExclusionSet, const DominatorTree *DT=nullptr, const LoopInfo *LI=nullptr)
Determine whether there is at least one path from a block in 'Worklist' to 'StopBB' without passing t...
Definition CFG.cpp:240
decltype(auto) dyn_cast(const From &Val)
dyn_cast<X> - Return the argument parameter cast to the specified type.
Definition Casting.h:643
LLVM_ABI bool isAllocaPromotable(const AllocaInst *AI)
Return true if this alloca is legal for promotion.
auto dyn_cast_or_null(const Y &Val)
Definition Casting.h:753
LLVM_ABI bool isManyPotentiallyReachableFromMany(SmallVectorImpl< BasicBlock * > &Worklist, const SmallPtrSetImpl< const BasicBlock * > &StopSet, const SmallPtrSetImpl< BasicBlock * > *ExclusionSet, const DominatorTree *DT=nullptr, const LoopInfo *LI=nullptr)
Determine whether there is a potentially a path from at least one block in 'Worklist' to at least one...
Definition CFG.cpp:249
bool isa(const From &Val)
isa<X> - Return true if the parameter to the template is an instance of one of the template type argu...
Definition Casting.h:547
uint64_t alignTo(uint64_t Size, Align A)
Returns a multiple of A needed to store Size bytes.
Definition Alignment.h:144
decltype(auto) cast(const From &Val)
cast<X> - Return the argument parameter cast to the specified type.
Definition Casting.h:559
static auto filterDbgVars(iterator_range< simple_ilist< DbgRecord >::iterator > R)
Filter the DbgRecord range to DbgVariableRecord types only and downcast.
This struct is a compact representation of a valid (non-zero power of two) alignment.
Definition Alignment.h:39
SmallVector< DbgVariableRecord *, 2 > DbgVariableRecords
SmallVector< IntrinsicInst *, 2 > LifetimeEnd
SmallVector< IntrinsicInst *, 2 > LifetimeStart
MapVector< BasicBlock *, struct BBInfo > BBInfos