Skip to content

[clang] Add -fno-debug-record-sysroot#192541

Open
keith wants to merge 2 commits intollvm:mainfrom
keith:ks/clang-add-fno-debug-record-sysroot
Open

[clang] Add -fno-debug-record-sysroot#192541
keith wants to merge 2 commits intollvm:mainfrom
keith:ks/clang-add-fno-debug-record-sysroot

Conversation

@keith
Copy link
Copy Markdown
Member

@keith keith commented Apr 16, 2026

This enables excluding the absolute path to the sysroot from debug info
for reproducible builds. These fields are used by lldb, which also has
fallbacks since it's possible these paths don't exist on the machine
doing the debugging when built remotely anyways.

This was also possible using -fdebug-prefix-map=/path/to/Xcode.app=/some/path
but depending on the environment you might not be able to easily pass
that with the user specific developer directory path.

Assisted by: claude

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules clang:codegen IR generation bugs: mangling, exceptions, etc. debuginfo labels Apr 16, 2026
@llvmbot
Copy link
Copy Markdown
Member

llvmbot commented Apr 16, 2026

@llvm/pr-subscribers-lldb
@llvm/pr-subscribers-clang-codegen
@llvm/pr-subscribers-clang-modules

@llvm/pr-subscribers-debuginfo

Author: Keith Smiley (keith)

Changes

This enables excluding the absolute path to the sysroot from debug info
for reproducible builds. These fields are used by lldb, which also has
fallbacks since it's possible these paths don't exist on the machine
doing the debugging when built remotely anyways.

This was also possible using -fdebug-prefix-map=/path/to/Xcode.app=/some/path
but depending on the environment you might not be able to easily pass
that with the user specific developer directory path.

Assisted by: claude


Full diff: https://github.com/llvm/llvm-project/pull/192541.diff

6 Files Affected:

  • (modified) clang/include/clang/Basic/DebugOptions.def (+3)
  • (modified) clang/include/clang/Options/Options.td (+7)
  • (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+11-4)
  • (modified) clang/lib/Driver/ToolChains/Clang.cpp (+4)
  • (modified) clang/test/DebugInfo/Generic/sysroot-sdk.c (+9)
  • (modified) clang/test/Modules/debug-info-moduleimport.m (+13)
diff --git a/clang/include/clang/Basic/DebugOptions.def b/clang/include/clang/Basic/DebugOptions.def
index 604e87e615a69..c9dd3f726e799 100644
--- a/clang/include/clang/Basic/DebugOptions.def
+++ b/clang/include/clang/Basic/DebugOptions.def
@@ -125,6 +125,9 @@ DEBUGOPT(CodeViewCommandLine, 1, 0, Compatible)
 /// Whether emit extra debug info for sample pgo profile collection.
 DEBUGOPT(DebugInfoForProfiling, 1, 0, Compatible)
 
+/// Whether to record the sysroot path or scrub it from debug info.
+DEBUGOPT(DebugRecordSysroot, 1, 1, Compatible)
+
 /// Whether to emit DW_TAG_template_alias for template aliases.
 DEBUGOPT(DebugTemplateAlias, 1, 0, Compatible)
 
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 5673fb0c47d5b..0afbb6f1d0b37 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -1823,6 +1823,13 @@ defm debug_info_for_profiling : BoolFOption<"debug-info-for-profiling",
   PosFlag<SetTrue, [], [ClangOption, CC1Option, FlangOption, FC1Option],
           "Emit extra debug info to make sample profile more accurate">,
   NegFlag<SetFalse, [], [ClangOption, FlangOption]>>;
