diff options
Diffstat (limited to 'source/Plugins/UnwindAssembly/x86/x86AssemblyInspectionEngine.cpp')
-rw-r--r-- | source/Plugins/UnwindAssembly/x86/x86AssemblyInspectionEngine.cpp | 287 |
1 files changed, 257 insertions, 30 deletions
diff --git a/source/Plugins/UnwindAssembly/x86/x86AssemblyInspectionEngine.cpp b/source/Plugins/UnwindAssembly/x86/x86AssemblyInspectionEngine.cpp index f8e70204e5098..43041ca1bb2fb 100644 --- a/source/Plugins/UnwindAssembly/x86/x86AssemblyInspectionEngine.cpp +++ b/source/Plugins/UnwindAssembly/x86/x86AssemblyInspectionEngine.cpp @@ -1,14 +1,15 @@ //===-- x86AssemblyInspectionEngine.cpp -------------------------*- C++ -*-===// // -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "x86AssemblyInspectionEngine.h" +#include <memory> + #include "llvm-c/Disassembler.h" #include "lldb/Core/Address.h" @@ -365,8 +366,8 @@ bool x86AssemblyInspectionEngine::push_reg_p(int ®no) { uint8_t *p = m_cur_insn; 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; + if (m_wordsize == 8 && (*p & 0xfe) == 0x40) { + regno_prefix_bit = (*p & 1) << 3; p++; } if (*p >= 0x50 && *p <= 0x57) { @@ -563,8 +564,8 @@ bool x86AssemblyInspectionEngine::pop_reg_p(int ®no) { uint8_t *p = m_cur_insn; 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; + if (m_wordsize == 8 && (*p & 0xfe) == 0x40) { + regno_prefix_bit = (*p & 1) << 3; p++; } if (*p >= 0x58 && *p <= 0x5f) { @@ -666,10 +667,207 @@ bool x86AssemblyInspectionEngine::mov_reg_to_local_stack_frame_p( return false; } -// ret [0xc9] or [0xc2 imm8] or [0xca imm8] +// 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]; + + 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) { + uint8_t b2 = m_cur_insn[1]; + 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 && addr_t(-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 == 0xc9 || *p == 0xc2 || *p == 0xca || *p == 0xc3; + 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) { @@ -679,6 +877,14 @@ uint32_t x86AssemblyInspectionEngine::extract_4(uint8_t *b) { 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) { @@ -704,7 +910,6 @@ bool x86AssemblyInspectionEngine::machine_regno_to_lldb_regno( return true; } return false; - return false; } bool x86AssemblyInspectionEngine::GetNonCallSiteUnwindPlanFromAssembly( @@ -1028,25 +1233,47 @@ bool x86AssemblyInspectionEngine::GetNonCallSiteUnwindPlanFromAssembly( } } - else if (ret_pattern_p() && prologue_completed_row.get()) { - // Reinstate the saved prologue setup for any instructions that come - // after the ret instruction - - 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]; + 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]; + } + + in_epilogue = true; + row_updated = true; + } } - - in_epilogue = true; - row_updated = true; } // call next instruction @@ -1173,7 +1400,7 @@ bool x86AssemblyInspectionEngine::AugmentUnwindPlanFromCallSite( *new_row = *original_last_row; new_row->SetOffset(offset); unwind_plan.AppendRow(new_row); - row.reset(new UnwindPlan::Row()); + row = std::make_shared<UnwindPlan::Row>(); *row = *new_row; reinstate_unwind_state = false; unwind_plan_updated = true; |