[lldb] Step over non-lldb breakpoints (#174348)
Several languages support some sort of "breakpoint" function, which adds
ISA-specific instructions to generate an interrupt at runtime. However,
on some platforms, these instructions don't increment the program
counter. When LLDB sets these instructions it isn't a problem, as we
remove them before continuing, then re-add them after stepping over the
location. However, for breakpoint sequences that are part of the
inferior process, this doesn't happen - and so users might be left
unable to continue past the breakpoint without manually interfering with
the program counter.
This patch adds logic to LLDB to intercept SIGTRAPs, inspect the bytes
of the inferior at the program counter, and if the instruction looks
like a BRK or BKPT or similar, increment the pc by the size of the
instruction we found. This unifies platform behaviour (e.g. on x86_64,
LLDB debug sessions already look like this) and improves UX (in my
opinion, but I think it beats messing with stuff every break).
Some ISAs (like AArch64) require slightly different handling, as while
there are multiple possible instructions, we should be careful only to
find the ones likely to have been emitted by a compiler backend, and not
those inserted from (for example) the UB sanitizer, or any others.
There is an existing builtin-debugtrap test which was under the macos
folder before. I've now moved that to "functionalities", made it pure C
only, and updated it a little bit so that it works regardless of
platform.
What I've not done is change the existing code in debugserver which was
added by Jason Molenda about five years ago
(https://reviews.llvm.org/D91238, 92b036d). It might not be required any
more after this change. Reading the history there, it seems like it was
agreed that this behaviour (skipping over unknown bps) was the desired
end goal.
Fixes #56268
---------
Co-authored-by: Jonas Devlieghere <jonas@devlieghere.com>
This commit is contained in:
1 parent
4f551b55ae
commit
304c680809
16 files changed
+271
-74
No files matched your search
@@ -138,6 +138,18 @@ public:
|
||||
std::shared_ptr<const UnwindPlan> current_unwindplan) {
|
||||
return lldb::UnwindPlanSP();
|
||||
}
|
||||
|
||||
/// Returns whether a given byte sequence is a valid trap instruction for the
|
||||
/// architecture. Some architectures feature instructions that have immediates
|
||||
/// that can take on any value, resulting in a family of valid byte sequences.
|
||||
/// If the observed byte sequence is shorter than the reference then they are
|
||||
/// considered not to match, even if the initial bytes would match.
|
||||
virtual bool IsValidTrapInstruction(llvm::ArrayRef<uint8_t> reference,
|
||||
llvm::ArrayRef<uint8_t> observed) const {
|
||||
if (reference.size() > observed.size())
|
||||
return false;
|
||||
return !std::memcmp(reference.data(), observed.data(), reference.size());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace lldb_private
|
||||
|
||||
@@ -330,6 +330,35 @@ public:
|
||||
virtual std::vector<ArchSpec>
|
||||
GetSupportedArchitectures(const ArchSpec &process_host_arch) = 0;
|
||||
|
||||
/// Get the bytes of the platform's software interrupt instruction. If there
|
||||
/// are multiple possible encodings, for example where there are immediate
|
||||
/// values encoded in the instruction, this will return the instruction with
|
||||
/// those bits set as 0.
|
||||
///
|
||||
/// \param[in] arch
|
||||
/// The architecture of the inferior.
|
||||
/// \param size_hint
|
||||
/// A hint to disambiguate which instruction is used on platforms where
|
||||
/// there are multiple interrupts with different sizes in the ISA (e.g
|
||||
/// ARM Thumb, RISC-V).
|
||||
///
|
||||
/// \return
|
||||
/// The bytes of the interrupt instruction, with any immediate value
|
||||
/// bits set to 0.
|
||||
llvm::ArrayRef<uint8_t> SoftwareTrapOpcodeBytes(const ArchSpec &arch,
|
||||
size_t size_hint = 0);
|
||||
|
||||
/// Get the suggested size hint for a trap instruction on the given target.
|
||||
/// Some platforms have a compressed instruction set which can be used
|
||||
/// instead of the "normal" encoding. This function attempts to determine
|
||||
/// a size hint for the size of the instruction at address \a addr, and
|
||||
/// return 0, 2 or 4, with 2 and 4 corresponding to the estimated size
|
||||
/// and zero meaning no applicable hint. Returns the estimated size in bytes
|
||||
/// of the instruction for this target at the given address, or 0 if no
|
||||
/// estimate is available.
|
||||
size_t GetTrapOpcodeSizeHint(Target &target, Address addr,
|
||||
llvm::ArrayRef<uint8_t> bytes);
|
||||
|
||||
virtual size_t GetSoftwareBreakpointTrapOpcode(Target &target,
|
||||
BreakpointSite *bp_site);
|
||||
|
||||
|
||||
@@ -218,6 +218,12 @@ protected:
|
||||
// to consult this later on.
|
||||
virtual bool ShouldStop(Event *event_ptr) { return true; }
|
||||
|
||||
// Shared implementation for when a trap instruction leaves the CPU PC at
|
||||
// the trap instruction, instead of just after it. Currently the subclasses
|
||||
// StopInfoMachException and StopInfoUnixSignal use this to skip over the
|
||||
// instruction at the PC if it is a matching trap instruction.
|
||||
void SkipOverTrapInstruction();
|
||||
|
||||
// Classes that inherit from StackID can see and modify these
|
||||
lldb::ThreadWP m_thread_wp; // The thread corresponding to the stop reason.
|
||||
uint32_t m_stop_id; // The process stop ID for which this stop info is valid
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include "lldb/Utility/DataBufferHeap.h"
|
||||
#include "lldb/Utility/DataExtractor.h"
|
||||
|
||||
#include "llvm/Support/Endian.h"
|
||||
|
||||
using namespace lldb_private;
|
||||
using namespace lldb;
|
||||
|
||||
@@ -141,3 +143,18 @@ bool ArchitectureAArch64::ReconfigureRegisterInfo(DynamicRegisterInfo ®_info,
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArchitectureAArch64::IsValidTrapInstruction(
|
||||
llvm::ArrayRef<uint8_t> reference, llvm::ArrayRef<uint8_t> observed) const {
|
||||
if (reference.size() < 4 || observed.size() < 4)
|
||||
return false;
|
||||
auto ref_bytes = llvm::support::endian::read32le(reference.data());
|
||||
auto bytes = llvm::support::endian::read32le(observed.data());
|
||||
// Only the 11 highest bits define the breakpoint instruction, the others
|
||||
// include an immediate value that we will explicitly check against.
|
||||
uint32_t mask = 0xFFE00000;
|
||||
// Check that the masked bytes match the reference, but also check that the
|
||||
// immediate in the instruction is the default output by llvm.debugtrap.
|
||||
// The reference has the immediate set as all-zero, so mask and check here.
|
||||
return (ref_bytes == (bytes & mask)) && ((bytes & ~mask) >> 5 == 0xF000);
|
||||
}
|
||||
@@ -39,6 +39,9 @@ public:
|
||||
DataExtractor ®_data,
|
||||
RegisterContext ®_context) const override;
|
||||
|
||||
bool IsValidTrapInstruction(llvm::ArrayRef<uint8_t> reference,
|
||||
llvm::ArrayRef<uint8_t> observed) const override;
|
||||
|
||||
private:
|
||||
static std::unique_ptr<Architecture> Create(const ArchSpec &arch);
|
||||
ArchitectureAArch64() = default;
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#include "lldb/Utility/Log.h"
|
||||
#include "lldb/Utility/RegisterValue.h"
|
||||
|
||||
#include "llvm/Support/Endian.h"
|
||||
|
||||
using namespace lldb_private;
|
||||
using namespace lldb;
|
||||
|
||||
@@ -336,3 +338,31 @@ UnwindPlanSP ArchitectureArm::GetArchitectureUnwindPlan(
|
||||
}
|
||||
return new_plan;
|
||||
}
|
||||
|
||||
bool ArchitectureArm::IsValidTrapInstruction(
|
||||
llvm::ArrayRef<uint8_t> reference, llvm::ArrayRef<uint8_t> observed) const {
|
||||
if (reference.size() > observed.size())
|
||||
return false;
|
||||
|
||||
if (reference.size() == 2) {
|
||||
auto ref_bytes = llvm::support::endian::read16le(reference.data());
|
||||
auto obs_bytes = llvm::support::endian::read16le(observed.data());
|
||||
if (ref_bytes == obs_bytes)
|
||||
return true;
|
||||
// LLDB uses an undef instruction encoding for breakpoints -
|
||||
// perhaps we have an actual BKPT in the inferior.
|
||||
uint16_t mask = 0xF0;
|
||||
if ((obs_bytes & mask) == 0xBE00)
|
||||
return true;
|
||||
} else if (reference.size() == 4) {
|
||||
auto ref_bytes = llvm::support::endian::read32le(reference.data());
|
||||
auto obs_bytes = llvm::support::endian::read32le(observed.data());
|
||||
if (ref_bytes == obs_bytes)
|
||||
return true;
|
||||
uint32_t mask = 0x0FF000F0;
|
||||
uint32_t bkpt_pattern = 0xE1200070;
|
||||
if ((obs_bytes & mask) == bkpt_pattern)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -34,6 +34,9 @@ public:
|
||||
lldb_private::Thread &thread, lldb_private::RegisterContextUnwind *regctx,
|
||||
std::shared_ptr<const UnwindPlan> current_unwindplan) override;
|
||||
|
||||
bool IsValidTrapInstruction(llvm::ArrayRef<uint8_t> reference,
|
||||
llvm::ArrayRef<uint8_t> observed) const override;
|
||||
|
||||
private:
|
||||
static std::unique_ptr<Architecture> Create(const ArchSpec &arch);
|
||||
ArchitectureArm() = default;
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
#include "lldb/Utility/LLDBLog.h"
|
||||
#include "lldb/Utility/Log.h"
|
||||
|
||||
#include "llvm/Support/Endian.h"
|
||||
|
||||
using namespace lldb_private;
|
||||
using namespace lldb;
|
||||
|
||||
@@ -229,3 +231,12 @@ Instruction *ArchitectureMips::GetInstructionAtAddress(
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ArchitectureMips::IsValidTrapInstruction(
|
||||
llvm::ArrayRef<uint8_t> reference, llvm::ArrayRef<uint8_t> observed) const {
|
||||
// The middle twenty bits of BREAK can be anything, so zero them
|
||||
uint32_t mask = 0xFC00003F;
|
||||
auto ref_bytes = llvm::support::endian::read32le(reference.data());
|
||||
auto bytes = llvm::support::endian::read32le(observed.data());
|
||||
return (ref_bytes & mask) == (bytes & mask);
|
||||
}
|
||||
@@ -33,6 +33,9 @@ public:
|
||||
lldb::addr_t GetOpcodeLoadAddress(lldb::addr_t load_addr,
|
||||
AddressClass addr_class) const override;
|
||||
|
||||
bool IsValidTrapInstruction(llvm::ArrayRef<uint8_t> reference,
|
||||
llvm::ArrayRef<uint8_t> observed) const override;
|
||||
|
||||
private:
|
||||
Instruction *GetInstructionAtAddress(Target &target,
|
||||
const Address &resolved_addr,
|
||||
|
||||
@@ -827,6 +827,13 @@ StopInfoSP StopInfoMachException::CreateStopReasonWithMachException(
|
||||
not_stepping_but_got_singlestep_exception);
|
||||
}
|
||||
|
||||
void StopInfoMachException::PerformAction([[maybe_unused]] Event *event_ptr) {
|
||||
if (!(m_value == 6 /*EXC_BREAKPOINT*/ &&
|
||||
m_exc_code == 1 /*EXC_ARM_BREAKPOINT*/))
|
||||
return;
|
||||
SkipOverTrapInstruction();
|
||||
}
|
||||
|
||||
// Detect an unusual situation on Darwin where:
|
||||
//
|
||||
// 0. We did an instruction-step before this.
|
||||
|
||||
@@ -86,6 +86,10 @@ public:
|
||||
};
|
||||
#endif
|
||||
|
||||
/// Allow this plugin to respond to stop events to enable skip-over-trap
|
||||
/// behaviour on AArch64.
|
||||
void PerformAction(Event *event_ptr) override;
|
||||
|
||||
// Since some mach exceptions will be reported as breakpoints, signals,
|
||||
// or trace, we use this static accessor which will translate the mach
|
||||
// exception into the correct StopInfo.
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "lldb/Utility/Log.h"
|
||||
#include "lldb/Utility/Status.h"
|
||||
#include "lldb/Utility/StructuredData.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
@@ -1946,121 +1947,99 @@ size_t Platform::ConnectToWaitingProcesses(lldb_private::Debugger &debugger,
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t Platform::GetSoftwareBreakpointTrapOpcode(Target &target,
|
||||
BreakpointSite *bp_site) {
|
||||
ArchSpec arch = target.GetArchitecture();
|
||||
assert(arch.IsValid());
|
||||
const uint8_t *trap_opcode = nullptr;
|
||||
size_t trap_opcode_size = 0;
|
||||
llvm::ArrayRef<uint8_t> Platform::SoftwareTrapOpcodeBytes(const ArchSpec &arch,
|
||||
size_t size_hint) {
|
||||
llvm::ArrayRef<uint8_t> trap_opcode;
|
||||
|
||||
switch (arch.GetMachine()) {
|
||||
case llvm::Triple::aarch64_32:
|
||||
case llvm::Triple::aarch64: {
|
||||
static const uint8_t g_aarch64_opcode[] = {0x00, 0x00, 0x20, 0xd4};
|
||||
trap_opcode = g_aarch64_opcode;
|
||||
trap_opcode_size = sizeof(g_aarch64_opcode);
|
||||
trap_opcode =
|
||||
llvm::ArrayRef<uint8_t>(g_aarch64_opcode, sizeof(g_aarch64_opcode));
|
||||
} break;
|
||||
|
||||
case llvm::Triple::arc: {
|
||||
static const uint8_t g_hex_opcode[] = { 0xff, 0x7f };
|
||||
trap_opcode = g_hex_opcode;
|
||||
trap_opcode_size = sizeof(g_hex_opcode);
|
||||
static const uint8_t g_hex_opcode[] = {0xff, 0x7f};
|
||||
trap_opcode = llvm::ArrayRef<uint8_t>(g_hex_opcode, sizeof(g_hex_opcode));
|
||||
} break;
|
||||
|
||||
// TODO: support big-endian arm and thumb trap codes.
|
||||
case llvm::Triple::arm: {
|
||||
// The ARM reference recommends the use of 0xe7fddefe and 0xdefe but the
|
||||
// linux kernel does otherwise.
|
||||
// ARM CPUs have dedicated BKPT instructions: 0xe7fddefe and 0xdefe.
|
||||
// However, the linux kernel recognizes two different sequences based on
|
||||
// undefined instruction encodings (linux/arch/arm/kernel/ptrace.c)
|
||||
static const uint8_t g_arm_breakpoint_opcode[] = {0xf0, 0x01, 0xf0, 0xe7};
|
||||
static const uint8_t g_thumb_breakpoint_opcode[] = {0x01, 0xde};
|
||||
|
||||
lldb::BreakpointLocationSP bp_loc_sp(bp_site->GetConstituentAtIndex(0));
|
||||
AddressClass addr_class = AddressClass::eUnknown;
|
||||
|
||||
if (bp_loc_sp) {
|
||||
addr_class = bp_loc_sp->GetAddress().GetAddressClass();
|
||||
if (addr_class == AddressClass::eUnknown &&
|
||||
(bp_loc_sp->GetAddress().GetFileAddress() & 1))
|
||||
addr_class = AddressClass::eCodeAlternateISA;
|
||||
}
|
||||
|
||||
if (addr_class == AddressClass::eCodeAlternateISA) {
|
||||
trap_opcode = g_thumb_breakpoint_opcode;
|
||||
trap_opcode_size = sizeof(g_thumb_breakpoint_opcode);
|
||||
if (size_hint == 2) {
|
||||
trap_opcode = llvm::ArrayRef<uint8_t>(g_thumb_breakpoint_opcode,
|
||||
sizeof(g_thumb_breakpoint_opcode));
|
||||
} else {
|
||||
trap_opcode = g_arm_breakpoint_opcode;
|
||||
trap_opcode_size = sizeof(g_arm_breakpoint_opcode);
|
||||
trap_opcode = llvm::ArrayRef<uint8_t>(g_arm_breakpoint_opcode,
|
||||
sizeof(g_arm_breakpoint_opcode));
|
||||
}
|
||||
} break;
|
||||
|
||||
case llvm::Triple::avr: {
|
||||
static const uint8_t g_hex_opcode[] = {0x98, 0x95};
|
||||
trap_opcode = g_hex_opcode;
|
||||
trap_opcode_size = sizeof(g_hex_opcode);
|
||||
trap_opcode = llvm::ArrayRef<uint8_t>(g_hex_opcode, sizeof(g_hex_opcode));
|
||||
} break;
|
||||
|
||||
case llvm::Triple::mips:
|
||||
case llvm::Triple::mips64: {
|
||||
static const uint8_t g_hex_opcode[] = {0x00, 0x00, 0x00, 0x0d};
|
||||
trap_opcode = g_hex_opcode;
|
||||
trap_opcode_size = sizeof(g_hex_opcode);
|
||||
trap_opcode = llvm::ArrayRef<uint8_t>(g_hex_opcode, sizeof(g_hex_opcode));
|
||||
} break;
|
||||
|
||||
case llvm::Triple::mipsel:
|
||||
case llvm::Triple::mips64el: {
|
||||
static const uint8_t g_hex_opcode[] = {0x0d, 0x00, 0x00, 0x00};
|
||||
trap_opcode = g_hex_opcode;
|
||||
trap_opcode_size = sizeof(g_hex_opcode);
|
||||
trap_opcode = llvm::ArrayRef<uint8_t>(g_hex_opcode, sizeof(g_hex_opcode));
|
||||
} break;
|
||||
|
||||
case llvm::Triple::msp430: {
|
||||
static const uint8_t g_msp430_opcode[] = {0x43, 0x43};
|
||||
trap_opcode = g_msp430_opcode;
|
||||
trap_opcode_size = sizeof(g_msp430_opcode);
|
||||
trap_opcode =
|
||||
llvm::ArrayRef<uint8_t>(g_msp430_opcode, sizeof(g_msp430_opcode));
|
||||
} break;
|
||||
|
||||
case llvm::Triple::systemz: {
|
||||
static const uint8_t g_hex_opcode[] = {0x00, 0x01};
|
||||
trap_opcode = g_hex_opcode;
|
||||
trap_opcode_size = sizeof(g_hex_opcode);
|
||||
trap_opcode = llvm::ArrayRef<uint8_t>(g_hex_opcode, sizeof(g_hex_opcode));
|
||||
} break;
|
||||
|
||||
case llvm::Triple::hexagon: {
|
||||
static const uint8_t g_hex_opcode[] = {0x0c, 0xdb, 0x00, 0x54};
|
||||
trap_opcode = g_hex_opcode;
|
||||
trap_opcode_size = sizeof(g_hex_opcode);
|
||||
trap_opcode = llvm::ArrayRef<uint8_t>(g_hex_opcode, sizeof(g_hex_opcode));
|
||||
} break;
|
||||
|
||||
case llvm::Triple::ppc:
|
||||
case llvm::Triple::ppc64: {
|
||||
static const uint8_t g_ppc_opcode[] = {0x7f, 0xe0, 0x00, 0x08};
|
||||
trap_opcode = g_ppc_opcode;
|
||||
trap_opcode_size = sizeof(g_ppc_opcode);
|
||||
trap_opcode = llvm::ArrayRef<uint8_t>(g_ppc_opcode, sizeof(g_ppc_opcode));
|
||||
} break;
|
||||
|
||||
case llvm::Triple::ppc64le: {
|
||||
static const uint8_t g_ppc64le_opcode[] = {0x08, 0x00, 0xe0, 0x7f}; // trap
|
||||
trap_opcode = g_ppc64le_opcode;
|
||||
trap_opcode_size = sizeof(g_ppc64le_opcode);
|
||||
trap_opcode =
|
||||
llvm::ArrayRef<uint8_t>(g_ppc64le_opcode, sizeof(g_ppc64le_opcode));
|
||||
} break;
|
||||
|
||||
case llvm::Triple::x86:
|
||||
case llvm::Triple::x86_64: {
|
||||
static const uint8_t g_i386_opcode[] = {0xCC};
|
||||
trap_opcode = g_i386_opcode;
|
||||
trap_opcode_size = sizeof(g_i386_opcode);
|
||||
trap_opcode = llvm::ArrayRef<uint8_t>(g_i386_opcode, sizeof(g_i386_opcode));
|
||||
} break;
|
||||
|
||||
case llvm::Triple::riscv32:
|
||||
case llvm::Triple::riscv64: {
|
||||
static const uint8_t g_riscv_opcode[] = {0x73, 0x00, 0x10, 0x00}; // ebreak
|
||||
static const uint8_t g_riscv_opcode_c[] = {0x02, 0x90}; // c.ebreak
|
||||
if (arch.GetFlags() & ArchSpec::eRISCV_rvc) {
|
||||
if (size_hint == 2) {
|
||||
trap_opcode = g_riscv_opcode_c;
|
||||
trap_opcode_size = sizeof(g_riscv_opcode_c);
|
||||
} else {
|
||||
trap_opcode = g_riscv_opcode;
|
||||
trap_opcode_size = sizeof(g_riscv_opcode);
|
||||
trap_opcode =
|
||||
llvm::ArrayRef<uint8_t>(g_riscv_opcode, sizeof(g_riscv_opcode));
|
||||
}
|
||||
} break;
|
||||
|
||||
@@ -2068,24 +2047,71 @@ size_t Platform::GetSoftwareBreakpointTrapOpcode(Target &target,
|
||||
case llvm::Triple::loongarch64: {
|
||||
static const uint8_t g_loongarch_opcode[] = {0x05, 0x00, 0x2a,
|
||||
0x00}; // break 0x5
|
||||
trap_opcode = g_loongarch_opcode;
|
||||
trap_opcode_size = sizeof(g_loongarch_opcode);
|
||||
trap_opcode =
|
||||
llvm::ArrayRef<uint8_t>(g_loongarch_opcode, sizeof(g_loongarch_opcode));
|
||||
} break;
|
||||
|
||||
// Unreachable (0x00) triggers an unconditional trap.
|
||||
case llvm::Triple::wasm32: {
|
||||
// Unreachable (0x00) triggers an unconditional trap.
|
||||
static const uint8_t g_wasm_opcode[] = {0x00};
|
||||
trap_opcode = g_wasm_opcode;
|
||||
trap_opcode_size = sizeof(g_wasm_opcode);
|
||||
trap_opcode = llvm::ArrayRef<uint8_t>(g_wasm_opcode, sizeof(g_wasm_opcode));
|
||||
} break;
|
||||
// The default case should not match against anything, so return empty Array.
|
||||
default: {
|
||||
trap_opcode = llvm::ArrayRef<uint8_t>{};
|
||||
};
|
||||
}
|
||||
return trap_opcode;
|
||||
}
|
||||
|
||||
default:
|
||||
return 0;
|
||||
size_t Platform::GetTrapOpcodeSizeHint(Target &target, Address addr,
|
||||
llvm::ArrayRef<uint8_t> bytes) {
|
||||
ArchSpec arch = target.GetArchitecture();
|
||||
assert(arch.IsValid());
|
||||
const auto &triple = arch.GetTriple();
|
||||
|
||||
if (bytes.size() && triple.isRISCV()) {
|
||||
// RISC-V instructions have the two LSB as 0b11 if they are four-byte.
|
||||
return (bytes[0] & 0b11) == 0b11 ? 4 : 2;
|
||||
}
|
||||
|
||||
assert(bp_site);
|
||||
if (bp_site->SetTrapOpcode(trap_opcode, trap_opcode_size))
|
||||
return trap_opcode_size;
|
||||
if (triple.isARM()) {
|
||||
if (auto addr_class = addr.GetAddressClass();
|
||||
addr_class == AddressClass::eCodeAlternateISA) {
|
||||
return 2;
|
||||
} else {
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t Platform::GetSoftwareBreakpointTrapOpcode(Target &target,
|
||||
BreakpointSite *bp_site) {
|
||||
ArchSpec arch = target.GetArchitecture();
|
||||
assert(arch.IsValid());
|
||||
AddressClass addr_class = AddressClass::eUnknown;
|
||||
if (bp_site) {
|
||||
// TODO: support big-endian arm and thumb trap codes.
|
||||
lldb::BreakpointLocationSP bp_loc_sp(bp_site->GetConstituentAtIndex(0));
|
||||
if (bp_loc_sp) {
|
||||
addr_class = bp_loc_sp->GetAddress().GetAddressClass();
|
||||
if (addr_class == AddressClass::eUnknown &&
|
||||
(bp_loc_sp->GetAddress().GetFileAddress() & 1))
|
||||
addr_class = AddressClass::eCodeAlternateISA;
|
||||
}
|
||||
}
|
||||
|
||||
size_t size_hint = 0;
|
||||
// Check for either ARM or RISC-V short instruction conditions.
|
||||
if ((addr_class == AddressClass::eCodeAlternateISA) ||
|
||||
(arch.GetFlags() & ArchSpec::eRISCV_rvc))
|
||||
size_hint = 2;
|
||||
auto trap_opcode = SoftwareTrapOpcodeBytes(arch, size_hint);
|
||||
|
||||
if (bp_site &&
|
||||
bp_site->SetTrapOpcode(trap_opcode.begin(), trap_opcode.size()))
|
||||
return trap_opcode.size();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "lldb/Breakpoint/Breakpoint.h"
|
||||
@@ -82,6 +83,41 @@ bool StopInfo::HasTargetRunSinceMe() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void StopInfo::SkipOverTrapInstruction() {
|
||||
Status error;
|
||||
Log *log = GetLog(LLDBLog::Process);
|
||||
|
||||
// We don't expect to see byte sequences longer than four bytes long for
|
||||
// any breakpoint instructions known to LLDB.
|
||||
std::array<uint8_t, 4> bytes_at_pc = {0, 0, 0, 0};
|
||||
auto reg_ctx_sp = GetThread()->GetRegisterContext();
|
||||
auto process_sp = GetThread()->GetProcess();
|
||||
addr_t pc = reg_ctx_sp->GetPC();
|
||||
if (!process_sp->ReadMemory(pc, bytes_at_pc.data(), bytes_at_pc.size(),
|
||||
error)) {
|
||||
// If this fails, we simply don't handle the step-over-break logic.
|
||||
LLDB_LOG(log, "failed to read program bytes at pc address {}, error {}", pc,
|
||||
error);
|
||||
return;
|
||||
}
|
||||
|
||||
auto &target = process_sp->GetTarget();
|
||||
auto platform_sp = target.GetPlatform();
|
||||
auto size_hint = platform_sp->GetTrapOpcodeSizeHint(target, pc, bytes_at_pc);
|
||||
auto platform_opcode =
|
||||
platform_sp->SoftwareTrapOpcodeBytes(target.GetArchitecture(), size_hint);
|
||||
|
||||
if (auto *arch_plugin = target.GetArchitecturePlugin();
|
||||
arch_plugin &&
|
||||
arch_plugin->IsValidTrapInstruction(
|
||||
platform_opcode,
|
||||
llvm::ArrayRef<uint8_t>(bytes_at_pc.data(), bytes_at_pc.size()))) {
|
||||
LLDB_LOG(log, "stepping over breakpoint in inferior to new pc: {}",
|
||||
pc + platform_opcode.size());
|
||||
reg_ctx_sp->SetPC(pc + platform_opcode.size());
|
||||
}
|
||||
}
|
||||
|
||||
// StopInfoBreakpoint
|
||||
|
||||
namespace lldb_private {
|
||||
@@ -1159,6 +1195,12 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
void PerformAction([[maybe_unused]] Event *event_ptr) override {
|
||||
// A signal of SIGTRAP indicates that a trap instruction has been hit.
|
||||
if (m_value == SIGTRAP)
|
||||
SkipOverTrapInstruction();
|
||||
}
|
||||
|
||||
bool ShouldStop(Event *event_ptr) override { return IsShouldStopSignal(); }
|
||||
|
||||
// If should stop returns false, check if we should notify of this event
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
CXX_SOURCES := main.cpp
|
||||
C_SOURCES := main.c
|
||||
|
||||
include Makefile.rules
|
||||
+15
-10
@@ -11,16 +11,15 @@ from lldbsuite.test.lldbtest import *
|
||||
class BuiltinDebugTrapTestCase(TestBase):
|
||||
NO_DEBUG_INFO_TESTCASE = True
|
||||
|
||||
# Currently this depends on behavior in debugserver to
|
||||
# advance the pc past __builtin_trap instructions so that
|
||||
# continue works. Everyone is in agreement that this
|
||||
# should be moved up into lldb instead of depending on the
|
||||
# remote stub rewriting the pc values.
|
||||
@skipUnlessDarwin
|
||||
def test(self):
|
||||
platform_stop_reason = lldb.eStopReasonSignal
|
||||
platform = self.getPlatform()
|
||||
if platform == "darwin" or platform == "windows":
|
||||
platform_stop_reason = lldb.eStopReasonException
|
||||
|
||||
self.build()
|
||||
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
|
||||
self, "// Set a breakpoint here", lldb.SBFileSpec("main.cpp")
|
||||
self, "// Set a breakpoint here", lldb.SBFileSpec("main.c")
|
||||
)
|
||||
|
||||
# Continue to __builtin_debugtrap()
|
||||
@@ -31,7 +30,7 @@ class BuiltinDebugTrapTestCase(TestBase):
|
||||
self.runCmd("ta v global")
|
||||
|
||||
self.assertEqual(
|
||||
process.GetSelectedThread().GetStopReason(), lldb.eStopReasonException
|
||||
process.GetSelectedThread().GetStopReason(), platform_stop_reason
|
||||
)
|
||||
|
||||
list = target.FindGlobalVariables("global", 1, lldb.eMatchTypeNormal)
|
||||
@@ -49,12 +48,18 @@ class BuiltinDebugTrapTestCase(TestBase):
|
||||
self.runCmd("ta v global")
|
||||
|
||||
self.assertEqual(
|
||||
process.GetSelectedThread().GetStopReason(), lldb.eStopReasonException
|
||||
process.GetSelectedThread().GetStopReason(), platform_stop_reason
|
||||
)
|
||||
|
||||
# "global" is now 10.
|
||||
self.assertEqual(global_value.GetValueAsUnsigned(), 10)
|
||||
|
||||
# Change the handling of SIGILL on x86-64 Linux - do not pass it
|
||||
# to the inferior, but stop and notify lldb. If we don't do this,
|
||||
# the inferior will receive the SIGILL and be terminated.
|
||||
if self.getArchitecture() == "x86_64" and platform == "linux":
|
||||
self.runCmd("process handle -p false SIGILL")
|
||||
|
||||
# We should be at the same point as before -- cannot advance
|
||||
# past a __builtin_trap().
|
||||
process.Continue()
|
||||
@@ -64,7 +69,7 @@ class BuiltinDebugTrapTestCase(TestBase):
|
||||
self.runCmd("ta v global")
|
||||
|
||||
self.assertEqual(
|
||||
process.GetSelectedThread().GetStopReason(), lldb.eStopReasonException
|
||||
process.GetSelectedThread().GetStopReason(), platform_stop_reason
|
||||
)
|
||||
|
||||
# "global" is still 10.
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
#include <stdio.h>
|
||||
int global = 0;
|
||||
int main()
|
||||
{
|
||||
int main() {
|
||||
global = 5; // Set a breakpoint here
|
||||
puts("");
|
||||
__builtin_debugtrap();
|
||||
Reference in new issue
Block a user