
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.
168 lines
6.7 KiB
C++
168 lines
6.7 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/DWARFCFIChecker/DWARFCFIState.h"
|
|
#include "llvm/BinaryFormat/Dwarf.h"
|
|
#include "llvm/DebugInfo/DWARF/LowLevel/DWARFUnwindTable.h"
|
|
#include "llvm/MC/MCDwarf.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include "llvm/Support/ErrorHandling.h"
|
|
#include "llvm/Support/FormatVariadic.h"
|
|
#include <cassert>
|
|
#include <optional>
|
|
|
|
using namespace llvm;
|
|
|
|
std::optional<dwarf::UnwindRow> DWARFCFIState::getCurrentUnwindRow() const {
|
|
if (!IsInitiated)
|
|
return std::nullopt;
|
|
return Row;
|
|
}
|
|
|
|
void DWARFCFIState::update(const MCCFIInstruction &Directive) {
|
|
auto CFIP = convert(Directive);
|
|
|
|
// This is a copy of the current row, its value will be updated by
|
|
// `parseRows`.
|
|
dwarf::UnwindRow NewRow = Row;
|
|
|
|
// `parseRows` updates the current row by applying the `CFIProgram` to it.
|
|
// During this process, it may create multiple rows preceding the newly
|
|
// updated row and following the previous rows. These middle rows are stored
|
|
// in `PrecedingRows`. For now, there is no need to store these rows in the
|
|
// state, so they are ignored in the end.
|
|
dwarf::UnwindTable::RowContainer PrecedingRows;
|
|
|
|
// TODO: `.cfi_remember_state` and `.cfi_restore_state` directives are not
|
|
// supported yet. The reason is that `parseRows` expects the stack of states
|
|
// to be produced and used in a single `CFIProgram`. However, in this use
|
|
// case, each instruction creates its own `CFIProgram`, which means the stack
|
|
// of states is forgotten between instructions. To fix it, `parseRows` should
|
|
// be refactored to read the current stack of states from the argument and
|
|
// update it based on the `CFIProgram.`
|
|
if (Error Err = parseRows(CFIP, NewRow, nullptr).takeError()) {
|
|
Context->reportError(
|
|
Directive.getLoc(),
|
|
formatv("could not parse this CFI directive due to: {0}",
|
|
toString(std::move(Err))));
|
|
|
|
// Proceed the analysis by ignoring this CFI directive.
|
|
return;
|
|
}
|
|
|
|
Row = NewRow;
|
|
IsInitiated = true;
|
|
}
|
|
|
|
dwarf::CFIProgram DWARFCFIState::convert(MCCFIInstruction Directive) {
|
|
auto CFIP = dwarf::CFIProgram(
|
|
/* CodeAlignmentFactor */ 1, /* DataAlignmentFactor */ 1,
|
|
Context->getTargetTriple().getArch());
|
|
|
|
auto MaybeCurrentRow = getCurrentUnwindRow();
|
|
switch (Directive.getOperation()) {
|
|
case MCCFIInstruction::OpSameValue:
|
|
CFIP.addInstruction(dwarf::DW_CFA_same_value, Directive.getRegister());
|
|
break;
|
|
case MCCFIInstruction::OpRememberState:
|
|
// TODO: remember state is not supported yet, the following line does not
|
|
// work:
|
|
// CFIP.addInstruction(dwarf::DW_CFA_remember_state);
|
|
// The reason is explained in the `DWARFCFIState::update` method where
|
|
// `dwarf::parseRows` is used.
|
|
Context->reportWarning(Directive.getLoc(),
|
|
"this directive is not supported, ignoring it");
|
|
break;
|
|
case MCCFIInstruction::OpRestoreState:
|
|
// TODO: restore state is not supported yet, the following line does not
|
|
// work:
|
|
// CFIP.addInstruction(dwarf::DW_CFA_restore_state);
|
|
// The reason is explained in the `DWARFCFIState::update` method where
|
|
// `dwarf::parseRows` is used.
|
|
Context->reportWarning(Directive.getLoc(),
|
|
"this directive is not supported, ignoring it");
|
|
break;
|
|
case MCCFIInstruction::OpOffset:
|
|
CFIP.addInstruction(dwarf::DW_CFA_offset, Directive.getRegister(),
|
|
Directive.getOffset());
|
|
break;
|
|
case MCCFIInstruction::OpLLVMDefAspaceCfa:
|
|
CFIP.addInstruction(dwarf::DW_CFA_LLVM_def_aspace_cfa,
|
|
Directive.getRegister());
|
|
break;
|
|
case MCCFIInstruction::OpDefCfaRegister:
|
|
CFIP.addInstruction(dwarf::DW_CFA_def_cfa_register,
|
|
Directive.getRegister());
|
|
break;
|
|
case MCCFIInstruction::OpDefCfaOffset:
|
|
CFIP.addInstruction(dwarf::DW_CFA_def_cfa_offset, Directive.getOffset());
|
|
break;
|
|
case MCCFIInstruction::OpDefCfa:
|
|
CFIP.addInstruction(dwarf::DW_CFA_def_cfa, Directive.getRegister(),
|
|
Directive.getOffset());
|
|
break;
|
|
case MCCFIInstruction::OpRelOffset:
|
|
assert(
|
|
IsInitiated &&
|
|
"cannot define relative offset to a non-existing CFA unwinding rule");
|
|
|
|
CFIP.addInstruction(dwarf::DW_CFA_offset, Directive.getRegister(),
|
|
Directive.getOffset() - Row.getCFAValue().getOffset());
|
|
break;
|
|
case MCCFIInstruction::OpAdjustCfaOffset:
|
|
assert(IsInitiated &&
|
|
"cannot adjust CFA offset of a non-existing CFA unwinding rule");
|
|
|
|
CFIP.addInstruction(dwarf::DW_CFA_def_cfa_offset,
|
|
Directive.getOffset() + Row.getCFAValue().getOffset());
|
|
break;
|
|
case MCCFIInstruction::OpEscape:
|
|
// TODO: DWARFExpressions are not supported yet, ignoring expression here.
|
|
Context->reportWarning(Directive.getLoc(),
|
|
"this directive is not supported, ignoring it");
|
|
break;
|
|
case MCCFIInstruction::OpRestore:
|
|
// The `.cfi_restore register` directive restores the register's unwinding
|
|
// information to its CIE value. However, assemblers decide where CIE ends
|
|
// and the FDE starts, so the functionality of this directive depends on the
|
|
// assembler's decision and cannot be validated.
|
|
Context->reportWarning(
|
|
Directive.getLoc(),
|
|
"this directive behavior depends on the assembler, ignoring it");
|
|
break;
|
|
case MCCFIInstruction::OpUndefined:
|
|
CFIP.addInstruction(dwarf::DW_CFA_undefined, Directive.getRegister());
|
|
break;
|
|
case MCCFIInstruction::OpRegister:
|
|
CFIP.addInstruction(dwarf::DW_CFA_register, Directive.getRegister(),
|
|
Directive.getRegister2());
|
|
break;
|
|
case MCCFIInstruction::OpWindowSave:
|
|
CFIP.addInstruction(dwarf::DW_CFA_GNU_window_save);
|
|
break;
|
|
case MCCFIInstruction::OpNegateRAState:
|
|
CFIP.addInstruction(dwarf::DW_CFA_AARCH64_negate_ra_state);
|
|
break;
|
|
case MCCFIInstruction::OpNegateRAStateWithPC:
|
|
CFIP.addInstruction(dwarf::DW_CFA_AARCH64_negate_ra_state_with_pc);
|
|
break;
|
|
case MCCFIInstruction::OpGnuArgsSize:
|
|
CFIP.addInstruction(dwarf::DW_CFA_GNU_args_size);
|
|
break;
|
|
case MCCFIInstruction::OpLabel:
|
|
// `.cfi_label` does not have any functional effect on unwinding process.
|
|
break;
|
|
case MCCFIInstruction::OpValOffset:
|
|
CFIP.addInstruction(dwarf::DW_CFA_val_offset, Directive.getRegister(),
|
|
Directive.getOffset());
|
|
break;
|
|
}
|
|
|
|
return CFIP;
|
|
}
|