Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[BOLT] Gadget scanner: improve handling of unreachable basic blocks #136183

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: users/atrosinenko/bolt-gs-cfi-debug-printing
Choose a base branch
from

Conversation

atrosinenko
Copy link
Contributor

Instead of refusing to analyze an instruction completely, when it is
unreachable according to the CFG reconstructed by BOLT, pessimistically
assume all registers to be unsafe at the start of basic blocks without
any predecessors. Nevertheless, unreachable basic blocks found in
optimized code likely means imprecise CFG reconstruction, thus report a
warning once per basic block without predecessors.

@atrosinenko atrosinenko marked this pull request as ready for review April 17, 2025 19:07
@llvmbot llvmbot added the BOLT label Apr 17, 2025
@llvmbot
Copy link
Member

llvmbot commented Apr 17, 2025

@llvm/pr-subscribers-bolt

Author: Anatoly Trosinenko (atrosinenko)

Changes

Instead of refusing to analyze an instruction completely, when it is
unreachable according to the CFG reconstructed by BOLT, pessimistically
assume all registers to be unsafe at the start of basic blocks without
any predecessors. Nevertheless, unreachable basic blocks found in
optimized code likely means imprecise CFG reconstruction, thus report a
warning once per basic block without predecessors.


Full diff: https://github.com/llvm/llvm-project/pull/136183.diff

3 Files Affected:

  • (modified) bolt/lib/Passes/PAuthGadgetScanner.cpp (+32-14)
  • (modified) bolt/test/binary-analysis/AArch64/gs-pacret-autiasp.s (+1-1)
  • (modified) bolt/test/binary-analysis/AArch64/gs-pauth-calls.s (+57)
diff --git a/bolt/lib/Passes/PAuthGadgetScanner.cpp b/bolt/lib/Passes/PAuthGadgetScanner.cpp
index 2d2126bf05ae1..f998b8fa0f950 100644
--- a/bolt/lib/Passes/PAuthGadgetScanner.cpp
+++ b/bolt/lib/Passes/PAuthGadgetScanner.cpp
@@ -346,6 +346,12 @@ class SrcSafetyAnalysis {
     return S;
   }
 
+  /// Creates a state with all registers marked unsafe (not to be confused
+  /// with empty state).
+  SrcState createUnsafeState() const {
+    return SrcState(NumRegs, RegsToTrackInstsFor.getNumTrackedRegisters());
+  }
+
   BitVector getClobberedRegs(const MCInst &Point) const {
     BitVector Clobbered(NumRegs);
     // Assume a call can clobber all registers, including callee-saved
@@ -585,6 +591,13 @@ class DataflowSrcSafetyAnalysis
     if (BB.isEntryPoint())
       return createEntryState();
 
+    // If a basic block without any predecessors is found in an optimized code,
+    // this likely means that some CFG edges were not detected. Pessimistically
+    // assume all registers to be unsafe before this basic block and warn about
+    // this fact in FunctionAnalysis::findUnsafeUses().
+    if (BB.pred_empty())
+      return createUnsafeState();
+
     return SrcState();
   }
 
@@ -658,12 +671,6 @@ class CFGUnawareSrcSafetyAnalysis : public SrcSafetyAnalysis {
       BC.MIB->removeAnnotation(I.second, StateAnnotationIndex);
   }
 
-  /// Creates a state with all registers marked unsafe (not to be confused
-  /// with empty state).
-  SrcState createUnsafeState() const {
-    return SrcState(NumRegs, RegsToTrackInstsFor.getNumTrackedRegisters());
-  }
-
 public:
   CFGUnawareSrcSafetyAnalysis(BinaryFunction &BF,
                               MCPlusBuilder::AllocatorIdTy AllocId,
@@ -1335,19 +1342,30 @@ void FunctionAnalysis::findUnsafeUses(
     BF.dump();
   });
 
