On x86 & x86_64, try to use eh_frame for frame 0.

We decided to use assmbly profiler instead of eh_frame for frame 0 because for compiler generated code, eh_frame is usually synchronous(a.k.a. only valid at call site); and we have no way to tell if it's asynchronous or not.
But for x86 & x86_64 compiler generated code:
1. clang & GCC describes all prologue instructions in eh_frame;
2. mid-function stack pointer altering instructions can be easily detected.
So we can grab eh_frame, and use assembly profiler to augment it into asynchronous unwind table.
This change also benefits hand-written assembly; eh_frame for hand-written assembly is often asynchronous,so we have a much better chance to successfully unwind through them.

Change by Tong Shen.

llvm-svn: 216406
This commit is contained in:
Todd Fiala 2014-08-25 20:29:09 +00:00
parent be7bf7285b
commit 0562524b45
11 changed files with 315 additions and 17 deletions

View File

@ -44,7 +44,7 @@ public:
GetUnwindPlanAtCallSite (int current_offset);
lldb::UnwindPlanSP
GetUnwindPlanAtNonCallSite (lldb_private::Thread& thread);
GetUnwindPlanAtNonCallSite (Target& target, lldb_private::Thread& thread, int current_offset);
lldb::UnwindPlanSP
GetUnwindPlanFastUnwind (lldb_private::Thread& Thread);

View File

@ -365,6 +365,9 @@ public:
void
AppendRow (const RowSP& row_sp);
void
InsertRow (const RowSP& row_sp);
// Returns a pointer to the best row for the given offset into the function's instructions.
// If offset is -1 it indicates that the function start is unknown - the final row in the UnwindPlan is returned.
// In practice, the UnwindPlan for a function with no known start address will be the architectural default

View File

