diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h --- a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h +++ b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h @@ -124,6 +124,33 @@ /// located in. static FileSpec GetCurrentCommandLineToolsDirectory(); + /// Search each CU associated with the specified 'module' for + /// the SDK paths the CUs were compiled against. In the presence + /// of different SDKs, we try to pick the most appropriate one + /// using \ref XcodeSDK::Merge. + /// + /// \param[in] module Module whose debug-info CUs to parse for + /// which SDK they were compiled against. + /// + /// \returns If successful, returns a pair of a parsed XcodeSDK + /// object and a boolean that is 'true' if we encountered + /// a conflicting combination of SDKs when parsing the CUs + /// (e.g., a public and internal SDK). + static llvm::Expected> + GetSDKPathFromDebugInfo(Module &module); + + /// Returns the full path of the most appropriate SDK for the + /// specified 'module'. This function gets this path by parsing + /// debug-info (see \ref `GetSDKPathFromDebugInfo`). + /// + /// \param[in] module Module whose debug-info to parse for + /// which SDK it was compiled against. + /// + /// \returns If successful, returns the full path to an + /// Xcode SDK. + static llvm::Expected + ResolveSDKPathFromDebugInfo(Module &module); + protected: static const char *GetCompatibleArch(ArchSpec::Core core, size_t idx); diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp --- a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp +++ b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp @@ -42,6 +42,7 @@ #include "lldb/Utility/Status.h" #include "lldb/Utility/Timer.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Threading.h" #include "llvm/Support/VersionTuple.h" @@ -1095,8 +1096,21 @@ } FileSpec sysroot_spec; - // Scope for mutex locker below - { + + if (target) { + if (ModuleSP exe_module_sp = target->GetExecutableModule()) { + auto path_or_err = ResolveSDKPathFromDebugInfo(*exe_module_sp); + if (path_or_err) { + sysroot_spec = FileSpec(*path_or_err); + } else { + LLDB_LOG_ERROR(GetLog(LLDBLog::Types | LLDBLog::Host), + path_or_err.takeError(), + "Failed to resolve SDK path: {0}"); + } + } + } + + if (!FileSystem::Instance().IsDirectory(sysroot_spec.GetPath())) { std::lock_guard guard(m_mutex); sysroot_spec = GetSDKDirectoryForModules(sdk_type); } @@ -1335,3 +1349,53 @@ #endif #endif // __APPLE__ } + +llvm::Expected> +PlatformDarwin::GetSDKPathFromDebugInfo(Module &module) { + SymbolFile *sym_file = module.GetSymbolFile(); + if (!sym_file) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + llvm::formatv("No symbol file available for module '{0}'", + module.GetFileSpec().GetFilename().AsCString(""))); + + bool found_public_sdk = false; + bool found_internal_sdk = false; + XcodeSDK merged_sdk; + for (unsigned i = 0; i < sym_file->GetNumCompileUnits(); ++i) { + if (auto cu_sp = sym_file->GetCompileUnitAtIndex(i)) { + auto cu_sdk = sym_file->ParseXcodeSDK(*cu_sp); + bool is_internal_sdk = cu_sdk.IsAppleInternalSDK(); + found_public_sdk |= !is_internal_sdk; + found_internal_sdk |= is_internal_sdk; + + merged_sdk.Merge(cu_sdk); + } + } + + const bool found_mismatch = found_internal_sdk && found_public_sdk; + + return std::pair{std::move(merged_sdk), found_mismatch}; +} + +llvm::Expected +PlatformDarwin::ResolveSDKPathFromDebugInfo(Module &module) { + auto sdk_or_err = GetSDKPathFromDebugInfo(module); + if (!sdk_or_err) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + llvm::formatv("Failed to parse SDK path from debug-info: {0}", + llvm::toString(sdk_or_err.takeError()))); + + auto [sdk, _] = std::move(*sdk_or_err); + + auto path_or_err = HostInfo::GetSDKRoot(HostInfo::SDKOptions{sdk}); + if (!path_or_err) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + llvm::formatv("Error while searching for SDK (XcodeSDK '{0}'): {1}", + sdk.GetString(), + llvm::toString(path_or_err.takeError()))); + + return path_or_err->str(); +} diff --git a/lldb/unittests/SymbolFile/DWARF/XcodeSDKModuleTests.cpp b/lldb/unittests/SymbolFile/DWARF/XcodeSDKModuleTests.cpp --- a/lldb/unittests/SymbolFile/DWARF/XcodeSDKModuleTests.cpp +++ b/lldb/unittests/SymbolFile/DWARF/XcodeSDKModuleTests.cpp @@ -12,6 +12,8 @@ #include "Plugins/TypeSystem/Clang/TypeSystemClang.h" #include "TestingSupport/Symbol/YAMLModuleTester.h" #include "lldb/Core/PluginManager.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/Path.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -23,8 +25,55 @@ class XcodeSDKModuleTests : public testing::Test { SubsystemRAII subsystems; }; -} // namespace +struct SDKPathParsingTestData { + /// Each path will be put into a new CU's + /// DW_AT_LLVM_sysroot. + std::vector input_sdk_paths; + + /// 'true' if we expect \ref GetSDKPathFromDebugInfo + /// to notify us about an SDK mismatch. + bool expect_mismatch; + + /// 'true if the test expects the parsed SDK to + /// be an internal one. + bool expect_internal_sdk; + + /// A substring that the final parsed sdk + /// is expected to contain. + llvm::StringRef expect_sdk_path_pattern; +}; + +struct SDKPathParsingMultiparamTests + : public XcodeSDKModuleTests, + public testing::WithParamInterface { + std::vector + createCompileUnits(std::vector const &sdk_paths) { + std::vector compile_units; + + for (auto sdk_path : sdk_paths) { + compile_units.emplace_back(llvm::formatv( + R"( + - Version: 2 + AddrSize: 8 + AbbrevTableID: 0 + AbbrOffset: 0x0 + Entries: + - AbbrCode: 0x00000001 + Values: + - Value: 0x000000000000000C + - CStr: {0} + - CStr: {1} + - AbbrCode: 0x00000000 + )", + llvm::sys::path::filename(sdk_path, llvm::sys::path::Style::posix), + sdk_path)); + } + + return compile_units; + } +}; +} // namespace TEST_F(XcodeSDKModuleTests, TestModuleGetXcodeSDK) { const char *yamldata = R"( @@ -72,4 +121,190 @@ ASSERT_EQ(sdk.GetType(), XcodeSDK::Type::MacOSX); ASSERT_EQ(module->GetSourceMappingList().GetSize(), 1u); } + +TEST_F(XcodeSDKModuleTests, TestSDKPathFromDebugInfo_InvalidSDKPath) { + // Tests that parsing a CU with an invalid SDK directory name fails. + + const char *yamldata = R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_386 +DWARF: + debug_abbrev: + - Table: + - Code: 0x00000001 + Tag: DW_TAG_compile_unit + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_language + Form: DW_FORM_data2 + - Attribute: DW_AT_APPLE_sdk + Form: DW_FORM_string + debug_info: + - Version: 2 + AddrSize: 8 + AbbrevTableID: 0 + AbbrOffset: 0x0 + Entries: + - AbbrCode: 0x00000001 + Values: + - Value: 0x000000000000000C + - CStr: "1abc@defgh2" + - AbbrCode: 0x00000000 +... +)"; + + YAMLModuleTester t(yamldata); + ModuleSP module = t.GetModule(); + ASSERT_NE(module, nullptr); + + auto path_or_err = PlatformDarwin::ResolveSDKPathFromDebugInfo(*module); + EXPECT_FALSE(static_cast(path_or_err)); + llvm::consumeError(path_or_err.takeError()); +} + +TEST_F(XcodeSDKModuleTests, TestSDKPathFromDebugInfo_No_DW_AT_APPLE_sdk) { + // Tests that parsing a CU without a DW_AT_APPLE_sdk fails. + + const char *yamldata = R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_386 +DWARF: + debug_abbrev: + - Table: + - Code: 0x00000001 + Tag: DW_TAG_compile_unit + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_language + Form: DW_FORM_data2 + - Attribute: DW_AT_LLVM_sysroot + Form: DW_FORM_string + debug_info: + - Version: 2 + AddrSize: 8 + AbbrevTableID: 0 + AbbrOffset: 0x0 + Entries: + - AbbrCode: 0x00000001 + Values: + - Value: 0x000000000000000C + - CStr: "/Library/Developer/CommandLineTools/SDKs/iPhoneOS14.0.Internal.sdk" + - AbbrCode: 0x00000000 +... +)"; + + YAMLModuleTester t(yamldata); + ModuleSP module = t.GetModule(); + ASSERT_NE(module, nullptr); + + auto path_or_err = PlatformDarwin::ResolveSDKPathFromDebugInfo(*module); + EXPECT_FALSE(static_cast(path_or_err)); + llvm::consumeError(path_or_err.takeError()); +} + +TEST_P(SDKPathParsingMultiparamTests, TestSDKPathFromDebugInfo) { + // Tests that we can parse the SDK path from debug-info. + // In the presence of multiple compile units, one of which + // points to an internal SDK, we should pick the internal SDK. + + std::string yamldata = R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_386 +DWARF: + debug_abbrev: + - Table: + - Code: 0x00000001 + Tag: DW_TAG_compile_unit + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_language + Form: DW_FORM_data2 + - Attribute: DW_AT_APPLE_sdk + Form: DW_FORM_string + - Attribute: DW_AT_LLVM_sysroot + Form: DW_FORM_string + debug_info: +)"; + + auto [input_sdk_paths, expect_mismatch, expect_internal_sdk, + expect_sdk_path_pattern] = GetParam(); + + for (auto &&sdk : createCompileUnits(input_sdk_paths)) + yamldata += std::move(sdk); + + YAMLModuleTester t(yamldata); + DWARFUnit *dwarf_unit = t.GetDwarfUnit(); + auto *dwarf_cu = llvm::cast(dwarf_unit); + ASSERT_TRUE(static_cast(dwarf_cu)); + SymbolFileDWARF &sym_file = dwarf_cu->GetSymbolFileDWARF(); + ASSERT_EQ(sym_file.GetNumCompileUnits(), input_sdk_paths.size()); + ModuleSP module = t.GetModule(); + ASSERT_NE(module, nullptr); + + auto sdk_or_err = PlatformDarwin::GetSDKPathFromDebugInfo(*module); + EXPECT_TRUE(static_cast(sdk_or_err)); + + auto [sdk, found_mismatch] = *sdk_or_err; + + EXPECT_EQ(found_mismatch, expect_mismatch); + EXPECT_EQ(sdk.IsAppleInternalSDK(), expect_internal_sdk); + EXPECT_NE(sdk.GetString().find(expect_sdk_path_pattern), std::string::npos); +} + +SDKPathParsingTestData sdkPathParsingTestCases[] = { + /// Multiple CUs with a mix of internal and public SDKs + {.input_sdk_paths = + {"/Library/Developer/CommandLineTools/SDKs/MacOSX10.9.sdk", + "/invalid/path/to/something.invalid.sdk", + "/Library/Developer/CommandLineTools/SDKs/iPhoneOS14.0.Internal.sdk", + "/Library/Developer/CommandLineTools/SDKs/MacOSX10.9.sdk"}, + .expect_mismatch = true, + .expect_internal_sdk = true, + .expect_sdk_path_pattern = "Internal.sdk"}, + + /// Single CU with a public SDK + {.input_sdk_paths = + {"/Library/Developer/CommandLineTools/SDKs/MacOSX10.9.sdk"}, + .expect_mismatch = false, + .expect_internal_sdk = false, + .expect_sdk_path_pattern = "MacOSX10.9.sdk"}, + + /// Single CU with an internal SDK + {.input_sdk_paths = + {"/Library/Developer/CommandLineTools/SDKs/iPhoneOS14.0.Internal.sdk"}, + .expect_mismatch = false, + .expect_internal_sdk = true, + .expect_sdk_path_pattern = "Internal.sdk"}, + + /// Two CUs with an internal SDK each + {.input_sdk_paths = + {"/Library/Developer/CommandLineTools/SDKs/iPhoneOS14.0.Internal.sdk", + "/Library/Developer/CommandLineTools/SDKs/iPhoneOS12.9.Internal.sdk"}, + .expect_mismatch = false, + .expect_internal_sdk = true, + .expect_sdk_path_pattern = "Internal.sdk"}, + + /// Two CUs with an internal SDK each + {.input_sdk_paths = + {"/Library/Developer/CommandLineTools/SDKs/iPhoneOS14.1.sdk", + "/Library/Developer/CommandLineTools/SDKs/MacOSX11.3.sdk"}, + .expect_mismatch = false, + .expect_internal_sdk = false, + .expect_sdk_path_pattern = "iPhoneOS14.1.sdk"}, +}; + +INSTANTIATE_TEST_CASE_P(SDKPathParsingTests, SDKPathParsingMultiparamTests, + ::testing::ValuesIn(sdkPathParsingTestCases)); #endif