| //===- JMCInstrumenter.cpp - JMC Instrumentation --------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // JMCInstrumenter pass: | |
| // - instrument each function with a call to __CheckForDebuggerJustMyCode. The | |
| // sole argument should be defined in .msvcjmc. Each flag is 1 byte initilized | |
| // to 1. | |
| // - create the dummy COMDAT function __JustMyCode_Default to prevent linking | |
| // error if __CheckForDebuggerJustMyCode is not available. | |
| // - For MSVC: | |
| // add "/alternatename:__CheckForDebuggerJustMyCode=__JustMyCode_Default" to | |
| // "llvm.linker.options" | |
| // For ELF: | |
| // Rename __JustMyCode_Default to __CheckForDebuggerJustMyCode and mark it as | |
| // weak symbol. | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/ADT/SmallString.h" | |
| #include "llvm/ADT/StringExtras.h" | |
| #include "llvm/CodeGen/Passes.h" | |
| #include "llvm/IR/DIBuilder.h" | |
| #include "llvm/IR/DebugInfoMetadata.h" | |
| #include "llvm/IR/DerivedTypes.h" | |
| #include "llvm/IR/Function.h" | |
| #include "llvm/IR/Instructions.h" | |
| #include "llvm/IR/LLVMContext.h" | |
| #include "llvm/IR/Module.h" | |
| #include "llvm/IR/Type.h" | |
| #include "llvm/InitializePasses.h" | |
| #include "llvm/Pass.h" | |
| #include "llvm/Support/DJB.h" | |
| #include "llvm/Support/Path.h" | |
| #include "llvm/Transforms/Utils/ModuleUtils.h" | |
| using namespace llvm; | |
| #define DEBUG_TYPE "jmc-instrument" | |
| namespace { | |
| struct JMCInstrumenter : public ModulePass { | |
| static char ID; | |
| JMCInstrumenter() : ModulePass(ID) { | |
| initializeJMCInstrumenterPass(*PassRegistry::getPassRegistry()); | |
| } | |
| bool runOnModule(Module &M) override; | |
| }; | |
| char JMCInstrumenter::ID = 0; | |
| } // namespace | |
| INITIALIZE_PASS( | |
| JMCInstrumenter, DEBUG_TYPE, | |
| "Instrument function entry with call to __CheckForDebuggerJustMyCode", | |
| false, false) | |
| ModulePass *llvm::createJMCInstrumenterPass() { return new JMCInstrumenter(); } | |
| namespace { | |
| const char CheckFunctionName[] = "__CheckForDebuggerJustMyCode"; | |
| std::string getFlagName(DISubprogram &SP, bool UseX86FastCall) { | |
| // absolute windows path: windows_backslash | |
| // relative windows backslash path: windows_backslash | |
| // relative windows slash path: posix | |
| // absolute posix path: posix | |
| // relative posix path: posix | |
| sys::path::Style PathStyle = | |
| has_root_name(SP.getDirectory(), sys::path::Style::windows_backslash) || | |
| SP.getDirectory().contains("\\") || | |
| SP.getFilename().contains("\\") | |
| ? sys::path::Style::windows_backslash | |
| : sys::path::Style::posix; | |
| // Best effort path normalization. This is to guarantee an unique flag symbol | |
| // is produced for the same directory. Some builds may want to use relative | |
| // paths, or paths with a specific prefix (see the -fdebug-compilation-dir | |
| // flag), so only hash paths in debuginfo. Don't expand them to absolute | |
| // paths. | |
| SmallString<256> FilePath(SP.getDirectory()); | |
| sys::path::append(FilePath, PathStyle, SP.getFilename()); | |
| sys::path::native(FilePath, PathStyle); | |
| sys::path::remove_dots(FilePath, /*remove_dot_dot=*/true, PathStyle); | |
| // The naming convention for the flag name is __<hash>_<file name> with '.' in | |
| // <file name> replaced with '@'. For example C:\file.any.c would have a flag | |
| // __D032E919_file@any@c. The naming convention match MSVC's format however | |
| // the match is not required to make JMC work. The hashing function used here | |
| // is different from MSVC's. | |
| std::string Suffix; | |
| for (auto C : sys::path::filename(FilePath, PathStyle)) | |
| Suffix.push_back(C == '.' ? '@' : C); | |
| sys::path::remove_filename(FilePath, PathStyle); | |
| return (UseX86FastCall ? "_" : "__") + | |
| utohexstr(djbHash(FilePath), /*LowerCase=*/false, | |
| /*Width=*/8) + | |
| "_" + Suffix; | |
| } | |
| void attachDebugInfo(GlobalVariable &GV, DISubprogram &SP) { | |
| Module &M = *GV.getParent(); | |
| DICompileUnit *CU = SP.getUnit(); | |
| assert(CU); | |
| DIBuilder DB(M, false, CU); | |
| auto *DType = | |
| DB.createBasicType("unsigned char", 8, dwarf::DW_ATE_unsigned_char, | |
| llvm::DINode::FlagArtificial); | |
| auto *DGVE = DB.createGlobalVariableExpression( | |
| CU, GV.getName(), /*LinkageName=*/StringRef(), SP.getFile(), | |
| /*LineNo=*/0, DType, /*IsLocalToUnit=*/true, /*IsDefined=*/true); | |
| GV.addMetadata(LLVMContext::MD_dbg, *DGVE); | |
| DB.finalize(); | |
| } | |
| FunctionType *getCheckFunctionType(LLVMContext &Ctx) { | |
| Type *VoidTy = Type::getVoidTy(Ctx); | |
| PointerType *VoidPtrTy = Type::getInt8PtrTy(Ctx); | |
| return FunctionType::get(VoidTy, VoidPtrTy, false); | |
| } | |
| Function *createDefaultCheckFunction(Module &M, bool UseX86FastCall) { | |
| LLVMContext &Ctx = M.getContext(); | |
| const char *DefaultCheckFunctionName = | |
| UseX86FastCall ? "_JustMyCode_Default" : "__JustMyCode_Default"; | |
| // Create the function. | |
| Function *DefaultCheckFunc = | |
| Function::Create(getCheckFunctionType(Ctx), GlobalValue::ExternalLinkage, | |
| DefaultCheckFunctionName, &M); | |
| DefaultCheckFunc->setUnnamedAddr(GlobalValue::UnnamedAddr::Global); | |
| DefaultCheckFunc->addParamAttr(0, Attribute::NoUndef); | |
| if (UseX86FastCall) | |
| DefaultCheckFunc->addParamAttr(0, Attribute::InReg); | |
| BasicBlock *EntryBB = BasicBlock::Create(Ctx, "", DefaultCheckFunc); | |
| ReturnInst::Create(Ctx, EntryBB); | |
| return DefaultCheckFunc; | |
| } | |
| } // namespace | |
| bool JMCInstrumenter::runOnModule(Module &M) { | |
| bool Changed = false; | |
| LLVMContext &Ctx = M.getContext(); | |
| Triple ModuleTriple(M.getTargetTriple()); | |
| bool IsMSVC = ModuleTriple.isKnownWindowsMSVCEnvironment(); | |
| bool IsELF = ModuleTriple.isOSBinFormatELF(); | |
| assert((IsELF || IsMSVC) && "Unsupported triple for JMC"); | |
| bool UseX86FastCall = IsMSVC && ModuleTriple.getArch() == Triple::x86; | |
| const char *const FlagSymbolSection = IsELF ? ".data.just.my.code" : ".msvcjmc"; | |
| GlobalValue *CheckFunction = nullptr; | |
| DenseMap<DISubprogram *, Constant *> SavedFlags(8); | |
| for (auto &F : M) { | |
| if (F.isDeclaration()) | |
| continue; | |
| auto *SP = F.getSubprogram(); | |
| if (!SP) | |
| continue; | |
| Constant *&Flag = SavedFlags[SP]; | |
| if (!Flag) { | |
| std::string FlagName = getFlagName(*SP, UseX86FastCall); | |
| IntegerType *FlagTy = Type::getInt8Ty(Ctx); | |
| Flag = M.getOrInsertGlobal(FlagName, FlagTy, [&] { | |
| // FIXME: Put the GV in comdat and have linkonce_odr linkage to save | |
| // .msvcjmc section space? maybe not worth it. | |
| GlobalVariable *GV = new GlobalVariable( | |
| M, FlagTy, /*isConstant=*/false, GlobalValue::InternalLinkage, | |
| ConstantInt::get(FlagTy, 1), FlagName); | |
| GV->setSection(FlagSymbolSection); | |
| GV->setAlignment(Align(1)); | |
| GV->setUnnamedAddr(GlobalValue::UnnamedAddr::Global); | |
| attachDebugInfo(*GV, *SP); | |
| return GV; | |
| }); | |
| } | |
| if (!CheckFunction) { | |
| Function *DefaultCheckFunc = | |
| createDefaultCheckFunction(M, UseX86FastCall); | |
| if (IsELF) { | |
| DefaultCheckFunc->setName(CheckFunctionName); | |
| DefaultCheckFunc->setLinkage(GlobalValue::WeakAnyLinkage); | |
| CheckFunction = DefaultCheckFunc; | |
| } else { | |
| assert(!M.getFunction(CheckFunctionName) && | |
| "JMC instrument more than once?"); | |
| auto *CheckFunc = cast<Function>( | |
| M.getOrInsertFunction(CheckFunctionName, getCheckFunctionType(Ctx)) | |
| .getCallee()); | |
| CheckFunc->setUnnamedAddr(GlobalValue::UnnamedAddr::Global); | |
| CheckFunc->addParamAttr(0, Attribute::NoUndef); | |
| if (UseX86FastCall) { | |
| CheckFunc->setCallingConv(CallingConv::X86_FastCall); | |
| CheckFunc->addParamAttr(0, Attribute::InReg); | |
| } | |
| CheckFunction = CheckFunc; | |
| StringRef DefaultCheckFunctionName = DefaultCheckFunc->getName(); | |
| appendToUsed(M, {DefaultCheckFunc}); | |
| Comdat *C = M.getOrInsertComdat(DefaultCheckFunctionName); | |
| C->setSelectionKind(Comdat::Any); | |
| DefaultCheckFunc->setComdat(C); | |
| // Add a linker option /alternatename to set the default implementation | |
| // for the check function. | |
| // https://devblogs.microsoft.com/oldnewthing/20200731-00/?p=104024 | |
| std::string AltOption = std::string("/alternatename:") + | |
| CheckFunctionName + "=" + | |
| DefaultCheckFunctionName.str(); | |
| llvm::Metadata *Ops[] = {llvm::MDString::get(Ctx, AltOption)}; | |
| MDTuple *N = MDNode::get(Ctx, Ops); | |
| M.getOrInsertNamedMetadata("llvm.linker.options")->addOperand(N); | |
| } | |
| } | |
| // FIXME: it would be nice to make CI scheduling boundary, although in | |
| // practice it does not matter much. | |
| auto *CI = CallInst::Create(getCheckFunctionType(Ctx), CheckFunction, | |
| {Flag}, "", &*F.begin()->getFirstInsertionPt()); | |
| CI->addParamAttr(0, Attribute::NoUndef); | |
| if (UseX86FastCall) { | |
| CI->setCallingConv(CallingConv::X86_FastCall); | |
| CI->addParamAttr(0, Attribute::InReg); | |
| } | |
| Changed = true; | |
| } | |
| return Changed; | |
| } |