Skip to content
30 changes: 29 additions & 1 deletion compiler-rt/lib/ubsan/ubsan_handlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,32 @@ void __ubsan::__ubsan_handle_pointer_overflow_abort(PointerOverflowData *Data,
Die();
}

// Returns true if this is an artificial debug location created by the
// LowerTypeTests pass (see createJumpTableDebugInfo in LLVM).
static bool isArtificialStack(const SymbolizedStack *S) {
static constexpr char kSuffix[] = "ubsan_interface.h";
if (!S || !S->info.function || !S->info.file)
return false;
const char *File = S->info.file;
uptr FileLen = internal_strlen(File);
uptr SuffixLen = internal_strlen(kSuffix);
if (FileLen < SuffixLen)
return false;
return internal_strcmp(File + FileLen - SuffixLen, kSuffix) == 0;
}

// Stripping the file name from artificial frames forces the UBSan Diag
// to fall back to module names. This preserves the original behavior
// of showing the module, while still allowing the symbolizer
// to include the helpful (.cfi_jt) function suffix.
static SymbolizedStack *removeArtificialFiles(SymbolizedStack *FS) {
for (SymbolizedStack *S = FS; S; S = S->next) {
if (isArtificialStack(S))
S->info.file = nullptr;
}
return FS;
}