@ -32,6 +32,11 @@ public:
Thread& thread,
UnwindPlan& unwind_plan) = 0;
virtual bool
AugmentUnwindPlanFromCallSite (AddressRange& func,
Thread& thread,
UnwindPlan& unwind_plan) = 0;
virtual bool
GetFastUnwindPlan (AddressRange& func,
Thread& thread,

View File

@ -3724,7 +3724,7 @@ protected:
result.GetOutputStream().Printf ("\n");
}
UnwindPlanSP non_callsite_unwind_plan = func_unwinders_sp->GetUnwindPlanAtNonCallSite(*thread.get());
UnwindPlanSP non_callsite_unwind_plan = func_unwinders_sp->GetUnwindPlanAtNonCallSite(*target, *thread.get(), -1);
if (non_callsite_unwind_plan.get())
{
result.GetOutputStream().Printf("Asynchronous (not restricted to call-sites) UnwindPlan for %s`%s (start addr 0x%" PRIx64 "):\n", sc.module_sp->GetPlatformFileSpec().GetFilename().AsCString(), funcname.AsCString(), start_addr);

View File

@ -794,7 +794,7 @@ RegisterContextLLDB::GetFullUnwindPlanForFrame ()
// Typically the NonCallSite UnwindPlan is the unwind created by inspecting the assembly language instructions
if (behaves_like_zeroth_frame)
{
unwind_plan_sp = func_unwinders_sp->GetUnwindPlanAtNonCallSite (m_thread);
unwind_plan_sp = func_unwinders_sp->GetUnwindPlanAtNonCallSite (process->GetTarget(), m_thread, m_current_offset_backed_up_one);
if (unwind_plan_sp && unwind_plan_sp->PlanValidAtAddress (m_current_pc))
{
if (unwind_plan_sp->GetSourcedFromCompiler() == eLazyBoolNo)
@ -822,7 +822,7 @@ RegisterContextLLDB::GetFullUnwindPlanForFrame ()
// We'd prefer to use an UnwindPlan intended for call sites when we're at a call site but if we've
// struck out on that, fall back to using the non-call-site assembly inspection UnwindPlan if possible.
unwind_plan_sp = func_unwinders_sp->GetUnwindPlanAtNonCallSite (m_thread);
unwind_plan_sp = func_unwinders_sp->GetUnwindPlanAtNonCallSite (process->GetTarget(), m_thread, m_current_offset_backed_up_one);
if (unwind_plan_sp && unwind_plan_sp->GetSourcedFromCompiler() == eLazyBoolNo)
{
// We probably have an UnwindPlan created by inspecting assembly instructions, and we probably

View File

@ -284,6 +284,14 @@ UnwindAssemblyInstEmulation::GetNonCallSiteUnwindPlanFromAssembly (AddressRange&
return false;
}
bool
UnwindAssemblyInstEmulation::AugmentUnwindPlanFromCallSite (AddressRange& func,
Thread& thread,
UnwindPlan& unwind_plan)
{
return false;
}
bool
UnwindAssemblyInstEmulation::GetFastUnwindPlan (AddressRange& func,
Thread& thread,

View File

@ -30,6 +30,11 @@ public:
lldb_private::Thread& thread,
lldb_private::UnwindPlan& unwind_plan);
virtual bool
AugmentUnwindPlanFromCallSite (lldb_private::AddressRange& func,
lldb_private::Thread& thread,
lldb_private::UnwindPlan& unwind_plan);
virtual bool
GetFastUnwindPlan (lldb_private::AddressRange& func,
lldb_private::Thread& thread,

View File

@ -123,6 +123,8 @@ public:
bool get_non_call_site_unwind_plan (UnwindPlan &unwind_plan);
bool augment_unwind_plan_from_call_site (AddressRange& func, UnwindPlan &unwind_plan);
bool get_fast_unwind_plan (AddressRange& func, UnwindPlan &unwind_plan);
bool find_first_non_prologue_insn (Address &address);
@ -135,9 +137,14 @@ private:
bool push_0_pattern_p ();
bool mov_rsp_rbp_pattern_p ();
bool sub_rsp_pattern_p (int& amount);
bool add_rsp_pattern_p (int& amount);
bool push_reg_p (int& regno);
bool pop_reg_p (int& regno);
bool push_imm_pattern_p ();
bool mov_reg_to_local_stack_frame_p (int& regno, int& fp_offset);
bool ret_pattern_p ();
bool pop_rbp_pattern_p ();
bool call_next_insn_pattern_p();
uint32_t extract_4 (uint8_t *b);
bool machine_regno_to_lldb_regno (int machine_regno, uint32_t& lldb_regno);
bool instruction_length (Address addr, int &length);
@ -149,13 +156,13 @@ private:
Address m_cur_insn;
uint8_t m_cur_insn_bytes[kMaxInstructionByteSize];
int m_machine_ip_regnum;
int m_machine_sp_regnum;
int m_machine_fp_regnum;
uint32_t m_machine_ip_regnum;
uint32_t m_machine_sp_regnum;
uint32_t m_machine_fp_regnum;
int m_lldb_ip_regnum;
int m_lldb_sp_regnum;
int m_lldb_fp_regnum;
uint32_t m_lldb_ip_regnum;
uint32_t m_lldb_sp_regnum;
uint32_t m_lldb_fp_regnum;
int m_wordsize;
int m_cpu;
@ -318,6 +325,15 @@ bool AssemblyParse_x86::push_0_pattern_p ()
return false;
}
// pushq $0
// pushl $0
bool AssemblyParse_x86::push_imm_pattern_p () {
uint8_t *p = m_cur_insn_bytes;
if (*p == 0x68 || *p == 0x6a)
return true;
return false;
}
// movq %rsp, %rbp [0x48 0x8b 0xec] or [0x48 0x89 0xe5]
// movl %esp, %ebp [0x8b 0xec] or [0x89 0xe5]
bool AssemblyParse_x86::mov_rsp_rbp_pattern_p () {
@ -346,13 +362,29 @@ bool AssemblyParse_x86::sub_rsp_pattern_p (int& amount) {
amount = (int32_t) extract_4 (p + 2);
return true;
}
// Not handled: [0x83 0xc4] for imm8 with neg values
// [0x81 0xc4] for imm32 with neg values
return false;
}
// addq $0x20, %rsp
bool AssemblyParse_x86::add_rsp_pattern_p (int& amount) {
uint8_t *p = m_cur_insn_bytes;
if (m_wordsize == 8 && *p == 0x48)
p++;
// 8-bit immediate operand
if (*p == 0x83 && *(p + 1) == 0xc4) {
amount = (int8_t) *(p + 2);
return true;
}
// 32-bit immediate operand
if (*p == 0x81 && *(p + 1) == 0xc4) {
amount = (int32_t) extract_4 (p + 2);
return true;
}
return false;
}
// pushq %rbx
// pushl $ebx
// pushl %ebx
bool AssemblyParse_x86::push_reg_p (int& regno) {
uint8_t *p = m_cur_insn_bytes;
int regno_prefix_bit = 0;
@ -368,6 +400,37 @@ bool AssemblyParse_x86::push_reg_p (int& regno) {
return false;
}
// popq %rbx
// popl %ebx
bool AssemblyParse_x86::pop_reg_p (int& regno) {
uint8_t *p = m_cur_insn_bytes;
int regno_prefix_bit = 0;
// If we have a rex prefix byte, check to see if a B bit is set
if (m_wordsize == 8 && *p == 0x41) {
regno_prefix_bit = 1 << 3;
p++;
}
if (*p >= 0x58 && *p <= 0x5f) {
regno = (*p - 0x58) | regno_prefix_bit;
return true;
}
return false;
}
// popq %rbp [0x5d]
// popl %ebp [0x5d]
bool AssemblyParse_x86::pop_rbp_pattern_p () {
uint8_t *p = m_cur_insn_bytes;
return (*p == 0x5d);
}
// call $0 [0xe8 0x0 0x0 0x0 0x0]
bool AssemblyParse_x86::call_next_insn_pattern_p () {
uint8_t *p = m_cur_insn_bytes;
return (*p == 0xe8) && (*(p+1) == 0x0) && (*(p+2) == 0x0)
&& (*(p+3) == 0x0) && (*(p+4) == 0x0);
}
// Look for an instruction sequence storing a nonvolatile register
// on to the stack frame.
@ -608,7 +671,7 @@ AssemblyParse_x86::get_non_call_site_unwind_plan (UnwindPlan &unwind_plan)
bool need_to_push_row = false;
// the PUSH instruction has moved the stack pointer - if the CFA is set in terms of the stack pointer,
// we need to add a new row of instructions.
if (row->GetCFARegister() == static_cast<uint32_t>(m_lldb_sp_regnum))
if (row->GetCFARegister() == m_lldb_sp_regnum)
{
need_to_push_row = true;
row->SetCFAOffset (current_sp_bytes_offset_from_cfa);
@ -659,7 +722,7 @@ AssemblyParse_x86::get_non_call_site_unwind_plan (UnwindPlan &unwind_plan)
if (sub_rsp_pattern_p (stack_offset))
{
current_sp_bytes_offset_from_cfa += stack_offset;
if (row->GetCFARegister() == static_cast<uint32_t>(m_lldb_sp_regnum))
if (row->GetCFARegister() == m_lldb_sp_regnum)
{
row->SetOffset (current_func_text_offset + insn_len);
row->SetCFAOffset (current_sp_bytes_offset_from_cfa);
@ -787,6 +850,183 @@ loopnext:
return true;
}
bool
AssemblyParse_x86::augment_unwind_plan_from_call_site (AddressRange& func, UnwindPlan &unwind_plan)
{
// Is func address valid?
Address addr_start = func.GetBaseAddress();
if (!addr_start.IsValid())
return false;
// Is original unwind_plan valid?
// unwind_plan should have at least one row which is ABI-default (CFA register is sp),
// and another row in mid-function.
if (unwind_plan.GetRowCount() < 2)
return false;
UnwindPlan::RowSP first_row = unwind_plan.GetRowAtIndex (0);
if (first_row->GetOffset() != 0)
return false;
uint32_t cfa_reg = m_exe_ctx.GetThreadPtr()->GetRegisterContext()
->ConvertRegisterKindToRegisterNumber (unwind_plan.GetRegisterKind(),
first_row->GetCFARegister());
if (cfa_reg != m_lldb_sp_regnum || first_row->GetCFAOffset() != m_wordsize)
return false;
Target *target = m_exe_ctx.GetTargetPtr();
m_cur_insn = func.GetBaseAddress();
uint64_t offset = 0;
int row_id = 1;
UnwindPlan::RowSP row(new UnwindPlan::Row(*first_row));
while (func.ContainsFileAddress (m_cur_insn))
{
int insn_len;
if (!instruction_length (m_cur_insn, insn_len)
|| insn_len == 0 || insn_len > kMaxInstructionByteSize)
{
// An unrecognized/junk instruction.
break;
}
const bool prefer_file_cache = true;
Error error;
if (target->ReadMemory (m_cur_insn, prefer_file_cache, m_cur_insn_bytes,
insn_len, error) == static_cast<size_t>(-1))
{
// Error reading the instruction out of the file, stop scanning.
break;
}
// Advance offsets.
offset += insn_len;
m_cur_insn.SetOffset(m_cur_insn.GetOffset() + insn_len);
// If we already have one row for this instruction, we can continue.
while (row_id < unwind_plan.GetRowCount()
&& unwind_plan.GetRowAtIndex (row_id)->GetOffset() <= offset)
row_id++;
UnwindPlan::RowSP original_row = unwind_plan.GetRowAtIndex (row_id - 1);
if (original_row->GetOffset() == offset)
{
*row = *original_row;
continue;
}
if (row_id == 0) {
// If we are here, compiler didn't generate CFI for prologue.
// This won't happen to GCC or clang.
// In this case, bail out directly.
return false;
}
// Inspect the instruction to check if we need a new row for it.
cfa_reg = m_exe_ctx.GetThreadPtr()->GetRegisterContext()
->ConvertRegisterKindToRegisterNumber (unwind_plan.GetRegisterKind(),
row->GetCFARegister());
if (cfa_reg == m_lldb_sp_regnum)
{
// CFA register is sp.
// call next instruction
// call 0
// => pop %ebx
if (call_next_insn_pattern_p ())
{
row->SetOffset (offset);
row->SetCFAOffset (m_wordsize + row->GetCFAOffset());
UnwindPlan::RowSP new_row(new UnwindPlan::Row(*row));
unwind_plan.InsertRow (new_row);
continue;
}
// push/pop register
int regno;
if (push_reg_p (regno)) {
row->SetOffset (offset);
row->SetCFAOffset (m_wordsize + row->GetCFAOffset());
UnwindPlan::RowSP new_row(new UnwindPlan::Row(*row));
unwind_plan.InsertRow (new_row);
continue;
}
if (pop_reg_p (regno)) {
// Technically, this might be a nonvolatile register recover in epilogue.
// We should reset RegisterInfo for the register.
// But in practice, previous rule for the register is still valid...
// So we ignore this case.
row->SetOffset (offset);
row->SetCFAOffset (-m_wordsize + row->GetCFAOffset());
UnwindPlan::RowSP new_row(new UnwindPlan::Row(*row));
unwind_plan.InsertRow (new_row);
continue;
}
// push imm
if (push_imm_pattern_p ()) {
row->SetOffset (offset);
row->SetCFAOffset (m_wordsize + row->GetCFAOffset());
UnwindPlan::RowSP new_row(new UnwindPlan::Row(*row));
unwind_plan.InsertRow (new_row);
continue;
}
// add/sub %rsp/%esp
int amount;
if (add_rsp_pattern_p (amount)) {
row->SetOffset (offset);
row->SetCFAOffset (-amount + row->GetCFAOffset());
UnwindPlan::RowSP new_row(new UnwindPlan::Row(*row));
unwind_plan.InsertRow (new_row);
continue;
}
if (sub_rsp_pattern_p (amount)) {
row->SetOffset (offset);
row->SetCFAOffset (amount + row->GetCFAOffset());
UnwindPlan::RowSP new_row(new UnwindPlan::Row(*row));
unwind_plan.InsertRow (new_row);
continue;
}
}
else if (cfa_reg == m_lldb_fp_regnum)
{
// CFA register is fp.
// The only case we care about is epilogue:
// [0x5d] pop %rbp/%ebp
// => [0xc3] ret
if (pop_rbp_pattern_p ())
{
if (target->ReadMemory (m_cur_insn, prefer_file_cache, m_cur_insn_bytes,
1, error) != static_cast<size_t>(-1)
&& ret_pattern_p ())
{
row->SetOffset (offset);
row->SetCFARegister (first_row->GetCFARegister());
row->SetCFAOffset (m_wordsize);
UnwindPlan::RowSP new_row(new UnwindPlan::Row(*row));
unwind_plan.InsertRow (new_row);
continue;
}
}
}
else
{
// CFA register is not sp or fp.
// This must be hand-written assembly.
// Just trust eh_frame and assume we have finished.
break;
}
}
unwind_plan.SetPlanValidAddressRange (func);
return true;
}
/* The "fast unwind plan" is valid for functions that follow the usual convention of
using the frame pointer register (ebp, rbp), i.e. the function prologue looks like
push %rbp [0x55]
@ -945,6 +1185,14 @@ UnwindAssembly_x86::GetNonCallSiteUnwindPlanFromAssembly (AddressRange& func, Th
return asm_parse.get_non_call_site_unwind_plan (unwind_plan);
}
bool
UnwindAssembly_x86::AugmentUnwindPlanFromCallSite (AddressRange& func, Thread& thread, UnwindPlan& unwind_plan)
{
ExecutionContext exe_ctx (thread.shared_from_this());
AssemblyParse_x86 asm_parse(exe_ctx, m_cpu, m_arch, func);
return asm_parse.augment_unwind_plan_from_call_site (func, unwind_plan);
}
bool
UnwindAssembly_x86::GetFastUnwindPlan (AddressRange& func, Thread& thread, UnwindPlan &unwind_plan)
{

View File

@ -26,6 +26,11 @@ public:
lldb_private::Thread& thread,
lldb_private::UnwindPlan& unwind_plan);
virtual bool
AugmentUnwindPlanFromCallSite (lldb_private::AddressRange& func,
lldb_private::Thread& thread,
lldb_private::UnwindPlan& unwind_plan);
virtual bool
GetFastUnwindPlan (lldb_private::AddressRange& func,
lldb_private::Thread& thread,

View File

@ -32,7 +32,7 @@ FuncUnwinders::FuncUnwinders
) :
m_unwind_table(unwind_table),
m_range(range),
m_mutex (Mutex::eMutexTypeNormal),
m_mutex (Mutex::eMutexTypeRecursive),
m_unwind_plan_call_site_sp (),
m_unwind_plan_non_call_site_sp (),
m_unwind_plan_fast_sp (),
@ -94,7 +94,7 @@ FuncUnwinders::GetUnwindPlanAtCallSite (int current_offset)
}
UnwindPlanSP
FuncUnwinders::GetUnwindPlanAtNonCallSite (Thread& thread)
FuncUnwinders::GetUnwindPlanAtNonCallSite (Target& target, Thread& thread, int current_offset)
{
// Lock the mutex to ensure we can always give out the most appropriate
// information. We want to make sure if someone requests an unwind
@ -114,6 +114,17 @@ FuncUnwinders::GetUnwindPlanAtNonCallSite (Thread& thread)
UnwindAssemblySP assembly_profiler_sp (GetUnwindAssemblyProfiler());
if (assembly_profiler_sp)
{
if (target.GetArchitecture().GetCore() == ArchSpec::eCore_x86_32_i386
|| target.GetArchitecture().GetCore() == ArchSpec::eCore_x86_64_x86_64)
{
// For 0th frame on i386 & x86_64, we fetch eh_frame and try using assembly profiler
// to augment it into asynchronous unwind table.
GetUnwindPlanAtCallSite(current_offset);
if (m_unwind_plan_call_site_sp
&& assembly_profiler_sp->AugmentUnwindPlanFromCallSite(m_range, thread, *m_unwind_plan_call_site_sp))
return m_unwind_plan_call_site_sp;
}
m_unwind_plan_non_call_site_sp.reset (new UnwindPlan (lldb::eRegisterKindGeneric));
if (!assembly_profiler_sp->GetNonCallSiteUnwindPlanFromAssembly (m_range, thread, *m_unwind_plan_non_call_site_sp))
m_unwind_plan_non_call_site_sp.reset();

View File

@ -313,6 +313,19 @@ UnwindPlan::AppendRow (const UnwindPlan::RowSP &row_sp)
m_row_list.back() = row_sp;
}
void
UnwindPlan::InsertRow (const UnwindPlan::RowSP &row_sp)
{
collection::iterator it = m_row_list.begin();
while (it != m_row_list.end()) {
RowSP row = *it;
if (row->GetOffset() > row_sp->GetOffset())
break;
it++;
}
m_row_list.insert(it, row_sp);
}
UnwindPlan::RowSP
UnwindPlan::GetRowForFunctionOffset (int offset) const
{