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
32using namespace llvm;
33
34struct CFARegOffsetInfo {
35 DWARFRegNum Reg;
36 int64_t Offset;
37
38 CFARegOffsetInfo(DWARFRegNum Reg, int64_t Offset)
39 : Reg(Reg), Offset(Offset) {}
40
41 bool operator==(const CFARegOffsetInfo &RHS) const {
42 return Reg == RHS.Reg && Offset == RHS.Offset;
43 }
44};
45
46static std::optional<CFARegOffsetInfo>
47getCFARegOffsetInfo(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
56static SmallSet<DWARFRegNum, 4>
57getUnwindRuleRegSet(const dwarf::UnwindRow &UnwindRow, DWARFRegNum Reg) {
58 auto MaybeLoc = UnwindRow.getRegisterLocations().getRegisterLocation(RegNum: 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
83DWARFCFIAnalysis::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(Reg: LLVMReg).IsArtificial || MCRI->get(Reg: LLVMReg).IsConstant)
91 continue;
92
93 DWARFRegNum Reg = MCRI->getDwarfRegNum(Reg: LLVMReg, isEH: IsEH);
94 // Based on dwarf documentation, the default rule for all columns before
95 // interpretation of the initial instructions is the undefined rule.
96
97 // For now, this tool depends on the user to write a prologue that
98 // establishes the rules required for proper validation of the rest of the
99 // function.
100 State.update(Directive: MCCFIInstruction::createUndefined(L: nullptr, Register: Reg));
101 }
102
103 State.update(Directive: MCCFIInstruction::createUndefined(
104 L: nullptr, Register: MCRI->getDwarfRegNum(Reg: MCRI->getProgramCounter(), isEH: IsEH)));
105
106 for (auto &&InitialFrameStateCFIDirective :
107 Context->getAsmInfo().getInitialFrameState())
108 State.update(Directive: InitialFrameStateCFIDirective);
109
110 // Applying the prologue after default assumptions to overwrite them.
111 for (auto &&Directive : Prologue)
112 State.update(Directive);
113}
114
115void DWARFCFIAnalysis::update(const MCInst &Inst,
116 ArrayRef<MCCFIInstruction> Directives) {
117 const MCInstrDesc &MCInstInfo = MCII.get(Opcode: Inst.getOpcode());
118
119 auto MaybePrevRow = State.getCurrentUnwindRow();
120 assert(MaybePrevRow && "the analysis should have initialized the "
121 "state with at least one row by now");
122 auto PrevRow = *MaybePrevRow;
123
124 for (auto &&Directive : Directives)
125 State.update(Directive);
126
127 SmallSet<DWARFRegNum, 4> Writes;
128 for (unsigned I = 0; I < MCInstInfo.NumImplicitDefs; I++)
129 Writes.insert(V: MCRI->getDwarfRegNum(
130 Reg: getSuperReg(MCRI, Reg: MCInstInfo.implicit_defs()[I]), isEH: IsEH));
131
132 for (unsigned I = 0; I < Inst.getNumOperands(); I++) {
133 auto &&Op = Inst.getOperand(i: I);
134 if (Op.isReg()) {
135 if (I < MCInstInfo.getNumDefs())
136 Writes.insert(
137 V: MCRI->getDwarfRegNum(Reg: getSuperReg(MCRI, Reg: Op.getReg()), isEH: IsEH));
138 }
139 }
140
141 auto MaybeNextRow = State.getCurrentUnwindRow();
142 assert(MaybeNextRow && "previous row existed, so should the current row");
143 auto NextRow = *MaybeNextRow;
144
145 checkCFADiff(Inst, PrevRow, NextRow, Writes);
146
147 for (auto LLVMReg : getTrackingRegs(MCRI)) {
148 DWARFRegNum Reg = MCRI->getDwarfRegNum(Reg: LLVMReg, isEH: IsEH);
149
150 checkRegDiff(Inst, Reg, PrevRow, NextRow, Writes);
151 }
152}
153
154void DWARFCFIAnalysis::checkRegDiff(const MCInst &Inst, DWARFRegNum Reg,
155 const dwarf::UnwindRow &PrevRow,
156 const dwarf::UnwindRow &NextRow,
157 const SmallSet<DWARFRegNum, 4> &Writes) {
158 auto MaybePrevLoc = PrevRow.getRegisterLocations().getRegisterLocation(RegNum: Reg);
159 auto MaybeNextLoc = NextRow.getRegisterLocations().getRegisterLocation(RegNum: Reg);
160
161 // All the tracked registers are added during initiation. So if a register is
162 // not added, should stay the same during execution and vice versa.
163 if (!MaybePrevLoc) {
164 assert(!MaybeNextLoc && "the register unwind info suddenly appeared here");
165 return;
166 }
167 assert(MaybeNextLoc && "the register unwind info suddenly vanished here");
168
169 auto PrevLoc = MaybePrevLoc.value();
170 auto NextLoc = MaybeNextLoc.value();
171
172 auto MaybeLLVMReg = MCRI->getLLVMRegNum(RegNum: Reg, isEH: IsEH);
173 if (!MaybeLLVMReg) {
174 if (!(PrevLoc == NextLoc))
175 Context->reportWarning(
176 L: Inst.getLoc(),
177 Msg: formatv(Fmt: "the dwarf register {0} does not have a LLVM number, but its "
178 "unwind info changed. Ignoring this change",
179 Vals&: Reg));
180 return;
181 }
182 const char *RegName = MCRI->getName(RegNo: *MaybeLLVMReg);
183
184 // Each case is annotated with its corresponding number as described in
185 // `llvm/include/llvm/DWARFCFIChecker/DWARFCFIAnalysis.h`.
186
187 // TODO: Expressions are not supported yet, but if they were to be supported,
188 // note that structure equality for expressions is defined as follows: Two
189 // expressions are structurally equal if they become the same after you
190 // replace every operand with a placeholder.
191
192 if (PrevLoc == NextLoc) { // Case 1
193 for (DWARFRegNum UsedReg : getUnwindRuleRegSet(UnwindRow: PrevRow, Reg))
194 if (Writes.count(V: UsedReg)) { // Case 1.b
195 auto MaybeLLVMUsedReg = MCRI->getLLVMRegNum(RegNum: UsedReg, isEH: IsEH);
196 assert(MaybeLLVMUsedReg && "instructions will always write to a "
197 "register that has an LLVM register number");
198 Context->reportError(
199 L: Inst.getLoc(),
200 Msg: formatv(Fmt: "changed register {1}, that register {0}'s unwinding rule "
201 "uses, but there is no CFI directives about it",
202 Vals&: RegName, Vals: MCRI->getName(RegNo: *MaybeLLVMUsedReg)));
203 return;
204 }
205 return; // Case 1.a
206 }
207 // Case 2
208 if (PrevLoc.getLocation() != NextLoc.getLocation()) { // Case 2.a
209 Context->reportWarning(
210 L: Inst.getLoc(),
211 Msg: formatv(Fmt: "validating changes happening to register {0} unwinding "
212 "rule structure is not implemented yet",
213 Vals&: RegName));
214 return;
215 }
216 auto &&PrevRegSet = getUnwindRuleRegSet(UnwindRow: PrevRow, Reg);
217 if (PrevRegSet != getUnwindRuleRegSet(UnwindRow: NextRow, Reg)) { // Case 2.b
218 Context->reportWarning(
219 L: Inst.getLoc(),
220 Msg: formatv(Fmt: "validating changes happening to register {0} unwinding "
221 "rule register set is not implemented yet",
222 Vals&: RegName));
223 return;
224 }
225 // Case 2.c
226 for (DWARFRegNum UsedReg : PrevRegSet)
227 if (Writes.count(V: UsedReg)) { // Case 2.c.i
228 Context->reportWarning(
229 L: Inst.getLoc(),
230 Msg: formatv(Fmt: "register {0} unwinding rule's offset is changed, and one of "
231 "the rule's registers is modified, but validating the "
232 "modification amount is not implemented yet",
233 Vals&: RegName));
234 return;
235 }
236 // Case 2.c.ii
237 Context->reportError(
238 L: Inst.getLoc(), Msg: formatv(Fmt: "register {0} unwinding rule's offset is changed, "
239 "but not any of the rule's registers are modified",
240 Vals&: RegName));
241}
242
243void DWARFCFIAnalysis::checkCFADiff(const MCInst &Inst,
244 const dwarf::UnwindRow &PrevRow,
245 const dwarf::UnwindRow &NextRow,
246 const SmallSet<DWARFRegNum, 4> &Writes) {
247
248 auto MaybePrevCFA = getCFARegOffsetInfo(UnwindRow: PrevRow);
249 auto MaybeNextCFA = getCFARegOffsetInfo(UnwindRow: NextRow);
250
251 if (!MaybePrevCFA) {
252 if (MaybeNextCFA) {
253 Context->reportWarning(L: Inst.getLoc(),
254 Msg: "CFA rule changed to [reg + offset], this "
255 "transition will not be checked");
256 return;
257 }
258
259 Context->reportWarning(L: Inst.getLoc(),
260 Msg: "CFA rule is not [reg + offset], not checking it");
261 return;
262 }
263
264 if (!MaybeNextCFA) {
265 Context->reportWarning(L: Inst.getLoc(),
266 Msg: "CFA rule changed from [reg + offset], this "
267 "transition will not be checked");
268 return;
269 }
270
271 auto PrevCFA = *MaybePrevCFA;
272 auto NextCFA = *MaybeNextCFA;
273
274 auto MaybeLLVMPrevReg = MCRI->getLLVMRegNum(RegNum: PrevCFA.Reg, isEH: IsEH);
275 const char *PrevCFARegName =
276 MaybeLLVMPrevReg ? MCRI->getName(RegNo: *MaybeLLVMPrevReg) : "";
277 auto MaybeLLVMNextReg = MCRI->getLLVMRegNum(RegNum: NextCFA.Reg, isEH: IsEH);
278 const char *NextCFARegName =
279 MaybeLLVMNextReg ? MCRI->getName(RegNo: *MaybeLLVMNextReg) : "";
280
281 if (PrevCFA == NextCFA) { // Case 1
282 if (!Writes.count(V: PrevCFA.Reg)) // Case 1.a
283 return;
284 // Case 1.b
285 Context->reportError(
286 L: Inst.getLoc(),
287 Msg: formatv(Fmt: "modified CFA register {0} but not changed CFA rule",
288 Vals&: PrevCFARegName));
289 return;
290 }
291
292 if (PrevCFA.Reg != NextCFA.Reg) { // Case 2.b
293 Context->reportWarning(
294 L: Inst.getLoc(),
295 Msg: formatv(Fmt: "CFA register changed from register {0} to register {1}, "
296 "validating this change is not implemented yet",
297 Vals&: PrevCFARegName, Vals&: NextCFARegName));
298 return;
299 }
300 // Case 2.c
301 if (Writes.count(V: PrevCFA.Reg)) { // Case 2.c.i
302 Context->reportWarning(
303 L: Inst.getLoc(), Msg: formatv(Fmt: "CFA offset is changed from {0} to {1}, and CFA "
304 "register {2} is modified, but validating the "
305 "modification amount is not implemented yet",
306 Vals&: PrevCFA.Offset, Vals&: NextCFA.Offset, Vals&: PrevCFARegName));
307 return;
308 }
309 // Case 2.c.ii
310 Context->reportError(
311 L: Inst.getLoc(),
312 Msg: formatv(Fmt: "did not modify CFA register {0} but changed CFA rule",
313 Vals&: PrevCFARegName));
314}
315