forked from OSchip/llvm-project
234 lines
9.0 KiB
C++
234 lines
9.0 KiB
C++
//===- 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 ? ".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;
|
|
}
|