133 changes: 97 additions & 36 deletions lldb/source/Symbol/FuncUnwinders.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ FuncUnwinders::FuncUnwinders(UnwindTable &unwind_table, AddressRange range)
m_unwind_plan_arch_default_sp(),
m_unwind_plan_arch_default_at_func_entry_sp(),
m_tried_unwind_plan_assembly(false), m_tried_unwind_plan_eh_frame(false),
m_tried_unwind_plan_debug_frame(false),
m_tried_unwind_plan_eh_frame_augmented(false),
m_tried_unwind_plan_debug_frame_augmented(false),
m_tried_unwind_plan_compact_unwind(false),
m_tried_unwind_plan_arm_unwind(false), m_tried_unwind_fast(false),
m_tried_unwind_arch_default(false),
Expand All @@ -56,17 +58,14 @@ UnwindPlanSP FuncUnwinders::GetUnwindPlanAtCallSite(Target &target,
int current_offset) {
std::lock_guard<std::recursive_mutex> guard(m_mutex);

UnwindPlanSP unwind_plan_sp = GetEHFrameUnwindPlan(target, current_offset);
if (unwind_plan_sp)
return unwind_plan_sp;

unwind_plan_sp = GetCompactUnwindUnwindPlan(target, current_offset);
if (unwind_plan_sp)
return unwind_plan_sp;

unwind_plan_sp = GetArmUnwindUnwindPlan(target, current_offset);
if (unwind_plan_sp)
return unwind_plan_sp;
if (UnwindPlanSP plan_sp = GetEHFrameUnwindPlan(target, current_offset))
return plan_sp;
if (UnwindPlanSP plan_sp = GetDebugFrameUnwindPlan(target, current_offset))
return plan_sp;
if (UnwindPlanSP plan_sp = GetCompactUnwindUnwindPlan(target, current_offset))
return plan_sp;
if (UnwindPlanSP plan_sp = GetArmUnwindUnwindPlan(target, current_offset))
return plan_sp;

return nullptr;
}
Expand Down Expand Up @@ -121,6 +120,29 @@ UnwindPlanSP FuncUnwinders::GetEHFrameUnwindPlan(Target &target,
return m_unwind_plan_eh_frame_sp;
}

UnwindPlanSP FuncUnwinders::GetDebugFrameUnwindPlan(Target &target,
int current_offset) {
std::lock_guard<std::recursive_mutex> guard(m_mutex);
if (m_unwind_plan_debug_frame_sp || m_tried_unwind_plan_debug_frame)
return m_unwind_plan_debug_frame_sp;

m_tried_unwind_plan_debug_frame = true;
if (m_range.GetBaseAddress().IsValid()) {
Address current_pc(m_range.GetBaseAddress());
if (current_offset != -1)
current_pc.SetOffset(current_pc.GetOffset() + current_offset);
DWARFCallFrameInfo *debug_frame = m_unwind_table.GetDebugFrameInfo();
if (debug_frame) {
m_unwind_plan_debug_frame_sp.reset(
new UnwindPlan(lldb::eRegisterKindGeneric));
if (!debug_frame->GetUnwindPlan(current_pc,
*m_unwind_plan_debug_frame_sp))
m_unwind_plan_debug_frame_sp.reset();
}
}
return m_unwind_plan_debug_frame_sp;
}

