[lldb] Read from the Rosetta shared cache with Xcode 14

Xcode 14 no longer puts the Rosetta expanded shared cache in a directory
named "16.0". Instead, it includes the real version number (e.g. 13.0),
the build string and the architecture, similar to the device support
directory names for iOS, tvOS and watchOS.

Currently, when there are multiple directories, we might end up picking
the wrong one in GetSDKDirectoryForCurrentOSVersion. The problem is that
without the build string we have no way to differentiate between
multiple directories with the same version number. This patch fixes the
problem by using GetOSBuildString which, as the name implies, returns
the build string if known.

This also adds a test for Rosetta debugging on Apple Silicon. Depending
on whether the Rosetta expanded shared cache is present, the test
ensures that there is or isn't a diagnostic about reading out of memory.

rdar://97576121

Differential revision: https://reviews.llvm.org/D130540

GitOrigin-RevId: ecda408178fc6486ea568263cdda6624f5bcb8c7
diff --git a/packages/Python/lldbsuite/test/decorators.py b/packages/Python/lldbsuite/test/decorators.py
index 191a1e0..6d6591c 100644
--- a/packages/Python/lldbsuite/test/decorators.py
+++ b/packages/Python/lldbsuite/test/decorators.py
@@ -698,6 +698,17 @@
             archs),
         bugnumber)
 
+def skipUnlessAppleSilicon(func):
+    """Decorate the item to skip tests unless running on Apple Silicon."""
+    def not_apple_silicon(test):
+        if platform.system() != 'Darwin' or test.getArchitecture() not in [
+                'arm64', 'arm64e'
+        ]:
+            return "Test only runs on Apple Silicon"
+        return None
+
+    return skipTestIfFn(not_apple_silicon)(func)
+
 def skipUnlessSupportedTypeAttribute(attr):
     """Decorate the item to skip test unless Clang supports type __attribute__(attr)."""
     def compiler_doesnt_support_struct_attribute(self):
diff --git a/source/Plugins/Platform/MacOSX/PlatformDarwinDevice.cpp b/source/Plugins/Platform/MacOSX/PlatformDarwinDevice.cpp
index 7feaef9..f4f866b 100644
--- a/source/Plugins/Platform/MacOSX/PlatformDarwinDevice.cpp
+++ b/source/Plugins/Platform/MacOSX/PlatformDarwinDevice.cpp
@@ -148,11 +148,20 @@
   uint32_t i;
   if (UpdateSDKDirectoryInfosIfNeeded()) {
     const uint32_t num_sdk_infos = m_sdk_directory_infos.size();
-
-    // Check to see if the user specified a build string. If they did, then be
-    // sure to match it.
     std::vector<bool> check_sdk_info(num_sdk_infos, true);
-    ConstString build(m_sdk_build);
+
+    // Prefer the user SDK build string.
+    ConstString build = GetSDKBuild();
+
+    // Fall back to the platform's build string.
+    if (!build) {
+      if (llvm::Optional<std::string> os_build_str = GetOSBuildString()) {
+        build = ConstString(*os_build_str);
+      }
+    }
+
+    // If we have a build string, only check platforms for which the build
+    // string matches.
     if (build) {
       for (i = 0; i < num_sdk_infos; ++i)
         check_sdk_info[i] = m_sdk_directory_infos[i].build == build;
@@ -163,14 +172,14 @@
     llvm::VersionTuple version = GetOSVersion();
     if (!version.empty()) {
       if (UpdateSDKDirectoryInfosIfNeeded()) {
-        // First try for an exact match of major, minor and update
+        // First try for an exact match of major, minor and update.
         for (i = 0; i < num_sdk_infos; ++i) {
           if (check_sdk_info[i]) {
             if (m_sdk_directory_infos[i].version == version)
               return &m_sdk_directory_infos[i];
           }
         }
-        // First try for an exact match of major and minor
+        // Try for an exact match of major and minor.
         for (i = 0; i < num_sdk_infos; ++i) {
           if (check_sdk_info[i]) {
             if (m_sdk_directory_infos[i].version.getMajor() ==
@@ -181,7 +190,7 @@
             }
           }
         }
-        // Lastly try to match of major version only..
+        // Lastly try to match of major version only.
         for (i = 0; i < num_sdk_infos; ++i) {
           if (check_sdk_info[i]) {
             if (m_sdk_directory_infos[i].version.getMajor() ==
@@ -192,7 +201,7 @@
         }
       }
     } else if (build) {
-      // No version, just a build number, search for the first one that matches
+      // No version, just a build number, return the first one that matches.
       for (i = 0; i < num_sdk_infos; ++i)
         if (check_sdk_info[i])
           return &m_sdk_directory_infos[i];
diff --git a/test/API/macosx/rosetta/Makefile b/test/API/macosx/rosetta/Makefile
new file mode 100644
index 0000000..8dd53c8
--- /dev/null
+++ b/test/API/macosx/rosetta/Makefile
@@ -0,0 +1,4 @@
+C_SOURCES := main.c
+override ARCH = x86_64
+
+include Makefile.rules
diff --git a/test/API/macosx/rosetta/TestRosetta.py b/test/API/macosx/rosetta/TestRosetta.py
new file mode 100644
index 0000000..628d265
--- /dev/null
+++ b/test/API/macosx/rosetta/TestRosetta.py
@@ -0,0 +1,55 @@
+import re
+import lldb
+import lldbsuite.test.lldbutil as lldbutil
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+
+
+def get_os_version():
+    try:
+        os_version_str = subprocess.check_output(["sysctl", "kern.osversion"
+                                                  ]).decode('utf-8')
+    except subprocess.CalledProcessError:
+        return None
+    m = re.match(r'kern\.osversion: (\w+)', os_version_str)
+    if m:
+        return m.group(1)
+    return None
+
+
+def has_rosetta_shared_cache(os_version):
+    if not os_version:
+        return False
+    macos_device_support = os.path.join(os.path.expanduser("~"), 'Library',
+                                        'Developer', 'Xcode',
+                                        'macOS DeviceSupport')
+    for _, subdirs, _ in os.walk(macos_device_support):
+        for subdir in subdirs:
+            if os_version in subdir:
+                return True
+    return False
+
+
+class TestRosetta(TestBase):
+
+    NO_DEBUG_INFO_TESTCASE = True
+
+    @skipUnlessAppleSilicon
+    def test_rosetta(self):
+        """There can be many tests in a test case - describe this test here."""
+        self.build()
+        self.main_source_file = lldb.SBFileSpec("main.c")
+
+        broadcaster = self.dbg.GetBroadcaster()
+        listener = lldbutil.start_listening_from(
+            broadcaster, lldb.SBDebugger.eBroadcastBitWarning)
+
+        target, process, thread, bkpt = lldbutil.run_to_source_breakpoint(
+            self, "Set a breakpoint here", self.main_source_file)
+
+        event = lldb.SBEvent()
+        os_version = get_os_version()
+        if not has_rosetta_shared_cache(os_version):
+            self.assertTrue(listener.GetNextEvent(event))
+        else:
+            self.assertFalse(listener.GetNextEvent(event))
diff --git a/test/API/macosx/rosetta/main.c b/test/API/macosx/rosetta/main.c
new file mode 100644
index 0000000..ca9c416
--- /dev/null
+++ b/test/API/macosx/rosetta/main.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main() {
+  int i = 0; // Set a breakpoint here
+  return i;
+}