Skip to content

Commit 69511ae

Browse files
authored
[lldb] Unwind through ARM Cortex-M exceptions automatically (#153922)
When a processor faults/is interrupted/gets an exception, it will stop running code and jump to an exception catcher routine. Most processors will store the pc that was executing in a system register, and the catcher functions have special instructions to retrieve that & possibly other registers. It may then save those values to stack, and the author can add .cfi directives to tell lldb's unwinder where to find those saved values. ARM Cortex-M (microcontroller) processors have a simpler mechanism where a fixed set of registers are saved to the stack on an exception, and a unique value is put in the link register to indicate to the caller that this has taken place. No special handling needs to be written into the exception catcher, unless it wants to inspect these preserved values. And it is possible for a general stack walker to walk the stack with no special knowledge about what the catch function does. This patch adds an Architecture plugin method to allow an Architecture to override/augment the UnwindPlan that lldb would use for a stack frame, given the contents of the return address register. It resembles a feature where the LanguageRuntime can replace/augment the unwind plan for a function, but it is doing it at offset by one level. The LanguageRuntime is looking at the local register context and/or symbol name to decide if it will override the unwind rules. For the Cortex-M exception unwinds, we need to modify THIS frame's unwind plan if the CALLER's LR had a specific value. RegisterContextUnwind has to retrieve the caller's LR value before it has completely decided on the UnwindPlan it will use for THIS stack frame. This does mean that we will need one additional read of stack memory than we currently do when unwinding, on Armv7 Cortex-M targets. The unwinder walks the stack lazily, as stack frames are requested, and so now if you ask for 2 stack frames, we will read enough stack to walk 2 frames, plus we will read one extra word of memory, the spilled LR value from the stack. In practice, with 512-byte memory cache reads, this is unlikely to be a real performance hit. This PR includes a test with a yaml corefile description and a JSON ObjectFile, incorporating all of the necessary stack memory and symbol names from a real debug session I worked on. The architectural default unwind plans are used for all stack frames except the 0th because there's no instructions for the functions, and no unwind info. I may need to add an encoding of unwind fules to ObjectFileJSON in the future as we create more test cases like this. This PR depends on the yaml2macho-core utility from #153911 to run its API test. rdar://110663219
1 parent c4b17bf commit 69511ae

File tree

14 files changed

+488
-21
lines changed

14 files changed

+488
-21
lines changed

lldb/include/lldb/Core/Architecture.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "lldb/Core/PluginInterface.h"
1313
#include "lldb/Target/DynamicRegisterInfo.h"
1414
#include "lldb/Target/MemoryTagManager.h"
15+
#include "lldb/Target/RegisterContextUnwind.h"
1516

1617
namespace lldb_private {
1718

@@ -129,6 +130,14 @@ class Architecture : public PluginInterface {
129130
RegisterContext &reg_context) const {
130131
return false;
131132
}
133+
134+
/// Return an UnwindPlan that allows architecture-defined rules for finding
135+
/// saved registers, given a particular set of register values.
136+
virtual lldb::UnwindPlanSP GetArchitectureUnwindPlan(
137+
lldb_private::Thread &thread, lldb_private::RegisterContextUnwind *regctx,
138+
std::shared_ptr<const UnwindPlan> current_unwindplan) {
139+
return lldb::UnwindPlanSP();
140+
}
132141
};
133142

134143
} // namespace lldb_private

