From cd90f878d4156ef1a732cdd64bc67aa88e7ac2ec Mon Sep 17 00:00:00 2001 From: Frederic Riss Date: Fri, 6 Apr 2018 04:28:12 +0000 Subject: [PATCH] [debugserver] Fix LC_BUILD_VERSION load command handling. Summary: In one of the 2 places the LC_BUILD_VERSION load command is handled, there is a bug preventing us from actually handling them (the address where to read the load command was not updated). This patch factors reading the deployment target load commands into a helper and adds testing for the 2 code paths calling the helper. The testing is a little bit complicated because the only times those load commands matter is when debugging a simulator process. I added a new decorator to check that a specific SDK is available. The actual testing was fairly easy once I knew how to run a simulated process. Reviewers: jasonmolenda, labath Subscribers: lldb-commits Differential Revision: https://reviews.llvm.org/D45298 llvm-svn: 329374 --- .../Python/lldbsuite/test/decorators.py | 22 +++ .../lldb-server/TestAppleSimulatorOSType.py | 112 +++++++++++++ .../lldbsuite/test/tools/lldb-server/main.cpp | 8 + lldb/tools/debugserver/source/DNB.cpp | 15 ++ lldb/tools/debugserver/source/DNB.h | 6 + .../debugserver/source/MacOSX/MachProcess.h | 5 +- .../debugserver/source/MacOSX/MachProcess.mm | 153 +++++++++--------- lldb/tools/debugserver/source/RNBRemote.cpp | 94 +---------- 8 files changed, 246 insertions(+), 169 deletions(-) create mode 100644 lldb/packages/Python/lldbsuite/test/tools/lldb-server/TestAppleSimulatorOSType.py diff --git a/lldb/packages/Python/lldbsuite/test/decorators.py b/lldb/packages/Python/lldbsuite/test/decorators.py index aad78043a0d9..e91f3ff65baa 100644 --- a/lldb/packages/Python/lldbsuite/test/decorators.py +++ b/lldb/packages/Python/lldbsuite/test/decorators.py @@ -344,6 +344,28 @@ def no_debug_info_test(func): wrapper.__no_debug_info_test__ = True return wrapper +def apple_simulator_test(platform): + """ + Decorate the test as a test requiring a simulator for a specific platform. + + Consider that a simulator is available if you have the corresponding SDK installed. + The SDK identifiers for simulators are iphonesimulator, appletvsimulator, watchsimulator + """ + def should_skip_simulator_test(): + if lldbplatformutil.getHostPlatform() != 'darwin': + return "simulator tests are run only on darwin hosts" + try: + DEVNULL = open(os.devnull, 'w') + output = subprocess.check_output(["xcodebuild", "-showsdks"], stderr=DEVNULL) + if re.search('%ssimulator' % platform, output): + return None + else: + return "%s simulator is not supported on this system." % platform + except subprocess.CalledProcessError: + return "%s is not supported on this system (xcodebuild failed)." % feature + + return skipTestIfFn(should_skip_simulator_test) + def debugserver_test(func): """Decorate the item as a debugserver test.""" diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/TestAppleSimulatorOSType.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/TestAppleSimulatorOSType.py new file mode 100644 index 000000000000..e5589ca4f0b4 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/TestAppleSimulatorOSType.py @@ -0,0 +1,112 @@ +from __future__ import print_function + + +import gdbremote_testcase +import lldbgdbserverutils +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + +import json + +class TestAppleSimulatorOSType(gdbremote_testcase.GdbRemoteTestCaseBase): + + mydir = TestBase.compute_mydir(__file__) + + def check_simulator_ostype(self, sdk, platform_names, arch='x86_64'): + sim_devices_str = subprocess.check_output(['xcrun', 'simctl', 'list', + '-j', 'devices']) + sim_devices = json.loads(sim_devices_str)['devices'] + # Find an available simulator for the requested platform + deviceUDID = None + for (runtime,devices) in sim_devices.items(): + if not any(p in runtime.lower() for p in platform_names): + continue + for device in devices: + if device['availability'] != '(available)': + continue + deviceUDID = device['udid'] + break + if deviceUDID != None: + break + + # Launch the process using simctl + self.assertIsNotNone(deviceUDID) + exe_name = 'test_simulator_platform_{}'.format(platform_names[0]) + sdkroot = subprocess.check_output(['xcrun', '--show-sdk-path', '--sdk', + sdk]) + self.build(dictionary={ 'EXE': exe_name, 'SDKROOT': sdkroot.strip(), + 'ARCH': arch }) + exe_path = self.getBuildArtifact(exe_name) + sim_launcher = subprocess.Popen(['xcrun', 'simctl', 'spawn', + deviceUDID, exe_path, + 'print-pid', 'sleep:10'], + stderr=subprocess.PIPE) + # Get the PID from the process output + pid = None + while not pid: + stderr = sim_launcher.stderr.readline() + if stderr == '': + continue + m = re.match(r"PID: (.*)", stderr) + self.assertIsNotNone(m) + pid = int(m.group(1)) + + # Launch debug monitor attaching to the simulated process + self.init_debugserver_test() + server = self.connect_to_debug_monitor(attach_pid=pid) + + # Setup packet sequences + self.add_no_ack_remote_stream() + self.add_process_info_collection_packets() + self.test_sequence.add_log_lines( + ["read packet: " + + "$jGetLoadedDynamicLibrariesInfos:{\"fetch_all_solibs\" : true}]#ce", + {"direction": "send", "regex": r"^\$(.+)#[0-9a-fA-F]{2}$", + "capture": {1: "dylib_info_raw"}}], + True) + + # Run the stream + context = self.expect_gdbremote_sequence() + self.assertIsNotNone(context) + + # Gather process info response + process_info = self.parse_process_info_response(context) + self.assertIsNotNone(process_info) + + # Check that ostype is correct + self.assertTrue(process_info['ostype'] in platform_names) + + # Now for dylibs + dylib_info_raw = context.get("dylib_info_raw") + dylib_info = json.loads(self.decode_gdbremote_binary(dylib_info_raw)) + images = dylib_info['images'] + + image_info = None + for image in images: + if image['pathname'] != exe_path: + continue + image_info = image + break + + self.assertIsNotNone(image_info) + self.assertTrue(image['min_version_os_name'] in platform_names) + + + @apple_simulator_test('iphone') + @debugserver_test + def test_simulator_ostype_ios(self): + self.check_simulator_ostype(sdk='iphonesimulator', + platform_names=['iphoneos', 'ios']) + + @apple_simulator_test('appletv') + @debugserver_test + def test_simulator_ostype_tvos(self): + self.check_simulator_ostype(sdk='appletvsimulator', + platform_names=['tvos']) + + @apple_simulator_test('watch') + @debugserver_test + def test_simulator_ostype_watchos(self): + self.check_simulator_ostype(sdk='watchsimulator', + platform_names=['watchos'], arch='i386') diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/main.cpp b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/main.cpp index 399c1d35ae6b..ca032c120beb 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-server/main.cpp +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-server/main.cpp @@ -48,6 +48,8 @@ static const char *const THREAD_COMMAND_NEW = "new"; static const char *const THREAD_COMMAND_PRINT_IDS = "print-ids"; static const char *const THREAD_COMMAND_SEGFAULT = "segfault"; +static const char *const PRINT_PID_COMMAND = "print-pid"; + static bool g_print_thread_ids = false; static pthread_mutex_t g_print_mutex = PTHREAD_MUTEX_INITIALIZER; static bool g_threads_do_segfault = false; @@ -61,6 +63,10 @@ static char g_message[256]; static volatile char g_c1 = '0'; static volatile char g_c2 = '1'; +static void print_pid() { + fprintf(stderr, "PID: %d\n", getpid()); +} + static void print_thread_id() { // Put in the right magic here for your platform to spit out the thread id (tid) // that debugserver/lldb-gdbserver would see as a TID. Otherwise, let the else @@ -349,6 +355,8 @@ int main(int argc, char **argv) { // At this point we don't do anything else with threads. // Later use thread index and send command to thread. } + } else if (std::strstr(argv[i], PRINT_PID_COMMAND)) { + print_pid(); } else { // Treat the argument as text for stdout. printf("%s\n", argv[i]); diff --git a/lldb/tools/debugserver/source/DNB.cpp b/lldb/tools/debugserver/source/DNB.cpp index 1168f1fc0a1e..4f212a73d64e 100644 --- a/lldb/tools/debugserver/source/DNB.cpp +++ b/lldb/tools/debugserver/source/DNB.cpp @@ -1426,6 +1426,21 @@ nub_bool_t DNBProcessSharedLibrariesUpdated(nub_process_t pid) { return false; } +const char *DNBGetDeploymentInfo(nub_process_t pid, + const struct load_command& lc, + uint64_t load_command_address, + uint32_t& major_version, + uint32_t& minor_version, + uint32_t& patch_version) { + MachProcessSP procSP; + if (GetProcessSP(pid, procSP)) + return procSP->GetDeploymentInfo(lc, load_command_address, + major_version, minor_version, + patch_version); + return nullptr; +} + + //---------------------------------------------------------------------- // Get the current shared library information for a process. Only return // the shared libraries that have changed since the last shared library diff --git a/lldb/tools/debugserver/source/DNB.h b/lldb/tools/debugserver/source/DNB.h index 7acbd42810cc..bce2c6abafd1 100644 --- a/lldb/tools/debugserver/source/DNB.h +++ b/lldb/tools/debugserver/source/DNB.h @@ -127,6 +127,12 @@ nub_bool_t DNBProcessSharedLibrariesUpdated(nub_process_t pid) DNB_EXPORT; 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, + uint64_t load_command_address, + 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/MacOSX/MachProcess.h b/lldb/tools/debugserver/source/MacOSX/MachProcess.h index 9ab06bcda9c6..2fb4dc5dbb6e 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachProcess.h +++ b/lldb/tools/debugserver/source/MacOSX/MachProcess.h @@ -246,7 +246,10 @@ public: uint64_t plo_pthread_tsd_base_address_offset, uint64_t plo_pthread_tsd_base_offset, uint64_t plo_pthread_tsd_entry_size); - + const char * + GetDeploymentInfo(const struct load_command&, uint64_t load_command_address, + uint32_t& major_version, uint32_t& minor_version, + uint32_t& patch_version); bool GetMachOInformationFromMemory(nub_addr_t mach_o_header_addr, int wordsize, struct mach_o_information &inf); diff --git a/lldb/tools/debugserver/source/MacOSX/MachProcess.mm b/lldb/tools/debugserver/source/MacOSX/MachProcess.mm index 96225543fe81..c1689521e30d 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachProcess.mm +++ b/lldb/tools/debugserver/source/MacOSX/MachProcess.mm @@ -572,6 +572,68 @@ nub_addr_t MachProcess::GetTSDAddressForThread( plo_pthread_tsd_entry_size); } + +const char *MachProcess::GetDeploymentInfo(const struct load_command& lc, + uint64_t load_command_address, + uint32_t& major_version, + uint32_t& minor_version, + uint32_t& patch_version) { + uint32_t cmd = lc.cmd & ~LC_REQ_DYLD; + bool lc_cmd_known = + cmd == LC_VERSION_MIN_IPHONEOS || cmd == LC_VERSION_MIN_MACOSX || + cmd == LC_VERSION_MIN_TVOS || cmd == LC_VERSION_MIN_WATCHOS; + + if (lc_cmd_known) { + struct version_min_command vers_cmd; + if (ReadMemory(load_command_address, sizeof(struct version_min_command), + &vers_cmd) != sizeof(struct version_min_command)) { + return nullptr; + } + major_version = vers_cmd.sdk >> 16; + minor_version = (vers_cmd.sdk >> 8) & 0xffu; + patch_version = vers_cmd.sdk & 0xffu; + + switch (cmd) { + case LC_VERSION_MIN_IPHONEOS: + return "iphoneos"; + case LC_VERSION_MIN_MACOSX: + return "macosx"; + case LC_VERSION_MIN_TVOS: + return "tvos"; + case LC_VERSION_MIN_WATCHOS: + return "watchos"; + default: + return nullptr; + } + } +#if defined (LC_BUILD_VERSION) + if (cmd == LC_BUILD_VERSION) { + struct build_version_command build_vers; + if (ReadMemory(load_command_address, sizeof(struct build_version_command), + &build_vers) != sizeof(struct build_version_command)) { + return nullptr; + } + major_version = build_vers.sdk >> 16;; + minor_version = (build_vers.sdk >> 8) & 0xffu; + patch_version = build_vers.sdk & 0xffu; + + switch (build_vers.platform) { + case PLATFORM_MACOS: + return "macosx"; + case PLATFORM_IOS: + return "iphoneos"; + case PLATFORM_TVOS: + return "tvos"; + case PLATFORM_WATCHOS: + return "watchos"; + case PLATFORM_BRIDGEOS: + return "bridgeos"; + } + } +#endif + return nullptr; +} + // Given an address, read the mach-o header and load commands out of memory to // fill in // the mach_o_information "inf" object. @@ -670,91 +732,22 @@ bool MachProcess::GetMachOInformationFromMemory( sizeof(struct uuid_command)) uuid_copy(inf.uuid, uuidcmd.uuid); } - bool lc_cmd_known = - lc.cmd == LC_VERSION_MIN_IPHONEOS || lc.cmd == LC_VERSION_MIN_MACOSX; -#if defined(LC_VERSION_MIN_TVOS) - lc_cmd_known |= lc.cmd == LC_VERSION_MIN_TVOS; -#endif -#if defined(LC_VERSION_MIN_WATCHOS) - lc_cmd_known |= lc.cmd == LC_VERSION_MIN_WATCHOS; -#endif - if (lc_cmd_known) { - struct version_min_command vers_cmd; - if (ReadMemory(load_cmds_p, sizeof(struct version_min_command), - &vers_cmd) != sizeof(struct version_min_command)) { - return false; - } - switch (lc.cmd) { - case LC_VERSION_MIN_IPHONEOS: - inf.min_version_os_name = "iphoneos"; - break; - case LC_VERSION_MIN_MACOSX: - inf.min_version_os_name = "macosx"; - break; -#if defined(LC_VERSION_MIN_TVOS) - case LC_VERSION_MIN_TVOS: - inf.min_version_os_name = "tvos"; - break; -#endif -#if defined(LC_VERSION_MIN_WATCHOS) - case LC_VERSION_MIN_WATCHOS: - inf.min_version_os_name = "watchos"; - break; -#endif - default: - return false; - } - uint32_t xxxx = vers_cmd.sdk >> 16; - uint32_t yy = (vers_cmd.sdk >> 8) & 0xffu; - uint32_t zz = vers_cmd.sdk & 0xffu; + + uint32_t major_version, minor_version, patch_version; + if (const char *platform = GetDeploymentInfo(lc, load_cmds_p, + major_version, minor_version, + patch_version)) { + inf.min_version_os_name = platform; inf.min_version_os_version = ""; - inf.min_version_os_version += std::to_string(xxxx); + inf.min_version_os_version += std::to_string(major_version); inf.min_version_os_version += "."; - inf.min_version_os_version += std::to_string(yy); - if (zz != 0) { + inf.min_version_os_version += std::to_string(minor_version); + if (patch_version != 0) { inf.min_version_os_version += "."; - inf.min_version_os_version += std::to_string(zz); + inf.min_version_os_version += std::to_string(patch_version); } } -#if defined (LC_BUILD_VERSION) - if (lc.cmd == LC_BUILD_VERSION) - { - struct build_version_command build_vers; - if (ReadMemory(load_cmds_p, sizeof(struct build_version_command), - &build_vers) != sizeof(struct build_version_command)) { - return false; - } - switch (build_vers.platform) - { - case PLATFORM_MACOS: - inf.min_version_os_name = "macosx"; - break; - case PLATFORM_IOS: - inf.min_version_os_name = "iphoneos"; - break; - case PLATFORM_TVOS: - inf.min_version_os_name = "tvos"; - break; - case PLATFORM_WATCHOS: - inf.min_version_os_name = "watchos"; - break; - case PLATFORM_BRIDGEOS: - inf.min_version_os_name = "bridgeos"; - break; - } - uint32_t xxxx = build_vers.sdk >> 16;; - uint32_t yy = (build_vers.sdk >> 8) & 0xffu; - uint32_t zz = build_vers.sdk & 0xffu; - inf.min_version_os_version = ""; - inf.min_version_os_version += std::to_string(xxxx); - inf.min_version_os_version += "."; - inf.min_version_os_version += std::to_string(yy); - if (zz != 0) { - inf.min_version_os_version += "."; - inf.min_version_os_version += std::to_string(zz); - } - } -#endif + load_cmds_p += lc.cmdsize; } return true; diff --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp index 28792343bbc3..361cbd451965 100644 --- a/lldb/tools/debugserver/source/RNBRemote.cpp +++ b/lldb/tools/debugserver/source/RNBRemote.cpp @@ -6091,100 +6091,18 @@ rnb_err_t RNBRemote::HandlePacket_qProcessInfo(const char *p) { for (uint32_t i = 0; i < mh.ncmds && !os_handled; ++i) { const nub_size_t bytes_read = DNBProcessMemoryRead(pid, load_command_addr, sizeof(lc), &lc); - uint32_t raw_cmd = lc.cmd & ~LC_REQ_DYLD; - if (bytes_read != sizeof(lc)) - break; - switch (raw_cmd) { - case LC_VERSION_MIN_IPHONEOS: - os_handled = true; - rep << "ostype:ios;"; - DNBLogThreadedIf(LOG_RNB_PROC, - "LC_VERSION_MIN_IPHONEOS -> 'ostype:ios;'"); - break; - case LC_VERSION_MIN_MACOSX: + uint32_t major_version, minor_version, patch_version; + auto *platform = DNBGetDeploymentInfo(pid, lc, load_command_addr, + major_version, minor_version, + patch_version); + if (platform) { os_handled = true; - rep << "ostype:macosx;"; - DNBLogThreadedIf(LOG_RNB_PROC, - "LC_VERSION_MIN_MACOSX -> 'ostype:macosx;'"); - break; - -#if defined(LC_VERSION_MIN_TVOS) - case LC_VERSION_MIN_TVOS: - os_handled = true; - rep << "ostype:tvos;"; - DNBLogThreadedIf(LOG_RNB_PROC, - "LC_VERSION_MIN_TVOS -> 'ostype:tvos;'"); - break; -#endif - -#if defined(LC_VERSION_MIN_WATCHOS) - case LC_VERSION_MIN_WATCHOS: - os_handled = true; - rep << "ostype:watchos;"; - DNBLogThreadedIf(LOG_RNB_PROC, - "LC_VERSION_MIN_WATCHOS -> 'ostype:watchos;'"); - break; -#endif - - default: + rep << "ostype:" << platform << ";"; break; } load_command_addr = load_command_addr + lc.cmdsize; } - -// Test that the PLATFORM_* defines are available from mach-o/loader.h -#if defined (PLATFORM_MACOS) - for (uint32_t i = 0; i < mh.ncmds && !os_handled; ++i) - { - nub_size_t bytes_read = - DNBProcessMemoryRead(pid, load_command_addr, sizeof(lc), &lc); - uint32_t raw_cmd = lc.cmd & ~LC_REQ_DYLD; - if (bytes_read != sizeof(lc)) - break; - - if (raw_cmd == LC_BUILD_VERSION) - { - uint32_t platform; // first field of 'struct build_version_command' - bytes_read = DNBProcessMemoryRead(pid, load_command_addr + 8, sizeof(platform), &platform); - if (bytes_read != sizeof (platform)) - break; - switch (platform) - { - case PLATFORM_MACOS: - os_handled = true; - rep << "ostype:macosx;"; - DNBLogThreadedIf(LOG_RNB_PROC, - "LC_BUILD_VERSION PLATFORM_MACOS -> 'ostype:macosx;'"); - break; - case PLATFORM_IOS: - os_handled = true; - rep << "ostype:ios;"; - DNBLogThreadedIf(LOG_RNB_PROC, - "LC_BUILD_VERSION PLATFORM_IOS -> 'ostype:ios;'"); - break; - case PLATFORM_TVOS: - os_handled = true; - rep << "ostype:tvos;"; - DNBLogThreadedIf(LOG_RNB_PROC, - "LC_BUILD_VERSION PLATFORM_TVOS -> 'ostype:tvos;'"); - break; - case PLATFORM_WATCHOS: - os_handled = true; - rep << "ostype:watchos;"; - DNBLogThreadedIf(LOG_RNB_PROC, - "LC_BUILD_VERSION PLATFORM_WATCHOS -> 'ostype:watchos;'"); - break; - case PLATFORM_BRIDGEOS: - os_handled = true; - rep << "ostype:bridgeos;"; - DNBLogThreadedIf(LOG_RNB_PROC, - "LC_BUILD_VERSION PLATFORM_BRIDGEOS -> 'ostype:bridgeos;'"); - break; - } - } - } -#endif // PLATFORM_MACOS } #endif // when compiling this on x86 targets }