Skip to content

Commit 47721d4

Browse files
authored
[lldb] Realpath symlinks for breakpoints (llvm#102223)
Improve the chance of resolving file/line breakpoints by realpath'ing the support files before doing a second match attempt, with some conditions applied. A working [hello-world example](https://github.com/royitaqi/lldb_demos/blob/main/realpath/README.md). See [patch](llvm#102223) for more info about problem/motivation, details of the feature, new settings, telemetries and tests.
1 parent 52337d5 commit 47721d4

29 files changed

+1002
-170
lines changed

lldb/include/lldb/Symbol/CompileUnit.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
#include "lldb/Utility/Stream.h"
2020
#include "lldb/Utility/UserID.h"
2121
#include "lldb/lldb-enumerations.h"
22+
#include "lldb/lldb-forward.h"
2223

2324
#include "llvm/ADT/DenseMap.h"
2425
#include "llvm/ADT/DenseSet.h"
2526

2627
namespace lldb_private {
28+
2729
/// \class CompileUnit CompileUnit.h "lldb/Symbol/CompileUnit.h"
2830
/// A class that describes a compilation unit.
2931
///
@@ -389,10 +391,15 @@ class CompileUnit : public std::enable_shared_from_this<CompileUnit>,
389391
/// A SymbolContext list class that will get any matching
390392
/// entries appended to.
391393
///
394+
/// \param[in] realpath_prefixes
395+
/// Paths that start with one of the prefixes in this list will be
396+
/// realpath'ed to resolve any symlinks.
397+
///
392398
/// \see enum SymbolContext::Scope
393399
void ResolveSymbolContext(const SourceLocationSpec &src_location_spec,
394400
lldb::SymbolContextItem resolve_scope,
395-
SymbolContextList &sc_list);
401+
SymbolContextList &sc_list,
402+
RealpathPrefixes *realpath_prefixes = nullptr);
396403

397404
/// Get whether compiler optimizations were enabled for this compile unit
398405
///

lldb/include/lldb/Target/Statistics.h

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

1212
#include "lldb/Utility/ConstString.h"
13+
#include "lldb/Utility/RealpathPrefixes.h"
1314
#include "lldb/Utility/Stream.h"
1415
#include "lldb/lldb-forward.h"
1516
#include "llvm/ADT/StringMap.h"
@@ -184,6 +185,8 @@ class TargetStats {
184185
void SetFirstPrivateStopTime();
185186
void SetFirstPublicStopTime();
186187
void IncreaseSourceMapDeduceCount();
188+
void IncreaseSourceRealpathAttemptCount(uint32_t count);
189+
void IncreaseSourceRealpathCompatibleCount(uint32_t count);
187190

188191
StatsDuration &GetCreateTime() { return m_create_time; }
189192
StatsSuccessFail &GetExpressionStats() { return m_expr_eval; }
@@ -198,6 +201,8 @@ class TargetStats {
198201
StatsSuccessFail m_frame_var{"frameVariable"};
199202
std::vector<intptr_t> m_module_identifiers;
200203
uint32_t m_source_map_deduce_count = 0;
204+
uint32_t m_source_realpath_attempt_count = 0;
205+
uint32_t m_source_realpath_compatible_count = 0;
201206
void CollectStats(Target &target);
202207
};
203208

lldb/include/lldb/Target/Target.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "lldb/Utility/ArchSpec.h"
3535
#include "lldb/Utility/Broadcaster.h"
3636
#include "lldb/Utility/LLDBAssert.h"
37+
#include "lldb/Utility/RealpathPrefixes.h"
3738
#include "lldb/Utility/Timeout.h"
3839
#include "lldb/lldb-public.h"
3940

@@ -117,6 +118,8 @@ class TargetProperties : public Properties {
117118

118119
InlineStrategy GetInlineStrategy() const;
119120

121+
RealpathPrefixes GetSourceRealpathPrefixes() const;
122+
120123
llvm::StringRef GetArg0() const;
121124

122125
void SetArg0(llvm::StringRef arg);

lldb/include/lldb/Utility/FileSpecList.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,16 @@ class SupportFileList {
6464
/// \param[in] file
6565
/// The file specification to search for.
6666
///
67+
/// \param[in] realpath_prefixes
68+
/// Paths that start with one of the prefixes in this list will be
69+
/// realpath'ed to resolve any symlinks.
70+
///
6771
/// \return
6872
/// The index of the file that matches \a file if it is found,
6973
/// else UINT32_MAX is returned.
70-
size_t FindCompatibleIndex(size_t idx, const FileSpec &file) const;
74+
size_t
75+
FindCompatibleIndex(size_t idx, const FileSpec &file,
76+
RealpathPrefixes *realpath_prefixes = nullptr) const;
7177

7278
template <class... Args> void EmplaceBack(Args &&...args) {
7379
m_files.push_back(
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//===-- RealpathPrefixes.h --------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLDB_UTILITY_REALPATHPREFIXES_H
10+
#define LLDB_UTILITY_REALPATHPREFIXES_H
11+
12+
#include "lldb/lldb-forward.h"
13+
#include "llvm/ADT/IntrusiveRefCntPtr.h"
14+
#include "llvm/Support/VirtualFileSystem.h"
15+
16+
#include <optional>
17+
#include <string>
18+
#include <vector>
19+
20+
namespace lldb_private {
21+
22+
class RealpathPrefixes {
23+
public:
24+
/// \param[in] file_spec_list
25+
/// Prefixes are obtained from FileSpecList, through FileSpec::GetPath(),
26+
/// which ensures that the paths are normalized. For example:
27+
/// "./foo/.." -> ""
28+
/// "./foo/../bar" -> "bar"
29+
///
30+
/// \param[in] fs
31+
/// An optional filesystem to use for realpath'ing. If not set, the real
32+
/// filesystem will be used.
33+
explicit RealpathPrefixes(const FileSpecList &file_spec_list,
34+
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs =
35+
llvm::vfs::getRealFileSystem());
36+
37+
std::optional<FileSpec> ResolveSymlinks(const FileSpec &file_spec);
38+
39+
// If/when Statistics.h/cpp is moved into Utility, we can remove these
40+
// methods, hold a (weak) pointer to `TargetStats` and directly increment
41+
// on that object.
42+
void IncreaseSourceRealpathAttemptCount() {
43+
++m_source_realpath_attempt_count;
44+
}
45+
uint32_t GetSourceRealpathAttemptCount() const {
46+
return m_source_realpath_attempt_count;
47+
}
48+
void IncreaseSourceRealpathCompatibleCount() {
49+
++m_source_realpath_compatible_count;
50+
}
51+
uint32_t GetSourceRealpathCompatibleCount() const {
52+
return m_source_realpath_compatible_count;
53+
}
54+
55+
private:
56+
// Paths that start with one of the prefixes in this list will be realpath'ed
57+
// to resolve any symlinks.
58+
//
59+
// Wildcard prefixes:
60+
// - "" (empty string) will match all paths.
61+
// - "/" will match all absolute paths.
62+
std::vector<std::string> m_prefixes;
63+
64+
// The filesystem to use for realpath'ing.
65+
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> m_fs;
66+
67+
// The optional Target instance to gather statistics.
68+
lldb::TargetWP m_target;
69+
70+
// Statistics that we temprarily hold here, to be gathered into TargetStats
71+
uint32_t m_source_realpath_attempt_count = 0;
72+
uint32_t m_source_realpath_compatible_count = 0;
73+
};
74+
75+
} // namespace lldb_private
76+
77+
#endif // LLDB_UTILITY_REALPATHPREFIXES_H

lldb/include/lldb/lldb-forward.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ class Queue;
175175
class QueueImpl;
176176
class QueueItem;
177177
class REPL;
178+
class RealpathPrefixes;
178179
class RecognizedStackFrame;
179180
class RegisterCheckpoint;
180181
class RegisterContext;

lldb/source/Breakpoint/BreakpointResolverFileLine.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "lldb/Target/Target.h"
1616
#include "lldb/Utility/LLDBLog.h"
1717
#include "lldb/Utility/Log.h"
18+
#include "lldb/Utility/RealpathPrefixes.h"
1819
#include "lldb/Utility/StreamString.h"
1920
#include <optional>
2021

@@ -290,16 +291,25 @@ Searcher::CallbackReturn BreakpointResolverFileLine::SearchCallback(
290291
const uint32_t line = m_location_spec.GetLine().value_or(0);
291292
const std::optional<uint16_t> column = m_location_spec.GetColumn();
292293

294+
Target &target = GetBreakpoint()->GetTarget();
295+
RealpathPrefixes realpath_prefixes = target.GetSourceRealpathPrefixes();
296+
293297
const size_t num_comp_units = context.module_sp->GetNumCompileUnits();
294298
for (size_t i = 0; i < num_comp_units; i++) {
295299
CompUnitSP cu_sp(context.module_sp->GetCompileUnitAtIndex(i));
296300
if (cu_sp) {
297301
if (filter.CompUnitPasses(*cu_sp))
298302
cu_sp->ResolveSymbolContext(m_location_spec, eSymbolContextEverything,
299-
sc_list);
303+
sc_list, &realpath_prefixes);
300304
}
301305
}
302306

307+
// Gather stats into the Target
308+
target.GetStatistics().IncreaseSourceRealpathAttemptCount(
309+
realpath_prefixes.GetSourceRealpathAttemptCount());
310+
target.GetStatistics().IncreaseSourceRealpathCompatibleCount(
311+
realpath_prefixes.GetSourceRealpathCompatibleCount());
312+
303313
FilterContexts(sc_list);
304314

305315
DeduceSourceMapping(sc_list);

lldb/source/Symbol/CompileUnit.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -213,11 +213,12 @@ VariableListSP CompileUnit::GetVariableList(bool can_create) {
213213
return m_variables;
214214
}
215215

216-
std::vector<uint32_t> FindFileIndexes(const SupportFileList &files,
217-
const FileSpec &file) {
216+
std::vector<uint32_t>
217+
FindFileIndexes(const SupportFileList &files, const FileSpec &file,
218+
RealpathPrefixes *realpath_prefixes = nullptr) {
218219
std::vector<uint32_t> result;
219220
uint32_t idx = -1;
220-
while ((idx = files.FindCompatibleIndex(idx + 1, file)) !=
221+
while ((idx = files.FindCompatibleIndex(idx + 1, file, realpath_prefixes)) !=
221222
UINT32_MAX)
222223
result.push_back(idx);
223224
return result;
@@ -247,7 +248,8 @@ uint32_t CompileUnit::FindLineEntry(uint32_t start_idx, uint32_t line,
247248

248249
void CompileUnit::ResolveSymbolContext(
249250
const SourceLocationSpec &src_location_spec,
250-
SymbolContextItem resolve_scope, SymbolContextList &sc_list) {
251+
SymbolContextItem resolve_scope, SymbolContextList &sc_list,
252+
RealpathPrefixes *realpath_prefixes) {
251253
const FileSpec file_spec = src_location_spec.GetFileSpec();
252254
const uint32_t line = src_location_spec.GetLine().value_or(0);
253255
const bool check_inlines = src_location_spec.GetCheckInlines();
@@ -275,8 +277,8 @@ void CompileUnit::ResolveSymbolContext(
275277
return;
276278
}
277279

278-
std::vector<uint32_t> file_indexes = FindFileIndexes(GetSupportFiles(),
279-
file_spec);
280+
std::vector<uint32_t> file_indexes =
281+
FindFileIndexes(GetSupportFiles(), file_spec, realpath_prefixes);
280282
const size_t num_file_indexes = file_indexes.size();
281283
if (num_file_indexes == 0)
282284
return;

lldb/source/Target/Statistics.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,10 @@ TargetStats::ToJSON(Target &target,
192192
}
193193
target_metrics_json.try_emplace("sourceMapDeduceCount",
194194
m_source_map_deduce_count);
195+
target_metrics_json.try_emplace("sourceRealpathAttemptCount",
196+
m_source_realpath_attempt_count);
197+
target_metrics_json.try_emplace("sourceRealpathCompatibleCount",
198+
m_source_realpath_compatible_count);
195199
return target_metrics_json;
196200
}
197201

@@ -220,6 +224,14 @@ void TargetStats::IncreaseSourceMapDeduceCount() {
220224
++m_source_map_deduce_count;
221225
}
222226

227+
void TargetStats::IncreaseSourceRealpathAttemptCount(uint32_t count) {
228+
m_source_realpath_attempt_count += count;
229+
}
230+
231+
void TargetStats::IncreaseSourceRealpathCompatibleCount(uint32_t count) {
232+
m_source_realpath_compatible_count += count;
233+
}
234+
223235
bool DebuggerStats::g_collecting_stats = false;
224236

225237
llvm::json::Value DebuggerStats::ReportStatistics(

lldb/source/Target/Target.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
#include "lldb/Utility/LLDBAssert.h"
6161
#include "lldb/Utility/LLDBLog.h"
6262
#include "lldb/Utility/Log.h"
63+
#include "lldb/Utility/RealpathPrefixes.h"
6364
#include "lldb/Utility/State.h"
6465
#include "lldb/Utility/StreamString.h"
6566
#include "lldb/Utility/Timer.h"
@@ -4354,6 +4355,13 @@ InlineStrategy TargetProperties::GetInlineStrategy() const {
43544355
static_cast<InlineStrategy>(g_target_properties[idx].default_uint_value));
43554356
}
43564357

4358+
// Returning RealpathPrefixes, but the setting's type is FileSpecList. We do
4359+
// this because we want the FileSpecList to normalize the file paths for us.
4360+
RealpathPrefixes TargetProperties::GetSourceRealpathPrefixes() const {
4361+
const uint32_t idx = ePropertySourceRealpathPrefixes;
4362+
return RealpathPrefixes(GetPropertyAtIndexAs<FileSpecList>(idx, {}));
4363+
}
4364+
43574365
llvm::StringRef TargetProperties::GetArg0() const {
43584366
const uint32_t idx = ePropertyArg0;
43594367
return GetPropertyAtIndexAs<llvm::StringRef>(

lldb/source/Target/TargetProperties.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ let Definition = "target" in {
150150
DefaultEnumValue<"eInlineBreakpointsAlways">,
151151
EnumValues<"OptionEnumValues(g_inline_breakpoint_enums)">,
152152
Desc<"The strategy to use when settings breakpoints by file and line. Breakpoint locations can end up being inlined by the compiler, so that a compile unit 'a.c' might contain an inlined function from another source file. Usually this is limited to breakpoint locations from inlined functions from header or other include files, or more accurately non-implementation source files. Sometimes code might #include implementation files and cause inlined breakpoint locations in inlined implementation files. Always checking for inlined breakpoint locations can be expensive (memory and time), so if you have a project with many headers and find that setting breakpoints is slow, then you can change this setting to headers. This setting allows you to control exactly which strategy is used when setting file and line breakpoints.">;
153+
def SourceRealpathPrefixes: Property<"source-realpath-prefixes", "FileSpecList">,
154+
DefaultStringValue<"">,
155+
Desc<"Realpath any source paths that start with one of these prefixes. If the debug info contains symlinks which match the original source file's basename but don't match its location that the user will use to set breakpoints, then this setting can help resolve breakpoints correctly. This handles both symlinked files and directories. Wild card prefixes: An empty string matches all paths. A forward slash matches absolute paths.">;
153156
def DisassemblyFlavor: Property<"x86-disassembly-flavor", "Enum">,
154157
DefaultEnumValue<"eX86DisFlavorDefault">,
155158
EnumValues<"OptionEnumValues(g_x86_dis_flavor_value_types)">,

lldb/source/Utility/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ add_lldb_library(lldbUtility NO_INTERNAL_DEPENDENCIES
5151
Log.cpp
5252
NameMatches.cpp
5353
ProcessInfo.cpp
54+
RealpathPrefixes.cpp
5455
RegisterValue.cpp
5556
RegularExpression.cpp
5657
Instrumentation.cpp

0 commit comments

Comments
 (0)