lldb/include/lldb/Target/RegisterContextUnwind.h

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
namespace lldb_private {
2222

2323
class UnwindLLDB;
24+
class ArchitectureArm;
2425

2526
class RegisterContextUnwind : public lldb_private::RegisterContext {
2627
public:
@@ -72,6 +73,25 @@ class RegisterContextUnwind : public lldb_private::RegisterContext {
7273
// above asynchronous trap handlers (sigtramp) for instance.
7374
bool BehavesLikeZerothFrame() const override;
7475

76+
protected:
77+
// Provide a location for where THIS function saved the CALLER's register
78+
// value, or a frame "below" this one saved it. That is, this function doesn't
79+
// modify the register, it may call a function that does & saved it to stack.
80+
//
81+
// The ConcreteRegisterLocation type may be set to eRegisterNotAvailable --
82+
// this will happen for a volatile register being queried mid-stack. Instead
83+
// of floating frame 0's contents of that register up the stack (which may or
84+
// may not be the value of that reg when the function was executing), we won't
85+
// return any value.
86+
//
87+
// If a non-volatile register (a "preserved" register, a callee-preserved
88+
// register) is requested mid-stack, and no frames "below" the requested stack
89+
// have saved the register anywhere, it is safe to assume that frame 0's
90+
// register value is the same.
91+
lldb_private::UnwindLLDB::RegisterSearchResult SavedLocationForRegister(
92+
uint32_t lldb_regnum,
93+
lldb_private::UnwindLLDB::ConcreteRegisterLocation &regloc);
94+
7595
private:
7696
enum FrameType {
7797
eNormalFrame,
@@ -86,6 +106,8 @@ class RegisterContextUnwind : public lldb_private::RegisterContext {
86106

87107
// UnwindLLDB needs to pass around references to ConcreteRegisterLocations
88108
friend class UnwindLLDB;
109+
// Architecture may need to retrieve caller register values from this frame
110+
friend class ArchitectureArm;
89111

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

133-
// Provide a location for where THIS function saved the CALLER's register
134-
// value
135-
// Or a frame "below" this one saved it, i.e. a function called by this one,
136-
// preserved a register that this
137-
// function didn't modify/use.
138-
//
139-
// The ConcreteRegisterLocation type may be set to eRegisterNotAvailable --
140-
// this will happen for a volatile register being queried mid-stack. Instead
141-
// of floating frame 0's contents of that register up the stack (which may or
142-
// may not be the value of that reg when the function was executing), we won't
143-
// return any value.
144-
//
145-
// If a non-volatile register (a "preserved" register) is requested mid-stack
146-
// and no frames "below" the requested
147-
// stack have saved the register anywhere, it is safe to assume that frame 0's
148-
// register values are still the same
149-
// as the requesting frame's.
150-
lldb_private::UnwindLLDB::RegisterSearchResult SavedLocationForRegister(
151-
uint32_t lldb_regnum,
152-
lldb_private::UnwindLLDB::ConcreteRegisterLocation &regloc);
153-
154155
std::optional<UnwindPlan::Row::AbstractRegisterLocation>
155156
GetAbstractRegisterLocation(uint32_t lldb_regnum, lldb::RegisterKind &kind);
156157

@@ -202,6 +203,8 @@ class RegisterContextUnwind : public lldb_private::RegisterContext {
202203

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

206+
lldb::UnwindPlanSP TryAdoptArchitectureUnwindPlan();
207+
205208
void UnwindLogMsg(const char *fmt, ...) __attribute__((format(printf, 2, 3)));
206209

207210
void UnwindLogMsgVerbose(const char *fmt, ...)

lldb/include/lldb/Target/UnwindLLDB.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
namespace lldb_private {
2323

2424
class RegisterContextUnwind;
25+
class ArchitectureArm;
2526

2627
class UnwindLLDB : public lldb_private::Unwind {
2728
public:
@@ -37,6 +38,7 @@ class UnwindLLDB : public lldb_private::Unwind {
3738

3839
protected:
3940
friend class lldb_private::RegisterContextUnwind;
41+
friend class lldb_private::ArchitectureArm;
4042

4143
/// An UnwindPlan::Row::AbstractRegisterLocation, combined with the register
4244
/// context and memory for a specific stop point, is used to create a

lldb/source/Plugins/ABI/ARM/ABISysV_arm.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1921,6 +1921,13 @@ UnwindPlanSP ABISysV_arm::CreateFunctionEntryUnwindPlan() {
19211921

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

lldb/source/Plugins/Architecture/Arm/ArchitectureArm.cpp

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,18 @@
99
#include "Plugins/Architecture/Arm/ArchitectureArm.h"
1010
#include "Plugins/Process/Utility/ARMDefines.h"
1111
#include "Plugins/Process/Utility/InstructionUtils.h"
12+
#include "Utility/ARM_DWARF_Registers.h"
1213
#include "lldb/Core/PluginManager.h"
14+
#include "lldb/Symbol/UnwindPlan.h"
15+
#include "lldb/Target/Process.h"
1316
#include "lldb/Target/RegisterContext.h"
17+
#include "lldb/Target/RegisterNumber.h"
1418
#include "lldb/Target/Thread.h"
19+
#include "lldb/Target/UnwindLLDB.h"
1520
#include "lldb/Utility/ArchSpec.h"
21+
#include "lldb/Utility/LLDBLog.h"
22+
#include "lldb/Utility/Log.h"
23+
#include "lldb/Utility/RegisterValue.h"
1624

1725
using namespace lldb_private;
1826
using namespace lldb;
@@ -150,3 +158,181 @@ addr_t ArchitectureArm::GetOpcodeLoadAddress(addr_t opcode_addr,
150158
}
151159
return opcode_addr & ~(1ull);
152160
}
161+
162+
// The ARM M-Profile Armv7-M Architecture Reference Manual,
163+
// subsection "B1.5 Armv7-M exception model", see the parts
164+
// describing "Exception entry behavior" and "Exception
165+
// return behavior".
166+
// When an exception happens on this processor, certain registers are
167+
// saved below the stack pointer, the stack pointer is decremented,
168+
// a special value is put in the link register to indicate the
169+
// exception has been taken, and an exception handler function
170+
// is invoked.
171+
//
172+
// Detect that special value in $lr, and if present, add
173+
// unwind rules for the registers that were saved above this
174+
// stack frame's CFA. Overwrite any register locations that
175+
// the current_unwindplan has for these registers; they are
176+
// not correct when we're invoked this way.
177+
UnwindPlanSP ArchitectureArm::GetArchitectureUnwindPlan(
178+
Thread &thread, RegisterContextUnwind *regctx,
179+
std::shared_ptr<const UnwindPlan> current_unwindplan) {
180+
181+
ProcessSP process_sp = thread.GetProcess();
182+
if (!process_sp)
183+
return {};
184+
185+
const ArchSpec arch = process_sp->GetTarget().GetArchitecture();
186+
if (!arch.GetTriple().isArmMClass() || arch.GetAddressByteSize() != 4)
187+
return {};
188+
189+
// Get the caller's LR value from regctx (the LR value
190+
// at function entry to this function).
191+
RegisterNumber ra_regnum(thread, eRegisterKindGeneric,
192+
LLDB_REGNUM_GENERIC_RA);
193+
uint32_t ra_regnum_lldb = ra_regnum.GetAsKind(eRegisterKindLLDB);
194+
195+
if (ra_regnum_lldb == LLDB_INVALID_REGNUM)
196+
return {};
197+
198+
UnwindLLDB::ConcreteRegisterLocation regloc = {};
199+
bool got_concrete_location = false;
200+
if (regctx->SavedLocationForRegister(ra_regnum_lldb, regloc) ==
201+
UnwindLLDB::RegisterSearchResult::eRegisterFound) {
202+
got_concrete_location = true;
203+
} else {
204+
RegisterNumber pc_regnum(thread, eRegisterKindGeneric,
205+
LLDB_REGNUM_GENERIC_PC);
206+
uint32_t pc_regnum_lldb = pc_regnum.GetAsKind(eRegisterKindLLDB);
207+
if (regctx->SavedLocationForRegister(pc_regnum_lldb, regloc) ==
208+
UnwindLLDB::RegisterSearchResult::eRegisterFound)
209+
got_concrete_location = true;
210+
}
211+
212+
if (!got_concrete_location)
213+
return {};
214+
215+
addr_t callers_return_address = LLDB_INVALID_ADDRESS;
216+
const RegisterInfo *reg_info = regctx->GetRegisterInfoAtIndex(ra_regnum_lldb);
217+
if (reg_info) {
218+
RegisterValue reg_value;
219+
if (regctx->ReadRegisterValueFromRegisterLocation(regloc, reg_info,
220+
reg_value)) {
221+
callers_return_address = reg_value.GetAsUInt32();
222+
}
223+
}
224+
225+
if (callers_return_address == LLDB_INVALID_ADDRESS)
226+
return {};
227+
228+
// ARMv7-M ARM says that the LR will be set to
229+
// one of these values when an exception has taken
230+
// place:
231+
// if HaveFPExt() then
232+
// if CurrentMode==Mode_Handler then
233+
// LR = Ones(27):NOT(CONTROL.FPCA):'0001';
234+
// else
235+
// LR = Ones(27):NOT(CONTROL.FPCA):'1':CONTROL.SPSEL:'01';
236+
// else
237+
// if CurrentMode==Mode_Handler then
238+
// LR = Ones(28):'0001';
239+
// else
240+
// LR = Ones(29):CONTROL.SPSEL:'01';
241+
242+
// Top 27 bits are set for an exception return.
243+
const uint32_t exception_return = -1U & ~0b11111U;
244+
// Bit4 is 1 if only GPRs were saved.
245+
const uint32_t gprs_only = 0b10000;
246+
// Bit<1:0> are '01'.
247+
const uint32_t lowbits = 0b01;
248+
249+
if ((callers_return_address & exception_return) != exception_return)
250+
return {};
251+
if ((callers_return_address & lowbits) != lowbits)
252+
return {};
253+
254+
const bool fp_regs_saved = !(callers_return_address & gprs_only);
255+
256+
const RegisterKind plan_regkind = current_unwindplan->GetRegisterKind();
257+
UnwindPlanSP new_plan = std::make_shared<UnwindPlan>(plan_regkind);
258+
new_plan->SetSourceName("Arm Cortex-M exception return UnwindPlan");
259+
new_plan->SetSourcedFromCompiler(eLazyBoolNo);
260+
new_plan->SetUnwindPlanValidAtAllInstructions(eLazyBoolYes);
261+
new_plan->SetUnwindPlanForSignalTrap(eLazyBoolYes);
262+
263+
int stored_regs_size = fp_regs_saved ? 0x68 : 0x20;
264+
265+
uint32_t gpr_regs[] = {dwarf_r0, dwarf_r1, dwarf_r2, dwarf_r3,
266+
dwarf_r12, dwarf_lr, dwarf_pc, dwarf_cpsr};
267+
const int gpr_reg_count = std::size(gpr_regs);
268+
uint32_t fpr_regs[] = {dwarf_s0, dwarf_s1, dwarf_s2, dwarf_s3,
269+
dwarf_s4, dwarf_s5, dwarf_s6, dwarf_s7,
270+
dwarf_s8, dwarf_s9, dwarf_s10, dwarf_s11,
271+
dwarf_s12, dwarf_s13, dwarf_s14, dwarf_s15};
272+
const int fpr_reg_count = std::size(fpr_regs);
273+
274+
RegisterContextSP reg_ctx_sp = thread.GetRegisterContext();
275+
std::vector<uint32_t> saved_regs;
276+
for (int i = 0; i < gpr_reg_count; i++) {
277+
uint32_t regno = gpr_regs[i];
278+
reg_ctx_sp->ConvertBetweenRegisterKinds(eRegisterKindDWARF, gpr_regs[i],
279+
plan_regkind, regno);
280+
saved_regs.push_back(regno);
281+
}
282+
if (fp_regs_saved) {
283+
for (int i = 0; i < fpr_reg_count; i++) {
284+
uint32_t regno = fpr_regs[i];
285+
reg_ctx_sp->ConvertBetweenRegisterKinds(eRegisterKindDWARF, fpr_regs[i],
286+
plan_regkind, regno);
287+
saved_regs.push_back(regno);
288+
}
289+
}
290+
291+
addr_t cfa;
292+
if (!regctx->GetCFA(cfa))
293+
return {};
294+
295+
// The CPSR value saved to stack is actually (from Armv7-M ARM)
296+
// "XPSR<31:10>:frameptralign:XPSR<8:0>"
297+
// Bit 9 indicates that the stack pointer was aligned (to
298+
// an 8-byte alignment) when the exception happened, and we must
299+
// account for that when restoring the original stack pointer value.
300+
Status error;
301+
uint32_t callers_xPSR =
302+
process_sp->ReadUnsignedIntegerFromMemory(cfa + 0x1c, 4, 0, error);
303+
const bool align_stack = callers_xPSR & (1U << 9);
304+
uint32_t callers_sp = cfa + stored_regs_size;
305+
if (align_stack)
306+
callers_sp |= 4;
307+
308+
Log *log = GetLog(LLDBLog::Unwind);
309+
LLDB_LOGF(log,
310+
"ArchitectureArm::GetArchitectureUnwindPlan found caller return "
311+
"addr of 0x%" PRIx64 ", for frame with CFA 0x%" PRIx64
312+
", fp_regs_saved %d, stored_regs_size 0x%x, align stack %d",
313+
callers_return_address, cfa, fp_regs_saved, stored_regs_size,
314+
align_stack);
315+
316+
uint32_t sp_regnum = dwarf_sp;
317+
reg_ctx_sp->ConvertBetweenRegisterKinds(eRegisterKindDWARF, dwarf_sp,
318+
plan_regkind, sp_regnum);
319+
320+
const int row_count = current_unwindplan->GetRowCount();
321+
for (int i = 0; i < row_count; i++) {
322+
UnwindPlan::Row row = *current_unwindplan->GetRowAtIndex(i);
323+
uint32_t offset = 0;
324+
const size_t saved_reg_count = saved_regs.size();
325+
for (size_t j = 0; j < saved_reg_count; j++) {
326+
// The locations could be set with
327+
// SetRegisterLocationToIsConstant(regno, cfa+offset)
328+
// expressing it in terms of CFA addr+offset - this UnwindPlan
329+
// is only used once, with this specific CFA. I'm not sure
330+
// which will be clearer for someone reading the unwind log.
331+
row.SetRegisterLocationToAtCFAPlusOffset(saved_regs[j], offset, true);
332+
offset += 4;
333+
}
334+
row.SetRegisterLocationToIsCFAPlusOffset(sp_regnum, callers_sp - cfa, true);
335+
new_plan->AppendRow(row);
336+
}
337+
return new_plan;
338+
}

lldb/source/Plugins/Architecture/Arm/ArchitectureArm.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#define LLDB_SOURCE_PLUGINS_ARCHITECTURE_ARM_ARCHITECTUREARM_H
1111

1212
#include "lldb/Core/Architecture.h"
13+
#include "lldb/Target/Thread.h"
1314

1415
namespace lldb_private {
1516

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

33+
lldb::UnwindPlanSP GetArchitectureUnwindPlan(
34+
lldb_private::Thread &thread, lldb_private::RegisterContextUnwind *regctx,
35+
std::shared_ptr<const UnwindPlan> current_unwindplan) override;
36+
3237
private:
3338
static std::unique_ptr<Architecture> Create(const ArchSpec &arch);
3439
ArchitectureArm() = default;

0 commit comments

Comments
 (0)