JS controlled Debug Proxy (#211)

* ensure heartbeat happened recently before enabling jit

* JS controlled Debug Proxy

* Fix heartbeat issue & JS nil issue

* Update idevice

* Fix compile issue
This commit is contained in:
Huge_Black 2025-07-05 00:24:00 +08:00 • committed by GitHub
parent 144f748d81
commit 354333d873
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 2223 additions and 1158 deletions

View file

@ -0,0 +1,33 @@
//
// IDeviceJSBridgeDebugProxy.m
// StikJIT
//
// Created by s s on 2025/4/25.
//
@import Foundation;
@import JavaScriptCore;
#import "JSSupport.h"
#import "../idevice/JITEnableContext.h"
#import "../idevice/idevice.h"
#include "../idevice/jit.h"
NSString* handleJSContextSendDebugCommand(JSContext* context, NSString* commandStr, DebugProxyHandle* debugProxy) {
DebugserverCommandHandle* command = 0;
command = debugserver_command_new([commandStr UTF8String], NULL, 0);
char* attach_response = 0;
IdeviceFfiError* err = debug_proxy_send_command2(debugProxy, command, &attach_response);
debugserver_command_free(command);
if (err) {
context.exception = [JSValue valueWithObject:[NSString stringWithFormat:@"error code %d, msg %s", err->code, err->message] inContext:context];
idevice_error_free(err);
return nil;
}
NSString* commandResponse = nil;
if(attach_response) {
commandResponse = @(attach_response);
}
idevice_string_free(attach_response);
return commandResponse;
}

View file

@ -0,0 +1,11 @@
//
// JSSupport.h
// StikJIT
//
// Created by s s on 2025/4/24.
//
@import WebKit;
@import JavaScriptCore;
#include "../idevice/jit.h"
NSString* handleJSContextSendDebugCommand(JSContext* context, NSString* commandStr, DebugProxyHandle* debugProxy);

View file

@ -0,0 +1,81 @@
//
// RunJSView.swift
// StikJIT
//
// Created by s s on 2025/4/24.
//
import SwiftUI
import JavaScriptCore
class RunJSViewModel : ObservableObject {
var context: JSContext?
@Published var logs: [String] = []
@Published var scriptName : String = "Script"
var pid: Int
var debugProxy: OpaquePointer?
var semaphore: dispatch_semaphore_t?
init(pid: Int, debugProxy: OpaquePointer?, semaphore: dispatch_semaphore_t?) {
self.pid = pid
self.debugProxy = debugProxy
self.semaphore = semaphore
}
func runScript(path: URL) throws {
let scriptContent = try String(contentsOf: path, encoding: .utf8)
scriptName = path.lastPathComponent
let getPidFunction: @convention(block) () -> Int = {
return self.pid
}
let sendCommandFunction: @convention(block) (String?) -> String? = { commandStr in
guard let commandStr else {
self.context?.exception = JSValue(object: "command should not be nil", in: self.context!)
return nil
}
return handleJSContextSendDebugCommand(self.context, commandStr, self.debugProxy) ?? ""
}
let logFunction: @convention(block) (String) -> Void = { logStr in
DispatchQueue.main.async {
self.logs.append(logStr)
}
}
context = JSContext()
context?.setObject(getPidFunction, forKeyedSubscript: "get_pid" as NSString)
context?.setObject(sendCommandFunction, forKeyedSubscript: "send_command" as NSString)
context?.setObject(logFunction, forKeyedSubscript: "log" as NSString)
context?.evaluateScript(scriptContent)
if let semaphore {
semaphore.signal()
}
DispatchQueue.main.async {
if let exception = self.context?.exception {
self.logs.append(exception.debugDescription)
}
self.logs.append("Script Execution Completed.")
}
}
}
struct RunJSView : View {
@ObservedObject var model : RunJSViewModel
@State var webViewShow = false
var body: some View {
List {
ForEach(model.logs, id: \.self) { logStr in
Text(logStr)
}
}
.navigationTitle("Running \(model.scriptName)")
}
}

View file

@ -0,0 +1,50 @@
//
// ScriptEditorView.swift
// StikDebug
//
// Created by s s on 2025/7/4.
//
import SwiftUI
struct ScriptEditorView: View {
let scriptURL: URL
@State private var scriptContent: String = ""
@Environment(\.dismiss) private var dismiss
var body: some View {
VStack {
TextEditor(text: $scriptContent)
.padding()
.border(Color.gray, width: 1)
.navigationTitle(scriptURL.lastPathComponent)
.navigationBarTitleDisplayMode(.inline)
.font(.system(.footnote, design: .monospaced))
HStack {
Button("Cancel") {
dismiss()
}
.buttonStyle(.bordered)
Spacer()
Button("Save") {
saveScript()
dismiss()
}
.buttonStyle(.borderedProminent)
}
.padding()
}
.onAppear(perform: loadScript)
}
private func loadScript() {
scriptContent = (try? String(contentsOf: scriptURL)) ?? ""
}
private func saveScript() {
try? scriptContent.write(to: scriptURL, atomically: true, encoding: .utf8)
}
}

View file

@ -0,0 +1,139 @@
//
// ScriptListView.swift
// StikDebug
//
// Created by s s on 2025/7/4.
//
import SwiftUI
struct ScriptListView: View {
@State private var scripts: [URL] = []
@State private var selectedScript: URL?
@State private var navigateToEditor = false
@State private var showNewFileAlert = false
@State private var newFileName = ""
@AppStorage("DefaultScriptName") var defaultScriptName = "attachDetach.js"
var body: some View {
NavigationStack {
List {
Section {
ForEach(scripts, id: \.self) { script in
NavigationLink {
ScriptEditorView(scriptURL: script)
} label: {
HStack {
Text(script.lastPathComponent)
.font(.headline)
if(defaultScriptName == script.lastPathComponent) {
Spacer()
Image(systemName: "star.fill")
}
}
}
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
deleteScript(script)
} label: {
Label("Delete", systemImage: "trash")
}
Button {
saveDefaultScript(script)
} label: {
Label("Set Default", systemImage: "star")
}
.tint(.blue)
}
}
} footer: {
Text("Swipe left to set a script as the default script. Enable script execution after connecting in settings.")
}
}
.navigationTitle("JavaScript Files")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(action: {
showNewFileAlert = true
}) {
Label("New Script", systemImage: "plus")
}
}
}
.onAppear(perform: loadScripts)
.alert("New Script", isPresented: $showNewFileAlert, actions: {
TextField("Filename", text: $newFileName)
Button("Create", action: createNewScript)
Button("Cancel", role: .cancel) {}
})
}
}
private func scriptsDirectory() -> URL {
let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
.appendingPathComponent("scripts")
var isDir : ObjCBool = false
var isDirExist = FileManager.default.fileExists(atPath: dir.path(), isDirectory: &isDir)
do {
if isDirExist && !isDir.boolValue {
try FileManager.default.removeItem(at: dir)
isDirExist = false
}
if !isDirExist {
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
try FileManager.default.copyItem(at: Bundle.main.url(forResource: "attachDetach", withExtension: "js")!, to: dir.appendingPathComponent("attachDetach.js"))
}
} catch {
showAlert(title: "Unable to Create Scripts Folder", message: error.localizedDescription, showOk: true)
}
return dir
}
private func loadScripts() {
let dir = scriptsDirectory()
scripts = (try? FileManager.default.contentsOfDirectory(at: dir, includingPropertiesForKeys: nil))?
.filter { $0.pathExtension == "js" } ?? []
}
private func saveDefaultScript(_ url: URL) {
defaultScriptName = url.lastPathComponent
}
private func createNewScript() {
guard !newFileName.isEmpty else { return }
var filename = newFileName
if !filename.hasSuffix(".js") {
filename += ".js"
}
let newURL = scriptsDirectory().appendingPathComponent(filename)
guard !FileManager.default.fileExists(atPath: newURL.path) else {
showAlert(title: "Failed to Create New Script", message: "A script with the same name already exists.", showOk: true)
return
}
do {
try "".write(to: newURL, atomically: true, encoding: .utf8)
newFileName = ""
loadScripts()
} catch {
print("Error creating file: \(error)")
}
}
private func deleteScript(_ url: URL) {
try? FileManager.default.removeItem(at: url)
if url.lastPathComponent == defaultScriptName {
UserDefaults.standard.removeObject(forKey: "DefaultScriptName")
}
loadScripts()
}
}

