diff --git a/lldb/packages/Python/lldbsuite/test/lldbutil.py b/lldb/packages/Python/lldbsuite/test/lldbutil.py --- a/lldb/packages/Python/lldbsuite/test/lldbutil.py +++ b/lldb/packages/Python/lldbsuite/test/lldbutil.py @@ -1458,3 +1458,40 @@ (file_path, max_attempts)) return read_file_on_target(testcase, file_path) + +def packetlog_get_process_info(log): + """parse a gdb-remote packet log file and extract the response to qProcessInfo""" + process_info = dict() + with open(log, "r") as logfile: + process_info_ostype = None + expect_process_info_response = False + for line in logfile: + if expect_process_info_response: + for pair in line.split(';'): + keyval = pair.split(':') + if len(keyval) == 2: + process_info[keyval[0]] = keyval[1] + break + if 'send packet: $qProcessInfo#' in line: + expect_process_info_response = True + return process_info + +def packetlog_get_dylib_info(log): + """parse a gdb-remote packet log file and extract the *last* response to jGetLoadedDynamicLibrariesInfos""" + import json + dylib_info = None + with open(log, "r") as logfile: + dylib_info = None + expect_dylib_info_response = False + for line in logfile: + if expect_dylib_info_response: + while line[0] != '$': + line = line[1:] + line = line[1:] + # Unescape '}'. + dylib_info = json.loads(line.replace('}]','}')[:-4]) + expect_dylib_info_response = False + if 'send packet: $jGetLoadedDynamicLibrariesInfos:{' in line: + expect_dylib_info_response = True + + return dylib_info diff --git a/lldb/test/API/macosx/macCatalyst/Makefile b/lldb/test/API/macosx/macCatalyst/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/macosx/macCatalyst/Makefile @@ -0,0 +1,10 @@ +C_SOURCES := main.c + +TRIPLE := $(ARCH)-apple-ios13.0-macabi +CFLAGS_EXTRAS := -target $(TRIPLE) + +# FIXME: rdar://problem/54986190 +# There is a Clang driver change missing on llvm.org. +override CC=xcrun clang + +include Makefile.rules diff --git a/lldb/test/API/macosx/macCatalyst/TestMacCatalyst.py b/lldb/test/API/macosx/macCatalyst/TestMacCatalyst.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/macosx/macCatalyst/TestMacCatalyst.py @@ -0,0 +1,43 @@ +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +import lldbsuite.test.lldbutil as lldbutil +import os +import unittest2 + + +class TestMacCatalyst(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + @skipIf(macos_version=["<", "10.15"]) + @skipUnlessDarwin + @skipIfDarwinEmbedded + @skipIfReproducer # This is hitting https://bugs.python.org/issue22393 + def test_macabi(self): + """Test the x86_64-apple-ios-macabi target linked against a macos dylib""" + self.build() + log = self.getBuildArtifact('packets.log') + self.expect("log enable gdb-remote packets -f "+log) + lldbutil.run_to_source_breakpoint(self, "break here", + lldb.SBFileSpec('main.c')) + self.expect("image list -t -b", + patterns=[self.getArchitecture() + + r'.*-apple-ios.*-macabi a\.out']) + self.expect("fr v s", "Hello macCatalyst") + self.expect("p s", "Hello macCatalyst") + self.check_debugserver(log) + + def check_debugserver(self, log): + """scan the debugserver packet log""" + process_info = lldbutil.packetlog_get_process_info(log) + self.assertTrue('ostype' in process_info) + self.assertEquals(process_info['ostype'], 'maccatalyst') + + aout_info = None + dylib_info = lldbutil.packetlog_get_dylib_info(log) + for image in dylib_info['images']: + if image['pathname'].endswith('a.out'): + aout_info = image + self.assertTrue(aout_info) + self.assertEquals(aout_info['min_version_os_name'], 'maccatalyst') diff --git a/lldb/test/API/macosx/macCatalyst/main.c b/lldb/test/API/macosx/macCatalyst/main.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/macosx/macCatalyst/main.c @@ -0,0 +1,4 @@ +int main() { + const char *s = "Hello macCatalyst!"; + return 0; // break here +} diff --git a/lldb/test/API/macosx/macabi/Makefile b/lldb/test/API/macosx/macCatalystAppMacOSFramework/Makefile rename from lldb/test/API/macosx/macabi/Makefile rename to lldb/test/API/macosx/macCatalystAppMacOSFramework/Makefile --- a/lldb/test/API/macosx/macabi/Makefile +++ b/lldb/test/API/macosx/macCatalystAppMacOSFramework/Makefile @@ -1,9 +1,12 @@ C_SOURCES := main.c LD_EXTRAS := -L. -lfoo -TRIPLE := x86_64-apple-ios13.0-macabi +TRIPLE := $(ARCH)-apple-ios13.0-macabi CFLAGS_EXTRAS := -target $(TRIPLE) +# FIXME: rdar://problem/54986190 +override CC=xcrun clang + all: libfoo.dylib a.out libfoo.dylib: foo.c diff --git a/lldb/test/API/macosx/macCatalystAppMacOSFramework/TestMacCatalystAppWithMacOSFramework.py b/lldb/test/API/macosx/macCatalystAppMacOSFramework/TestMacCatalystAppWithMacOSFramework.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/macosx/macCatalystAppMacOSFramework/TestMacCatalystAppWithMacOSFramework.py @@ -0,0 +1,51 @@ +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +import lldbsuite.test.lldbutil as lldbutil +import os +import unittest2 + + +class TestMacCatalystAppWithMacOSFramework(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + @skipIf(macos_version=["<", "10.15"]) + @skipUnlessDarwin + @skipIfDarwinEmbedded + # There is a Clang driver change missing on llvm.org. + @expectedFailureAll(bugnumber="rdar://problem/54986190>") + @skipIfReproducer # This is hitting https://bugs.python.org/issue22393 + def test(self): + """Test the x86_64-apple-ios-macabi target linked against a macos dylib""" + self.build() + log = self.getBuildArtifact('packets.log') + self.expect("log enable gdb-remote packets -f "+log) + lldbutil.run_to_source_breakpoint(self, "break here", + lldb.SBFileSpec('main.c')) + arch = self.getArchitecture() + self.expect("image list -t -b", + patterns=[arch + r'.*-apple-ios.*-macabi a\.out', + arch + r'.*-apple-macosx.* libfoo.dylib[^(]']) + self.expect("fr v s", "Hello macCatalyst") + self.expect("p s", "Hello macCatalyst") + self.check_debugserver(log) + + def check_debugserver(self, log): + """scan the debugserver packet log""" + process_info = lldbutil.packetlog_get_process_info(log) + self.assertTrue('ostype' in process_info) + self.assertEquals(process_info['ostype'], 'maccatalyst') + + aout_info = None + libfoo_info = None + dylib_info = lldbutil.packetlog_get_dylib_info(log) + for image in dylib_info['images']: + if image['pathname'].endswith('a.out'): + aout_info = image + if image['pathname'].endswith('libfoo.dylib'): + libfoo_info = image + self.assertTrue(aout_info) + self.assertTrue(libfoo_info) + self.assertEquals(aout_info['min_version_os_name'], 'maccatalyst') + self.assertEquals(libfoo_info['min_version_os_name'], 'macosx') diff --git a/lldb/test/API/macosx/macabi/foo.h b/lldb/test/API/macosx/macCatalystAppMacOSFramework/foo.h rename from lldb/test/API/macosx/macabi/foo.h rename to lldb/test/API/macosx/macCatalystAppMacOSFramework/foo.h diff --git a/lldb/test/API/macosx/macabi/foo.c b/lldb/test/API/macosx/macCatalystAppMacOSFramework/foo.c rename from lldb/test/API/macosx/macabi/foo.c rename to lldb/test/API/macosx/macCatalystAppMacOSFramework/foo.c diff --git a/lldb/test/API/macosx/macabi/main.c b/lldb/test/API/macosx/macCatalystAppMacOSFramework/main.c rename from lldb/test/API/macosx/macabi/main.c rename to lldb/test/API/macosx/macCatalystAppMacOSFramework/main.c --- a/lldb/test/API/macosx/macabi/main.c +++ b/lldb/test/API/macosx/macCatalystAppMacOSFramework/main.c @@ -1,5 +1,5 @@ #include "foo.h" int main() { - const char *s = "Hello MacABI!"; + const char *s = "Hello macCatalyst!"; return foo(); // break here } diff --git a/lldb/test/API/macosx/macabi/TestMacABImacOSFramework.py b/lldb/test/API/macosx/macabi/TestMacABImacOSFramework.py deleted file mode 100644 --- a/lldb/test/API/macosx/macabi/TestMacABImacOSFramework.py +++ /dev/null @@ -1,28 +0,0 @@ -import lldb -from lldbsuite.test.lldbtest import * -from lldbsuite.test.decorators import * -import lldbsuite.test.lldbutil as lldbutil -import os -import unittest2 - - -class TestMacABImacOSFramework(TestBase): - - mydir = TestBase.compute_mydir(__file__) - - @skipIf(macos_version=["<", "10.15"]) - @skipUnlessDarwin - @skipIfDarwinEmbedded - # There is a Clang driver change missing on llvm.org. - @expectedFailureAll(bugnumber="rdar://problem/54986190>") - @skipIfReproducer # This is hitting https://bugs.python.org/issue22393 - def test_macabi(self): - """Test the x86_64-apple-ios-macabi target linked against a macos dylib""" - self.build() - lldbutil.run_to_source_breakpoint(self, "break here", - lldb.SBFileSpec('main.c')) - self.expect("image list -t -b", - patterns=["x86_64.*-apple-ios.*-macabi a\.out", - "x86_64.*-apple-macosx.* libfoo.dylib[^(]"]) - self.expect("fr v s", "Hello MacABI") - self.expect("p s", "Hello MacABI") diff --git a/lldb/test/API/macosx/simulator/TestSimulatorPlatform.py b/lldb/test/API/macosx/simulator/TestSimulatorPlatform.py --- a/lldb/test/API/macosx/simulator/TestSimulatorPlatform.py +++ b/lldb/test/API/macosx/simulator/TestSimulatorPlatform.py @@ -25,30 +25,10 @@ def check_debugserver(self, log, expected_platform, expected_version): """scan the debugserver packet log""" - logfile = open(log, "r") - dylib_info = None - process_info_ostype = None - expect_dylib_info_response = False - expect_process_info_response = False - for line in logfile: - if expect_dylib_info_response: - while line[0] != '$': - line = line[1:] - line = line[1:] - # Unescape '}'. - dylib_info = json.loads(line.replace('}]','}')[:-4]) - expect_dylib_info_response = False - if 'send packet: $jGetLoadedDynamicLibrariesInfos:{' in line: - expect_dylib_info_response = True - if expect_process_info_response: - for pair in line.split(';'): - keyval = pair.split(':') - if len(keyval) == 2 and keyval[0] == 'ostype': - process_info_ostype = keyval[1] - if 'send packet: $qProcessInfo#' in line: - expect_process_info_response = True - - self.assertEquals(process_info_ostype, expected_platform) + process_info = lldbutil.packetlog_get_process_info(log) + self.assertTrue('ostype' in process_info) + self.assertEquals(process_info['ostype'], expected_platform) + dylib_info = lldbutil.packetlog_get_dylib_info(log) self.assertTrue(dylib_info) aout_info = None for image in dylib_info['images']: diff --git a/lldb/tools/debugserver/source/DNB.h b/lldb/tools/debugserver/source/DNB.h --- a/lldb/tools/debugserver/source/DNB.h +++ b/lldb/tools/debugserver/source/DNB.h @@ -128,12 +128,12 @@ nub_size_t DNBProcessGetSharedLibraryInfo(nub_process_t pid, nub_bool_t only_changed, DNBExecutableImageInfo **image_infos) DNB_EXPORT; -const char *DNBGetDeploymentInfo(nub_process_t pid, - const struct load_command& lc, +const char *DNBGetDeploymentInfo(nub_process_t pid, bool is_executable, + const struct load_command &lc, uint64_t load_command_address, - uint32_t& major_version, - uint32_t& minor_version, - uint32_t& patch_version); + uint32_t &major_version, + uint32_t &minor_version, + uint32_t &patch_version); nub_bool_t DNBProcessSetNameToAddressCallback(nub_process_t pid, DNBCallbackNameToAddress callback, void *baton) DNB_EXPORT; diff --git a/lldb/tools/debugserver/source/DNB.cpp b/lldb/tools/debugserver/source/DNB.cpp --- a/lldb/tools/debugserver/source/DNB.cpp +++ b/lldb/tools/debugserver/source/DNB.cpp @@ -1418,19 +1418,20 @@ return false; } -const char *DNBGetDeploymentInfo(nub_process_t pid, - const struct load_command& lc, +const char *DNBGetDeploymentInfo(nub_process_t pid, bool is_executable, + const struct load_command &lc, uint64_t load_command_address, - uint32_t& major_version, - uint32_t& minor_version, - uint32_t& patch_version) { + uint32_t &major_version, + uint32_t &minor_version, + uint32_t &patch_version) { MachProcessSP procSP; if (GetProcessSP(pid, procSP)) { // FIXME: This doesn't return the correct result when xctest (a // macOS binary) is loaded with the macCatalyst dyld platform // override. The image info corrects for this, but qProcessInfo // will return what is in the binary. - auto info = procSP->GetDeploymentInfo(lc, load_command_address); + auto info = + procSP->GetDeploymentInfo(lc, load_command_address, is_executable); major_version = info.major_version; minor_version = info.minor_version; patch_version = info.patch_version; @@ -1439,7 +1440,6 @@ return nullptr; } - // Get the current shared library information for a process. Only return // the shared libraries that have changed since the last shared library // state changed event if only_changed is non-zero. diff --git a/lldb/tools/debugserver/source/MacOSX/MachProcess.h b/lldb/tools/debugserver/source/MacOSX/MachProcess.h --- a/lldb/tools/debugserver/source/MacOSX/MachProcess.h +++ b/lldb/tools/debugserver/source/MacOSX/MachProcess.h @@ -241,7 +241,8 @@ uint32_t patch_version = 0; }; DeploymentInfo GetDeploymentInfo(const struct load_command &, - uint64_t load_command_address); + uint64_t load_command_address, + bool is_executable); static const char *GetPlatformString(unsigned char platform); bool GetMachOInformationFromMemory(uint32_t platform, nub_addr_t mach_o_header_addr, @@ -249,7 +250,15 @@ struct mach_o_information &inf); JSONGenerator::ObjectSP FormatDynamicLibrariesIntoJSON( const std::vector &image_infos); - uint32_t GetAllLoadedBinariesViaDYLDSPI( + /// Get the runtime platform from DYLD via SPI. + uint32_t GetProcessPlatformViaDYLDSPI(); + /// Use the dyld SPI present in macOS 10.12, iOS 10, tvOS 10, + /// watchOS 3 and newer to get the load address, uuid, and filenames + /// of all the libraries. This only fills in those three fields in + /// the 'struct binary_image_information' - call + /// GetMachOInformationFromMemory to fill in the mach-o header/load + /// command details. + void GetAllLoadedBinariesViaDYLDSPI( std::vector &image_infos); JSONGenerator::ObjectSP GetLoadedDynamicLibrariesInfos( nub_process_t pid, nub_addr_t image_list_address, nub_addr_t image_count); diff --git a/lldb/tools/debugserver/source/MacOSX/MachProcess.mm b/lldb/tools/debugserver/source/MacOSX/MachProcess.mm --- a/lldb/tools/debugserver/source/MacOSX/MachProcess.mm +++ b/lldb/tools/debugserver/source/MacOSX/MachProcess.mm @@ -93,6 +93,7 @@ typedef bool (*CallOpenApplicationFunction)(NSString *bundleIDNSStr, NSDictionary *options, DNBError &error, pid_t *return_pid); + // This function runs the BKSSystemService (or FBSSystemService) method // openApplication:options:clientPort:withResult, // messaging the app passed in bundleIDNSStr. @@ -483,6 +484,7 @@ #define _POSIX_SPAWN_DISABLE_ASLR 0x0100 #endif + MachProcess::MachProcess() : m_pid(0), m_cpu_type(0), m_child_stdin(-1), m_child_stdout(-1), m_child_stderr(-1), m_path(), m_args(), m_task(this), @@ -603,9 +605,11 @@ MachProcess::DeploymentInfo MachProcess::GetDeploymentInfo(const struct load_command &lc, - uint64_t load_command_address) { + uint64_t load_command_address, + bool is_executable) { DeploymentInfo info; uint32_t cmd = lc.cmd & ~LC_REQ_DYLD; + // Handle the older LC_VERSION load commands, which don't // distinguish between simulator and real hardware. auto handle_version_min = [&](char platform) { @@ -640,6 +644,7 @@ // unambiguous LC_BUILD_VERSION load commands. #endif }; + switch (cmd) { case LC_VERSION_MIN_IPHONEOS: handle_version_min(PLATFORM_IOS); @@ -667,6 +672,27 @@ } #endif } + + // The xctest binary is a pure macOS binary but is launched with + // DYLD_FORCE_PLATFORM=6. In that case, force the platform to + // macCatalyst and use the macCatalyst version of the host OS + // instead of the macOS deployment target. + if (is_executable && GetProcessPlatformViaDYLDSPI() == PLATFORM_MACCATALYST) { + info.platform = PLATFORM_MACCATALYST; + std::string catalyst_version = GetMacCatalystVersionString(); + const char *major = catalyst_version.c_str(); + char *minor = nullptr; + char *patch = nullptr; + info.major_version = std::strtoul(major, &minor, 10); + info.minor_version = 0; + info.patch_version = 0; + if (minor && *minor == '.') { + info.minor_version = std::strtoul(++minor, &patch, 10); + if (patch && *patch == '.') + info.patch_version = std::strtoul(++patch, nullptr, 10); + } + } + return info; } @@ -798,37 +824,21 @@ sizeof(struct uuid_command)) uuid_copy(inf.uuid, uuidcmd.uuid); } - if (DeploymentInfo deployment_info = GetDeploymentInfo(lc, load_cmds_p)) { + if (DeploymentInfo deployment_info = GetDeploymentInfo( + lc, load_cmds_p, inf.mach_header.filetype == MH_EXECUTE)) { const char *lc_platform = GetPlatformString(deployment_info.platform); - // macCatalyst support. - // - // This handles two special cases: - // - // 1. Frameworks that have both a PLATFORM_MACOS and a - // PLATFORM_MACCATALYST load command. Make sure to select - // the requested one. - // - // 2. The xctest binary is a pure macOS binary but is launched - // with DYLD_FORCE_PLATFORM=6. - if (dyld_platform == PLATFORM_MACCATALYST && - inf.mach_header.filetype == MH_EXECUTE && - inf.min_version_os_name.empty() && - (strcmp("macosx", lc_platform) == 0)) { - // DYLD says this *is* a macCatalyst process. If we haven't - // parsed any load commands, transform a macOS load command - // into a generic macCatalyst load command. It will be - // overwritten by a more specific one if there is one. This - // is only done for the main executable. It is perfectly fine - // for a macCatalyst binary to link against a macOS-only framework. - inf.min_version_os_name = "maccatalyst"; - inf.min_version_os_version = GetMacCatalystVersionString(); - } else if (dyld_platform != PLATFORM_MACCATALYST && - inf.min_version_os_name == "macosx") { - // This is a binary with both PLATFORM_MACOS and - // PLATFORM_MACCATALYST load commands and the process is not - // running as PLATFORM_MACCATALYST. Stick with the - // "macosx" load command that we've already processed, - // ignore this one, which is presumed to be a + if (dyld_platform != PLATFORM_MACCATALYST && + inf.min_version_os_name == "macosx") { + // macCatalyst support. + // + // This the special case of "zippered" frameworks that have both + // a PLATFORM_MACOS and a PLATFORM_MACCATALYST load command. + // + // When we are in this block, this is a binary with both + // PLATFORM_MACOS and PLATFORM_MACCATALYST load commands and + // the process is not running as PLATFORM_MACCATALYST. Stick + // with the "macosx" load command that we've already + // processed, ignore this one, which is presumed to be a // PLATFORM_MACCATALYST one. } else { inf.min_version_os_name = lc_platform; @@ -1056,25 +1066,36 @@ return reply_sp; } -// From dyld SPI header dyld_process_info.h +/// From dyld SPI header dyld_process_info.h typedef void *dyld_process_info; struct dyld_process_cache_info { - uuid_t cacheUUID; // UUID of cache used by process - uint64_t cacheBaseAddress; // load address of dyld shared cache - bool noCache; // process is running without a dyld cache - bool privateCache; // process is using a private copy of its dyld cache + /// UUID of cache used by process. + uuid_t cacheUUID; + /// Load address of dyld shared cache. + uint64_t cacheBaseAddress; + /// Process is running without a dyld cache. + bool noCache; + /// Process is using a private copy of its dyld cache. + bool privateCache; }; -// Use the dyld SPI present in macOS 10.12, iOS 10, tvOS 10, watchOS 3 and newer -// to get -// the load address, uuid, and filenames of all the libraries. -// This only fills in those three fields in the 'struct -// binary_image_information' - call -// GetMachOInformationFromMemory to fill in the mach-o header/load command -// details. -uint32_t MachProcess::GetAllLoadedBinariesViaDYLDSPI( - std::vector &image_infos) { +uint32_t MachProcess::GetProcessPlatformViaDYLDSPI() { + kern_return_t kern_ret; uint32_t platform = 0; + if (m_dyld_process_info_create) { + dyld_process_info info = + m_dyld_process_info_create(m_task.TaskPort(), 0, &kern_ret); + if (info) { + if (m_dyld_process_info_get_platform) + platform = m_dyld_process_info_get_platform(info); + m_dyld_process_info_release(info); + } + } + return platform; +} + +void MachProcess::GetAllLoadedBinariesViaDYLDSPI( + std::vector &image_infos) { kern_return_t kern_ret; if (m_dyld_process_info_create) { dyld_process_info info = @@ -1089,12 +1110,9 @@ image.load_address = mach_header_addr; image_infos.push_back(image); }); - if (m_dyld_process_info_get_platform) - platform = m_dyld_process_info_get_platform(info); m_dyld_process_info_release(info); } } - return platform; } // Fetch information about all shared libraries using the dyld SPIs that exist @@ -1115,7 +1133,8 @@ pointer_size = 8; std::vector image_infos; - uint32_t platform = GetAllLoadedBinariesViaDYLDSPI(image_infos); + GetAllLoadedBinariesViaDYLDSPI(image_infos); + uint32_t platform = GetProcessPlatformViaDYLDSPI(); const size_t image_count = image_infos.size(); for (size_t i = 0; i < image_count; i++) { GetMachOInformationFromMemory(platform, @@ -1145,7 +1164,8 @@ pointer_size = 8; std::vector all_image_infos; - uint32_t platform = GetAllLoadedBinariesViaDYLDSPI(all_image_infos); + GetAllLoadedBinariesViaDYLDSPI(all_image_infos); + uint32_t platform = GetProcessPlatformViaDYLDSPI(); std::vector image_infos; const size_t macho_addresses_count = macho_addresses.size(); @@ -1173,7 +1193,7 @@ JSONGenerator::ObjectSP MachProcess::GetSharedCacheInfo(nub_process_t pid) { JSONGenerator::DictionarySP reply_sp(new JSONGenerator::Dictionary()); - ; + kern_return_t kern_ret; if (m_dyld_process_info_create && m_dyld_process_info_get_cache) { dyld_process_info info = diff --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp --- a/lldb/tools/debugserver/source/RNBRemote.cpp +++ b/lldb/tools/debugserver/source/RNBRemote.cpp @@ -6356,10 +6356,11 @@ DNBProcessMemoryRead(pid, load_command_addr, sizeof(lc), &lc); (void)bytes_read; + bool is_executable = true; uint32_t major_version, minor_version, patch_version; - auto *platform = DNBGetDeploymentInfo(pid, lc, load_command_addr, - major_version, minor_version, - patch_version); + auto *platform = + DNBGetDeploymentInfo(pid, is_executable, lc, load_command_addr, + major_version, minor_version, patch_version); if (platform) { os_handled = true; rep << "ostype:" << platform << ";";