Skip to content

[lldb][HostInfoMacOSX] Try to use DW_AT_LLVM_sysroot instead of xcrun when looking up SDK #128712

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

Merged
merged 8 commits into from
Mar 3, 2025

Conversation

Michael137
Copy link
Member

@Michael137 Michael137 commented Feb 25, 2025

GetSDKRoot uses xcrun to find an SDK root path for a given SDK version string. But if the SDK doesn't exist in the Xcode installations, but instead lives in the CommandLineTools, xcrun will fail to find it. Negative searches for an SDK path cost a lot (a few seconds) each time xcrun is invoked. We do cache negative results in find_cached_path inside LLDB, but we would still pay the price on every new debug session the first time we evaluate an expression. This doesn't only cause a noticable delay in running the expression, but also generates following error:

error: Error while searching for Xcode SDK: timed out waiting for shell command to complete
(int) $0 = 42

In this patch we avoid these possibly expensive calls to xcrun by checking the DW_AT_LLVM_sysroot, and if it exists, using that as the SDK path. We need an explicit check for the CommandLineTools path before we call RegisterXcodeSDK, because that will try to call xcrun. This won't prevent other uses of GetSDKRoot popping up that cause us to make expensive xcrun calls, but for now this addresses the regression in the expression evaluator. We also had to adjust the XcodeSDK::Merge logic to update the sysroot. There is one case for which this wouldn't make sense: if a CU was compiled with CommandLineTools and a different one with an older internal SDK, in that case we would update the CommandLineTools sysroot with a .Internal.sdk prefix, which won't possibly exist for CommandLineTools. I added a unit-test for this. Not sure if we want to explicitly detect and disallow this, given it's quite a niche scenario.

rdar://113619904
rdar://113619723

…up SDK paths

`GetSDKRoot` uses `xcrun` to find an SDK root path for a given SDK
version string. But if the SDK doesn't exist in the Xcode installations,
but instead lives in the `CommandLineTools`, `xcrun` will fail to find
it. Negative searches for an SDK path cost a lot (a few seconds) each
time `xcrun` is invoked. We do cache negative results in
`find_cached_path` inside LLDB, but we would still pay the price on
every new debug session the first time we evaluate an expression. This
doesn't only cause a noticable delay in running the expression, but also
generates following error:
```
error: Error while searching for Xcode SDK: timed out waiting for shell command to complete
(int) $0 = 42
```

To avoid this `xcrun` penalty, we search `CommandLineTools` for a
matching SDK ourselves, and only if we don't find it, do we fall back to
calling `xcrun`.

rdar://113619904
rdar://113619723
@llvmbot
Copy link
Member

llvmbot commented Feb 25, 2025

@llvm/pr-subscribers-lldb

Author: Michael Buch (Michael137)

Changes

GetSDKRoot uses xcrun to find an SDK root path for a given SDK version string. But if the SDK doesn't exist in the Xcode installations, but instead lives in the CommandLineTools, xcrun will fail to find it. Negative searches for an SDK path cost a lot (a few seconds) each time xcrun is invoked. We do cache negative results in find_cached_path inside LLDB, but we would still pay the price on every new debug session the first time we evaluate an expression. This doesn't only cause a noticable delay in running the expression, but also generates following error:

error: Error while searching for Xcode SDK: timed out waiting for shell command to complete
(int) $0 = 42

To avoid this xcrun penalty, we search CommandLineTools for a matching SDK ourselves, and only if we don't find it, do we fall back to calling xcrun.

rdar://113619904
rdar://113619723


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

2 Files Affected:

  • (modified) lldb/include/lldb/Host/FileSystem.h (+3-2)
  • (modified) lldb/source/Host/macosx/objcxx/HostInfoMacOSX.mm (+60)
diff --git a/lldb/include/lldb/Host/FileSystem.h b/lldb/include/lldb/Host/FileSystem.h
index 640f3846e448c..4128d7b012041 100644
--- a/lldb/include/lldb/Host/FileSystem.h
+++ b/lldb/include/lldb/Host/FileSystem.h
@@ -183,8 +183,9 @@ class FileSystem {
     eEnumerateDirectoryResultQuit
   };
 
-  typedef EnumerateDirectoryResult (*EnumerateDirectoryCallbackType)(
-      void *baton, llvm::sys::fs::file_type file_type, llvm::StringRef);
+  typedef std::function<EnumerateDirectoryResult(
+      void *baton, llvm::sys::fs::file_type file_type, llvm::StringRef)>
+      EnumerateDirectoryCallbackType;
 
   typedef std::function<EnumerateDirectoryResult(
       llvm::sys::fs::file_type file_type, llvm::StringRef)>