UnwindPlanSP FuncUnwinders::GetArmUnwindUnwindPlan(Target &target,
int current_offset) {
std::lock_guard<std::recursive_mutex> guard(m_mutex);
Expand Down Expand Up @@ -187,6 +209,48 @@ UnwindPlanSP FuncUnwinders::GetEHFrameAugmentedUnwindPlan(Target &target,
return m_unwind_plan_eh_frame_augmented_sp;
}

UnwindPlanSP
FuncUnwinders::GetDebugFrameAugmentedUnwindPlan(Target &target, Thread &thread,
int current_offset) {
std::lock_guard<std::recursive_mutex> guard(m_mutex);
if (m_unwind_plan_debug_frame_augmented_sp.get() ||
m_tried_unwind_plan_debug_frame_augmented)
return m_unwind_plan_debug_frame_augmented_sp;

// Only supported on x86 architectures where we get debug_frame from the
// compiler that describes the prologue instructions perfectly, and sometimes
// the epilogue instructions too.
if (target.GetArchitecture().GetCore() != ArchSpec::eCore_x86_32_i386 &&
target.GetArchitecture().GetCore() != ArchSpec::eCore_x86_64_x86_64 &&
target.GetArchitecture().GetCore() != ArchSpec::eCore_x86_64_x86_64h) {
m_tried_unwind_plan_debug_frame_augmented = true;
return m_unwind_plan_debug_frame_augmented_sp;
}

m_tried_unwind_plan_debug_frame_augmented = true;

UnwindPlanSP debug_frame_plan =
GetDebugFrameUnwindPlan(target, current_offset);
if (!debug_frame_plan)
return m_unwind_plan_debug_frame_augmented_sp;

m_unwind_plan_debug_frame_augmented_sp.reset(
new UnwindPlan(*debug_frame_plan));

// Augment the debug_frame instructions with epilogue descriptions if
// necessary so the UnwindPlan can be used at any instruction in the function.

UnwindAssemblySP assembly_profiler_sp(GetUnwindAssemblyProfiler(target));
if (assembly_profiler_sp) {
if (!assembly_profiler_sp->AugmentUnwindPlanFromCallSite(
m_range, thread, *m_unwind_plan_debug_frame_augmented_sp)) {
m_unwind_plan_debug_frame_augmented_sp.reset();
}
} else
m_unwind_plan_debug_frame_augmented_sp.reset();
return m_unwind_plan_debug_frame_augmented_sp;
}

UnwindPlanSP FuncUnwinders::GetAssemblyUnwindPlan(Target &target,
Thread &thread,
int current_offset) {
Expand Down Expand Up @@ -248,35 +312,31 @@ UnwindPlanSP FuncUnwinders::GetUnwindPlanAtNonCallSite(Target &target,
Thread &thread,
int current_offset) {
UnwindPlanSP eh_frame_sp = GetEHFrameUnwindPlan(target, current_offset);
if (!eh_frame_sp)
eh_frame_sp = GetDebugFrameUnwindPlan(target, current_offset);
UnwindPlanSP arch_default_at_entry_sp =
GetUnwindPlanArchitectureDefaultAtFunctionEntry(thread);
UnwindPlanSP arch_default_sp = GetUnwindPlanArchitectureDefault(thread);
UnwindPlanSP assembly_sp =
GetAssemblyUnwindPlan(target, thread, current_offset);

// This point of this code is to detect when a function is using a
// non-standard ABI, and the eh_frame
// correctly describes that alternate ABI. This is addressing a specific
// situation on x86_64 linux
// systems where one function in a library pushes a value on the stack and
// jumps to another function.
// So using an assembly instruction based unwind will not work when you're in
// the second function -
// the stack has been modified in a non-ABI way. But we have eh_frame that
// correctly describes how to
// unwind from this location. So we're looking to see if the initial pc
// register save location from
// the eh_frame is different from the assembly unwind, the arch default
// unwind, and the arch default at
// initial function entry.
// non-standard ABI, and the eh_frame correctly describes that alternate ABI.
// This is addressing a specific situation on x86_64 linux systems where one
// function in a library pushes a value on the stack and jumps to another
// function. So using an assembly instruction based unwind will not work when
// you're in the second function - the stack has been modified in a non-ABI
// way. But we have eh_frame that correctly describes how to unwind from this
// location. So we're looking to see if the initial pc register save location
// from the eh_frame is different from the assembly unwind, the arch default
// unwind, and the arch default at initial function entry.
//
// We may have eh_frame that describes the entire function -- or we may have
// eh_frame that only describes
// the unwind after the prologue has executed -- so we need to check both the
// arch default (once the prologue
// has executed) and the arch default at initial function entry. And we may
// be running on a target where
// we have only some of the assembly/arch default unwind plans available.
// eh_frame that only describes the unwind after the prologue has executed --
// so we need to check both the arch default (once the prologue has executed)
// and the arch default at initial function entry. And we may be running on a
// target where we have only some of the assembly/arch default unwind plans
// available.

if (CompareUnwindPlansForIdenticalInitialPCLocation(
thread, eh_frame_sp, arch_default_at_entry_sp) == eLazyBoolNo &&
Expand All @@ -287,11 +347,12 @@ UnwindPlanSP FuncUnwinders::GetUnwindPlanAtNonCallSite(Target &target,
return eh_frame_sp;
}

UnwindPlanSP eh_frame_augmented_sp =
GetEHFrameAugmentedUnwindPlan(target, thread, current_offset);
if (eh_frame_augmented_sp) {
return eh_frame_augmented_sp;
}
if (UnwindPlanSP plan_sp =
GetEHFrameAugmentedUnwindPlan(target, thread, current_offset))
return plan_sp;
if (UnwindPlanSP plan_sp =
GetDebugFrameAugmentedUnwindPlan(target, thread, current_offset))
return plan_sp;

return assembly_sp;
}
Expand Down
110 changes: 61 additions & 49 deletions lldb/source/Symbol/UnwindTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,38 +44,64 @@ void UnwindTable::Initialize() {

if (m_initialized) // check again once we've acquired the lock
return;
m_initialized = true;

SectionList *sl = m_object_file.GetSectionList();
if (sl) {
SectionSP sect = sl->FindSectionByType(eSectionTypeEHFrame, true);
if (sect.get()) {
m_eh_frame_up.reset(new DWARFCallFrameInfo(m_object_file, sect,
eRegisterKindEHFrame, true));
}
sect = sl->FindSectionByType(eSectionTypeCompactUnwind, true);
if (sect.get()) {
m_compact_unwind_up.reset(new CompactUnwindInfo(m_object_file, sect));
}
sect = sl->FindSectionByType(eSectionTypeARMexidx, true);
if (sect.get()) {
SectionSP sect_extab = sl->FindSectionByType(eSectionTypeARMextab, true);
if (sect_extab.get()) {
m_arm_unwind_up.reset(
new ArmUnwindInfo(m_object_file, sect, sect_extab));
}
}
if (!sl)
return;

SectionSP sect = sl->FindSectionByType(eSectionTypeEHFrame, true);
if (sect.get()) {
m_eh_frame_up.reset(new DWARFCallFrameInfo(m_object_file, sect,
eRegisterKindEHFrame, true));
}

m_initialized = true;
sect = sl->FindSectionByType(eSectionTypeDWARFDebugFrame, true);
if (sect) {
m_debug_frame_up.reset(
new DWARFCallFrameInfo(m_object_file, sect, eRegisterKindDWARF, false));
}

sect = sl->FindSectionByType(eSectionTypeCompactUnwind, true);
if (sect) {
m_compact_unwind_up.reset(new CompactUnwindInfo(m_object_file, sect));
}

sect = sl->FindSectionByType(eSectionTypeARMexidx, true);
if (sect) {
SectionSP sect_extab = sl->FindSectionByType(eSectionTypeARMextab, true);
if (sect_extab.get()) {
m_arm_unwind_up.reset(new ArmUnwindInfo(m_object_file, sect, sect_extab));
}
}
}

UnwindTable::~UnwindTable() {}

llvm::Optional<AddressRange> UnwindTable::GetAddressRange(const Address &addr,
SymbolContext &sc) {
AddressRange range;

// First check the symbol context
if (sc.GetAddressRange(eSymbolContextFunction | eSymbolContextSymbol, 0,
false, range) &&
range.GetBaseAddress().IsValid())
return range;

// Does the eh_frame unwind info has a function bounds for this addr?
if (m_eh_frame_up && m_eh_frame_up->GetAddressRange(addr, range))
return range;

// Try debug_frame as well
if (m_debug_frame_up && m_debug_frame_up->GetAddressRange(addr, range))
return range;

return llvm::None;
}

FuncUnwindersSP
UnwindTable::GetFuncUnwindersContainingAddress(const Address &addr,
SymbolContext &sc) {
FuncUnwindersSP no_unwind_found;

Initialize();

std::lock_guard<std::mutex> guard(m_mutex);
Expand All @@ -96,23 +122,14 @@ UnwindTable::GetFuncUnwindersContainingAddress(const Address &addr,
return pos->second;
}

AddressRange range;
if (!sc.GetAddressRange(eSymbolContextFunction | eSymbolContextSymbol, 0,
false, range) ||
!range.GetBaseAddress().IsValid()) {
// Does the eh_frame unwind info has a function bounds for this addr?
if (m_eh_frame_up == nullptr ||
!m_eh_frame_up->GetAddressRange(addr, range)) {
return no_unwind_found;
}
}
auto range_or = GetAddressRange(addr, sc);
if (!range_or)
return nullptr;

FuncUnwindersSP func_unwinder_sp(new FuncUnwinders(*this, range));
FuncUnwindersSP func_unwinder_sp(new FuncUnwinders(*this, *range_or));
m_unwinds.insert(insert_pos,
std::make_pair(range.GetBaseAddress().GetFileAddress(),
std::make_pair(range_or->GetBaseAddress().GetFileAddress(),
func_unwinder_sp));
// StreamFile s(stdout, false);
// Dump (s);
return func_unwinder_sp;
}

Expand All @@ -121,26 +138,16 @@ UnwindTable::GetFuncUnwindersContainingAddress(const Address &addr,
// UnwindTable. This is intended for use by target modules show-unwind where we
// want to create
// new UnwindPlans, not re-use existing ones.

FuncUnwindersSP
UnwindTable::GetUncachedFuncUnwindersContainingAddress(const Address &addr,
SymbolContext &sc) {
FuncUnwindersSP no_unwind_found;
Initialize();

AddressRange range;
if (!sc.GetAddressRange(eSymbolContextFunction | eSymbolContextSymbol, 0,
false, range) ||
!range.GetBaseAddress().IsValid()) {
// Does the eh_frame unwind info has a function bounds for this addr?
if (m_eh_frame_up == nullptr ||
!m_eh_frame_up->GetAddressRange(addr, range)) {
return no_unwind_found;
}
}
auto range_or = GetAddressRange(addr, sc);
if (!range_or)
return nullptr;

FuncUnwindersSP func_unwinder_sp(new FuncUnwinders(*this, range));
return func_unwinder_sp;
return std::make_shared<FuncUnwinders>(*this, *range_or);
}

void UnwindTable::Dump(Stream &s) {
Expand All @@ -161,6 +168,11 @@ DWARFCallFrameInfo *UnwindTable::GetEHFrameInfo() {
return m_eh_frame_up.get();
}

DWARFCallFrameInfo *UnwindTable::GetDebugFrameInfo() {
Initialize();
return m_debug_frame_up.get();
}

CompactUnwindInfo *UnwindTable::GetCompactUnwindInfo() {
Initialize();
return m_compact_unwind_up.get();
Expand Down
8 changes: 8 additions & 0 deletions lldb/unittests/Symbol/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
add_lldb_unittest(SymbolTests
TestClangASTContext.cpp
TestDWARFCallFrameInfo.cpp
TestType.cpp

LINK_LIBS
lldbHost
lldbSymbol
)

add_dependencies(SymbolTests yaml2obj)
add_definitions(-DYAML2OBJ="$<TARGET_FILE:yaml2obj>")
set(test_inputs
basic-call-frame-info.yaml
)
add_unittest_inputs(SymbolTests "${test_inputs}")
138 changes: 138 additions & 0 deletions lldb/unittests/Symbol/Inputs/basic-call-frame-info.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
--- !ELF
FileHeader:
Class: ELFCLASS64
Data: ELFDATA2LSB
Type: ET_DYN
Machine: EM_X86_64
Entry: 0x0000000000000260
Sections:
- Name: .text
Type: SHT_PROGBITS
Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
Address: 0x0000000000000260
AddressAlign: 0x0000000000000010
Content: 554889E5897DFC8B45FC5DC30F1F4000554889E5897DFC8B45FC5DC30F1F4000554889E5897DFC8B45FC5DC3
#0000000000000260 <eh_frame>:
# 260: 55 push %rbp
# 261: 48 89 e5 mov %rsp,%rbp
# 264: 89 7d fc mov %edi,-0x4(%rbp)
# 267: 8b 45 fc mov -0x4(%rbp),%eax
# 26a: 5d pop %rbp
# 26b: c3 retq
# 26c: 0f 1f 40 00 nopl 0x0(%rax)
#
#0000000000000270 <debug_frame3>:
# 270: 55 push %rbp
# 271: 48 89 e5 mov %rsp,%rbp
# 274: 89 7d fc mov %edi,-0x4(%rbp)
# 277: 8b 45 fc mov -0x4(%rbp),%eax
# 27a: 5d pop %rbp
# 27b: c3 retq
# 27c: 0f 1f 40 00 nopl 0x0(%rax)
#
#0000000000000280 <debug_frame4>:
# 280: 55 push %rbp
# 281: 48 89 e5 mov %rsp,%rbp
# 284: 89 7d fc mov %edi,-0x4(%rbp)
# 287: 8b 45 fc mov -0x4(%rbp),%eax
# 28a: 5d pop %rbp
# 28b: c3 retq
- Name: .eh_frame
Type: SHT_X86_64_UNWIND
Flags: [ SHF_ALLOC ]
Address: 0x0000000000000290
AddressAlign: 0x0000000000000008
Content: 1400000000000000017A5200017810011B0C0708900100001C0000001C000000B0FFFFFF0C00000000410E108602430D0600000000000000
#00000000 0000000000000014 00000000 CIE
# Version: 1
# Augmentation: "zR"
# Code alignment factor: 1
# Data alignment factor: -8
# Return address column: 16
# Augmentation data: 1b
#
# DW_CFA_def_cfa: r7 (rsp) ofs 8
# DW_CFA_offset: r16 (rip) at cfa-8
# DW_CFA_nop
# DW_CFA_nop
#
#00000018 000000000000001c 0000001c FDE cie=00000000 pc=ffffffffffffffd0..ffffffffffffffdc
# DW_CFA_advance_loc: 1 to ffffffffffffffd1
# DW_CFA_def_cfa_offset: 16
# DW_CFA_offset: r6 (rbp) at cfa-16
# DW_CFA_advance_loc: 3 to ffffffffffffffd4
# DW_CFA_def_cfa_register: r6 (rbp)
# DW_CFA_nop
# DW_CFA_nop
# DW_CFA_nop
# DW_CFA_nop
# DW_CFA_nop
# DW_CFA_nop
# DW_CFA_nop
- Name: .debug_frame
Type: SHT_PROGBITS
AddressAlign: 0x0000000000000008
Content: 14000000FFFFFFFF03000178100C070890010000000000001C0000000000000070020000000000000C00000000000000410E108602430D0614000000FFFFFFFF040008000178100C07089001000000001C0000003800000080020000000000000C00000000000000410E108602430D06
#00000000 0000000000000014 ffffffff CIE
# Version: 3
# Augmentation: ""
# Code alignment factor: 1
# Data alignment factor: -8
# Return address column: 16
#
# DW_CFA_def_cfa: r7 (rsp) ofs 8
# DW_CFA_offset: r16 (rip) at cfa-8
# DW_CFA_nop
# DW_CFA_nop
# DW_CFA_nop
# DW_CFA_nop
# DW_CFA_nop
# DW_CFA_nop
#
#00000018 000000000000001c 00000000 FDE cie=00000000 pc=0000000000000270..000000000000027c
# DW_CFA_advance_loc: 1 to 0000000000000271
# DW_CFA_def_cfa_offset: 16
# DW_CFA_offset: r6 (rbp) at cfa-16
# DW_CFA_advance_loc: 3 to 0000000000000274
# DW_CFA_def_cfa_register: r6 (rbp)
#
#00000038 0000000000000014 ffffffff CIE
# Version: 4
# Augmentation: ""
# Pointer Size: 8
# Segment Size: 0
# Code alignment factor: 1
# Data alignment factor: -8
# Return address column: 16
#
# DW_CFA_def_cfa: r7 (rsp) ofs 8
# DW_CFA_offset: r16 (rip) at cfa-8
# DW_CFA_nop
# DW_CFA_nop
# DW_CFA_nop
# DW_CFA_nop
#
#00000050 000000000000001c 00000038 FDE cie=00000038 pc=0000000000000280..000000000000028c
# DW_CFA_advance_loc: 1 to 0000000000000281
# DW_CFA_def_cfa_offset: 16
# DW_CFA_offset: r6 (rbp) at cfa-16
# DW_CFA_advance_loc: 3 to 0000000000000284
# DW_CFA_def_cfa_register: r6 (rbp)
Symbols:
Global:
- Name: eh_frame
Type: STT_FUNC
Section: .text
Value: 0x0000000000000260
Size: 0x000000000000000C
- Name: debug_frame3
Type: STT_FUNC
Section: .text
Value: 0x0000000000000270
Size: 0x000000000000000C
- Name: debug_frame4
Type: STT_FUNC
Section: .text
Value: 0x0000000000000280
Size: 0x000000000000000C
...
147 changes: 147 additions & 0 deletions lldb/unittests/Symbol/TestDWARFCallFrameInfo.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
//===-- TestDWARFCallFrameInfo.cpp ------------------------------*- C++ -*-===//
//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "Plugins/ObjectFile/ELF/ObjectFileELF.h"
#include "Plugins/Process/Utility/RegisterContext_x86.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/ModuleSpec.h"
#include "lldb/Core/Section.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Symbol/DWARFCallFrameInfo.h"
#include "lldb/Utility/StreamString.h"
#include "llvm/Support/FileUtilities.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/raw_ostream.h"
#include "gtest/gtest.h"

extern const char *TestMainArgv0;

using namespace lldb_private;
using namespace lldb;

class DWARFCallFrameInfoTest : public testing::Test {
public:
void SetUp() override {
HostInfo::Initialize();
ObjectFileELF::Initialize();

m_inputs_folder = llvm::sys::path::parent_path(TestMainArgv0);
llvm::sys::path::append(m_inputs_folder, "Inputs");
llvm::sys::fs::make_absolute(m_inputs_folder);
}

void TearDown() override {
ObjectFileELF::Terminate();
HostInfo::Terminate();
}

protected:
llvm::SmallString<128> m_inputs_folder;

void TestBasic(bool eh_frame, llvm::StringRef symbol);
};

#define ASSERT_NO_ERROR(x) \
if (std::error_code ASSERT_NO_ERROR_ec = x) { \
llvm::SmallString<128> MessageStorage; \
llvm::raw_svector_ostream Message(MessageStorage); \
Message << #x ": did not return errc::success.\n" \
<< "error number: " << ASSERT_NO_ERROR_ec.value() << "\n" \
<< "error message: " << ASSERT_NO_ERROR_ec.message() << "\n"; \
GTEST_FATAL_FAILURE_(MessageStorage.c_str()); \
} else { \
}

namespace lldb_private {
static std::ostream &operator<<(std::ostream &OS, const UnwindPlan::Row &row) {
StreamString SS;
row.Dump(SS, nullptr, nullptr, 0);
return OS << SS.GetData();
}
} // namespace lldb_private

static UnwindPlan::Row GetExpectedRow0() {
UnwindPlan::Row row;
row.SetOffset(0);
row.GetCFAValue().SetIsRegisterPlusOffset(dwarf_rsp_x86_64, 8);
row.SetRegisterLocationToAtCFAPlusOffset(dwarf_rip_x86_64, -8, false);
return row;
}

static UnwindPlan::Row GetExpectedRow1() {
UnwindPlan::Row row;
row.SetOffset(1);
row.GetCFAValue().SetIsRegisterPlusOffset(dwarf_rsp_x86_64, 16);
row.SetRegisterLocationToAtCFAPlusOffset(dwarf_rip_x86_64, -8, false);
row.SetRegisterLocationToAtCFAPlusOffset(dwarf_rbp_x86_64, -16, false);
return row;
}

static UnwindPlan::Row GetExpectedRow2() {
UnwindPlan::Row row;
row.SetOffset(4);
row.GetCFAValue().SetIsRegisterPlusOffset(dwarf_rbp_x86_64, 16);
row.SetRegisterLocationToAtCFAPlusOffset(dwarf_rip_x86_64, -8, false);
row.SetRegisterLocationToAtCFAPlusOffset(dwarf_rbp_x86_64, -16, false);
return row;
}

void DWARFCallFrameInfoTest::TestBasic(bool eh_frame, llvm::StringRef symbol) {
llvm::SmallString<128> yaml = m_inputs_folder;
llvm::sys::path::append(yaml, "basic-call-frame-info.yaml");
llvm::SmallString<128> obj = m_inputs_folder;

ASSERT_NO_ERROR(llvm::sys::fs::createTemporaryFile(
"basic-call-frame-info-%%%%%%", "obj", obj));
llvm::FileRemover obj_remover(obj);

const char *args[] = {YAML2OBJ, yaml.c_str(), nullptr};
llvm::StringRef obj_ref = obj;
const llvm::StringRef *redirects[] = {nullptr, &obj_ref, nullptr};
ASSERT_EQ(0, llvm::sys::ExecuteAndWait(YAML2OBJ, args, nullptr, redirects));

uint64_t size;
ASSERT_NO_ERROR(llvm::sys::fs::file_size(obj, size));
ASSERT_GT(size, 0u);

auto module_sp = std::make_shared<Module>(ModuleSpec(FileSpec(obj, false)));
SectionList *list = module_sp->GetSectionList();
ASSERT_NE(nullptr, list);

auto section_sp = list->FindSectionByType(
eh_frame ? eSectionTypeEHFrame : eSectionTypeDWARFDebugFrame, false);
ASSERT_NE(nullptr, section_sp);

DWARFCallFrameInfo cfi(*module_sp->GetObjectFile(), section_sp,
eh_frame ? eRegisterKindEHFrame : eRegisterKindDWARF,
eh_frame);

const Symbol *sym = module_sp->FindFirstSymbolWithNameAndType(
ConstString(symbol), eSymbolTypeAny);
ASSERT_NE(nullptr, sym);

UnwindPlan plan(eRegisterKindGeneric);
ASSERT_TRUE(cfi.GetUnwindPlan(sym->GetAddress(), plan));
ASSERT_EQ(3, plan.GetRowCount());
EXPECT_EQ(GetExpectedRow0(), *plan.GetRowAtIndex(0));
EXPECT_EQ(GetExpectedRow1(), *plan.GetRowAtIndex(1));
EXPECT_EQ(GetExpectedRow2(), *plan.GetRowAtIndex(2));
}

TEST_F(DWARFCallFrameInfoTest, Basic_dwarf3) {
TestBasic(false, "debug_frame3");
}

TEST_F(DWARFCallFrameInfoTest, Basic_dwarf4) {
TestBasic(false, "debug_frame4");
}

TEST_F(DWARFCallFrameInfoTest, Basic_eh) { TestBasic(true, "eh_frame"); }