333 lines · cpp
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-exception6//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(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 is68 // constant throughout the entire frame; only the way to calculate it might69 // change.70 case dwarf::UnwindLocation::Location::DWARFExpr:71 // TODO: Expressions are not supported yet, but if they were to be72 // supported, all the registers used in an expression should extracted and73 // 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(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 because95 // initial frame state doesn't have any directives about callee saved96 // registers, every register is tracked. After initial frame state is97 // 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 initial117 // 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 125void 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 170void 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 is179 // 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 in202 // `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: Two206 // expressions are structurally equal if they become the same after you207 // replace every operand with a placeholder.208 209 if (PrevLoc == NextLoc) { // Case 1210 for (DWARFRegNum UsedReg : getUnwindRuleRegSet(PrevRow, Reg))211 if (Writes.count(UsedReg)) { // Case 1.b212 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.a223 }224 // Case 2225 if (PrevLoc.getLocation() != NextLoc.getLocation()) { // Case 2.a226 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.b235 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.c243 for (DWARFRegNum UsedReg : PrevRegSet)244 if (Writes.count(UsedReg)) { // Case 2.c.i245 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.ii254 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 260void 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 1300 if (!Writes.count(PrevCFA.Reg)) // Case 1.a301 return;302 // Case 1.b303 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.b311 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.c319 if (Writes.count(PrevCFA.Reg)) { // Case 2.c.i320 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.ii328 Context->reportError(329 Inst.getLoc(),330 formatv("did not modify CFA register {0} but changed CFA rule",331 PrevCFARegName));332}333