[Lldb-commits] [lldb] [PrefixMap] Teach lldb to auto-load compilation-prefix-map.json (PR #187145)

Steven Wu via lldb-commits lldb-commits at lists.llvm.org
Tue Mar 17 15:49:08 PDT 2026


https://github.com/cachemeifyoucan created https://github.com/llvm/llvm-project/pull/187145

Add a LoadCompilationPrefixMap() helper in SymbolFile::FindPlugin that
walks up from the symbol file's directory looking for a
compilation-prefix-map.json file. When found, each key→value entry is
applied to the module's source path mapping list, allowing LLDB to
resolve source file paths that were rewritten by -fdebug-prefix-map at
build time without requiring manual `settings set target.source-map`.

The JSON file format maps fake paths (as written into debug info) back
to their real on-disk counterparts:
  { "/fake/srcdir": "/real/srcdir" }

Directory results are cached so the filesystem is walked at most once
per unique directory across all modules loaded in a session.

Also apply the module's source path remappings in
SymbolFileDWARFDebugMap::ParseCompileUnitAtIndex when constructing
compile units from N_SO stabs. This mirrors what MakeAbsoluteAndRemap
does for the dSYM case so that fake paths baked into the debug map are
transparently resolved to real paths.

rdar://84824567

Assisted-By: Claude


>From f377a68f09a329e7838df0392fa61ab179b1aee0 Mon Sep 17 00:00:00 2001
From: Steven Wu <stevenwu at apple.com>
Date: Tue, 17 Mar 2026 15:48:56 -0700
Subject: [PATCH] =?UTF-8?q?[=F0=9D=98=80=F0=9D=97=BD=F0=9D=97=BF]=20initia?=
 =?UTF-8?q?l=20version?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Created using spr 1.3.7
---
 lldb/include/lldb/Core/Module.h               | 18 ++++-
 lldb/source/Core/Module.cpp                   | 65 ++++++++++++++++++-
 .../DWARF/SymbolFileDWARFDebugMap.cpp         |  9 +++
 lldb/source/Symbol/SymbolFile.cpp             | 11 ++++
 .../compilation-prefix-map/Makefile           |  9 +++
 .../TestCompilationPrefixMap.py               | 41 ++++++++++++
 .../compilation-prefix-map/main.c             |  7 ++
 7 files changed, 155 insertions(+), 5 deletions(-)
 create mode 100644 lldb/test/API/functionalities/compilation-prefix-map/Makefile
 create mode 100644 lldb/test/API/functionalities/compilation-prefix-map/TestCompilationPrefixMap.py
 create mode 100644 lldb/test/API/functionalities/compilation-prefix-map/main.c

diff --git a/lldb/include/lldb/Core/Module.h b/lldb/include/lldb/Core/Module.h
index ea875ac68cde2..cfe006629ae99 100644
--- a/lldb/include/lldb/Core/Module.h
+++ b/lldb/include/lldb/Core/Module.h
@@ -845,7 +845,7 @@ class Module : public std::enable_shared_from_this<Module>,
   ///     /b true if \a orig_spec was successfully located and
   ///     \a new_spec is filled in with an existing file spec,
   ///     \b false otherwise.
-  bool FindSourceFile(const FileSpec &orig_spec, FileSpec &new_spec) const;
+  bool FindSourceFile(const FileSpec &orig_spec, FileSpec &new_spec);
 
   /// Remaps a source file given \a path into \a new_path.
   ///
@@ -859,9 +859,14 @@ class Module : public std::enable_shared_from_this<Module>,
   /// \return
   ///     The newly remapped filespec that is may or may not exist if
   ///     \a path was successfully located.
-  std::optional<std::string> RemapSourceFile(llvm::StringRef path) const;
+  std::optional<std::string> RemapSourceFile(llvm::StringRef path);
   bool RemapSourceFile(const char *, std::string &) const = delete;
 
+  /// Register a directory to be searched for \c compilation-prefix-map.json
+  /// on the first call to RemapSourceFile or FindSourceFile. Duplicate
+  /// directories are silently ignored.
+  void AddPrefixMapSearchDir(FileSpec dir);
+
   /// Update the ArchSpec to a more specific variant.
   bool MergeArchitecture(const ArchSpec &arch_spec);
 
@@ -1068,6 +1073,15 @@ class Module : public std::enable_shared_from_this<Module>,
   PathMappingList m_source_mappings =
       ModuleList::GetGlobalModuleListProperties().GetSymlinkMappings();
 
+  /// Directories registered via AddPrefixMapSearchDir, searched lazily on the
+  /// first call to RemapSourceFile or FindSourceFile. Cleared after searching.
+  llvm::DenseSet<ConstString> m_prefix_map_search_dirs;
+
+  /// Search each registered directory upward for compilation-prefix-map.json
+  /// and apply any found mappings to m_source_mappings. Called at most once.
+  /// Must be called with m_mutex held.
+  void LoadPrefixMapsIfNeeded();
+
   lldb::SectionListUP m_sections_up; ///< Unified section list for module that
                                      /// is used by the ObjectFile and
                                      /// ObjectFile instances for the debug info
diff --git a/lldb/source/Core/Module.cpp b/lldb/source/Core/Module.cpp
index 79618ab91e27a..79a43258aa36f 100644
--- a/lldb/source/Core/Module.cpp
+++ b/lldb/source/Core/Module.cpp
@@ -58,7 +58,9 @@
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/JSON.h"
+#include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Signals.h"
+#include "llvm/Support/VirtualFileSystem.h"
 #include "llvm/Support/raw_ostream.h"
 
 #include <cassert>
@@ -1553,9 +1555,9 @@ bool Module::MatchesModuleSpec(const ModuleSpec &module_ref) {
   return true;
 }
 
-bool Module::FindSourceFile(const FileSpec &orig_spec,
-                            FileSpec &new_spec) const {
+bool Module::FindSourceFile(const FileSpec &orig_spec, FileSpec &new_spec) {
   std::lock_guard<std::recursive_mutex> guard(m_mutex);
+  LoadPrefixMapsIfNeeded();
   if (auto remapped = m_source_mappings.FindFile(orig_spec)) {
     new_spec = *remapped;
     return true;
@@ -1563,8 +1565,65 @@ bool Module::FindSourceFile(const FileSpec &orig_spec,
   return false;
 }
 
-std::optional<std::string> Module::RemapSourceFile(llvm::StringRef path) const {
+void Module::AddPrefixMapSearchDir(FileSpec dir) {
   std::lock_guard<std::recursive_mutex> guard(m_mutex);
+  m_prefix_map_search_dirs.insert(ConstString(dir.GetPath()));
+}
+
+void Module::LoadPrefixMapsIfNeeded() {
+  // Must be called with m_mutex held.
+  if (m_prefix_map_search_dirs.empty())
+    return;
+
+  Log *log = GetLog(LLDBLog::Symbols);
+  llvm::vfs::FileSystem &vfs = *llvm::vfs::getRealFileSystem();
+  // Track visited directories so two starting paths that share ancestors
+  // don't redundantly walk the same directory.
+  llvm::DenseSet<ConstString> searched;
+  for (ConstString start_cs : m_prefix_map_search_dirs) {
+    for (FileSpec d(start_cs.GetStringRef());;) {
+      ConstString d_cs(d.GetPath());
+      if (!searched.insert(d_cs).second)
+        break;
+      FileSpec jp(d);
+      jp.AppendPathComponent("compilation-prefix-map.json");
+      llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> file =
+          vfs.openFileForRead(jp.GetPath());
+      if (file && *file) {
+        LLDB_LOG(log, "found compilation-prefix-map.json at {0}", jp.GetPath());
+        llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> buf =
+            (*file)->getBuffer(jp.GetPath());
+        if (buf && *buf) {
+          llvm::Expected<llvm::json::Value> val =
+              llvm::json::parse((*buf)->getBuffer());
+          if (val) {
+            if (llvm::json::Object *obj = val->getAsObject()) {
+              for (const llvm::json::Object::value_type &kv : *obj)
+                if (std::optional<llvm::StringRef> to =
+                        kv.second.getAsString()) {
+                  LLDB_LOG(log, "applying prefix map: '{0}' -> '{1}'", kv.first,
+                           *to);
+                  m_source_mappings.AppendUnique(kv.first.str(), to->str(),
+                                                 /*notify=*/false);
+                }
+            }
+          }
+        }
+        break;
+      }
+      FileSpec parent = d;
+      parent.RemoveLastPathComponent();
+      if (parent == d)
+        break;
+      d = parent;
+    }
+  }
+  m_prefix_map_search_dirs.clear();
+}
+
+std::optional<std::string> Module::RemapSourceFile(llvm::StringRef path) {
+  std::lock_guard<std::recursive_mutex> guard(m_mutex);
+  LoadPrefixMapsIfNeeded();
   if (auto remapped = m_source_mappings.RemapPath(path))
     return remapped->GetPath();
   return {};
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
index 1553fcbc831a9..fd2da1ea46fec 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
@@ -600,6 +600,15 @@ CompUnitSP SymbolFileDWARFDebugMap::ParseCompileUnitAtIndex(uint32_t cu_idx) {
     if (oso_module) {
       FileSpec so_file_spec;
       if (GetFileSpecForSO(cu_idx, so_file_spec)) {
+        // Apply the module's source path remappings so that compile units
+        // created from N_SO stabs (which may contain paths rewritten by
+        // -fdebug-prefix-map at build time) report their real on-disk paths.
+        // This mirrors what MakeAbsoluteAndRemap does for the dSYM case.
+        if (ModuleSP module_sp = m_objfile_sp->GetModule())
+          if (auto remapped =
+                  module_sp->RemapSourceFile(so_file_spec.GetPath()))
+            so_file_spec.SetFile(*remapped, FileSpec::Style::native);
+
         // User zero as the ID to match the compile unit at offset zero in each
         // .o file.
         lldb::user_id_t cu_id = 0;
diff --git a/lldb/source/Symbol/SymbolFile.cpp b/lldb/source/Symbol/SymbolFile.cpp
index 7c2b499c97b4c..0ef139b1d453a 100644
--- a/lldb/source/Symbol/SymbolFile.cpp
+++ b/lldb/source/Symbol/SymbolFile.cpp
@@ -16,6 +16,7 @@
 #include "lldb/Symbol/TypeMap.h"
 #include "lldb/Symbol/TypeSystem.h"
 #include "lldb/Symbol/VariableList.h"
+#include "lldb/Utility/LLDBLog.h"
 #include "lldb/Utility/Log.h"
 #include "lldb/Utility/StreamString.h"
 #include "lldb/Utility/StructuredData.h"
@@ -96,6 +97,16 @@ SymbolFile *SymbolFile::FindPlugin(ObjectFileSP objfile_sp) {
       // Let the winning symbol file parser initialize itself more completely
       // now that it has been chosen
       best_symfile_up->InitializeObject();
+
+      // Register the object file's directory so the module can lazily search
+      // for a compilation-prefix-map.json when source paths are first remapped.
+      if (ObjectFile *obj = best_symfile_up->GetMainObjectFile())
+        if (ModuleSP mod = obj->GetModule()) {
+          FileSpec dir = obj->GetFileSpec();
+          dir.ClearFilename();
+          if (dir)
+            mod->AddPrefixMapSearchDir(std::move(dir));
+        }
     }
   }
   return best_symfile_up.release();
diff --git a/lldb/test/API/functionalities/compilation-prefix-map/Makefile b/lldb/test/API/functionalities/compilation-prefix-map/Makefile
new file mode 100644
index 0000000000000..ebf94eff13390
--- /dev/null
+++ b/lldb/test/API/functionalities/compilation-prefix-map/Makefile
@@ -0,0 +1,9 @@
+C_SOURCES := main.c
+CFLAGS_EXTRAS = -fdebug-prefix-map=$(SRCDIR)=/fake/srcdir
+
+all: $(EXE) compilation-prefix-map.json
+
+include Makefile.rules
+
+compilation-prefix-map.json:
+	printf '{ "/fake/srcdir": "%s" }' "$(SRCDIR)" > $(BUILDDIR)/compilation-prefix-map.json
diff --git a/lldb/test/API/functionalities/compilation-prefix-map/TestCompilationPrefixMap.py b/lldb/test/API/functionalities/compilation-prefix-map/TestCompilationPrefixMap.py
new file mode 100644
index 0000000000000..9ea2d9813ddc3
--- /dev/null
+++ b/lldb/test/API/functionalities/compilation-prefix-map/TestCompilationPrefixMap.py
@@ -0,0 +1,41 @@
+# TestCompilationPrefixMap.py
+#
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+#
+"""
+Test that LLDB auto-loads compilation-prefix-map.json to resolve remapped
+source paths without requiring manual `settings set target.source-map`.
+"""
+import os
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+import lldbsuite.test.lldbutil as lldbutil
+
+
+class TestCompilationPrefixMap(TestBase):
+    @skipIfWindows
+    def test_compilation_prefix_map(self):
+        """
+        Build a binary with -fdebug-prefix-map remapping the source directory
+        to /fake/srcdir, place compilation-prefix-map.json next to the binary
+        mapping /fake/srcdir back to the real source directory, and verify that
+        LLDB resolves a source-line breakpoint without any manual source-map
+        configuration.
+        """
+        self.build()
+
+        src_dir = self.getSourceDir()
+
+        log = self.getBuildArtifact("symbol.log")
+        self.runCmd('log enable lldb symbol -f "%s"' % log)
+
+        source_spec = lldb.SBFileSpec(os.path.join(src_dir, "main.c"))
+        lldbutil.run_to_source_breakpoint(self, "return x", source_spec)
+
+        self.filecheck_log(log, __file__)
+#       CHECK: found compilation-prefix-map.json
+#       CHECK: applying prefix map: '/fake/srcdir'
diff --git a/lldb/test/API/functionalities/compilation-prefix-map/main.c b/lldb/test/API/functionalities/compilation-prefix-map/main.c
new file mode 100644
index 0000000000000..0f34f1945b09a
--- /dev/null
+++ b/lldb/test/API/functionalities/compilation-prefix-map/main.c
@@ -0,0 +1,7 @@
+int add(int x, int y) {
+  return x + y;
+}
+
+int main() {
+  return add(1, 2) - 3;
+}



More information about the lldb-commits mailing list