207 changes: 207 additions & 0 deletions llvm/lib/CodeGen/JMCInstrumenter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
//===- 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:
// - add "/alternatename:__CheckForDebuggerJustMyCode=__JustMyCode_Default" to
// "llvm.linker.options"
// - create the dummy COMDAT function __JustMyCode_Default
// - instrument each function with a call to __CheckForDebuggerJustMyCode. The
// sole argument should be defined in .msvcjmc. Each flag is 1 byte initilized
// to 1.
// - (TODO) currently targeting MSVC, adds ELF debuggers support
//
//===----------------------------------------------------------------------===//

#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) {
// 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, SP.getFilename());
sys::path::native(FilePath);
sys::path::remove_dots(FilePath, /*remove_dot_dot=*/true);

// 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))
Suffix.push_back(C == '.' ? '@' : C);

sys::path::remove_filename(FilePath);
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);
}

void 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);
appendToUsed(M, {DefaultCheckFunc});
Comdat *C = M.getOrInsertComdat(DefaultCheckFunctionName);
C->setSelectionKind(Comdat::Any);
DefaultCheckFunc->setComdat(C);
BasicBlock *EntryBB = BasicBlock::Create(Ctx, "", DefaultCheckFunc);
ReturnInst::Create(Ctx, EntryBB);

// 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;
llvm::Metadata *Ops[] = {llvm::MDString::get(Ctx, AltOption)};
MDTuple *N = MDNode::get(Ctx, Ops);
M.getOrInsertNamedMetadata("llvm.linker.options")->addOperand(N);
}
} // namespace

bool JMCInstrumenter::runOnModule(Module &M) {
bool Changed = false;
LLVMContext &Ctx = M.getContext();
Triple ModuleTriple(M.getTargetTriple());
bool UseX86FastCall =
ModuleTriple.isOSWindows() && ModuleTriple.getArch() == Triple::x86;

Function *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(".msvcjmc");
GV->setAlignment(Align(1));
GV->setUnnamedAddr(GlobalValue::UnnamedAddr::Global);
attachDebugInfo(*GV, *SP);
return GV;
});
}

if (!CheckFunction) {
assert(!M.getFunction(CheckFunctionName) &&
"JMC instrument more than once?");
CheckFunction = cast<Function>(
M.getOrInsertFunction(CheckFunctionName, getCheckFunctionType(Ctx))
.getCallee());
CheckFunction->setUnnamedAddr(GlobalValue::UnnamedAddr::Global);
CheckFunction->addParamAttr(0, Attribute::NoUndef);
if (UseX86FastCall) {
CheckFunction->setCallingConv(CallingConv::X86_FastCall);
CheckFunction->addParamAttr(0, Attribute::InReg);
}
}
// FIXME: it would be nice to make CI scheduling boundary, although in
// practice it does not matter much.
auto *CI = CallInst::Create(CheckFunction, {Flag}, "",
&*F.begin()->getFirstInsertionPt());
CI->addParamAttr(0, Attribute::NoUndef);
if (UseX86FastCall) {
CI->setCallingConv(CallingConv::X86_FastCall);
CI->addParamAttr(0, Attribute::InReg);
}

Changed = true;
}
if (!Changed)
return false;

createDefaultCheckFunction(M, UseX86FastCall);
return true;
}
3 changes: 3 additions & 0 deletions llvm/lib/Target/AArch64/AArch64TargetMachine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,9 @@ void AArch64PassConfig::addIRPasses() {
// Add Control Flow Guard checks.
if (TM->getTargetTriple().isOSWindows())
addPass(createCFGuardCheckPass());

if (TM->Options.JMCInstrument)
addPass(createJMCInstrumenterPass());
}

// Pass Pipeline Configuration
Expand Down
3 changes: 3 additions & 0 deletions llvm/lib/Target/ARM/ARMTargetMachine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,9 @@ void ARMPassConfig::addIRPasses() {
// Add Control Flow Guard checks.
if (TM->getTargetTriple().isOSWindows())
addPass(createCFGuardCheckPass());

if (TM->Options.JMCInstrument)
addPass(createJMCInstrumenterPass());
}

