您的位置:首页 > Web前端 > JavaScript

实现一个 JavaScriptCore 的 debugger —— iOS 篇

2018-01-23 14:32 2651 查看

JSC 的 debugger 是个神奇的东西,在网上资料甚少,几乎完全搜索不到,尤其是 iOS 上,OC 接口没有暴露任何 debugger 信息。

不过好在 JSC 是开源的,通过分析源代码可以找到 JSC::Debugger 这个抽象类,我们继承这个抽象类,然后实现掉虚函数,创建实例并且把它挂载到 global object 即可开启 debug 能力了。

思路是简单的,在 iOS 设备上,我们要面临的另一个问题是它的 JavaScriptCore 本身是以 Framework 的形式而非源代码形式提供的,所以我们只有公开的 OC 头文件和静态库文件。

所以要想使用 debugger,我们需要:

  1. 编译时使用私有的头文件

  • 确保头文件的版本跟 framework 一致

  • 确保编译选项跟 framework 一致

  • 链接时链接 framwork 中的方法

  • 如何解决呢?步骤如下:

    1. 查看 framework 中的 JSC 版本

    2. 根据 JSC 版本,找到对应的源代码

    3. 构建 JSC 获取私有头文件

    4. 建立新项目,引入私有 JSC 头文件

    5. 调整宏和编译选项

    6. 编写代码

    查看 framework 中的 JSC 版本

    framework 是一个文件夹(在 XCode 中右键即可打开),可以从tbd 文件中找到系统中库的路径(一般是 /System/Library/Frameworks/JavaScriptCore.framework/ ),然后从 version.plist 中找到当前版本。

    在我的 XCode9.2中,找到的版本是 604.4.7.1.3。

    下载源代码

    在 https://svn.webkit.org/repository/webkit/tags 可以找到对应的源代码。

    注意一般 opensource.apple.com 中找不到对应版本。

    我们并不需要整个 webkit 代码,所以只要下载 source 目录下的 bmalloc, WTF 和 JavaScriptCore 三个项目就够了。

    构建 JSC

    首先我们需要建立一个 workspace,然后把三个项目文件拖进 workspace。

    依次构建 bmalloc、WTF 和 JavaScriptCore 三个项目即可。

    如果配置正确,构建 JSC 应该不会遇到什么困难。

    我们只需要构建好的头文件,所以不需要选择 iOS 设备,使用默认的 mac 作为目标就好了。

    建立新项目

    接下来我们要建立一个 Debugger 项目,随便叫什么名字,选择 iOS 项目。

    我们需要调整编译选项:

    • other C++ flags: -std=c++14

    • enable C++ runtime Types: No

    • system header search path: $(PRODUCT_NAME)/

    • Processor Macros:(太多了,建议直接到项目文件源代码里修改)

      ENABLE3DTRANSFORMS, ENABLEACCELERATEDOVERFLOWSCROLLING, ENABLEAPPLEPAY, ENABLEAPPLEPAYSESSIONV3, ENABLEATTACHMENTELEMENT, ENABLEAVFCAPTIONS, ENABLECACHEPARTITIONING, ENABLECANVASPATH, ENABLECANVASPROXY, ENABLECHANNELMESSAGING, ENABLECONTENTFILTERING, ENABLECSSANIMATIONSLEVEL2, ENABLECSSBOXDECORATIONBREAK, ENABLECSSCOMPOSITING, ENABLECSSDEVICEADAPTATION, ENABLECSSIMAGEORIENTATION, ENABLECSSIMAGERESOLUTION, ENABLECSSREGIONS, ENABLECSSSCROLLSNAP, ENABLECSSSELECTORSLEVEL4, ENABLECSSTRAILINGWORD, ENABLECSS3TEXT, ENABLECURSORVISIBILITY, ENABLECUSTOMSCHEMEHANDLER, ENABLEDASHBOARDSUPPORT, ENABLEDATAINTERACTION, ENABLEDATATRANSFERITEMS, ENABLEDATACUEVALUE, ENABLEDATALISTELEMENT, ENABLEDEVICEORIENTATION, ENABLEDRAGSUPPORT, ENABLEENCRYPTEDMEDIA, ENABLEFETCHAPI, ENABLEFILTERSLEVEL2, ENABLEFTLJIT, ENABLEFULLSCREENAPI, ENABLEGAMEPADDEPRECATED, ENABLEGAMEPAD, ENABLEGEOLOCATION, ENABLEICONDATABASE, ENABLEINDEXEDDATABASEINWORKERS, ENABLEINDEXEDDATABASE, ENABLEINPUTTYPECOLORPOPOVER, ENABLEINPUTTYPECOLOR, ENABLEINPUTTYPEDATE, ENABLEINPUTTYPEDATETIMEINCOMPLETE, ENABLEINPUTTYPEDATETIMELOCAL, ENABLEINPUTTYPEMONTH, ENABLEINPUTTYPETIME, ENABLEINPUTTYPEWEEK, ENABLEINTERSECTIONOBSERVER, ENABLEINTL, ENABLEIOSGESTUREEVENTS, ENABLEIOSTOUCHEVENTS, ENABLEJIT, ENABLEKEYBOARDKEYATTRIBUTE, ENABLEKEYBOARDCODEATTRIBUTE, ENABLELEGACYCSSVENDORPREFIXES, ENABLELEGACYENCRYPTEDMEDIA, ENABLELEGACYVENDORPREFIXES, ENABLELETTERPRESS, ENABLELINKPREFETCH, ENABLEMACGESTUREEVENTS, ENABLEMATHML, ENABLEMEDIACAPTURE, ENABLEMEDIACONTROLSSCRIPT, ENABLEMEDIASESSION, ENABLEMEDIASOURCE, ENABLEMEDIASTATISTICS, ENABLEMEDIASTREAM, ENABLEMETERELEMENT, ENABLEMHTML, ENABLEMOUSECURSORSCALE, ENABLENAVIGATORCONTENTUTILS, ENABLENAVIGATORSTANDALONE, ENABLENOTIFICATIONS, ENABLEPDFKITPLUGIN, ENABLEPOINTERLOCK, ENABLEPROXIMITYEVENTS, ENABLEPUBLICSUFFIXLIST, ENABLEQUOTA, ENABLEREMOTEINSPECTOR, ENABLEREQUESTAUTOCOMPLETE, ENABLERESOLUTIONMEDIAQUERY, ENABLERESOURCEUSAGE, ENABLERUBBERBANDING, ENABLESERVICECONTROLS, ENABLESPEECHSYNTHESIS, ENABLESTREAMSAPI, ENABLESUBTLECRYPTO, ENABLESVGFONTS, ENABLETELEPHONENUMBERDETECTION, ENABLETEXTAUTOSIZING, ENABLETOUCHEVENTS, ENABLETOUCHICONLOADING, ENABLEUSERSELECTALL, ENABLEVARIATIONFONTS, ENABLEVIDEOPRESENTATIONMODE, ENABLEMACVIDEOTOOLBOX, ENABLEVIDEOTRACK, ENABLEVIDEO, ENABLEVIEWMODECSSMEDIA, ENABLEWEBANIMATIONS, ENABLEWEBAUDIO, ENABLEWEBRTC, ENABLEWEBSOCKETS, ENABLEWEBTIMING, ENABLEWEBGL, ENABLEWEBGL2, ENABLEWEBGPU, ENABLEWIRELESSPLAYBACKTARGET, ENABLEXSLTFASTJITPERMISSIONS

    然后我们打开构建好的 JSC 项目目标, 复制其中 PrivateHeaders 目录,到项目目录的 JavaScriptCore 目录。

    再打开 WTF 项目目标, 复制目录下 /usr/local/include/wtf

    接下来,我们需要对源代码做一下小修改,因为系统的 JSC 是在非 debug 模式下编译的,所以我们强行把头文件中跟 debug 相关的代码改成非 debug 模式:

    JavaScriptCore/HandleStack.h

    1. 所有 #ifdef NDEBUG

    WTF/hashtable.h

    1. #ifdef NDEBUG

    2. #define CHECK_HASHTABLE_ITERATORS 0

    3. #define CHECK_HASHTABLE_USE_AFTER_DESTRUCTION 0

    4. #else

    5. #define CHECK_HASHTABLE_ITERATORS 0

    6. #define CHECK_HASHTABLE_USE_AFTER_DESTRUCTION 0

    7. #endif

    编写代码

    代码必须使用 .mm 文件。

    我们需要在项目的 build phases 中加入 JavaScriptCore.framework。

    1. #import <JavaScriptCore/JavaScriptCore.h>

    2. #import "JavaScriptCore/HeapInlines.h"

    3. #import "JavaScriptCore/HeapCellInlines.h"

    4. #import "JavaScriptCore/APICast.h"

    5. #import "JavaScriptCore/Debugger.h"

    6. #import "JavaScriptCore/SourceProvider.h"

    7. #import "JavaScriptCore/JSRunLoopTimer.h"

    8. #import "JavaScriptCore/JSVirtualMachineInternal.h"

    9. class MyDebugger: public JSC::Debugger {

    10. public:

    11.    MyDebugger(JSC::VM& vm) : JSC::Debugger::Debugger(vm){

    12.    }

    13.    virtual ~MyDebugger(){

    14.        JSC::Debugger::~Debugger();

    15.    }

    16.    /*virtual void recompileAllJSFunctions() {

    17.        //JSC::Debugger::recompileAllJSFunctions();

    18.    }*/

    19.    virtual void sourceParsed(JSC::ExecState* state, JSC::SourceProvider* sourceProvider, int errorLineNumber, const WTF::String& errorMessage) {

    20.        //StringView sourceString = sourceProvider->source();

    21.        //NSLog(@"sourceParsed:%@", (NSString*)sourceString.toString());

    22.        NSLog(@"sourceParsed");

    23.        return;

    24.    };

    25.    virtual void handleBreakpointHit(JSC::JSGlobalObject*, const JSC::Breakpoint&) {

    26.        NSLog(@"handleBreakpointHit");

    27.    }

    28.    virtual void handleExceptionInBreakpointCondition(JSC::ExecState*, JSC::Exception*) const {

    29.        NSLog(@"handleExceptionInBreakpointCondition");

    30.    }

    31.    virtual void handlePause(JSC::JSGlobalObject* globalObject, ReasonForPause reason) {

    32.        NSLog(@"handlePause");

    33.    }

    34.    virtual void notifyDoneProcessingDebuggerEvents() {

    35.        NSLog(@"notifyDoneProcessingDebuggerEvents");

    36.    }

    37. };

    使用示例:

    1.    JSContext *jsContext = [[JSContext alloc] init];

    2.    JSGlobalContextRef globalContext = [jsContext JSGlobalContextRef];

    1.    JSC::ExecState* es = toJS(globalContext);

    2.    JSC::JSGlobalObject* globalObject = es->vmEntryGlobalObject();

    3.    MyDebugger* debugger = new MyDebugger(globalObject->vm());

    4.    globalObject->setDebugger(static_cast<JSC::Debugger*>(debugger));

    5.    debugger->setPauseOnNextStatement(TRUE);

    6.    globalObject->vm().heap.acquireAccess();

    7.    debugger->activateBreakpoints();

    8.    globalObject->vm().heap.releaseAccess();

    1.    [jsContext evaluateScript:@"debugger;"];

    题图:https://unsplash.com/photos/Sf5Q7Ljjf58 By @Katerina Pavlickova


    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: