debugserver+lldb很好用,但启动起来太麻烦?咱们开发了一款iOS SpringBoard tweak小插件,简化debugserver启动过程。老铁,请双击!ios
咱们常常要经过debugserver对App进行调试,有书籍和论坛对相关的技术和实践进行了说明,但实际应用起来仍是有些麻烦。先要重签拷贝,再要启动终端ssh到iPhone启动debugserver,各类ls加grep找到想调试的应用,敲命令启动debugserver,而后Mac本地终端启动lldb。这样折腾下来,至少要开两个终端,有的时候甚至更多。GitHub上有个issh工具对上述操做有封装和优化,可是仍是须要敲命令找App,再运行debugserver。git
因此作个tweak提高一下生产力。只需双击应用图标,便可一键启动debugserver。github
代码见:Githubapp
https://github.com/TalkingDat...ssh
运行界面异步
咱们所用的开发环境是iOS 13.3,可是并无用到特殊版本的API,低版本手机应该也OK。函数
下面简单分享开发过程:工具
从界面找逻辑,逆向发现SpringBoard的图标是SBIconView。而且有一个叫属性 applicationBundleIdentifierForShortcuts 返回的是图标对应的App的Bundle ID。经过Bundle ID构造LSApplicationProxy对象,而且得到canonicalExecutablePath属性,也就是应用的可执行文件路径。学习
Class LSApplicationProxy_class = objc_getClass("LSApplicationProxy");NSObject* proxyObj = [LSApplicationProxy_class performSelector:@selector(applicationProxyForIdentifier:) withObject:bundle];NSString * canonicalExecutablePath = [proxyObj performSelector:@selector(canonicalExecutablePath)];复制代码
接续看SBIconView,图标上有两个手势对象:测试
因此,咱们来给图标交互加个双击扩展。
%hook SBIconView
- (void)didMoveToWindow{ %orig; UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(handleDoubleClick:)]; [doubleTap setNumberOfTapsRequired:2]; [self addGestureRecognizer:doubleTap]; NSArray * ges = self.gestureRecognizers; for(UITapGestureRecognizer * each in ges){ if([each isKindOfClass:[UITapGestureRecognizer class]]){ [each requireGestureRecognizerToFail: doubleTap]; } }}复制代码
这里额外说一句,[each requireGestureRecognizerToFail: doubleTap]添加了双击手势指挥,因为iOS内部维护了手势的状态机,咱们进行单击操做的时候,其实产生了两种Possible State。第一种是识别为单击,而后结束。第二种是识别为双击的第一下并等待第二下的发生,而后根据两次点击之时间间隔阈值来判断是否是合法的双击。
因此咱们手动增长了约束,至关于指定了识别的优先级,只有双击失败了,才继续执行单击回调。这种操做会带来一点几乎无感的瑕疵:单击后等待双击识别失败的延迟,延迟的值就是双击识别执行的阈值(大约零点几秒)。
debugserver是一个二进制文件,狗神的教程里有如何重签,issh把这些过程给简化了。先看一下debugserver的权限:
-rwxr-xr-x 1 root admin 9876848 Jan 19 11:28 /iOSRE/tools/debugserver*
再来看一下SpringBoard的权限:
-rwxr-xr-x 1 root wheel 71264 Dec 5 13:15 SpringBoard*
属主用户都是root,没毛病。找个函数调用一下:
代码以下:
task = [[NSTask alloc]init];[task setLaunchPath:bin_serverpath];[task setArguments:args];[task launch];复制代码
每次server在launch以前,要把以前的task结束掉。
- (void)interrupt; // Not always possible. Sends SIGINT.复制代码
- (void)terminate; // Not always possible. Sends SIGTERM.复制代码
NSTask头文件里居然告诉我 Not always possible。事实上,在调用的时候,还真的不怎么possible,实际测试第一次server正常启动,后续因为没成功关闭,因此第二次就无法启动了。
因此仍是换种方式关闭吧。简单粗暴的 kill 函数:
NSTask * task = [TaskManager sharedManager].runningTask;if(task){ kill(task.processIdentifier,SIGKILL); task = nil;}复制代码
直接用Alert,又有按钮又有输入框,不过UIAlertView已经被废弃掉了,须要用UIAlertController。因为弹出Controller须要父Controller,经过View找到当前的Controller,作正向的应该都写过这段代码吧:
@implementation UIView(find)-(UIViewController*)findViewController{ UIResponder* target= self; while (target) { target = target.nextResponder; if ([target isKindOfClass:[UIViewController class]]) { break; } } return (UIViewController*)target;}@end复制代码
输入框里的IP和debugserver的path,每一个人都不同,因此在第一次输入完成以后,把这些值用NSUserDefault持久化存储起来,下次直接读取填充。
以前在相关技术论坛读到讨论用Root身份运行App的帖子,学习完帖子里的技巧,加强对操做系统的理解以及实践以后,发现若是真的想RootApp运行,其实SpringBoard自己就是一个RootApp,咱们把SpringBoard当作RootViewController,很容易把一些系统工具作出界面,从而提高生产力。好比砸壳、重签、拷贝App等。
**
做者:TalkingData小张同窗
本文版权归TalkingData全部,如需转载请注明来源**