Skip to content

Commit db03408

Browse files
authored
Minimal unwinding information (DWARF CFI) checker (#145633)
This PR adds a minimal version of `UnwindInfoChecker` described in [here](https://discourse.llvm.org/t/rfc-dwarf-cfi-validation/86936). This implementation looks into the modified registers by each instruction and checks: - If one of them is the CFA register, and it informs the user if the CFA data is not modified as well. - If one of them is used in another register's unwinding rule, it informs the user if the unwind info is not modified after this instruction. This implementation does not support DWARF expressions and treats them as unknown unwinding rules.
1 parent be19a27 commit db03408

26 files changed

+1448
-1
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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+
/// \file
10+
/// This file declares `DWARFCFIAnalysis` class.
11+
/// `DWARFCFIAnalysis` is a minimal implementation of a DWARF CFI checker
12+
/// described in this link:
13+
/// https://discourse.llvm.org/t/rfc-dwarf-cfi-validation/86936
14+
///
15+
/// The goal of the checker is to validate DWARF CFI directives using the
16+
/// prologue directives and the machine instructions. The main proposed
17+
/// algorithm validates the directives by comparing the CFI state in each
18+
/// instruction with the state achieved by abstract execution of the instruction
19+
/// on the CFI state. However, the current version implemented here is a simple
20+
/// conditional check based on the registers modified by each instruction.
21+
///
22+
//===----------------------------------------------------------------------===//
23+
24+
#ifndef LLVM_DWARFCFICHECKER_DWARFCFIANALYSIS_H
25+
#define LLVM_DWARFCFICHECKER_DWARFCFIANALYSIS_H
26+
27+
#include "DWARFCFIState.h"
28+
#include "llvm/ADT/ArrayRef.h"
29+
#include "llvm/ADT/SmallSet.h"
30+
#include "llvm/ADT/SmallVector.h"
31+
#include "llvm/DebugInfo/DWARF/LowLevel/DWARFUnwindTable.h"
32+
#include "llvm/MC/MCContext.h"
33+
#include "llvm/MC/MCDwarf.h"
34+
#include "llvm/MC/MCExpr.h"
35+
#include "llvm/MC/MCInst.h"
36+
#include "llvm/MC/MCInstrInfo.h"
37+
#include "llvm/MC/MCRegisterInfo.h"
38+
#include "llvm/MC/MCStreamer.h"
39+
#include "llvm/MC/MCSubtargetInfo.h"
40+
#include "llvm/MC/TargetRegistry.h"
41+
42+
namespace llvm {
43+
44+
/// `DWARFCFIAnalysis` validates the DWARF Call Frame Information one machine
45+
/// instruction at a time. This class maintains an internal CFI state
46+
/// initialized with the prologue directives and updated with each instruction's
47+
/// associated directives. In each update, it checks if the machine
48+
/// instruction changes the CFI state in a way that matches the changes
49+
/// from the CFI directives. This checking may results in errors and warnings.
50+
///
51+
/// In current stage, the analysis is only aware of what registers the
52+
/// instruction modifies. If the modification is happening to a sub-register,
53+
/// the analysis considers the super-register is modified.
54+
///
55+
/// In each update, for each register (or CFA), the following cases can happen:
56+
/// 1. The unwinding rule is not changed:
57+
/// a. The registers involved in this rule are not modified: the analysis
58+
/// proceeds without emitting error or warning.
59+
/// b. The registers involved in this rule are modified: it emits an error.
60+
/// 2. The unwinding rule is changed:
61+
/// a. The rule is structurally modified (i.e., the location is changed): It
62+
/// emits a warning.
63+
/// b. The rule is structurally the same, but the register set is changed: it
64+
/// emits a warning.
65+
/// c. The rule is structurally the same, using the same set of registers, but
66+
/// the offset is changed:
67+
/// i. If the registers included in the rule are modified as well: It
68+
/// emits a warning.
69+
/// ii. If the registers included in the rule are not modified: It emits an
70+
/// error.
71+
///
72+
/// The analysis only checks the CFA unwinding rule when the rule is a register
73+
/// plus some offset. Therefore, for CFA, only cases 1, 2.b, and 2.c are
74+
/// checked, and in all other case(s), a warning is emitted.
75+
class DWARFCFIAnalysis {
76+
public:
77+
DWARFCFIAnalysis(MCContext *Context, MCInstrInfo const &MCII, bool IsEH,
78+
ArrayRef<MCCFIInstruction> Prologue);
79+
80+
void update(const MCInst &Inst, ArrayRef<MCCFIInstruction> Directives);
81+
82+
private:
83+
void checkRegDiff(const MCInst &Inst, DWARFRegNum Reg,
84+
const dwarf::UnwindRow &PrevRow,
85+
const dwarf::UnwindRow &NextRow,
86+
const SmallSet<DWARFRegNum, 4> &Reads,
87+
const SmallSet<DWARFRegNum, 4> &Writes);
88+
89+
void checkCFADiff(const MCInst &Inst, const dwarf::UnwindRow &PrevRow,
90+
const dwarf::UnwindRow &NextRow,
91+
const SmallSet<DWARFRegNum, 4> &Reads,
92+
const SmallSet<DWARFRegNum, 4> &Writes);
93+
94+
private:
95+
DWARFCFIState State;
96+
MCContext *Context;
97+
MCInstrInfo const &MCII;
98+
MCRegisterInfo const *MCRI;
99+
bool IsEH;
100+
};
101+
102+
} // namespace llvm
103+
104+
#endif
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
/// \file
10+
/// This file declares CFIFunctionFrameAnalyzer class.
11+
///
12+
//===----------------------------------------------------------------------===//
13+
14+
#ifndef LLVM_DWARFCFICHECKER_DWARFCFIFUNCTIONFRAMEANALYZER_H
15+
#define LLVM_DWARFCFICHECKER_DWARFCFIFUNCTIONFRAMEANALYZER_H
16+
17+
#include "DWARFCFIAnalysis.h"
18+
#include "DWARFCFIFunctionFrameReceiver.h"
19+
#include "llvm/ADT/ArrayRef.h"
20+
#include "llvm/ADT/SmallVector.h"
21+
22+
namespace llvm {
23+
24+
/// This class implements the `CFIFunctionFrameReceiver` interface to validate
25+
/// Call Frame Information in a stream of function frames. For validation, it
26+
/// instantiates a `DWARFCFIAnalysis` for each frame. The errors/warnings are
27+
/// emitted through the `MCContext` instance to the constructor. If a frame
28+
/// finishes without being started or if all the frames are not finished before
29+
/// this classes is destructed, the program fails through an assertion.
30+
class CFIFunctionFrameAnalyzer : public CFIFunctionFrameReceiver {
31+
public:
32+
CFIFunctionFrameAnalyzer(MCContext &Context, const MCInstrInfo &MCII)
33+
: CFIFunctionFrameReceiver(Context), MCII(MCII) {}
34+
~CFIFunctionFrameAnalyzer();
35+
36+
void startFunctionFrame(bool IsEH,
37+
ArrayRef<MCCFIInstruction> Prologue) override;
38+
void
39+
emitInstructionAndDirectives(const MCInst &Inst,
40+
ArrayRef<MCCFIInstruction> Directives) override;
41+
void finishFunctionFrame() override;
42+
43+
private:
44+
MCInstrInfo const &MCII;
45+
SmallVector<DWARFCFIAnalysis> UIAs;
46+
};
47+
48+
} // namespace llvm
49+
50+
#endif
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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+
/// \file
10+
/// This file declares CFIFunctionFrameReceiver class.
11+
///
12+
//===----------------------------------------------------------------------===//
13+
14+
#ifndef LLVM_DWARFCFICHECKER_DWARFCFIFUNCTIONFRAMERECEIVER_H
15+
#define LLVM_DWARFCFICHECKER_DWARFCFIFUNCTIONFRAMERECEIVER_H
16+
17+
#include "llvm/ADT/ArrayRef.h"
18+
19+
namespace llvm {
20+
21+
class MCCFIInstruction;
22+
class MCContext;
23+
class MCInst;
24+
25+
/// This abstract base class is an interface for receiving DWARF function frames
26+
/// Call Frame Information. `DWARFCFIFunctionFrameStreamer` channels the
27+
/// function frames information gathered from an `MCStreamer` using a pointer to
28+
/// an instance of this class for the whole program.
29+
class CFIFunctionFrameReceiver {
30+
public:
31+
CFIFunctionFrameReceiver(const CFIFunctionFrameReceiver &) = delete;
32+
CFIFunctionFrameReceiver &
33+
operator=(const CFIFunctionFrameReceiver &) = delete;
34+
virtual ~CFIFunctionFrameReceiver() = default;
35+
36+
CFIFunctionFrameReceiver(MCContext &Context) : Context(Context) {}
37+
38+
MCContext &getContext() const { return Context; }
39+
40+
virtual void startFunctionFrame(bool IsEH,
41+
ArrayRef<MCCFIInstruction> Prologue) {}
42+
/// Instructions are processed in the program order.
43+
virtual void
44+
emitInstructionAndDirectives(const MCInst &Inst,
45+
ArrayRef<MCCFIInstruction> Directives) {}
46+
virtual void finishFunctionFrame() {}
47+
48+
private:
49+
MCContext &Context;
50+
};
51+
52+
} // namespace llvm
53+
54+
#endif
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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+
/// \file
10+
/// This file declares CFIFunctionFrameStreamer class.
11+
///
12+
//===----------------------------------------------------------------------===//
13+
14+
#ifndef LLVM_DWARFCFICHECKER_DWARFCFIFUNCTIONFRAMESTREAMER_H
15+
#define LLVM_DWARFCFICHECKER_DWARFCFIFUNCTIONFRAMESTREAMER_H
16+
17+
#include "DWARFCFIFunctionFrameReceiver.h"
18+
#include "llvm/ADT/SmallVector.h"
19+
#include "llvm/MC/MCContext.h"
20+
#include "llvm/MC/MCDwarf.h"
21+
#include "llvm/MC/MCInstrInfo.h"
22+
#include "llvm/MC/MCStreamer.h"
23+
#include <memory>
24+
#include <optional>
25+
26+
namespace llvm {
27+
28+
/// This class is an `MCStreamer` implementation that watches for machine
29+
/// instructions and CFI directives. It cuts the stream into function frames and
30+
/// channels them to `CFIFunctionFrameReceiver`. A function frame is the machine
31+
/// instructions and CFI directives that are between `.cfi_startproc` and
32+
/// `.cfi_endproc` directives.
33+
class CFIFunctionFrameStreamer : public MCStreamer {
34+
public:
35+
CFIFunctionFrameStreamer(MCContext &Context,
36+
std::unique_ptr<CFIFunctionFrameReceiver> Receiver)
37+
: MCStreamer(Context), Receiver(std::move(Receiver)) {
38+
assert(this->Receiver && "Receiver should not be null");
39+
}
40+
41+
bool hasRawTextSupport() const override { return true; }
42+
void emitRawTextImpl(StringRef String) override {}
43+
44+
bool emitSymbolAttribute(MCSymbol *Symbol, MCSymbolAttr Attribute) override {
45+
return true;
46+
}
47+
48+
void emitCommonSymbol(MCSymbol *Symbol, uint64_t Size,
49+
Align ByteAlignment) override {}
50+
void emitSubsectionsViaSymbols() override {};
51+
void beginCOFFSymbolDef(const MCSymbol *Symbol) override {}
52+
void emitCOFFSymbolStorageClass(int StorageClass) override {}
53+
void emitCOFFSymbolType(int Type) override {}
54+
void endCOFFSymbolDef() override {}
55+
void emitXCOFFSymbolLinkageWithVisibility(MCSymbol *Symbol,
56+
MCSymbolAttr Linkage,
57+
MCSymbolAttr Visibility) override {}
58+
59+
void emitInstruction(const MCInst &Inst, const MCSubtargetInfo &STI) override;
60+
void emitCFIStartProcImpl(MCDwarfFrameInfo &Frame) override;
61+
void emitCFIEndProcImpl(MCDwarfFrameInfo &CurFrame) override;
62+
63+
private:
64+
/// This method sends the last instruction, along with its associated
65+
/// directives, to the receiver and then updates the internal state of the
66+
/// class. It moves the directive index to after the last directive and sets
67+
/// the last instruction to \p NewInst . This method assumes it is called in
68+
/// the middle of an unfinished DWARF debug frame; if not, an assertion will
69+
/// fail.
70+
void updateReceiver(const std::optional<MCInst> &NewInst);
71+
72+
private:
73+
/// The following fields are stacks that store the state of the stream sent to
74+
/// the receiver in each frame. This class, like `MCStreamer`, assumes that
75+
/// the debug frames are intertwined with each other only in stack form.
76+
77+
/// The last instruction that is not sent to the receiver for each frame.
78+
SmallVector<std::optional<MCInst>> LastInstructions;
79+
/// The index of the last directive that is not sent to the receiver for each
80+
/// frame.
81+
SmallVector<unsigned> LastDirectiveIndices;
82+
/// The index of each frame in `DwarfFrameInfos` field in `MCStreamer`.
83+
SmallVector<unsigned> FrameIndices;
84+
85+
std::unique_ptr<CFIFunctionFrameReceiver> Receiver;
86+
};
87+
88+
} // namespace llvm
89+
90+
#endif
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
/// \file
10+
/// This file declares DWARFCFIState class.
11+
///
12+
//===----------------------------------------------------------------------===//
13+
14+
#ifndef LLVM_DWARFCFICHECKER_UNWINDINFOSTATE_H
15+
#define LLVM_DWARFCFICHECKER_UNWINDINFOSTATE_H
16+
17+
#include "llvm/DebugInfo/DWARF/LowLevel/DWARFUnwindTable.h"
18+
#include "llvm/MC/MCContext.h"
19+
#include "llvm/MC/MCDwarf.h"
20+
#include <optional>
21+
22+
namespace llvm {
23+
24+
using DWARFRegNum = uint32_t;
25+
26+
/// This class is used to maintain a CFI state, referred to as an unwinding row,
27+
/// during CFI analysis. The only way to modify the state is by updating it with
28+
/// a CFI directive.
29+
class DWARFCFIState {
30+
public:
31+
DWARFCFIState(MCContext *Context) : Context(Context), IsInitiated(false) {};
32+
33+
std::optional<dwarf::UnwindRow> getCurrentUnwindRow() const;
34+
35+
/// This method updates the state by applying \p Directive to the current
36+
/// state. If the directive is not supported by the checker or any error
37+
/// happens while applying the CFI directive, a warning or error is reported
38+
/// to the user, and the directive is ignored, leaving the state unchanged.
39+
void update(const MCCFIInstruction &Directive);
40+
41+
private:
42+
dwarf::CFIProgram convert(MCCFIInstruction Directive);
43+
44+
private:
45+
dwarf::UnwindRow Row;
46+
MCContext *Context;
47+
bool IsInitiated;
48+
};
49+
50+
} // namespace llvm
51+
52+
#endif

llvm/lib/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ add_subdirectory(Option)
3131
add_subdirectory(Remarks)
3232
add_subdirectory(Debuginfod)
3333
add_subdirectory(DebugInfo)
34+
add_subdirectory(DWARFCFIChecker)
3435
add_subdirectory(DWP)
3536
add_subdirectory(ExecutionEngine)
3637
add_subdirectory(Target)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
add_llvm_component_library(LLVMDWARFCFIChecker
2+
DWARFCFIAnalysis.cpp
3+
DWARFCFIFunctionFrameAnalyzer.cpp
4+
DWARFCFIFunctionFrameStreamer.cpp
5+
DWARFCFIState.cpp
6+
7+
ADDITIONAL_HEADER_DIRS
8+
${LLVM_MAIN_INCLUDE_DIR}/llvm/MCA
9+
10+
LINK_COMPONENTS
11+
MC
12+
DebugInfoDWARFLowLevel
13+
Support
14+
)

0 commit comments

Comments
 (0)