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