Index: lldb/trunk/source/Plugins/UnwindAssembly/x86/x86AssemblyInspectionEngine.h =================================================================== --- lldb/trunk/source/Plugins/UnwindAssembly/x86/x86AssemblyInspectionEngine.h +++ lldb/trunk/source/Plugins/UnwindAssembly/x86/x86AssemblyInspectionEngine.h @@ -114,7 +114,19 @@ bool call_next_insn_pattern_p(); bool mov_reg_to_local_stack_frame_p(int ®no, int &rbp_offset); bool ret_pattern_p(); + bool jmp_to_reg_p(); + bool pc_rel_branch_or_jump_p (const int instruction_length, int &offset); + bool non_local_branch_p (const lldb::addr_t current_func_text_offset, + const lldb_private::AddressRange &func_range, + const int instruction_length); + bool local_branch_p (const lldb::addr_t current_func_text_offset, + const lldb_private::AddressRange &func_range, + const int instruction_length, + lldb::addr_t &target_insn_offset); + uint16_t extract_2(uint8_t *b); + int16_t extract_2_signed(uint8_t *b); uint32_t extract_4(uint8_t *b); + int32_t extract_4_signed(uint8_t *b); bool instruction_length(uint8_t *insn, int &length, uint32_t buffer_remaining_bytes); Index: lldb/trunk/source/Plugins/UnwindAssembly/x86/x86AssemblyInspectionEngine.cpp =================================================================== --- lldb/trunk/source/Plugins/UnwindAssembly/x86/x86AssemblyInspectionEngine.cpp +++ lldb/trunk/source/Plugins/UnwindAssembly/x86/x86AssemblyInspectionEngine.cpp @@ -667,12 +667,209 @@ return false; } +// Returns true if this is a jmp instruction where we can't +// know the destination address statically. +// +// ff e0 jmpq *%rax +// ff e1 jmpq *%rcx +// ff 60 28 jmpq *0x28(%rax) +// ff 60 60 jmpq *0x60(%rax) +bool x86AssemblyInspectionEngine::jmp_to_reg_p() { + if (*m_cur_insn != 0xff) + return false; + + // The second byte is a ModR/M /4 byte, strip off the registers + uint8_t second_byte_sans_reg = *(m_cur_insn + 1) & ~7; + + // Don't handle 0x24 disp32, because the target address is + // knowable statically - pc_rel_branch_or_jump_p() will + // return the target address. + + // [reg] + if (second_byte_sans_reg == 0x20) + return true; + + // [reg]+disp8 + if (second_byte_sans_reg == 0x60) + return true; + + // [reg]+disp32 + if (second_byte_sans_reg == 0xa0) + return true; + + // reg + if (second_byte_sans_reg == 0xe0) + return true; + + // disp32 + // jumps to an address stored in memory, the value can't be cached + // in an unwind plan. + if (second_byte_sans_reg == 0x24) + return true; + + // use SIB byte + // ff 24 fe jmpq *(%rsi,%rdi,8) + if (second_byte_sans_reg == 0x24) + return true; + + return false; +} + +// Detect branches to fixed pc-relative offsets. +// Returns the offset from the address of the next instruction +// that may be branch/jumped to. +// +// Cannot determine the offset of a JMP that jumps to the address in +// a register ("jmpq *%rax") or offset from a register value +// ("jmpq *0x28(%rax)"), this method will return false on those +// instructions. +// +// These instructions all end in either a relative 8/16/32 bit value +// depending on the instruction and the current execution mode of the +// inferior process. Once we know the size of the opcode instruction, +// we can use the total instruction length to determine the size of +// the relative offset without having to compute it correctly. + +bool x86AssemblyInspectionEngine::pc_rel_branch_or_jump_p ( + const int instruction_length, int &offset) +{ + int opcode_size = 0; + + uint8_t b1 = m_cur_insn[0]; + uint8_t b2 = m_cur_insn[1]; + + switch (b1) { + case 0x77: // JA/JNBE rel8 + case 0x73: // JAE/JNB/JNC rel8 + case 0x72: // JB/JC/JNAE rel8 + case 0x76: // JBE/JNA rel8 + case 0xe3: // JCXZ/JECXZ/JRCXZ rel8 + case 0x74: // JE/JZ rel8 + case 0x7f: // JG/JNLE rel8 + case 0x7d: // JGE/JNL rel8 + case 0x7c: // JL/JNGE rel8 + case 0x7e: // JNG/JLE rel8 + case 0x71: // JNO rel8 + case 0x7b: // JNP/JPO rel8 + case 0x79: // JNS rel8 + case 0x75: // JNE/JNZ rel8 + case 0x70: // JO rel8 + case 0x7a: // JP/JPE rel8 + case 0x78: // JS rel8 + case 0xeb: // JMP rel8 + case 0xe9: // JMP rel16/rel32 + opcode_size = 1; + break; + default: + break; + } + if (b1 == 0x0f && opcode_size == 0) { + switch (b2) { + case 0x87: // JA/JNBE rel16/rel32 + case 0x86: // JBE/JNA rel16/rel32 + case 0x84: // JE/JZ rel16/rel32 + case 0x8f: // JG/JNLE rel16/rel32 + case 0x8d: // JNL/JGE rel16/rel32 + case 0x8e: // JLE rel16/rel32 + case 0x82: // JB/JC/JNAE rel16/rel32 + case 0x83: // JAE/JNB/JNC rel16/rel32 + case 0x85: // JNE/JNZ rel16/rel32 + case 0x8c: // JL/JNGE rel16/rel32 + case 0x81: // JNO rel16/rel32 + case 0x8b: // JNP/JPO rel16/rel32 + case 0x89: // JNS rel16/rel32 + case 0x80: // JO rel16/rel32 + case 0x8a: // JP rel16/rel32 + case 0x88: // JS rel16/rel32 + opcode_size = 2; + break; + default: + break; + } + } + + if (opcode_size == 0) + return false; + + offset = 0; + if (instruction_length - opcode_size == 1) { + int8_t rel8 = (int8_t) *(m_cur_insn + opcode_size); + offset = rel8; + } else if (instruction_length - opcode_size == 2) { + int16_t rel16 = extract_2_signed (m_cur_insn + opcode_size); + offset = rel16; + } else if (instruction_length - opcode_size == 4) { + int32_t rel32 = extract_4_signed (m_cur_insn + opcode_size); + offset = rel32; + } else { + return false; + } + return true; +} + +// Returns true if this instruction is a intra-function branch or jump - +// a branch/jump within the bounds of this same function. +// Cannot predict where a jump through a register value ("jmpq *%rax") +// will go, so it will return false on that instruction. +bool x86AssemblyInspectionEngine::local_branch_p ( + const addr_t current_func_text_offset, + const AddressRange &func_range, + const int instruction_length, + addr_t &target_insn_offset) { + int offset; + if (pc_rel_branch_or_jump_p (instruction_length, offset) && offset != 0) { + addr_t next_pc_value = current_func_text_offset + instruction_length; + if (offset < 0 && -offset > current_func_text_offset) { + // Branch target is before the start of this function + return false; + } + if (offset + next_pc_value > func_range.GetByteSize()) { + // Branch targets outside this function's bounds + return false; + } + // This instruction branches to target_insn_offset (byte offset into the function) + target_insn_offset = next_pc_value + offset; + return true; + } + return false; +} + +// Returns true if this instruction is a inter-function branch or jump - a +// branch/jump to another function. +// Cannot predict where a jump through a register value ("jmpq *%rax") +// will go, so it will return false on that instruction. +bool x86AssemblyInspectionEngine::non_local_branch_p ( + const addr_t current_func_text_offset, + const AddressRange &func_range, + const int instruction_length) { + int offset; + addr_t target_insn_offset; + if (pc_rel_branch_or_jump_p (instruction_length, offset)) { + return !local_branch_p(current_func_text_offset,func_range,instruction_length,target_insn_offset); + } + return false; +} + // ret [0xc3] or [0xcb] or [0xc2 imm16] or [0xca imm16] bool x86AssemblyInspectionEngine::ret_pattern_p() { uint8_t *p = m_cur_insn; return *p == 0xc3 || *p == 0xc2 || *p == 0xca || *p == 0xcb; } +uint16_t x86AssemblyInspectionEngine::extract_2(uint8_t *b) { + uint16_t v = 0; + for (int i = 1; i >= 0; i--) + v = (v << 8) | b[i]; + return v; +} + +int16_t x86AssemblyInspectionEngine::extract_2_signed(uint8_t *b) { + int16_t v = 0; + for (int i = 1; i >= 0; i--) + v = (v << 8) | b[i]; + return v; +} + uint32_t x86AssemblyInspectionEngine::extract_4(uint8_t *b) { uint32_t v = 0; for (int i = 3; i >= 0; i--) @@ -680,6 +877,14 @@ return v; } +int32_t x86AssemblyInspectionEngine::extract_4_signed(uint8_t *b) { + int32_t v = 0; + for (int i = 3; i >= 0; i--) + v = (v << 8) | b[i]; + return v; +} + + bool x86AssemblyInspectionEngine::instruction_length(uint8_t *insn_p, int &length, uint32_t buffer_remaining_bytes) { @@ -705,7 +910,6 @@ return true; } return false; - return false; } bool x86AssemblyInspectionEngine::GetNonCallSiteUnwindPlanFromAssembly( @@ -1029,25 +1233,47 @@ } } - else if (ret_pattern_p() && prologue_completed_row.get()) { - // Reinstate the saved prologue setup for any instructions that come - // after the ret instruction + else if (prologue_completed_row.get() && + (ret_pattern_p() || + non_local_branch_p (current_func_text_offset, func_range, insn_len) || + jmp_to_reg_p())) { + // Check if the current instruction is the end of an epilogue sequence, + // and if so, re-instate the prologue-completed unwind state. + + // The current instruction is a branch/jump outside this function, + // a ret, or a jump through a register value which we cannot + // determine the effcts of. Verify that the stack frame state + // has been unwound to the same as it was at function entry to avoid + // mis-identifying a JMP instruction as an epilogue. + UnwindPlan::Row::RegisterLocation sp, pc; + if (row->GetRegisterInfo(m_lldb_sp_regnum, sp) && + row->GetRegisterInfo(m_lldb_ip_regnum, pc)) { + // Any ret instruction variant is definitely indicative of an + // epilogue; for other insn patterns verify that we're back to + // the original unwind state. + if (ret_pattern_p() || + (sp.IsCFAPlusOffset() && sp.GetOffset() == 0 && + pc.IsAtCFAPlusOffset() && pc.GetOffset() == -m_wordsize)) { + // Reinstate the saved prologue setup for any instructions that come + // after the epilogue + + UnwindPlan::Row *newrow = new UnwindPlan::Row; + *newrow = *prologue_completed_row.get(); + row.reset(newrow); + current_sp_bytes_offset_from_fa = + prologue_completed_sp_bytes_offset_from_cfa; + is_aligned = prologue_completed_is_aligned; + + saved_registers.clear(); + saved_registers.resize(prologue_completed_saved_registers.size(), false); + for (size_t i = 0; i < prologue_completed_saved_registers.size(); ++i) { + saved_registers[i] = prologue_completed_saved_registers[i]; + } - UnwindPlan::Row *newrow = new UnwindPlan::Row; - *newrow = *prologue_completed_row.get(); - row.reset(newrow); - current_sp_bytes_offset_from_fa = - prologue_completed_sp_bytes_offset_from_cfa; - is_aligned = prologue_completed_is_aligned; - - saved_registers.clear(); - saved_registers.resize(prologue_completed_saved_registers.size(), false); - for (size_t i = 0; i < prologue_completed_saved_registers.size(); ++i) { - saved_registers[i] = prologue_completed_saved_registers[i]; + in_epilogue = true; + row_updated = true; + } } - - in_epilogue = true; - row_updated = true; } // call next instruction Index: lldb/trunk/unittests/UnwindAssembly/x86/Testx86AssemblyInspectionEngine.cpp =================================================================== --- lldb/trunk/unittests/UnwindAssembly/x86/Testx86AssemblyInspectionEngine.cpp +++ lldb/trunk/unittests/UnwindAssembly/x86/Testx86AssemblyInspectionEngine.cpp @@ -2723,3 +2723,125 @@ EXPECT_TRUE(regloc.IsAtCFAPlusOffset()); EXPECT_EQ(-8, regloc.GetOffset()); } + + +// Test mid-function epilogues - the unwind state post-prologue +// should be re-instated. + +TEST_F(Testx86AssemblyInspectionEngine, TestDisassemblyMidFunctionEpilogues) { + AddressRange sample_range; + UnwindPlan unwind_plan(eRegisterKindLLDB); + std::unique_ptr engine32 = Geti386Inspector(); + std::unique_ptr engine64 = Getx86_64Inspector(); + + uint8_t data[] = { + 0x55, // <+0>: pushq %rbp + 0x48, 0x89, 0xe5, // <+1>: movq %rsp, %rbp + 0x48, 0x83, 0xec, 0x70, // <+4>: subq $0x70, %rsp + 0x90, // <+8>: nop // prologue set up + + 0x74, 0x7, // <+9>: je 7 <+18> + 0x48, 0x83, 0xc4, 0x70, // <+11>: addq $0x70, %rsp + 0x5d, // <+15>: popq %rbp + 0xff, 0xe0, // <+16>: jmpq *%rax // epilogue completed + + 0x90, // <+18>: nop // prologue setup back + + 0x74, 0x7, // <+19>: je 6 <+27> + 0x48, 0x83, 0xc4, 0x70, // <+21>: addq $0x70, %rsp + 0x5d, // <+25>: popq %rbp + 0xc3, // <+26>: retq // epilogue completed + + 0x90, // <+27>: nop // prologue setup back + + 0x48, 0x83, 0xc4, 0x70, // <+28>: addq $0x70, %rsp + 0x5d, // <+32>: popq %rbp + 0xc3, // <+33>: retq // epilogue completed + + }; + + sample_range = AddressRange(0x1000, sizeof(data)); + + int wordsize = 4; + EXPECT_TRUE(engine32->GetNonCallSiteUnwindPlanFromAssembly( + data, sizeof(data), sample_range, unwind_plan)); + + // Check that we've unwound the stack after the first mid-function epilogue + // row: CFA=esp +4 => esp=CFA+0 eip=[CFA-4] + UnwindPlan::RowSP row_sp = unwind_plan.GetRowForFunctionOffset(16); + EXPECT_EQ(16ull, row_sp->GetOffset()); + EXPECT_TRUE(row_sp->GetCFAValue().GetRegisterNumber() == k_esp); + EXPECT_TRUE(row_sp->GetCFAValue().IsRegisterPlusOffset() == true); + EXPECT_EQ(wordsize, row_sp->GetCFAValue().GetOffset()); + + // Check that we've reinstated the stack frame setup + // unwind instructions after a jmpq *%eax + // row: CFA=ebp +8 => esp=CFA+0 eip=[CFA-8] + row_sp = unwind_plan.GetRowForFunctionOffset(18); + EXPECT_EQ(18ull, row_sp->GetOffset()); + EXPECT_TRUE(row_sp->GetCFAValue().GetRegisterNumber() == k_ebp); + EXPECT_TRUE(row_sp->GetCFAValue().IsRegisterPlusOffset() == true); + EXPECT_EQ(wordsize * 2, row_sp->GetCFAValue().GetOffset()); + + // Check that we've reinstated the stack frame setup + // unwind instructions after a mid-function retq + // row: CFA=ebp +8 => esp=CFA+0 eip=[CFA-8] + row_sp = unwind_plan.GetRowForFunctionOffset(27); + EXPECT_EQ(27ull, row_sp->GetOffset()); + EXPECT_TRUE(row_sp->GetCFAValue().GetRegisterNumber() == k_ebp); + EXPECT_TRUE(row_sp->GetCFAValue().IsRegisterPlusOffset() == true); + EXPECT_EQ(wordsize * 2, row_sp->GetCFAValue().GetOffset()); + + // After last instruction in the function, verify that + // the stack frame has been unwound + // row: CFA=esp +4 => esp=CFA+0 eip=[CFA-4] + row_sp = unwind_plan.GetRowForFunctionOffset(33); + EXPECT_EQ(33ull, row_sp->GetOffset()); + EXPECT_TRUE(row_sp->GetCFAValue().GetRegisterNumber() == k_esp); + EXPECT_TRUE(row_sp->GetCFAValue().IsRegisterPlusOffset() == true); + EXPECT_EQ(wordsize, row_sp->GetCFAValue().GetOffset()); + + + unwind_plan.Clear(); + + wordsize = 8; + EXPECT_TRUE(engine64->GetNonCallSiteUnwindPlanFromAssembly( + data, sizeof(data), sample_range, unwind_plan)); + + // Check that we've unwound the stack after the first mid-function epilogue + // row: CFA=rsp +8 => rsp=CFA+0 rip=[CFA-8] + row_sp = unwind_plan.GetRowForFunctionOffset(16); + EXPECT_EQ(16ull, row_sp->GetOffset()); + EXPECT_TRUE(row_sp->GetCFAValue().GetRegisterNumber() == k_rsp); + EXPECT_TRUE(row_sp->GetCFAValue().IsRegisterPlusOffset() == true); + EXPECT_EQ(wordsize, row_sp->GetCFAValue().GetOffset()); + + // Check that we've reinstated the stack frame setup + // unwind instructions after a jmpq *%eax + // row: CFA=rbp+16 => rsp=CFA+0 rip=[CFA-16] + row_sp = unwind_plan.GetRowForFunctionOffset(18); + EXPECT_EQ(18ull, row_sp->GetOffset()); + EXPECT_TRUE(row_sp->GetCFAValue().GetRegisterNumber() == k_rbp); + EXPECT_TRUE(row_sp->GetCFAValue().IsRegisterPlusOffset() == true); + EXPECT_EQ(wordsize * 2, row_sp->GetCFAValue().GetOffset()); + + // Check that we've reinstated the stack frame setup + // unwind instructions after a mid-function retq + // row: CFA=rbp+16 => rsp=CFA+0 rip=[CFA-16] + row_sp = unwind_plan.GetRowForFunctionOffset(27); + EXPECT_EQ(27ull, row_sp->GetOffset()); + EXPECT_TRUE(row_sp->GetCFAValue().GetRegisterNumber() == k_rbp); + EXPECT_TRUE(row_sp->GetCFAValue().IsRegisterPlusOffset() == true); + EXPECT_EQ(wordsize * 2, row_sp->GetCFAValue().GetOffset()); + + // After last instruction in the function, verify that + // the stack frame has been unwound + // row: CFA=rsp +8 => esp=CFA+0 rip=[CFA-8] + row_sp = unwind_plan.GetRowForFunctionOffset(33); + EXPECT_EQ(33ull, row_sp->GetOffset()); + EXPECT_TRUE(row_sp->GetCFAValue().GetRegisterNumber() == k_rsp); + EXPECT_TRUE(row_sp->GetCFAValue().IsRegisterPlusOffset() == true); + EXPECT_EQ(wordsize, row_sp->GetCFAValue().GetOffset()); + + +}