利用 Cycript 调试 iOS 应用(无需越狱)

Cycript 是由大神 @saurik 开发的一套工具,它允许开发者在运行时探查和修改一个 iOS 或 Mac OS X 应用;它使用一种混合了 Objective-C++ 和 JavaScript 语法的脚本语言,你可以通过自带的一个 REPL (具备语法高亮和自动补全)进行操作。Cycript 的使用有两种方式,一种是在越狱设备上通过 MobileSubstrate 加载,好处是可以注入到 iOS 上的所有应用,具体的使用方法可以参考官方文档;还有一种是接下来要介绍的,通过其提供的静态库的方式将 Cycript 集成到自己的应用,从而避免越狱的需要(当然也只能在自己的应用内使用了)。

集成过程

Cycript 集成的过程在官方的文档中没有详细说明,只在 @saurik 的一个演讲视频中有过演示,而且在实际的集成构成中会出现一些链接问题,所以下面会将所需步骤一一说明:

下载

首先,你需要到官方网站下载 Cycript,这里使用的版本是 0.9.501,解压下载回来的 zip 包后里面会有以下文件:

Cycript_0.9.501 ❯ ls

Cycript.framework Cycript.lib       cycript

其中 Cycript.framework 是我们需要链接的静态库,而 cycript 是我们用来使用 Cycript 的 REPL 程序。

创建新 target

由于我们只想将 Cycript 用于调试,所以最好创建一个新的 target ,你可以通过只接从现有的 target 复制一个。注意,接下来的操作请确保在选中该 target 的情况下进行。

image

添加框架及其依赖

Cycript.framework 拖放到工程里面的 frameworks 目录下,注意要选择添加到新创建的 target 中去。然后在侧栏中选中要集成的 target,点击 Build Phrase ,在 Link Binary With Libraries 下添加依赖的框架。

image

Cycript 依赖于两个库:JavaScriptCorelibstdc++;但是我们目前所使用的 Cycript 版本对于链接的 libstdc++ 版本有特定的要求,一定要选择 libstdc++.6.0.9.dylib,否则会造成在设备或者模拟器上链接错误(iOS 7)。

image

编译选项

为了解决 libstdc++ 的兼容问题,我们还需要设置一些编译选项。首先,选中当前 target 的 Build Settings 标签,搜索 "Other Linker Flags",添加 -lstdc++

image

然后,同样在 Build Settings 里面,搜索 "C++ Standard Library",确保选项为 "Compiler Default"

image

这里如果没有选择特定的 libstdc++ 版本,并且配置正确的编译器选项会导致 iOS 7 下链接失败,在 IRC 上给 @saurik 反馈这个问题后得到这样的回答:

(in a subsequent version of the framework I actually intend to inline/static libstdc++ and leave just a requirement on libc++abi, which should fix that)

调用 Cycript

接下来,我们需要在代码中开启 Cycript 。由于需要确保这段代码只在特定 target 中执行,所以需要在该 target 中添加一个预编译宏,具体做法是在 Build Settings 中搜索 "Preprocessor Macros",然后添加 CYCRIPT_ENABLED=1

image

最后,在 AppDelegate 里面引入 Cycript 的头文件 #import <Cycript/Cycript.h>,并在 -application:didFinishLaunchingWithOptions: 方法开头添加如下代码:

#ifdef CYCRIPT_ENABLED
    CYListenServer(8888);
#endif

其中8888是 Cycript 的监听端口。

连接 Cycript

首先,在Xcode 中选中集成了 Cycript 的 target 对应的 scheme,编译运行你的应用。然后在命令行下面进入解压出来的 Cycript 目录里面,执行下面命令:

./cycript -r 127.0.0.1:8888

其中 127.0.0.1 是你设备或模拟器的地址,8888 是你在代码中配置的监听端口。如果一切正常,会出现一个 Cycript 的 REPL,你可以通过它在你的应用里面运行各种 Cycript 语句,比如修改 window 的背景颜色:

cy# UIApp
#"<UIApplication: 0x109522120>"

cy# UIApp.delegate.window.backgroundColor = UIColor.redColor
#"UIDeviceRGBColorSpace 1 0 0 1"

image

注意事项

目前的版本在模拟器上只支持 64-bit,如果你使用 32-bit 模拟其可能会出现如下的链接错误:

sectionForAddress(0x59B94) address not in any section file 'path/Cycript.framework/Cycript' for architecture i386

如果切换到 64-bit 模拟器后依然出现该错误,请确认错误信息中的架构显示为 x86_64,如果依然为 i386 那么这是 Xcode 5.0.2 的 bug 导致的,请清除缓存然后重启 Xcode 再试,或者直接升级更新版本 (目前为 Xcode 5.1 Beta)。

总结

通过集成 Cycript,我们得到了一个非常棒的调试工具,让你可以随时通过它的 REPL 进行调试。比如,在开发的时候可以实时调整各种参数,避免重复编译;在测试人员发现难以重现的 bug 时可以马上查找原因,等等。更多的使用方法请参考官方的文档和这个视频

Update

如果你只需要在模拟器上面使用,可以利用 LLDB 进行动态加载,从而避免修改工程,具体做法如下:

首先编译运行你的应用,然后在 Xcode 菜单中选择 Debug -> Pause,接着在 LLDB 中依次执行以下命令:

process load /path/to/your/dylib/folder/libcycript-sim.dylib

p (void)CYListenServer(8888)

continue

你也可以在 ~/.lldbinit 文件中为这两个命令添加快捷方式:

command alias load_cycript process load /path/to/your/dylib/folder/libcycript-sim.dylib

command alias run_cycript p (void)CYListenServer(8888)