Skip to content

Commit eac74f9

Browse files
committed
[lldb] Unwind through ARM Cortex-M exceptions automatically
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 use when unwinding. 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 RA value from the stack (see RegisterContextUnwind::AdoptArchitectureUnwindPlan()). In practice, with 512-byte memory cache reads, this is unlikely to be a problem, but I'm wondering if I should add an Architecture method of "does this Architecture implement `GetArchitectureUnwindPlan`" method -- and only do the memory read if it does. So the performance impact would be limited to armv7/Cortex-M debug sessions. 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 llvm#153911 rdar://110663219
1 parent 638bd11 commit eac74f9

File tree

13 files changed

+436
-0
lines changed

13 files changed

+436
-0
lines changed

lldb/include/lldb/Core/Architecture.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,15 @@ class Architecture : public PluginInterface {
129129
RegisterContext &reg_context) const {
130130
return false;
131131
}
132+
133+
/// Return an UnwindPlan that allows architecture-defined rules for finding
134+
/// saved registers in a specific context (not specific to a function's
135+
/// instructions/unwind info).
136+
virtual lldb::UnwindPlanSP GetArchitectureUnwindPlan(
137+
lldb_private::Thread &thread, lldb::addr_t callers_return_address,
138+
lldb::addr_t cfa, 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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ class RegisterContextUnwind : public lldb_private::RegisterContext {
202202

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

205+
lldb::UnwindPlanSP AdoptArchitectureUnwindPlan();
206+
205207
void UnwindLogMsg(const char *fmt, ...) __attribute__((format(printf, 2, 3)));
206208

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

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: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,15 @@
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"
1417
#include "lldb/Target/Thread.h"
1518
#include "lldb/Utility/ArchSpec.h"
19+
#include "lldb/Utility/LLDBLog.h"
20+
#include "lldb/Utility/Log.h"
1621

1722
using namespace lldb_private;
1823
using namespace lldb;
@@ -150,3 +155,118 @@ addr_t ArchitectureArm::GetOpcodeLoadAddress(addr_t opcode_addr,
150155
}
151156
return opcode_addr & ~(1ull);
152157
}
158+
159+
// The ARM M-Profile Armv7-M Architecture Reference Manual
160+
// "Exception return behavior" describes how the processor
161+
// saves registers to the stack, decrements the stack pointer,
162+
// puts a special value in $lr, and then calls a registered
163+
// exception handler routine.
164+
//
165+
// Detect that special value in $lr, and if present, add
166+
// unwind rules for the registers that were saved above this
167+
// stack frame's CFA. Overwrite any register locations that
168+
// the current_unwindplan has for these registers; they are
169+
// not correct when we're invoked this way.
170+
UnwindPlanSP ArchitectureArm::GetArchitectureUnwindPlan(
171+
Thread &thread, addr_t callers_return_address, addr_t cfa,
172+
std::shared_ptr<const UnwindPlan> current_unwindplan) {
173+
174+
ProcessSP process_sp = thread.GetProcess();
175+
if (!process_sp)
176+
return {};
177+
178+
const ArchSpec arch = process_sp->GetTarget().GetArchitecture();
179+
if (!arch.GetTriple().isArmMClass() || arch.GetAddressByteSize() != 4)
180+
return {};
181+
182+
if (callers_return_address != 0xFFFFFFF1 &&
183+
callers_return_address != 0xFFFFFFF9 &&
184+
callers_return_address != 0xFFFFFFFD &&
185+
callers_return_address != 0xFFFFFFE1 &&
186+
callers_return_address != 0xFFFFFFE9 &&
187+
callers_return_address != 0xFFFFFFED)
188+
return {};
189+
190+
const RegisterKind plan_regkind = current_unwindplan->GetRegisterKind();
191+
UnwindPlanSP new_plan = std::make_shared<UnwindPlan>(plan_regkind);
192+
new_plan->SetSourceName("Arm Cortex-M exception return UnwindPlan");
193+
new_plan->SetSourcedFromCompiler(eLazyBoolNo);
194+
new_plan->SetUnwindPlanValidAtAllInstructions(eLazyBoolYes);
195+
new_plan->SetUnwindPlanForSignalTrap(eLazyBoolYes);
196+
197+
// bit 4 will be 1 if only the general purpose registers were saved.
198+
// bit 4 will be 0 if the GPRs + floating point registers were saved.
199+
const bool fp_regs_saved = (callers_return_address & 0x10) == 0;
200+
201+
int stored_regs_size = 0x20;
202+
if (fp_regs_saved)
203+
stored_regs_size = 0x68;
204+
205+
uint32_t gpr_regs[] = {dwarf_r0, dwarf_r1, dwarf_r2, dwarf_r3,
206+
dwarf_r12, dwarf_lr, dwarf_pc, dwarf_cpsr};
207+
const int gpr_reg_count = sizeof(gpr_regs) / sizeof(uint32_t);
208+
uint32_t fpr_regs[] = {dwarf_s0, dwarf_s1, dwarf_s2, dwarf_s3,
209+
dwarf_s4, dwarf_s5, dwarf_s6, dwarf_s7,
210+
dwarf_s8, dwarf_s9, dwarf_s10, dwarf_s11,
211+
dwarf_s12, dwarf_s13, dwarf_s14, dwarf_s15};
212+
const int fpr_reg_count = sizeof(fpr_regs) / sizeof(uint32_t);
213+
214+
RegisterContextSP reg_ctx_sp = thread.GetRegisterContext();
215+
std::vector<uint32_t> saved_regs;
216+
for (int i = 0; i < gpr_reg_count; i++) {
217+
uint32_t regno = gpr_regs[i];
218+
reg_ctx_sp->ConvertBetweenRegisterKinds(eRegisterKindDWARF, gpr_regs[i],
219+
plan_regkind, regno);
220+
saved_regs.push_back(regno);
221+
}
222+
if (fp_regs_saved) {
223+
for (int i = 0; i < fpr_reg_count; i++) {
224+
uint32_t regno = fpr_regs[i];
225+
reg_ctx_sp->ConvertBetweenRegisterKinds(eRegisterKindDWARF, fpr_regs[i],
226+
plan_regkind, regno);
227+
saved_regs.push_back(regno);
228+
}
229+
}
230+
231+
// PSR bit 9 indicates that the stack pointer was unaligned (to
232+
// an 8-byte alignment) when the exception happened, and we must
233+
// account for that when restoring the excepted stack pointer value.
234+
Status error;
235+
uint32_t callers_xPSR =
236+
process_sp->ReadUnsignedIntegerFromMemory(cfa + 0x28, 4, 0, error);
237+
const bool align_stack = callers_xPSR & (1 << 9U);
238+
uint32_t callers_sp = cfa + stored_regs_size;
239+
if (align_stack)
240+
callers_sp |= 4;
241+
242+
Log *log = GetLog(LLDBLog::Unwind);
243+
LLDB_LOGF(log,
244+
"ArchitectureArm::GetArchitectureUnwindPlan found caller return "
245+
"addr of 0x%" PRIx64 ", for frame with CFA 0x%" PRIx64
246+
", fp_regs_saved %d, stored_regs_size 0x%x, align stack %d",
247+
callers_return_address, cfa, fp_regs_saved, stored_regs_size,
248+
align_stack);
249+
250+
uint32_t sp_regnum = dwarf_sp;
251+
reg_ctx_sp->ConvertBetweenRegisterKinds(eRegisterKindDWARF, dwarf_sp,
252+
plan_regkind, sp_regnum);
253+
254+
const int row_count = current_unwindplan->GetRowCount();
255+
for (int i = 0; i < row_count; i++) {
256+
UnwindPlan::Row row = *current_unwindplan->GetRowAtIndex(i);
257+
uint32_t offset = 0;
258+
const size_t saved_reg_count = saved_regs.size();
259+
for (size_t j = 0; j < saved_reg_count; j++) {
260+
// The locations could be set with
261+
// SetRegisterLocationToIsConstant(regno, cfa+offset)
262+
// expressing it in terms of CFA addr+offset - this UnwindPlan
263+
// is only used once, with this specific CFA. I'm not sure
264+
// which will be clearer for someone reading the unwind log.
265+
row.SetRegisterLocationToAtCFAPlusOffset(saved_regs[j], offset, true);
266+
offset += 4;
267+
}
268+
row.SetRegisterLocationToIsCFAPlusOffset(sp_regnum, callers_sp - cfa, true);
269+
new_plan->AppendRow(row);
270+
}
271+
return new_plan;
272+
}

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

Lines changed: 6 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,11 @@ 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::addr_t callers_return_address,
35+
lldb::addr_t cfa,
36+
std::shared_ptr<const UnwindPlan> current_unwindplan) override;
37+
3238
private:
3339
static std::unique_ptr<Architecture> Create(const ArchSpec &arch);
3440
ArchitectureArm() = default;

