diff --git a/llvm/lib/MC/MCSFrame.cpp b/llvm/lib/MC/MCSFrame.cpp index ee17b774e4740..a0d6c80ab72ea 100644 --- a/llvm/lib/MC/MCSFrame.cpp +++ b/llvm/lib/MC/MCSFrame.cpp @@ -21,6 +21,24 @@ using namespace sframe; 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 @@ struct SFrameFDE { const MCDwarfFrameInfo &DFrame; // Label where this FDE's FREs start. MCSymbol *FREStart; + // Unwinding fres + SmallVector FREs; SFrameFDE(const MCDwarfFrameInfo &DF, MCSymbol *FRES) : DFrame(DF), FREStart(FRES) {} @@ -53,7 +73,8 @@ struct SFrameFDE { 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 @@ class SFrameEmitterImpl { MCObjectStreamer &Streamer; SmallVector 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 @@ class SFrameEmitterImpl { .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(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 @@ class SFrameEmitterImpl { // 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 @@ void MCSFrameEmitter::emit(MCObjectStreamer &Streamer) { // 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 0000000000000..20e902d064097 --- /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 66b9ee61bdaff..ecf77bc3ea6b3 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 @@ f2: // 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 @@ f2: // 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: }