diff --git a/lldb/source/Host/macosx/objcxx/HostInfoMacOSX.mm b/lldb/source/Host/macosx/objcxx/HostInfoMacOSX.mm
index 6e924fdc684cf..a94fd3b57f9d6 100644
--- a/lldb/source/Host/macosx/objcxx/HostInfoMacOSX.mm
+++ b/lldb/source/Host/macosx/objcxx/HostInfoMacOSX.mm
@@ -15,11 +15,14 @@
 #include "lldb/Utility/Log.h"
 #include "lldb/Utility/Timer.h"
 
+#include "clang/Basic/DarwinSDKInfo.h"
 #include "llvm/ADT/ScopeExit.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringMap.h"
+#include "llvm/Support/Error.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/Path.h"
+#include "llvm/Support/VersionTuple.h"
 #include "llvm/Support/raw_ostream.h"
 
 // C++ Includes
@@ -569,10 +572,52 @@ static bool ResolveAndVerifyCandidateSupportDir(FileSpec &path) {
     cache.insert({key, {error, true}});
     return llvm::createStringError(llvm::inconvertibleErrorCode(), error);
   }
+
+  if (path_or_err->empty())
+    return llvm::createStringError("Empty path determined for '%s'",
+                                   key.data());
+
   auto it_new = cache.insert({key, {*path_or_err, false}});
   return it_new.first->second.str;
 }
 
