xref: /freebsd/contrib/llvm-project/llvm/lib/DWARFCFIChecker/DWARFCFIAnalysis.cpp (revision 700637cbb5e582861067a11aaca4d053546871d2)
1 //===----------------------------------------------------------------------===//
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 #include "llvm/DWARFCFIChecker/DWARFCFIAnalysis.h"
10 #include "Registers.h"
11 #include "llvm/ADT/ArrayRef.h"
12 #include "llvm/ADT/SmallSet.h"
13 #include "llvm/ADT/SmallVector.h"
14 #include "llvm/ADT/Twine.h"
15 #include "llvm/DWARFCFIChecker/DWARFCFIState.h"
16 #include "llvm/DebugInfo/DWARF/LowLevel/DWARFUnwindTable.h"
17 #include "llvm/MC/MCAsmInfo.h"
18 #include "llvm/MC/MCContext.h"
19 #include "llvm/MC/MCDwarf.h"
20 #include "llvm/MC/MCExpr.h"
21 #include "llvm/MC/MCInst.h"
22 #include "llvm/MC/MCInstrInfo.h"
23 #include "llvm/MC/MCRegister.h"
24 #include "llvm/MC/MCRegisterInfo.h"
25 #include "llvm/MC/MCStreamer.h"
26 #include "llvm/MC/MCSubtargetInfo.h"
27 #include "llvm/MC/TargetRegistry.h"
28 #include "llvm/Support/ErrorHandling.h"
29 #include "llvm/Support/FormatVariadic.h"
30 #include <optional>
31 
32 using namespace llvm;
33 
34 struct CFARegOffsetInfo {
35   DWARFRegNum Reg;
36   int64_t Offset;
37 
CFARegOffsetInfoCFARegOffsetInfo38   CFARegOffsetInfo(DWARFRegNum Reg, int64_t Offset)
39       : Reg(Reg), Offset(Offset) {}
40 
operator ==CFARegOffsetInfo41   bool operator==(const CFARegOffsetInfo &RHS) const {
42     return Reg == RHS.Reg && Offset == RHS.Offset;
43   }
44 };
45 
46 static std::optional<CFARegOffsetInfo>
getCFARegOffsetInfo(const dwarf::UnwindRow & UnwindRow)47 getCFARegOffsetInfo(const dwarf::UnwindRow &UnwindRow) {
48   auto CFALocation = UnwindRow.getCFAValue();
49   if (CFALocation.getLocation() !=
50       dwarf::UnwindLocation::Location::RegPlusOffset)
51     return std::nullopt;
52 
53   return CFARegOffsetInfo(CFALocation.getRegister(), CFALocation.getOffset());
54 }
55 
56 static SmallSet<DWARFRegNum, 4>
getUnwindRuleRegSet(const dwarf::UnwindRow & UnwindRow,DWARFRegNum Reg)57 getUnwindRuleRegSet(const dwarf::UnwindRow &UnwindRow, DWARFRegNum Reg) {
58   auto MaybeLoc = UnwindRow.getRegisterLocations().getRegisterLocation(Reg);
59   assert(MaybeLoc && "the register should be included in the unwinding row");
60   auto Loc = *MaybeLoc;
61 
62   switch (Loc.getLocation()) {
63   case dwarf::UnwindLocation::Location::Unspecified:
64   case dwarf::UnwindLocation::Location::Undefined:
65   case dwarf::UnwindLocation::Location::Constant:
66   case dwarf::UnwindLocation::Location::CFAPlusOffset:
67     // [CFA + offset] does not depend on any register because the CFA value is
68     // constant throughout the entire frame; only the way to calculate it might
69     // change.
70   case dwarf::UnwindLocation::Location::DWARFExpr:
71     // TODO: Expressions are not supported yet, but if they were to be
72     // supported, all the registers used in an expression should extracted and
73     // returned here.
74     return {};
75   case dwarf::UnwindLocation::Location::Same:
76     return {Reg};
77   case dwarf::UnwindLocation::Location::RegPlusOffset:
78     return {Loc.getRegister()};
79   }
80   llvm_unreachable("Unknown dwarf::UnwindLocation::Location enum");
81 }
82 
DWARFCFIAnalysis(MCContext * Context,MCInstrInfo const & MCII,bool IsEH,ArrayRef<MCCFIInstruction> Prologue)83 DWARFCFIAnalysis::DWARFCFIAnalysis(MCContext *Context, MCInstrInfo const &MCII,
84                                    bool IsEH,
85                                    ArrayRef<MCCFIInstruction> Prologue)
86     : State(Context), Context(Context), MCII(MCII),
87       MCRI(Context->getRegisterInfo()), IsEH(IsEH) {
88 
89   for (auto LLVMReg : getTrackingRegs(MCRI)) {
90     if (MCRI->get(LLVMReg).IsArtificial || MCRI->get(LLVMReg).IsConstant)
91       continue;
92 
93     DWARFRegNum Reg = MCRI->getDwarfRegNum(LLVMReg, IsEH);
94     // TODO: this should be `undefined` instead of `same_value`, but because
95     // initial frame state doesn't have any directives about callee saved
96     // registers, every register is tracked. After initial frame state is
97     // corrected, this should be changed.
98     State.update(MCCFIInstruction::createSameValue(nullptr, Reg));
99   }
100 
101   // TODO: Ignoring PC should be in the initial frame state.
102   State.update(MCCFIInstruction::createUndefined(
103       nullptr, MCRI->getDwarfRegNum(MCRI->getProgramCounter(), IsEH)));
104 
105   for (auto &&InitialFrameStateCFIDirective :
106        Context->getAsmInfo()->getInitialFrameState())
107     State.update(InitialFrameStateCFIDirective);
108 
109   auto MaybeCurrentRow = State.getCurrentUnwindRow();
110   assert(MaybeCurrentRow && "there should be at least one row");
111   auto MaybeCFA = getCFARegOffsetInfo(*MaybeCurrentRow);
112   assert(MaybeCFA &&
113          "the CFA information should be describable in [reg + offset] in here");
114   auto CFA = *MaybeCFA;
115 
116   // TODO: CFA register callee value is CFA's value, this should be in initial
117   // frame state.
118   State.update(MCCFIInstruction::createOffset(nullptr, CFA.Reg, 0));
119 
120   // Applying the prologue after default assumptions to overwrite them.
121   for (auto &&Directive : Prologue)
122     State.update(Directive);
123 }
124 
update(const MCInst & Inst,ArrayRef<MCCFIInstruction> Directives)125 void DWARFCFIAnalysis::update(const MCInst &Inst,
126                               ArrayRef<MCCFIInstruction> Directives) {
127   const MCInstrDesc &MCInstInfo = MCII.get(Inst.getOpcode());
128 
129   auto MaybePrevRow = State.getCurrentUnwindRow();
130   assert(MaybePrevRow && "the analysis should have initialized the "
131                          "state with at least one row by now");
132   auto PrevRow = *MaybePrevRow;
133 
134   for (auto &&Directive : Directives)
135     State.update(Directive);
136 
137   SmallSet<DWARFRegNum, 4> Writes, Reads;
138   for (unsigned I = 0; I < MCInstInfo.NumImplicitUses; I++)
139     Reads.insert(MCRI->getDwarfRegNum(
140         getSuperReg(MCRI, MCInstInfo.implicit_uses()[I]), IsEH));
141   for (unsigned I = 0; I < MCInstInfo.NumImplicitDefs; I++)
142     Writes.insert(MCRI->getDwarfRegNum(
143         getSuperReg(MCRI, MCInstInfo.implicit_defs()[I]), IsEH));
144 
145   for (unsigned I = 0; I < Inst.getNumOperands(); I++) {
146     auto &&Op = Inst.getOperand(I);
147     if (Op.isReg()) {
148       if (I < MCInstInfo.getNumDefs())
149         Writes.insert(
150             MCRI->getDwarfRegNum(getSuperReg(MCRI, Op.getReg()), IsEH));
151       else if (Op.getReg())
152         Reads.insert(
153             MCRI->getDwarfRegNum(getSuperReg(MCRI, Op.getReg()), IsEH));
154     }
155   }
156 
157   auto MaybeNextRow = State.getCurrentUnwindRow();
158   assert(MaybeNextRow && "previous row existed, so should the current row");
159   auto NextRow = *MaybeNextRow;
160 
161   checkCFADiff(Inst, PrevRow, NextRow, Reads, Writes);
162 
163   for (auto LLVMReg : getTrackingRegs(MCRI)) {
164     DWARFRegNum Reg = MCRI->getDwarfRegNum(LLVMReg, IsEH);
165 
166     checkRegDiff(Inst, Reg, PrevRow, NextRow, Reads, Writes);
167   }
168 }
169 
checkRegDiff(const MCInst & Inst,DWARFRegNum Reg,const dwarf::UnwindRow & PrevRow,const dwarf::UnwindRow & NextRow,const SmallSet<DWARFRegNum,4> & Reads,const SmallSet<DWARFRegNum,4> & Writes)170 void DWARFCFIAnalysis::checkRegDiff(const MCInst &Inst, DWARFRegNum Reg,
171                                     const dwarf::UnwindRow &PrevRow,
172                                     const dwarf::UnwindRow &NextRow,
173                                     const SmallSet<DWARFRegNum, 4> &Reads,
174                                     const SmallSet<DWARFRegNum, 4> &Writes) {
175   auto MaybePrevLoc = PrevRow.getRegisterLocations().getRegisterLocation(Reg);
176   auto MaybeNextLoc = NextRow.getRegisterLocations().getRegisterLocation(Reg);
177 
178   // All the tracked registers are added during initiation. So if a register is
179   // not added, should stay the same during execution and vice versa.
180   if (!MaybePrevLoc) {
181     assert(!MaybeNextLoc && "the register unwind info suddenly appeared here");
182     return;
183   }
184   assert(MaybeNextLoc && "the register unwind info suddenly vanished here");
185 
186   auto PrevLoc = MaybePrevLoc.value();
187   auto NextLoc = MaybeNextLoc.value();
188 
189   auto MaybeLLVMReg = MCRI->getLLVMRegNum(Reg, IsEH);
190   if (!MaybeLLVMReg) {
191     if (!(PrevLoc == NextLoc))
192       Context->reportWarning(
193           Inst.getLoc(),
194           formatv("the dwarf register {0} does not have a LLVM number, but its "
195                   "unwind info changed. Ignoring this change",
196                   Reg));
197     return;
198   }
199   const char *RegName = MCRI->getName(*MaybeLLVMReg);
200 
201   // Each case is annotated with its corresponding number as described in
202   // `llvm/include/llvm/DWARFCFIChecker/DWARFCFIAnalysis.h`.
203 
204   // TODO: Expressions are not supported yet, but if they were to be supported,
205   // note that structure equality for expressions is defined as follows: Two
206   // expressions are structurally equal if they become the same after you
207   // replace every operand with a placeholder.
208 
209   if (PrevLoc == NextLoc) { // Case 1
210     for (DWARFRegNum UsedReg : getUnwindRuleRegSet(PrevRow, Reg))
211       if (Writes.count(UsedReg)) { // Case 1.b
212         auto MaybeLLVMUsedReg = MCRI->getLLVMRegNum(UsedReg, IsEH);
213         assert(MaybeLLVMUsedReg && "instructions will always write to a "
214                                    "register that has an LLVM register number");
215         Context->reportError(
216             Inst.getLoc(),
217             formatv("changed register {1}, that register {0}'s unwinding rule "
218                     "uses, but there is no CFI directives about it",
219                     RegName, MCRI->getName(*MaybeLLVMUsedReg)));
220         return;
221       }
222     return; // Case 1.a
223   }
224   // Case 2
225   if (PrevLoc.getLocation() != NextLoc.getLocation()) { // Case 2.a
226     Context->reportWarning(
227         Inst.getLoc(),
228         formatv("validating changes happening to register {0} unwinding "
229                 "rule structure is not implemented yet",
230                 RegName));
231     return;
232   }
233   auto &&PrevRegSet = getUnwindRuleRegSet(PrevRow, Reg);
234   if (PrevRegSet != getUnwindRuleRegSet(NextRow, Reg)) { // Case 2.b
235     Context->reportWarning(
236         Inst.getLoc(),
237         formatv("validating changes happening to register {0} unwinding "
238                 "rule register set is not implemented yet",
239                 RegName));
240     return;
241   }
242   // Case 2.c
243   for (DWARFRegNum UsedReg : PrevRegSet)
244     if (Writes.count(UsedReg)) { // Case 2.c.i
245       Context->reportWarning(
246           Inst.getLoc(),
247           formatv("register {0} unwinding rule's offset is changed, and one of "
248                   "the rule's registers is modified, but validating the "
249                   "modification amount is not implemented yet",
250                   RegName));
251       return;
252     }
253   // Case 2.c.ii
254   Context->reportError(
255       Inst.getLoc(), formatv("register {0} unwinding rule's offset is changed, "
256                              "but not any of the rule's registers are modified",
257                              RegName));
258 }
259 
checkCFADiff(const MCInst & Inst,const dwarf::UnwindRow & PrevRow,const dwarf::UnwindRow & NextRow,const SmallSet<DWARFRegNum,4> & Reads,const SmallSet<DWARFRegNum,4> & Writes)260 void DWARFCFIAnalysis::checkCFADiff(const MCInst &Inst,
261                                     const dwarf::UnwindRow &PrevRow,
262                                     const dwarf::UnwindRow &NextRow,
263                                     const SmallSet<DWARFRegNum, 4> &Reads,
264                                     const SmallSet<DWARFRegNum, 4> &Writes) {
265 
266   auto MaybePrevCFA = getCFARegOffsetInfo(PrevRow);
267   auto MaybeNextCFA = getCFARegOffsetInfo(NextRow);
268 
269   if (!MaybePrevCFA) {
270     if (MaybeNextCFA) {
271       Context->reportWarning(Inst.getLoc(),
272                              "CFA rule changed to [reg + offset], this "
273                              "transition will not be checked");
274       return;
275     }
276 
277     Context->reportWarning(Inst.getLoc(),
278                            "CFA rule is not [reg + offset], not checking it");
279     return;
280   }
281 
282   if (!MaybeNextCFA) {
283     Context->reportWarning(Inst.getLoc(),
284                            "CFA rule changed from [reg + offset], this "
285                            "transition will not be checked");
286     return;
287   }
288 
289   auto PrevCFA = *MaybePrevCFA;
290   auto NextCFA = *MaybeNextCFA;
291 
292   auto MaybeLLVMPrevReg = MCRI->getLLVMRegNum(PrevCFA.Reg, IsEH);
293   const char *PrevCFARegName =
294       MaybeLLVMPrevReg ? MCRI->getName(*MaybeLLVMPrevReg) : "";
295   auto MaybeLLVMNextReg = MCRI->getLLVMRegNum(NextCFA.Reg, IsEH);
296   const char *NextCFARegName =
297       MaybeLLVMNextReg ? MCRI->getName(*MaybeLLVMNextReg) : "";
298 
299   if (PrevCFA == NextCFA) {         // Case 1
300     if (!Writes.count(PrevCFA.Reg)) // Case 1.a
301       return;
302     // Case 1.b
303     Context->reportError(
304         Inst.getLoc(),
305         formatv("modified CFA register {0} but not changed CFA rule",
306                 PrevCFARegName));
307     return;
308   }
309 
310   if (PrevCFA.Reg != NextCFA.Reg) { // Case 2.b
311     Context->reportWarning(
312         Inst.getLoc(),
313         formatv("CFA register changed from register {0} to register {1}, "
314                 "validating this change is not implemented yet",
315                 PrevCFARegName, NextCFARegName));
316     return;
317   }
318   // Case 2.c
319   if (Writes.count(PrevCFA.Reg)) { // Case 2.c.i
320     Context->reportWarning(
321         Inst.getLoc(), formatv("CFA offset is changed from {0} to {1}, and CFA "
322                                "register {2} is modified, but validating the "
323                                "modification amount is not implemented yet",
324                                PrevCFA.Offset, NextCFA.Offset, PrevCFARegName));
325     return;
326   }
327   // Case 2.c.ii
328   Context->reportError(
329       Inst.getLoc(),
330       formatv("did not modify CFA register {0} but changed CFA rule",
331               PrevCFARegName));
332 }
333