View file

@ -0,0 +1,18 @@
function attach() {
log("Welcome to use StikDebug's srcipting feature! Here you can use JavaScript to customize your debug experience!")
log("This script is a demo script that attaches to the connected app and detaches from it.")
// get pid of the connected app
let pid = get_pid();
log(`pid = ${pid}`)
// attach
let attachResponse = send_command(`vAttach;${pid.toString(16)}`)
log(`attach_response = ${attachResponse}`)
// detach
let detachResponse = send_command(`D`)
log(`detachResponse = ${detachResponse}`)
}
attach()

View file

@ -5,3 +5,4 @@
#include "idevice/JITEnableContext.h"
#include "idevice/idevice.h"
#include "idevice/heartbeat.h"
#include "JSSupport/JSSupport.h"

View file

@ -783,9 +783,9 @@ func isPairing() -> Bool {
let pairingpath = URL.documentsDirectory.appendingPathComponent("pairingFile.plist").path
var pairingFile: IdevicePairingFile?
let err = idevice_pairing_file_read(pairingpath, &pairingFile)
if err != IdeviceSuccess {
print("Failed to read pairing file: \(err)")
if err.rawValue == -9 { // InvalidHostID is -9
if let err {
print("Failed to read pairing file: \(err.pointee.code)")
if err.pointee.code == -9 { // InvalidHostID is -9
return false
}
return false
@ -923,7 +923,7 @@ struct LoadingView: View {
}
}
public func showAlert(title: String, message: String, showOk: Bool, showTryAgain: Bool = false, primaryButtonText: String? = nil, messageType: MessageType = .error, completion: @escaping (Bool) -> Void) {
public func showAlert(title: String, message: String, showOk: Bool, showTryAgain: Bool = false, primaryButtonText: String? = nil, messageType: MessageType = .error, completion: ((Bool) -> Void)? = nil) {
DispatchQueue.main.async {
guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
return
@ -935,12 +935,12 @@ public func showAlert(title: String, message: String, showOk: Bool, showTryAgain
message: message,
onDismiss: {
rootViewController?.presentedViewController?.dismiss(animated: true)
completion(false)
completion?(false)
},
showButton: true,
primaryButtonText: primaryButtonText ?? "Try Again",
onPrimaryButtonTap: {
completion(true)
completion?(true)
},
messageType: messageType
)
@ -955,13 +955,13 @@ public func showAlert(title: String, message: String, showOk: Bool, showTryAgain
message: message,
onDismiss: {
rootViewController?.presentedViewController?.dismiss(animated: true)
completion(true)
completion?(true)
},
showButton: true,
primaryButtonText: primaryButtonText ?? "OK",
onPrimaryButtonTap: {
rootViewController?.presentedViewController?.dismiss(animated: true)
completion(true)
completion?(true)
},
messageType: messageType
)
@ -976,7 +976,7 @@ public func showAlert(title: String, message: String, showOk: Bool, showTryAgain
message: message,
onDismiss: {
rootViewController?.presentedViewController?.dismiss(animated: true)
completion(false)
completion?(false)
},
showButton: false,
messageType: messageType

View file

@ -63,7 +63,7 @@ func isMounted() -> Bool {
// Read pairing file
var pairingFile: IdevicePairingFile?
let err = idevice_pairing_file_read(pairingFilePath, &pairingFile)
if err != IdeviceSuccess {
if let err {
print("Failed to read pairing file: \(err)")
return false
}
@ -71,26 +71,26 @@ func isMounted() -> Bool {
// Create TCP provider
var provider: TcpProviderHandle?
let providerError = idevice_tcp_provider_new(sockaddrPointer, pairingFile, "ImageMounterTest", &provider)
if providerError != IdeviceSuccess {
if let providerError {
print("Failed to create TCP provider: \(providerError)")
return false
}
// Connect to image mounter
var client: ImageMounterHandle?
let connectError = image_mounter_connect_tcp(provider, &client)
if connectError != IdeviceSuccess {
let connectError = image_mounter_connect(provider, &client)
if let connectError {
print("Failed to connect to image mounter: \(connectError)")
return false
}
tcp_provider_free(provider)
idevice_provider_free(provider)
print("wow")
var devices: UnsafeMutableRawPointer?
var devicesLen: size_t = 0
let listError = image_mounter_copy_devices(client, &devices, &devicesLen)
if listError == IdeviceSuccess {
if listError == nil {
let deviceList = devices?.assumingMemoryBound(to: plist_t.self)
var devices: [String] = []
for i in 0..<devicesLen {
@ -140,15 +140,15 @@ func mountPersonalDDI(deviceIP: String = "10.7.0.1", imagePath: String, trustcac
var pairingFile: IdevicePairingFile?
let err = idevice_pairing_file_read(pairingFilePath.cString(using: .utf8), &pairingFile)
if err != IdeviceSuccess {
print("Failed to read pairing file: \(err)")
if let err {
print("Failed to read pairing file: \(err.pointee.code)")
return 3 // EC: 3
}
var provider: TcpProviderHandle?
let providerError = idevice_tcp_provider_new(sockaddrPointer, pairingFile, "ImageMounterTest".cString(using: .utf8), &provider)
if providerError != IdeviceSuccess {
if let providerError {
print("Failed to create TCP provider: \(providerError)")
return 4 // EC: 4
}
@ -156,24 +156,24 @@ func mountPersonalDDI(deviceIP: String = "10.7.0.1", imagePath: String, trustcac
var pairingFile2: IdevicePairingFile?
let P2err = idevice_pairing_file_read(pairingFilePath.cString(using: .utf8), &pairingFile2)
if P2err != IdeviceSuccess {
print("Failed to read pairing file: \(err)")
if let P2err {
print("Failed to read pairing file: \(P2err.pointee.code)")
return 5 // EC: 5
}
var lockdownClient: LockdowndClientHandle?
guard lockdownd_connect_tcp(provider, &lockdownClient) == IdeviceSuccess else {
if let err = lockdownd_connect(provider, &lockdownClient) {
print("Failed to connect to lockdownd")
return 6 // EC: 6
}
guard lockdownd_start_session(lockdownClient, pairingFile2) == IdeviceSuccess else {
if let err = lockdownd_start_session(lockdownClient, pairingFile2) {
print("Failed to start session")
return 7 // EC: 7
}
var uniqueChipIDPlist: plist_t?
guard lockdownd_get_value(lockdownClient, "UniqueChipID".cString(using: .utf8), &uniqueChipIDPlist) == IdeviceSuccess else {
if let err = lockdownd_get_value(lockdownClient, "UniqueChipID".cString(using: .utf8), nil, &uniqueChipIDPlist) {
print("Failed to get UniqueChipID")
return 8 // EC: 8
}
@ -185,7 +185,7 @@ func mountPersonalDDI(deviceIP: String = "10.7.0.1", imagePath: String, trustcac
var mounterClient: ImageMounterHandle?
guard image_mounter_connect_tcp(provider, &mounterClient) == IdeviceSuccess else {
if let err = image_mounter_connect(provider, &mounterClient) {
print("Failed to connect to image mounter")
return 9 // EC: 9
}
@ -193,7 +193,7 @@ func mountPersonalDDI(deviceIP: String = "10.7.0.1", imagePath: String, trustcac
let result = image.withUnsafeBytes { imagePtr in
trustcache.withUnsafeBytes { trustcachePtr in
buildManifest.withUnsafeBytes { manifestPtr in
image_mounter_mount_personalized_tcp_with_callback(
image_mounter_mount_personalized(
mounterClient,
provider,
imagePtr.baseAddress?.assumingMemoryBound(to: UInt8.self),
@ -203,13 +203,11 @@ func mountPersonalDDI(deviceIP: String = "10.7.0.1", imagePath: String, trustcac
manifestPtr.baseAddress?.assumingMemoryBound(to: UInt8.self),
buildManifest.count,
nil,
uniqueChipID,
progressCallback,
nil
uniqueChipID
)
}
}
}
return Int(result.rawValue)
return Int(result?.pointee.code ?? -1)
}

View file

@ -18,7 +18,6 @@ struct HomeView: View {
@AppStorage("username") private var username = "User"
@AppStorage("customAccentColor") private var customAccentColorHex: String = ""
@AppStorage("autoQuitAfterEnablingJIT") private var doAutoQuitAfterEnablingJIT = false
@Environment(\.colorScheme) private var colorScheme
@Environment(\.accentColor) private var environmentAccentColor
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
@ -40,6 +39,11 @@ struct HomeView: View {
@State private var pendingBundleIdToEnableJIT : String? = nil
@State private var pendingPIDToEnableJIT : Int? = nil
@AppStorage("useDefaultScript") private var useDefaultScript = false
@State var scriptViewShow = false
@AppStorage("DefaultScriptName") var selectedScript = "attachDetach.js"
@State var jsModel: RunJSViewModel?
private var accentColor: Color {
if customAccentColorHex.isEmpty {
return .blue
@ -296,6 +300,30 @@ struct HomeView: View {
startJITInBackground(with: selectedBundle)
}
}
.sheet(isPresented: $scriptViewShow) {
NavigationView {
if let jsModel {
RunJSView(model: jsModel)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Done") {
scriptViewShow = false
}
}
}
.navigationTitle(selectedScript)
.navigationBarTitleDisplayMode(.inline)
}
}
}
.onChange(of: scriptViewShow) { oldValue, newValue in
if !newValue, let jsModel {
jsModel.semaphore?.signal()
}
}
.textFieldAlert(
isPresented: $pidTextAlertShow,
title: "Please enter the PID of the process you want to connect to",
@ -373,6 +401,27 @@ struct HomeView: View {
// but we'll keep it empty to avoid breaking anything
}
private func getJsCallback() -> DebugAppCallback? {
let selectedScriptURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
.appendingPathComponent("scripts").appendingPathComponent(selectedScript)
if !FileManager.default.fileExists(atPath: selectedScriptURL.path()) {
return nil
}
return { pid, debugProxyHandle, semaphore in
jsModel = RunJSViewModel(pid: Int(pid), debugProxy: debugProxyHandle, semaphore: semaphore)
scriptViewShow = true
DispatchQueue.global(qos: .background).async {
do {
try jsModel?.runScript(path: selectedScriptURL)
} catch {
showAlert(title: "Error Occurred While Executing the Default Script.", message: error.localizedDescription, showOk: true)
}
}
}
}
private func startJITInBackground(with bundleID: String) {
isProcessing = true
@ -380,22 +429,17 @@ struct HomeView: View {
LogManager.shared.addInfoLog("Starting Debug for \(bundleID)")
DispatchQueue.global(qos: .background).async {
let success = JITEnableContext.shared.debugApp(withBundleID: bundleID, logger: { message in
if let message = message {
// Log messages from the JIT process
LogManager.shared.addInfoLog(message)
}
})
}, jsCallback: useDefaultScript ? getJsCallback() : nil)
DispatchQueue.main.async {
LogManager.shared.addInfoLog("Debug process completed for \(bundleID)")
isProcessing = false
if success && doAutoQuitAfterEnablingJIT {
exit(0)
}
}
}
}
@ -409,21 +453,17 @@ struct HomeView: View {
DispatchQueue.global(qos: .background).async {
let success = JITEnableContext.shared.debugApp(withPID: Int32(pid), logger: { message in
if let message = message {
// Log messages from the JIT process
LogManager.shared.addInfoLog(message)
}
})
}, jsCallback: useDefaultScript ? getJsCallback() : nil)
DispatchQueue.main.async {
LogManager.shared.addInfoLog("JIT process completed for \(pid)")
showAlert(title: "Success", message: "JIT has been enabled for pid \(pid).", showOk: true, messageType: .success, completion: { _ in })
showAlert(title: "Success", message: "JIT has been enabled for pid \(pid).", showOk: true, messageType: .success)
isProcessing = false
if success && doAutoQuitAfterEnablingJIT {
exit(0)
}
}
}
}

View file

@ -24,6 +24,10 @@ struct MainTabView: View {
.tabItem {
Label("Home", systemImage: "house")
}
ScriptListView()
.tabItem {
Label("Scripts", systemImage: "scroll")
}
SettingsView()
.tabItem {
Label("Settings", systemImage: "gearshape.fill")

View file

@ -11,7 +11,7 @@ struct SettingsView: View {
@AppStorage("username") private var username = "User"
@AppStorage("customAccentColor") private var customAccentColorHex: String = ""
@AppStorage("selectedAppIcon") private var selectedAppIcon: String = "AppIcon"
@AppStorage("autoQuitAfterEnablingJIT") private var doAutoQuitAfterEnablingJIT = false
@AppStorage("useDefaultScript") private var useDefaultScript = false
@State private var isShowingPairingFilePicker = false
@Environment(\.colorScheme) private var colorScheme
@ -271,7 +271,7 @@ struct SettingsView: View {
.foregroundColor(.primary)
.padding(.bottom, 4)
Toggle("Automatically Quit After Enabling JIT", isOn: $doAutoQuitAfterEnablingJIT)
Toggle("Run Default Script After Connecting", isOn: $useDefaultScript)
.foregroundColor(.primary)
.padding(.vertical, 6)
}

View file

@ -7,6 +7,7 @@
@import Foundation;
@import UIKit;
#include "idevice.h"
#include "jit.h"
typedef void (^HeartbeatCompletionHandler)(int result, NSString *message);
typedef void (^LogFuncC)(const char* message, ...);
@ -16,8 +17,8 @@ typedef void (^LogFunc)(NSString *message);
@property (class, readonly)JITEnableContext* shared;
- (IdevicePairingFile*)getPairingFileWithError:(NSError**)error;
- (void)startHeartbeatWithCompletionHandler:(HeartbeatCompletionHandler)completionHandler logger:(LogFunc)logger;
- (BOOL)debugAppWithBundleID:(NSString*)bundleID logger:(LogFunc)logger;
- (BOOL)debugAppWithPID:(int)pid logger:(LogFunc)logger;
- (BOOL)debugAppWithBundleID:(NSString*)bundleID logger:(LogFunc)logger jsCallback:(DebugAppCallback)jsCallback;
- (BOOL)debugAppWithPID:(int)pid logger:(LogFunc)logger jsCallback:(DebugAppCallback)jsCallback;
- (NSDictionary<NSString*, NSString*>*)getAppListWithError:(NSError**)error;
- (UIImage*)getAppIconWithBundleId:(NSString*)bundleId error:(NSError**)error;
@end

View file

@ -18,8 +18,8 @@
JITEnableContext* sharedJITContext = nil;
@implementation JITEnableContext {
int heartbeatSessionId;
TcpProviderHandle* provider;
bool heartbeatRunning;
IdeviceProviderHandle* provider;
}
+ (instancetype)shared {
@ -33,7 +33,7 @@ JITEnableContext* sharedJITContext = nil;
NSFileManager* fm = [NSFileManager defaultManager];
NSURL* docPathUrl = [fm URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].firstObject;
NSURL* logURL = [docPathUrl URLByAppendingPathComponent:@"idevice_log.txt"];
idevice_init_logger(Debug, Debug, (char*)logURL.path.UTF8String);
idevice_init_logger(Info, Debug, (char*)logURL.path.UTF8String);
return self;
}
@ -80,9 +80,9 @@ JITEnableContext* sharedJITContext = nil;
}
IdevicePairingFile* pairingFile = NULL;
IdeviceErrorCode err = idevice_pairing_file_read(pairingFileURL.fileSystemRepresentation, &pairingFile);
if (err != IdeviceSuccess) {
*error = [self errorWithStr:@"Failed to read pairing file!" code:err];
IdeviceFfiError* err = idevice_pairing_file_read(pairingFileURL.fileSystemRepresentation, &pairingFile);
if (err) {
*error = [self errorWithStr:@"Failed to read pairing file!" code:err->code];
return nil;
}
return pairingFile;
@ -106,11 +106,13 @@ JITEnableContext* sharedJITContext = nil;
return;
}
self->heartbeatSessionId = arc4random();
if(heartbeatRunning) {
return;
}
startHeartbeat(
pairingFile,
&provider,
&heartbeatSessionId,
&heartbeatRunning,
^(int result, const char *message) {
completionHandler(result,
[NSString stringWithCString:message
@ -129,7 +131,7 @@ JITEnableContext* sharedJITContext = nil;
}
}
- (BOOL)debugAppWithBundleID:(NSString*)bundleID logger:(LogFunc)logger {
- (BOOL)debugAppWithBundleID:(NSString*)bundleID logger:(LogFunc)logger jsCallback:(DebugAppCallback)jsCallback {
if (!provider) {
if (logger) {
logger(@"Provider not initialized!");
@ -142,10 +144,10 @@ JITEnableContext* sharedJITContext = nil;
return debug_app(provider,
[bundleID UTF8String],
[self createCLogger:logger]) == 0;
[self createCLogger:logger], jsCallback) == 0;
}
- (BOOL)debugAppWithPID:(int)pid logger:(LogFunc)logger {
- (BOOL)debugAppWithPID:(int)pid logger:(LogFunc)logger jsCallback:(DebugAppCallback)jsCallback {
if (!provider) {
if (logger) {
logger(@"Provider not initialized!");
@ -158,7 +160,7 @@ JITEnableContext* sharedJITContext = nil;
return debug_app_pid(provider,
pid,
[self createCLogger:logger]) == 0;
[self createCLogger:logger], jsCallback) == 0;
}
- (NSDictionary<NSString*, NSString*>*)getAppListWithError:(NSError**)error {
@ -194,9 +196,8 @@ JITEnableContext* sharedJITContext = nil;
}
- (void)dealloc {
heartbeatSessionId = arc4random();
if (provider) {
tcp_provider_free(provider);
idevice_provider_free(provider);
}
}

View file

@ -10,7 +10,7 @@
@import Foundation;
@import UIKit;
NSDictionary<NSString*, NSString*>* list_installed_apps(TcpProviderHandle* provider, NSString** error);
UIImage* getAppIcon(TcpProviderHandle* provider, NSString* bundleID, NSString** error);
NSDictionary<NSString*, NSString*>* list_installed_apps(IdeviceProviderHandle* provider, NSString** error);
UIImage* getAppIcon(IdeviceProviderHandle* provider, NSString* bundleID, NSString** error);
#endif /* APPLIST_H */

View file

@ -11,16 +11,16 @@
#include <string.h>
#import "applist.h"
NSDictionary<NSString*, NSString*>* list_installed_apps(TcpProviderHandle* provider, NSString** error) {
NSDictionary<NSString*, NSString*>* list_installed_apps(IdeviceProviderHandle* provider, NSString** error) {
InstallationProxyClientHandle *client = NULL;
if (installation_proxy_connect_tcp(provider, &client) != IdeviceSuccess) {
if (installation_proxy_connect_tcp(provider, &client)) {
*error = @"Failed to connect to installation proxy";
return nil;
}
void *apps = NULL;
size_t count = 0;
if (installation_proxy_get_apps(client, "User", NULL, 0, &apps, &count) != IdeviceSuccess) {
if (installation_proxy_get_apps(client, "User", NULL, 0, &apps, &count)) {
installation_proxy_client_free(client);
*error = @"Failed to get apps";
return nil;
@ -70,16 +70,16 @@ NSDictionary<NSString*, NSString*>* list_installed_apps(TcpProviderHandle* provi
return result;
}
UIImage* getAppIcon(TcpProviderHandle* provider, NSString* bundleID, NSString** error) {
UIImage* getAppIcon(IdeviceProviderHandle* provider, NSString* bundleID, NSString** error) {
SpringBoardServicesClientHandle *client = NULL;
if (springboard_services_connect_tcp(provider, &client) != IdeviceSuccess) {
if (springboard_services_connect(provider, &client)) {
*error = @"Failed to connect to SpringBoard Services";
return nil;
}
void *pngData = NULL;
size_t dataLen = 0;
if (springboard_services_get_icon(client, [bundleID UTF8String], &pngData, &dataLen) != IdeviceSuccess) {
if (springboard_services_get_icon(client, [bundleID UTF8String], &pngData, &dataLen)) {
springboard_services_free(client);
*error = @"Failed to get app icon";
return nil;

View file

@ -17,6 +17,6 @@ typedef void (^LogFuncC)(const char* message, ...);
extern bool isHeartbeat;
extern NSDate* lastHeartbeatDate;
void startHeartbeat(IdevicePairingFile* pairintFile, TcpProviderHandle** provider, int* heartbeatSessionId, HeartbeatCompletionHandlerC completion, LogFuncC logger);
void startHeartbeat(IdevicePairingFile* pairintFile, IdeviceProviderHandle** provider, bool* isHeartbeat, HeartbeatCompletionHandlerC completion, LogFuncC logger);
#endif /* HEARTBEAT_H */

View file

@ -15,74 +15,67 @@
bool isHeartbeat = false;
NSDate* lastHeartbeatDate = nil;
void startHeartbeat(IdevicePairingFile* pairing_file, TcpProviderHandle** provider, int* heartbeatSessionId, HeartbeatCompletionHandlerC completion, LogFuncC logger) {
int currentSessionId = *heartbeatSessionId;
void startHeartbeat(IdevicePairingFile* pairing_file, IdeviceProviderHandle** provider, bool* isHeartbeat, HeartbeatCompletionHandlerC completion, LogFuncC logger) {
isHeartbeat = true;
*isHeartbeat = true;
// Initialize logger
idevice_init_logger(Debug, Disabled, NULL);
// Create the socket address (replace with your device's IP)
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
if (inet_pton(AF_INET, "10.7.0.1", &addr.sin_addr) <= 0) {
logger("DEBUG: Error converting IP address.");
isHeartbeat = false;
addr.sin_port = htons(LOCKDOWN_PORT);
inet_pton(AF_INET, "10.7.0.2", &addr.sin_addr);
IdeviceFfiError* err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file,
"ExampleProvider", provider);
if (err != NULL) {
fprintf(stderr, "Failed to create TCP provider: [%d] %s", err->code,
err->message);
idevice_pairing_file_free(pairing_file);
idevice_error_free(err);
*isHeartbeat = false;
return;
}
logger("DEBUG: Socket address created for IP 10.7.0.1");
IdeviceErrorCode err = IdeviceSuccess;
logger("DEBUG: Creating TCP provider...");
err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file,
"ExampleProvider", provider);
if (err != IdeviceSuccess) {
logger("DEBUG: Failed to create TCP provider: %d", err);
completion(err, "Failed to create TCP provider");
isHeartbeat = false;
return;
}
logger("DEBUG: TCP provider created successfully.");
logger("DEBUG: Connecting to heartbeat...");
// Connect to installation proxy
HeartbeatClientHandle *client = NULL;
err = heartbeat_connect_tcp(*provider, &client);
if (err != IdeviceSuccess) {
completion(err, "Failed to connect to Heartbeat");
logger("DEBUG: Failed to connect to installation proxy: %d", err);
isHeartbeat = false;
err = heartbeat_connect(*provider, &client);
if (err != NULL) {
fprintf(stderr, "Failed to connect to installation proxy: [%d] %s",
err->code, err->message);
idevice_provider_free(*provider);
idevice_error_free(err);
*isHeartbeat = false;
return;
}
logger("DEBUG: Connected to heartbeat successfully.");
completion(0, "Heartbeat Completed");
u_int64_t current_interval = 15;
while (1) {
if(*heartbeatSessionId != currentSessionId) {
break;
}
// Get the new interval
u_int64_t new_interval = 0;
logger("DEBUG: Sending heartbeat with current interval: %llu seconds...", current_interval);
err = heartbeat_get_marco(client, current_interval, &new_interval);
if (err != IdeviceSuccess) {
logger("DEBUG: Failed to get marco: %d", err);
isHeartbeat = false;
if (err != NULL) {
fprintf(stderr, "Failed to get marco: [%d] %s", err->code, err->message);
heartbeat_client_free(client);
idevice_error_free(err);
*isHeartbeat = false;
return;
}
logger("DEBUG: Received new interval: %llu seconds.", new_interval);
current_interval = new_interval + 5;
logger("DEBUG: Sending polo reply...");
// Reply
err = heartbeat_send_polo(client);
if (err != IdeviceSuccess) {
logger("DEBUG: Failed to send polo: %d", err);
isHeartbeat = false;
if (err != NULL) {
fprintf(stderr, "Failed to get marco: [%d] %s", err->code, err->message);
heartbeat_client_free(client);
idevice_error_free(err);
*isHeartbeat = false;
return;
}
logger("DEBUG: Polo reply sent successfully.");
lastHeartbeatDate = [NSDate date];
isHeartbeat = true;
}
}

File diff suppressed because it is too large Load diff

View file

@ -18,473 +18,298 @@
#include "jit.h"
int debug_app(TcpProviderHandle* tcp_provider, const char *bundle_id, LogFuncC logger) {
// Initialize logger
idevice_init_logger(Debug, Disabled, NULL);
IdeviceErrorCode err = IdeviceSuccess;
// Connect to CoreDeviceProxy
CoreDeviceProxyHandle *core_device = NULL;
err = core_device_proxy_connect_tcp(tcp_provider, &core_device);
if (err != IdeviceSuccess) {
logger("Failed to connect to CoreDeviceProxy: %d", err);
return 1;
IdeviceFfiError* debug_proxy_send_command2(struct DebugProxyHandle *handle, struct DebugserverCommandHandle *command, char **response) {
IdeviceFfiError* err = debug_proxy_send_command(handle, command, response);
if(err) {
return err;
}
for(int i = 0; i < 10; ++i) {
if(*response) {
return err;
}
debug_proxy_read_response(handle, response);
}
return err;
}
void runDebugServerCommand(int pid, DebugProxyHandle* debug_proxy, LogFuncC logger, DebugAppCallback callback) {
// enable QStartNoAckMode
char *disableResponse = NULL;
debug_proxy_send_ack(debug_proxy);
debug_proxy_send_ack(debug_proxy);
DebugserverCommandHandle *disableAckCommand = debugserver_command_new("QStartNoAckMode", NULL, 0);
IdeviceFfiError* err = debug_proxy_send_command(debug_proxy, disableAckCommand, &disableResponse);
debugserver_command_free(disableAckCommand);
logger("QStartNoAckMode result = %s, err = %d", disableResponse, err);
idevice_string_free(disableResponse);
debug_proxy_set_ack_mode(debug_proxy, false);
// Get server RSD port
if(callback) {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
callback(pid, debug_proxy, semaphore);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
err = debug_proxy_send_raw(debug_proxy, "\x03", 1);
} else {
// Send vAttach command with PID in hex
char attach_command[64];
snprintf(attach_command, sizeof(attach_command), "vAttach;%" PRIx64, pid);
DebugserverCommandHandle *attach_cmd = debugserver_command_new(attach_command, NULL, 0);
if (attach_cmd == NULL) {
logger("Failed to create attach command");
return;
}
char *attach_response = NULL;
err = debug_proxy_send_command2(debug_proxy, attach_cmd, &attach_response);
debugserver_command_free(attach_cmd);
if (err) {
logger("Failed to attach to process: %d", err);
} else if (attach_response != NULL) {
logger("Attach response: %s", attach_response);
idevice_string_free(attach_response);
}
}
// Send detach command
DebugserverCommandHandle *detach_cmd = debugserver_command_new("D", NULL, 0);
if (detach_cmd == NULL) {
logger("Failed to create detach command");
} else {
char *detach_response = NULL;
err = debug_proxy_send_command2(debug_proxy, detach_cmd, &detach_response);
debugserver_command_free(detach_cmd);
if (err) {
logger("Failed to detach from process: %d", err);
} else if (detach_response != NULL) {
logger("Detach response: %s", detach_response);
idevice_string_free(detach_response);
}
}
}
int debug_app(IdeviceProviderHandle* tcp_provider, const char *bundle_id, LogFuncC logger, DebugAppCallback callback) {
// Initialize logger
idevice_init_logger(Info, Disabled, NULL);
IdeviceFfiError* err = 0;
CoreDeviceProxyHandle *core_device = NULL;
err = core_device_proxy_connect(tcp_provider, &core_device);
if (err != NULL) {
fprintf(stderr, "Failed to connect to CoreDeviceProxy: [%d] %s\n",
err->code, err->message);
idevice_error_free(err);
return 1;
}
uint16_t rsd_port;
err = core_device_proxy_get_server_rsd_port(core_device, &rsd_port);
if (err != IdeviceSuccess) {
logger("Failed to get server RSD port: %d", err);
core_device_proxy_free(core_device);
return 1;
if (err != NULL) {
fprintf(stderr, "Failed to get server RSD port: [%d] %s\n", err->code,
err->message);
idevice_error_free(err);
core_device_proxy_free(core_device);
return 1;
}
logger("Server RSD Port: %d", rsd_port);
/*****************************************************************
* Create TCP Tunnel Adapter
*****************************************************************/
logger("=== Creating TCP Tunnel Adapter ===");
printf("Server RSD Port: %d\n", rsd_port);
printf("\n=== Creating TCP Tunnel Adapter ===\n");
AdapterHandle *adapter = NULL;
err = core_device_proxy_create_tcp_adapter(core_device, &adapter);
if (err != IdeviceSuccess) {
logger("Failed to create TCP adapter: %d", err);
core_device_proxy_free(core_device);
return 1;
if (err != NULL) {
fprintf(stderr, "Failed to create TCP adapter: [%d] %s\n", err->code,
err->message);
idevice_error_free(err);
return 1;
}
// Connect to RSD port
err = adapter_connect(adapter, rsd_port);
if (err != IdeviceSuccess) {
logger("Failed to connect to RSD port: %d", err);
adapter_free(adapter);
return 1;
}
logger("Successfully connected to RSD port");
/*****************************************************************
* XPC Device Setup
*****************************************************************/
logger("=== Setting up XPC Device ===");
XPCDeviceAdapterHandle *xpc_device = NULL;
err = xpc_device_new(adapter, &xpc_device);
if (err != IdeviceSuccess) {
logger("Failed to create XPC device: %d", err);
adapter_free(adapter);
return 1;
}
// Get DebugProxy service
XPCServiceHandle *debug_service = NULL;
err = xpc_device_get_service(xpc_device, "com.apple.internal.dt.remote.debugproxy", &debug_service);
if (err != IdeviceSuccess) {
logger("Failed to get debug proxy service: %d", err);
xpc_device_free(xpc_device);
return 1;
}
// Get ProcessControl service
XPCServiceHandle *pc_service = NULL;
err = xpc_device_get_service(xpc_device, "com.apple.instruments.dtservicehub", &pc_service);
if (err != IdeviceSuccess) {
logger("Failed to get process control service: %d", err);
xpc_device_free(xpc_device);
return 1;
}
/*****************************************************************
* Process Control - Launch App
*****************************************************************/
logger("=== Launching App ===");
uint64_t pid;
// Get the adapter back from the XPC device
AdapterHandle *pc_adapter = NULL;
err = xpc_device_adapter_into_inner(xpc_device, &pc_adapter);
if (err != IdeviceSuccess) {
logger("Failed to extract adapter: %d", err);
xpc_device_free(xpc_device);
return 1;
AdapterStreamHandle *stream = NULL;
err = adapter_connect(adapter, rsd_port, (ReadWriteOpaque **)&stream);
if (err != NULL) {
fprintf(stderr, "Failed to connect to RSD port: [%d] %s\n", err->code,
err->message);
idevice_error_free(err);
adapter_free(adapter);
return 1;
}
// Connect to process control port
err = adapter_connect(pc_adapter, pc_service->port);
if (err != IdeviceSuccess) {
logger("Failed to connect to process control port: %d", err);
adapter_free(pc_adapter);
xpc_service_free(pc_service);
xpc_service_free(debug_service);
return 1;
printf("Successfully connected to RSD port\n");
printf("\n=== Performing RSD Handshake ===\n");
RsdHandshakeHandle *handshake = NULL;
err = rsd_handshake_new((ReadWriteOpaque *)stream, &handshake);
if (err != NULL) {
fprintf(stderr, "Failed to perform RSD handshake: [%d] %s\n", err->code,
err->message);
idevice_error_free(err);
adapter_close(stream);
adapter_free(adapter);
return 1;
}
logger("Successfully connected to process control port");
// Create RemoteServerClient
RemoteServerAdapterHandle *remote_server = NULL;
err = remote_server_adapter_new(pc_adapter, &remote_server);
if (err != IdeviceSuccess) {
logger("Failed to create remote server: %d", err);
adapter_free(pc_adapter);
xpc_service_free(pc_service);
xpc_service_free(debug_service);
return 1;
RemoteServerHandle *remote_server = NULL;
err = remote_server_connect_rsd(adapter, handshake, &remote_server);
if (err != NULL) {
fprintf(stderr, "Failed to create remote server: [%d] %s", err->code,
err->message);
idevice_error_free(err);
adapter_free(adapter);
rsd_handshake_free(handshake);
return 1;
}
printf("\n=== Testing Process Control ===\n");
// Create ProcessControlClient
ProcessControlAdapterHandle *process_control = NULL;
ProcessControlHandle *process_control = NULL;
err = process_control_new(remote_server, &process_control);
if (err != IdeviceSuccess) {
logger("Failed to create process control client: %d", err);
remote_server_free(remote_server);
xpc_service_free(pc_service);
xpc_service_free(debug_service);
return 1;
if (err != NULL) {
fprintf(stderr, "Failed to create process control client: [%d] %s",
err->code, err->message);
idevice_error_free(err);
remote_server_free(remote_server);
return 1;
}
// Launch application
uint64_t pid;
err = process_control_launch_app(process_control, bundle_id, NULL, 0, NULL, 0,
true, false, &pid);
if (err != IdeviceSuccess) {
logger("Failed to launch app: %d", err);
process_control_free(process_control);
remote_server_free(remote_server);
xpc_service_free(pc_service);
xpc_service_free(debug_service);
return 1;
if (err != NULL) {
fprintf(stderr, "Failed to launch app: [%d] %s", err->code, err->message);
idevice_error_free(err);
process_control_free(process_control);
remote_server_free(remote_server);
return 1;
}
printf("Successfully launched app with PID: %llu\n", pid);
printf("\n=== Setting up Debug Proxy ===\n");
DebugProxyHandle *debug_proxy = NULL;
err = debug_proxy_connect_rsd(adapter, handshake, &debug_proxy);
if (err != NULL) {
fprintf(stderr, "Failed to create debug proxy client: [%d] %s\n", err->code,
err->message);
idevice_error_free(err);
rsd_handshake_free(handshake);
adapter_free(adapter);
return 1;
}
logger("Successfully launched app with PID: %" PRIu64 "", pid);
// Disable memory limit for PID
err = process_control_disable_memory_limit(process_control, pid);
if (err != IdeviceSuccess) {
logger("failed to disable memory limit: %d", err);
}
/*****************************************************************
* Debug Proxy - Attach to Process
*****************************************************************/
logger("=== Attaching Debugger ===");
// Get the adapter back from the remote server
AdapterHandle *debug_adapter = NULL;
err = remote_server_adapter_into_inner(remote_server, &debug_adapter);
if (err != IdeviceSuccess) {
logger("Failed to extract adapter: %d", err);
xpc_service_free(debug_service);
process_control_free(process_control);
remote_server_free(remote_server);
xpc_service_free(pc_service);
return 1;
}
// Connect to debug proxy port
err = adapter_connect(debug_adapter, debug_service->port);
if (err != IdeviceSuccess) {
logger("Failed to connect to debug proxy port: %d", err);
adapter_free(debug_adapter);
xpc_service_free(debug_service);
process_control_free(process_control);
remote_server_free(remote_server);
xpc_service_free(pc_service);
return 1;
}
logger("Successfully connected to debug proxy port");
// Create DebugProxyClient
DebugProxyAdapterHandle *debug_proxy = NULL;
err = debug_proxy_adapter_new(debug_adapter, &debug_proxy);
if (err != IdeviceSuccess) {
logger("Failed to create debug proxy client: %d", err);
adapter_free(debug_adapter);
xpc_service_free(debug_service);
process_control_free(process_control);
remote_server_free(remote_server);
xpc_service_free(pc_service);
return 1;
}
// Send vAttach command with PID in hex
char attach_command[64];
snprintf(attach_command, sizeof(attach_command), "vAttach;%" PRIx64, pid);
DebugserverCommandHandle *attach_cmd = debugserver_command_new(attach_command, NULL, 0);
if (attach_cmd == NULL) {
logger("Failed to create attach command");
debug_proxy_free(debug_proxy);
xpc_service_free(debug_service);
process_control_free(process_control);
remote_server_free(remote_server);
xpc_service_free(pc_service);
return 1;
}
char *attach_response = NULL;
err = debug_proxy_send_command(debug_proxy, attach_cmd, &attach_response);
debugserver_command_free(attach_cmd);
if (err != IdeviceSuccess) {
logger("Failed to attach to process: %d", err);
} else if (attach_response != NULL) {
logger("Attach response: %s", attach_response);
idevice_string_free(attach_response);
}
// Send detach command
DebugserverCommandHandle *detach_cmd = debugserver_command_new("D", NULL, 0);
if (detach_cmd == NULL) {
logger("Failed to create detach command");
} else {
char *detach_response = NULL;
err = debug_proxy_send_command(debug_proxy, detach_cmd, &detach_response);
err = debug_proxy_send_command(debug_proxy, detach_cmd, &detach_response);
err = debug_proxy_send_command(debug_proxy, detach_cmd, &detach_response);
debugserver_command_free(detach_cmd);
if (err != IdeviceSuccess) {
logger("Failed to detach from process: %d", err);
} else if (detach_response != NULL) {
logger("Detach response: %s", detach_response);
idevice_string_free(detach_response);
}
}
runDebugServerCommand((int)pid, debug_proxy, logger, callback);
/*****************************************************************
* Cleanup
*****************************************************************/
debug_proxy_free(debug_proxy);
xpc_service_free(debug_service);
xpc_service_free(pc_service);
process_control_free(process_control);
remote_server_free(remote_server);
rsd_handshake_free(handshake);
adapter_free(adapter);
logger("Debug session completed");
return 0;
}
int debug_app_pid(TcpProviderHandle* tcp_provider, int pid, LogFuncC logger) {
// Connect to CoreDeviceProxy
CoreDeviceProxyHandle *core_device = NULL;
IdeviceErrorCode err = core_device_proxy_connect_tcp(tcp_provider, &core_device);
if (err != IdeviceSuccess) {
logger("Failed to connect to CoreDeviceProxy: %d", err);
return 1;
}
int debug_app_pid(IdeviceProviderHandle* tcp_provider, int pid, LogFuncC logger, DebugAppCallback callback) {
IdeviceFfiError* err = 0;
// Get server RSD port
CoreDeviceProxyHandle *core_device = NULL;
err = core_device_proxy_connect(tcp_provider, &core_device);
if (err != NULL) {
fprintf(stderr, "Failed to connect to CoreDeviceProxy: [%d] %s\n",
err->code, err->message);
idevice_error_free(err);
return 1;
}
uint16_t rsd_port;
err = core_device_proxy_get_server_rsd_port(core_device, &rsd_port);
if (err != IdeviceSuccess) {
logger("Failed to get server RSD port: %d", err);
core_device_proxy_free(core_device);
return 1;
if (err != NULL) {
fprintf(stderr, "Failed to get server RSD port: [%d] %s\n", err->code,
err->message);
idevice_error_free(err);
core_device_proxy_free(core_device);
return 1;
}
logger("Server RSD Port: %d", rsd_port);
/*****************************************************************
* Create TCP Tunnel Adapter
*****************************************************************/
logger("=== Creating TCP Tunnel Adapter ===");
printf("Server RSD Port: %d\n", rsd_port);
printf("\n=== Creating TCP Tunnel Adapter ===\n");
AdapterHandle *adapter = NULL;
err = core_device_proxy_create_tcp_adapter(core_device, &adapter);
if (err != IdeviceSuccess) {
logger("Failed to create TCP adapter: %d", err);
core_device_proxy_free(core_device);
return 1;
if (err != NULL) {
fprintf(stderr, "Failed to create TCP adapter: [%d] %s\n", err->code,
err->message);
idevice_error_free(err);
return 1;
}
// Connect to RSD port
err = adapter_connect(adapter, rsd_port);
if (err != IdeviceSuccess) {
logger("Failed to connect to RSD port: %d", err);
adapter_free(adapter);
return 1;
}
logger("Successfully connected to RSD port");
/*****************************************************************
* XPC Device Setup
*****************************************************************/
logger("=== Setting up XPC Device ===");
XPCDeviceAdapterHandle *xpc_device = NULL;
err = xpc_device_new(adapter, &xpc_device);
if (err != IdeviceSuccess) {
logger("Failed to create XPC device: %d", err);
adapter_free(adapter);
return 1;
}
// Get DebugProxy service
XPCServiceHandle *debug_service = NULL;
err = xpc_device_get_service(xpc_device, "com.apple.internal.dt.remote.debugproxy", &debug_service);
if (err != IdeviceSuccess) {
logger("Failed to get debug proxy service: %d", err);
xpc_device_free(xpc_device);
return 1;
}
// Get ProcessControl service
XPCServiceHandle *pc_service = NULL;
err = xpc_device_get_service(xpc_device, "com.apple.instruments.dtservicehub", &pc_service);
if (err != IdeviceSuccess) {
logger("Failed to get process control service: %d", err);
xpc_device_free(xpc_device);
return 1;
}
/*****************************************************************
* Process Control - Launch App
*****************************************************************/
logger("=== Launching App ===");
// Get the adapter back from the XPC device
AdapterStreamHandle *stream = NULL;
err = adapter_connect(adapter, rsd_port, (ReadWriteOpaque **)&stream);
if (err != NULL) {
fprintf(stderr, "Failed to connect to RSD port: [%d] %s\n", err->code,
err->message);
idevice_error_free(err);
adapter_free(adapter);
return 1;
}
printf("Successfully connected to RSD port\n");
AdapterHandle *pc_adapter = NULL;
err = xpc_device_adapter_into_inner(xpc_device, &pc_adapter);
if (err != IdeviceSuccess) {
logger("Failed to extract adapter: %d", err);
xpc_device_free(xpc_device);
return 1;
printf("\n=== Performing RSD Handshake ===\n");
RsdHandshakeHandle *handshake = NULL;
err = rsd_handshake_new((ReadWriteOpaque *)stream, &handshake);
if (err != NULL) {
fprintf(stderr, "Failed to perform RSD handshake: [%d] %s\n", err->code,
err->message);
idevice_error_free(err);
adapter_close(stream);
adapter_free(adapter);
return 1;
}
// Connect to process control port
err = adapter_connect(pc_adapter, pc_service->port);
if (err != IdeviceSuccess) {
logger("Failed to connect to process control port: %d", err);
adapter_free(pc_adapter);
xpc_service_free(pc_service);
xpc_service_free(debug_service);
return 1;
}
logger("Successfully connected to process control port");
// Create RemoteServerClient
RemoteServerAdapterHandle *remote_server = NULL;
err = remote_server_adapter_new(pc_adapter, &remote_server);
if (err != IdeviceSuccess) {
logger("Failed to create remote server: %d", err);
adapter_free(pc_adapter);
xpc_service_free(pc_service);
xpc_service_free(debug_service);
return 1;
RemoteServerHandle *remote_server = NULL;
err = remote_server_connect_rsd(adapter, handshake, &remote_server);
if (err != NULL) {
fprintf(stderr, "Failed to create remote server: [%d] %s", err->code,
err->message);
idevice_error_free(err);
adapter_free(adapter);
rsd_handshake_free(handshake);
return 1;
}
printf("\n=== Setting up Debug Proxy ===\n");
DebugProxyHandle *debug_proxy = NULL;
err = debug_proxy_connect_rsd(adapter, handshake, &debug_proxy);
if (err != NULL) {
fprintf(stderr, "Failed to create debug proxy client: [%d] %s\n", err->code,
err->message);
idevice_error_free(err);
rsd_handshake_free(handshake);
adapter_free(adapter);
return 1;
}
// Create ProcessControlClient
ProcessControlAdapterHandle *process_control = NULL;
err = process_control_new(remote_server, &process_control);
if (err != IdeviceSuccess) {
logger("Failed to create process control client: %d", err);
remote_server_free(remote_server);
xpc_service_free(pc_service);
xpc_service_free(debug_service);
return 1;
}
// Disable memory limit for PID
err = process_control_disable_memory_limit(process_control, pid);
if (err != IdeviceSuccess) {
logger("failed to disable memory limit: %d", err);
}
/*****************************************************************
* Debug Proxy - Attach to Process
*****************************************************************/
logger("=== Attaching Debugger ===");
// Get the adapter back from the remote server
AdapterHandle *debug_adapter = NULL;
err = remote_server_adapter_into_inner(remote_server, &debug_adapter);
if (err != IdeviceSuccess) {
logger("Failed to extract adapter: %d", err);
xpc_service_free(debug_service);
process_control_free(process_control);
remote_server_free(remote_server);
xpc_service_free(pc_service);
return 1;
}
// Connect to debug proxy port
err = adapter_connect(debug_adapter, debug_service->port);
if (err != IdeviceSuccess) {
logger("Failed to connect to debug proxy port: %d", err);
adapter_free(debug_adapter);
xpc_service_free(debug_service);
process_control_free(process_control);
remote_server_free(remote_server);
xpc_service_free(pc_service);
return 1;
}
logger("Successfully connected to debug proxy port");
// Create DebugProxyClient
DebugProxyAdapterHandle *debug_proxy = NULL;
err = debug_proxy_adapter_new(debug_adapter, &debug_proxy);
if (err != IdeviceSuccess) {
logger("Failed to create debug proxy client: %d", err);
adapter_free(debug_adapter);
xpc_service_free(debug_service);
process_control_free(process_control);
remote_server_free(remote_server);
xpc_service_free(pc_service);
return 1;
}
// Send vAttach command with PID in hex
char attach_command[64];
snprintf(attach_command, sizeof(attach_command), "vAttach;%" PRIx64, pid);
DebugserverCommandHandle *attach_cmd = debugserver_command_new(attach_command, NULL, 0);
if (attach_cmd == NULL) {
logger("Failed to create attach command");
debug_proxy_free(debug_proxy);
xpc_service_free(debug_service);
process_control_free(process_control);
remote_server_free(remote_server);
xpc_service_free(pc_service);
return 1;
}
char *attach_response = NULL;
err = debug_proxy_send_command(debug_proxy, attach_cmd, &attach_response);
debugserver_command_free(attach_cmd);
if (err != IdeviceSuccess) {
logger("Failed to attach to process: %d", err);
} else if (attach_response != NULL) {
logger("Attach response: %s", attach_response);
idevice_string_free(attach_response);
}
// Send detach command
DebugserverCommandHandle *detach_cmd = debugserver_command_new("D", NULL, 0);
if (detach_cmd == NULL) {
logger("Failed to create detach command");
} else {
char *detach_response = NULL;
err = debug_proxy_send_command(debug_proxy, detach_cmd, &detach_response);
err = debug_proxy_send_command(debug_proxy, detach_cmd, &detach_response);
err = debug_proxy_send_command(debug_proxy, detach_cmd, &detach_response);
debugserver_command_free(detach_cmd);
if (err != IdeviceSuccess) {
logger("Failed to detach from process: %d", err);
} else if (detach_response != NULL) {
logger("Detach response: %s", detach_response);
idevice_string_free(detach_response);
}
}
runDebugServerCommand(pid, debug_proxy, logger, callback);
/*****************************************************************
* Cleanup
*****************************************************************/
debug_proxy_free(debug_proxy);
xpc_service_free(debug_service);
xpc_service_free(pc_service);
rsd_handshake_free(handshake);
adapter_free(adapter);
logger("Debug session completed");
return 0;

View file

@ -11,7 +11,9 @@
#include "idevice.h"
typedef void (^LogFuncC)(const char* message, ...);
int debug_app(TcpProviderHandle* provider, const char *bundle_id, LogFuncC logger);
int debug_app_pid(TcpProviderHandle* provider, int pid, LogFuncC logger);
typedef void (^DebugAppCallback)(int pid, struct DebugProxyHandle* debug_proxy, dispatch_semaphore_t semaphore);
int debug_app(IdeviceProviderHandle* tcp_provider, const char *bundle_id, LogFuncC logger, DebugAppCallback callback);
IdeviceFfiError* debug_proxy_send_command2(struct DebugProxyHandle *handle, struct DebugserverCommandHandle *command, char **response);
int debug_app_pid(IdeviceProviderHandle* tcp_provider, int pid, LogFuncC logger, DebugAppCallback callback);
#endif /* JIT_H */

Binary file not shown.

Binary file not shown.