+static llvm::Expected<std::string>
+GetCommandLineToolsSDKRoot(llvm::VersionTuple version) {
+  std::string clt_root_dir;
+  FileSystem::Instance().EnumerateDirectory(
+      "/Library/Developer/CommandLineTools/SDKs/", /*find_directories=*/true,
+      /*find_files=*/false, /*find_other=*/false,
+      [&](void *baton, llvm::sys::fs::file_type file_type,
+          llvm::StringRef name) {
+        assert(file_type == llvm::sys::fs::file_type::directory_file);
+
+        if (!name.ends_with(".sdk"))
+          return FileSystem::eEnumerateDirectoryResultNext;
+
+        llvm::Expected<std::optional<clang::DarwinSDKInfo>> sdk_info =
+            clang::parseDarwinSDKInfo(
+                *FileSystem::Instance().GetVirtualFileSystem(), name);
+        if (!sdk_info) {
+          LLDB_LOG_ERROR(GetLog(LLDBLog::Expressions), sdk_info.takeError(),
+                         "Error while parsing {1}: {0}", name);
+          return FileSystem::eEnumerateDirectoryResultNext;
+        }
+
+        if (!*sdk_info)
+          return FileSystem::eEnumerateDirectoryResultNext;
+
+        if (version == (*sdk_info)->getVersion()) {
+          clt_root_dir = name;
+          return FileSystem::eEnumerateDirectoryResultQuit;
+        }
+
+        return FileSystem::eEnumerateDirectoryResultNext;
+      },
+      /*baton=*/nullptr);
+
+  return clt_root_dir;
+}
+
 llvm::Expected<llvm::StringRef> HostInfoMacOSX::GetSDKRoot(SDKOptions options) {
   static llvm::StringMap<ErrorOrPath> g_sdk_path;
   static std::mutex g_sdk_path_mutex;
@@ -581,6 +626,21 @@ static bool ResolveAndVerifyCandidateSupportDir(FileSpec &path) {
                                    "XcodeSDK not specified");
   XcodeSDK sdk = *options.XcodeSDKSelection;
   auto key = sdk.GetString();
+
+  // xcrun doesn't search SDKs in the CommandLineTools (CLT) directory. So if
+  // a program was compiled against a CLT SDK, but that SDK wasn't present in
+  // any of the Xcode installations, then xcrun would fail to find the SDK
+  // (which is expensive). To avoid this we first try to find the specified SDK
+  // in the CLT directory.
+  auto clt_root_dir = find_cached_path(g_sdk_path, g_sdk_path_mutex, key, [&] {
+    return GetCommandLineToolsSDKRoot(sdk.GetVersion());
+  });
+
+  if (clt_root_dir)
+    return clt_root_dir;
+  else
+    llvm::consumeError(clt_root_dir.takeError());
+
   return find_cached_path(g_sdk_path, g_sdk_path_mutex, key, [&](){
     return GetXcodeSDK(sdk);
   });

@@ -15,11 +15,14 @@
#include "lldb/Utility/Log.h"
#include "lldb/Utility/Timer.h"

#include "clang/Basic/DarwinSDKInfo.h"
Copy link
Member Author

Choose a reason for hiding this comment

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

Is this a dependency we can pull in? Probably not?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, this goes against all the work of localizing the places we depend on clang.

if (!name.ends_with(".sdk"))
return FileSystem::eEnumerateDirectoryResultNext;

llvm::Expected<std::optional<clang::DarwinSDKInfo>> sdk_info =
Copy link
Member Author

Choose a reason for hiding this comment

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

Alternatively we can scan the .plist using LLDB's ApplePropertyList, and extract the SDK version/name from there. That would allow us not to depend on Clang here

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm but looks like it's only able to parse the XML version, not the binary one?

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess worst case we could read the json ourselves and get the key we're looking for (Clang does a bit more than that, it looks at VersionMappings etc.)

Copy link
Member

Choose a reason for hiding this comment

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

I think it should support both. I'm sure it can write binary property lists. I would assume it can read them too.

Copy link
Member

@JDevlieghere JDevlieghere left a comment

Choose a reason for hiding this comment

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

This feels like it's working around a shortcoming of xcrun. Since that's somewhat within our control, have we asked the team to look into the CLT for an SDK? They already do for binaries so I'm not sure why the SDK would need to behave differently.

@@ -15,11 +15,14 @@
#include "lldb/Utility/Log.h"
#include "lldb/Utility/Timer.h"

#include "clang/Basic/DarwinSDKInfo.h"
Copy link
Member

Choose a reason for hiding this comment

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

Yeah, this goes against all the work of localizing the places we depend on clang.

// (which is expensive). To avoid this we first try to find the specified SDK
// in the CLT directory.
auto clt_root_dir = find_cached_path(g_sdk_path, g_sdk_path_mutex, key, [&] {
return GetCommandLineToolsSDKRoot(sdk.GetVersion());
Copy link
Member

Choose a reason for hiding this comment

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

Does this mean we're going to prefer the CommandLineTools SDK over the Xcode one when both exist? I know they should be the same, but I'd really prefer to avoid the CLT whenever we can.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That's my only concern with this, but if we wanted to avoid this behavior, then we would need to reimplement all of xcrun's logic to find SDKs in Xcode. That's also not something I'm excited about.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, you don't know you need the CLT until you know it's not in Xcode, which is exactly the problem this is trying to avoid.

@JDevlieghere JDevlieghere changed the title [lldb]HostInfoMacOSX] Search CommandLineTools directory when looking up SDK paths [lldb][HostInfoMacOSX] Search CommandLineTools directory when looking up SDK paths Feb 26, 2025
@Michael137 Michael137 marked this pull request as draft February 28, 2025 12:56
@Michael137 Michael137 force-pushed the bugfix/lldb-clt-sdk-root branch from bd5b896 to 96ae6db Compare February 28, 2025 12:58
@Michael137
Copy link
Member Author

Ok so latest approach is to check the DW_AT_LLVM_sysroot, and if that path exists, we don't run xcrun. I removed the GetSDKPathFromDebugInfo overload that iterated over all CUs and merged the XcodeSDK objects (@adrian-prantl said this was flawed anyway and we shouldn't require this overload). So now I'm just looking at the first CUs DW_AT_LLVM_sysroot.

Copy link
Member

@JDevlieghere JDevlieghere left a comment

Choose a reason for hiding this comment

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

I definitely like this approach much better. Two suggestions:

  • Should this use a FileSpec instead of a std::string for the sysroot?
  • I like the simplicity of a std::pair but on the other hand it's hard to tell what the string represents. I think it would help to either store the sysroot in the XcodeSDK (potentially as an std::optional) or having this return a struct with named fiels (e.g. xcode_sdk, sysroot). Putting the sysroot in the XcodeSDK means you don't have to update the Doxygen comments which are now all outdated.

@Michael137
Copy link
Member Author

Michael137 commented Feb 28, 2025

I definitely like this approach much better. Two suggestions:

  • Should this use a FileSpec instead of a std::string for the sysroot?

Yea I don't mind doing that. There's no concrete need for it but it's easier to reason about than a raw string I suppose

  • I like the simplicity of a std::pair but on the other hand it's hard to tell what the string represents. I think it would help to either store the sysroot in the XcodeSDK (potentially as an std::optional) or having this return a struct with named fiels (e.g. xcode_sdk, sysroot). Putting the sysroot in the XcodeSDK means you don't have to update the Doxygen comments which are now all outdated.

Agreed. I was toying around with storing it inside XcodeSDK. Though it's a bit awkward to have a concrete sysroot path since the XcodeSDK class currently represents a merged view of all SDKs (which may have different sysroots). Or at least that's how I interpreted this class. Happy to change it to a named structure. Don't have a strong opinion on this

@Michael137 Michael137 changed the title [lldb][HostInfoMacOSX] Search CommandLineTools directory when looking up SDK paths [lldb][HostInfoMacOSX] Try to use DW_AT_LLVM_sysroot instead of xcrun when looking up SDK Mar 3, 2025
@Michael137
Copy link
Member Author

Regarding source mapping tests, see #129526

@Michael137 Michael137 marked this pull request as ready for review March 3, 2025 13:27
@Michael137
Copy link
Member Author

Michael137 commented Mar 3, 2025

Updated the PR to store the DW_AT_LLVM_sysroot as a FileSpec inside XcodeSDK. Also updated the PR description explaining this a bit more

Copy link
Member

@JDevlieghere JDevlieghere left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks!

@Michael137 Michael137 merged commit da293b8 into llvm:main Mar 3, 2025
10 checks passed
augusto2112 added a commit that referenced this pull request Mar 4, 2025
…of xcrun when looking up SDK" (#129621)

Reverts #128712


```
******************** TEST 'lldb-unit :: SymbolFile/DWARF/./SymbolFileDWARFTests/10/14' FAILED ********************
Script(shard):
--
GTEST_OUTPUT=json:/Users/ec2-user/jenkins/workspace/llvm.org/as-lldb-cmake/lldb-build/tools/lldb/unittests/SymbolFile/DWARF/./SymbolFileDWARFTests-lldb-unit-1021-10-14.json GTEST_SHUFFLE=1 GTEST_TOTAL_SHARDS=14 GTEST_SHARD_INDEX=10 GTEST_RANDOM_SEED=62233 /Users/ec2-user/jenkins/workspace/llvm.org/as-lldb-cmake/lldb-build/tools/lldb/unittests/SymbolFile/DWARF/./SymbolFileDWARFTests
--

Script:
--
/Users/ec2-user/jenkins/workspace/llvm.org/as-lldb-cmake/lldb-build/tools/lldb/unittests/SymbolFile/DWARF/./SymbolFileDWARFTests --gtest_filter=SDKPathParsingTests/SDKPathParsingMultiparamTests.TestSDKPathFromDebugInfo/6
--
/Users/ec2-user/jenkins/workspace/llvm.org/as-lldb-cmake/llvm-project/lldb/unittests/SymbolFile/DWARF/XcodeSDKModuleTests.cpp:265: Failure
Expected equality of these values:
  found_mismatch
    Which is: true
  expect_mismatch
    Which is: false


/Users/ec2-user/jenkins/workspace/llvm.org/as-lldb-cmake/llvm-project/lldb/unittests/SymbolFile/DWARF/XcodeSDKModuleTests.cpp:265
Expected equality of these values:
  found_mismatch
    Which is: true
  expect_mismatch
    Which is: false
```
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request Mar 4, 2025
…ot instead of xcrun when looking up SDK" (#129621)

Reverts llvm/llvm-project#128712

```
******************** TEST 'lldb-unit :: SymbolFile/DWARF/./SymbolFileDWARFTests/10/14' FAILED ********************
Script(shard):
--
GTEST_OUTPUT=json:/Users/ec2-user/jenkins/workspace/llvm.org/as-lldb-cmake/lldb-build/tools/lldb/unittests/SymbolFile/DWARF/./SymbolFileDWARFTests-lldb-unit-1021-10-14.json GTEST_SHUFFLE=1 GTEST_TOTAL_SHARDS=14 GTEST_SHARD_INDEX=10 GTEST_RANDOM_SEED=62233 /Users/ec2-user/jenkins/workspace/llvm.org/as-lldb-cmake/lldb-build/tools/lldb/unittests/SymbolFile/DWARF/./SymbolFileDWARFTests
--

Script:
--
/Users/ec2-user/jenkins/workspace/llvm.org/as-lldb-cmake/lldb-build/tools/lldb/unittests/SymbolFile/DWARF/./SymbolFileDWARFTests --gtest_filter=SDKPathParsingTests/SDKPathParsingMultiparamTests.TestSDKPathFromDebugInfo/6
--
/Users/ec2-user/jenkins/workspace/llvm.org/as-lldb-cmake/llvm-project/lldb/unittests/SymbolFile/DWARF/XcodeSDKModuleTests.cpp:265: Failure
Expected equality of these values:
  found_mismatch
    Which is: true
  expect_mismatch
    Which is: false

/Users/ec2-user/jenkins/workspace/llvm.org/as-lldb-cmake/llvm-project/lldb/unittests/SymbolFile/DWARF/XcodeSDKModuleTests.cpp:265
Expected equality of these values:
  found_mismatch
    Which is: true
  expect_mismatch
    Which is: false
```
@Michael137 Michael137 deleted the bugfix/lldb-clt-sdk-root branch March 4, 2025 12:47
jph-13 pushed a commit to jph-13/llvm-project that referenced this pull request Mar 21, 2025
… when looking up SDK (llvm#128712)

`GetSDKRoot` uses `xcrun` to find an SDK root path for a given SDK
version string. But if the SDK doesn't exist in the Xcode installations,
but instead lives in the `CommandLineTools`, `xcrun` will fail to find
it. Negative searches for an SDK path cost a lot (a few seconds) each
time `xcrun` is invoked. We do cache negative results in
`find_cached_path` inside LLDB, but we would still pay the price on
every new debug session the first time we evaluate an expression. This
doesn't only cause a noticable delay in running the expression, but also
generates following error:
```
error: Error while searching for Xcode SDK: timed out waiting for shell command to complete
(int) $0 = 42
```

In this patch we avoid these possibly expensive calls to `xcrun` by
checking the `DW_AT_LLVM_sysroot`, and if it exists, using that as the
SDK path. We need an explicit check for the `CommandLineTools` path
before we call `RegisterXcodeSDK`, because that will try to call
`xcrun`. This won't prevent other uses of `GetSDKRoot` popping up that
cause us to make expensive `xcrun` calls, but for now this addresses the
regression in the expression evaluator. We also had to adjust the
`XcodeSDK::Merge` logic to update the sysroot. There is one case for
which this wouldn't make sense: if a CU was compiled with
`CommandLineTools` and a different one with an older internal SDK, in
that case we would update the `CommandLineTools` sysroot with a
`.Internal.sdk` prefix, which won't possibly exist for
`CommandLineTools`. I added a unit-test for this. Not sure if we want to
explicitly detect and disallow this, given it's quite a niche scenario.

rdar://113619904
rdar://113619723
jph-13 pushed a commit to jph-13/llvm-project that referenced this pull request Mar 21, 2025
…of xcrun when looking up SDK" (llvm#129621)

Reverts llvm#128712


```
******************** TEST 'lldb-unit :: SymbolFile/DWARF/./SymbolFileDWARFTests/10/14' FAILED ********************
Script(shard):
--
GTEST_OUTPUT=json:/Users/ec2-user/jenkins/workspace/llvm.org/as-lldb-cmake/lldb-build/tools/lldb/unittests/SymbolFile/DWARF/./SymbolFileDWARFTests-lldb-unit-1021-10-14.json GTEST_SHUFFLE=1 GTEST_TOTAL_SHARDS=14 GTEST_SHARD_INDEX=10 GTEST_RANDOM_SEED=62233 /Users/ec2-user/jenkins/workspace/llvm.org/as-lldb-cmake/lldb-build/tools/lldb/unittests/SymbolFile/DWARF/./SymbolFileDWARFTests
--

Script:
--
/Users/ec2-user/jenkins/workspace/llvm.org/as-lldb-cmake/lldb-build/tools/lldb/unittests/SymbolFile/DWARF/./SymbolFileDWARFTests --gtest_filter=SDKPathParsingTests/SDKPathParsingMultiparamTests.TestSDKPathFromDebugInfo/6
--
/Users/ec2-user/jenkins/workspace/llvm.org/as-lldb-cmake/llvm-project/lldb/unittests/SymbolFile/DWARF/XcodeSDKModuleTests.cpp:265: Failure
Expected equality of these values:
  found_mismatch
    Which is: true
  expect_mismatch
    Which is: false


/Users/ec2-user/jenkins/workspace/llvm.org/as-lldb-cmake/llvm-project/lldb/unittests/SymbolFile/DWARF/XcodeSDKModuleTests.cpp:265
Expected equality of these values:
  found_mismatch
    Which is: true
  expect_mismatch
    Which is: false
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants