Skip to content

Commit d865f32

Browse files
authored
[lldb] Parse DWARF CFI for discontinuous functions (#137006)
This patch uses the previously build infrastructure to parse multiple FDE entries into a single unwind plan. There is one catch though: we parse only one FDE entry per unwind range. This is not fully correct because lldb coalesces adjecant address ranges, which means that something that originally looked like two separate address ranges (and two FDE entries) may get merged into one because if the linker decides to put the two ranges next to each other. In this case, we will ignore the second FDE entry. It would be more correct to try to parse another entry when the one we found turns out to be short, but I'm not doing this (yet), because: - this is how we've done things so far (although, monolithic functions are unlikely to have more than one FDE entry) - in cases where we don't have debug info or (full) symbol tables, we can end up with "symbols" which appear to span many megabytes (potentially, the whole module). If we tried to fill short FDE entries, we could end up parsing the entire eh_frame section in a single go. In a way, this would be more correct, but it would also probably be very slow. I haven't quite decided what to do about this case yet, though it's not particularly likely to happen in the "production" cases as typically the functions are split into two parts (hot/cold) instead of one part per basic block.
1 parent 7c5f5f3 commit d865f32

File tree

8 files changed

+131
-114
lines changed

8 files changed

+131
-114
lines changed

lldb/include/lldb/Symbol/DWARFCallFrameInfo.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,15 @@ class DWARFCallFrameInfo {
4747
/// Return an UnwindPlan based on the call frame information encoded in the
4848
/// FDE of this DWARFCallFrameInfo section. The returned plan will be valid
4949
/// (at least) for the given address.
50-
bool GetUnwindPlan(const Address &addr, UnwindPlan &unwind_plan);
50+
std::unique_ptr<UnwindPlan> GetUnwindPlan(const Address &addr);
5151

5252
/// Return an UnwindPlan based on the call frame information encoded in the
5353
/// FDE of this DWARFCallFrameInfo section. The returned plan will be valid
54-
/// (at least) for some address in the given range.
55-
bool GetUnwindPlan(const AddressRange &range, UnwindPlan &unwind_plan);
54+
/// (at least) for some address in the given ranges. If no unwind information
55+
/// is found, nullptr is returned. \a addr represents the entry point of the
56+
/// function. It corresponds to the offset zero in the returned UnwindPlan.
57+
std::unique_ptr<UnwindPlan> GetUnwindPlan(llvm::ArrayRef<AddressRange> ranges,
58+
const Address &addr);
5659

5760
typedef RangeVector<lldb::addr_t, uint32_t> FunctionAddressAndSizeVector;
5861

lldb/source/Symbol/DWARFCallFrameInfo.cpp

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -151,53 +151,57 @@ DWARFCallFrameInfo::DWARFCallFrameInfo(ObjectFile &objfile,
151151
SectionSP &section_sp, Type type)
152152
: m_objfile(objfile), m_section_sp(section_sp), m_type(type) {}
153153

154-
bool DWARFCallFrameInfo::GetUnwindPlan(const Address &addr,
155-
UnwindPlan &unwind_plan) {
156-
return GetUnwindPlan(AddressRange(addr, 1), unwind_plan);
154+
std::unique_ptr<UnwindPlan>
155+
DWARFCallFrameInfo::GetUnwindPlan(const Address &addr) {
156+
return GetUnwindPlan({AddressRange(addr, 1)}, addr);
157157
}
158158

159-
bool DWARFCallFrameInfo::GetUnwindPlan(const AddressRange &range,
160-
UnwindPlan &unwind_plan) {
159+
std::unique_ptr<UnwindPlan>
160+
DWARFCallFrameInfo::GetUnwindPlan(llvm::ArrayRef<AddressRange> ranges,
161+
const Address &addr) {
161162
FDEEntryMap::Entry fde_entry;
162-
Address addr = range.GetBaseAddress();
163163

164164
// Make sure that the Address we're searching for is the same object file as
165165
// this DWARFCallFrameInfo, we only store File offsets in m_fde_index.
166166
ModuleSP module_sp = addr.GetModule();
167167
if (module_sp.get() == nullptr || module_sp->GetObjectFile() == nullptr ||
168168
module_sp->GetObjectFile() != &m_objfile)
169-
return false;
169+
return nullptr;
170170

171-
std::optional<FDEEntryMap::Entry> entry = GetFirstFDEEntryInRange(range);
172-
if (!entry)
173-
return false;
171+
std::vector<AddressRange> valid_ranges;
174172

175-
std::optional<FDE> fde = ParseFDE(entry->data, addr);
176-
if (!fde)
177-
return false;
178-
179-
unwind_plan.SetSourceName(m_type == EH ? "eh_frame CFI" : "DWARF CFI");
173+
auto result = std::make_unique<UnwindPlan>(GetRegisterKind());
174+
result->SetSourceName(m_type == EH ? "eh_frame CFI" : "DWARF CFI");
180175
// In theory the debug_frame info should be valid at all call sites
181176
// ("asynchronous unwind info" as it is sometimes called) but in practice
182177
// gcc et al all emit call frame info for the prologue and call sites, but
183178
// not for the epilogue or all the other locations during the function
184179
// reliably.
185-
unwind_plan.SetUnwindPlanValidAtAllInstructions(eLazyBoolNo);
186-
unwind_plan.SetSourcedFromCompiler(eLazyBoolYes);
187-
unwind_plan.SetRegisterKind(GetRegisterKind());
188-
189-
unwind_plan.SetPlanValidAddressRanges({fde->range});
190-
unwind_plan.SetUnwindPlanForSignalTrap(fde->for_signal_trap ? eLazyBoolYes
191-
: eLazyBoolNo);
192-
unwind_plan.SetReturnAddressRegister(fde->return_addr_reg_num);
193-
int64_t slide =
194-
fde->range.GetBaseAddress().GetFileAddress() - addr.GetFileAddress();
195-
for (UnwindPlan::Row &row : fde->rows) {
196-
row.SlideOffset(slide);
197-
unwind_plan.AppendRow(std::move(row));
180+
result->SetUnwindPlanValidAtAllInstructions(eLazyBoolNo);
181+
result->SetSourcedFromCompiler(eLazyBoolYes);
182+
result->SetUnwindPlanForSignalTrap(eLazyBoolNo);
183+
for (const AddressRange &range : ranges) {
184+
std::optional<FDEEntryMap::Entry> entry = GetFirstFDEEntryInRange(range);
185+
if (!entry)
186+
continue;
187+
std::optional<FDE> fde = ParseFDE(entry->data, addr);
188+
if (!fde)
189+
continue;
190+
int64_t slide =
191+
fde->range.GetBaseAddress().GetFileAddress() - addr.GetFileAddress();
192+
valid_ranges.push_back(std::move(fde->range));
193+
if (fde->for_signal_trap)
194+
result->SetUnwindPlanForSignalTrap(eLazyBoolYes);
195+
result->SetReturnAddressRegister(fde->return_addr_reg_num);
196+
for (UnwindPlan::Row &row : fde->rows) {
197+
row.SlideOffset(slide);
198+
result->AppendRow(std::move(row));
199+
}
198200
}
199-
200-
return true;
201+
result->SetPlanValidAddressRanges(std::move(valid_ranges));
202+
if (result->GetRowCount() == 0)
203+
return nullptr;
204+
return result;
201205
}
202206

203207
bool DWARFCallFrameInfo::GetAddressRange(Address addr, AddressRange &range) {

lldb/source/Symbol/FuncUnwinders.cpp

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,9 @@ FuncUnwinders::GetEHFrameUnwindPlan(Target &target) {
149149
return m_unwind_plan_eh_frame_sp;
150150

151151
m_tried_unwind_plan_eh_frame = true;
152-
if (m_range.GetBaseAddress().IsValid()) {
153-
DWARFCallFrameInfo *eh_frame = m_unwind_table.GetEHFrameInfo();
154-
if (eh_frame) {
155-
auto plan_sp = std::make_shared<UnwindPlan>(lldb::eRegisterKindGeneric);
156-
if (eh_frame->GetUnwindPlan(m_range, *plan_sp))
157-
m_unwind_plan_eh_frame_sp = std::move(plan_sp);
158-
}
152+
if (m_addr.IsValid()) {
153+
if (DWARFCallFrameInfo *eh_frame = m_unwind_table.GetEHFrameInfo())
154+
m_unwind_plan_eh_frame_sp = eh_frame->GetUnwindPlan(m_ranges, m_addr);
159155
}
160156
return m_unwind_plan_eh_frame_sp;
161157
}
@@ -167,13 +163,10 @@ FuncUnwinders::GetDebugFrameUnwindPlan(Target &target) {
167163
return m_unwind_plan_debug_frame_sp;
168164

169165
m_tried_unwind_plan_debug_frame = true;
170-
if (m_range.GetBaseAddress().IsValid()) {
171-
DWARFCallFrameInfo *debug_frame = m_unwind_table.GetDebugFrameInfo();
172-
if (debug_frame) {
173-
auto plan_sp = std::make_shared<UnwindPlan>(lldb::eRegisterKindGeneric);
174-
if (debug_frame->GetUnwindPlan(m_range, *plan_sp))
175-
m_unwind_plan_debug_frame_sp = std::move(plan_sp);
176-
}
166+
if (!m_ranges.empty()) {
167+
if (DWARFCallFrameInfo *debug_frame = m_unwind_table.GetDebugFrameInfo())
168+
m_unwind_plan_debug_frame_sp =
169+
debug_frame->GetUnwindPlan(m_ranges, m_addr);
177170
}
178171
return m_unwind_plan_debug_frame_sp;
179172
}

lldb/source/Symbol/UnwindTable.cpp

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,13 @@ AddressRanges UnwindTable::GetAddressRanges(const Address &addr,
122122
return {};
123123
}
124124

125+
static Address GetFunctionOrSymbolAddress(const Address &addr,
126+
const SymbolContext &sc) {
127+
if (Address result = sc.GetFunctionOrSymbolAddress(); result.IsValid())
128+
return result;
129+
return addr;
130+
}
131+
125132
FuncUnwindersSP
126133
UnwindTable::GetFuncUnwindersContainingAddress(const Address &addr,
127134
const SymbolContext &sc) {
@@ -131,25 +138,20 @@ UnwindTable::GetFuncUnwindersContainingAddress(const Address &addr,
131138

132139
// There is an UnwindTable per object file, so we can safely use file handles
133140
addr_t file_addr = addr.GetFileAddress();
134-
iterator end = m_unwinds.end();
135-
iterator insert_pos = end;
136-
if (!m_unwinds.empty()) {
137-
insert_pos = m_unwinds.lower_bound(file_addr);
138-
iterator pos = insert_pos;
139-
if ((pos == m_unwinds.end()) ||
140-
(pos != m_unwinds.begin() &&
141-
pos->second->GetFunctionStartAddress() != addr))
142-
--pos;
143-
141+
iterator insert_pos = m_unwinds.upper_bound(file_addr);
142+
if (insert_pos != m_unwinds.begin()) {
143+
auto pos = std::prev(insert_pos);
144144
if (pos->second->ContainsAddress(addr))
145145
return pos->second;
146146
}
147147

148+
Address start_addr = GetFunctionOrSymbolAddress(addr, sc);
148149
AddressRanges ranges = GetAddressRanges(addr, sc);
149150
if (ranges.empty())
150151
return nullptr;
151152

152-
auto func_unwinder_sp = std::make_shared<FuncUnwinders>(*this, addr, ranges);
153+
auto func_unwinder_sp =
154+
std::make_shared<FuncUnwinders>(*this, start_addr, ranges);
153155
for (const AddressRange &range : ranges)
154156
m_unwinds.emplace_hint(insert_pos, range.GetBaseAddress().GetFileAddress(),
155157
func_unwinder_sp);
@@ -164,11 +166,12 @@ FuncUnwindersSP UnwindTable::GetUncachedFuncUnwindersContainingAddress(
164166
const Address &addr, const SymbolContext &sc) {
165167
Initialize();
166168

169+
Address start_addr = GetFunctionOrSymbolAddress(addr, sc);
167170
AddressRanges ranges = GetAddressRanges(addr, sc);
168171
if (ranges.empty())
169172
return nullptr;
170173

171-
return std::make_shared<FuncUnwinders>(*this, addr, std::move(ranges));
174+
return std::make_shared<FuncUnwinders>(*this, start_addr, std::move(ranges));
172175
}
173176

174177
void UnwindTable::Dump(Stream &s) {

lldb/source/Target/RegisterContextUnwind.cpp

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -868,13 +868,11 @@ RegisterContextUnwind::GetFullUnwindPlanForFrame() {
868868

869869
// Even with -fomit-frame-pointer, we can try eh_frame to get back on
870870
// track.
871-
DWARFCallFrameInfo *eh_frame =
872-
pc_module_sp->GetUnwindTable().GetEHFrameInfo();
873-
if (eh_frame) {
874-
auto unwind_plan_sp =
875-
std::make_shared<UnwindPlan>(lldb::eRegisterKindGeneric);
876-
if (eh_frame->GetUnwindPlan(m_current_pc, *unwind_plan_sp))
877-
return unwind_plan_sp;
871+
if (DWARFCallFrameInfo *eh_frame =
872+
pc_module_sp->GetUnwindTable().GetEHFrameInfo()) {
873+
if (std::unique_ptr<UnwindPlan> plan_up =
874+
eh_frame->GetUnwindPlan(m_current_pc))
875+
return plan_up;
878876
}
879877

880878
ArmUnwindInfo *arm_exidx =
@@ -1345,9 +1343,9 @@ RegisterContextUnwind::SavedLocationForRegister(
13451343
// value instead of the Return Address register.
13461344
// If $pc is not available, fall back to the RA reg.
13471345
UnwindPlan::Row::AbstractRegisterLocation scratch;
1348-
if (m_frame_type == eTrapHandlerFrame &&
1349-
active_row->GetRegisterInfo
1350-
(pc_regnum.GetAsKind (unwindplan_registerkind), scratch)) {
1346+
if (m_frame_type == eTrapHandlerFrame && active_row &&
1347+
active_row->GetRegisterInfo(
1348+
pc_regnum.GetAsKind(unwindplan_registerkind), scratch)) {
13511349
UnwindLogMsg("Providing pc register instead of rewriting to "
13521350
"RA reg because this is a trap handler and there is "
13531351
"a location for the saved pc register value.");
@@ -1377,7 +1375,7 @@ RegisterContextUnwind::SavedLocationForRegister(
13771375
}
13781376
}
13791377

1380-
if (regnum.IsValid() &&
1378+
if (regnum.IsValid() && active_row &&
13811379
active_row->GetRegisterInfo(regnum.GetAsKind(unwindplan_registerkind),
13821380
unwindplan_regloc)) {
13831381
have_unwindplan_regloc = true;

lldb/test/Shell/Unwind/Inputs/basic-block-sections-with-dwarf.s

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
# int bar() { return foo(0); }
55
# int foo(int flag) { return flag ? bar() : baz(); }
66
# int main() { return foo(1); }
7-
# The function bar has been placed "in the middle" of foo.
7+
# The function bar has been placed "in the middle" of foo. The functions are not
8+
# using the frame pointer register and the are deliberately adjusting the stack
9+
# pointer to test that we're using the correct unwind row.
810

911
.text
1012

@@ -20,35 +22,36 @@ baz:
2022
.type foo,@function
2123
foo:
2224
.cfi_startproc
23-
pushq %rbp
25+
pushq %rbx
2426
.cfi_def_cfa_offset 16
25-
.cfi_offset %rbp, -16
26-
movq %rsp, %rbp
27-
.cfi_def_cfa_register %rbp
28-
subq $16, %rsp
29-
movl %edi, -8(%rbp)
30-
cmpl $0, -8(%rbp)
27+
.cfi_offset %rbx, -16
28+
movl %edi, %ebx
29+
cmpl $0, %ebx
3130
je foo.__part.2
3231
jmp foo.__part.1
3332
.cfi_endproc
3433
.Lfoo_end:
3534
.size foo, .Lfoo_end-foo
3635

36+
# NB: Deliberately inserting padding to separate the two parts of the function
37+
# as we're currently only parsing a single FDE entry from a (coalesced) address
38+
# range.
39+
nop
40+
3741
foo.__part.1:
3842
.cfi_startproc
39-
.cfi_def_cfa %rbp, 16
40-
.cfi_offset %rbp, -16
43+
.cfi_def_cfa_offset 16
44+
.cfi_offset %rbx, -16
45+
subq $16, %rsp
46+
.cfi_def_cfa_offset 32
4147
callq bar
42-
movl %eax, -4(%rbp)
4348
jmp foo.__part.3
4449
.Lfoo.__part.1_end:
4550
.size foo.__part.1, .Lfoo.__part.1_end-foo.__part.1
4651
.cfi_endproc
4752

4853
bar:
4954
.cfi_startproc
50-
# NB: Decrease the stack pointer to make the unwind info for this function
51-
# different from the surrounding foo function.
5255
subq $24, %rsp
5356
.cfi_def_cfa_offset 32
5457
xorl %edi, %edi
@@ -62,22 +65,26 @@ bar:
6265

6366
foo.__part.2:
6467
.cfi_startproc
65-
.cfi_def_cfa %rbp, 16
66-
.cfi_offset %rbp, -16
68+
.cfi_def_cfa_offset 16
69+
.cfi_offset %rbx, -16
70+
subq $16, %rsp
71+
.cfi_def_cfa_offset 32
6772
callq baz
68-
movl %eax, -4(%rbp)
6973
jmp foo.__part.3
7074
.Lfoo.__part.2_end:
7175
.size foo.__part.2, .Lfoo.__part.2_end-foo.__part.2
7276
.cfi_endproc
7377

78+
# NB: Deliberately inserting padding to separate the two parts of the function
79+
# as we're currently only parsing a single FDE entry from a (coalesced) address
80+
# range.
81+
nop
82+
7483
foo.__part.3:
7584
.cfi_startproc
76-
.cfi_def_cfa %rbp, 16
77-
.cfi_offset %rbp, -16
78-
movl -4(%rbp), %eax
79-
addq $16, %rsp
80-
popq %rbp
85+
.cfi_def_cfa_offset 32
86+
.cfi_offset %rbx, -16
87+
addq $24, %rsp
8188
.cfi_def_cfa %rsp, 8
8289
retq
8390
.Lfoo.__part.3_end:
@@ -186,9 +193,8 @@ main:
186193
.byte 86
187194
.asciz "foo" # DW_AT_name
188195
.byte 4 # Abbrev [4] DW_TAG_formal_parameter
189-
.byte 2 # DW_AT_location
190-
.byte 145
191-
.byte 120
196+
.byte 1 # DW_AT_location
197+
.byte 0x53 # DW_OP_reg3
192198
.asciz "flag" # DW_AT_name
193199
.long .Lint-.Lcu_begin0 # DW_AT_type
194200
.byte 0 # End Of Children Mark

lldb/test/Shell/Unwind/basic-block-sections-with-dwarf-static.test

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,32 @@
1717
image show-unwind --cached true -n foo
1818
# CHECK: UNWIND PLANS for {{.*}}`foo
1919
#
20-
# CHECK: Assembly language inspection UnwindPlan:
21-
# CHECK-NEXT: This UnwindPlan originally sourced from assembly insn profiling
22-
# CHECK-NEXT: This UnwindPlan is sourced from the compiler: no.
23-
# CHECK-NEXT: This UnwindPlan is valid at all instruction locations: yes.
20+
# CHECK: eh_frame UnwindPlan:
21+
# CHECK-NEXT: This UnwindPlan originally sourced from eh_frame CFI
22+
# CHECK-NEXT: This UnwindPlan is sourced from the compiler: yes.
23+
# CHECK-NEXT: This UnwindPlan is valid at all instruction locations: no.
2424
# CHECK-NEXT: This UnwindPlan is for a trap handler function: no.
25-
# TODO: This address range isn't correct right now. We're just checking that
26-
# it's a different range from the one in the next query.
27-
# CHECK-NEXT: Address range of this UnwindPlan: [{{.*}}.text + 6-0x0000000000000046)
25+
# CHECK-NEXT: Address range of this UnwindPlan: [{{.*}}.text + 6-0x0000000000000010)[{{.*}}.text + 17-0x000000000000001c)[{{.*}}.text + 44-0x0000000000000037)[{{.*}}.text + 56-0x000000000000003d)
26+
# CHECK-NEXT: row[0]: 0: CFA=rsp +8 => rip=[CFA-8]
27+
# CHECK-NEXT: row[1]: 1: CFA=rsp+16 => rbx=[CFA-16] rip=[CFA-8]
28+
# CHECK-NEXT: row[2]: 11: CFA=rsp+16 => rbx=[CFA-16] rip=[CFA-8]
29+
# CHECK-NEXT: row[3]: 15: CFA=rsp+32 => rbx=[CFA-16] rip=[CFA-8]
30+
# CHECK-NEXT: row[4]: 38: CFA=rsp+16 => rbx=[CFA-16] rip=[CFA-8]
31+
# CHECK-NEXT: row[5]: 42: CFA=rsp+32 => rbx=[CFA-16] rip=[CFA-8]
32+
# CHECK-NEXT: row[6]: 50: CFA=rsp+32 => rbx=[CFA-16] rip=[CFA-8]
33+
# CHECK-NEXT: row[7]: 54: CFA=rsp +8 => rbx=[CFA-16] rip=[CFA-8]
34+
# CHECK-EMPTY:
2835

2936
image show-unwind --cached true -n bar
3037
# CHECK: UNWIND PLANS for {{.*}}`bar
3138

32-
# CHECK: Assembly language inspection UnwindPlan:
33-
# CHECK-NEXT: This UnwindPlan originally sourced from assembly insn profiling
34-
# CHECK-NEXT: This UnwindPlan is sourced from the compiler: no.
35-
# CHECK-NEXT: This UnwindPlan is valid at all instruction locations: yes.
39+
# CHECK: eh_frame UnwindPlan:
40+
# CHECK-NEXT: This UnwindPlan originally sourced from eh_frame CFI
41+
# CHECK-NEXT: This UnwindPlan is sourced from the compiler: yes.
42+
# CHECK-NEXT: This UnwindPlan is valid at all instruction locations: no.
3643
# CHECK-NEXT: This UnwindPlan is for a trap handler function: no.
37-
# TODO: This address range isn't correct right now. We're just checking that
38-
# it's a different range from the one in the previous query.
39-
# CHECK-NEXT: Address range of this UnwindPlan: [{{.*}}.text + 35-0x0000000000000033)
44+
# CHECK-NEXT: Address range of this UnwindPlan: [{{.*}}.text + 28-0x000000000000002c)
45+
# CHECK-NEXT: row[0]: 0: CFA=rsp +8 => rip=[CFA-8]
46+
# CHECK-NEXT: row[1]: 4: CFA=rsp+32 => rip=[CFA-8]
47+
# CHECK-NEXT: row[2]: 15: CFA=rsp +8 => rip=[CFA-8]
48+
# CHECK-EMPTY:

0 commit comments

Comments
 (0)