llvm-project/llvm/lib/DWARFCFIChecker/DWARFCFIAnalysis.cpp

333 lines
12 KiB
C++

//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "llvm/DWARFCFIChecker/DWARFCFIAnalysis.h"
#include "Registers.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/Twine.h"
#include "llvm/DWARFCFIChecker/DWARFCFIState.h"
#include "llvm/DebugInfo/DWARF/LowLevel/DWARFUnwindTable.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCDwarf.h"
#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCRegister.h"
#include "llvm/MC/MCRegisterInfo.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FormatVariadic.h"
#include <optional>
using namespace llvm;
struct CFARegOffsetInfo {
DWARFRegNum Reg;
int64_t Offset;
CFARegOffsetInfo(DWARFRegNum Reg, int64_t Offset)
: Reg(Reg), Offset(Offset) {}
bool operator==(const CFARegOffsetInfo &RHS) const {
return Reg == RHS.Reg && Offset == RHS.Offset;
}
};
static std::optional<CFARegOffsetInfo>
getCFARegOffsetInfo(const dwarf::UnwindRow &UnwindRow) {
auto CFALocation = UnwindRow.getCFAValue();
if (CFALocation.getLocation() !=
dwarf::UnwindLocation::Location::RegPlusOffset)
return std::nullopt;
return CFARegOffsetInfo(CFALocation.getRegister(), CFALocation.getOffset());
}
static SmallSet<DWARFRegNum, 4>
getUnwindRuleRegSet(const dwarf::UnwindRow &UnwindRow, DWARFRegNum Reg) {
auto MaybeLoc = UnwindRow.getRegisterLocations().getRegisterLocation(Reg);
assert(MaybeLoc && "the register should be included in the unwinding row");
auto Loc = *MaybeLoc;
switch (Loc.getLocation()) {
case dwarf::UnwindLocation::Location::Unspecified:
case dwarf::UnwindLocation::Location::Undefined:
case dwarf::UnwindLocation::Location::Constant:
case dwarf::UnwindLocation::Location::CFAPlusOffset:
// [CFA + offset] does not depend on any register because the CFA value is
// constant throughout the entire frame; only the way to calculate it might
// change.
case dwarf::UnwindLocation::Location::DWARFExpr:
// TODO: Expressions are not supported yet, but if they were to be
// supported, all the registers used in an expression should extracted and
// returned here.
return {};
case dwarf::UnwindLocation::Location::Same:
return {Reg};
case dwarf::UnwindLocation::Location::RegPlusOffset:
return {Loc.getRegister()};
}
llvm_unreachable("Unknown dwarf::UnwindLocation::Location enum");
}
DWARFCFIAnalysis::DWARFCFIAnalysis(MCContext *Context, MCInstrInfo const &MCII,
bool IsEH,
ArrayRef<MCCFIInstruction> Prologue)
: State(Context), Context(Context), MCII(MCII),
MCRI(Context->getRegisterInfo()), IsEH(IsEH) {
for (auto LLVMReg : getTrackingRegs(MCRI)) {
if (MCRI->get(LLVMReg).IsArtificial || MCRI->get(LLVMReg).IsConstant)
continue;
DWARFRegNum Reg = MCRI->getDwarfRegNum(LLVMReg, IsEH);
// TODO: this should be `undefined` instead of `same_value`, but because
// initial frame state doesn't have any directives about callee saved
// registers, every register is tracked. After initial frame state is
// corrected, this should be changed.
State.update(MCCFIInstruction::createSameValue(nullptr, Reg));
}
// TODO: Ignoring PC should be in the initial frame state.
State.update(MCCFIInstruction::createUndefined(
nullptr, MCRI->getDwarfRegNum(MCRI->getProgramCounter(), IsEH)));
for (auto &&InitialFrameStateCFIDirective :
Context->getAsmInfo()->getInitialFrameState())
State.update(InitialFrameStateCFIDirective);
auto MaybeCurrentRow = State.getCurrentUnwindRow();
assert(MaybeCurrentRow && "there should be at least one row");
auto MaybeCFA = getCFARegOffsetInfo(*MaybeCurrentRow);
assert(MaybeCFA &&
"the CFA information should be describable in [reg + offset] in here");
auto CFA = *MaybeCFA;
// TODO: CFA register callee value is CFA's value, this should be in initial
// frame state.
State.update(MCCFIInstruction::createOffset(nullptr, CFA.Reg, 0));
// Applying the prologue after default assumptions to overwrite them.
for (auto &&Directive : Prologue)
State.update(Directive);
}
void DWARFCFIAnalysis::update(const MCInst &Inst,
ArrayRef<MCCFIInstruction> Directives) {
const MCInstrDesc &MCInstInfo = MCII.get(Inst.getOpcode());
auto MaybePrevRow = State.getCurrentUnwindRow();
assert(MaybePrevRow && "the analysis should have initialized the "
"state with at least one row by now");
auto PrevRow = *MaybePrevRow;
for (auto &&Directive : Directives)
State.update(Directive);
SmallSet<DWARFRegNum, 4> Writes, Reads;
for (unsigned I = 0; I < MCInstInfo.NumImplicitUses; I++)
Reads.insert(MCRI->getDwarfRegNum(
getSuperReg(MCRI, MCInstInfo.implicit_uses()[I]), IsEH));
for (unsigned I = 0; I < MCInstInfo.NumImplicitDefs; I++)
Writes.insert(MCRI->getDwarfRegNum(
getSuperReg(MCRI, MCInstInfo.implicit_defs()[I]), IsEH));
for (unsigned I = 0; I < Inst.getNumOperands(); I++) {
auto &&Op = Inst.getOperand(I);
if (Op.isReg()) {
if (I < MCInstInfo.getNumDefs())
Writes.insert(
MCRI->getDwarfRegNum(getSuperReg(MCRI, Op.getReg()), IsEH));
else if (Op.getReg())
Reads.insert(
MCRI->getDwarfRegNum(getSuperReg(MCRI, Op.getReg()), IsEH));
}
}
auto MaybeNextRow = State.getCurrentUnwindRow();
assert(MaybeNextRow && "previous row existed, so should the current row");
auto NextRow = *MaybeNextRow;
checkCFADiff(Inst, PrevRow, NextRow, Reads, Writes);
for (auto LLVMReg : getTrackingRegs(MCRI)) {
DWARFRegNum Reg = MCRI->getDwarfRegNum(LLVMReg, IsEH);
checkRegDiff(Inst, Reg, PrevRow, NextRow, Reads, Writes);
}
}
void DWARFCFIAnalysis::checkRegDiff(const MCInst &Inst, DWARFRegNum Reg,
const dwarf::UnwindRow &PrevRow,
const dwarf::UnwindRow &NextRow,
const SmallSet<DWARFRegNum, 4> &Reads,
const SmallSet<DWARFRegNum, 4> &Writes) {
auto MaybePrevLoc = PrevRow.getRegisterLocations().getRegisterLocation(Reg);
auto MaybeNextLoc = NextRow.getRegisterLocations().getRegisterLocation(Reg);
// All the tracked registers are added during initiation. So if a register is
// not added, should stay the same during execution and vice versa.
if (!MaybePrevLoc) {
assert(!MaybeNextLoc && "the register unwind info suddenly appeared here");
return;
}
assert(MaybeNextLoc && "the register unwind info suddenly vanished here");
auto PrevLoc = MaybePrevLoc.value();
auto NextLoc = MaybeNextLoc.value();
auto MaybeLLVMReg = MCRI->getLLVMRegNum(Reg, IsEH);
if (!MaybeLLVMReg) {
if (!(PrevLoc == NextLoc))
Context->reportWarning(
Inst.getLoc(),
formatv("the dwarf register {0} does not have a LLVM number, but its "
"unwind info changed. Ignoring this change",
Reg));
return;
}
const char *RegName = MCRI->getName(*MaybeLLVMReg);
// Each case is annotated with its corresponding number as described in
// `llvm/include/llvm/DWARFCFIChecker/DWARFCFIAnalysis.h`.
// TODO: Expressions are not supported yet, but if they were to be supported,
// note that structure equality for expressions is defined as follows: Two
// expressions are structurally equal if they become the same after you
// replace every operand with a placeholder.
if (PrevLoc == NextLoc) { // Case 1
for (DWARFRegNum UsedReg : getUnwindRuleRegSet(PrevRow, Reg))
if (Writes.count(UsedReg)) { // Case 1.b
auto MaybeLLVMUsedReg = MCRI->getLLVMRegNum(UsedReg, IsEH);
assert(MaybeLLVMUsedReg && "instructions will always write to a "
"register that has an LLVM register number");
Context->reportError(
Inst.getLoc(),
formatv("changed register {1}, that register {0}'s unwinding rule "
"uses, but there is no CFI directives about it",
RegName, MCRI->getName(*MaybeLLVMUsedReg)));
return;
}
return; // Case 1.a
}
// Case 2
if (PrevLoc.getLocation() != NextLoc.getLocation()) { // Case 2.a
Context->reportWarning(
Inst.getLoc(),
formatv("validating changes happening to register {0} unwinding "
"rule structure is not implemented yet",
RegName));
return;
}
auto &&PrevRegSet = getUnwindRuleRegSet(PrevRow, Reg);
if (PrevRegSet != getUnwindRuleRegSet(NextRow, Reg)) { // Case 2.b
Context->reportWarning(
Inst.getLoc(),
formatv("validating changes happening to register {0} unwinding "
"rule register set is not implemented yet",
RegName));
return;
}
// Case 2.c
for (DWARFRegNum UsedReg : PrevRegSet)
if (Writes.count(UsedReg)) { // Case 2.c.i
Context->reportWarning(
Inst.getLoc(),
formatv("register {0} unwinding rule's offset is changed, and one of "
"the rule's registers is modified, but validating the "
"modification amount is not implemented yet",
RegName));
return;
}
// Case 2.c.ii
Context->reportError(
Inst.getLoc(), formatv("register {0} unwinding rule's offset is changed, "
"but not any of the rule's registers are modified",
RegName));
}
void DWARFCFIAnalysis::checkCFADiff(const MCInst &Inst,
const dwarf::UnwindRow &PrevRow,
const dwarf::UnwindRow &NextRow,
const SmallSet<DWARFRegNum, 4> &Reads,
const SmallSet<DWARFRegNum, 4> &Writes) {
auto MaybePrevCFA = getCFARegOffsetInfo(PrevRow);
auto MaybeNextCFA = getCFARegOffsetInfo(NextRow);
if (!MaybePrevCFA) {
if (MaybeNextCFA) {
Context->reportWarning(Inst.getLoc(),
"CFA rule changed to [reg + offset], this "
"transition will not be checked");
return;
}
Context->reportWarning(Inst.getLoc(),
"CFA rule is not [reg + offset], not checking it");
return;
}
if (!MaybeNextCFA) {
Context->reportWarning(Inst.getLoc(),
"CFA rule changed from [reg + offset], this "
"transition will not be checked");
return;
}
auto PrevCFA = *MaybePrevCFA;
auto NextCFA = *MaybeNextCFA;
auto MaybeLLVMPrevReg = MCRI->getLLVMRegNum(PrevCFA.Reg, IsEH);
const char *PrevCFARegName =
MaybeLLVMPrevReg ? MCRI->getName(*MaybeLLVMPrevReg) : "";
auto MaybeLLVMNextReg = MCRI->getLLVMRegNum(NextCFA.Reg, IsEH);
const char *NextCFARegName =
MaybeLLVMNextReg ? MCRI->getName(*MaybeLLVMNextReg) : "";
if (PrevCFA == NextCFA) { // Case 1
if (!Writes.count(PrevCFA.Reg)) // Case 1.a
return;
// Case 1.b
Context->reportError(
Inst.getLoc(),
formatv("modified CFA register {0} but not changed CFA rule",
PrevCFARegName));
return;
}
if (PrevCFA.Reg != NextCFA.Reg) { // Case 2.b
Context->reportWarning(
Inst.getLoc(),
formatv("CFA register changed from register {0} to register {1}, "
"validating this change is not implemented yet",
PrevCFARegName, NextCFARegName));
return;
}
// Case 2.c
if (Writes.count(PrevCFA.Reg)) { // Case 2.c.i
Context->reportWarning(
Inst.getLoc(), formatv("CFA offset is changed from {0} to {1}, and CFA "
"register {2} is modified, but validating the "
"modification amount is not implemented yet",
PrevCFA.Offset, NextCFA.Offset, PrevCFARegName));
return;
}
// Case 2.c.ii
Context->reportError(
Inst.getLoc(),
formatv("did not modify CFA register {0} but changed CFA rule",
PrevCFARegName));
}