lldb/source/Plugins/ObjectFile/JSON/ObjectFileJSON.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "lldb/Core/PluginManager.h"
1313
#include "lldb/Core/Section.h"
1414
#include "lldb/Symbol/Symbol.h"
15+
#include "lldb/Target/Target.h"
1516
#include "lldb/Utility/LLDBLog.h"
1617
#include "lldb/Utility/Log.h"
1718
#include "llvm/ADT/DenseSet.h"
@@ -233,6 +234,41 @@ void ObjectFileJSON::CreateSections(SectionList &unified_section_list) {
233234
}
234235
}
235236

237+
bool ObjectFileJSON::SetLoadAddress(Target &target, lldb::addr_t value,
238+
bool value_is_offset) {
239+
Log *log(GetLog(LLDBLog::DynamicLoader));
240+
if (!m_sections_up)
241+
return true;
242+
243+
const bool warn_multiple = true;
244+
245+
addr_t slide = value;
246+
if (!value_is_offset) {
247+
addr_t lowest_addr = LLDB_INVALID_ADDRESS;
248+
for (const SectionSP &section_sp : *m_sections_up) {
249+
addr_t section_load_addr = section_sp->GetFileAddress();
250+
lowest_addr = std::min(lowest_addr, section_load_addr);
251+
}
252+
if (lowest_addr == LLDB_INVALID_ADDRESS)
253+
return false;
254+
slide = value - lowest_addr;
255+
}
256+
257+
// Apply slide to each section's file address.
258+
for (const SectionSP &section_sp : *m_sections_up) {
259+
addr_t section_load_addr = section_sp->GetFileAddress();
260+
if (section_load_addr != LLDB_INVALID_ADDRESS) {
261+
LLDB_LOGF(
262+
log,
263+
"ObjectFileJSON::SetLoadAddress section %s to load addr 0x%" PRIx64,
264+
section_sp->GetName().AsCString(), section_load_addr + slide);
265+
target.SetSectionLoadAddress(section_sp, section_load_addr + slide,
266+
warn_multiple);
267+
}
268+
}
269+
return true;
270+
}
271+
236272
bool ObjectFileJSON::MagicBytesMatch(DataBufferSP data_sp,
237273
lldb::addr_t data_offset,
238274
lldb::addr_t data_length) {

lldb/source/Plugins/ObjectFile/JSON/ObjectFileJSON.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ class ObjectFileJSON : public ObjectFile {
8686

8787
Strata CalculateStrata() override { return eStrataUser; }
8888

89+
bool SetLoadAddress(Target &target, lldb::addr_t value,
90+
bool value_is_offset) override;
91+
8992
static bool MagicBytesMatch(lldb::DataBufferSP data_sp, lldb::addr_t offset,
9093
lldb::addr_t length);
9194

lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,23 @@ Status ProcessMachCore::DoGetMemoryRegionInfo(addr_t load_addr,
799799
region_info.SetMapped(MemoryRegionInfo::eNo);
800800
}
801801
return Status();
802+
} else {
803+
// The corefile has no LC_SEGMENT at this virtual address,
804+
// but see if there is a binary whose Section has been
805+
// loaded at that address in the current Target.
806+
Address addr;
807+
if (GetTarget().ResolveLoadAddress(load_addr, addr)) {
808+
SectionSP section_sp(addr.GetSection());
809+
if (section_sp) {
810+
region_info.GetRange().SetRangeBase(
811+
section_sp->GetLoadBaseAddress(&GetTarget()));
812+
region_info.GetRange().SetByteSize(section_sp->GetByteSize());
813+
if (region_info.GetRange().Contains(load_addr)) {
814+
region_info.SetLLDBPermissions(section_sp->GetPermissions());
815+
return Status();
816+
}
817+
}
818+
}
802819
}
803820

804821
region_info.GetRange().SetRangeBase(load_addr);

lldb/source/Target/RegisterContextUnwind.cpp

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,9 @@ void RegisterContextUnwind::InitializeZerothFrame() {
293293
return;
294294
}
295295

296+
// Give the Architecture a chance to replace the UnwindPlan.
297+
AdoptArchitectureUnwindPlan();
298+
296299
UnwindLogMsg("initialized frame current pc is 0x%" PRIx64 " cfa is 0x%" PRIx64
297300
" afa is 0x%" PRIx64 " using %s UnwindPlan",
298301
(uint64_t)m_current_pc.GetLoadAddress(exe_ctx.GetTargetPtr()),
@@ -482,6 +485,9 @@ void RegisterContextUnwind::InitializeNonZerothFrame() {
482485
}
483486
}
484487

488+
// Give the Architecture a chance to replace the UnwindPlan.
489+
AdoptArchitectureUnwindPlan();
490+
485491
UnwindLogMsg("initialized frame cfa is 0x%" PRIx64 " afa is 0x%" PRIx64,
486492
(uint64_t)m_cfa, (uint64_t)m_afa);
487493
return;
@@ -686,6 +692,9 @@ void RegisterContextUnwind::InitializeNonZerothFrame() {
686692
}
687693
}
688694