void ARMPassConfig::addCodeGenPrepare() {
Expand Down
3 changes: 3 additions & 0 deletions llvm/lib/Target/X86/X86TargetMachine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,9 @@ void X86PassConfig::addIRPasses() {
addPass(createCFGuardCheckPass());
}
}

if (TM->Options.JMCInstrument)
addPass(createJMCInstrumenterPass());
}

bool X86PassConfig::addInstSelector() {
Expand Down
53 changes: 53 additions & 0 deletions llvm/test/Instrumentation/JustMyCode/jmc-instrument-x86.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
; RUN: opt -jmc-instrument -S < %s | FileCheck %s

; CHECK: $_JustMyCode_Default = comdat any

; CHECK: @"_A85D9D03_x@c" = internal unnamed_addr global i8 1, section ".msvcjmc", align 1, !dbg !0
; CHECK: @llvm.used = appending global [1 x i8*] [i8* bitcast (void (i8*)* @_JustMyCode_Default to i8*)], section "llvm.metadata"

; CHECK: define void @w1() #0 !dbg !10 {
; CHECK: call x86_fastcallcc void @__CheckForDebuggerJustMyCode(i8* inreg noundef @"_A85D9D03_x@c")
; CHECK: ret void
; CHECK: }

; CHECK: declare x86_fastcallcc void @__CheckForDebuggerJustMyCode(i8* inreg noundef) unnamed_addr

; CHECK: define void @_JustMyCode_Default(i8* inreg noundef %0) unnamed_addr comdat {
; CHECK: ret void
; CHECK: }

; CHECK: !0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
; CHECK: !1 = distinct !DIGlobalVariable(name: "_A85D9D03_x@c", scope: !2, file: !3, type: !5, isLocal: true, isDefinition: true)
; CHECK: !2 = distinct !DICompileUnit(language: DW_LANG_C99, file: !3, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None)
; CHECK: !3 = !DIFile(filename: "./b/./../b/x.c", directory: "C:\\\\a\\\\")
; CHECK: !4 = !{!0}
; CHECK: !5 = !DIBasicType(name: "unsigned char", size: 8, encoding: DW_ATE_unsigned_char, flags: DIFlagArtificial)
; CHECK: !6 = !{i32 2, !"CodeView", i32 1}
; CHECK: !7 = !{i32 2, !"Debug Info Version", i32 3}
; CHECK: !8 = !{!"clang"}
; CHECK: !9 = !{!"/alternatename:__CheckForDebuggerJustMyCode=_JustMyCode_Default"}
; CHECK: !10 = distinct !DISubprogram(name: "w1", scope: !3, file: !3, line: 1, type: !11, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !13)
; CHECK: !11 = !DISubroutineType(types: !12)

target datalayout = "e-m:x-p:32:32-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32-a:0:32-S32"
target triple = "i386-pc-windows-msvc"

define void @w1() #0 !dbg !10 {
ret void
}

attributes #0 = { "target-cpu"="pentium4" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }

!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!7, !8}
!llvm.ident = !{!9}

!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
!1 = !DIFile(filename: "./b/./../b/x.c", directory: "C:\\\\a\\\\")
!7 = !{i32 2, !"CodeView", i32 1}
!8 = !{i32 2, !"Debug Info Version", i32 3}
!9 = !{!"clang"}
!10 = distinct !DISubprogram(name: "w1", scope: !1, file: !1, line: 1, type: !31, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !33)
!31 = !DISubroutineType(types: !32)
!32 = !{null}
!33 = !{}
120 changes: 120 additions & 0 deletions llvm/test/Instrumentation/JustMyCode/jmc-instrument.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
; RUN: opt -jmc-instrument -mtriple=x86_64-pc-windows-msvc -S < %s | FileCheck %s
; RUN: opt -jmc-instrument -mtriple=aarch64-pc-windows-msvc -S < %s | FileCheck %s
; RUN: opt -jmc-instrument -mtriple=arm-pc-windows-msvc -S < %s | FileCheck %s

