Skip to content

Commit fda9035

Browse files
committed
[lldb] Fixing edge cases in "command source"
While looking at how to make Function::GetEndLineSourceInfo (which is only used in "command source") work with discontinuous functions, I realized there are other corner cases that this function doesn't handle. The code assumed that the last line entry in the function will also correspond to the last source line. This is probably true for unoptimized code, but I don't think we can rely on the optimizer to preserve this property. What's worse, the code didn't check that the last line entry belonged to the same file as the first one, so if this line entry was the result of inlining, we could end up using a line from a completely different file. To fix this, I change the algorithm to iterate over all line entries in the function (which belong to the same file) and find the max line number out of those. This way we can naturally handle the discontinuous case as well. This implementations is going to be slower than the previous one, but I don't think that matters, because: - this command is only used rarely, and interactively - we have plenty of other code which iterates through the line table I added some basic tests for the function operation. I don't claim the tests to be comprehensive, or that the function handles all edge cases, but test framework created here could be used for testing other fixes/edge cases as well.
1 parent aebe6c5 commit fda9035

File tree

4 files changed

+304
-34
lines changed

4 files changed

+304
-34
lines changed

lldb/include/lldb/Symbol/Function.h

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "lldb/Expression/DWARFExpressionList.h"
1616
#include "lldb/Symbol/Block.h"
1717
#include "lldb/Utility/UserID.h"
18+
#include "lldb/lldb-forward.h"
1819
#include "llvm/ADT/ArrayRef.h"
1920

2021
#include <mutex>
@@ -460,6 +461,7 @@ class Function : public UserID, public SymbolContextScope {
460461
}
461462

