| 1 | //===- JMCInstrumenter.cpp - JMC Instrumentation --------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // JMCInstrumenter pass: |
| 10 | // - instrument each function with a call to __CheckForDebuggerJustMyCode. The |
| 11 | // sole argument should be defined in .msvcjmc. Each flag is 1 byte initilized |
| 12 | // to 1. |
| 13 | // - create the dummy COMDAT function __JustMyCode_Default to prevent linking |
| 14 | // error if __CheckForDebuggerJustMyCode is not available. |
| 15 | // - For MSVC: |
| 16 | // add "/alternatename:__CheckForDebuggerJustMyCode=__JustMyCode_Default" to |
| 17 | // "llvm.linker.options" |
| 18 | // For ELF: |
| 19 | // Rename __JustMyCode_Default to __CheckForDebuggerJustMyCode and mark it as |
| 20 | // weak symbol. |
| 21 | //===----------------------------------------------------------------------===// |
| 22 | |
| 23 | #include "llvm/CodeGen/JMCInstrumenter.h" |
| 24 | #include "llvm/ADT/SmallString.h" |
| 25 | #include "llvm/ADT/StringExtras.h" |
| 26 | #include "llvm/CodeGen/Passes.h" |
| 27 | #include "llvm/IR/DIBuilder.h" |
| 28 | #include "llvm/IR/DebugInfoMetadata.h" |
| 29 | #include "llvm/IR/DerivedTypes.h" |
| 30 | #include "llvm/IR/Function.h" |
| 31 | #include "llvm/IR/Instructions.h" |
| 32 | #include "llvm/IR/LLVMContext.h" |
| 33 | #include "llvm/IR/Module.h" |
| 34 | #include "llvm/IR/Type.h" |
| 35 | #include "llvm/InitializePasses.h" |
| 36 | #include "llvm/Pass.h" |
| 37 | #include "llvm/Support/DJB.h" |
| 38 | #include "llvm/Support/Path.h" |
| 39 | #include "llvm/Transforms/Utils/ModuleUtils.h" |
| 40 | |
| 41 | using namespace llvm; |
| 42 | |
| 43 | #define DEBUG_TYPE "jmc-instrumenter" |
| 44 | |
| 45 | static bool runImpl(Module &M); |
| 46 | namespace { |
| 47 | struct JMCInstrumenter : public ModulePass { |
| 48 | static char ID; |
| 49 | JMCInstrumenter() : ModulePass(ID) { |
| 50 | initializeJMCInstrumenterPass(*PassRegistry::getPassRegistry()); |
| 51 | } |
| 52 | bool runOnModule(Module &M) override { return runImpl(M); } |
| 53 | }; |
| 54 | char JMCInstrumenter::ID = 0; |
| 55 | } // namespace |
| 56 | |
| 57 | PreservedAnalyses JMCInstrumenterPass::run(Module &M, ModuleAnalysisManager &) { |
| 58 | bool Changed = runImpl(M); |
| 59 | return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all(); |
| 60 | } |
| 61 | |
| 62 | INITIALIZE_PASS( |
| 63 | JMCInstrumenter, DEBUG_TYPE, |
| 64 | "Instrument function entry with call to __CheckForDebuggerJustMyCode" , |
| 65 | false, false) |
| 66 | |
| 67 | ModulePass *llvm::createJMCInstrumenterPass() { return new JMCInstrumenter(); } |
| 68 | |
| 69 | namespace { |
| 70 | const char CheckFunctionName[] = "__CheckForDebuggerJustMyCode" ; |
| 71 | |
| 72 | std::string getFlagName(DISubprogram &SP, bool UseX86FastCall) { |
| 73 | // absolute windows path: windows_backslash |
| 74 | // relative windows backslash path: windows_backslash |
| 75 | // relative windows slash path: posix |
| 76 | // absolute posix path: posix |
| 77 | // relative posix path: posix |
| 78 | sys::path::Style PathStyle = |
| 79 | has_root_name(path: SP.getDirectory(), style: sys::path::Style::windows_backslash) || |
| 80 | SP.getDirectory().contains(Other: "\\" ) || |
| 81 | SP.getFilename().contains(Other: "\\" ) |
| 82 | ? sys::path::Style::windows_backslash |
| 83 | : sys::path::Style::posix; |
| 84 | // Best effort path normalization. This is to guarantee an unique flag symbol |
| 85 | // is produced for the same directory. Some builds may want to use relative |
| 86 | // paths, or paths with a specific prefix (see the -fdebug-compilation-dir |
| 87 | // flag), so only hash paths in debuginfo. Don't expand them to absolute |
| 88 | // paths. |
| 89 | SmallString<256> FilePath(SP.getDirectory()); |
| 90 | sys::path::append(path&: FilePath, style: PathStyle, a: SP.getFilename()); |
| 91 | sys::path::native(path&: FilePath, style: PathStyle); |
| 92 | sys::path::remove_dots(path&: FilePath, /*remove_dot_dot=*/true, style: PathStyle); |
| 93 | |
| 94 | // The naming convention for the flag name is __<hash>_<file name> with '.' in |
| 95 | // <file name> replaced with '@'. For example C:\file.any.c would have a flag |
| 96 | // __D032E919_file@any@c. The naming convention match MSVC's format however |
| 97 | // the match is not required to make JMC work. The hashing function used here |
| 98 | // is different from MSVC's. |
| 99 | |
| 100 | std::string Suffix; |
| 101 | for (auto C : sys::path::filename(path: FilePath, style: PathStyle)) |
| 102 | Suffix.push_back(c: C == '.' ? '@' : C); |
| 103 | |
| 104 | sys::path::remove_filename(path&: FilePath, style: PathStyle); |
| 105 | return (UseX86FastCall ? "_" : "__" ) + |
| 106 | utohexstr(X: djbHash(Buffer: FilePath), /*LowerCase=*/false, |
| 107 | /*Width=*/8) + |
| 108 | "_" + Suffix; |
| 109 | } |
| 110 | |
| 111 | void attachDebugInfo(GlobalVariable &GV, DISubprogram &SP) { |
| 112 | Module &M = *GV.getParent(); |
| 113 | DICompileUnit *CU = SP.getUnit(); |
| 114 | assert(CU); |
| 115 | DIBuilder DB(M, false, CU); |
| 116 | |
| 117 | auto *DType = |
| 118 | DB.createBasicType(Name: "unsigned char" , SizeInBits: 8, Encoding: dwarf::DW_ATE_unsigned_char, |
| 119 | Flags: llvm::DINode::FlagArtificial); |
| 120 | |
| 121 | auto *DGVE = DB.createGlobalVariableExpression( |
| 122 | Context: CU, Name: GV.getName(), /*LinkageName=*/StringRef(), File: SP.getFile(), |
| 123 | /*LineNo=*/0, Ty: DType, /*IsLocalToUnit=*/true, /*IsDefined=*/isDefined: true); |
| 124 | GV.addMetadata(KindID: LLVMContext::MD_dbg, MD&: *DGVE); |
| 125 | DB.finalize(); |
| 126 | } |
| 127 | |
| 128 | FunctionType *getCheckFunctionType(LLVMContext &Ctx) { |
| 129 | Type *VoidTy = Type::getVoidTy(C&: Ctx); |
| 130 | PointerType *VoidPtrTy = PointerType::getUnqual(C&: Ctx); |
| 131 | return FunctionType::get(Result: VoidTy, Params: VoidPtrTy, isVarArg: false); |
| 132 | } |
| 133 | |
| 134 | Function *createDefaultCheckFunction(Module &M, bool UseX86FastCall) { |
| 135 | LLVMContext &Ctx = M.getContext(); |
| 136 | const char *DefaultCheckFunctionName = |
| 137 | UseX86FastCall ? "_JustMyCode_Default" : "__JustMyCode_Default" ; |
| 138 | // Create the function. |
| 139 | Function *DefaultCheckFunc = |
| 140 | Function::Create(Ty: getCheckFunctionType(Ctx), Linkage: GlobalValue::ExternalLinkage, |
| 141 | N: DefaultCheckFunctionName, M: &M); |
| 142 | DefaultCheckFunc->setUnnamedAddr(GlobalValue::UnnamedAddr::Global); |
| 143 | DefaultCheckFunc->addParamAttr(ArgNo: 0, Kind: Attribute::NoUndef); |
| 144 | if (UseX86FastCall) |
| 145 | DefaultCheckFunc->addParamAttr(ArgNo: 0, Kind: Attribute::InReg); |
| 146 | |
| 147 | BasicBlock *EntryBB = BasicBlock::Create(Context&: Ctx, Name: "" , Parent: DefaultCheckFunc); |
| 148 | ReturnInst::Create(C&: Ctx, InsertAtEnd: EntryBB); |
| 149 | return DefaultCheckFunc; |
| 150 | } |
| 151 | } // namespace |
| 152 | |
| 153 | bool runImpl(Module &M) { |
| 154 | bool Changed = false; |
| 155 | LLVMContext &Ctx = M.getContext(); |
| 156 | Triple ModuleTriple(M.getTargetTriple()); |
| 157 | bool IsMSVC = ModuleTriple.isKnownWindowsMSVCEnvironment(); |
| 158 | bool IsELF = ModuleTriple.isOSBinFormatELF(); |
| 159 | assert((IsELF || IsMSVC) && "Unsupported triple for JMC" ); |
| 160 | bool UseX86FastCall = IsMSVC && ModuleTriple.getArch() == Triple::x86; |
| 161 | const char *const FlagSymbolSection = IsELF ? ".data.just.my.code" : ".msvcjmc" ; |
| 162 | |
| 163 | GlobalValue *CheckFunction = nullptr; |
| 164 | DenseMap<DISubprogram *, Constant *> SavedFlags(8); |
| 165 | for (auto &F : M) { |
| 166 | if (F.isDeclaration()) |
| 167 | continue; |
| 168 | auto *SP = F.getSubprogram(); |
| 169 | if (!SP) |
| 170 | continue; |
| 171 | |
| 172 | Constant *&Flag = SavedFlags[SP]; |
| 173 | if (!Flag) { |
| 174 | std::string FlagName = getFlagName(SP&: *SP, UseX86FastCall); |
| 175 | IntegerType *FlagTy = Type::getInt8Ty(C&: Ctx); |
| 176 | Flag = M.getOrInsertGlobal(Name: FlagName, Ty: FlagTy, CreateGlobalCallback: [&] { |
| 177 | // FIXME: Put the GV in comdat and have linkonce_odr linkage to save |
| 178 | // .msvcjmc section space? maybe not worth it. |
| 179 | GlobalVariable *GV = new GlobalVariable( |
| 180 | M, FlagTy, /*isConstant=*/false, GlobalValue::InternalLinkage, |
| 181 | ConstantInt::get(Ty: FlagTy, V: 1), FlagName); |
| 182 | GV->setSection(FlagSymbolSection); |
| 183 | GV->setAlignment(Align(1)); |
| 184 | GV->setUnnamedAddr(GlobalValue::UnnamedAddr::Global); |
| 185 | attachDebugInfo(GV&: *GV, SP&: *SP); |
| 186 | return GV; |
| 187 | }); |
| 188 | } |
| 189 | |
| 190 | if (!CheckFunction) { |
| 191 | Function *DefaultCheckFunc = |
| 192 | createDefaultCheckFunction(M, UseX86FastCall); |
| 193 | if (IsELF) { |
| 194 | DefaultCheckFunc->setName(CheckFunctionName); |
| 195 | DefaultCheckFunc->setLinkage(GlobalValue::WeakAnyLinkage); |
| 196 | CheckFunction = DefaultCheckFunc; |
| 197 | } else { |
| 198 | assert(!M.getFunction(CheckFunctionName) && |
| 199 | "JMC instrument more than once?" ); |
| 200 | auto *CheckFunc = cast<Function>( |
| 201 | Val: M.getOrInsertFunction(Name: CheckFunctionName, T: getCheckFunctionType(Ctx)) |
| 202 | .getCallee()); |
| 203 | CheckFunc->setUnnamedAddr(GlobalValue::UnnamedAddr::Global); |
| 204 | CheckFunc->addParamAttr(ArgNo: 0, Kind: Attribute::NoUndef); |
| 205 | if (UseX86FastCall) { |
| 206 | CheckFunc->setCallingConv(CallingConv::X86_FastCall); |
| 207 | CheckFunc->addParamAttr(ArgNo: 0, Kind: Attribute::InReg); |
| 208 | } |
| 209 | CheckFunction = CheckFunc; |
| 210 | |
| 211 | StringRef DefaultCheckFunctionName = DefaultCheckFunc->getName(); |
| 212 | appendToUsed(M, Values: {DefaultCheckFunc}); |
| 213 | Comdat *C = M.getOrInsertComdat(Name: DefaultCheckFunctionName); |
| 214 | C->setSelectionKind(Comdat::Any); |
| 215 | DefaultCheckFunc->setComdat(C); |
| 216 | // Add a linker option /alternatename to set the default implementation |
| 217 | // for the check function. |
| 218 | // https://devblogs.microsoft.com/oldnewthing/20200731-00/?p=104024 |
| 219 | std::string AltOption = std::string("/alternatename:" ) + |
| 220 | CheckFunctionName + "=" + |
| 221 | DefaultCheckFunctionName.str(); |
| 222 | llvm::Metadata *Ops[] = {llvm::MDString::get(Context&: Ctx, Str: AltOption)}; |
| 223 | MDTuple *N = MDNode::get(Context&: Ctx, MDs: Ops); |
| 224 | M.getOrInsertNamedMetadata(Name: "llvm.linker.options" )->addOperand(M: N); |
| 225 | } |
| 226 | } |
| 227 | // FIXME: it would be nice to make CI scheduling boundary, although in |
| 228 | // practice it does not matter much. |
| 229 | auto *CI = CallInst::Create(Ty: getCheckFunctionType(Ctx), Func: CheckFunction, |
| 230 | Args: {Flag}, NameStr: "" , InsertBefore: F.begin()->getFirstInsertionPt()); |
| 231 | CI->addParamAttr(ArgNo: 0, Kind: Attribute::NoUndef); |
| 232 | if (UseX86FastCall) { |
| 233 | CI->setCallingConv(CallingConv::X86_FastCall); |
| 234 | CI->addParamAttr(ArgNo: 0, Kind: Attribute::InReg); |
| 235 | } |
| 236 | |
| 237 | Changed = true; |
| 238 | } |
| 239 | return Changed; |
| 240 | } |
| 241 | |