Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions lldb/include/lldb/Core/Architecture.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "lldb/Core/PluginInterface.h"
#include "lldb/Target/DynamicRegisterInfo.h"
#include "lldb/Target/MemoryTagManager.h"
#include "lldb/Target/RegisterContextUnwind.h"

namespace lldb_private {

Expand Down Expand Up @@ -129,6 +130,14 @@ class Architecture : public PluginInterface {
RegisterContext &reg_context) const {
return false;
}

/// Return an UnwindPlan that allows architecture-defined rules for finding
/// saved registers, given a particular set of register values.
virtual lldb::UnwindPlanSP GetArchitectureUnwindPlan(
lldb_private::Thread &thread, lldb_private::RegisterContextUnwind *regctx,
std::shared_ptr<const UnwindPlan> current_unwindplan) {
return lldb::UnwindPlanSP();
}
};

} // namespace lldb_private
Expand Down
45 changes: 24 additions & 21 deletions lldb/include/lldb/Target/RegisterContextUnwind.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
namespace lldb_private {

class UnwindLLDB;
class ArchitectureArm;

class RegisterContextUnwind : public lldb_private::RegisterContext {
public:
Expand Down Expand Up @@ -72,6 +73,25 @@ class RegisterContextUnwind : public lldb_private::RegisterContext {
// above asynchronous trap handlers (sigtramp) for instance.
bool BehavesLikeZerothFrame() const override;

protected:
// Provide a location for where THIS function saved the CALLER's register
// value, or a frame "below" this one saved it. That is, this function doesn't
// modify the register, it may call a function that does & saved it to stack.
//
// The ConcreteRegisterLocation type may be set to eRegisterNotAvailable --
// this will happen for a volatile register being queried mid-stack. Instead
// of floating frame 0's contents of that register up the stack (which may or
// may not be the value of that reg when the function was executing), we won't
// return any value.
//
// If a non-volatile register (a "preserved" register, a callee-preserved
// register) is requested mid-stack, and no frames "below" the requested stack
// have saved the register anywhere, it is safe to assume that frame 0's
// register value is the same.
lldb_private::UnwindLLDB::RegisterSearchResult SavedLocationForRegister(
uint32_t lldb_regnum,
lldb_private::UnwindLLDB::ConcreteRegisterLocation &regloc);

private:
enum FrameType {
eNormalFrame,
Expand All @@ -86,6 +106,8 @@ class RegisterContextUnwind : public lldb_private::RegisterContext {

// UnwindLLDB needs to pass around references to ConcreteRegisterLocations
friend class UnwindLLDB;
// Architecture may need to retrieve caller register values from this frame
friend class ArchitectureArm;

// Returns true if we have an unwind loop -- the same stack frame unwinding
// multiple times.
Expand Down Expand Up @@ -130,27 +152,6 @@ class RegisterContextUnwind : public lldb_private::RegisterContext {
void PropagateTrapHandlerFlagFromUnwindPlan(
std::shared_ptr<const UnwindPlan> unwind_plan);

// Provide a location for where THIS function saved the CALLER's register
// value
// Or a frame "below" this one saved it, i.e. a function called by this one,
// preserved a register that this
// function didn't modify/use.
//
// The ConcreteRegisterLocation type may be set to eRegisterNotAvailable --
// this will happen for a volatile register being queried mid-stack. Instead
// of floating frame 0's contents of that register up the stack (which may or
// may not be the value of that reg when the function was executing), we won't
// return any value.
//
// If a non-volatile register (a "preserved" register) is requested mid-stack
// and no frames "below" the requested
// stack have saved the register anywhere, it is safe to assume that frame 0's
// register values are still the same
// as the requesting frame's.
lldb_private::UnwindLLDB::RegisterSearchResult SavedLocationForRegister(
uint32_t lldb_regnum,
lldb_private::UnwindLLDB::ConcreteRegisterLocation &regloc);

std::optional<UnwindPlan::Row::AbstractRegisterLocation>
GetAbstractRegisterLocation(uint32_t lldb_regnum, lldb::RegisterKind &kind);

Expand Down Expand Up @@ -202,6 +203,8 @@ class RegisterContextUnwind : public lldb_private::RegisterContext {

std::shared_ptr<const UnwindPlan> GetFullUnwindPlanForFrame();

lldb::UnwindPlanSP TryAdoptArchitectureUnwindPlan();

void UnwindLogMsg(const char *fmt, ...) __attribute__((format(printf, 2, 3)));

void UnwindLogMsgVerbose(const char *fmt, ...)
Expand Down
2 changes: 2 additions & 0 deletions lldb/include/lldb/Target/UnwindLLDB.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
namespace lldb_private {

class RegisterContextUnwind;
class ArchitectureArm;

class UnwindLLDB : public lldb_private::Unwind {
public:
Expand All @@ -37,6 +38,7 @@ class UnwindLLDB : public lldb_private::Unwind {

protected:
friend class lldb_private::RegisterContextUnwind;
friend class lldb_private::ArchitectureArm;

/// An UnwindPlan::Row::AbstractRegisterLocation, combined with the register
/// context and memory for a specific stop point, is used to create a
Expand Down
7 changes: 7 additions & 0 deletions lldb/source/Plugins/ABI/ARM/ABISysV_arm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1921,6 +1921,13 @@ UnwindPlanSP ABISysV_arm::CreateFunctionEntryUnwindPlan() {

UnwindPlanSP ABISysV_arm::CreateDefaultUnwindPlan() {
// TODO: Handle thumb
// If we had a Target argument, could at least check
// target.GetArchitecture().GetTriple().isArmMClass()
// which is always thumb.
// To handle thumb properly, we'd need to fetch the current
// CPSR state at unwind time to tell if the processor is
// in thumb mode in this stack frame. There's no way to
// express something like that in an UnwindPlan today.
uint32_t fp_reg_num = dwarf_r11;
uint32_t pc_reg_num = dwarf_pc;

Expand Down
186 changes: 186 additions & 0 deletions lldb/source/Plugins/Architecture/Arm/ArchitectureArm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,18 @@
#include "Plugins/Architecture/Arm/ArchitectureArm.h"
#include "Plugins/Process/Utility/ARMDefines.h"
#include "Plugins/Process/Utility/InstructionUtils.h"
#include "Utility/ARM_DWARF_Registers.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Symbol/UnwindPlan.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/RegisterContext.h"
#include "lldb/Target/RegisterNumber.h"
#include "lldb/Target/Thread.h"
#include "lldb/Target/UnwindLLDB.h"
#include "lldb/Utility/ArchSpec.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/RegisterValue.h"

using namespace lldb_private;
using namespace lldb;
Expand Down Expand Up @@ -150,3 +158,181 @@ addr_t ArchitectureArm::GetOpcodeLoadAddress(addr_t opcode_addr,
}
return opcode_addr & ~(1ull);
}

// The ARM M-Profile Armv7-M Architecture Reference Manual,
// subsection "B1.5 Armv7-M exception model", see the parts
// describing "Exception entry behavior" and "Exception
// return behavior".
// When an exception happens on this processor, certain registers are
// saved below the stack pointer, the stack pointer is decremented,
// a special value is put in the link register to indicate the
// exception has been taken, and an exception handler function
// is invoked.
//
// Detect that special value in $lr, and if present, add
// unwind rules for the registers that were saved above this
// stack frame's CFA. Overwrite any register locations that
// the current_unwindplan has for these registers; they are
// not correct when we're invoked this way.
UnwindPlanSP ArchitectureArm::GetArchitectureUnwindPlan(
Thread &thread, RegisterContextUnwind *regctx,
std::shared_ptr<const UnwindPlan> current_unwindplan) {

ProcessSP process_sp = thread.GetProcess();
if (!process_sp)
return {};

const ArchSpec arch = process_sp->GetTarget().GetArchitecture();
if (!arch.GetTriple().isArmMClass() || arch.GetAddressByteSize() != 4)
return {};

// Get the caller's LR value from regctx (the LR value
// at function entry to this function).
RegisterNumber ra_regnum(thread, eRegisterKindGeneric,
LLDB_REGNUM_GENERIC_RA);
uint32_t ra_regnum_lldb = ra_regnum.GetAsKind(eRegisterKindLLDB);

if (ra_regnum_lldb == LLDB_INVALID_REGNUM)
return {};

UnwindLLDB::ConcreteRegisterLocation regloc = {};
bool got_concrete_location = false;
if (regctx->SavedLocationForRegister(ra_regnum_lldb, regloc) ==
UnwindLLDB::RegisterSearchResult::eRegisterFound) {
got_concrete_location = true;
} else {
RegisterNumber pc_regnum(thread, eRegisterKindGeneric,
LLDB_REGNUM_GENERIC_PC);
uint32_t pc_regnum_lldb = pc_regnum.GetAsKind(eRegisterKindLLDB);
if (regctx->SavedLocationForRegister(pc_regnum_lldb, regloc) ==
UnwindLLDB::RegisterSearchResult::eRegisterFound)
got_concrete_location = true;
}

if (!got_concrete_location)
return {};

addr_t callers_return_address = LLDB_INVALID_ADDRESS;
const RegisterInfo *reg_info = regctx->GetRegisterInfoAtIndex(ra_regnum_lldb);
if (reg_info) {
RegisterValue reg_value;
if (regctx->ReadRegisterValueFromRegisterLocation(regloc, reg_info,
reg_value)) {
callers_return_address = reg_value.GetAsUInt32();
}
}

if (callers_return_address == LLDB_INVALID_ADDRESS)
return {};

// ARMv7-M ARM says that the LR will be set to
// one of these values when an exception has taken
// place:
// if HaveFPExt() then
// if CurrentMode==Mode_Handler then
// LR = Ones(27):NOT(CONTROL.FPCA):'0001';
// else
// LR = Ones(27):NOT(CONTROL.FPCA):'1':CONTROL.SPSEL:'01';
// else
// if CurrentMode==Mode_Handler then
// LR = Ones(28):'0001';
// else
// LR = Ones(29):CONTROL.SPSEL:'01';

// Top 27 bits are set for an exception return.
const uint32_t exception_return = -1U & ~0b11111U;
// Bit4 is 1 if only GPRs were saved.
const uint32_t gprs_only = 0b10000;
// Bit<1:0> are '01'.
const uint32_t lowbits = 0b01;

if ((callers_return_address & exception_return) != exception_return)
return {};
if ((callers_return_address & lowbits) != lowbits)
return {};

const bool fp_regs_saved = !(callers_return_address & gprs_only);

const RegisterKind plan_regkind = current_unwindplan->GetRegisterKind();
UnwindPlanSP new_plan = std::make_shared<UnwindPlan>(plan_regkind);
new_plan->SetSourceName("Arm Cortex-M exception return UnwindPlan");
new_plan->SetSourcedFromCompiler(eLazyBoolNo);
new_plan->SetUnwindPlanValidAtAllInstructions(eLazyBoolYes);
new_plan->SetUnwindPlanForSignalTrap(eLazyBoolYes);

int stored_regs_size = fp_regs_saved ? 0x68 : 0x20;

uint32_t gpr_regs[] = {dwarf_r0, dwarf_r1, dwarf_r2, dwarf_r3,
dwarf_r12, dwarf_lr, dwarf_pc, dwarf_cpsr};
const int gpr_reg_count = std::size(gpr_regs);
uint32_t fpr_regs[] = {dwarf_s0, dwarf_s1, dwarf_s2, dwarf_s3,
dwarf_s4, dwarf_s5, dwarf_s6, dwarf_s7,
dwarf_s8, dwarf_s9, dwarf_s10, dwarf_s11,
dwarf_s12, dwarf_s13, dwarf_s14, dwarf_s15};
const int fpr_reg_count = std::size(fpr_regs);

RegisterContextSP reg_ctx_sp = thread.GetRegisterContext();
std::vector<uint32_t> saved_regs;
for (int i = 0; i < gpr_reg_count; i++) {
uint32_t regno = gpr_regs[i];
reg_ctx_sp->ConvertBetweenRegisterKinds(eRegisterKindDWARF, gpr_regs[i],
plan_regkind, regno);
saved_regs.push_back(regno);
}
if (fp_regs_saved) {
for (int i = 0; i < fpr_reg_count; i++) {
uint32_t regno = fpr_regs[i];
reg_ctx_sp->ConvertBetweenRegisterKinds(eRegisterKindDWARF, fpr_regs[i],
plan_regkind, regno);
saved_regs.push_back(regno);
}
}

addr_t cfa;
if (!regctx->GetCFA(cfa))
return {};

// The CPSR value saved to stack is actually (from Armv7-M ARM)
// "XPSR<31:10>:frameptralign:XPSR<8:0>"
// Bit 9 indicates that the stack pointer was aligned (to
// an 8-byte alignment) when the exception happened, and we must
// account for that when restoring the original stack pointer value.
Status error;
uint32_t callers_xPSR =
process_sp->ReadUnsignedIntegerFromMemory(cfa + 0x1c, 4, 0, error);
const bool align_stack = callers_xPSR & (1U << 9);
uint32_t callers_sp = cfa + stored_regs_size;
if (align_stack)
callers_sp |= 4;

Log *log = GetLog(LLDBLog::Unwind);
LLDB_LOGF(log,
"ArchitectureArm::GetArchitectureUnwindPlan found caller return "
"addr of 0x%" PRIx64 ", for frame with CFA 0x%" PRIx64
", fp_regs_saved %d, stored_regs_size 0x%x, align stack %d",
callers_return_address, cfa, fp_regs_saved, stored_regs_size,
align_stack);

uint32_t sp_regnum = dwarf_sp;
reg_ctx_sp->ConvertBetweenRegisterKinds(eRegisterKindDWARF, dwarf_sp,
plan_regkind, sp_regnum);

const int row_count = current_unwindplan->GetRowCount();
for (int i = 0; i < row_count; i++) {
UnwindPlan::Row row = *current_unwindplan->GetRowAtIndex(i);
uint32_t offset = 0;
const size_t saved_reg_count = saved_regs.size();
for (size_t j = 0; j < saved_reg_count; j++) {
// The locations could be set with
// SetRegisterLocationToIsConstant(regno, cfa+offset)
// expressing it in terms of CFA addr+offset - this UnwindPlan
// is only used once, with this specific CFA. I'm not sure
// which will be clearer for someone reading the unwind log.
row.SetRegisterLocationToAtCFAPlusOffset(saved_regs[j], offset, true);
offset += 4;
}
row.SetRegisterLocationToIsCFAPlusOffset(sp_regnum, callers_sp - cfa, true);
new_plan->AppendRow(row);
}
return new_plan;
}
5 changes: 5 additions & 0 deletions lldb/source/Plugins/Architecture/Arm/ArchitectureArm.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#define LLDB_SOURCE_PLUGINS_ARCHITECTURE_ARM_ARCHITECTUREARM_H

#include "lldb/Core/Architecture.h"
#include "lldb/Target/Thread.h"

namespace lldb_private {

Expand All @@ -29,6 +30,10 @@ class ArchitectureArm : public Architecture {
lldb::addr_t GetOpcodeLoadAddress(lldb::addr_t load_addr,
AddressClass addr_class) const override;

lldb::UnwindPlanSP GetArchitectureUnwindPlan(
lldb_private::Thread &thread, lldb_private::RegisterContextUnwind *regctx,
std::shared_ptr<const UnwindPlan> current_unwindplan) override;

private:
static std::unique_ptr<Architecture> Create(const ArchSpec &arch);
ArchitectureArm() = default;
Expand Down
Loading