static void handleCFIBadIcall(CFICheckFailData *Data, ValueHandle Function,
ReportOptions Opts) {
if (Data->CheckKind != CFITCK_ICall && Data->CheckKind != CFITCK_NVMFCall)
Expand All @@ -875,7 +901,9 @@ static void handleCFIBadIcall(CFICheckFailData *Data, ValueHandle Function,
"control flow integrity check for type %0 failed during %1")
<< Data->Type << CheckKindStr;

SymbolizedStackHolder FLoc(getSymbolizedLocation(Function));
SymbolizedStackHolder FLoc(
removeArtificialFiles(getSymbolizedLocation(Function)));

const char *FName = FLoc.get()->info.function;
if (!FName)
FName = "(unknown)";
Expand Down
2 changes: 1 addition & 1 deletion compiler-rt/test/cfi/cross-dso/icall/diag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ int main(int argc, char *argv[]) {
void *p;
if (argv[1][0] == 'i') {
// ICALL-DIAG: runtime error: control flow integrity check for type 'void *(int)' failed during indirect function call
// ICALL-DIAG-NEXT: dynamic.so+0x{{[[:xdigit:]]+}}): note: create_B() defined here
// ICALL-DIAG-NEXT: dynamic.so+0x{{[[:xdigit:]]+}}): note: create_B() {{(\(.cfi_jt\) )?}}defined here
// ICALL-NODIAG-NOT: runtime error: control flow integrity check {{.*}} during indirect function call
p = ((void *(*)(int))create_B)(42);
} else {
Expand Down
2 changes: 1 addition & 1 deletion compiler-rt/test/cfi/cross-dso/icall/icall-from-dso.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ void f() {
// CHECK: =2=
fprintf(stderr, "=2=\n");
// CFI-DIAG: runtime error: control flow integrity check for type 'void (int)' failed during indirect function call
// CFI-DIAG-NEXT: ({{.*}}exe+0x{{[[:xdigit:]]+}}): note: g() defined here
// CFI-DIAG-NEXT: ({{.*}}exe+0x{{[[:xdigit:]]+}}): note: g() {{(\(.cfi_jt\) )?}}defined here
((void (*)(int))g)(42); // UB here
// CHECK-DIAG: =3=
// CHECK-NOT: =3=
Expand Down
2 changes: 1 addition & 1 deletion compiler-rt/test/cfi/cross-dso/icall/icall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ int main() {
// CHECK: =2=
fprintf(stderr, "=2=\n");
// CFI-DIAG: runtime error: control flow integrity check for type 'void (int)' failed during indirect function call
// CFI-DIAG-NEXT: ({{.*}}dynamic.so+0x{{[[:xdigit:]]+}}): note: f() defined here
// CFI-DIAG-NEXT: ({{.*}}dynamic.so+0x{{[[:xdigit:]]+}}): note: f() {{(\(.cfi_jt\) )?}}defined here
((void (*)(int))f)(42); // UB here
// CHECK-DIAG: =3=
// CHECK-NOT: =3=
Expand Down
4 changes: 2 additions & 2 deletions compiler-rt/test/cfi/mfcall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ int main(int argc, char **argv) {
switch (argv[1][0]) {
case 'a':
// A: runtime error: control flow integrity check for type 'int (S::*)()' failed during non-virtual pointer to member function call
// A: note: S::f1() defined here
// A: note: S::f1() {{(\(.cfi_jt\) )?}}defined here
(s.*bitcast<S_int>(&S::f1))();
break;
case 'b':
// B: runtime error: control flow integrity check for type 'int (T::*)()' failed during non-virtual pointer to member function call
// B: note: S::f2() defined here
// B: note: S::f2() {{(\(.cfi_jt\) )?}}defined here
(t.*bitcast<T_int>(&S::f2))();
break;
case 'c':
Expand Down
73 changes: 69 additions & 4 deletions llvm/lib/Transforms/IPO/LowerTypeTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Constant.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DIBuilder.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/Function.h"
Expand Down Expand Up @@ -123,6 +124,11 @@ static cl::opt<std::string> ClWriteSummary(
cl::desc("Write summary to given YAML file after running pass"),
cl::Hidden);

// FIXME: Remove in clang 24.
static cl::opt<bool> EnableJumpTableDebugInfo(
"lowertypetests-jump-table-debug-info", cl::init(true), cl::Hidden,
cl::desc("Enable debug info generation for jump tables"));

bool BitSetInfo::containsGlobalOffset(uint64_t Offset) const {
if (Offset < ByteOffset)
return false;
Expand Down Expand Up @@ -1523,25 +1529,84 @@ Triple::ArchType LowerTypeTestsModule::selectJumpTableArmEncoding(
return ArmCount > ThumbCount ? Triple::arm : Triple::thumb;
}

// Create location for each function entry which should look like this:
// frame #0: c::c() (.cfi_jt) at sanitizer/ubsan_interface.h:0:0
// frame #1: __ubsan_check_cfi_icall_jt at sanitizer/ubsan_interface.h:0
static SmallVector<DILocation *>
createJumpTableDebugInfo(Function *F, ArrayRef<GlobalTypeMember *> Functions) {
Module &M = *F->getParent();
DICompileUnit *CU = nullptr;
auto CUs = M.debug_compile_units();
if (!CUs.empty())
CU = *CUs.begin();
Comment thread
vitalybuka marked this conversation as resolved.

DIBuilder DIB(M, /*AllowUnresolved=*/true, CU);
DIFile *File = DIB.createFile("ubsan_interface.h", "sanitizer");
if (!CU) {
// Synthetic module (like ld-temp.o), it frequently lacks a DICompileUnit
// even if the rest of the program has debug info.
CU = DIB.createCompileUnit(
DISourceLanguageName(dwarf::DW_LANG_C), File, "llvm", true, "", 0, "",
DICompileUnit::DebugEmissionKind::LineTablesOnly);
}

DISubroutineType *DIFnTy = DIB.createSubroutineType(nullptr);

DISubprogram *UbsanSP = DIB.createFunction(
CU, "__ubsan_check_cfi_icall_jt", {}, File, 0, DIFnTy, 0,
DINode::FlagArtificial, DISubprogram::SPFlagDefinition);

F->setSubprogram(UbsanSP);

DILocation *UbsanLoc = DILocation::get(M.getContext(), 0, 0, UbsanSP);

SmallVector<DILocation *> Locations;
Locations.reserve(Functions.size());

for (auto *Func : Functions) {
StringRef FuncName = Func->getGlobal()->getName();
FuncName.consume_back(".cfi");
DISubprogram *JumpSP = DIB.createFunction(
CU, (FuncName + ".cfi_jt").str(), {}, File, 0, DIFnTy, 0,
DINode::FlagArtificial, DISubprogram::SPFlagDefinition);

DILocation *EntryLoc =
DILocation::get(M.getContext(), 0, 0, JumpSP, UbsanLoc);

Locations.push_back(EntryLoc);
}

DIB.finalize();

return Locations;
}

void LowerTypeTestsModule::createJumpTable(
Function *F, ArrayRef<GlobalTypeMember *> Functions,
Triple::ArchType JumpTableArch) {
BasicBlock *BB = BasicBlock::Create(M.getContext(), "entry", F);
IRBuilder<> IRB(BB);

SmallVector<DILocation *> Locations;
if (M.getDwarfVersion() != 0 && EnableJumpTableDebugInfo)
Locations = createJumpTableDebugInfo(F, Functions);
Comment thread
vitalybuka marked this conversation as resolved.

InlineAsm *JumpTableAsm = createJumpTableEntryAsm(JumpTableArch);

// Check if all entries have the NoUnwind attribute.
// If all entries have it, we can safely mark the
// cfi.jumptable as NoUnwind, otherwise, direct calls
// to the jump table will not handle exceptions properly
bool areAllEntriesNounwind = true;
for (GlobalTypeMember *GTM : Functions) {
if (!llvm::cast<llvm::Function>(GTM->getGlobal())
->hasFnAttribute(llvm::Attribute::NoUnwind)) {
assert(Locations.empty() || Functions.size() == Locations.size());
for (auto [GTM, Loc] : zip_longest(Functions, Locations)) {
if (Loc.has_value())
IRB.SetCurrentDebugLocation(*Loc);
if (!cast<Function>((*GTM)->getGlobal())
->hasFnAttribute(Attribute::NoUnwind)) {
areAllEntriesNounwind = false;
}
IRB.CreateCall(JumpTableAsm, GTM->getGlobal());
IRB.CreateCall(JumpTableAsm, (*GTM)->getGlobal());
}
IRB.CreateUnreachable();

Expand Down
19 changes: 14 additions & 5 deletions llvm/test/Transforms/LowerTypeTests/aarch64-jumptable-dbg.ll
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --check-globals --include-generated-funcs
; RUN: opt -S -passes=lowertypetests -mtriple=aarch64-unknown-linux-gnu %s | FileCheck --check-prefixes=AARCH64 %s
; RUN: opt -S -passes=lowertypetests -mtriple=aarch64-unknown-linux-gnu %s | FileCheck --check-prefixes=AARCH64-OFF %s
; RUN: opt -S -passes=lowertypetests -lowertypetests-jump-table-debug-info=0 -mtriple=aarch64-unknown-linux-gnu %s | FileCheck --check-prefixes=AARCH64-OFF %s

; Test for the jump table generation with branch protection on AArch64

Expand Down Expand Up @@ -62,9 +62,9 @@ define i1 @foo(ptr %p) {
; AARCH64: Function Attrs: naked noinline
; AARCH64-LABEL: @.cfi.jumptable(
; AARCH64-NEXT: entry:
; AARCH64-NEXT: call void asm sideeffect "bti c\0Ab $0\0A", "s"(ptr @f.cfi)
; AARCH64-NEXT: call void asm sideeffect "bti c\0Ab $0\0A", "s"(ptr @g.cfi)
; AARCH64-NEXT: unreachable
; AARCH64-NEXT: call void asm sideeffect "bti c\0Ab $0\0A", "s"(ptr @f.cfi), !dbg [[DBG8:![0-9]+]]
; AARCH64-NEXT: call void asm sideeffect "bti c\0Ab $0\0A", "s"(ptr @g.cfi), !dbg [[DBG11:![0-9]+]]
; AARCH64-NEXT: unreachable, !dbg [[DBG11]]
;
;
; AARCH64-OFF-LABEL: @f.cfi(
Expand Down Expand Up @@ -102,7 +102,16 @@ define i1 @foo(ptr %p) {
; AARCH64: [[META0:![0-9]+]] = !{i32 4, !"branch-target-enforcement", i32 1}
; AARCH64: [[META1:![0-9]+]] = !{i32 7, !"Dwarf Version", i32 5}
; AARCH64: [[META2:![0-9]+]] = !{i32 2, !"Debug Info Version", i32 3}
; AARCH64: [[META3:![0-9]+]] = !{i32 0, !"typeid1"}
; AARCH64: [[META3:![0-9]+]] = distinct !DICompileUnit(language: DW_LANG_C, file: [[META4:![0-9]+]], producer: "llvm", isOptimized: true, runtimeVersion: 0, emissionKind: LineTablesOnly)
; AARCH64: [[META4]] = !DIFile(filename: "{{.*}}ubsan_interface.h", directory: {{.*}})
; AARCH64: [[META5:![0-9]+]] = !{i32 0, !"typeid1"}
; AARCH64: [[META6:![0-9]+]] = distinct !DISubprogram(name: "__ubsan_check_cfi_icall_jt", scope: null, file: [[META4]], type: [[META7:![0-9]+]], flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: [[META3]])
; AARCH64: [[META7]] = !DISubroutineType(types: null)
; AARCH64: [[DBG8]] = !DILocation(line: 0, scope: [[META9:![0-9]+]], inlinedAt: [[META10:![0-9]+]])
; AARCH64: [[META9]] = distinct !DISubprogram(name: "f.cfi_jt", scope: null, file: [[META4]], type: [[META7]], flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: [[META3]])
; AARCH64: [[META10]] = !DILocation(line: 0, scope: [[META6]])
; AARCH64: [[DBG11]] = !DILocation(line: 0, scope: [[META12:![0-9]+]], inlinedAt: [[META10]])
; AARCH64: [[META12]] = distinct !DISubprogram(name: "g.cfi_jt", scope: null, file: [[META4]], type: [[META7]], flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: [[META3]])
;.
; AARCH64-OFF: [[META0:![0-9]+]] = !{i32 4, !"branch-target-enforcement", i32 1}
; AARCH64-OFF: [[META1:![0-9]+]] = !{i32 7, !"Dwarf Version", i32 5}
Expand Down
36 changes: 27 additions & 9 deletions llvm/test/Transforms/LowerTypeTests/x86-jumptable-dbg.ll
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
;; Test jump table generation with Indirect Branch Tracking on x86.
; RUN: opt -S -passes=lowertypetests -mtriple=i686 %s | FileCheck --check-prefixes=X86_32 %s
; RUN: opt -S -passes=lowertypetests -mtriple=x86_64 %s | FileCheck --check-prefixes=X86_64 %s
; RUN: opt -S -passes=lowertypetests -mtriple=x86_64 %s | FileCheck --check-prefixes=X86_64-OFF %s
; RUN: opt -S -passes=lowertypetests -lowertypetests-jump-table-debug-info=0 -mtriple=x86_64 %s | FileCheck --check-prefixes=X86_64-OFF %s

@0 = private unnamed_addr constant [2 x ptr] [ptr @f, ptr @g], align 16

Expand Down Expand Up @@ -62,9 +62,9 @@ define i1 @foo(ptr %p) {
;
; X86_32-LABEL: @.cfi.jumptable(
; X86_32-NEXT: entry:
; X86_32-NEXT: call void asm sideeffect "endbr32\0Ajmp ${0:c}@plt\0A.balign 16, 0xcc\0A", "s"(ptr @f.cfi)
; X86_32-NEXT: call void asm sideeffect "endbr32\0Ajmp ${0:c}@plt\0A.balign 16, 0xcc\0A", "s"(ptr @g.cfi)
; X86_32-NEXT: unreachable
; X86_32-NEXT: call void asm sideeffect "endbr32\0Ajmp ${0:c}@plt\0A.balign 16, 0xcc\0A", "s"(ptr @f.cfi), !dbg [[DBG8:![0-9]+]]
; X86_32-NEXT: call void asm sideeffect "endbr32\0Ajmp ${0:c}@plt\0A.balign 16, 0xcc\0A", "s"(ptr @g.cfi), !dbg [[DBG11:![0-9]+]]
; X86_32-NEXT: unreachable, !dbg [[DBG11]]
;
;
; X86_64-LABEL: @f.cfi(
Expand All @@ -85,9 +85,9 @@ define i1 @foo(ptr %p) {
;
; X86_64-LABEL: @.cfi.jumptable(
; X86_64-NEXT: entry:
; X86_64-NEXT: call void asm sideeffect "endbr64\0Ajmp ${0:c}@plt\0A.balign 16, 0xcc\0A", "s"(ptr @f.cfi)
; X86_64-NEXT: call void asm sideeffect "endbr64\0Ajmp ${0:c}@plt\0A.balign 16, 0xcc\0A", "s"(ptr @g.cfi)
; X86_64-NEXT: unreachable
; X86_64-NEXT: call void asm sideeffect "endbr64\0Ajmp ${0:c}@plt\0A.balign 16, 0xcc\0A", "s"(ptr @f.cfi), !dbg [[DBG8:![0-9]+]]
; X86_64-NEXT: call void asm sideeffect "endbr64\0Ajmp ${0:c}@plt\0A.balign 16, 0xcc\0A", "s"(ptr @g.cfi), !dbg [[DBG11:![0-9]+]]
; X86_64-NEXT: unreachable, !dbg [[DBG11]]
;
;
; X86_64-OFF-LABEL: @f.cfi(
Expand Down Expand Up @@ -128,12 +128,30 @@ define i1 @foo(ptr %p) {
; X86_32: [[META0:![0-9]+]] = !{i32 8, !"cf-protection-branch", i32 1}
; X86_32: [[META1:![0-9]+]] = !{i32 7, !"Dwarf Version", i32 5}
; X86_32: [[META2:![0-9]+]] = !{i32 2, !"Debug Info Version", i32 3}
; X86_32: [[META3:![0-9]+]] = !{i32 0, !"typeid1"}
; X86_32: [[META3:![0-9]+]] = distinct !DICompileUnit(language: DW_LANG_C, file: [[META4:![0-9]+]], producer: "llvm", isOptimized: true, runtimeVersion: 0, emissionKind: LineTablesOnly)
; X86_32: [[META4]] = !DIFile(filename: "{{.*}}ubsan_interface.h", directory: {{.*}})
; X86_32: [[META5:![0-9]+]] = !{i32 0, !"typeid1"}
; X86_32: [[META6:![0-9]+]] = distinct !DISubprogram(name: "__ubsan_check_cfi_icall_jt", scope: null, file: [[META4]], type: [[META7:![0-9]+]], flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: [[META3]])
; X86_32: [[META7]] = !DISubroutineType(types: null)
; X86_32: [[DBG8]] = !DILocation(line: 0, scope: [[META9:![0-9]+]], inlinedAt: [[META10:![0-9]+]])
; X86_32: [[META9]] = distinct !DISubprogram(name: "f.cfi_jt", scope: null, file: [[META4]], type: [[META7]], flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: [[META3]])
; X86_32: [[META10]] = !DILocation(line: 0, scope: [[META6]])
; X86_32: [[DBG11]] = !DILocation(line: 0, scope: [[META12:![0-9]+]], inlinedAt: [[META10]])
; X86_32: [[META12]] = distinct !DISubprogram(name: "g.cfi_jt", scope: null, file: [[META4]], type: [[META7]], flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: [[META3]])
;.
; X86_64: [[META0:![0-9]+]] = !{i32 8, !"cf-protection-branch", i32 1}
; X86_64: [[META1:![0-9]+]] = !{i32 7, !"Dwarf Version", i32 5}
; X86_64: [[META2:![0-9]+]] = !{i32 2, !"Debug Info Version", i32 3}
; X86_64: [[META3:![0-9]+]] = !{i32 0, !"typeid1"}
; X86_64: [[META3:![0-9]+]] = distinct !DICompileUnit(language: DW_LANG_C, file: [[META4:![0-9]+]], producer: "llvm", isOptimized: true, runtimeVersion: 0, emissionKind: LineTablesOnly)
; X86_64: [[META4]] = !DIFile(filename: "{{.*}}ubsan_interface.h", directory: {{.*}})
; X86_64: [[META5:![0-9]+]] = !{i32 0, !"typeid1"}
; X86_64: [[META6:![0-9]+]] = distinct !DISubprogram(name: "__ubsan_check_cfi_icall_jt", scope: null, file: [[META4]], type: [[META7:![0-9]+]], flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: [[META3]])
; X86_64: [[META7]] = !DISubroutineType(types: null)
; X86_64: [[DBG8]] = !DILocation(line: 0, scope: [[META9:![0-9]+]], inlinedAt: [[META10:![0-9]+]])
; X86_64: [[META9]] = distinct !DISubprogram(name: "f.cfi_jt", scope: null, file: [[META4]], type: [[META7]], flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: [[META3]])
; X86_64: [[META10]] = !DILocation(line: 0, scope: [[META6]])
; X86_64: [[DBG11]] = !DILocation(line: 0, scope: [[META12:![0-9]+]], inlinedAt: [[META10]])
; X86_64: [[META12]] = distinct !DISubprogram(name: "g.cfi_jt", scope: null, file: [[META4]], type: [[META7]], flags: DIFlagArtificial, spFlags: DISPFlagDefinition, unit: [[META3]])
;.
; X86_64-OFF: [[META0:![0-9]+]] = !{i32 8, !"cf-protection-branch", i32 1}
; X86_64-OFF: [[META1:![0-9]+]] = !{i32 7, !"Dwarf Version", i32 5}
Expand Down