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
41using namespace llvm;
42
43#define DEBUG_TYPE "jmc-instrumenter"
44
45static bool runImpl(Module &M);
46namespace {
47struct 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};
54char JMCInstrumenter::ID = 0;
55} // namespace
56
57PreservedAnalyses JMCInstrumenterPass::run(Module &M, ModuleAnalysisManager &) {
58 bool Changed = runImpl(M);
59 return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all();
60}
61
62INITIALIZE_PASS(
63 JMCInstrumenter, DEBUG_TYPE,
64 "Instrument function entry with call to __CheckForDebuggerJustMyCode",
65 false, false)
66
67ModulePass *llvm::createJMCInstrumenterPass() { return new JMCInstrumenter(); }
68
69namespace {
70const char CheckFunctionName[] = "__CheckForDebuggerJustMyCode";
71
72std::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
111void 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
128FunctionType *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
134Function *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
153bool 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