-
Notifications
You must be signed in to change notification settings - Fork 14.3k
Add support for inline DWARF source files. #75880
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@llvm/pr-subscribers-lldb Author: Adrian Prantl (adrian-prantl) ChangesLLVM supports DWARF 5 linetable extension to store source files inline in DWARF. This is particularly useful for compiler-generated source code. This implementation tries to materialize them as temporary files lazily, so SBAPI clients don't need to be aware of them. Full diff: https://github.com/llvm/llvm-project/pull/75880.diff 10 Files Affected:
diff --git a/lldb/include/lldb/Utility/FileSpecList.h b/lldb/include/lldb/Utility/FileSpecList.h
index 77587aa917916b..078fd7208e7b5f 100644
--- a/lldb/include/lldb/Utility/FileSpecList.h
+++ b/lldb/include/lldb/Utility/FileSpecList.h
@@ -17,13 +17,40 @@
namespace lldb_private {
class Stream;
+/// Represents a source file whose contents is known (for example
+/// because it can be reconstructed from debug info), but that
+/// hasn't been written to a local disk yet.
+struct LazyFileSpec {
+ virtual ~LazyFileSpec() {}
+ virtual const FileSpec &Materialize() = 0;
+};
+
+/// Wraps either a FileSpec that represents a local file or a
+/// LazyFileSpec that could be materialized into a local file.
+class FileSpecHolder {
+ FileSpec m_file_spec;
+ std::shared_ptr<LazyFileSpec> m_lazy;
+public:
+ FileSpecHolder(const FileSpec &spec, std::shared_ptr<LazyFileSpec> lazy = {})
+ : m_file_spec(spec), m_lazy(lazy) {}
+ FileSpecHolder(const FileSpecHolder &other) = default;
+ FileSpecHolder(FileSpecHolder &&other) = default;
+ FileSpecHolder &operator=(const FileSpecHolder &other) = default;
+ const FileSpec &GetSpecOnly() const { return m_file_spec; };
+ const FileSpec &Materialize() const {
+ if (m_lazy)
+ return m_lazy->Materialize();
+ return m_file_spec;
+ }
+};
+
/// \class FileSpecList FileSpecList.h "lldb/Utility/FileSpecList.h"
/// A file collection class.
///
/// A class that contains a mutable list of FileSpec objects.
class FileSpecList {
public:
- typedef std::vector<FileSpec> collection;
+ typedef std::vector<FileSpecHolder> collection;
typedef collection::const_iterator const_iterator;
/// Default constructor.
@@ -38,7 +65,10 @@ class FileSpecList {
FileSpecList(FileSpecList &&rhs) = default;
/// Initialize this object from a vector of FileSpecs
- FileSpecList(std::vector<FileSpec> &&rhs) : m_files(std::move(rhs)) {}
+ FileSpecList(std::vector<FileSpec> &&rhs) {
+ for (auto &fs : rhs)
+ m_files.emplace_back(fs);
+ }
/// Destructor.
~FileSpecList();
@@ -83,7 +113,11 @@ class FileSpecList {
/// \param[in] args
/// Arguments to create the FileSpec
template <class... Args> void EmplaceBack(Args &&...args) {
- m_files.emplace_back(std::forward<Args>(args)...);
+ m_files.emplace_back(FileSpec(std::forward<Args>(args)...));
+ }
+
+ void Append(FileSpecHolder &&fsh) {
+ m_files.push_back(std::move(fsh));
}
/// Clears the file list.
@@ -175,10 +209,10 @@ class FileSpecList {
bool Insert(size_t idx, const FileSpec &file) {
if (idx < m_files.size()) {
- m_files.insert(m_files.begin() + idx, file);
+ m_files.insert(m_files.begin() + idx, FileSpecHolder(file));
return true;
} else if (idx == m_files.size()) {
- m_files.push_back(file);
+ m_files.push_back(FileSpecHolder(file));
return true;
}
return false;
@@ -186,7 +220,7 @@ class FileSpecList {
bool Replace(size_t idx, const FileSpec &file) {
if (idx < m_files.size()) {
- m_files[idx] = file;
+ m_files[idx] = FileSpecHolder(file);
return true;
}
return false;
diff --git a/lldb/source/Core/ModuleList.cpp b/lldb/source/Core/ModuleList.cpp
index aa89c93c8d0521..3b6c3ea899caf7 100644
--- a/lldb/source/Core/ModuleList.cpp
+++ b/lldb/source/Core/ModuleList.cpp
@@ -164,11 +164,13 @@ void ModuleListProperties::UpdateSymlinkMappings() {
llvm::sys::ScopedWriter lock(m_symlink_paths_mutex);
const bool notify = false;
m_symlink_paths.Clear(notify);
- for (FileSpec symlink : list) {
+ for (auto symlink : list) {
FileSpec resolved;
- Status status = FileSystem::Instance().Readlink(symlink, resolved);
+ Status status =
+ FileSystem::Instance().Readlink(symlink.Materialize(), resolved);
if (status.Success())
- m_symlink_paths.Append(symlink.GetPath(), resolved.GetPath(), notify);
+ m_symlink_paths.Append(symlink.Materialize().GetPath(),
+ resolved.GetPath(), notify);
}
}
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
index 68bdd96e8adb03..ec03a38752c2fe 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
@@ -488,8 +488,8 @@ CppModuleConfiguration GetModuleConfig(lldb::LanguageType language,
// Build a list of files we need to analyze to build the configuration.
FileSpecList files;
- for (const FileSpec &f : sc.comp_unit->GetSupportFiles())
- files.AppendIfUnique(f);
+ for (auto &f : sc.comp_unit->GetSupportFiles())
+ files.AppendIfUnique(f.Materialize());
// We also need to look at external modules in the case of -gmodules as they
// contain the support files for libc++ and the C library.
llvm::DenseSet<SymbolFile *> visited_symbol_files;
@@ -498,8 +498,8 @@ CppModuleConfiguration GetModuleConfig(lldb::LanguageType language,
for (std::size_t i = 0; i < module.GetNumCompileUnits(); ++i) {
const FileSpecList &support_files =
module.GetCompileUnitAtIndex(i)->GetSupportFiles();
- for (const FileSpec &f : support_files) {
- files.AppendIfUnique(f);
+ for (auto &f : support_files) {
+ files.AppendIfUnique(f.Materialize());
}
}
return false;
@@ -508,9 +508,9 @@ CppModuleConfiguration GetModuleConfig(lldb::LanguageType language,
LLDB_LOG(log, "[C++ module config] Found {0} support files to analyze",
files.GetSize());
if (log && log->GetVerbose()) {
- for (const FileSpec &f : files)
+ for (auto &f : files)
LLDB_LOGV(log, "[C++ module config] Analyzing support file: {0}",
- f.GetPath());
+ f.Materialize().GetPath());
}
// Try to create a configuration from the files. If there is no valid
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/CppModuleConfiguration.cpp b/lldb/source/Plugins/ExpressionParser/Clang/CppModuleConfiguration.cpp
index 847dab6592b886..0ea5b1581c8e06 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/CppModuleConfiguration.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/CppModuleConfiguration.cpp
@@ -134,9 +134,9 @@ bool CppModuleConfiguration::hasValidConfig() {
CppModuleConfiguration::CppModuleConfiguration(
const FileSpecList &support_files, const llvm::Triple &triple) {
// Analyze all files we were given to build the configuration.
- bool error = !llvm::all_of(support_files,
- std::bind(&CppModuleConfiguration::analyzeFile,
- this, std::placeholders::_1, triple));
+ bool error = !llvm::all_of(support_files, [&](auto &file) {
+ return CppModuleConfiguration::analyzeFile(file.Materialize(), triple);
+ });
// If we have a valid configuration at this point, set the
// include directories and module list that should be used.
if (!error && hasValidConfig()) {
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
index 7eddc5074eff12..f9faa85056b1a0 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
@@ -10,6 +10,7 @@
#include "llvm/DebugInfo/DWARF/DWARFDebugLoc.h"
#include "llvm/Support/Casting.h"
+#include "llvm/Support/FileUtilities.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/Threading.h"
@@ -235,6 +236,51 @@ ParseSupportFilesFromPrologue(const lldb::ModuleSP &module,
for (size_t idx = first_file_idx; idx <= last_file_idx; ++idx) {
std::string remapped_file;
if (auto file_path = GetFileByIndex(prologue, idx, compile_dir, style)) {
+ auto entry = prologue.getFileNameEntry(idx);
+ auto source = entry.Source.getAsCString();
+ if (!source)
+ consumeError(source.takeError());
+ else {
+ llvm::StringRef source_ref(*source);
+ if (!source_ref.empty()) {
+ /// Wrap a path for an in-DWARF source file. Lazily write it
+ /// to disk when Materialize() is called.
+ struct LazyDWARFFile : public LazyFileSpec {
+ LazyDWARFFile(std::string file_name, llvm::StringRef source,
+ FileSpec::Style style)
+ : file_name(file_name), source(source), style(style) {}
+ std::string file_name;
+ FileSpec tmp_file;
+ llvm::StringRef source;
+ std::unique_ptr<llvm::FileRemover> remover;
+ FileSpec::Style style;
+
+ const FileSpec &Materialize() override {
+ if (tmp_file)
+ return tmp_file;
+ llvm::SmallString<0> name;
+ int fd;
+ auto ec = llvm::sys::fs::createTemporaryFile(
+ "", llvm::sys::path::filename(file_name, style), fd, name);
+ if (ec || fd <= 0) {
+ LLDB_LOG(GetLog(DWARFLog::DebugInfo),
+ "Could not create temporary file");
+ return tmp_file;
+ }
+ remover = std::make_unique<llvm::FileRemover>(name);
+ NativeFile file(fd, File::eOpenOptionWriteOnly, true);
+ size_t num_bytes = source.size();
+ file.Write(source.data(), num_bytes);
+ tmp_file.SetPath(name);
+ return tmp_file;
+ }
+ };
+ support_files.Append(FileSpecHolder(
+ FileSpec(*file_path),
+ std::make_shared<LazyDWARFFile>(*file_path, *source, style)));
+ continue;
+ }
+ }
if (auto remapped = module->RemapSourceFile(llvm::StringRef(*file_path)))
remapped_file = *remapped;
else
diff --git a/lldb/source/Utility/FileSpecList.cpp b/lldb/source/Utility/FileSpecList.cpp
index d5369ac4bbe516..749fdcc6431117 100644
--- a/lldb/source/Utility/FileSpecList.cpp
+++ b/lldb/source/Utility/FileSpecList.cpp
@@ -30,7 +30,9 @@ void FileSpecList::Append(const FileSpec &file_spec) {
// a copy of "file_spec".
bool FileSpecList::AppendIfUnique(const FileSpec &file_spec) {
collection::iterator end = m_files.end();
- if (find(m_files.begin(), end, file_spec) == end) {
+ if (find_if(m_files.begin(), end, [&](auto &holder) {
+ return holder.GetSpecOnly() == file_spec;
+ }) == end) {
m_files.push_back(file_spec);
return true;
}
@@ -44,7 +46,7 @@ void FileSpecList::Clear() { m_files.clear(); }
void FileSpecList::Dump(Stream *s, const char *separator_cstr) const {
collection::const_iterator pos, end = m_files.end();
for (pos = m_files.begin(); pos != end; ++pos) {
- pos->Dump(s->AsRawOstream());
+ pos->GetSpecOnly().Dump(s->AsRawOstream());
if (separator_cstr && ((pos + 1) != end))
s->PutCString(separator_cstr);
}
@@ -64,13 +66,14 @@ size_t FileSpecList::FindFileIndex(size_t start_idx, const FileSpec &file_spec,
bool compare_filename_only = file_spec.GetDirectory().IsEmpty();
for (size_t idx = start_idx; idx < num_files; ++idx) {
+ auto f = m_files[idx].GetSpecOnly();
if (compare_filename_only) {
if (ConstString::Equals(
- m_files[idx].GetFilename(), file_spec.GetFilename(),
- file_spec.IsCaseSensitive() || m_files[idx].IsCaseSensitive()))
+ f.GetFilename(), file_spec.GetFilename(),
+ file_spec.IsCaseSensitive() || f.IsCaseSensitive()))
return idx;
} else {
- if (FileSpec::Equal(m_files[idx], file_spec, full))
+ if (FileSpec::Equal(f, file_spec, full))
return idx;
}
}
@@ -92,7 +95,7 @@ size_t FileSpecList::FindCompatibleIndex(size_t start_idx,
const bool full = !file_spec.GetDirectory().IsEmpty();
for (size_t idx = start_idx; idx < num_files; ++idx) {
- const FileSpec &curr_file = m_files[idx];
+ const FileSpec &curr_file = m_files[idx].GetSpecOnly();
// Always start by matching the filename first
if (!curr_file.FileEquals(file_spec))
@@ -135,7 +138,7 @@ size_t FileSpecList::FindCompatibleIndex(size_t start_idx,
// an empty FileSpec object will be returned.
const FileSpec &FileSpecList::GetFileSpecAtIndex(size_t idx) const {
if (idx < m_files.size())
- return m_files[idx];
+ return m_files[idx].Materialize();
static FileSpec g_empty_file_spec;
return g_empty_file_spec;
}
@@ -148,7 +151,7 @@ size_t FileSpecList::MemorySize() const {
size_t mem_size = sizeof(FileSpecList);
collection::const_iterator pos, end = m_files.end();
for (pos = m_files.begin(); pos != end; ++pos) {
- mem_size += pos->MemorySize();
+ mem_size += pos->GetSpecOnly().MemorySize();
}
return mem_size;
diff --git a/lldb/test/API/functionalities/inline-sourcefile/Makefile b/lldb/test/API/functionalities/inline-sourcefile/Makefile
new file mode 100644
index 00000000000000..adb29d3a88e26c
--- /dev/null
+++ b/lldb/test/API/functionalities/inline-sourcefile/Makefile
@@ -0,0 +1,11 @@
+C_SOURCES := main.c
+CFLAGS_EXTRAS := -gdwarf-5
+
+include Makefile.rules
+
+OBJECTS += inline.o
+
+$(EXE): main.c inline.o
+
+%.o: %.ll
+ $(CC) $< -c -o $@
diff --git a/lldb/test/API/functionalities/inline-sourcefile/TestInlineSourceFiles.py b/lldb/test/API/functionalities/inline-sourcefile/TestInlineSourceFiles.py
new file mode 100644
index 00000000000000..6daf970b895b0f
--- /dev/null
+++ b/lldb/test/API/functionalities/inline-sourcefile/TestInlineSourceFiles.py
@@ -0,0 +1,17 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbplatform
+from lldbsuite.test import lldbutil
+
+
+class InlineSourceFilesTestCase(TestBase):
+ @skipIf(compiler="gcc")
+ @skipIf(compiler="clang", compiler_version=["<", "18.0"])
+ def test(self):
+ """Test DWARF inline source files."""
+ self.build()
+ #target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
+ # self, 'break here', lldb.SBFileSpec('inlined.c'))
+ target, process, thread, bkpt = lldbutil.run_to_name_breakpoint(
+ self, 'f')
diff --git a/lldb/test/API/functionalities/inline-sourcefile/inline.ll b/lldb/test/API/functionalities/inline-sourcefile/inline.ll
new file mode 100644
index 00000000000000..56194e45b81387
--- /dev/null
+++ b/lldb/test/API/functionalities/inline-sourcefile/inline.ll
@@ -0,0 +1,39 @@
+; ModuleID = '/tmp/t.c'
+source_filename = "/tmp/t.c"
+target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128"
+
+; Function Attrs: noinline nounwind optnone ssp uwtable(sync)
+define void @f() #0 !dbg !9 {
+entry:
+ call void @stop(), !dbg !13
+ ret void, !dbg !14
+}
+
+declare void @stop(...) #1
+
+attributes #0 = { noinline nounwind optnone ssp uwtable(sync) }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5, !6, !7}
+!llvm.ident = !{!8}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 18.0.0git ([email protected]:llvm/llvm-project.git 29ee66f4a0967e43a035f147c960743c7b640f2f)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: Apple, sysroot: "/")
+!1 = !DIFile(filename: "/INLINE/inlined.c", directory: "/Volumes/Data/llvm-project", checksumkind: CSK_MD5, checksum: "3183154a5cb31debe9a8e27ca500bc3c")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{i32 1, !"wchar_size", i32 4}
+!5 = !{i32 8, !"PIC Level", i32 2}
+!6 = !{i32 7, !"uwtable", i32 1}
+!7 = !{i32 7, !"frame-pointer", i32 1}
+!8 = !{!"clang version 18.0.0git ([email protected]:llvm/llvm-project.git 29ee66f4a0967e43a035f147c960743c7b640f2f)"}
+!9 = distinct !DISubprogram(name: "f", scope: !10, file: !10, line: 2, type: !11, scopeLine: 2, spFlags: DISPFlagDefinition, unit: !0)
+!10 = !DIFile(filename: "/INLINE/inlined.c", directory: "", source: "void stop();
+void f() {
+ // This is inline source code.
+ stop(); // break here
+}
+")
+!11 = !DISubroutineType(types: !12)
+!12 = !{null}
+!13 = !DILocation(line: 4, column: 3, scope: !9)
+!14 = !DILocation(line: 5, column: 1, scope: !9)
diff --git a/lldb/test/API/functionalities/inline-sourcefile/main.c b/lldb/test/API/functionalities/inline-sourcefile/main.c
new file mode 100644
index 00000000000000..29a22d956ad510
--- /dev/null
+++ b/lldb/test/API/functionalities/inline-sourcefile/main.c
@@ -0,0 +1,8 @@
+void f();
+void stop() {}
+
+int main(int argc, char const *argv[])
+{
+ f();
+ return 0;
+}
|
You can test this locally with the following command:darker --check --diff -r 7df28fd61aa4603846b3ce16f9f988ccc780a584...9af00c37715c614cac700cd8763ee3d8167111e5 lldb/test/API/functionalities/inline-sourcefile/TestInlineSourceFiles.py View the diff from darker here.--- TestInlineSourceFiles.py 2024-01-04 17:03:16.000000 +0000
+++ TestInlineSourceFiles.py 2024-01-04 17:06:09.764133 +0000
@@ -9,7 +9,7 @@
@skipIf(compiler="gcc")
@skipIf(compiler="clang", compiler_version=["<", "18.0"])
def test(self):
"""Test DWARF inline source files."""
self.build()
- lldbutil.run_to_name_breakpoint(self, 'f')
+ lldbutil.run_to_name_breakpoint(self, "f")
self.expect("list f", substrs=["This is inline source code"])
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
The primary use-case I have in mind for this are Swift macro expansions, which may produce hundreds of tiny inline files per source file, hence the lazy approach. |
437b780
to
f66f34f
Compare
@JDevlieghere and I had an offline conversation about whether we couldn't separate out a SupportFileList from FileSpecList to limit the amount of places that need to think about lazy files and also to potentially move the FileSpec::m_checksum into. Here is a very rough patch that implements this idea to show how much of LLDB is affected by the change. |
Ping @JDevlieghere |
a23330a
to
d5c6055
Compare
@JDevlieghere I think I addressed your comments. Mind taking another look? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks awesome. Without the need for copying the SupportFileList, can we make the UnspooledSupportFile a subclass of SupportFile and store unique pointers in the support file list?
@JDevlieghere I merged the two. This made the regular (on-disk) case somewhat less efficient because all SupportFiles are now on the Heap but that probably isn't even measurable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
bce8320
to
4368519
Compare
4368519
to
919f93c
Compare
LLVM supports DWARF 5 linetable extension to store source files inline in DWARF. This is particularly useful for compiler-generated source code. This implementation tries to materialize them as temporary files lazily, so SBAPI clients don't need to be aware of them. As an implementation detail, this patch separate SupportFileList from FileSpecList and makes SupportFileList uncopyable.
919f93c
to
9af00c3
Compare
LLVM supports DWARF 5 linetable extension to store source files inline in DWARF. This is particularly useful for compiler-generated source code. This implementation tries to materialize them as temporary files lazily, so SBAPI clients don't need to be aware of them. rdar://110926168 (cherry picked from commit 917b404) Conflicts: lldb/include/lldb/Symbol/CompileUnit.h lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h lldb/source/Symbol/CompileUnit.cpp lldb/source/Utility/FileSpecList.cpp
This is failing on Windows: https://lab.llvm.org/buildbot/#/builders/219/builds/7982 Any obvious reason that this would be happening? I do see the DWARF in the binary:
Maybe it is path style, but at least at a glance, the code is handling that. |
Skipped for now 8b49ed8 |
I'll probably need some help from someone with a windows machine to debug this. Your hunch about path separators sounds plausible. |
@adrian-prantl, I'll be glad to help:)
while linux has one
you can look at Win test result there (+ added 2 .md with the objdump outputs) ps. played replacing '/' on '\' in paths in |
Hi again! Some notes about Win test fail I discovered: Setting function breakpoints on binary with The next analysis was done from repo based on b4001e3.
particularly
|
LLVM supports DWARF 5 linetable extension to store source files inline in DWARF. This is particularly useful for compiler-generated source code. This implementation tries to materialize them as temporary files lazily, so SBAPI clients don't need to be aware of them.
rdar://110926168