Parse CFI instructions to create SFrame FREs (#155496)
This PR parses CFI instructions to generate FREs.
Unfortunately, actually emitting the FREs into the object file is
somewhat involved and would make this PR quite a bit harder to review.
And the dumper itself properly errors if the FRE count is included in
the header or FDEs, but they are not actually emitted. So actually
testing that the proper FREs are generated will have to wait for a
subsequent PR.
For now, just check for common issues with CFI that sframe doesn't
support, and that proper error handling is done.
diff --git a/llvm/lib/MC/MCSFrame.cpp b/llvm/lib/MC/MCSFrame.cpp
index ee17b77..a0d6c80 100644
--- a/llvm/lib/MC/MCSFrame.cpp
+++ b/llvm/lib/MC/MCSFrame.cpp
@@ -21,6 +21,24 @@
namespace {
+// High-level structure to track info needed to emit a
+// sframe_frame_row_entry_addrX. On disk these have both a fixed portion of type
+// sframe_frame_row_entry_addrX and trailing data of X * S bytes, where X is the
+// datum size, and S is 1, 2, or 3 depending on which of CFA, SP, and FP are
+// being tracked.
+struct SFrameFRE {
+ // An FRE describes how to find the registers when the PC is at this
+ // Label from function start.
+ const MCSymbol *Label = nullptr;
+ size_t CFAOffset = 0;
+ size_t FPOffset = 0;
+ size_t RAOffset = 0;
+ bool FromFP = false;
+ bool CFARegSet = false;
+
+ SFrameFRE(const MCSymbol *Start) : Label(Start) {}
+};
+
// High-level structure to track info needed to emit a sframe_func_desc_entry
// and its associated FREs.
struct SFrameFDE {
@@ -28,6 +46,8 @@
const MCDwarfFrameInfo &DFrame;
// Label where this FDE's FREs start.
MCSymbol *FREStart;
+ // Unwinding fres
+ SmallVector<SFrameFRE> FREs;
SFrameFDE(const MCDwarfFrameInfo &DF, MCSymbol *FRES)
: DFrame(DF), FREStart(FRES) {}
@@ -53,7 +73,8 @@
MCFixup::getDataKindForSize(4)));
S.emitInt32(0);
- // sfde_func_start_num_fres
+ // sfde_func_num_fres
+ // TODO: When we actually emit fres, replace 0 with FREs.size()
S.emitInt32(0);
// sfde_func_info word
@@ -76,10 +97,90 @@
MCObjectStreamer &Streamer;
SmallVector<SFrameFDE> FDEs;
ABI SFrameABI;
+ // Target-specific convenience variables to detect when a CFI instruction
+ // references these registers. Unlike in dwarf frame descriptions, they never
+ // escape into the sframe section itself.
+ unsigned SPReg;
+ unsigned FPReg;
+ unsigned RAReg;
MCSymbol *FDESubSectionStart;
MCSymbol *FRESubSectionStart;
MCSymbol *FRESubSectionEnd;
+ bool setCFARegister(SFrameFRE &FRE, const MCCFIInstruction &I) {
+ if (I.getRegister() == SPReg) {
+ FRE.CFARegSet = true;
+ FRE.FromFP = false;
+ return true;
+ }
+ if (I.getRegister() == FPReg) {
+ FRE.CFARegSet = true;
+ FRE.FromFP = true;
+ return true;
+ }
+ Streamer.getContext().reportWarning(
+ I.getLoc(), "canonical Frame Address not in stack- or frame-pointer. "
+ "Omitting SFrame unwind info for this function");
+ return false;
+ }
+
+ bool setCFAOffset(SFrameFRE &FRE, const SMLoc &Loc, size_t Offset) {
+ if (!FRE.CFARegSet) {
+ Streamer.getContext().reportWarning(
+ Loc, "adjusting CFA offset without a base register. "
+ "Omitting SFrame unwind info for this function");
+ return false;
+ }
+ FRE.CFAOffset = Offset;
+ return true;
+ }
+
+ // Add the effects of CFI to the current FDE, creating a new FRE when
+ // necessary.
+ bool handleCFI(SFrameFDE &FDE, SFrameFRE &FRE, const MCCFIInstruction &CFI) {
+ switch (CFI.getOperation()) {
+ case MCCFIInstruction::OpDefCfaRegister:
+ return setCFARegister(FRE, CFI);
+ case MCCFIInstruction::OpDefCfa:
+ case MCCFIInstruction::OpLLVMDefAspaceCfa:
+ if (!setCFARegister(FRE, CFI))
+ return false;
+ return setCFAOffset(FRE, CFI.getLoc(), CFI.getOffset());
+ case MCCFIInstruction::OpOffset:
+ if (CFI.getRegister() == FPReg)
+ FRE.FPOffset = CFI.getOffset();
+ else if (CFI.getRegister() == RAReg)
+ FRE.RAOffset = CFI.getOffset();
+ return true;
+ case MCCFIInstruction::OpRelOffset:
+ if (CFI.getRegister() == FPReg)
+ FRE.FPOffset += CFI.getOffset();
+ else if (CFI.getRegister() == RAReg)
+ FRE.RAOffset += CFI.getOffset();
+ return true;
+ case MCCFIInstruction::OpDefCfaOffset:
+ return setCFAOffset(FRE, CFI.getLoc(), CFI.getOffset());
+ case MCCFIInstruction::OpAdjustCfaOffset:
+ return setCFAOffset(FRE, CFI.getLoc(), FRE.CFAOffset + CFI.getOffset());
+ case MCCFIInstruction::OpRememberState:
+ // TODO: Implement. Will use FDE.
+ return true;
+ case MCCFIInstruction::OpRestore:
+ // TODO: Implement. Will use FDE.
+ return true;
+ case MCCFIInstruction::OpRestoreState:
+ // TODO: Implement. Will use FDE.
+ return true;
+ case MCCFIInstruction::OpEscape:
+ // TODO: Implement. Will use FDE.
+ return true;
+ default:
+ // Instructions that don't affect the CFA, RA, and SP can be safely
+ // ignored.
+ return true;
+ }
+ }
+
public:
SFrameEmitterImpl(MCObjectStreamer &Streamer) : Streamer(Streamer) {
assert(Streamer.getContext()
@@ -88,13 +189,96 @@
.has_value());
FDEs.reserve(Streamer.getDwarfFrameInfos().size());
SFrameABI = *Streamer.getContext().getObjectFileInfo()->getSFrameABIArch();
+ switch (SFrameABI) {
+ case ABI::AArch64EndianBig:
+ case ABI::AArch64EndianLittle:
+ SPReg = 31;
+ RAReg = 29;
+ FPReg = 30;
+ break;
+ case ABI::AMD64EndianLittle:
+ SPReg = 7;
+ // RARegister untracked in this abi. Value chosen to match
+ // MCDwarfFrameInfo constructor.
+ RAReg = static_cast<unsigned>(INT_MAX);
+ FPReg = 6;
+ break;
+ }
+
FDESubSectionStart = Streamer.getContext().createTempSymbol();
FRESubSectionStart = Streamer.getContext().createTempSymbol();
FRESubSectionEnd = Streamer.getContext().createTempSymbol();
}
- void BuildSFDE(const MCDwarfFrameInfo &DF) {
- FDEs.emplace_back(DF, Streamer.getContext().createTempSymbol());
+ bool atSameLocation(const MCSymbol *Left, const MCSymbol *Right) {
+ return Left != nullptr && Right != nullptr &&
+ Left->getFragment() == Right->getFragment() &&
+ Left->getOffset() == Right->getOffset();
+ }
+
+ bool equalIgnoringLocation(const SFrameFRE &Left, const SFrameFRE &Right) {
+ return Left.CFAOffset == Right.CFAOffset &&
+ Left.FPOffset == Right.FPOffset && Left.RAOffset == Right.RAOffset &&
+ Left.FromFP == Right.FromFP && Left.CFARegSet == Right.CFARegSet;
+ }
+
+ void buildSFDE(const MCDwarfFrameInfo &DF) {
+ bool Valid = true;
+ SFrameFDE FDE(DF, Streamer.getContext().createTempSymbol());
+ // This would have been set via ".cfi_return_column", but
+ // MCObjectStreamer doesn't emit an MCCFIInstruction for that. It just
+ // sets the DF.RAReg.
+ // FIXME: This also prevents providing a proper location for the error.
+ // LLVM doesn't change the return column itself, so this was
+ // hand-written assembly.
+ if (DF.RAReg != RAReg) {
+ Streamer.getContext().reportWarning(
+ SMLoc(), "non-default RA register in .cfi_return_column " +
+ Twine(DF.RAReg) +
+ ". Omitting SFrame unwind info for this function");
+ Valid = false;
+ }
+ MCSymbol *LastLabel = DF.Begin;
+ SFrameFRE BaseFRE(LastLabel);
+ if (!DF.IsSimple) {
+ for (const auto &CFI :
+ Streamer.getContext().getAsmInfo()->getInitialFrameState())
+ if (!handleCFI(FDE, BaseFRE, CFI))
+ Valid = false;
+ }
+ FDE.FREs.push_back(BaseFRE);
+
+ for (const auto &CFI : DF.Instructions) {
+ // Instructions from InitialFrameState may not have a label, but if these
+ // instructions don't, then they are in dead code or otherwise unused.
+ // TODO: This check follows MCDwarf.cpp
+ // FrameEmitterImplementation::emitCFIInstructions, but nothing in the
+ // testsuite triggers it. We should see if it can be removed in both
+ // places, or alternately, add a test to exercise it.
+ auto *L = CFI.getLabel();
+ if (L && !L->isDefined())
+ continue;
+
+ SFrameFRE FRE = FDE.FREs.back();
+ if (!handleCFI(FDE, FRE, CFI))
+ Valid = false;
+
+ // If nothing relevant but the location changed, don't add the FRE.
+ if (equalIgnoringLocation(FRE, FDE.FREs.back()))
+ continue;
+
+ // If the location stayed the same, then update the current
+ // row. Otherwise, add a new one.
+ if (atSameLocation(LastLabel, L))
+ FDE.FREs.back() = FRE;
+ else {
+ FDE.FREs.push_back(FRE);
+ FDE.FREs.back().Label = L;
+ LastLabel = L;
+ }
+ }
+ if (Valid)
+ FDEs.push_back(FDE);
}
void emitPreamble() {
@@ -116,7 +300,9 @@
// shf_num_fdes
Streamer.emitInt32(FDEs.size());
// shf_num_fres
- Streamer.emitInt32(0);
+ uint32_t TotalFREs = 0;
+ Streamer.emitInt32(TotalFREs);
+
// shf_fre_len
Streamer.emitAbsoluteSymbolDiff(FRESubSectionEnd, FRESubSectionStart,
sizeof(int32_t));
@@ -161,7 +347,7 @@
// Both the header itself and the FDEs include various offsets and counts.
// Therefore, all of this must be precomputed.
for (const auto &DFrame : FrameArray)
- Emitter.BuildSFDE(DFrame);
+ Emitter.buildSFDE(DFrame);
MCSection *Section = Context.getObjectFileInfo()->getSFrameSection();
// Not strictly necessary, but gas always aligns to 8, so match that.
diff --git a/llvm/test/MC/ELF/cfi-sframe-errors.s b/llvm/test/MC/ELF/cfi-sframe-errors.s
new file mode 100644
index 0000000..20e902d
--- /dev/null
+++ b/llvm/test/MC/ELF/cfi-sframe-errors.s
@@ -0,0 +1,31 @@
+// TODO: Add other architectures as they gain sframe support
+// REQUIRES: x86-registered-target
+// RUN: llvm-mc --assemble --filetype=obj -triple x86_64 %s -o %t.o 2>&1 | FileCheck %s
+// RUN: llvm-readelf --sframe %t.o | FileCheck --check-prefix=CHECK-NOFDES %s
+
+
+ .cfi_sections .sframe
+f1:
+ .cfi_startproc simple
+// CHECK: non-default RA register {{.*}}
+ .cfi_return_column 0
+ nop
+// CHECK: {{.*}} adjusting CFA offset without a base register.{{.*}}
+ .cfi_def_cfa_offset 16 // no line number reported here.
+ nop
+// CHECK: [[@LINE+1]]:{{.*}} adjusting CFA offset without a base register.{{.*}}
+ .cfi_adjust_cfa_offset 16
+ nop
+ .cfi_endproc
+
+f2:
+ .cfi_startproc
+ nop
+// CHECK: canonical Frame Address not in stack- or frame-pointer. {{.*}}
+ .cfi_def_cfa 0, 4
+ nop
+
+ .cfi_endproc
+
+// CHECK-NOFDES: Num FDEs: 0
+// CHECK-NOFDES: Num FREs: 0
diff --git a/llvm/test/MC/ELF/cfi-sframe.s b/llvm/test/MC/ELF/cfi-sframe.s
index 66b9ee6..ecf77bc 100644
--- a/llvm/test/MC/ELF/cfi-sframe.s
+++ b/llvm/test/MC/ELF/cfi-sframe.s
@@ -5,8 +5,18 @@
.cfi_sections .sframe
f1:
- .cfi_startproc
+ .cfi_startproc // FRE 0
nop
+ .cfi_def_cfa_offset 16 // FRE 1
+ .cfi_def_cfa_offset 8 // location didn't change. No new FRE, but new offset.
+ nop
+ .cfi_def_cfa_offset 8 // offset didn't change. No new FRE.
+ nop
+ .cfi_def_cfa_offset 16 // FRE 2. new location, new offset.
+ nop
+ .cfi_register 0, 1 // Uninteresting register. No new FRE.
+ nop
+
.cfi_endproc
f2:
@@ -32,11 +42,11 @@
// CHECK: Function Index [
// CHECK-NEXT: FuncDescEntry [0] {
// CHECK-NEXT: PC {
-// CHECK-NEXT: Relocation: {{.*}}32{{.*}}
+// CHECK-NEXT: Relocation: {{.*}}PC32{{.*}}
// CHECK-NEXT: Symbol Name: .text
-// CHECK-NEXT: Start Address: 0x0
+// CHECK-NEXT: Start Address: {{.*}}
// CHECK-NEXT: }
-// CHECK-NEXT: Size: 0x1
+// CHECK-NEXT: Size: 0x5
// CHECK-NEXT: Start FRE Offset: 0x0
// CHECK-NEXT: Num FREs: 0
// CHECK-NEXT: Info {
@@ -51,7 +61,7 @@
// CHECK-NEXT: }
// CHECK-NEXT: FuncDescEntry [1] {
// CHECK-NEXT: PC {
-// CHECK-NEXT: Relocation: R_X86_64_PC32
+// CHECK-NEXT: Relocation: {{.*}}PC32{{.*}}
// CHECK-NEXT: Symbol Name: .text
// CHECK-NEXT: Start Address: {{.*}}
// CHECK-NEXT: }