; CHECK: $__JustMyCode_Default = comdat any

; CHECK: @"__7DF23CF5_x@c" = internal unnamed_addr global i8 1, section ".msvcjmc", align 1, !dbg !0
; CHECK: @"__A85D9D03_x@c" = internal unnamed_addr global i8 1, section ".msvcjmc", align 1, !dbg !5
; CHECK: @llvm.used = appending global [1 x i8*] [i8* bitcast (void (i8*)* @__JustMyCode_Default to i8*)], section "llvm.metadata"

; CHECK: define void @l1() !dbg !13 {
; CHECK: call void @__CheckForDebuggerJustMyCode(i8* noundef @"__7DF23CF5_x@c")
; CHECK: ret void
; CHECK: }

; CHECK: define void @l2() !dbg !17 {
; CHECK: call void @__CheckForDebuggerJustMyCode(i8* noundef @"__7DF23CF5_x@c")
; CHECK: ret void
; CHECK: }

; CHECK: define void @w1() !dbg !19 {
; CHECK: call void @__CheckForDebuggerJustMyCode(i8* noundef @"__A85D9D03_x@c")
; CHECK: ret void
; CHECK: }

; CHECK: define void @w2() !dbg !20 {
; CHECK: call void @__CheckForDebuggerJustMyCode(i8* noundef @"__A85D9D03_x@c")
; CHECK: ret void
; CHECK: }

; CHECK: define void @w3() !dbg !22 {
; CHECK: call void @__CheckForDebuggerJustMyCode(i8* noundef @"__A85D9D03_x@c")
; CHECK: ret void
; CHECK: }

; CHECK: define void @w4() !dbg !24 {
; CHECK: call void @__CheckForDebuggerJustMyCode(i8* noundef @"__A85D9D03_x@c")
; CHECK: ret void
; CHECK: }

; CHECK: declare void @__CheckForDebuggerJustMyCode(i8* noundef) unnamed_addr

; CHECK: define void @__JustMyCode_Default(i8* noundef %0) unnamed_addr comdat {
; CHECK: ret void
; CHECK: }

; CHECK: !llvm.linker.options = !{!12}

; CHECK: !0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
; CHECK: !1 = distinct !DIGlobalVariable(name: "__7DF23CF5_x@c", scope: !2, file: !3, type: !8, isLocal: true, isDefinition: true)
; CHECK: !2 = distinct !DICompileUnit(language: DW_LANG_C99, file: !3, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None)
; CHECK: !3 = !DIFile(filename: "a/x.c", directory: "/tmp")
; CHECK: !4 = !{!0, !5}
; CHECK: !5 = !DIGlobalVariableExpression(var: !6, expr: !DIExpression())
; CHECK: !6 = distinct !DIGlobalVariable(name: "__A85D9D03_x@c", scope: !2, file: !7, type: !8, isLocal: true, isDefinition: true)
; CHECK: !7 = !DIFile(filename: "./x.c", directory: "C:\\\\a\\\\b\\\\")
; CHECK: !8 = !DIBasicType(name: "unsigned char", size: 8, encoding: DW_ATE_unsigned_char, flags: DIFlagArtificial)
; CHECK: !9 = !{i32 2, !"CodeView", i32 1}
; CHECK: !10 = !{i32 2, !"Debug Info Version", i32 3}
; CHECK: !11 = !{!"clang"}
; CHECK: !12 = !{!"/alternatename:__CheckForDebuggerJustMyCode=__JustMyCode_Default"}
; CHECK: !13 = distinct !DISubprogram(name: "f", scope: !3, file: !3, line: 1, type: !14, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !16)
; CHECK: !14 = !DISubroutineType(types: !15)
; CHECK: !15 = !{null}
; CHECK: !16 = !{}
; CHECK: !17 = distinct !DISubprogram(name: "f", scope: !18, file: !18, line: 1, type: !14, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !16)
; CHECK: !18 = !DIFile(filename: "x.c", directory: "/tmp/a")
; CHECK: !19 = distinct !DISubprogram(name: "f", scope: !7, file: !7, line: 1, type: !14, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !16)
; CHECK: !20 = distinct !DISubprogram(name: "f", scope: !21, file: !21, line: 1, type: !14, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !16)
; CHECK: !21 = !DIFile(filename: "./b\\x.c", directory: "C:\\\\a\\\\")
; CHECK: !22 = distinct !DISubprogram(name: "f", scope: !23, file: !23, line: 1, type: !14, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !16)
; CHECK: !23 = !DIFile(filename: "./b/x.c", directory: "C:\\\\a\\\\")
; CHECK: !24 = distinct !DISubprogram(name: "f", scope: !25, file: !25, line: 1, type: !14, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !2, retainedNodes: !16)
; CHECK: !25 = !DIFile(filename: "./b/./../b/x.c", directory: "C:\\\\a")

