diff options
author | Evgeniy Stepanov <eugeni.stepanov@gmail.com> | 2016-11-11 18:49:09 +0000 |
---|---|---|
committer | Evgeniy Stepanov <eugeni.stepanov@gmail.com> | 2016-11-11 18:49:09 +0000 |
commit | f48ffab554fa77306246eaac63970d53229b0c0d (patch) | |
tree | 6ba0f0313f89f1b277f04e6a72209dcd5d8fd51a /llvm/lib | |
parent | 6285f5b441bf602319c2bc3b4ed04a1450aa8247 (diff) | |
download | bcm5719-llvm-f48ffab554fa77306246eaac63970d53229b0c0d.tar.gz bcm5719-llvm-f48ffab554fa77306246eaac63970d53229b0c0d.zip |
[cfi] Implement cfi-icall using inline assembly.
The current implementation is emitting a global constant that happens
to evaluate to the same bytes + relocation as a jump instruction on
X86. This does not work for PIE executables and shared libraries
though, because we end up with a wrong relocation type. And it has no
chance of working on ARM/AArch64 which use different relocation types
for jump instructions (R_ARM_JUMP24) that is never generated for
data.
This change replaces the constant with module-level inline assembly
followed by a hidden declaration of the jump table. Works fine for
ARM/AArch64, but has some drawbacks.
* Extra symbols are added to the static symbol table, which inflate
the size of the unstripped binary a little. Stripped binaries are not
affected. This happens because jump table declarations must be
external (because their body is in the inline asm).
* Original functions that were anonymous are now named
<original name>.cfi, and it affects symbolization sometimes. This is
necessary because the only user of these functions is the (inline
asm) jump table, so they had to be added to @llvm.used, which does
not allow unnamed functions.
llvm-svn: 286611
Diffstat (limited to 'llvm/lib')
-rw-r--r-- | llvm/lib/Transforms/IPO/LowerTypeTests.cpp | 234 |
1 files changed, 159 insertions, 75 deletions
diff --git a/llvm/lib/Transforms/IPO/LowerTypeTests.cpp b/llvm/lib/Transforms/IPO/LowerTypeTests.cpp index 8f9d665ef05..8631886a281 100644 --- a/llvm/lib/Transforms/IPO/LowerTypeTests.cpp +++ b/llvm/lib/Transforms/IPO/LowerTypeTests.cpp @@ -24,6 +24,7 @@ #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/Intrinsics.h" +#include "llvm/IR/Mangler.h" #include "llvm/IR/Module.h" #include "llvm/IR/Operator.h" #include "llvm/Pass.h" @@ -31,6 +32,7 @@ #include "llvm/Support/raw_ostream.h" #include "llvm/Transforms/IPO.h" #include "llvm/Transforms/Utils/BasicBlockUtils.h" +#include "llvm/Transforms/Utils/ModuleUtils.h" using namespace llvm; using namespace lowertypetests; @@ -225,6 +227,8 @@ class LowerTypeTestsModule { std::vector<ByteArrayInfo> ByteArrayInfos; + Mangler Mang; + BitSetInfo buildBitSet(Metadata *TypeId, const DenseMap<GlobalObject *, uint64_t> &GlobalLayout); @@ -243,12 +247,13 @@ class LowerTypeTestsModule { ArrayRef<GlobalVariable *> Globals); unsigned getJumpTableEntrySize(); Type *getJumpTableEntryType(); - Constant *createJumpTableEntry(GlobalObject *Src, Function *Dest, - unsigned Distance); + void createJumpTableEntry(raw_ostream &OS, Function *Dest, unsigned Distance); + void createJumpTableAlias(raw_ostream &OS, Function *Dest, + GlobalVariable *JumpTable, unsigned Distance); void verifyTypeMDNode(GlobalObject *GO, MDNode *Type); void buildBitSetsFromFunctions(ArrayRef<Metadata *> TypeIds, ArrayRef<Function *> Functions); - void buildBitSetsFromFunctionsX86(ArrayRef<Metadata *> TypeIds, + void buildBitSetsFromFunctionsNative(ArrayRef<Metadata *> TypeIds, ArrayRef<Function *> Functions); void buildBitSetsFromFunctionsWASM(ArrayRef<Metadata *> TypeIds, ArrayRef<Function *> Functions); @@ -627,53 +632,101 @@ void LowerTypeTestsModule::verifyTypeMDNode(GlobalObject *GO, MDNode *Type) { } static const unsigned kX86JumpTableEntrySize = 8; +static const unsigned kARMJumpTableEntrySize = 4; unsigned LowerTypeTestsModule::getJumpTableEntrySize() { - return kX86JumpTableEntrySize; + switch (Arch) { + case Triple::x86: + case Triple::x86_64: + return kX86JumpTableEntrySize; + case Triple::arm: + case Triple::aarch64: + return kARMJumpTableEntrySize; + default: + report_fatal_error("Unsupported architecture for jump tables"); + } +} + +static bool isValidAsmUnquotedName(StringRef Name) { + if (Name.empty()) + return false; + + for (char C : Name) { + if (!((C >= 'a' && C <= 'z') || (C >= 'A' && C <= 'Z') || + (C >= '0' && C <= '9') || C == '_' || C == '$' || C == '.' || + C == '@')) + return false; + } + + return true; } // Create a constant representing a jump table entry for the target. This // consists of an instruction sequence containing a relative branch to Dest. The // constant will be laid out at address Src+(Len*Distance) where Len is the // target-specific jump table entry size. -Constant *LowerTypeTestsModule::createJumpTableEntry(GlobalObject *Src, - Function *Dest, - unsigned Distance) { - const unsigned kJmpPCRel32Code = 0xe9; - const unsigned kInt3Code = 0xcc; - - ConstantInt *Jmp = ConstantInt::get(Int8Ty, kJmpPCRel32Code); - - // Build a constant representing the displacement between the constant's - // address and Dest. This will resolve to a PC32 relocation referring to Dest. - Constant *DestInt = ConstantExpr::getPtrToInt(Dest, IntPtrTy); - Constant *SrcInt = ConstantExpr::getPtrToInt(Src, IntPtrTy); - Constant *Disp = ConstantExpr::getSub(DestInt, SrcInt); - ConstantInt *DispOffset = - ConstantInt::get(IntPtrTy, Distance * kX86JumpTableEntrySize + 5); - Constant *OffsetedDisp = ConstantExpr::getSub(Disp, DispOffset); - OffsetedDisp = ConstantExpr::getTruncOrBitCast(OffsetedDisp, Int32Ty); - - ConstantInt *Int3 = ConstantInt::get(Int8Ty, kInt3Code); - - Constant *Fields[] = { - Jmp, OffsetedDisp, Int3, Int3, Int3, - }; - return ConstantStruct::getAnon(Fields, /*Packed=*/true); +void LowerTypeTestsModule::createJumpTableEntry(raw_ostream &OS, Function *Dest, + unsigned Distance) { + // FIXME: replace IR Mangler with TargetLoweringObjectFile interface. + // A private instance of Mangler we use here can not deal with unnamed + // symbols, as it may create colliding labels. Thankfully(?), the use of + // inline asm requires us to give names to all affected functions anyway. + assert(Dest->hasName() && "jumptable targets can not be anonymous"); + SmallString<16> Name; + Mang.getNameWithPrefix(Name, Dest, /* CannotUsePrivateLabel */ false); + + if (!isValidAsmUnquotedName(Name)) { + // We are going to emit a function call as textual asm. Escaped strings + // in such expressions are not well supported. + report_fatal_error( + "CFI-ICall does not allow special characters in a function name."); + } + + if (Arch == Triple::x86 || Arch == Triple::x86_64) { + OS << "jmp " << Name << "@plt\n"; + OS << "int3\nint3\nint3\n"; + } else if (Arch == Triple::arm || Arch == Triple::aarch64) { + OS << "b " << Name << "\n"; + } else { + report_fatal_error("Unsupported architecture for jump tables"); + } +} + +void LowerTypeTestsModule::createJumpTableAlias(raw_ostream &OS, Function *Dest, + GlobalVariable *JumpTable, + unsigned Distance) { + assert(Dest->hasName() && "jumptable targets can not be anonymous"); + SmallString<16> Name; + Mang.getNameWithPrefix(Name, Dest, /* CannotUsePrivateLabel */ false); + + if (!isValidAsmUnquotedName(Name)) { + // We are going to emit a function alias as textual asm. Escaped strings + // in such expressions are not well supported. + report_fatal_error( + "CFI-ICall does not allow special characters in a function name."); + } + + if (Dest->isWeakForLinker()) + OS << ".weak " << Name << "\n"; + else if (!Dest->hasLocalLinkage()) + OS << ".globl " << Name << "\n"; + OS << ".type " << Name << ", function\n"; + OS << Name << " = " << JumpTable->getName() << " + " + << (getJumpTableEntrySize() * Distance) << "\n"; + OS << ".size " << Name << ", " << getJumpTableEntrySize() << "\n"; } Type *LowerTypeTestsModule::getJumpTableEntryType() { - return StructType::get(M.getContext(), - {Int8Ty, Int32Ty, Int8Ty, Int8Ty, Int8Ty}, - /*Packed=*/true); + return ArrayType::get(Int8Ty, getJumpTableEntrySize()); } /// Given a disjoint set of type identifiers and functions, build the bit sets /// and lower the llvm.type.test calls, architecture dependently. void LowerTypeTestsModule::buildBitSetsFromFunctions( ArrayRef<Metadata *> TypeIds, ArrayRef<Function *> Functions) { - if (Arch == Triple::x86 || Arch == Triple::x86_64) - buildBitSetsFromFunctionsX86(TypeIds, Functions); + if (Arch == Triple::x86 || Arch == Triple::x86_64 || Arch == Triple::arm || + Arch == Triple::aarch64) + buildBitSetsFromFunctionsNative(TypeIds, Functions); else if (Arch == Triple::wasm32 || Arch == Triple::wasm64) buildBitSetsFromFunctionsWASM(TypeIds, Functions); else @@ -682,7 +735,7 @@ void LowerTypeTestsModule::buildBitSetsFromFunctions( /// Given a disjoint set of type identifiers and functions, build a jump table /// for the functions, build the bit sets and lower the llvm.type.test calls. -void LowerTypeTestsModule::buildBitSetsFromFunctionsX86( +void LowerTypeTestsModule::buildBitSetsFromFunctionsNative( ArrayRef<Metadata *> TypeIds, ArrayRef<Function *> Functions) { // Unlike the global bitset builder, the function bitset builder cannot // re-arrange functions in a particular order and base its calculations on the @@ -717,39 +770,35 @@ void LowerTypeTestsModule::buildBitSetsFromFunctionsX86( // mov h, %ecx // ret // - // To create a jump table for these functions, we instruct the LLVM code - // generator to output a jump table in the .text section. This is done by - // representing the instructions in the jump table as an LLVM constant and - // placing them in a global variable in the .text section. The end result will - // (conceptually) look like this: + // We output the jump table as module-level inline asm string. The end result + // will (conceptually) look like this: // - // f: - // jmp .Ltmp0 ; 5 bytes + // f = .cfi.jumptable + // g = .cfi.jumptable + 4 + // h = .cfi.jumptable + 8 + // .cfi.jumptable: + // jmp f.cfi ; 5 bytes // int3 ; 1 byte // int3 ; 1 byte // int3 ; 1 byte - // - // g: - // jmp .Ltmp1 ; 5 bytes + // jmp g.cfi ; 5 bytes // int3 ; 1 byte // int3 ; 1 byte // int3 ; 1 byte - // - // h: - // jmp .Ltmp2 ; 5 bytes + // jmp h.cfi ; 5 bytes // int3 ; 1 byte // int3 ; 1 byte // int3 ; 1 byte // - // .Ltmp0: + // f.cfi: // mov 0, %eax // ret // - // .Ltmp1: + // g.cfi: // mov 1, %eax // ret // - // .Ltmp2: + // h.cfi: // mov 2, %eax // ret // @@ -763,6 +812,8 @@ void LowerTypeTestsModule::buildBitSetsFromFunctionsX86( // normal case the check can be carried out using the same kind of simple // arithmetic that we normally use for globals. + // FIXME: find a better way to represent the jumptable in the IR. + assert(!Functions.empty()); // Build a simple layout based on the regular layout of jump tables. @@ -774,45 +825,78 @@ void LowerTypeTestsModule::buildBitSetsFromFunctionsX86( // Create a constant to hold the jump table. ArrayType *JumpTableType = ArrayType::get(getJumpTableEntryType(), Functions.size()); - auto JumpTable = new GlobalVariable(M, JumpTableType, - /*isConstant=*/true, - GlobalValue::PrivateLinkage, nullptr); - JumpTable->setSection(ObjectFormat == Triple::MachO - ? "__TEXT,__text,regular,pure_instructions" - : ".text"); + auto JumpTable = + new GlobalVariable(M, JumpTableType, + /*isConstant=*/true, GlobalValue::ExternalLinkage, + nullptr, ".cfi.jumptable"); + JumpTable->setVisibility(GlobalValue::HiddenVisibility); lowerTypeTestCalls(TypeIds, JumpTable, GlobalLayout); + std::string AsmStr; + raw_string_ostream AsmOS(AsmStr); + // Build aliases pointing to offsets into the jump table, and replace // references to the original functions with references to the aliases. for (unsigned I = 0; I != Functions.size(); ++I) { - Constant *CombinedGlobalElemPtr = ConstantExpr::getBitCast( - ConstantExpr::getGetElementPtr( - JumpTableType, JumpTable, - ArrayRef<Constant *>{ConstantInt::get(IntPtrTy, 0), - ConstantInt::get(IntPtrTy, I)}), - Functions[I]->getType()); + // Need a name for the asm label. Normally, unnamed functions get temporary + // asm labels in TargetLoweringObjectFile but we don't have access to that + // here. + if (!Functions[I]->hasName()) + Functions[I]->setName("unnamed"); if (LinkerSubsectionsViaSymbols || Functions[I]->isDeclarationForLinker()) { + Constant *CombinedGlobalElemPtr = ConstantExpr::getBitCast( + ConstantExpr::getGetElementPtr( + JumpTableType, JumpTable, + ArrayRef<Constant *>{ConstantInt::get(IntPtrTy, 0), + ConstantInt::get(IntPtrTy, I)}), + Functions[I]->getType()); Functions[I]->replaceAllUsesWith(CombinedGlobalElemPtr); + + if (Functions[I]->isWeakForLinker()) + AsmOS << ".weak " << Functions[I]->getName() << "\n"; } else { assert(Functions[I]->getType()->getAddressSpace() == 0); - GlobalAlias *GAlias = GlobalAlias::create(Functions[I]->getValueType(), 0, - Functions[I]->getLinkage(), "", - CombinedGlobalElemPtr, &M); - GAlias->setVisibility(Functions[I]->getVisibility()); - GAlias->takeName(Functions[I]); - Functions[I]->replaceAllUsesWith(GAlias); + + createJumpTableAlias(AsmOS, Functions[I], JumpTable, I); + + Function *DeclAlias = + Function::Create(cast<FunctionType>(Functions[I]->getValueType()), + GlobalValue::ExternalLinkage, "", &M); + // Since the alias (DeclAlias) is actually a declaration, it can not have + // internal linkage. Compensate for that by giving it hidden visibility. + // With this we end up with a GOT relocation against a local symbol. + DeclAlias->setVisibility(Functions[I]->hasLocalLinkage() + ? GlobalValue::HiddenVisibility + : Functions[I]->getVisibility()); + DeclAlias->takeName(Functions[I]); + // Unnamed functions can not be added to llvm.used. + Functions[I]->setName(DeclAlias->getName() + ".cfi"); + Functions[I]->replaceAllUsesWith(DeclAlias); } if (!Functions[I]->isDeclarationForLinker()) - Functions[I]->setLinkage(GlobalValue::PrivateLinkage); + Functions[I]->setLinkage(GlobalValue::InternalLinkage); } - // Build and set the jump table's initializer. - std::vector<Constant *> JumpTableEntries; + // Try to emit the jump table at the end of the text segment. + // Jump table must come after __cfi_check in the cross-dso mode. + // FIXME: this magic section name seems to do the trick. + AsmOS << ".section " << (ObjectFormat == Triple::MachO + ? "__TEXT,__text,regular,pure_instructions" + : ".text.cfi, \"ax\", @progbits") + << "\n"; + // Align the whole table by entry size. + AsmOS << ".balign " << EntrySize << "\n"; + AsmOS << JumpTable->getName() << ":\n"; for (unsigned I = 0; I != Functions.size(); ++I) - JumpTableEntries.push_back( - createJumpTableEntry(JumpTable, Functions[I], I)); - JumpTable->setInitializer( - ConstantArray::get(JumpTableType, JumpTableEntries)); + createJumpTableEntry(AsmOS, Functions[I], I); + + M.appendModuleInlineAsm(AsmOS.str()); + + SmallVector<GlobalValue *, 16> Used; + Used.reserve(Functions.size()); + for (auto *F : Functions) + Used.push_back(F); + appendToUsed(M, Used); } /// Assign a dummy layout using an incrementing counter, tag each function |