695+
// Give the Architecture a chance to replace the UnwindPlan.
696+
AdoptArchitectureUnwindPlan();
697+
689698
UnwindLogMsg("initialized frame current pc is 0x%" PRIx64
690699
" cfa is 0x%" PRIx64 " afa is 0x%" PRIx64,
691700
(uint64_t)m_current_pc.GetLoadAddress(exe_ctx.GetTargetPtr()),
@@ -1717,6 +1726,78 @@ RegisterContextUnwind::SavedLocationForRegister(
17171726
return UnwindLLDB::RegisterSearchResult::eRegisterNotFound;
17181727
}
17191728

1729+
UnwindPlanSP RegisterContextUnwind::AdoptArchitectureUnwindPlan() {
1730+
if (!m_full_unwind_plan_sp)
1731+
return {};
1732+
ProcessSP process_sp = m_thread.GetProcess();
1733+
if (!process_sp)
1734+
return {};
1735+
1736+
UnwindPlanSP arch_override_plan_sp;
1737+
1738+
RegisterNumber ra_regnum(m_thread, eRegisterKindGeneric,
1739+
LLDB_REGNUM_GENERIC_RA);
1740+
uint32_t ra_regnum_lldb = ra_regnum.GetAsKind(eRegisterKindLLDB);
1741+
// The only Architecture UnwindPlan today requires the value of
1742+
// the return address register.
1743+
if (ra_regnum_lldb == LLDB_INVALID_REGNUM)
1744+
return {};
1745+
1746+
UnwindLLDB::ConcreteRegisterLocation regloc = {};
1747+
bool got_concrete_location = false;
1748+
if (SavedLocationForRegister(ra_regnum_lldb, regloc) ==
1749+
UnwindLLDB::RegisterSearchResult::eRegisterFound) {
1750+
got_concrete_location = true;
1751+
} else {
1752+
RegisterNumber pc_regnum(m_thread, eRegisterKindGeneric,
1753+
LLDB_REGNUM_GENERIC_PC);
1754+
uint32_t pc_regnum_lldb = pc_regnum.GetAsKind(eRegisterKindLLDB);
1755+
if (SavedLocationForRegister(pc_regnum_lldb, regloc) ==
1756+
UnwindLLDB::RegisterSearchResult::eRegisterFound)
1757+
got_concrete_location = true;
1758+
}
1759+
1760+
if (got_concrete_location) {
1761+
const RegisterInfo *reg_info = GetRegisterInfoAtIndex(ra_regnum_lldb);
1762+
if (reg_info) {
1763+
RegisterValue reg_value;
1764+
if (ReadRegisterValueFromRegisterLocation(regloc, reg_info, reg_value)) {
1765+
addr_t ra;
1766+
if (process_sp->GetTarget().GetArchitecture().GetAddressByteSize() == 4)
1767+
ra = reg_value.GetAsUInt32();
1768+
else
1769+
ra = reg_value.GetAsUInt64();
1770+
if (Architecture *arch =
1771+
process_sp->GetTarget().GetArchitecturePlugin())
1772+
arch_override_plan_sp = arch->GetArchitectureUnwindPlan(
1773+
m_thread, ra, m_cfa, m_full_unwind_plan_sp);
1774+
}
1775+
}
1776+
}
1777+
if (arch_override_plan_sp) {
1778+
m_full_unwind_plan_sp = arch_override_plan_sp;
1779+
PropagateTrapHandlerFlagFromUnwindPlan(m_full_unwind_plan_sp);
1780+
m_registers.clear();
1781+
Log *log = GetLog(LLDBLog::Unwind);
1782+
if (log) {
1783+
UnwindLogMsg(
1784+
"Replacing Full Unwindplan with Architecture UnwindPlan, '%s'",
1785+
m_full_unwind_plan_sp->GetSourceName().AsCString());
1786+
const UnwindPlan::Row *active_row =
1787+
m_full_unwind_plan_sp->GetRowForFunctionOffset(m_current_offset);
1788+
if (active_row) {
1789+
StreamString active_row_strm;
1790+
active_row->Dump(active_row_strm, m_full_unwind_plan_sp.get(),
1791+
&m_thread,
1792+
m_start_pc.GetLoadAddress(&process_sp->GetTarget()));
1793+
UnwindLogMsg("%s", active_row_strm.GetData());
1794+
}
1795+
}
1796+
}
1797+
1798+
return {};
1799+
}
1800+
17201801
// TryFallbackUnwindPlan() -- this method is a little tricky.
17211802
//
17221803
// When this is called, the frame above -- the caller frame, the "previous"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include Makefile.rules

0 commit comments

Comments
 (0)