diff --git a/share/qtcreator/debugger/lldbbridge.py b/share/qtcreator/debugger/lldbbridge.py index 57275c1e9a9..e612f69e36a 100644 --- a/share/qtcreator/debugger/lldbbridge.py +++ b/share/qtcreator/debugger/lldbbridge.py @@ -98,6 +98,7 @@ class Dumper(DumperBase): self.startMode_ = None self.processArgs_ = None self.attachPid_ = None + self.deviceUuid_ = None self.dyldImageSuffix = None self.dyldLibraryPath = None self.dyldFrameworkPath = None @@ -918,6 +919,7 @@ class Dumper(DumperBase): self.environment_ = args.get('environment', []) self.environment_ = list(map(lambda x: self.hexdecode(x), self.environment_)) self.attachPid_ = args.get('attachpid', 0) + self.deviceUuid_ = args.get('deviceUuid', '') self.sysRoot_ = args.get('sysroot', '') self.remoteChannel_ = args.get('remotechannel', '') self.platform_ = args.get('platform', '') @@ -942,14 +944,26 @@ class Dumper(DumperBase): if self.startMode_ == DebuggerStartMode.AttachExternal: self.symbolFile_ = '' - self.target = self.debugger.CreateTarget( - self.symbolFile_, None, self.platform_, True, error) + if self.startMode_ == DebuggerStartMode.AttachToIosDevice: + # The script code depends on a target from now on, + # so we already need to attach with the special Apple lldb debugger commands + self.runDebuggerCommand('device select ' + self.deviceUuid_) + self.runDebuggerCommand('device process attach -p ' + str(self.attachPid_)) + self.target = self.debugger.GetSelectedTarget() + else: + self.target = self.debugger.CreateTarget( + self.symbolFile_, None, self.platform_, True, error) if not error.Success(): self.report(self.describeError(error)) self.reportState('enginerunfailed') return + if not self.target: + self.report('Debugger failed to create target.') + self.reportState('enginerunfailed') + return + broadcaster = self.target.GetBroadcaster() listener = self.debugger.GetListener() broadcaster.AddListener(listener, lldb.SBProcess.eBroadcastBitStateChanged) @@ -1090,6 +1104,11 @@ class Dumper(DumperBase): self.reportState('enginerunokandinferiorunrunnable') else: self.reportState('enginerunfailed') + elif self.startMode_ == DebuggerStartMode.AttachToIosDevice: + # Already attached in setupInferior (to get a SBTarget), + # just get the process from it + self.process = self.target.GetProcess() + self.reportState('enginerunandinferiorrunok') else: launchInfo = lldb.SBLaunchInfo(self.processArgs_) launchInfo.SetWorkingDirectory(self.workingDirectory_) @@ -1920,16 +1939,20 @@ class Dumper(DumperBase): self.debugger.GetCommandInterpreter().HandleCommand(command, result) self.reportResult('fulltrace="%s"' % self.hexencode(result.GetOutput()), args) - def executeDebuggerCommand(self, args): - self.reportToken(args) + def runDebuggerCommand(self, command): + self.report('Running debugger command "{}"'.format(command)) result = lldb.SBCommandReturnObject() - command = args['command'] self.debugger.GetCommandInterpreter().HandleCommand(command, result) success = result.Succeeded() output = toCString(result.GetOutput()) error = toCString(str(result.GetError())) self.report('success="%d",output="%s",error="%s"' % (success, output, error)) + def executeDebuggerCommand(self, args): + self.reportToken(args) + command = args['command'] + self.runDebuggerCommand(command) + def executeRoundtrip(self, args): self.reportResult('', args) diff --git a/share/qtcreator/debugger/utils.py b/share/qtcreator/debugger/utils.py index 8019d1e530a..9cd4a8822ec 100644 --- a/share/qtcreator/debugger/utils.py +++ b/share/qtcreator/debugger/utils.py @@ -15,8 +15,10 @@ class DebuggerStartMode(): AttachCore, AttachToRemoteServer, AttachToRemoteProcess, + AttachToQmlServer, StartRemoteProcess, - ) = range(0, 9) + AttachToIosDevice + ) = range(0, 11) # Known special formats. Keep in sync with DisplayFormat in debuggerprotocol.h diff --git a/src/plugins/debugger/debuggerconstants.h b/src/plugins/debugger/debuggerconstants.h index 48dfeb5b67f..8d92cc67412 100644 --- a/src/plugins/debugger/debuggerconstants.h +++ b/src/plugins/debugger/debuggerconstants.h @@ -28,9 +28,8 @@ const char ANALYZERTASK_ID[] = "Analyzer.TaskId"; } // namespace Constants -// Keep in sync with dumper.py -enum DebuggerStartMode -{ +// Keep in sync with debugger/utils.py +enum DebuggerStartMode { NoStartMode, StartInternal, // Start current start project's binary StartExternal, // Start binary found in file system @@ -40,7 +39,8 @@ enum DebuggerStartMode AttachToRemoteServer, // Attach to a running gdbserver AttachToRemoteProcess, // Attach to a running remote process AttachToQmlServer, // Attach to a running QmlServer - StartRemoteProcess // Start and attach to a remote process + StartRemoteProcess, // Start and attach to a remote process + AttachToIosDevice // Attach to an application on a iOS 17+ device }; enum DebuggerCloseMode diff --git a/src/plugins/debugger/debuggerengine.h b/src/plugins/debugger/debuggerengine.h index e35bdf27af9..9b907837651 100644 --- a/src/plugins/debugger/debuggerengine.h +++ b/src/plugins/debugger/debuggerengine.h @@ -116,6 +116,8 @@ public: QString deviceSymbolsRoot; bool continueAfterAttach = false; Utils::FilePath sysRoot; + // iOS 17+ + QString deviceUuid; // Used by general core file debugging. Public access requested in QTCREATORBUG-17158. Utils::FilePath coreFile; diff --git a/src/plugins/debugger/debuggerruncontrol.cpp b/src/plugins/debugger/debuggerruncontrol.cpp index 1e87a30c7ab..9812c28f860 100644 --- a/src/plugins/debugger/debuggerruncontrol.cpp +++ b/src/plugins/debugger/debuggerruncontrol.cpp @@ -255,6 +255,11 @@ void DebuggerRunTool::setDeviceSymbolsRoot(const QString &deviceSymbolsRoot) m_runParameters.deviceSymbolsRoot = deviceSymbolsRoot; } +void DebuggerRunTool::setDeviceUuid(const QString &uuid) +{ + m_runParameters.deviceUuid = uuid; +} + void DebuggerRunTool::setTestCase(int testCase) { m_runParameters.testCase = testCase; diff --git a/src/plugins/debugger/debuggerruncontrol.h b/src/plugins/debugger/debuggerruncontrol.h index 66b559393ec..2faacc846bf 100644 --- a/src/plugins/debugger/debuggerruncontrol.h +++ b/src/plugins/debugger/debuggerruncontrol.h @@ -101,6 +101,8 @@ public: void setIosPlatform(const QString &platform); void setDeviceSymbolsRoot(const QString &deviceSymbolsRoot); + void setDeviceUuid(const QString &uuid); + void setAbi(const ProjectExplorer::Abi &abi); DebuggerEngineType cppEngineType() const; diff --git a/src/plugins/debugger/lldb/lldbengine.cpp b/src/plugins/debugger/lldb/lldbengine.cpp index 0010000edbd..c1d208dcf8c 100644 --- a/src/plugins/debugger/lldb/lldbengine.cpp +++ b/src/plugins/debugger/lldb/lldbengine.cpp @@ -270,6 +270,7 @@ void LldbEngine::handleLldbStarted() cmd2.arg("startmode", rp.startMode); cmd2.arg("nativemixed", isNativeMixedActive()); cmd2.arg("workingdirectory", inferior.workingDirectory.path()); + cmd2.arg("deviceUuid", rp.deviceUuid); Environment environment = inferior.environment; // Prevent lldb from automatically setting OS_ACTIVITY_DT_MODE to mirror // NSLog to stderr, as that will also mirror os_log, which we pick up in @@ -303,18 +304,20 @@ void LldbEngine::handleLldbStarted() if (rp.startMode != StartInternal) { // it is better not to check the start mode on the python sid (as we would have to duplicate the // enum values), and thus we assume that if the rp.attachPID is valid we really have to attach - QTC_CHECK(rp.attachPID.isValid() && (rp.startMode == AttachToRemoteProcess - || rp.startMode == AttachToLocalProcess - || rp.startMode == AttachToRemoteServer)); + QTC_CHECK( + rp.attachPID.isValid() + && (rp.startMode == AttachToRemoteProcess || rp.startMode == AttachToLocalProcess + || rp.startMode == AttachToRemoteServer || rp.startMode == AttachToIosDevice)); cmd2.arg("attachpid", rp.attachPID.pid()); cmd2.arg("sysroot", rp.deviceSymbolsRoot.isEmpty() ? rp.sysRoot.toString() : rp.deviceSymbolsRoot); cmd2.arg("remotechannel", ((rp.startMode == AttachToRemoteProcess || rp.startMode == AttachToRemoteServer) ? rp.remoteChannel : QString())); - QTC_CHECK(!rp.continueAfterAttach || (rp.startMode == AttachToRemoteProcess - || rp.startMode == AttachToLocalProcess - || rp.startMode == AttachToRemoteServer)); + QTC_CHECK( + !rp.continueAfterAttach + || (rp.startMode == AttachToRemoteProcess || rp.startMode == AttachToLocalProcess + || rp.startMode == AttachToRemoteServer || rp.startMode == AttachToIosDevice)); m_continueAtNextSpontaneousStop = false; } } diff --git a/src/plugins/ios/iosrunconfiguration.cpp b/src/plugins/ios/iosrunconfiguration.cpp index 42c6fc455cd..008d6f6d631 100644 --- a/src/plugins/ios/iosrunconfiguration.cpp +++ b/src/plugins/ios/iosrunconfiguration.cpp @@ -103,7 +103,8 @@ bool IosRunConfiguration::isEnabled(Id runMode) const IosDevice::ConstPtr iosdevice = std::dynamic_pointer_cast(dev); if (iosdevice && iosdevice->handler() == IosDevice::Handler::DeviceCtl - && runMode != ProjectExplorer::Constants::NORMAL_RUN_MODE) { + && runMode != ProjectExplorer::Constants::NORMAL_RUN_MODE + && !IosDeviceManager::isDeviceCtlDebugSupported()) { return false; } @@ -280,9 +281,9 @@ QString IosRunConfiguration::disabledReason(Id runMode) const } IosDevice::ConstPtr iosdevice = std::dynamic_pointer_cast(dev); if (iosdevice && iosdevice->handler() == IosDevice::Handler::DeviceCtl - && runMode != ProjectExplorer::Constants::NORMAL_RUN_MODE) { - return Tr::tr("Debugging and profiling is currently not supported for devices with iOS " - "17 and later."); + && runMode != ProjectExplorer::Constants::NORMAL_RUN_MODE + && !IosDeviceManager::isDeviceCtlDebugSupported()) { + return Tr::tr("Debugging on devices with iOS 17 and later requires Xcode 16 or later."); } } return RunConfiguration::disabledReason(runMode); diff --git a/src/plugins/ios/iosrunner.cpp b/src/plugins/ios/iosrunner.cpp index 0a4c55d536d..348bbe35781 100644 --- a/src/plugins/ios/iosrunner.cpp +++ b/src/plugins/ios/iosrunner.cpp @@ -92,6 +92,12 @@ static void stopRunningRunControl(RunControl *runControl) activeRunControls[devId] = runControl; } +static QString getBundleIdentifier(const FilePath &bundlePath) +{ + QSettings settings(bundlePath.pathAppended("Info.plist").toString(), QSettings::NativeFormat); + return settings.value(QString::fromLatin1("CFBundleIdentifier")).toString(); +} + struct AppInfo { QUrl pathOnDevice; @@ -104,16 +110,18 @@ public: DeviceCtlRunnerBase(RunControl *runControl); void start(); + qint64 processIdentifier() const { return m_processIdentifier; } protected: + GroupItem findApp(const QString &bundleIdentifier, Storage appInfo); + GroupItem findProcess(Storage &appInfo); void reportStoppedImpl(); IosDevice::ConstPtr m_device; QStringList m_arguments; + qint64 m_processIdentifier = -1; private: - GroupItem findApp(const QString &bundleIdentifier, Storage appInfo); - GroupItem findProcess(Storage &appInfo); GroupItem killProcess(Storage &appInfo); virtual GroupItem launchTask(const QString &bundleIdentifier) = 0; @@ -135,7 +143,6 @@ private: std::unique_ptr m_stopTask; std::unique_ptr m_pollTask; QTimer m_pollTimer; - qint64 m_processIdentifier = -1; }; class DeviceCtlRunner final : public DeviceCtlRunnerBase @@ -145,11 +152,15 @@ public: void stop(); + void setStartStopped(bool startStopped) { m_startStopped = startStopped; } + private: GroupItem launchTask(const QString &bundleIdentifier); Process m_process; std::unique_ptr m_deviceCtlOutput; + std::unique_ptr m_processIdTask; + bool m_startStopped = false; }; DeviceCtlRunnerBase::DeviceCtlRunnerBase(RunControl *runControl) @@ -305,9 +316,7 @@ void DeviceCtlRunnerBase::reportStoppedImpl() void DeviceCtlRunnerBase::start() { - QSettings settings(m_bundlePath.pathAppended("Info.plist").toString(), QSettings::NativeFormat); - const QString bundleIdentifier - = settings.value(QString::fromLatin1("CFBundleIdentifier")).toString(); + const QString bundleIdentifier = getBundleIdentifier(m_bundlePath); if (bundleIdentifier.isEmpty()) { reportFailure(Tr::tr("Failed to determine bundle identifier.")); return; @@ -450,25 +459,47 @@ GroupItem DeviceCtlRunner::launchTask(const QString &bundleIdentifier) reportFailure(Tr::tr("Running failed. Failed to create the temporary output file.")); return false; } - m_process.setCommand( - {FilePath::fromString("/usr/bin/xcrun"), - {"devicectl", - "device", - "process", - "launch", - "--device", - m_device->uniqueInternalDeviceId(), - "--quiet", - "--json-output", - m_deviceCtlOutput->fileName(), - "--console", - bundleIdentifier, - m_arguments}}); - connect(&m_process, &Process::started, this, [this] { reportStarted(); }); + const QStringList startStoppedArg = m_startStopped ? QStringList("--start-stopped") + : QStringList(); + const QStringList arguments = QStringList( + {"devicectl", + "device", + "process", + "launch", + "--device", + m_device->uniqueInternalDeviceId(), + "--quiet", + "--json-output", + m_deviceCtlOutput->fileName()}) + + startStoppedArg + + QStringList({"--console", bundleIdentifier}) + m_arguments; + m_process.setCommand({FilePath::fromString("/usr/bin/xcrun"), arguments}); + connect(&m_process, &Process::started, this, [this, bundleIdentifier] { + // devicectl does report the process ID in its json output, but that is broken + // for --console. When that is used, the json output is only written after the process + // finished, which is not helpful. + // Manually find the process ID for the bundle identifier. + Storage appInfo; + m_processIdTask.reset(new TaskTree(Group{ + sequential, + appInfo, + findApp(bundleIdentifier, appInfo), + findProcess(appInfo), + onGroupDone([this, appInfo](DoneWith doneWith) { + if (doneWith == DoneWith::Success) { + m_processIdentifier = appInfo->processIdentifier; + reportStarted(); + } else { + reportFailure(Tr::tr("Failed to retrieve process ID.")); + } + })})); + m_processIdTask->start(); + }); connect(&m_process, &Process::done, this, [this] { if (m_process.error() != QProcess::UnknownError) reportFailure(Tr::tr("Failed to run devicectl: %1.").arg(m_process.errorString())); m_deviceCtlOutput->reset(); + m_processIdTask.reset(); reportStoppedImpl(); }); connect(&m_process, &Process::readyReadStandardError, this, [this] { @@ -841,7 +872,8 @@ public: private: void start() override; - IosRunner *m_runner; + IosRunner *m_iosRunner = nullptr; + DeviceCtlRunner *m_deviceCtlRunner = nullptr; }; static expected_str findDeviceSdk(IosDevice::ConstPtr dev) @@ -874,15 +906,29 @@ IosDebugSupport::IosDebugSupport(RunControl *runControl) { setId("IosDebugSupport"); - m_runner = new IosRunner(runControl); - m_runner->setCppDebugging(isCppDebugging()); - m_runner->setQmlDebugging(isQmlDebugging() ? QmlDebuggerServices : NoQmlDebugServices); + IosDevice::ConstPtr dev = std::dynamic_pointer_cast(device()); - addStartDependency(m_runner); + if (dev->type() == Ios::Constants::IOS_SIMULATOR_TYPE + || dev->handler() == IosDevice::Handler::IosTool) { + m_iosRunner = new IosRunner(runControl); + m_iosRunner->setCppDebugging(isCppDebugging()); + m_iosRunner->setQmlDebugging(isQmlDebugging() ? QmlDebuggerServices : NoQmlDebugServices); + addStartDependency(m_iosRunner); + } else { + QTC_CHECK(isCppDebugging()); + m_deviceCtlRunner = new DeviceCtlRunner(runControl); + m_deviceCtlRunner->setStartStopped(true); + addStartDependency(m_deviceCtlRunner); + } if (device()->type() == Ios::Constants::IOS_DEVICE_TYPE) { - IosDevice::ConstPtr dev = std::dynamic_pointer_cast(device()); - setStartMode(AttachToRemoteProcess); + if (dev->handler() == IosDevice::Handler::DeviceCtl) { + QTC_CHECK(IosDeviceManager::isDeviceCtlDebugSupported()); + setStartMode(AttachToIosDevice); + setDeviceUuid(dev->uniqueInternalDeviceId()); + } else { + setStartMode(AttachToRemoteProcess); + } setIosPlatform("remote-ios"); const expected_str deviceSdk = findDeviceSdk(dev); @@ -898,20 +944,39 @@ IosDebugSupport::IosDebugSupport(RunControl *runControl) void IosDebugSupport::start() { - if (!m_runner->isAppRunning()) { + const IosDeviceTypeAspect::Data *data = runControl()->aspectData(); + QTC_ASSERT(data, reportFailure("Broken IosDeviceTypeAspect setup."); return); + setRunControlName(data->applicationName); + setContinueAfterAttach(true); + + IosDevice::ConstPtr dev = std::dynamic_pointer_cast(device()); + if (dev->type() == Ios::Constants::IOS_DEVICE_TYPE + && dev->handler() == IosDevice::Handler::DeviceCtl) { + const auto msgOnlyCppDebuggingSupported = [] { + return Tr::tr("Only C++ debugging is supported for devices with iOS 17 and later."); + }; + if (!isCppDebugging()) { + reportFailure(msgOnlyCppDebuggingSupported()); + return; + } + if (isQmlDebugging()) { + runParameters().isQmlDebugging = false; + appendMessage(msgOnlyCppDebuggingSupported(), OutputFormat::LogMessageFormat, true); + } + setAttachPid(m_deviceCtlRunner->processIdentifier()); + setInferiorExecutable(data->localExecutable); + DebuggerRunTool::start(); + return; + } + + if (!m_iosRunner->isAppRunning()) { reportFailure(Tr::tr("Application not running.")); return; } - const IosDeviceTypeAspect::Data *data = runControl()->aspectData(); - QTC_ASSERT(data, reportFailure("Broken IosDeviceTypeAspect setup."); return); - - setRunControlName(data->applicationName); - setContinueAfterAttach(true); - - Port gdbServerPort = m_runner->gdbServerPort(); - Port qmlServerPort = m_runner->qmlServerPort(); - setAttachPid(ProcessHandle(m_runner->pid())); + Port gdbServerPort = m_iosRunner->gdbServerPort(); + Port qmlServerPort = m_iosRunner->qmlServerPort(); + setAttachPid(ProcessHandle(m_iosRunner->pid())); const bool cppDebug = isCppDebugging(); const bool qmlDebug = isQmlDebugging();