+defm debug_record_sysroot : BoolFOption<"debug-record-sysroot",
+  CodeGenOpts<"DebugRecordSysroot">, DefaultTrue,
+  PosFlag<SetTrue, [], [ClangOption, CC1Option],
+          "Record the sysroot path in debug info as DW_AT_LLVM_sysroot and in DW_AT_LLVM_include_path "
+          "(used by LLDB to locate the SDK for Clang module imports).">,
+  NegFlag<SetFalse, [], [ClangOption, CC1Option],
+          "Scrub the sysroot path from debug info.">>;
 def fprofile_generate_cold_function_coverage : Flag<["-"], "fprofile-generate-cold-function-coverage">,
     Group<f_Group>, Visibility<[ClangOption, CLOption]>,
     HelpText<"Generate instrumented code to collect coverage info for cold functions into default.profraw file (overridden by '=' form of option or LLVM_PROFILE_FILE env var)">;
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index c5a92a8e7ceb0..253cb03230b48 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -852,9 +852,11 @@ void CGDebugInfo::CreateCompileUnit() {
 
   StringRef Sysroot, SDK;
   if (CGM.getCodeGenOpts().getDebuggerTuning() == llvm::DebuggerKind::LLDB) {
-    Sysroot = CGM.getHeaderSearchOpts().Sysroot;
-    auto B = llvm::sys::path::rbegin(Sysroot);
-    auto E = llvm::sys::path::rend(Sysroot);
+    StringRef FullSysroot = CGM.getHeaderSearchOpts().Sysroot;
+    if (CGM.getCodeGenOpts().DebugRecordSysroot)
+      Sysroot = FullSysroot;
+    auto B = llvm::sys::path::rbegin(FullSysroot);
+    auto E = llvm::sys::path::rend(FullSysroot);
     auto It =
         std::find_if(B, E, [](auto SDK) { return SDK.ends_with(".sdk"); });
     if (It != E)
@@ -3529,7 +3531,12 @@ llvm::DIModule *CGDebugInfo::getOrCreateModuleRef(ASTSourceDescriptor Mod,
       IsRootModule ? nullptr
                    : getOrCreateModuleRef(ASTSourceDescriptor(*M->Parent),
                                           CreateSkeletonCU);
-  std::string IncludePath = Mod.getPath().str();
+  StringRef IncludePath = Mod.getPath();
+  if (!CGM.getCodeGenOpts().DebugRecordSysroot) {
+    StringRef Sysroot = CGM.getHeaderSearchOpts().Sysroot;
+    if (!Sysroot.empty() && IncludePath.starts_with(Sysroot))
+      IncludePath = "";
+  }
   llvm::DIModule *DIMod =
       DBuilder.createModule(Parent, Mod.getModuleName(), ConfigMacros,
                             RemapPath(IncludePath));
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 267e674441599..ecd5bf5a31fce 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -4460,6 +4460,10 @@ renderDebugOptions(const ToolChain &TC, const Driver &D, const llvm::Triple &T,
 
   addDebugInfoForProfilingArgs(D, TC, Args, CmdArgs);
 
+  if (!Args.hasFlag(options::OPT_fdebug_record_sysroot,
+                    options::OPT_fno_debug_record_sysroot, true))
+    CmdArgs.push_back("-fno-debug-record-sysroot");
+
   // The 'g' groups options involve a somewhat intricate sequence of decisions
   // about what to pass from the driver to the frontend, but by the time they
   // reach cc1 they've been factored into three well-defined orthogonal choices:
diff --git a/clang/test/DebugInfo/Generic/sysroot-sdk.c b/clang/test/DebugInfo/Generic/sysroot-sdk.c
index b52d2e1de9c1d..b424e62f5d3b0 100644
--- a/clang/test/DebugInfo/Generic/sysroot-sdk.c
+++ b/clang/test/DebugInfo/Generic/sysroot-sdk.c
@@ -4,6 +4,10 @@
 // RUN: %clang_cc1 -debug-info-kind=limited -triple %itanium_abi_triple \
 // RUN:   %s -isysroot /CLANG_SYSROOT/MacOSX.sdk -emit-llvm -o - \
 // RUN:   -debugger-tuning=gdb | FileCheck %s --check-prefix=GDB
+// RUN: %clang_cc1 -debug-info-kind=limited -triple %itanium_abi_triple \
+// RUN:   %s -isysroot /CLANG_SYSROOT/MacOSX.sdk -emit-llvm -o - \
+// RUN:   -debugger-tuning=lldb -fno-debug-record-sysroot \
+// RUN:   | FileCheck %s --check-prefix=NOSYSROOT
 
 void foo(void) {}
 
@@ -14,3 +18,8 @@ void foo(void) {}
 // GDB: distinct !DICompileUnit(
 // GDB-NOT: sysroot: "/CLANG_SYSROOT/MacOSX.sdk"
 // GDB-NOT: sdk: "MacOSX.sdk"
+
+// -fno-debug-record-sysroot suppresses the sysroot path but keeps the SDK marker.
+// NOSYSROOT: distinct !DICompileUnit(
+// NOSYSROOT-NOT: sysroot: "/CLANG_SYSROOT/MacOSX.sdk"
+// NOSYSROOT-SAME: sdk: "MacOSX.sdk"
diff --git a/clang/test/Modules/debug-info-moduleimport.m b/clang/test/Modules/debug-info-moduleimport.m
index bdeb05bc08a02..b242364e7100e 100644
--- a/clang/test/Modules/debug-info-moduleimport.m
+++ b/clang/test/Modules/debug-info-moduleimport.m
@@ -44,3 +44,16 @@
 // SKEL-CHECK: ![[CUFILE]] = !DIFile({{.*}}directory: "[[COMP_DIR:.*]]"
 // SKEL-CHECK: distinct !DICompileUnit({{.*}}file: ![[DWOFILE:[0-9]+]]{{.*}}splitDebugFilename: "/MODULE-CACHE{{.*}}dwoId
 // SKEL-CHECK: ![[DWOFILE]] = !DIFile({{.*}}directory: "[[COMP_DIR]]"
+
+// With -fno-debug-record-sysroot and a sysroot that covers the module's
+// include path (%S contains %S/Inputs), the DICompileUnit has no sysroot
+// field and the DIModule has no includePath field.
+// RUN: rm -rf %t
+// RUN: %clang_cc1 -debug-info-kind=limited -fmodules -fimplicit-module-maps \
+// RUN:   -fmodules-cache-path=%t/cache %s -I %S/Inputs -isysroot %S -I %t \
+// RUN:   -emit-llvm -debugger-tuning=lldb -fno-debug-record-sysroot -o - \
+// RUN:   | FileCheck %s --check-prefix=NO-SYSROOT
+
+// NO-SYSROOT-NOT: sysroot:
+// NO-SYSROOT-NOT: includePath:
+// NO-SYSROOT: !DIModule(scope: null, name: "DebugObjC")

@llvmbot
Copy link
Copy Markdown
Member

llvmbot commented Apr 16, 2026

@llvm/pr-subscribers-clang-driver

Author: Keith Smiley (keith)

Changes

This enables excluding the absolute path to the sysroot from debug info
for reproducible builds. These fields are used by lldb, which also has
fallbacks since it's possible these paths don't exist on the machine
doing the debugging when built remotely anyways.

This was also possible using -fdebug-prefix-map=/path/to/Xcode.app=/some/path
but depending on the environment you might not be able to easily pass
that with the user specific developer directory path.

Assisted by: claude


Full diff: https://github.com/llvm/llvm-project/pull/192541.diff

6 Files Affected:

  • (modified) clang/include/clang/Basic/DebugOptions.def (+3)
  • (modified) clang/include/clang/Options/Options.td (+7)
  • (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+11-4)
  • (modified) clang/lib/Driver/ToolChains/Clang.cpp (+4)
  • (modified) clang/test/DebugInfo/Generic/sysroot-sdk.c (+9)
  • (modified) clang/test/Modules/debug-info-moduleimport.m (+13)
diff --git a/clang/include/clang/Basic/DebugOptions.def b/clang/include/clang/Basic/DebugOptions.def
index 604e87e615a69..c9dd3f726e799 100644
--- a/clang/include/clang/Basic/DebugOptions.def
+++ b/clang/include/clang/Basic/DebugOptions.def
@@ -125,6 +125,9 @@ DEBUGOPT(CodeViewCommandLine, 1, 0, Compatible)
 /// Whether emit extra debug info for sample pgo profile collection.
 DEBUGOPT(DebugInfoForProfiling, 1, 0, Compatible)
 
+/// Whether to record the sysroot path or scrub it from debug info.
+DEBUGOPT(DebugRecordSysroot, 1, 1, Compatible)
+
 /// Whether to emit DW_TAG_template_alias for template aliases.
 DEBUGOPT(DebugTemplateAlias, 1, 0, Compatible)
 
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 5673fb0c47d5b..0afbb6f1d0b37 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -1823,6 +1823,13 @@ defm debug_info_for_profiling : BoolFOption<"debug-info-for-profiling",
   PosFlag<SetTrue, [], [ClangOption, CC1Option, FlangOption, FC1Option],
           "Emit extra debug info to make sample profile more accurate">,
   NegFlag<SetFalse, [], [ClangOption, FlangOption]>>;
+defm debug_record_sysroot : BoolFOption<"debug-record-sysroot",
+  CodeGenOpts<"DebugRecordSysroot">, DefaultTrue,
+  PosFlag<SetTrue, [], [ClangOption, CC1Option],
+          "Record the sysroot path in debug info as DW_AT_LLVM_sysroot and in DW_AT_LLVM_include_path "
+          "(used by LLDB to locate the SDK for Clang module imports).">,
+  NegFlag<SetFalse, [], [ClangOption, CC1Option],
+          "Scrub the sysroot path from debug info.">>;
 def fprofile_generate_cold_function_coverage : Flag<["-"], "fprofile-generate-cold-function-coverage">,
     Group<f_Group>, Visibility<[ClangOption, CLOption]>,
     HelpText<"Generate instrumented code to collect coverage info for cold functions into default.profraw file (overridden by '=' form of option or LLVM_PROFILE_FILE env var)">;
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index c5a92a8e7ceb0..253cb03230b48 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -852,9 +852,11 @@ void CGDebugInfo::CreateCompileUnit() {
 
   StringRef Sysroot, SDK;
   if (CGM.getCodeGenOpts().getDebuggerTuning() == llvm::DebuggerKind::LLDB) {
-    Sysroot = CGM.getHeaderSearchOpts().Sysroot;
-    auto B = llvm::sys::path::rbegin(Sysroot);
-    auto E = llvm::sys::path::rend(Sysroot);
+    StringRef FullSysroot = CGM.getHeaderSearchOpts().Sysroot;
+    if (CGM.getCodeGenOpts().DebugRecordSysroot)
+      Sysroot = FullSysroot;
+    auto B = llvm::sys::path::rbegin(FullSysroot);
+    auto E = llvm::sys::path::rend(FullSysroot);
     auto It =
         std::find_if(B, E, [](auto SDK) { return SDK.ends_with(".sdk"); });
     if (It != E)
@@ -3529,7 +3531,12 @@ llvm::DIModule *CGDebugInfo::getOrCreateModuleRef(ASTSourceDescriptor Mod,
       IsRootModule ? nullptr
                    : getOrCreateModuleRef(ASTSourceDescriptor(*M->Parent),
                                           CreateSkeletonCU);
-  std::string IncludePath = Mod.getPath().str();
+  StringRef IncludePath = Mod.getPath();
+  if (!CGM.getCodeGenOpts().DebugRecordSysroot) {
+    StringRef Sysroot = CGM.getHeaderSearchOpts().Sysroot;
+    if (!Sysroot.empty() && IncludePath.starts_with(Sysroot))
+      IncludePath = "";
+  }
   llvm::DIModule *DIMod =
       DBuilder.createModule(Parent, Mod.getModuleName(), ConfigMacros,
                             RemapPath(IncludePath));
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 267e674441599..ecd5bf5a31fce 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -4460,6 +4460,10 @@ renderDebugOptions(const ToolChain &TC, const Driver &D, const llvm::Triple &T,
 
   addDebugInfoForProfilingArgs(D, TC, Args, CmdArgs);
 
+  if (!Args.hasFlag(options::OPT_fdebug_record_sysroot,
+                    options::OPT_fno_debug_record_sysroot, true))
+    CmdArgs.push_back("-fno-debug-record-sysroot");
+
   // The 'g' groups options involve a somewhat intricate sequence of decisions
   // about what to pass from the driver to the frontend, but by the time they
   // reach cc1 they've been factored into three well-defined orthogonal choices:
diff --git a/clang/test/DebugInfo/Generic/sysroot-sdk.c b/clang/test/DebugInfo/Generic/sysroot-sdk.c
index b52d2e1de9c1d..b424e62f5d3b0 100644
--- a/clang/test/DebugInfo/Generic/sysroot-sdk.c
+++ b/clang/test/DebugInfo/Generic/sysroot-sdk.c
@@ -4,6 +4,10 @@
 // RUN: %clang_cc1 -debug-info-kind=limited -triple %itanium_abi_triple \
 // RUN:   %s -isysroot /CLANG_SYSROOT/MacOSX.sdk -emit-llvm -o - \
 // RUN:   -debugger-tuning=gdb | FileCheck %s --check-prefix=GDB
+// RUN: %clang_cc1 -debug-info-kind=limited -triple %itanium_abi_triple \
+// RUN:   %s -isysroot /CLANG_SYSROOT/MacOSX.sdk -emit-llvm -o - \
+// RUN:   -debugger-tuning=lldb -fno-debug-record-sysroot \
+// RUN:   | FileCheck %s --check-prefix=NOSYSROOT
 
 void foo(void) {}
 
@@ -14,3 +18,8 @@ void foo(void) {}
 // GDB: distinct !DICompileUnit(
 // GDB-NOT: sysroot: "/CLANG_SYSROOT/MacOSX.sdk"
 // GDB-NOT: sdk: "MacOSX.sdk"
+
+// -fno-debug-record-sysroot suppresses the sysroot path but keeps the SDK marker.
+// NOSYSROOT: distinct !DICompileUnit(
+// NOSYSROOT-NOT: sysroot: "/CLANG_SYSROOT/MacOSX.sdk"
+// NOSYSROOT-SAME: sdk: "MacOSX.sdk"
diff --git a/clang/test/Modules/debug-info-moduleimport.m b/clang/test/Modules/debug-info-moduleimport.m
index bdeb05bc08a02..b242364e7100e 100644
--- a/clang/test/Modules/debug-info-moduleimport.m
+++ b/clang/test/Modules/debug-info-moduleimport.m
@@ -44,3 +44,16 @@
 // SKEL-CHECK: ![[CUFILE]] = !DIFile({{.*}}directory: "[[COMP_DIR:.*]]"
 // SKEL-CHECK: distinct !DICompileUnit({{.*}}file: ![[DWOFILE:[0-9]+]]{{.*}}splitDebugFilename: "/MODULE-CACHE{{.*}}dwoId
 // SKEL-CHECK: ![[DWOFILE]] = !DIFile({{.*}}directory: "[[COMP_DIR]]"
+
+// With -fno-debug-record-sysroot and a sysroot that covers the module's
+// include path (%S contains %S/Inputs), the DICompileUnit has no sysroot
+// field and the DIModule has no includePath field.
+// RUN: rm -rf %t
+// RUN: %clang_cc1 -debug-info-kind=limited -fmodules -fimplicit-module-maps \
+// RUN:   -fmodules-cache-path=%t/cache %s -I %S/Inputs -isysroot %S -I %t \
+// RUN:   -emit-llvm -debugger-tuning=lldb -fno-debug-record-sysroot -o - \
+// RUN:   | FileCheck %s --check-prefix=NO-SYSROOT
+
+// NO-SYSROOT-NOT: sysroot:
+// NO-SYSROOT-NOT: includePath:
+// NO-SYSROOT: !DIModule(scope: null, name: "DebugObjC")

@llvmbot
Copy link
Copy Markdown
Member

llvmbot commented Apr 16, 2026

@llvm/pr-subscribers-clang

Author: Keith Smiley (keith)

Changes

This enables excluding the absolute path to the sysroot from debug info
for reproducible builds. These fields are used by lldb, which also has
fallbacks since it's possible these paths don't exist on the machine
doing the debugging when built remotely anyways.

This was also possible using -fdebug-prefix-map=/path/to/Xcode.app=/some/path
but depending on the environment you might not be able to easily pass
that with the user specific developer directory path.

Assisted by: claude


Full diff: https://github.com/llvm/llvm-project/pull/192541.diff

6 Files Affected:

  • (modified) clang/include/clang/Basic/DebugOptions.def (+3)
  • (modified) clang/include/clang/Options/Options.td (+7)
  • (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+11-4)
  • (modified) clang/lib/Driver/ToolChains/Clang.cpp (+4)
  • (modified) clang/test/DebugInfo/Generic/sysroot-sdk.c (+9)
  • (modified) clang/test/Modules/debug-info-moduleimport.m (+13)
diff --git a/clang/include/clang/Basic/DebugOptions.def b/clang/include/clang/Basic/DebugOptions.def
index 604e87e615a69..c9dd3f726e799 100644
--- a/clang/include/clang/Basic/DebugOptions.def
+++ b/clang/include/clang/Basic/DebugOptions.def
@@ -125,6 +125,9 @@ DEBUGOPT(CodeViewCommandLine, 1, 0, Compatible)
 /// Whether emit extra debug info for sample pgo profile collection.
 DEBUGOPT(DebugInfoForProfiling, 1, 0, Compatible)
 
+/// Whether to record the sysroot path or scrub it from debug info.
+DEBUGOPT(DebugRecordSysroot, 1, 1, Compatible)
+
 /// Whether to emit DW_TAG_template_alias for template aliases.
 DEBUGOPT(DebugTemplateAlias, 1, 0, Compatible)
 
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 5673fb0c47d5b..0afbb6f1d0b37 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -1823,6 +1823,13 @@ defm debug_info_for_profiling : BoolFOption<"debug-info-for-profiling",
   PosFlag<SetTrue, [], [ClangOption, CC1Option, FlangOption, FC1Option],
           "Emit extra debug info to make sample profile more accurate">,
   NegFlag<SetFalse, [], [ClangOption, FlangOption]>>;
+defm debug_record_sysroot : BoolFOption<"debug-record-sysroot",
+  CodeGenOpts<"DebugRecordSysroot">, DefaultTrue,
+  PosFlag<SetTrue, [], [ClangOption, CC1Option],
+          "Record the sysroot path in debug info as DW_AT_LLVM_sysroot and in DW_AT_LLVM_include_path "
+          "(used by LLDB to locate the SDK for Clang module imports).">,
+  NegFlag<SetFalse, [], [ClangOption, CC1Option],
+          "Scrub the sysroot path from debug info.">>;
 def fprofile_generate_cold_function_coverage : Flag<["-"], "fprofile-generate-cold-function-coverage">,
     Group<f_Group>, Visibility<[ClangOption, CLOption]>,
     HelpText<"Generate instrumented code to collect coverage info for cold functions into default.profraw file (overridden by '=' form of option or LLVM_PROFILE_FILE env var)">;
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index c5a92a8e7ceb0..253cb03230b48 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -852,9 +852,11 @@ void CGDebugInfo::CreateCompileUnit() {
 
   StringRef Sysroot, SDK;
   if (CGM.getCodeGenOpts().getDebuggerTuning() == llvm::DebuggerKind::LLDB) {
-    Sysroot = CGM.getHeaderSearchOpts().Sysroot;
-    auto B = llvm::sys::path::rbegin(Sysroot);
-    auto E = llvm::sys::path::rend(Sysroot);
+    StringRef FullSysroot = CGM.getHeaderSearchOpts().Sysroot;
+    if (CGM.getCodeGenOpts().DebugRecordSysroot)
+      Sysroot = FullSysroot;
+    auto B = llvm::sys::path::rbegin(FullSysroot);
+    auto E = llvm::sys::path::rend(FullSysroot);
     auto It =
         std::find_if(B, E, [](auto SDK) { return SDK.ends_with(".sdk"); });
     if (It != E)
@@ -3529,7 +3531,12 @@ llvm::DIModule *CGDebugInfo::getOrCreateModuleRef(ASTSourceDescriptor Mod,
       IsRootModule ? nullptr
                    : getOrCreateModuleRef(ASTSourceDescriptor(*M->Parent),
                                           CreateSkeletonCU);
-  std::string IncludePath = Mod.getPath().str();
+  StringRef IncludePath = Mod.getPath();
+  if (!CGM.getCodeGenOpts().DebugRecordSysroot) {
+    StringRef Sysroot = CGM.getHeaderSearchOpts().Sysroot;
+    if (!Sysroot.empty() && IncludePath.starts_with(Sysroot))
+      IncludePath = "";
+  }
   llvm::DIModule *DIMod =
       DBuilder.createModule(Parent, Mod.getModuleName(), ConfigMacros,
                             RemapPath(IncludePath));
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 267e674441599..ecd5bf5a31fce 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -4460,6 +4460,10 @@ renderDebugOptions(const ToolChain &TC, const Driver &D, const llvm::Triple &T,
 
   addDebugInfoForProfilingArgs(D, TC, Args, CmdArgs);
 
+  if (!Args.hasFlag(options::OPT_fdebug_record_sysroot,
+                    options::OPT_fno_debug_record_sysroot, true))
+    CmdArgs.push_back("-fno-debug-record-sysroot");
+
   // The 'g' groups options involve a somewhat intricate sequence of decisions
   // about what to pass from the driver to the frontend, but by the time they
   // reach cc1 they've been factored into three well-defined orthogonal choices:
diff --git a/clang/test/DebugInfo/Generic/sysroot-sdk.c b/clang/test/DebugInfo/Generic/sysroot-sdk.c
index b52d2e1de9c1d..b424e62f5d3b0 100644
--- a/clang/test/DebugInfo/Generic/sysroot-sdk.c
+++ b/clang/test/DebugInfo/Generic/sysroot-sdk.c
@@ -4,6 +4,10 @@
 // RUN: %clang_cc1 -debug-info-kind=limited -triple %itanium_abi_triple \
 // RUN:   %s -isysroot /CLANG_SYSROOT/MacOSX.sdk -emit-llvm -o - \
 // RUN:   -debugger-tuning=gdb | FileCheck %s --check-prefix=GDB
+// RUN: %clang_cc1 -debug-info-kind=limited -triple %itanium_abi_triple \
+// RUN:   %s -isysroot /CLANG_SYSROOT/MacOSX.sdk -emit-llvm -o - \
+// RUN:   -debugger-tuning=lldb -fno-debug-record-sysroot \
+// RUN:   | FileCheck %s --check-prefix=NOSYSROOT
 
 void foo(void) {}
 
@@ -14,3 +18,8 @@ void foo(void) {}
 // GDB: distinct !DICompileUnit(
 // GDB-NOT: sysroot: "/CLANG_SYSROOT/MacOSX.sdk"
 // GDB-NOT: sdk: "MacOSX.sdk"
+
+// -fno-debug-record-sysroot suppresses the sysroot path but keeps the SDK marker.
+// NOSYSROOT: distinct !DICompileUnit(
+// NOSYSROOT-NOT: sysroot: "/CLANG_SYSROOT/MacOSX.sdk"
+// NOSYSROOT-SAME: sdk: "MacOSX.sdk"
diff --git a/clang/test/Modules/debug-info-moduleimport.m b/clang/test/Modules/debug-info-moduleimport.m
index bdeb05bc08a02..b242364e7100e 100644
--- a/clang/test/Modules/debug-info-moduleimport.m
+++ b/clang/test/Modules/debug-info-moduleimport.m
@@ -44,3 +44,16 @@
 // SKEL-CHECK: ![[CUFILE]] = !DIFile({{.*}}directory: "[[COMP_DIR:.*]]"
 // SKEL-CHECK: distinct !DICompileUnit({{.*}}file: ![[DWOFILE:[0-9]+]]{{.*}}splitDebugFilename: "/MODULE-CACHE{{.*}}dwoId
 // SKEL-CHECK: ![[DWOFILE]] = !DIFile({{.*}}directory: "[[COMP_DIR]]"
+
+// With -fno-debug-record-sysroot and a sysroot that covers the module's
+// include path (%S contains %S/Inputs), the DICompileUnit has no sysroot
+// field and the DIModule has no includePath field.
+// RUN: rm -rf %t
+// RUN: %clang_cc1 -debug-info-kind=limited -fmodules -fimplicit-module-maps \
+// RUN:   -fmodules-cache-path=%t/cache %s -I %S/Inputs -isysroot %S -I %t \
+// RUN:   -emit-llvm -debugger-tuning=lldb -fno-debug-record-sysroot -o - \
+// RUN:   | FileCheck %s --check-prefix=NO-SYSROOT
+
+// NO-SYSROOT-NOT: sysroot:
+// NO-SYSROOT-NOT: includePath:
+// NO-SYSROOT: !DIModule(scope: null, name: "DebugObjC")

@Michael137
Copy link
Copy Markdown
Member

Possibly orthogonal to the discussion, but we've previously talked about removing the need for DW_LLVM_AT_include_path entirely (I think @cachemeifyoucan mentioned that it's incompatible with the LLVM CAS work). Not sure about sysroot though. I guess LLDB uses it to determine which SDK to reach into when needed, so we probably do need it there.

@keith could you elaborate on the "reproducible builds" part?

@adrian-prantl
Copy link
Copy Markdown
Contributor

I (think I) understand the problem you want solve, but I don't think this is a good solution because it breaks LLDB's ability to detect what SDK type the object file was built with. On Darwin-derived operating systems LLDB really only consumes the name of the SDK directory, because it will want to look up a compatible sysroot relative to where LLDB is running (as opposed to where the object file was built). I would propose that for the sysroot in the compile unit we either

  • only store the .sdk directory name
  • introduce a new DW_AT_APPLE_sdk attribute for this purpose

For the sysroot/include_dir in DW_TAG_module, the former should be redundant with the sysroot of the compile unit, but LLDB needs at least the latter to attempt to import the Clang module from source. The key to making that reproducible should IMHO be a prefix remapping.

I need to double-check what we practically pass as sysroot on Windows and Linux (is it always / or always the install prefix of the toolchain)? In both cases the information contained in it seems to not carry too much weight, assuming LLDB is part of the same toolchain.

@cachemeifyoucan
Copy link
Copy Markdown
Collaborator

For our implementation of caching work, DW_LLVM_AT_include_path is not needed (or sysroot) unless there is a relative include path.

I don't think I want to have an opinion here since this is not in the path CAS based caching compilation is done. This is fine as long as it is a configurable build configuration and people decide to scrub the sys root knows the tradeoff.

Also I really hope we don't use the name of SDK directory to do some meaningful decision in lldb. @adrian-prantl we need to change that!

@keith
Copy link
Copy Markdown
Member Author

keith commented Apr 17, 2026

@keith could you elaborate on the "reproducible builds" part?

the case I'm interested in at the moment is that if you build clang foo.m -c -g -o foo.o -ffile-compilation-dir=. the debug info has an absolute path to your Xcode installation, which may not be the same as the path on the machine you end up debugging on in the case of distributed builds. Today you can fix this with -fdebug-prefix-map=/path/to/xcode.app=/PLACEHOLDER but that's potentially annoying to fit into your build system.

On Darwin-derived operating systems LLDB really only consumes the name of the SDK directory

I might be misunderstanding the issue here, but in this patch I left the DW_AT_APPLE_sdk, so I think that info is still there?

This is with -fno-debug-record-sysroot:

0x0000000c: DW_TAG_compile_unit
              DW_AT_producer	("clang version 23.0.0git (https://github.com/llvm/llvm-project 860e78e212c2bb9091fb6a96160c40e9d5f80491)")
              DW_AT_language	(DW_LANG_ObjC)
              DW_AT_name	("foo.m")
              DW_AT_APPLE_sdk	("MacOSX.sdk")

introduce a new DW_AT_APPLE_sdk attribute for this purpose

see above.

but LLDB needs at least the latter to attempt to import the Clang module from source. The key to making that reproducible should IMHO be a prefix remapping.

if this is the case than likely the -fdebug-prefix-map flag I mentioned above is the solution. Although what happens in the case that path is not valid? Since at least today in our builds we leave it as the /PLACEHOLDER and debugging still works fine, but even if we didn't and just left this full path in there, it could be invalid if the Xcode path is different.

For our implementation of caching work, DW_LLVM_AT_include_path is not needed (or sysroot) unless there is a relative include path.

are you doing anything special to handle this path or just letting lldb's fallback logic deal with it?

@adrian-prantl
Copy link
Copy Markdown
Contributor

Also I really hope we don't use the name of SDK directory to do some meaningful decision in lldb. @adrian-prantl we need to change that!

It's definitely used. Just to be clear: only the last path component (e.g.: MacOSX.sdk) is parsed by lldb_private::XcodeSDK and then used to find a matching SDK on the host system.

See the uses of GetSDKPathFromDebugInfo for example:
https://github.com/swiftlang/llvm-project/blob/4a8f552d072f7dfdecc241c02696f82761ec0596/lldb/source/Plugins/TypeSystem/Swift/SwiftASTContext.cpp#L2338

@adrian-prantl
Copy link
Copy Markdown
Contributor

@keith Sorry, you are correct. I forgot that we already recorded SDK and sysroot separately. LLDB is currently using the sysroot for Clang module importing, and to automagically remap source paths pointing into the original sysroot (

// corresponding debug map, in which case both should be updated.
). Especially the second use-case should be irrelevant, if you already prove the remapping yourself, so I retract my concerns with this patch!

@MaskRay
Copy link
Copy Markdown
Member

MaskRay commented Apr 22, 2026

This was also possible using -fdebug-prefix-map=/path/to/Xcode.app=/some/path but depending on the environment you might not be able to easily pass that with the user specific developer directory path.

You can also use -fdebug-compilation-dir=.

@adrian-prantl adrian-prantl requested a review from Teemperor April 22, 2026 00:36
@keith
Copy link
Copy Markdown
Member Author

keith commented Apr 22, 2026

You can also use -fdebug-compilation-dir=.

Not for this case since the compilation root likely isn't within the Xcode directory

@Teemperor
Copy link
Copy Markdown
Collaborator

I think the Objective-C/C module importing in LLDB might be affected by this, but that code has so many fallbacks that for standard use cases (e.g., importing Foundation) it probably works just fine with this patch/flag enabled. At least our test for this can find Foundation just fine.

Also an LLDB test case as part of this patch would be nice. Otherwise I would just check in the test case below as a follow up PR (depends on #194357 in case you have to cherry-pick).

diff --git a/lldb/test/API/lang/objc/modules-auto-import/Makefile b/lldb/test/API/lang/objc/modules-auto-import/Makefile
index 3b2bd504c89d..5a8aeda2e250 100644
--- a/lldb/test/API/lang/objc/modules-auto-import/Makefile
+++ b/lldb/test/API/lang/objc/modules-auto-import/Makefile
@@ -1,5 +1,5 @@
 OBJC_SOURCES := main.m
 
-CFLAGS_EXTRAS = $(MANDATORY_MODULE_BUILD_CFLAGS)
+CFLAGS_EXTRAS = $(MANDATORY_MODULE_BUILD_CFLAGS) $(TEST_EXTRA_FLAGS)
 
 include Makefile.rules
diff --git a/lldb/test/API/lang/objc/modules-auto-import/TestModulesAutoImport.py b/lldb/test/API/lang/objc/modules-auto-import/TestModulesAutoImport.py
index 9c8ba88bc6d8..bc75e7b7bc02 100644
--- a/lldb/test/API/lang/objc/modules-auto-import/TestModulesAutoImport.py
+++ b/lldb/test/API/lang/objc/modules-auto-import/TestModulesAutoImport.py
@@ -8,6 +8,7 @@ from lldbsuite.test import lldbutil
 
 
 class ObjCModulesAutoImportTestCase(TestBase):
+    SHARED_BUILD_TESTCASE = False
 
     @skipIf(macos_version=["<", "10.12"])
     @skipIf(compiler="clang", compiler_version=["<", "19.0"])
@@ -19,3 +20,14 @@ class ObjCModulesAutoImportTestCase(TestBase):
 
         self.runCmd("settings set target.auto-import-clang-modules true")
         self.expect_expr("getpid()", result_type="pid_t")
+
+    @skipIf(macos_version=["<", "10.12"])
+    @skipIf(compiler="clang", compiler_version=["<", "19.0"])
+    def test_expr_no_sysroot(self):
+        self.build(dictionary={"TEST_EXTRA_FLAGS": f"-fno-debug-record-sysroot"})
+        lldbutil.run_to_source_breakpoint(
+            self, "// break here", lldb.SBFileSpec("main.m", False)
+        )
+
+        self.runCmd("settings set target.auto-import-clang-modules true")
+        self.expect_expr("getpid()", result_type="pid_t")

keith added 2 commits April 27, 2026 11:54
This enables excluding the absolute path to the sysroot from debug info
for reproducible builds. These fields are used by lldb, which also has
fallbacks since it's possible these paths don't exist on the machine
doing the debugging when built remotely anyways.

This was also possible using `-fdebug-prefix-map=/path/to/Xcode.app=/some/path`
but depending on the environment you might not be able to easily pass
that with the user specific developer directory path.

Assisted by: claude
@keith keith force-pushed the ks/clang-add-fno-debug-record-sysroot branch from 20a8f42 to f553895 Compare April 27, 2026 18:54
@keith keith requested a review from JDevlieghere as a code owner April 27, 2026 18:54
@llvmbot llvmbot added the lldb label Apr 27, 2026
@keith
Copy link
Copy Markdown
Member Author

keith commented Apr 27, 2026

applied your patch, we'll see what CI thinks. my understanding from the code was definitely that lldb's fallback behavior would actually be the ideal for my case anyways, since I'm trying to normalize across arbitrary Xcode install paths, and really lldb using the one that is selected at time of debug is what I'd want

Copy link
Copy Markdown
Collaborator

@Teemperor Teemperor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally don't have any objections assuming the CI is happy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:codegen IR generation bugs: mangling, exceptions, etc. clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules clang Clang issues not falling into any other category debuginfo lldb

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants