//===- 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/CodeGen/JMCInstrumenter.h" #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-instrumenter" static bool runImpl(Module &M); namespace { struct JMCInstrumenter : public ModulePass { static char ID; JMCInstrumenter() : ModulePass(ID) { initializeJMCInstrumenterPass(*PassRegistry::getPassRegistry()); } bool runOnModule(Module &M) override { return runImpl(M); } }; char JMCInstrumenter::ID = 0; } // namespace PreservedAnalyses JMCInstrumenterPass::run(Module &M, ModuleAnalysisManager &) { bool Changed = runImpl(M); return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all(); } 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 ___ with '.' in // 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 = PointerType::getUnqual(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 runImpl(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 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( 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; }