+  if (BF.hasCFG()) {
+    // Warn on basic blocks being unreachable according to BOLT, as this
+    // likely means CFG is imprecise.
+    for (BinaryBasicBlock &BB : BF) {
+      if (!BB.pred_empty() || BB.isEntryPoint())
+        continue;
+      // Arbitrarily attach the report to the first instruction of BB.
+      MCInst *InstToReport = BB.getFirstNonPseudoInstr();
+      if (!InstToReport)
+        continue; // BB has no real instructions
+
+      Reports.push_back(
+          make_generic_report(MCInstReference::get(InstToReport, BF),
+                              "Warning: no predecessor basic blocks detected "
+                              "(possibly incomplete CFG)"));
+    }
+  }
+
   iterateOverInstrs(BF, [&](MCInstReference Inst) {
     if (BC.MIB->isCFI(Inst))
       return;
 
     const SrcState &S = Analysis->getStateBefore(Inst);
-
-    // If non-empty state was never propagated from the entry basic block
-    // to Inst, assume it to be unreachable and report a warning.
-    if (S.empty()) {
-      Reports.push_back(
-          make_generic_report(Inst, "Warning: unreachable instruction found"));
-      return;
-    }
+    assert(!S.empty() && "Instruction has no associated state");
 
     if (auto Report = shouldReportReturnGadget(BC, Inst, S))
       Reports.push_back(*Report);
diff --git a/bolt/test/binary-analysis/AArch64/gs-pacret-autiasp.s b/bolt/test/binary-analysis/AArch64/gs-pacret-autiasp.s
index 2193d40131478..a8cc6352de438 100644
--- a/bolt/test/binary-analysis/AArch64/gs-pacret-autiasp.s
+++ b/bolt/test/binary-analysis/AArch64/gs-pacret-autiasp.s
@@ -215,7 +215,7 @@ f_callclobbered_calleesaved:
         .globl  f_unreachable_instruction
         .type   f_unreachable_instruction,@function
 f_unreachable_instruction:
-// CHECK-LABEL: GS-PAUTH: Warning: unreachable instruction found in function f_unreachable_instruction, basic block {{[0-9a-zA-Z.]+}}, at address
+// CHECK-LABEL: GS-PAUTH: Warning: no predecessor basic blocks detected (possibly incomplete CFG) in function f_unreachable_instruction, basic block {{[0-9a-zA-Z.]+}}, at address
 // CHECK-NEXT:    The instruction is     {{[0-9a-f]+}}:       add     x0, x1, x2
         b       1f
         add     x0, x1, x2
diff --git a/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s b/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s
index c79c5926a05cd..c20b47ca93e03 100644
--- a/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s
+++ b/bolt/test/binary-analysis/AArch64/gs-pauth-calls.s
@@ -1428,6 +1428,63 @@ printed_instrs_nocfg:
         br      x0
         .size   printed_instrs_nocfg, .-printed_instrs_nocfg
 
+// Test handling of unreachable basic blocks.
+//
+// Basic blocks without any predecessors were observed in real-world optimized
+// code. At least sometimes they were actually reachable via jump table, which
+// was not detected, but the function was processed as if its CFG was
+// reconstructed successfully.
+//
+// As a more predictable model example, let's use really unreachable code
+// for testing.
+
+        .globl  bad_unreachable_call
+        .type   bad_unreachable_call,@function
+bad_unreachable_call:
+// CHECK-LABEL: GS-PAUTH: Warning: no predecessor basic blocks detected (possibly incomplete CFG) in function bad_unreachable_call, basic block {{[^,]+}}, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x0
+// CHECK-NEXT:  The 0 instructions that write to the affected registers after any authentication are:
+// CHECK-LABEL: GS-PAUTH: non-protected call found in function bad_unreachable_call, basic block {{[^,]+}}, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      blr     x0
+// CHECK-NEXT:  The 0 instructions that write to the affected registers after any authentication are:
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        b       1f
+        // unreachable basic block:
+        blr     x0
+
+1:      // reachable basic block:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size bad_unreachable_call, .-bad_unreachable_call
+
+        .globl  good_unreachable_call
+        .type   good_unreachable_call,@function
+good_unreachable_call:
+// CHECK-NOT: non-protected call{{.*}}good_unreachable_call
+// CHECK-LABEL: GS-PAUTH: Warning: no predecessor basic blocks detected (possibly incomplete CFG) in function good_unreachable_call, basic block {{[^,]+}}, at address
+// CHECK-NEXT:  The instruction is     {{[0-9a-f]+}}:      autia   x0, x1
+// CHECK-NEXT:  The 0 instructions that write to the affected registers after any authentication are:
+// CHECK-NOT: non-protected call{{.*}}good_unreachable_call
+        paciasp
+        stp     x29, x30, [sp, #-16]!
+        mov     x29, sp
+
+        b       1f
+        // unreachable basic block:
+        autia   x0, x1
+        blr     x0      // <-- this call is definitely protected provided at least
+                        //     basic block boundaries are detected correctly
+
+1:      // reachable basic block:
+        ldp     x29, x30, [sp], #16
+        autiasp
+        ret
+        .size good_unreachable_call, .-good_unreachable_call
+
         .globl  main
         .type   main,@function
 main:

@atrosinenko atrosinenko force-pushed the users/atrosinenko/bolt-gs-unreachable-basic-blocks branch from b3acb46 to f8680ea Compare April 18, 2025 16:34
@atrosinenko atrosinenko force-pushed the users/atrosinenko/bolt-gs-cfi-debug-printing branch 2 times, most recently from 8d581df to f49ccac Compare April 18, 2025 18:43
@atrosinenko atrosinenko force-pushed the users/atrosinenko/bolt-gs-unreachable-basic-blocks branch from f8680ea to 082a34d Compare April 18, 2025 18:43
@atrosinenko atrosinenko force-pushed the users/atrosinenko/bolt-gs-cfi-debug-printing branch from f49ccac to 323acbd Compare April 22, 2025 16:08
@atrosinenko atrosinenko force-pushed the users/atrosinenko/bolt-gs-unreachable-basic-blocks branch from 082a34d to 629a423 Compare April 22, 2025 16:08
@atrosinenko atrosinenko force-pushed the users/atrosinenko/bolt-gs-cfi-debug-printing branch from 323acbd to 543e183 Compare April 22, 2025 17:39
@atrosinenko atrosinenko force-pushed the users/atrosinenko/bolt-gs-unreachable-basic-blocks branch from 629a423 to ff2193b Compare April 22, 2025 17:39
@atrosinenko atrosinenko force-pushed the users/atrosinenko/bolt-gs-cfi-debug-printing branch from 543e183 to e22ae5e Compare April 24, 2025 18:17
@atrosinenko atrosinenko force-pushed the users/atrosinenko/bolt-gs-unreachable-basic-blocks branch from ff2193b to b03ffd0 Compare April 24, 2025 18:17
@atrosinenko atrosinenko force-pushed the users/atrosinenko/bolt-gs-cfi-debug-printing branch from e22ae5e to 991e0e4 Compare April 25, 2025 20:42
@atrosinenko atrosinenko force-pushed the users/atrosinenko/bolt-gs-unreachable-basic-blocks branch 2 times, most recently from 43cc9f0 to 8c62a8a Compare April 29, 2025 14:19
@atrosinenko atrosinenko force-pushed the users/atrosinenko/bolt-gs-cfi-debug-printing branch from 991e0e4 to e48c57b Compare April 29, 2025 14:19
@atrosinenko atrosinenko force-pushed the users/atrosinenko/bolt-gs-unreachable-basic-blocks branch from 8c62a8a to 96e1b04 Compare April 29, 2025 17:32
@atrosinenko atrosinenko force-pushed the users/atrosinenko/bolt-gs-cfi-debug-printing branch from e48c57b to 992e377 Compare April 29, 2025 17:32
@atrosinenko atrosinenko force-pushed the users/atrosinenko/bolt-gs-unreachable-basic-blocks branch from 93e1a90 to e5fdebd Compare April 30, 2025 14:54
@atrosinenko atrosinenko force-pushed the users/atrosinenko/bolt-gs-cfi-debug-printing branch from 992e377 to a0a9cdf Compare April 30, 2025 14:54
Instead of refusing to analyze an instruction completely, when it is
unreachable according to the CFG reconstructed by BOLT, pessimistically
assume all registers to be unsafe at the start of basic blocks without
any predecessors. Nevertheless, unreachable basic blocks found in
optimized code likely means imprecise CFG reconstruction, thus report a
warning once per basic block without predecessors.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants