Skip to content
Merged
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
Loading