mirror of
https://github.com/StikDebug/StikDebug.git
synced 2026-10-15 20:10:30 +00:00
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:
parent
144f748d81
commit
354333d873
23 changed files with 2223 additions and 1158 deletions
33
StikJIT/JSSupport/IDeviceJSBridgeDebugProxy.m
Normal file
33
StikJIT/JSSupport/IDeviceJSBridgeDebugProxy.m
Normal 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;
|
||||
}
|
||||
11
StikJIT/JSSupport/JSSupport.h
Normal file
11
StikJIT/JSSupport/JSSupport.h
Normal 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);
|
||||
81
StikJIT/JSSupport/RunJSView.swift
Normal file
81
StikJIT/JSSupport/RunJSView.swift
Normal 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)")
|
||||
}
|
||||
|
||||
}
|
||||
50
StikJIT/JSSupport/ScriptEditorView.swift
Normal file
50
StikJIT/JSSupport/ScriptEditorView.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
139
StikJIT/JSSupport/ScriptListView.swift
Normal file
139
StikJIT/JSSupport/ScriptListView.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
18
StikJIT/JSSupport/attachDetach.js
Normal file
18
StikJIT/JSSupport/attachDetach.js
Normal 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()
|
||||
|
|
@ -5,3 +5,4 @@
|
|||
#include "idevice/JITEnableContext.h"
|
||||
#include "idevice/idevice.h"
|
||||
#include "idevice/heartbeat.h"
|
||||
#include "JSSupport/JSSupport.h"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
Loading…
Add table
Add a link
Reference in a new issue