462463
lldb::LanguageType GetLanguage() const;
464+
463465
/// Find the file and line number of the source location of the start of the
464466
/// function. This will use the declaration if present and fall back on the
465467
/// line table if that fails. So there may NOT be a line table entry for
@@ -473,16 +475,9 @@ class Function : public UserID, public SymbolContextScope {
473475
void GetStartLineSourceInfo(lldb::SupportFileSP &source_file_sp,
474476
uint32_t &line_no);
475477

476-
/// Find the file and line number of the source location of the end of the
477-
/// function.
478-
///
479-
///
480-
/// \param[out] source_file
481-
/// The source file.
482-
///
483-
/// \param[out] line_no
484-
/// The line number.
485-
void GetEndLineSourceInfo(FileSpec &source_file, uint32_t &line_no);
478+
using SourceRange = Range<uint32_t, uint32_t>;
479+
/// Find the file and line number range of the function.
480+
llvm::Expected<std::pair<lldb::SupportFileSP, SourceRange>> GetSourceInfo();
486481

487482
/// Get the outgoing call edges from this function, sorted by their return
488483
/// PC addresses (in increasing order).

lldb/source/Commands/CommandObjectSource.cpp

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -784,14 +784,12 @@ class CommandObjectSourceList : public CommandObjectParsed {
784784

785785
if (sc.block == nullptr) {
786786
// Not an inlined function
787-
sc.function->GetStartLineSourceInfo(start_file, start_line);
788-
if (start_line == 0) {
789-
result.AppendErrorWithFormat("Could not find line information for "
790-
"start of function: \"%s\".\n",
791-
source_info.function.GetCString());
792-
return 0;
793-
}
794-
sc.function->GetEndLineSourceInfo(end_file, end_line);
787+
auto expected_info = sc.function->GetSourceInfo();
788+
if (!expected_info)
789+
result.AppendError(llvm::toString(expected_info.takeError()));
790+
start_file = expected_info->first;
791+
start_line = expected_info->second.GetRangeBase();
792+
end_line = expected_info->second.GetRangeEnd();
795793
} else {
796794
// We have an inlined function
797795
start_file = source_info.line_entry.file_sp;

lldb/source/Symbol/Function.cpp

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -320,25 +320,37 @@ void Function::GetStartLineSourceInfo(SupportFileSP &source_file_sp,
320320
}
321321
}
322322

323-
void Function::GetEndLineSourceInfo(FileSpec &source_file, uint32_t &line_no) {
324-
line_no = 0;
325-
source_file.Clear();
326-
327-
// The -1 is kind of cheesy, but I want to get the last line entry for the
328-
// given function, not the first entry of the next.
329-
Address scratch_addr(GetAddressRange().GetBaseAddress());
330-
scratch_addr.SetOffset(scratch_addr.GetOffset() +
331-
GetAddressRange().GetByteSize() - 1);
332-
323+
llvm::Expected<std::pair<SupportFileSP, Function::SourceRange>>
324+
Function::GetSourceInfo() {
325+
SupportFileSP source_file_sp;
326+
uint32_t start_line;
327+
GetStartLineSourceInfo(source_file_sp, start_line);
333328
LineTable *line_table = m_comp_unit->GetLineTable();
334-
if (line_table == nullptr)
335-
return;
329+
if (start_line == 0 || !line_table) {
330+
return llvm::createStringError(llvm::formatv(
331+
"Could not find line information for function \"{0}\".", GetName()));
332+
}
336333

337-
LineEntry line_entry;
338-
if (line_table->FindLineEntryByAddress(scratch_addr, line_entry, nullptr)) {
339-
line_no = line_entry.line;
340-
source_file = line_entry.GetFile();
334+
uint32_t end_line = start_line;
335+
for (const AddressRange &range : GetAddressRanges()) {
336+
LineEntry entry;
337+
uint32_t idx;
338+
if (!line_table->FindLineEntryByAddress(range.GetBaseAddress(), entry,
339+
&idx))
340+
continue;
341+
342+
addr_t end_addr =
343+
range.GetBaseAddress().GetFileAddress() + range.GetByteSize();
344+
while (line_table->GetLineEntryAtIndex(idx++, entry) &&
345+
entry.range.GetBaseAddress().GetFileAddress() < end_addr) {
346+
// Ignore entries belonging to inlined functions or #included files.
347+
if (source_file_sp->Equal(*entry.file_sp,
348+
SupportFile::eEqualFileSpecAndChecksumIfSet))
349+
end_line = std::max(end_line, entry.line);
350+
}
341351
}
352+
return std::make_pair(std::move(source_file_sp),
353+
SourceRange(start_line, end_line - start_line));
342354
}
343355

344356
llvm::ArrayRef<std::unique_ptr<CallEdge>> Function::GetCallEdges() {
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
# REQUIRES: x86
2+
3+
# RUN: split-file %s %t
4+
# RUN: llvm-mc --triple=x86_64-pc-linux -filetype=obj %t/a.s -o %t/a.o
5+
# RUN: %lldb %t/a.o -o "settings set target.source-map . %t" -s %t/commands -o exit | FileCheck %s
6+
7+
#--- commands
8+
# CASE 0: function at the start of the file
9+
source list -n func0
10+
# CHECK-LABEL: source list -n func0
11+
# CHECK-NEXT: File: file0.c
12+
# CHECK-NEXT: 1 content of file0.c:1
13+
# CHECK-NEXT: 2 content of file0.c:2
14+
# CHECK-NEXT: 3 content of file0.c:3
15+
# CHECK-NEXT: 4 content of file0.c:4
16+
# CHECK-NEXT: 5 content of file0.c:5
17+
# CHECK-NEXT: 6 content of file0.c:6
18+
# CHECK-NEXT: 7 content of file0.c:7
19+
# CHECK-NEXT: 8 content of file0.c:8
20+
# CHECK-NEXT: 9 content of file0.c:9
21+
# CHECK-NEXT: 10 content of file0.c:10
22+
23+
# CASE 1: function in the middle of the file
24+
source list -n func1
25+
# CHECK-NEXT: source list -n func1
26+
# CHECK-NEXT: File: file0.c
27+
# CHECK-NEXT: 5 content of file0.c:5
28+
# CHECK-NEXT: 6 content of file0.c:6
29+
# CHECK-NEXT: 7 content of file0.c:7
30+
# CHECK-NEXT: 8 content of file0.c:8
31+
# CHECK-NEXT: 9 content of file0.c:9
32+
# CHECK-NEXT: 10 content of file0.c:10
33+
# CHECK-NEXT: 11 content of file0.c:11
34+
# CHECK-NEXT: 12 content of file0.c:12
35+
# CHECK-NEXT: 13 content of file0.c:13
36+
# CHECK-NEXT: 14 content of file0.c:14
37+
# CHECK-NEXT: 15 content of file0.c:15
38+
# CHECK-NEXT: 16 content of file0.c:16
39+
# CHECK-NEXT: 17 content of file0.c:17
40+
41+
# CASE 2: function at the end of the file
42+
source list -n func2
43+
# CHECK-NEXT: source list -n func2
44+
# CHECK-NEXT: File: file0.c
45+
# CHECK-NEXT: 20 content of file0.c:20
46+
# CHECK-NEXT: 21 content of file0.c:21
47+
# CHECK-NEXT: 22 content of file0.c:22
48+
# CHECK-NEXT: 23 content of file0.c:23
49+
# CHECK-NEXT: 24 content of file0.c:24
50+
# CHECK-NEXT: 25 content of file0.c:25
51+
# CHECK-NEXT: 26 content of file0.c:26
52+
# CHECK-NEXT: 27 content of file0.c:27
53+
# CHECK-NEXT: 28 content of file0.c:28
54+
# CHECK-NEXT: 29 content of file0.c:29
55+
# CHECK-NEXT: 30 content of file0.c:30
56+
57+
# CASE 3: function ends in a different file
58+
source list -n func3
59+
# CHECK-NEXT: source list -n func3
60+
# CHECK-NEXT: File: file0.c
61+
# CHECK-NEXT: 1 content of file0.c:1
62+
# CHECK-NEXT: 2 content of file0.c:2
63+
# CHECK-NEXT: 3 content of file0.c:3
64+
# CHECK-NEXT: 4 content of file0.c:4
65+
# CHECK-NEXT: 5 content of file0.c:5
66+
# CHECK-NEXT: 6 content of file0.c:6
67+
# CHECK-NEXT: 7 content of file0.c:7
68+
# CHECK-NEXT: 8 content of file0.c:8
69+
# CHECK-NEXT: 9 content of file0.c:9
70+
# CHECK-NEXT: 10 content of file0.c:10
71+
72+
# CASE 4: discontinuous function
73+
source list -n func4
74+
# CHECK-NEXT: source list -n func4
75+
# CHECK-NEXT: File: file0.c
76+
# CHECK-NEXT: 1 content of file0.c:1
77+
# CHECK-NEXT: 2 content of file0.c:2
78+
# CHECK-NEXT: 3 content of file0.c:3
79+
# CHECK-NEXT: 4 content of file0.c:4
80+
# CHECK-NEXT: 5 content of file0.c:5
81+
# CHECK-NEXT: 6 content of file0.c:6
82+
# CHECK-NEXT: 7 content of file0.c:7
83+
# CHECK-NEXT: 8 content of file0.c:8
84+
# CHECK-NEXT: 9 content of file0.c:9
85+
# CHECK-NEXT: 10 content of file0.c:10
86+
87+
88+
#--- a.s
89+
.file 0 "." "file0.c"
90+
.file 1 "." "file1.c"
91+
.text
92+
func0:
93+
.loc 0 1
94+
nop
95+
.loc 0 5
96+
nop
97+
.Lfunc0_end:
98+
99+
func1:
100+
.loc 0 10
101+
nop
102+
.loc 0 12
103+
nop
104+
.Lfunc1_end:
105+
106+
func2:
107+
.loc 0 25
108+
nop
109+
.loc 0 30
110+
nop
111+
.Lfunc2_end:
112+
113+
func3:
114+
.loc 0 1
115+
nop
116+
.loc 0 5
117+
nop
118+
.loc 1 5
119+
nop
120+
.Lfunc3_end:
121+
122+
func4.__part.1:
123+
.loc 0 1
124+
nop
125+
.Lfunc4.__part.1_end:
126+
127+
.Lpadding:
128+
nop
129+
130+
func4:
131+
.loc 0 5
132+
nop
133+
.Lfunc4_end:
134+
135+
.Ltext_end:
136+
137+
.section .debug_abbrev,"",@progbits
138+
.byte 1 # Abbreviation Code
139+
.byte 17 # DW_TAG_compile_unit
140+
.byte 1 # DW_CHILDREN_yes
141+
.byte 3 # DW_AT_name
142+
.byte 8 # DW_FORM_string
143+
.byte 37 # DW_AT_producer
144+
.byte 8 # DW_FORM_string
145+
.byte 19 # DW_AT_language
146+
.byte 5 # DW_FORM_data2
147+
.byte 17 # DW_AT_low_pc
148+
.byte 1 # DW_FORM_addr
149+
.byte 18 # DW_AT_high_pc
150+
.byte 1 # DW_FORM_addr
151+
.byte 16 # DW_AT_stmt_list
152+
.byte 23 # DW_FORM_sec_offset
153+
.byte 0 # EOM(1)
154+
.byte 0 # EOM(2)
155+
.byte 2 # Abbreviation Code
156+
.byte 46 # DW_TAG_subprogram
157+
.byte 0 # DW_CHILDREN_no
158+
.byte 17 # DW_AT_low_pc
159+
.byte 1 # DW_FORM_addr
160+
.byte 18 # DW_AT_high_pc
161+
.byte 1 # DW_FORM_addr
162+
.byte 3 # DW_AT_name
163+
.byte 8 # DW_FORM_string
164+
.byte 0 # EOM(1)
165+
.byte 0 # EOM(2)
166+
.byte 3 # Abbreviation Code
167+
.byte 46 # DW_TAG_subprogram
168+
.byte 0 # DW_CHILDREN_no
169+
.byte 85 # DW_AT_ranges
170+
.byte 23 # DW_FORM_sec_offset
171+
.byte 3 # DW_AT_name
172+
.byte 8 # DW_FORM_string
173+
.byte 0 # EOM(1)
174+
.byte 0 # EOM(2)
175+
.byte 0 # EOM(3)
176+
.section .debug_info,"",@progbits
177+
.Lcu_begin0:
178+
.long .Ldebug_info_end0-.Ldebug_info_start0 # Length of Unit
179+
.Ldebug_info_start0:
180+
.short 5 # DWARF version number
181+
.byte 1 # DWARF Unit Type
182+
.byte 8 # Address Size (in bytes)
183+
.long .debug_abbrev # Offset Into Abbrev. Section
184+
.byte 1 # Abbrev DW_TAG_compile_unit
185+
.asciz "file0.c" # DW_AT_producer
186+
.asciz "Hand-written DWARF" # DW_AT_producer
187+
.short 29 # DW_AT_language
188+
.quad .text # DW_AT_low_pc
189+
.quad .Ltext_end # DW_AT_high_pc
190+
.long .Lline_table_start0 # DW_AT_stmt_list
191+
.rept 4
192+
.byte 2 # Abbrev DW_TAG_subprogram
193+
.quad func\+ # DW_AT_low_pc
194+
.quad .Lfunc\+_end # DW_AT_high_pc
195+
.asciz "func\+" # DW_AT_name
196+
.endr
197+
.byte 3 # Abbrev DW_TAG_subprogram
198+
.long .Ldebug_ranges0
199+
.asciz "func4" # DW_AT_name
200+
.byte 0 # End Of Children Mark
201+
.Ldebug_info_end0:
202+
203+
.section .debug_rnglists,"",@progbits
204+
.long .Ldebug_list_header_end0-.Ldebug_list_header_start0 # Length
205+
.Ldebug_list_header_start0:
206+
.short 5 # Version
207+
.byte 8 # Address size
208+
.byte 0 # Segment selector size
209+
.long 2 # Offset entry count
210+
.Lrnglists_table_base0:
211+
.long .Ldebug_ranges0-.Lrnglists_table_base0
212+
.Ldebug_ranges0:
213+
.byte 6 # DW_RLE_start_end
214+
.quad func4
215+
.quad .Lfunc4_end
216+
.byte 6 # DW_RLE_start_end
217+
.quad func4.__part.1
218+
.quad .Lfunc4.__part.1_end
219+
.byte 0 # DW_RLE_end_of_list
220+
.Ldebug_list_header_end0:
221+
.section .debug_line,"",@progbits
222+
.Lline_table_start0:
223+
224+
#--- file0.c
225+
content of file0.c:1
226+
content of file0.c:2
227+
content of file0.c:3
228+
content of file0.c:4
229+
content of file0.c:5
230+
content of file0.c:6
231+
content of file0.c:7
232+
content of file0.c:8
233+
content of file0.c:9
234+
content of file0.c:10
235+
content of file0.c:11
236+
content of file0.c:12
237+
content of file0.c:13
238+
content of file0.c:14
239+
content of file0.c:15
240+
content of file0.c:16
241+
content of file0.c:17
242+
content of file0.c:18
243+
content of file0.c:19
244+
content of file0.c:20
245+
content of file0.c:21
246+
content of file0.c:22
247+
content of file0.c:23
248+
content of file0.c:24
249+
content of file0.c:25
250+
content of file0.c:26
251+
content of file0.c:27
252+
content of file0.c:28
253+
content of file0.c:29
254+
content of file0.c:30
255+
#--- file1.c
256+
content of file1.c:1
257+
content of file1.c:2
258+
content of file1.c:3
259+
content of file1.c:4
260+
content of file1.c:5
261+
content of file1.c:6
262+
content of file1.c:7
263+
content of file1.c:8
264+
content of file1.c:9
265+
content of file1.c:10

0 commit comments

Comments
 (0)