Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
155 changes: 151 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,152 @@ 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);
// The parser should have failed in this case.
assert(Reg && "DW_CFA_val_offset with no register.");
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 +411,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 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 if 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