Skip to content
Open
3 changes: 2 additions & 1 deletion llvm/lib/MC/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,10 @@ add_llvm_component_library(LLVMMC
${LLVM_MAIN_INCLUDE_DIR}/llvm/MC

LINK_COMPONENTS
BinaryFormat
DebugInfoDWARFLowLevel
Support
TargetParser
BinaryFormat

DEPENDS
intrinsics_gen
Expand Down
158 changes: 154 additions & 4 deletions llvm/lib/MC/MCSFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

#include "llvm/MC/MCSFrame.h"
#include "llvm/BinaryFormat/SFrame.h"
#include "llvm/DebugInfo/DWARF/LowLevel/DWARFCFIProgram.h"
#include "llvm/DebugInfo/DWARF/LowLevel/DWARFDataExtractorSimple.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCObjectFileInfo.h"
Expand Down Expand Up @@ -211,8 +213,155 @@ class SFrameEmitterImpl {
return true;
}

// Technically, the escape data could be anything, but it is commonly a dwarf
// CFI program. Even then, it could contain an arbitrarily complicated Dwarf
// expression. Following gnu-gas, look for certain common cases that could
// invalidate an FDE, emit a warning for those sequences, and don't generate
// an FDE in those cases. Allow any that are known safe. It is likely that
// more thorough test cases could refine this code, but it handles the most
// important ones compatibly with gas.
// Returns true if the CFI escape sequence is safe for sframes.
bool isCFIEscapeSafe(SFrameFDE &FDE, const SFrameFRE &FRE,
const MCCFIInstruction &CFI) {
const MCAsmInfo *AI = Streamer.getContext().getAsmInfo();
DWARFDataExtractorSimple data(CFI.getValues(), AI->isLittleEndian(),
AI->getCodePointerSize());

// Normally, both alignment factors are extracted from the enclosing Dwarf
// FDE or CIE. We don't have one here. Alignments are used for scaling
// factors for ops like CFA_def_cfa_offset_sf. But this particular function
// is only interested in registers.
dwarf::CFIProgram P(/* CodeAlignmentFactor */ 1,
/* DataAlignmentFactor*/ 1,
Streamer.getContext().getTargetTriple().getArch());
uint64_t Offset = 0;
if (P.parse(data, &Offset, CFI.getValues().size())) {
// Not a parsable dwarf expression. Assume the worst.
Streamer.getContext().reportWarning(
CFI.getLoc(),
"skipping SFrame FDE; .cfi_escape with unknown effects");
return false;
}

// This loop deals with dwarf::CFIProgram::Instructions. Everywhere else
// this file deals with MCCFIInstructions.
for (const dwarf::CFIProgram::Instruction &I : P) {
switch (I.Opcode) {
case dwarf::DW_CFA_nop:
break;
case dwarf::DW_CFA_val_offset: {
// First argument is a register. Anything that touches CFA, FP, or RA is
// a problem, but allow others through. As an even more special case,
// allow SP + 0.
auto Reg = I.getOperandAsUnsigned(P, 0);
if (!Reg) {
Streamer.getContext().reportWarning(
CFI.getLoc(),
"skipping SFrame FDE; .cfi_escape with unknown effects");
}
bool SPOk = true;
if (*Reg == SPReg) {
auto Opnd = I.getOperandAsSigned(P, 1);
if (!Opnd || *Opnd != 0)
SPOk = false;
}
if (!SPOk || *Reg == RAReg || *Reg == FPReg) {
StringRef RN = *Reg == SPReg
? "SP reg "
: (*Reg == FPReg ? "FP reg " : "RA reg ");
Streamer.getContext().reportWarning(
CFI.getLoc(),
Twine(
"skipping SFrame FDE; .cfi_escape DW_CFA_val_offset with ") +
RN + Twine(*Reg));
return false;
}
} break;
case dwarf::DW_CFA_expression: {
// First argument is a register. Anything that touches CFA, FP, or RA is
// a problem, but allow others through.
auto Reg = I.getOperandAsUnsigned(P, 0);
if (!Reg) {
Streamer.getContext().reportWarning(
CFI.getLoc(),
"skipping SFrame FDE; .cfi_escape with unknown effects");
return false;
}
if (*Reg == SPReg || *Reg == RAReg || *Reg == FPReg) {
StringRef RN = *Reg == SPReg
? "SP reg "
: (*Reg == FPReg ? "FP reg " : "RA reg ");
Streamer.getContext().reportWarning(
CFI.getLoc(),
Twine(
"skipping SFrame FDE; .cfi_escape DW_CFA_expression with ") +
RN + Twine(*Reg));
return false;
}
} break;
case dwarf::DW_CFA_GNU_args_size: {
auto Size = I.getOperandAsSigned(P, 0);
// Zero size doesn't affect the cfa.
if (Size && *Size == 0)
break;
if (FRE.Info.getBaseRegister() != BaseReg::FP) {
Streamer.getContext().reportWarning(
CFI.getLoc(),
Twine("skipping SFrame FDE; .cfi_escape DW_CFA_GNU_args_size "
"with non frame-pointer CFA"));
return false;
}
} break;
// Cases that gas doesn't specially handle. TODO: Some of these could be
// analyzed and handled instead of just punting. But these are uncommon,
// or should be written as normal cfi directives. Some will need fixes to
// the scaling factor.
case dwarf::DW_CFA_advance_loc:
case dwarf::DW_CFA_offset:
case dwarf::DW_CFA_restore:
case dwarf::DW_CFA_set_loc:
case dwarf::DW_CFA_advance_loc1:
case dwarf::DW_CFA_advance_loc2:
case dwarf::DW_CFA_advance_loc4:
case dwarf::DW_CFA_offset_extended:
case dwarf::DW_CFA_restore_extended:
case dwarf::DW_CFA_undefined:
case dwarf::DW_CFA_same_value:
case dwarf::DW_CFA_register:
case dwarf::DW_CFA_remember_state:
case dwarf::DW_CFA_restore_state:
case dwarf::DW_CFA_def_cfa:
case dwarf::DW_CFA_def_cfa_register:
case dwarf::DW_CFA_def_cfa_offset:
case dwarf::DW_CFA_def_cfa_expression:
case dwarf::DW_CFA_offset_extended_sf:
case dwarf::DW_CFA_def_cfa_sf:
case dwarf::DW_CFA_def_cfa_offset_sf:
case dwarf::DW_CFA_val_offset_sf:
case dwarf::DW_CFA_val_expression:
case dwarf::DW_CFA_MIPS_advance_loc8:
case dwarf::DW_CFA_AARCH64_negate_ra_state_with_pc:
case dwarf::DW_CFA_AARCH64_negate_ra_state:
case dwarf::DW_CFA_LLVM_def_aspace_cfa:
case dwarf::DW_CFA_LLVM_def_aspace_cfa_sf:
Streamer.getContext().reportWarning(
CFI.getLoc(), "skipping SFrame FDE; .cfi_escape "
"CFA expression with unknown side effects");
return false;
default:
// Dwarf expression was only partially valid, and user could have
// written anything.
Streamer.getContext().reportWarning(
CFI.getLoc(),
"skipping SFrame FDE; .cfi_escape with unknown effects");
return false;
}
}
return true;
}

// Add the effects of CFI to the current FDE, creating a new FRE when
// necessary.
// necessary. Return true if the CFI is representable in the sframe format.
bool handleCFI(SFrameFDE &FDE, SFrameFRE &FRE, const MCCFIInstruction &CFI) {
switch (CFI.getOperation()) {
case MCCFIInstruction::OpDefCfaRegister:
Expand Down Expand Up @@ -265,10 +414,11 @@ class SFrameEmitterImpl {
FRE = FDE.SaveState.pop_back_val();
return true;
case MCCFIInstruction::OpEscape:
// TODO: Implement. Will use FDE.
return true;
// This is a string of bytes that contains an arbitrary dwarf-expression
// that may or may not affect unwind info.
return isCFIEscapeSafe(FDE, FRE, CFI);
default:
// Instructions that don't affect the CFA, RA, and SP can be safely
// Instructions that don't affect the CFA, RA, and FP can be safely
// ignored.
return true;
}
Expand Down
37 changes: 37 additions & 0 deletions llvm/test/MC/ELF/cfi-sframe-cfi-escape-errors.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# RUN: llvm-mc --filetype=obj --gsframe -triple x86_64 %s -o %t.o 2>&1 | FileCheck %s
# RUN: llvm-readelf --sframe %t.o | FileCheck %s --check-prefix=NOFDES

# Tests that .cfi_escape sequences that are unrepresentable in sframe warn
# and do not produce FDEs.

.align 1024
cfi_escape_sp:
.cfi_startproc
.long 0
# Setting SP via other registers makes it unrepresentable in sframe
# DW_CFA_expression,reg 0x7,length 2,DW_OP_breg6,SLEB(-8)
# CHECK: skipping SFrame FDE; .cfi_escape DW_CFA_expression with SP reg 7
.cfi_escape 0x10, 0x7, 0x2, 0x76, 0x78
.long 0
.cfi_endproc

cfi_escape_args_sp:
.cfi_startproc
.long 0
# DW_CFA_GNU_args_size is not OK if cfa is SP
# CHECK: skipping SFrame FDE; .cfi_escape DW_CFA_GNU_args_size with non frame-pointer CFA
.cfi_escape 0x2e, 0x20
.cfi_endproc

cfi_escape_val_offset:
.cfi_startproc
.long 0
.cfi_def_cfa_offset 16
# DW_CFA_val_offset,rbp,ULEB scaled offset(16)
# CHECK: skipping SFrame FDE; .cfi_escape DW_CFA_val_offset with FP reg 6
.cfi_escape 0x14,0x6,0x2
.long 0
.cfi_endproc


# NOFDES: Num FDEs: 0
46 changes: 46 additions & 0 deletions llvm/test/MC/ELF/cfi-sframe-cfi-escape.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# RUN: llvm-mc --filetype=obj --gsframe -triple x86_64 %s -o %t.o
# RUN: llvm-readelf --sframe %t.o | FileCheck %s

# Tests that .cfi_escape sequences that are are ok to pass through work.

.align 1024
cfi_escape_ok:
.cfi_startproc
.long 0
.cfi_def_cfa_offset 16
# Uninteresting register
# DW_CFA_expression,reg 0xc,length 2,DW_OP_breg6,SLEB(-8)
.cfi_escape 0x10,0xc,0x2,0x76,0x78
# DW_CFA_nop
.cfi_escape 0x0
.cfi_escape 0x0,0x0,0x0,0x0
# Uninteresting register
# DW_CFA_val_offset,reg 0xc,ULEB scaled offset
.cfi_escape 0x14,0xc,0x4
.long 0
.cfi_endproc

cfi_escape_gnu_args_fp:
.cfi_startproc
.long 0
# DW_CFA_GNU_args_size is OK arg size is zero
.cfi_escape 0x2e, 0x0
.long 0
.cfi_def_cfa_register 6
.long 0
# DW_CFA_GNU_args_size is OK if cfa is FP
.cfi_escape 0x2e, 0x20
.cfi_endproc

cfi_escape_long_expr:
.cfi_startproc
.long 0
.cfi_def_cfa_offset 16
# This is a long, but valid, dwarf expression without sframe
# implications. An FDE can still be created.
# DW_CFA_val_offset,rcx,ULEB scaled offset(16), DW_CFA_expr,r10,length,DW_OP_deref,SLEB(-8)
.cfi_escape 0x14,0x2,0x2,0x10,0xa,0x2,0x76,0x78
.long 0
.cfi_endproc

# CHECK: Num FDEs: 3
1 change: 1 addition & 0 deletions utils/bazel/llvm-project-overlay/llvm/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,7 @@ cc_library(
deps = [
":BinaryFormat",
":DebugInfoCodeView",
":DebugInfoDWARFLowLevel",
":Support",
":TargetParser",
":config",
Expand Down