You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
537 lines
16 KiB
537 lines
16 KiB
#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;
|
|
}
|