diff --git a/llvm/include/llvm/DWARFCFIChecker/DWARFCFIAnalysis.h b/llvm/include/llvm/DWARFCFIChecker/DWARFCFIAnalysis.h new file mode 100644 index 0000000000000..3803dfd4ebd20 --- /dev/null +++ b/llvm/include/llvm/DWARFCFIChecker/DWARFCFIAnalysis.h @@ -0,0 +1,104 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file declares `DWARFCFIAnalysis` class. +/// `DWARFCFIAnalysis` is a minimal implementation of a DWARF CFI checker +/// described in this link: +/// https://discourse.llvm.org/t/rfc-dwarf-cfi-validation/86936 +/// +/// The goal of the checker is to validate DWARF CFI directives using the +/// prologue directives and the machine instructions. The main proposed +/// algorithm validates the directives by comparing the CFI state in each +/// instruction with the state achieved by abstract execution of the instruction +/// on the CFI state. However, the current version implemented here is a simple +/// conditional check based on the registers modified by each instruction. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_DWARFCFICHECKER_DWARFCFIANALYSIS_H +#define LLVM_DWARFCFICHECKER_DWARFCFIANALYSIS_H + +#include "DWARFCFIState.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/DebugInfo/DWARF/LowLevel/DWARFUnwindTable.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/MCRegisterInfo.h" +#include "llvm/MC/MCStreamer.h" +#include "llvm/MC/MCSubtargetInfo.h" +#include "llvm/MC/TargetRegistry.h" + +namespace llvm { + +/// `DWARFCFIAnalysis` validates the DWARF Call Frame Information one machine +/// instruction at a time. This class maintains an internal CFI state +/// initialized with the prologue directives and updated with each instruction's +/// associated directives. In each update, it checks if the machine +/// instruction changes the CFI state in a way that matches the changes +/// from the CFI directives. This checking may results in errors and warnings. +/// +/// In current stage, the analysis is only aware of what registers the +/// instruction modifies. If the modification is happening to a sub-register, +/// the analysis considers the super-register is modified. +/// +/// In each update, for each register (or CFA), the following cases can happen: +/// 1. The unwinding rule is not changed: +/// a. The registers involved in this rule are not modified: the analysis +/// proceeds without emitting error or warning. +/// b. The registers involved in this rule are modified: it emits an error. +/// 2. The unwinding rule is changed: +/// a. The rule is structurally modified (i.e., the location is changed): It +/// emits a warning. +/// b. The rule is structurally the same, but the register set is changed: it +/// emits a warning. +/// c. The rule is structurally the same, using the same set of registers, but +/// the offset is changed: +/// i. If the registers included in the rule are modified as well: It +/// emits a warning. +/// ii. If the registers included in the rule are not modified: It emits an +/// error. +/// +/// The analysis only checks the CFA unwinding rule when the rule is a register +/// plus some offset. Therefore, for CFA, only cases 1, 2.b, and 2.c are +/// checked, and in all other case(s), a warning is emitted. +class DWARFCFIAnalysis { +public: + DWARFCFIAnalysis(MCContext *Context, MCInstrInfo const &MCII, bool IsEH, + ArrayRef Prologue); + + void update(const MCInst &Inst, ArrayRef Directives); + +private: + void checkRegDiff(const MCInst &Inst, DWARFRegNum Reg, + const dwarf::UnwindRow &PrevRow, + const dwarf::UnwindRow &NextRow, + const SmallSet &Reads, + const SmallSet &Writes); + + void checkCFADiff(const MCInst &Inst, const dwarf::UnwindRow &PrevRow, + const dwarf::UnwindRow &NextRow, + const SmallSet &Reads, + const SmallSet &Writes); + +private: + DWARFCFIState State; + MCContext *Context; + MCInstrInfo const &MCII; + MCRegisterInfo const *MCRI; + bool IsEH; +}; + +} // namespace llvm + +#endif diff --git a/llvm/include/llvm/DWARFCFIChecker/DWARFCFIFunctionFrameAnalyzer.h b/llvm/include/llvm/DWARFCFIChecker/DWARFCFIFunctionFrameAnalyzer.h new file mode 100644 index 0000000000000..70caceae563f1 --- /dev/null +++ b/llvm/include/llvm/DWARFCFIChecker/DWARFCFIFunctionFrameAnalyzer.h @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file declares CFIFunctionFrameAnalyzer class. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_DWARFCFICHECKER_DWARFCFIFUNCTIONFRAMEANALYZER_H +#define LLVM_DWARFCFICHECKER_DWARFCFIFUNCTIONFRAMEANALYZER_H + +#include "DWARFCFIAnalysis.h" +#include "DWARFCFIFunctionFrameReceiver.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallVector.h" + +namespace llvm { + +/// This class implements the `CFIFunctionFrameReceiver` interface to validate +/// Call Frame Information in a stream of function frames. For validation, it +/// instantiates a `DWARFCFIAnalysis` for each frame. The errors/warnings are +/// emitted through the `MCContext` instance to the constructor. If a frame +/// finishes without being started or if all the frames are not finished before +/// this classes is destructed, the program fails through an assertion. +class CFIFunctionFrameAnalyzer : public CFIFunctionFrameReceiver { +public: + CFIFunctionFrameAnalyzer(MCContext &Context, const MCInstrInfo &MCII) + : CFIFunctionFrameReceiver(Context), MCII(MCII) {} + ~CFIFunctionFrameAnalyzer(); + + void startFunctionFrame(bool IsEH, + ArrayRef Prologue) override; + void + emitInstructionAndDirectives(const MCInst &Inst, + ArrayRef Directives) override; + void finishFunctionFrame() override; + +private: + MCInstrInfo const &MCII; + SmallVector UIAs; +}; + +} // namespace llvm + +#endif diff --git a/llvm/include/llvm/DWARFCFIChecker/DWARFCFIFunctionFrameReceiver.h b/llvm/include/llvm/DWARFCFIChecker/DWARFCFIFunctionFrameReceiver.h new file mode 100644 index 0000000000000..ee4e3fce21b1d --- /dev/null +++ b/llvm/include/llvm/DWARFCFIChecker/DWARFCFIFunctionFrameReceiver.h @@ -0,0 +1,54 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file declares CFIFunctionFrameReceiver class. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_DWARFCFICHECKER_DWARFCFIFUNCTIONFRAMERECEIVER_H +#define LLVM_DWARFCFICHECKER_DWARFCFIFUNCTIONFRAMERECEIVER_H + +#include "llvm/ADT/ArrayRef.h" + +namespace llvm { + +class MCCFIInstruction; +class MCContext; +class MCInst; + +/// This abstract base class is an interface for receiving DWARF function frames +/// Call Frame Information. `DWARFCFIFunctionFrameStreamer` channels the +/// function frames information gathered from an `MCStreamer` using a pointer to +/// an instance of this class for the whole program. +class CFIFunctionFrameReceiver { +public: + CFIFunctionFrameReceiver(const CFIFunctionFrameReceiver &) = delete; + CFIFunctionFrameReceiver & + operator=(const CFIFunctionFrameReceiver &) = delete; + virtual ~CFIFunctionFrameReceiver() = default; + + CFIFunctionFrameReceiver(MCContext &Context) : Context(Context) {} + + MCContext &getContext() const { return Context; } + + virtual void startFunctionFrame(bool IsEH, + ArrayRef Prologue) {} + /// Instructions are processed in the program order. + virtual void + emitInstructionAndDirectives(const MCInst &Inst, + ArrayRef Directives) {} + virtual void finishFunctionFrame() {} + +private: + MCContext &Context; +}; + +} // namespace llvm + +#endif diff --git a/llvm/include/llvm/DWARFCFIChecker/DWARFCFIFunctionFrameStreamer.h b/llvm/include/llvm/DWARFCFIChecker/DWARFCFIFunctionFrameStreamer.h new file mode 100644 index 0000000000000..522010033f1a8 --- /dev/null +++ b/llvm/include/llvm/DWARFCFIChecker/DWARFCFIFunctionFrameStreamer.h @@ -0,0 +1,90 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file declares CFIFunctionFrameStreamer class. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_DWARFCFICHECKER_DWARFCFIFUNCTIONFRAMESTREAMER_H +#define LLVM_DWARFCFICHECKER_DWARFCFIFUNCTIONFRAMESTREAMER_H + +#include "DWARFCFIFunctionFrameReceiver.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/MC/MCContext.h" +#include "llvm/MC/MCDwarf.h" +#include "llvm/MC/MCInstrInfo.h" +#include "llvm/MC/MCStreamer.h" +#include +#include + +namespace llvm { + +/// This class is an `MCStreamer` implementation that watches for machine +/// instructions and CFI directives. It cuts the stream into function frames and +/// channels them to `CFIFunctionFrameReceiver`. A function frame is the machine +/// instructions and CFI directives that are between `.cfi_startproc` and +/// `.cfi_endproc` directives. +class CFIFunctionFrameStreamer : public MCStreamer { +public: + CFIFunctionFrameStreamer(MCContext &Context, + std::unique_ptr Receiver) + : MCStreamer(Context), Receiver(std::move(Receiver)) { + assert(this->Receiver && "Receiver should not be null"); + } + + bool hasRawTextSupport() const override { return true; } + void emitRawTextImpl(StringRef String) override {} + + bool emitSymbolAttribute(MCSymbol *Symbol, MCSymbolAttr Attribute) override { + return true; + } + + void emitCommonSymbol(MCSymbol *Symbol, uint64_t Size, + Align ByteAlignment) override {} + void emitSubsectionsViaSymbols() override {}; + void beginCOFFSymbolDef(const MCSymbol *Symbol) override {} + void emitCOFFSymbolStorageClass(int StorageClass) override {} + void emitCOFFSymbolType(int Type) override {} + void endCOFFSymbolDef() override {} + void emitXCOFFSymbolLinkageWithVisibility(MCSymbol *Symbol, + MCSymbolAttr Linkage, + MCSymbolAttr Visibility) override {} + + void emitInstruction(const MCInst &Inst, const MCSubtargetInfo &STI) override; + void emitCFIStartProcImpl(MCDwarfFrameInfo &Frame) override; + void emitCFIEndProcImpl(MCDwarfFrameInfo &CurFrame) override; + +private: + /// This method sends the last instruction, along with its associated + /// directives, to the receiver and then updates the internal state of the + /// class. It moves the directive index to after the last directive and sets + /// the last instruction to \p NewInst . This method assumes it is called in + /// the middle of an unfinished DWARF debug frame; if not, an assertion will + /// fail. + void updateReceiver(const std::optional &NewInst); + +private: + /// The following fields are stacks that store the state of the stream sent to + /// the receiver in each frame. This class, like `MCStreamer`, assumes that + /// the debug frames are intertwined with each other only in stack form. + + /// The last instruction that is not sent to the receiver for each frame. + SmallVector> LastInstructions; + /// The index of the last directive that is not sent to the receiver for each + /// frame. + SmallVector LastDirectiveIndices; + /// The index of each frame in `DwarfFrameInfos` field in `MCStreamer`. + SmallVector FrameIndices; + + std::unique_ptr Receiver; +}; + +} // namespace llvm + +#endif diff --git a/llvm/include/llvm/DWARFCFIChecker/DWARFCFIState.h b/llvm/include/llvm/DWARFCFIChecker/DWARFCFIState.h new file mode 100644 index 0000000000000..363f76a043bbd --- /dev/null +++ b/llvm/include/llvm/DWARFCFIChecker/DWARFCFIState.h @@ -0,0 +1,52 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file declares DWARFCFIState class. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_DWARFCFICHECKER_UNWINDINFOSTATE_H +#define LLVM_DWARFCFICHECKER_UNWINDINFOSTATE_H + +#include "llvm/DebugInfo/DWARF/LowLevel/DWARFUnwindTable.h" +#include "llvm/MC/MCContext.h" +#include "llvm/MC/MCDwarf.h" +#include + +namespace llvm { + +using DWARFRegNum = uint32_t; + +/// This class is used to maintain a CFI state, referred to as an unwinding row, +/// during CFI analysis. The only way to modify the state is by updating it with +/// a CFI directive. +class DWARFCFIState { +public: + DWARFCFIState(MCContext *Context) : Context(Context), IsInitiated(false) {}; + + std::optional getCurrentUnwindRow() const; + + /// This method updates the state by applying \p Directive to the current + /// state. If the directive is not supported by the checker or any error + /// happens while applying the CFI directive, a warning or error is reported + /// to the user, and the directive is ignored, leaving the state unchanged. + void update(const MCCFIInstruction &Directive); + +private: + dwarf::CFIProgram convert(MCCFIInstruction Directive); + +private: + dwarf::UnwindRow Row; + MCContext *Context; + bool IsInitiated; +}; + +} // namespace llvm + +#endif diff --git a/llvm/lib/CMakeLists.txt b/llvm/lib/CMakeLists.txt index f6465612d30c0..a56183000f085 100644 --- a/llvm/lib/CMakeLists.txt +++ b/llvm/lib/CMakeLists.txt @@ -31,6 +31,7 @@ add_subdirectory(Option) add_subdirectory(Remarks) add_subdirectory(Debuginfod) add_subdirectory(DebugInfo) +add_subdirectory(DWARFCFIChecker) add_subdirectory(DWP) add_subdirectory(ExecutionEngine) add_subdirectory(Target) diff --git a/llvm/lib/DWARFCFIChecker/CMakeLists.txt b/llvm/lib/DWARFCFIChecker/CMakeLists.txt new file mode 100644 index 0000000000000..2d9abad674bfa --- /dev/null +++ b/llvm/lib/DWARFCFIChecker/CMakeLists.txt @@ -0,0 +1,14 @@ +add_llvm_component_library(LLVMDWARFCFIChecker + DWARFCFIAnalysis.cpp + DWARFCFIFunctionFrameAnalyzer.cpp + DWARFCFIFunctionFrameStreamer.cpp + DWARFCFIState.cpp + + ADDITIONAL_HEADER_DIRS + ${LLVM_MAIN_INCLUDE_DIR}/llvm/MCA + + LINK_COMPONENTS + MC + DebugInfoDWARFLowLevel + Support + ) diff --git a/llvm/lib/DWARFCFIChecker/DWARFCFIAnalysis.cpp b/llvm/lib/DWARFCFIChecker/DWARFCFIAnalysis.cpp new file mode 100644 index 0000000000000..f3db54b032b08 --- /dev/null +++ b/llvm/lib/DWARFCFIChecker/DWARFCFIAnalysis.cpp @@ -0,0 +1,331 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +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 +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 +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()}; + } +} + +DWARFCFIAnalysis::DWARFCFIAnalysis(MCContext *Context, MCInstrInfo const &MCII, + bool IsEH, + ArrayRef 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 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 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 &Reads, + const SmallSet &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 &Reads, + const SmallSet &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)); +} diff --git a/llvm/lib/DWARFCFIChecker/DWARFCFIFunctionFrameAnalyzer.cpp b/llvm/lib/DWARFCFIChecker/DWARFCFIFunctionFrameAnalyzer.cpp new file mode 100644 index 0000000000000..250cfdff77777 --- /dev/null +++ b/llvm/lib/DWARFCFIChecker/DWARFCFIFunctionFrameAnalyzer.cpp @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// 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/DWARFCFIFunctionFrameAnalyzer.h" + +using namespace llvm; + +CFIFunctionFrameAnalyzer::~CFIFunctionFrameAnalyzer() { + assert(UIAs.empty() && + "all frames should be closed before the analysis finishes"); +} + +void CFIFunctionFrameAnalyzer::startFunctionFrame( + bool IsEH, ArrayRef Prologue) { + UIAs.emplace_back(&getContext(), MCII, IsEH, Prologue); +} + +void CFIFunctionFrameAnalyzer::emitInstructionAndDirectives( + const MCInst &Inst, ArrayRef Directives) { + assert(!UIAs.empty() && "if the instruction is in a frame, there should be " + "a analysis instantiated for it"); + UIAs.back().update(Inst, Directives); +} + +void CFIFunctionFrameAnalyzer::finishFunctionFrame() { + assert(!UIAs.empty() && "there should be an analysis for each frame"); + UIAs.pop_back(); +} diff --git a/llvm/lib/DWARFCFIChecker/DWARFCFIFunctionFrameStreamer.cpp b/llvm/lib/DWARFCFIChecker/DWARFCFIFunctionFrameStreamer.cpp new file mode 100644 index 0000000000000..1b77584a4789b --- /dev/null +++ b/llvm/lib/DWARFCFIChecker/DWARFCFIFunctionFrameStreamer.cpp @@ -0,0 +1,88 @@ +//===----------------------------------------------------------------------===// +// +// 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/DWARFCFIFunctionFrameStreamer.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/MC/MCContext.h" +#include "llvm/MC/MCDwarf.h" +#include "llvm/MC/MCInst.h" +#include "llvm/MC/MCInstrInfo.h" +#include "llvm/MC/MCStreamer.h" +#include + +using namespace llvm; + +void CFIFunctionFrameStreamer::updateReceiver( + const std::optional &NewInst) { + assert(hasUnfinishedDwarfFrameInfo() && + "should have an unfinished DWARF frame here"); + assert(!FrameIndices.empty() && + "there should be an index available for the current frame"); + assert(FrameIndices.size() == LastInstructions.size()); + assert(LastInstructions.size() == LastDirectiveIndices.size()); + + auto Frames = getDwarfFrameInfos(); + assert(FrameIndices.back() < Frames.size()); + unsigned LastDirectiveIndex = LastDirectiveIndices.back(); + unsigned CurrentDirectiveIndex = + Frames[FrameIndices.back()].Instructions.size(); + assert(CurrentDirectiveIndex >= LastDirectiveIndex); + + const MCDwarfFrameInfo *LastFrame = &Frames[FrameIndices.back()]; + ArrayRef Directives; + if (LastDirectiveIndex < CurrentDirectiveIndex) { + Directives = ArrayRef(LastFrame->Instructions); + Directives = + Directives.drop_front(LastDirectiveIndex) + .drop_back(LastFrame->Instructions.size() - CurrentDirectiveIndex); + } + + auto MaybeLastInstruction = LastInstructions.back(); + if (MaybeLastInstruction) + // The directives are associated with an instruction. + Receiver->emitInstructionAndDirectives(*MaybeLastInstruction, Directives); + else + // The directives are the prologue directives. + Receiver->startFunctionFrame(false /* TODO: should put isEH here */, + Directives); + + // Update the internal state for the top frame. + LastInstructions.back() = NewInst; + LastDirectiveIndices.back() = CurrentDirectiveIndex; +} + +void CFIFunctionFrameStreamer::emitInstruction(const MCInst &Inst, + const MCSubtargetInfo &STI) { + if (hasUnfinishedDwarfFrameInfo()) + // Send the last instruction with the unsent directives already in the frame + // to the receiver. + updateReceiver(Inst); +} + +void CFIFunctionFrameStreamer::emitCFIStartProcImpl(MCDwarfFrameInfo &Frame) { + LastInstructions.push_back(std::nullopt); + LastDirectiveIndices.push_back(0); + FrameIndices.push_back(getNumFrameInfos()); + + MCStreamer::emitCFIStartProcImpl(Frame); +} + +void CFIFunctionFrameStreamer::emitCFIEndProcImpl(MCDwarfFrameInfo &CurFrame) { + // Send the last instruction with the final directives of the current frame to + // the receiver. + updateReceiver(std::nullopt); + + assert(!FrameIndices.empty() && "There should be at least one frame to pop"); + LastDirectiveIndices.pop_back(); + LastInstructions.pop_back(); + FrameIndices.pop_back(); + + Receiver->finishFunctionFrame(); + + MCStreamer::emitCFIEndProcImpl(CurFrame); +} diff --git a/llvm/lib/DWARFCFIChecker/DWARFCFIState.cpp b/llvm/lib/DWARFCFIChecker/DWARFCFIState.cpp new file mode 100644 index 0000000000000..bca820fa807c8 --- /dev/null +++ b/llvm/lib/DWARFCFIChecker/DWARFCFIState.cpp @@ -0,0 +1,167 @@ +//===----------------------------------------------------------------------===// +// +// 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/DWARFCFIState.h" +#include "llvm/BinaryFormat/Dwarf.h" +#include "llvm/DebugInfo/DWARF/LowLevel/DWARFUnwindTable.h" +#include "llvm/MC/MCDwarf.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/FormatVariadic.h" +#include +#include + +using namespace llvm; + +std::optional DWARFCFIState::getCurrentUnwindRow() const { + if (!IsInitiated) + return std::nullopt; + return Row; +} + +void DWARFCFIState::update(const MCCFIInstruction &Directive) { + auto CFIP = convert(Directive); + + // This is a copy of the current row, its value will be updated by + // `parseRows`. + dwarf::UnwindRow NewRow = Row; + + // `parseRows` updates the current row by applying the `CFIProgram` to it. + // During this process, it may create multiple rows preceding the newly + // updated row and following the previous rows. These middle rows are stored + // in `PrecedingRows`. For now, there is no need to store these rows in the + // state, so they are ignored in the end. + dwarf::UnwindTable::RowContainer PrecedingRows; + + // TODO: `.cfi_remember_state` and `.cfi_restore_state` directives are not + // supported yet. The reason is that `parseRows` expects the stack of states + // to be produced and used in a single `CFIProgram`. However, in this use + // case, each instruction creates its own `CFIProgram`, which means the stack + // of states is forgotten between instructions. To fix it, `parseRows` should + // be refactored to read the current stack of states from the argument and + // update it based on the `CFIProgram.` + if (Error Err = parseRows(CFIP, NewRow, nullptr).takeError()) { + Context->reportError( + Directive.getLoc(), + formatv("could not parse this CFI directive due to: {0}", + toString(std::move(Err)))); + + // Proceed the analysis by ignoring this CFI directive. + return; + } + + Row = NewRow; + IsInitiated = true; +} + +dwarf::CFIProgram DWARFCFIState::convert(MCCFIInstruction Directive) { + auto CFIP = dwarf::CFIProgram( + /* CodeAlignmentFactor */ 1, /* DataAlignmentFactor */ 1, + Context->getTargetTriple().getArch()); + + auto MaybeCurrentRow = getCurrentUnwindRow(); + switch (Directive.getOperation()) { + case MCCFIInstruction::OpSameValue: + CFIP.addInstruction(dwarf::DW_CFA_same_value, Directive.getRegister()); + break; + case MCCFIInstruction::OpRememberState: + // TODO: remember state is not supported yet, the following line does not + // work: + // CFIP.addInstruction(dwarf::DW_CFA_remember_state); + // The reason is explained in the `DWARFCFIState::update` method where + // `dwarf::parseRows` is used. + Context->reportWarning(Directive.getLoc(), + "this directive is not supported, ignoring it"); + break; + case MCCFIInstruction::OpRestoreState: + // TODO: restore state is not supported yet, the following line does not + // work: + // CFIP.addInstruction(dwarf::DW_CFA_restore_state); + // The reason is explained in the `DWARFCFIState::update` method where + // `dwarf::parseRows` is used. + Context->reportWarning(Directive.getLoc(), + "this directive is not supported, ignoring it"); + break; + case MCCFIInstruction::OpOffset: + CFIP.addInstruction(dwarf::DW_CFA_offset, Directive.getRegister(), + Directive.getOffset()); + break; + case MCCFIInstruction::OpLLVMDefAspaceCfa: + CFIP.addInstruction(dwarf::DW_CFA_LLVM_def_aspace_cfa, + Directive.getRegister()); + break; + case MCCFIInstruction::OpDefCfaRegister: + CFIP.addInstruction(dwarf::DW_CFA_def_cfa_register, + Directive.getRegister()); + break; + case MCCFIInstruction::OpDefCfaOffset: + CFIP.addInstruction(dwarf::DW_CFA_def_cfa_offset, Directive.getOffset()); + break; + case MCCFIInstruction::OpDefCfa: + CFIP.addInstruction(dwarf::DW_CFA_def_cfa, Directive.getRegister(), + Directive.getOffset()); + break; + case MCCFIInstruction::OpRelOffset: + assert( + IsInitiated && + "cannot define relative offset to a non-existing CFA unwinding rule"); + + CFIP.addInstruction(dwarf::DW_CFA_offset, Directive.getRegister(), + Directive.getOffset() - Row.getCFAValue().getOffset()); + break; + case MCCFIInstruction::OpAdjustCfaOffset: + assert(IsInitiated && + "cannot adjust CFA offset of a non-existing CFA unwinding rule"); + + CFIP.addInstruction(dwarf::DW_CFA_def_cfa_offset, + Directive.getOffset() + Row.getCFAValue().getOffset()); + break; + case MCCFIInstruction::OpEscape: + // TODO: DWARFExpressions are not supported yet, ignoring expression here. + Context->reportWarning(Directive.getLoc(), + "this directive is not supported, ignoring it"); + break; + case MCCFIInstruction::OpRestore: + // The `.cfi_restore register` directive restores the register's unwinding + // information to its CIE value. However, assemblers decide where CIE ends + // and the FDE starts, so the functionality of this directive depends on the + // assembler's decision and cannot be validated. + Context->reportWarning( + Directive.getLoc(), + "this directive behavior depends on the assembler, ignoring it"); + break; + case MCCFIInstruction::OpUndefined: + CFIP.addInstruction(dwarf::DW_CFA_undefined, Directive.getRegister()); + break; + case MCCFIInstruction::OpRegister: + CFIP.addInstruction(dwarf::DW_CFA_register, Directive.getRegister(), + Directive.getRegister2()); + break; + case MCCFIInstruction::OpWindowSave: + CFIP.addInstruction(dwarf::DW_CFA_GNU_window_save); + break; + case MCCFIInstruction::OpNegateRAState: + CFIP.addInstruction(dwarf::DW_CFA_AARCH64_negate_ra_state); + break; + case MCCFIInstruction::OpNegateRAStateWithPC: + CFIP.addInstruction(dwarf::DW_CFA_AARCH64_negate_ra_state_with_pc); + break; + case MCCFIInstruction::OpGnuArgsSize: + CFIP.addInstruction(dwarf::DW_CFA_GNU_args_size); + break; + case MCCFIInstruction::OpLabel: + // `.cfi_label` does not have any functional effect on unwinding process. + break; + case MCCFIInstruction::OpValOffset: + CFIP.addInstruction(dwarf::DW_CFA_val_offset, Directive.getRegister(), + Directive.getOffset()); + break; + } + + return CFIP; +} diff --git a/llvm/lib/DWARFCFIChecker/Registers.h b/llvm/lib/DWARFCFIChecker/Registers.h new file mode 100644 index 0000000000000..262fb5cfa7af9 --- /dev/null +++ b/llvm/lib/DWARFCFIChecker/Registers.h @@ -0,0 +1,66 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains helper functions to find and list registers that are +/// tracked by the unwinding information checker. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_DWARFCFICHECKER_REGISTERS_H +#define LLVM_DWARFCFICHECKER_REGISTERS_H + +#include "llvm/MC/MCRegister.h" +#include "llvm/MC/MCRegisterInfo.h" +#include + +namespace llvm { + +/// This analysis only keeps track and cares about super registers, not the +/// subregisters. All reads from/writes to subregisters are considered the +/// same operation to super registers. +inline bool isSuperReg(const MCRegisterInfo *MCRI, MCPhysReg Reg) { + return MCRI->superregs(Reg).empty(); +} + +inline SmallVector getSuperRegs(const MCRegisterInfo *MCRI) { + SmallVector SuperRegs; + for (auto &&RegClass : MCRI->regclasses()) + for (unsigned I = 0; I < RegClass.getNumRegs(); I++) { + MCPhysReg Reg = RegClass.getRegister(I); + if (isSuperReg(MCRI, Reg)) + SuperRegs.push_back(Reg); + } + + sort(SuperRegs.begin(), SuperRegs.end()); + SuperRegs.resize(std::distance( + SuperRegs.begin(), std::unique(SuperRegs.begin(), SuperRegs.end()))); + return SuperRegs; +} + +inline SmallVector getTrackingRegs(const MCRegisterInfo *MCRI) { + SmallVector TrackingRegs; + for (auto Reg : getSuperRegs(MCRI)) + if (!MCRI->isArtificial(Reg) && !MCRI->isConstant(Reg)) + TrackingRegs.push_back(Reg); + return TrackingRegs; +} + +inline MCPhysReg getSuperReg(const MCRegisterInfo *MCRI, MCPhysReg Reg) { + if (isSuperReg(MCRI, Reg)) + return Reg; + for (auto SuperReg : MCRI->superregs(Reg)) + if (isSuperReg(MCRI, SuperReg)) + return SuperReg; + + llvm_unreachable("Should either be a super reg, or have a super reg"); +} + +} // namespace llvm + +#endif diff --git a/llvm/test/DWARFCFIChecker/X86/cfa-corner-cases.s b/llvm/test/DWARFCFIChecker/X86/cfa-corner-cases.s new file mode 100644 index 0000000000000..94a847a969623 --- /dev/null +++ b/llvm/test/DWARFCFIChecker/X86/cfa-corner-cases.s @@ -0,0 +1,25 @@ +# RUN: not llvm-mc -triple x86_64-pc-linux-gnu %s --validate-cfi --filetype=null 2>&1 | FileCheck %s +# RUN: llvm-mc -triple x86_64-pc-linux-gnu %s --filetype=asm 2>&1 | FileCheck %s -check-prefix=ASSEMBLER --implicit-check-not warning: --implicit-check-not error: +# ASSEMBLER: .text +.text + .globl f + .type f, @function +f: + .cfi_startproc + + ## TODO: Remove these lines when the initial frame directives set the callee saved registers + .cfi_undefined %rax + .cfi_undefined %flags + + .cfi_val_offset %rbp, -8 + + pushq %rax + # CHECK: error: modified CFA register RSP but not changed CFA rule + + addq $10, %rax + # CHECK: error: did not modify CFA register RSP but changed CFA rule + .cfi_adjust_cfa_offset 8 + +.Lfunc_end0: + .size f, .Lfunc_end0-f + .cfi_endproc diff --git a/llvm/test/DWARFCFIChecker/X86/empty-nested-frames.s b/llvm/test/DWARFCFIChecker/X86/empty-nested-frames.s new file mode 100644 index 0000000000000..cbc0689721201 --- /dev/null +++ b/llvm/test/DWARFCFIChecker/X86/empty-nested-frames.s @@ -0,0 +1,16 @@ +# RUN: llvm-mc -triple x86_64-pc-linux-gnu %s --validate-cfi --filetype=null 2>&1 | FileCheck %s --allow-empty --implicit-check-not warning: --implicit-check-not error: +## TODO: `--allow-empty` should be erased and replaced with a simple check for the asm output when `--filetype=asm` is implemented for `--validate-cfi`. +# RUN: llvm-mc -triple x86_64-pc-linux-gnu %s --filetype=asm 2>&1 | FileCheck %s -check-prefix=ASSEMBLER --implicit-check-not warning: --implicit-check-not error: +# ASSEMBLER: .section +.pushsection A +f: +.cfi_startproc +.pushsection B +g: +.cfi_startproc +ret +.cfi_endproc +.popsection +ret +.cfi_endproc +.popsection diff --git a/llvm/test/DWARFCFIChecker/X86/empty-section.s b/llvm/test/DWARFCFIChecker/X86/empty-section.s new file mode 100644 index 0000000000000..8000bbf6d8e02 --- /dev/null +++ b/llvm/test/DWARFCFIChecker/X86/empty-section.s @@ -0,0 +1,12 @@ +# RUN: llvm-mc -triple x86_64-pc-linux-gnu %s --validate-cfi --filetype=null 2>&1 | FileCheck %s --allow-empty --implicit-check-not warning: --implicit-check-not error: +## TODO: `--allow-empty` should be erased and replaced with a simple check for the asm output when `--filetype=asm` is implemented for `--validate-cfi`. +# RUN: llvm-mc -triple x86_64-pc-linux-gnu %s --filetype=asm 2>&1 | FileCheck %s -check-prefix=ASSEMBLER --implicit-check-not warning: --implicit-check-not error: +# ASSEMBLER: .text +.text + .globl f + .type f, @function +f: + .cfi_startproc +.Lfunc_end0: + .size f, .Lfunc_end0-f + .cfi_endproc diff --git a/llvm/test/DWARFCFIChecker/X86/multiple-sections.s b/llvm/test/DWARFCFIChecker/X86/multiple-sections.s new file mode 100644 index 0000000000000..394be6f7eedb3 --- /dev/null +++ b/llvm/test/DWARFCFIChecker/X86/multiple-sections.s @@ -0,0 +1,27 @@ +# RUN: not llvm-mc -triple x86_64-pc-linux-gnu %s --validate-cfi --filetype=null 2>&1 | FileCheck %s +# RUN: llvm-mc -triple x86_64-pc-linux-gnu %s --filetype=asm 2>&1 | FileCheck %s -check-prefix=ASSEMBLER --implicit-check-not warning: --implicit-check-not error: +# ASSEMBLER: .text +.text + .globl f + .type f, @function +f: + .cfi_startproc + pushq %rax + # CHECK: error: modified CFA register RSP but not changed CFA rule + pushq %rax + .cfi_def_cfa %rbp, -24 + pushq %rax + .cfi_endproc + + .cfi_startproc + pushq %rax + # CHECK: error: modified CFA register RSP but not changed CFA rule + pushq %rax + .cfi_def_cfa %rbp, -24 + pushq %rax + .cfi_endproc + + retq + +.Lfunc_end0: + .size f, .Lfunc_end0-f diff --git a/llvm/test/DWARFCFIChecker/X86/nested-frames.s b/llvm/test/DWARFCFIChecker/X86/nested-frames.s new file mode 100644 index 0000000000000..d7f7fa8f58ce2 --- /dev/null +++ b/llvm/test/DWARFCFIChecker/X86/nested-frames.s @@ -0,0 +1,31 @@ +# RUN: not llvm-mc -triple x86_64-pc-linux-gnu %s --validate-cfi --filetype=null 2>&1 | FileCheck %s +# RUN: llvm-mc -triple x86_64-pc-linux-gnu %s --filetype=asm 2>&1 | FileCheck %s -check-prefix=ASSEMBLER --implicit-check-not warning: --implicit-check-not error: +# ASSEMBLER: .section +.pushsection A +f: +.cfi_startproc +## TODO: Remove this line when the initial frame directives set the callee saved registers +.cfi_undefined %flags +addq $10, %rbp +# CHECK: error: changed register RBP, that register RBP's unwinding rule uses, but there is no CFI directives about it +nop +.cfi_undefined %rbp + +.pushsection B +g: +.cfi_startproc +## TODO: Remove this line when the initial frame directives set the callee saved registers +.cfi_undefined %flags +addq $10, %rbp +# CHECK: error: changed register RBP, that register RBP's unwinding rule uses, but there is no CFI directives about it +nop +.cfi_undefined %rsi +ret +.cfi_endproc +.popsection + +addq $10, %rsi +# CHECK: error: changed register RSI, that register RSI's unwinding rule uses, but there is no CFI directives about it +ret +.cfi_endproc +.popsection diff --git a/llvm/test/DWARFCFIChecker/X86/single-func-cfa-mistake.s b/llvm/test/DWARFCFIChecker/X86/single-func-cfa-mistake.s new file mode 100644 index 0000000000000..97726ef0a50b6 --- /dev/null +++ b/llvm/test/DWARFCFIChecker/X86/single-func-cfa-mistake.s @@ -0,0 +1,39 @@ +# RUN: llvm-mc -triple x86_64-pc-linux-gnu %s --validate-cfi --filetype=null 2>&1 | FileCheck %s +# RUN: llvm-mc -triple x86_64-pc-linux-gnu %s --filetype=asm 2>&1 | FileCheck %s -check-prefix=ASSEMBLER --implicit-check-not warning: --implicit-check-not error: +# ASSEMBLER: .text + .text + .globl f + .type f,@function +f: + .cfi_startproc + + ## TODO: Remove these lines when the initial frame directives set the callee saved registers + .cfi_undefined %rax + .cfi_undefined %flags + + pushq %rbp + # CHECK: warning: CFA offset is changed from 8 to 17, and CFA register RSP is modified, but validating the modification amount is not implemented yet + # CHECK: warning: validating changes happening to register RBP unwinding rule structure is not implemented yet + .cfi_def_cfa_offset 17 + .cfi_offset %rbp, -16 + + movq %rsp, %rbp + # CHECK: warning: CFA register changed from register RSP to register RBP, validating this change is not implemented yet + .cfi_def_cfa_register %rbp + + movl %edi, -4(%rbp) + + movl -4(%rbp), %eax + + addl $10, %eax + + popq %rbp + # CHECK: warning: CFA register changed from register RBP to register RSP, validating this change is not implemented yet + .cfi_def_cfa %rsp, 8 + + retq + +.Lfunc_end0: + .size f, .Lfunc_end0-f + + .cfi_endproc diff --git a/llvm/test/DWARFCFIChecker/X86/single-func-missed-cfi-directive.s b/llvm/test/DWARFCFIChecker/X86/single-func-missed-cfi-directive.s new file mode 100644 index 0000000000000..7596250124ac7 --- /dev/null +++ b/llvm/test/DWARFCFIChecker/X86/single-func-missed-cfi-directive.s @@ -0,0 +1,34 @@ +# RUN: not llvm-mc -triple x86_64-pc-linux-gnu %s --validate-cfi --filetype=null 2>&1 | FileCheck %s +# RUN: llvm-mc -triple x86_64-pc-linux-gnu %s --filetype=asm 2>&1 | FileCheck %s -check-prefix=ASSEMBLER --implicit-check-not warning: --implicit-check-not error: +# ASSEMBLER: .text + .text + .globl f + .type f,@function +f: + .cfi_startproc + + .cfi_undefined %rax + + pushq %rbp + .cfi_def_cfa_offset 16 + + movq %rsp, %rbp + # CHECK: error: changed register RBP, that register RBP's unwinding rule uses, but there is no CFI directives about it + .cfi_def_cfa_register %rbp + + movl %edi, -4(%rbp) + + movl -4(%rbp), %eax + + addl $10, %eax + + popq %rbp + # CHECK: error: changed register RBP, that register RBP's unwinding rule uses, but there is no CFI directives about it + .cfi_def_cfa %rsp, 8 + + retq + +.Lfunc_end0: + .size f, .Lfunc_end0-f + + .cfi_endproc diff --git a/llvm/test/DWARFCFIChecker/X86/single-func.s b/llvm/test/DWARFCFIChecker/X86/single-func.s new file mode 100644 index 0000000000000..2d44da9958321 --- /dev/null +++ b/llvm/test/DWARFCFIChecker/X86/single-func.s @@ -0,0 +1,39 @@ +# RUN: llvm-mc -triple x86_64-pc-linux-gnu %s --validate-cfi --filetype=null 2>&1 | FileCheck %s +# RUN: llvm-mc -triple x86_64-pc-linux-gnu %s --filetype=asm 2>&1 | FileCheck %s -check-prefix=ASSEMBLER --implicit-check-not warning: --implicit-check-not error: +# ASSEMBLER: .text + .text + .globl f + .type f,@function +f: + .cfi_startproc + + ## TODO: Remove these lines when the initial frame directives set the callee saved registers + .cfi_undefined %rax + .cfi_undefined %flags + + pushq %rbp + # CHECK: warning: CFA offset is changed from 8 to 16, and CFA register RSP is modified, but validating the modification amount is not implemented yet + # CHECK: warning: validating changes happening to register RBP unwinding rule structure is not implemented yet + .cfi_def_cfa_offset 16 + .cfi_offset %rbp, -16 + + movq %rsp, %rbp + # CHECK: warning: CFA register changed from register RSP to register RBP, validating this change is not implemented yet + .cfi_def_cfa_register %rbp + + movl %edi, -4(%rbp) + + movl -4(%rbp), %eax + + addl $10, %eax + + popq %rbp + # CHECK: warning: CFA register changed from register RBP to register RSP, validating this change is not implemented yet + .cfi_def_cfa %rsp, 8 + + retq + +.Lfunc_end0: + .size f, .Lfunc_end0-f + + .cfi_endproc diff --git a/llvm/test/DWARFCFIChecker/X86/spill-two-reg-reversed.s b/llvm/test/DWARFCFIChecker/X86/spill-two-reg-reversed.s new file mode 100644 index 0000000000000..babd8123b885f --- /dev/null +++ b/llvm/test/DWARFCFIChecker/X86/spill-two-reg-reversed.s @@ -0,0 +1,46 @@ +# RUN: not llvm-mc -triple x86_64-pc-linux-gnu %s --validate-cfi --filetype=null 2>&1 | FileCheck %s +# RUN: llvm-mc -triple x86_64-pc-linux-gnu %s --filetype=asm 2>&1 | FileCheck %s -check-prefix=ASSEMBLER --implicit-check-not warning: --implicit-check-not error: +# ASSEMBLER: .text + .text + .type _start,@function + .globl _start + .hidden _start +_start: + .cfi_startproc + + .cfi_same_value %rdi + .cfi_same_value %rsi + + pushq %rbp + .cfi_adjust_cfa_offset 8 + .cfi_offset %rbp, -16 + + movq %rsp, %rbp + + pushq %rdi + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rdi, 0 + + pushq %rsi + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rsi, 0 + + popq %rsi + .cfi_adjust_cfa_offset -8 + .cfi_same_value %rdi + + popq %rdi + # CHECK: error: changed register RDI, that register RDI's unwinding rule uses, but there is no CFI directives about it + .cfi_adjust_cfa_offset -8 + .cfi_same_value %rsi + + popq %rbp + .cfi_adjust_cfa_offset -8 + .cfi_same_value %rbp + + retq + + .cfi_endproc +.Ltmp0: + .size _start, .Ltmp0-_start + .text diff --git a/llvm/test/DWARFCFIChecker/X86/spill-two-reg.s b/llvm/test/DWARFCFIChecker/X86/spill-two-reg.s new file mode 100644 index 0000000000000..a0015e1256f26 --- /dev/null +++ b/llvm/test/DWARFCFIChecker/X86/spill-two-reg.s @@ -0,0 +1,58 @@ +# RUN: llvm-mc -triple x86_64-pc-linux-gnu %s --validate-cfi --filetype=null 2>&1 | FileCheck %s +## TODO: Should check no warnings are emitted but for now, the tool is naive and emitting warnings for every change. +# RUN: llvm-mc -triple x86_64-pc-linux-gnu %s --filetype=asm 2>&1 | FileCheck %s -check-prefix=ASSEMBLER --implicit-check-not warning: --implicit-check-not error: +# ASSEMBLER: .text + .text + .type _start,@function + .globl _start + .hidden _start +_start: + .cfi_startproc + + .cfi_same_value %rdi + .cfi_same_value %rsi + + pushq %rbp + # CHECK: warning: CFA offset is changed from 8 to 16, and CFA register RSP is modified, but validating the modification amount is not implemented yet + # CHECK: warning: validating changes happening to register RBP unwinding rule structure is not implemented yet + .cfi_adjust_cfa_offset 8 + .cfi_offset %rbp, -16 + + movq %rsp, %rbp + + pushq %rdi + # CHECK: warning: CFA offset is changed from 16 to 24, and CFA register RSP is modified, but validating the modification amount is not implemented yet + # CHECK: warning: validating changes happening to register RDI unwinding rule structure is not implemented yet + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rdi, 0 + + pushq %rsi + # CHECK: warning: CFA offset is changed from 24 to 32, and CFA register RSP is modified, but validating the modification amount is not implemented yet + # CHECK: warning: validating changes happening to register RSI unwinding rule structure is not implemented yet + .cfi_adjust_cfa_offset 8 + .cfi_rel_offset %rsi, 0 + + popq %rsi + # CHECK: warning: CFA offset is changed from 32 to 24, and CFA register RSP is modified, but validating the modification amount is not implemented yet + # CHECK: warning: validating changes happening to register RSI unwinding rule structure is not implemented yet + .cfi_adjust_cfa_offset -8 + .cfi_same_value %rsi + + popq %rdi + # CHECK: warning: CFA offset is changed from 24 to 16, and CFA register RSP is modified, but validating the modification amount is not implemented yet + # CHECK: warning: validating changes happening to register RDI unwinding rule structure is not implemented yet + .cfi_adjust_cfa_offset -8 + .cfi_same_value %rdi + + popq %rbp + # CHECK: warning: CFA offset is changed from 16 to 8, and CFA register RSP is modified, but validating the modification amount is not implemented yet + # CHECK: warning: validating changes happening to register RBP unwinding rule structure is not implemented yet + .cfi_adjust_cfa_offset -8 + .cfi_same_value %rbp + + retq + + .cfi_endproc +.Ltmp0: + .size _start, .Ltmp0-_start + .text diff --git a/llvm/test/DWARFCFIChecker/X86/unsupported-directives.s b/llvm/test/DWARFCFIChecker/X86/unsupported-directives.s new file mode 100644 index 0000000000000..9b02b463a0760 --- /dev/null +++ b/llvm/test/DWARFCFIChecker/X86/unsupported-directives.s @@ -0,0 +1,20 @@ +# RUN: llvm-mc -triple x86_64-pc-linux-gnu %s --validate-cfi --filetype=null 2>&1 | FileCheck %s +# RUN: llvm-mc -triple x86_64-pc-linux-gnu %s --filetype=asm 2>&1 | FileCheck %s -check-prefix=ASSEMBLER --implicit-check-not warning: --implicit-check-not error: +# ASSEMBLER: .text +.text + .globl f + .type f, @function +f: + .cfi_startproc + + .cfi_remember_state + # CHECK: warning: this directive is not supported, ignoring it + .cfi_escape 0 + # CHECK: warning: this directive is not supported, ignoring it + .cfi_restore %rsp + # CHECK: warning: this directive behavior depends on the assembler, ignoring it + .cfi_restore_state + # CHECK: warning: this directive is not supported, ignoring it +.Lfunc_end0: + .size f, .Lfunc_end0-f + .cfi_endproc diff --git a/llvm/test/DWARFCFIChecker/X86/update-with-no-cfi.s b/llvm/test/DWARFCFIChecker/X86/update-with-no-cfi.s new file mode 100644 index 0000000000000..18ed0bc6a291a --- /dev/null +++ b/llvm/test/DWARFCFIChecker/X86/update-with-no-cfi.s @@ -0,0 +1,32 @@ +# RUN: not llvm-mc -triple x86_64-pc-linux-gnu %s --validate-cfi --filetype=null 2>&1 | FileCheck %s +# RUN: llvm-mc -triple x86_64-pc-linux-gnu %s --filetype=asm 2>&1 | FileCheck %s -check-prefix=ASSEMBLER --implicit-check-not warning: --implicit-check-not error: +# ASSEMBLER: .text + .text + .globl f + .type f,@function +f: + .cfi_startproc + + .cfi_same_value %rax + .cfi_same_value %rbx + .cfi_same_value %rcx + .cfi_same_value %rdx + + movq $10, %rax + # CHECK: error: changed register RAX, that register RAX's unwinding rule uses, but there is no CFI directives about it + + movq $10, %rbx + # CHECK: error: changed register RBX, that register RBX's unwinding rule uses, but there is no CFI directives about it + + movq $10, %rcx + # CHECK: error: changed register RCX, that register RCX's unwinding rule uses, but there is no CFI directives about it + + movq $10, %rdx + # CHECK: error: changed register RDX, that register RDX's unwinding rule uses, but there is no CFI directives about it + + retq + +.Lfunc_end0: + .size f, .Lfunc_end0-f + + .cfi_endproc diff --git a/llvm/tools/llvm-mc/CMakeLists.txt b/llvm/tools/llvm-mc/CMakeLists.txt index f57356f9ee0c0..67ac890966fa6 100644 --- a/llvm/tools/llvm-mc/CMakeLists.txt +++ b/llvm/tools/llvm-mc/CMakeLists.txt @@ -7,9 +7,11 @@ set(LLVM_LINK_COMPONENTS MCParser Support TargetParser + DWARFCFIChecker ) add_llvm_tool(llvm-mc llvm-mc.cpp Disassembler.cpp ) + diff --git a/llvm/tools/llvm-mc/llvm-mc.cpp b/llvm/tools/llvm-mc/llvm-mc.cpp index b59a54d8fbc8a..da89af71bbbe8 100644 --- a/llvm/tools/llvm-mc/llvm-mc.cpp +++ b/llvm/tools/llvm-mc/llvm-mc.cpp @@ -12,6 +12,8 @@ //===----------------------------------------------------------------------===// #include "Disassembler.h" +#include "llvm/DWARFCFIChecker/DWARFCFIFunctionFrameAnalyzer.h" +#include "llvm/DWARFCFIChecker/DWARFCFIFunctionFrameStreamer.h" #include "llvm/MC/MCAsmBackend.h" #include "llvm/MC/MCAsmInfo.h" #include "llvm/MC/MCCodeEmitter.h" @@ -38,6 +40,7 @@ #include "llvm/Support/ToolOutputFile.h" #include "llvm/Support/WithColor.h" #include "llvm/TargetParser/Host.h" +#include using namespace llvm; @@ -212,6 +215,10 @@ static cl::opt NoExecStack("no-exec-stack", cl::desc("File doesn't need an exec stack"), cl::cat(MCCategory)); +static cl::opt ValidateCFI("validate-cfi", + cl::desc("Validate the CFI directives"), + cl::cat(MCCategory)); + enum ActionType { AC_AsLex, AC_Assemble, @@ -520,7 +527,16 @@ int main(int argc, char **argv) { assert(MCII && "Unable to create instruction info!"); std::unique_ptr IP; - if (FileType == OFT_AssemblyFile) { + if (ValidateCFI) { + // TODO: The DWARF CFI checker support for emitting anything other than + // errors and warnings has not been implemented yet. Because of this, it is + // assert-checked that the filetype output is null. + assert(FileType == OFT_Null); + auto FFA = std::make_unique(Ctx, *MCII); + auto FFS = std::make_unique(Ctx, std::move(FFA)); + TheTarget->createNullTargetStreamer(*FFS); + Str = std::move(FFS); + } else if (FileType == OFT_AssemblyFile) { IP.reset(TheTarget->createMCInstPrinter( Triple(TripleName), OutputAsmVariant, *MAI, *MCII, *MRI));