; All use the same flag
define void @l1() !dbg !10 {
ret void
}
define void @l2() !dbg !11 {
ret void
}

; All use the same flag
define void @w1() !dbg !12 {
ret void
}
define void @w2() !dbg !13 {
ret void
}
define void @w3() !dbg !14 {
ret void
}
define void @w4() !dbg !15 {
ret void
}

!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!7, !8}
!llvm.ident = !{!9}

!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
!1 = !DIFile(filename: "a/x.c", directory: "/tmp")
!2 = !DIFile(filename: "x.c", directory: "/tmp/a")
!3 = !DIFile(filename: "./x.c", directory: "C:\\\\a\\\\b\\\\")
!4 = !DIFile(filename: "./b\\x.c", directory: "C:\\\\a\\\\")
!5 = !DIFile(filename: "./b/x.c", directory: "C:\\\\a\\\\")
!6 = !DIFile(filename: "./b/./../b/x.c", directory: "C:\\\\a")
!7 = !{i32 2, !"CodeView", i32 1}
!8 = !{i32 2, !"Debug Info Version", i32 3}
!9 = !{!"clang"}
!10 = distinct !DISubprogram(name: "f", scope: !1, file: !1, line: 1, type: !31, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !33)
!11 = distinct !DISubprogram(name: "f", scope: !2, file: !2, line: 1, type: !31, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !33)
!12 = distinct !DISubprogram(name: "f", scope: !3, file: !3, line: 1, type: !31, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !33)
!13 = distinct !DISubprogram(name: "f", scope: !4, file: !4, line: 1, type: !31, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !33)
!14 = distinct !DISubprogram(name: "f", scope: !5, file: !5, line: 1, type: !31, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !33)
!15 = distinct !DISubprogram(name: "f", scope: !6, file: !6, line: 1, type: !31, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !33)
!31 = !DISubroutineType(types: !32)
!32 = !{null}
!33 = !{}
4 changes: 3 additions & 1 deletion llvm/tools/opt/opt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,8 @@ static bool shouldPinPassToLegacyPM(StringRef Pass) {
"generic-to-nvvm", "expandmemcmp",
"loop-reduce", "lower-amx-type",
"pre-amx-config", "lower-amx-intrinsics",
"polyhedral-info", "replace-with-veclib"};
"polyhedral-info", "replace-with-veclib",
"jmc-instrument"};
for (const auto &P : PassNamePrefix)
if (Pass.startswith(P))
return true;
Expand Down Expand Up @@ -572,6 +573,7 @@ int main(int argc, char **argv) {
initializeHardwareLoopsPass(Registry);
initializeTypePromotionPass(Registry);
initializeReplaceWithVeclibLegacyPass(Registry);
initializeJMCInstrumenterPass(Registry);

#ifdef BUILD_EXAMPLES
initializeExampleIRTransforms(Registry);
Expand Down