blob: 522c9a65e8682bb462aba9320a650c15fbeff498 [file] [log] [blame]
#include "PECallFrameInfo.h"
#include "ObjectFilePECOFF.h"
#include "Plugins/Process/Utility/lldb-x86-register-enums.h"
#include "lldb/Symbol/UnwindPlan.h"
#include "llvm/Support/Win64EH.h"
using namespace lldb;
using namespace lldb_private;
using namespace llvm::Win64EH;
template <typename T>
static const T *TypedRead(const DataExtractor &data_extractor, offset_t &offset,
offset_t size = sizeof(T)) {
return static_cast<const T *>(data_extractor.GetData(&offset, size));
}
struct EHInstruction {
enum class Type {
PUSH_REGISTER,
ALLOCATE,
SET_FRAME_POINTER_REGISTER,
SAVE_REGISTER
};
uint8_t offset;
Type type;
uint32_t reg;
uint32_t frame_offset;
};
using EHProgram = std::vector<EHInstruction>;
class UnwindCodesIterator {
public:
UnwindCodesIterator(ObjectFilePECOFF &object_file, uint32_t unwind_info_rva);
bool GetNext();
bool IsError() const { return m_error; }
const UnwindInfo *GetUnwindInfo() const { return m_unwind_info; }
const UnwindCode *GetUnwindCode() const { return m_unwind_code; }
bool IsChained() const { return m_chained; }
private:
ObjectFilePECOFF &m_object_file;
bool m_error;
uint32_t m_unwind_info_rva;
DataExtractor m_unwind_info_data;
const UnwindInfo *m_unwind_info;
DataExtractor m_unwind_code_data;
offset_t m_unwind_code_offset;
const UnwindCode *m_unwind_code;
bool m_chained;
};
UnwindCodesIterator::UnwindCodesIterator(ObjectFilePECOFF &object_file,
uint32_t unwind_info_rva)
: m_object_file(object_file), m_error(false),
m_unwind_info_rva(unwind_info_rva),
m_unwind_info(nullptr), m_unwind_code_offset{}, m_unwind_code(nullptr),
m_chained(false) {}
bool UnwindCodesIterator::GetNext() {
static constexpr int UNWIND_INFO_SIZE = 4;
m_error = false;
m_unwind_code = nullptr;
while (!m_unwind_code) {
if (!m_unwind_info) {
m_unwind_info_data =
m_object_file.ReadImageDataByRVA(m_unwind_info_rva, UNWIND_INFO_SIZE);
offset_t offset = 0;
m_unwind_info =
TypedRead<UnwindInfo>(m_unwind_info_data, offset, UNWIND_INFO_SIZE);
if (!m_unwind_info) {
m_error = true;
break;
}
m_unwind_code_data = m_object_file.ReadImageDataByRVA(
m_unwind_info_rva + UNWIND_INFO_SIZE,
m_unwind_info->NumCodes * sizeof(UnwindCode));
m_unwind_code_offset = 0;
}
if (m_unwind_code_offset < m_unwind_code_data.GetByteSize()) {
m_unwind_code =
TypedRead<UnwindCode>(m_unwind_code_data, m_unwind_code_offset);
m_error = !m_unwind_code;
break;
}
if (!(m_unwind_info->getFlags() & UNW_ChainInfo))
break;
uint32_t runtime_function_rva =
m_unwind_info_rva + UNWIND_INFO_SIZE +
((m_unwind_info->NumCodes + 1) & ~1) * sizeof(UnwindCode);
DataExtractor runtime_function_data = m_object_file.ReadImageDataByRVA(
runtime_function_rva, sizeof(RuntimeFunction));
offset_t offset = 0;
const auto *runtime_function =
TypedRead<RuntimeFunction>(runtime_function_data, offset);
if (!runtime_function) {
m_error = true;
break;
}
m_unwind_info_rva = runtime_function->UnwindInfoOffset;
m_unwind_info = nullptr;
m_chained = true;
}
return !!m_unwind_code;
}
class EHProgramBuilder {
public:
EHProgramBuilder(ObjectFilePECOFF &object_file, uint32_t unwind_info_rva);
bool Build();
const EHProgram &GetProgram() const { return m_program; }
private:
static uint32_t ConvertMachineToLLDBRegister(uint8_t machine_reg);
static uint32_t ConvertXMMToLLDBRegister(uint8_t xmm_reg);
bool ProcessUnwindCode(UnwindCode code);
void Finalize();
bool ParseBigOrScaledFrameOffset(uint32_t &result, bool big, uint32_t scale);
bool ParseBigFrameOffset(uint32_t &result);
bool ParseFrameOffset(uint32_t &result);
UnwindCodesIterator m_iterator;
EHProgram m_program;
};
EHProgramBuilder::EHProgramBuilder(ObjectFilePECOFF &object_file,
uint32_t unwind_info_rva)
: m_iterator(object_file, unwind_info_rva) {}
bool EHProgramBuilder::Build() {
while (m_iterator.GetNext())
if (!ProcessUnwindCode(*m_iterator.GetUnwindCode()))
return false;
if (m_iterator.IsError())
return false;
Finalize();
return true;
}
uint32_t EHProgramBuilder::ConvertMachineToLLDBRegister(uint8_t machine_reg) {
static uint32_t machine_to_lldb_register[] = {
lldb_rax_x86_64, lldb_rcx_x86_64, lldb_rdx_x86_64, lldb_rbx_x86_64,
lldb_rsp_x86_64, lldb_rbp_x86_64, lldb_rsi_x86_64, lldb_rdi_x86_64,
lldb_r8_x86_64, lldb_r9_x86_64, lldb_r10_x86_64, lldb_r11_x86_64,
lldb_r12_x86_64, lldb_r13_x86_64, lldb_r14_x86_64, lldb_r15_x86_64};
if (machine_reg >= llvm::array_lengthof(machine_to_lldb_register))
return LLDB_INVALID_REGNUM;
return machine_to_lldb_register[machine_reg];
}
uint32_t EHProgramBuilder::ConvertXMMToLLDBRegister(uint8_t xmm_reg) {
static uint32_t xmm_to_lldb_register[] = {
lldb_xmm0_x86_64, lldb_xmm1_x86_64, lldb_xmm2_x86_64,
lldb_xmm3_x86_64, lldb_xmm4_x86_64, lldb_xmm5_x86_64,
lldb_xmm6_x86_64, lldb_xmm7_x86_64, lldb_xmm8_x86_64,
lldb_xmm9_x86_64, lldb_xmm10_x86_64, lldb_xmm11_x86_64,
lldb_xmm12_x86_64, lldb_xmm13_x86_64, lldb_xmm14_x86_64,
lldb_xmm15_x86_64};
if (xmm_reg >= llvm::array_lengthof(xmm_to_lldb_register))
return LLDB_INVALID_REGNUM;
return xmm_to_lldb_register[xmm_reg];
}
bool EHProgramBuilder::ProcessUnwindCode(UnwindCode code) {
uint8_t o = m_iterator.IsChained() ? 0 : code.u.CodeOffset;
uint8_t unwind_operation = code.getUnwindOp();
uint8_t operation_info = code.getOpInfo();
switch (unwind_operation) {
case UOP_PushNonVol: {
uint32_t r = ConvertMachineToLLDBRegister(operation_info);
if (r == LLDB_INVALID_REGNUM)
return false;
m_program.emplace_back(
EHInstruction{o, EHInstruction::Type::PUSH_REGISTER, r, 8});
return true;
}
case UOP_AllocLarge: {
uint32_t fo;
if (!ParseBigOrScaledFrameOffset(fo, operation_info, 8))
return false;
m_program.emplace_back(EHInstruction{o, EHInstruction::Type::ALLOCATE,
LLDB_INVALID_REGNUM, fo});
return true;
}
case UOP_AllocSmall: {
m_program.emplace_back(
EHInstruction{o, EHInstruction::Type::ALLOCATE, LLDB_INVALID_REGNUM,
static_cast<uint32_t>(operation_info) * 8 + 8});
return true;
}
case UOP_SetFPReg: {
uint32_t fpr = LLDB_INVALID_REGNUM;
if (m_iterator.GetUnwindInfo()->getFrameRegister())
fpr = ConvertMachineToLLDBRegister(
m_iterator.GetUnwindInfo()->getFrameRegister());
if (fpr == LLDB_INVALID_REGNUM)
return false;
uint32_t fpro =
static_cast<uint32_t>(m_iterator.GetUnwindInfo()->getFrameOffset()) *
16;
m_program.emplace_back(EHInstruction{
o, EHInstruction::Type::SET_FRAME_POINTER_REGISTER, fpr, fpro});
return true;
}
case UOP_SaveNonVol:
case UOP_SaveNonVolBig: {
uint32_t r = ConvertMachineToLLDBRegister(operation_info);
if (r == LLDB_INVALID_REGNUM)
return false;
uint32_t fo;
if (!ParseBigOrScaledFrameOffset(fo, unwind_operation == UOP_SaveNonVolBig,
8))
return false;
m_program.emplace_back(
EHInstruction{o, EHInstruction::Type::SAVE_REGISTER, r, fo});
return true;
}
case UOP_Epilog: {
return m_iterator.GetNext();
}
case UOP_SpareCode: {
// ReSharper disable once CppIdenticalOperandsInBinaryExpression
return m_iterator.GetNext() && m_iterator.GetNext();
}
case UOP_SaveXMM128:
case UOP_SaveXMM128Big: {
uint32_t r = ConvertXMMToLLDBRegister(operation_info);
if (r == LLDB_INVALID_REGNUM)
return false;
uint32_t fo;
if (!ParseBigOrScaledFrameOffset(fo, unwind_operation == UOP_SaveXMM128Big,
16))
return false;
m_program.emplace_back(
EHInstruction{o, EHInstruction::Type::SAVE_REGISTER, r, fo});
return true;
}
case UOP_PushMachFrame: {
if (operation_info)
m_program.emplace_back(EHInstruction{o, EHInstruction::Type::ALLOCATE,
LLDB_INVALID_REGNUM, 8});
m_program.emplace_back(EHInstruction{o, EHInstruction::Type::PUSH_REGISTER,
lldb_rip_x86_64, 8});
m_program.emplace_back(EHInstruction{o, EHInstruction::Type::PUSH_REGISTER,
lldb_cs_x86_64, 8});
m_program.emplace_back(EHInstruction{o, EHInstruction::Type::PUSH_REGISTER,
lldb_rflags_x86_64, 8});
m_program.emplace_back(EHInstruction{o, EHInstruction::Type::PUSH_REGISTER,
lldb_rsp_x86_64, 8});
m_program.emplace_back(EHInstruction{o, EHInstruction::Type::PUSH_REGISTER,
lldb_ss_x86_64, 8});
return true;
}
default:
return false;
}
}
void EHProgramBuilder::Finalize() {
for (const EHInstruction &i : m_program)
if (i.reg == lldb_rip_x86_64)
return;
m_program.emplace_back(
EHInstruction{0, EHInstruction::Type::PUSH_REGISTER, lldb_rip_x86_64, 8});
}
bool EHProgramBuilder::ParseBigOrScaledFrameOffset(uint32_t &result, bool big,
uint32_t scale) {
if (big) {
if (!ParseBigFrameOffset(result))
return false;
} else {
if (!ParseFrameOffset(result))
return false;
result *= scale;
}
return true;
}
bool EHProgramBuilder::ParseBigFrameOffset(uint32_t &result) {
if (!m_iterator.GetNext())
return false;
result = m_iterator.GetUnwindCode()->FrameOffset;
if (!m_iterator.GetNext())
return false;
result += static_cast<uint32_t>(m_iterator.GetUnwindCode()->FrameOffset)
<< 16;
return true;
}
bool EHProgramBuilder::ParseFrameOffset(uint32_t &result) {
if (!m_iterator.GetNext())
return false;
result = m_iterator.GetUnwindCode()->FrameOffset;
return true;
}
class EHProgramRange {
public:
EHProgramRange(EHProgram::const_iterator begin,
EHProgram::const_iterator end);
std::unique_ptr<UnwindPlan::Row> BuildUnwindPlanRow() const;
private:
int32_t GetCFAFrameOffset() const;
EHProgram::const_iterator m_begin;
EHProgram::const_iterator m_end;
};
EHProgramRange::EHProgramRange(EHProgram::const_iterator begin,
EHProgram::const_iterator end)
: m_begin(begin), m_end(end) {}
std::unique_ptr<UnwindPlan::Row> EHProgramRange::BuildUnwindPlanRow() const {
std::unique_ptr<UnwindPlan::Row> row = std::make_unique<UnwindPlan::Row>();
if (m_begin != m_end)
row->SetOffset(m_begin->offset);
int32_t cfa_frame_offset = GetCFAFrameOffset();
bool frame_pointer_found = false;
for (EHProgram::const_iterator it = m_begin; it != m_end; ++it) {
switch (it->type) {
case EHInstruction::Type::SET_FRAME_POINTER_REGISTER:
row->GetCFAValue().SetIsRegisterPlusOffset(it->reg, cfa_frame_offset -
it->frame_offset);
frame_pointer_found = true;
break;
default:
break;
}
if (frame_pointer_found)
break;
}
if (!frame_pointer_found)
row->GetCFAValue().SetIsRegisterPlusOffset(lldb_rsp_x86_64,
cfa_frame_offset);
int32_t rsp_frame_offset = 0;
for (EHProgram::const_iterator it = m_begin; it != m_end; ++it) {
switch (it->type) {
case EHInstruction::Type::PUSH_REGISTER:
row->SetRegisterLocationToAtCFAPlusOffset(
it->reg, rsp_frame_offset - cfa_frame_offset, false);
rsp_frame_offset += it->frame_offset;
break;
case EHInstruction::Type::ALLOCATE:
rsp_frame_offset += it->frame_offset;
break;
case EHInstruction::Type::SAVE_REGISTER:
row->SetRegisterLocationToAtCFAPlusOffset(
it->reg, it->frame_offset - cfa_frame_offset, false);
break;
default:
break;
}
}
row->SetRegisterLocationToIsCFAPlusOffset(lldb_rsp_x86_64, 0, false);
return row;
}
int32_t EHProgramRange::GetCFAFrameOffset() const {
int32_t result = 0;
for (EHProgram::const_iterator it = m_begin; it != m_end; ++it) {
switch (it->type) {
case EHInstruction::Type::PUSH_REGISTER:
case EHInstruction::Type::ALLOCATE:
result += it->frame_offset;
break;
default:
break;
}
}
return result;
}
PECallFrameInfo::PECallFrameInfo(ObjectFilePECOFF &object_file,
uint32_t exception_dir_rva,
uint32_t exception_dir_size)
: m_object_file(object_file),
m_exception_dir(object_file.ReadImageDataByRVA(exception_dir_rva,
exception_dir_size)) {}
bool PECallFrameInfo::GetAddressRange(Address addr, AddressRange &range) {
range.Clear();
const RuntimeFunction *runtime_function =
FindRuntimeFunctionIntersectsWithRange(AddressRange(addr, 1));
if (!runtime_function)
return false;
range.GetBaseAddress() =
m_object_file.GetAddress(runtime_function->StartAddress);
range.SetByteSize(runtime_function->EndAddress -
runtime_function->StartAddress);
return true;
}
bool PECallFrameInfo::GetUnwindPlan(const Address &addr,
UnwindPlan &unwind_plan) {
return GetUnwindPlan(AddressRange(addr, 1), unwind_plan);
}
bool PECallFrameInfo::GetUnwindPlan(const AddressRange &range,
UnwindPlan &unwind_plan) {
unwind_plan.Clear();
unwind_plan.SetSourceName("PE EH info");
unwind_plan.SetSourcedFromCompiler(eLazyBoolYes);
unwind_plan.SetRegisterKind(eRegisterKindLLDB);
const RuntimeFunction *runtime_function =
FindRuntimeFunctionIntersectsWithRange(range);
if (!runtime_function)
return false;
EHProgramBuilder builder(m_object_file, runtime_function->UnwindInfoOffset);
if (!builder.Build())
return false;
std::vector<UnwindPlan::RowSP> rows;
uint32_t last_offset = UINT32_MAX;
for (auto it = builder.GetProgram().begin(); it != builder.GetProgram().end();
++it) {
if (it->offset == last_offset)
continue;
EHProgramRange program_range =
EHProgramRange(it, builder.GetProgram().end());
rows.push_back(program_range.BuildUnwindPlanRow());
last_offset = it->offset;
}
for (auto it = rows.rbegin(); it != rows.rend(); ++it)
unwind_plan.AppendRow(*it);
unwind_plan.SetPlanValidAddressRange(AddressRange(
m_object_file.GetAddress(runtime_function->StartAddress),
runtime_function->EndAddress - runtime_function->StartAddress));
unwind_plan.SetUnwindPlanValidAtAllInstructions(eLazyBoolNo);
return true;
}
const RuntimeFunction *PECallFrameInfo::FindRuntimeFunctionIntersectsWithRange(
const AddressRange &range) const {
uint32_t rva = m_object_file.GetRVA(range.GetBaseAddress());
addr_t size = range.GetByteSize();
uint32_t begin = 0;
uint32_t end = m_exception_dir.GetByteSize() / sizeof(RuntimeFunction);
while (begin < end) {
uint32_t curr = (begin + end) / 2;
offset_t offset = curr * sizeof(RuntimeFunction);
const auto *runtime_function =
TypedRead<RuntimeFunction>(m_exception_dir, offset);
if (!runtime_function)
break;
if (runtime_function->StartAddress < rva + size &&
runtime_function->EndAddress > rva)
return runtime_function;
if (runtime_function->StartAddress >= rva + size)
end = curr;
if (runtime_function->EndAddress <= rva)
begin = curr + 1;
}
return nullptr;
}