[Lldb-commits] [lldb] [DRAFT] Add support for inline DWARF source files. (PR #75880)

Adrian Prantl via lldb-commits lldb-commits at lists.llvm.org
Mon Dec 18 17:02:57 PST 2023


https://github.com/adrian-prantl created https://github.com/llvm/llvm-project/pull/75880

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.

>From 638bd4e7e939ef28bf23ae2cfd1723797040b0a5 Mon Sep 17 00:00:00 2001
From: Adrian Prantl <aprantl at apple.com>
Date: Mon, 18 Dec 2023 15:59:00 -0800
Subject: [PATCH] Add support for inline DWARF source files.

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.
---
 lldb/include/lldb/Utility/FileSpecList.h      | 46 ++++++++++++++++---
 lldb/source/Core/ModuleList.cpp               |  8 ++--
 .../Clang/ClangUserExpression.cpp             | 12 ++---
 .../Clang/CppModuleConfiguration.cpp          |  6 +--
 .../SymbolFile/DWARF/SymbolFileDWARF.cpp      | 46 +++++++++++++++++++
 lldb/source/Utility/FileSpecList.cpp          | 19 ++++----
 .../inline-sourcefile/Makefile                | 11 +++++
 .../TestInlineSourceFiles.py                  | 17 +++++++
 .../inline-sourcefile/inline.ll               | 39 ++++++++++++++++
 .../functionalities/inline-sourcefile/main.c  |  8 ++++
 10 files changed, 186 insertions(+), 26 deletions(-)
 create mode 100644 lldb/test/API/functionalities/inline-sourcefile/Makefile
 create mode 100644 lldb/test/API/functionalities/inline-sourcefile/TestInlineSourceFiles.py
 create mode 100644 lldb/test/API/functionalities/inline-sourcefile/inline.ll
 create mode 100644 lldb/test/API/functionalities/inline-sourcefile/main.c

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 (git at github.com: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 (git at github.com: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;
+}



More information about the lldb-commits mailing list