本文最后更新于:星期一, 五月 20日 2019, 12:22 凌晨

最近在学习JSC的相关知识,这里写篇博客简单记录一下。

JavaScriptCore简介

JavaScriptCore是一个JS 引擎,是专门负责处理javascript代码的虚拟机,简写为JSC,一般会附在浏览器中。

它是一个C++实现的开源项目,使用Apple提供的JavaScriptCore框架。我们可以在Objective-C或者基于C的程序中执行Javascript代码,也可以向JavaScript环境中插入一些自定义的对象。

JavaScript 引擎在解析源码后将其转换为抽象语法树(AST),基于 AST,解释器便可以开始工作并产生字节码,此时引擎正在执行 JavaScript 代码。

img

为了使它执行得更快,可以将字节码与分析数据(profiling data)一起发给优化编译器JIT。优化编译器根据已有的分析数据做出特定假设,然后生成高度优化的机器码。如果在某点上一个假设被证明是不正确的,那么优化编译器会去优化并回退至解释器部分

JavaScriptCore的组成

JavaScriptCore主要由以下模块组成:

参考wiki JavaScriptCore

  • Lexer 词法分析器,将脚本源码分解成一系列的Token
  • Parser 语法分析器,处理Token并生成相应的语法树
  • LLInt 低级解释器,执行Parser生成的二进制代码
  • Baseline JIT 基线JIT(just in time 实施编译)
  • DFG 低延迟优化的JIT
  • FTL 高通量优化的JIT

JavaScriptCore基本组件

1557810204143

基本组件的作用

  • Parser

    这是所有js代码执行的入口点,它会解析源代码,并生成自定义的bytecode

  • Interpreter

    解析执行Parser生成的二进制代码

  • JIT Compiler (即时编译器)

    JSC有三种不同的优化编译器

    • Baseline JIT 基线JIT

      接收低级解释器 LLInt解释后的代码,进行优化

    • DFG 低延迟优化的JIT

      接收Baseline JIT 优化后的代码,进行处理

    • FTL 高通量优化的JIT

      接收DFG 低延迟优化的JIT处理后的代码,进行最终优化

  • Runtime

    RunTime是内存空间,JS引擎利用它来管理上下文、对象和与JS函数及脚本相关的变量,提供内置函数和对象。

  • Garbage Collector

    回收不需要的内存空间

JavaScriptCore值的表现形式

JSC使用一项叫NaN-boxing的技术。NaN-boxing利用多位模式呈现,因此这些位中的其他值可以被编码。具体来说,每个IEEE 754(IEEE二进制浮点数算术标准)规定了浮点数所有指数位用不等于零的分数表示Nan-boxing。对于双精度值,这就由2 ^ 51个不同的位模式(忽略符号位并将第一个小数位设置为1,所以这也可以表示nullptr)。在64位平台上,目前只有48位用于寻址,其余32位整数和指针也就可以进行编码。

JSC具体规则实现在JSValue.h文件中实现

1557819327079

前十六位表示已编码的JSValue的类型,这个方案通过对数值执行64位整数加法来对双精度进行编码。在这个操作后没有编码的双精度值将以模式0x0000或0xffff开始。在执行浮点数运算前,必须通过反转该操作作为解码。标志0x0000用来表示指针或者其他标志的立即数。布尔值、空值和未定义的值由特定的、无效的指针表示。

JavaScript对象与数组

JavaScript对象模型

在js中,除了五种原始类型(即数字,字符串,布尔值,null,undefined)之外的都是对象。

ECMAScript 规范基本上将所有对象定义为由字符串键值映射到 property 属性的字典,可以使用点运算符(foo.bar)或通过方括号(foo[‘bar’])来访问属性。

1557819807736

每个property属性的字典都有四个属性,见上图。除了[[Value]], 还规定了以下属性

  • [[Writable]] 决定该属性是否可以被重新赋值;
  • [[Enumerable]] 决定该属性是否出现在 for-in 循环中;
  • [[Configurable]] 决定该属性是否可被删除。

javascript如何获取一个对象的属性

1557820522010

数组

数组被定义为特殊的对象,它是具有特殊的“length”的对象,length的值总是等于最高元素的索引加一。索引被限制在0-2^32 - 2的范围间,数组最多只能有2^32-1项。

1557820973774

对象在内存中的存储

通用的

只存在一个对象时,它会使用上面所讲的对象的存储的字典数据结构。它包含用字符串表示的键值,指向各自的属性值。

1557823212207

而具有相同键值属性的不同对象在内存中的存储和上面所讲的不同。它多了一个叫shape的东西,shape包含了除[[Value]]外的所有属性名,并且包含了JSObject内部元素的偏移量,方便JSC去查找对象属性的值。每个具有相同键值属性的对象都会指向这个shape实例。

1557822194099

不同的JavaScript引擎都使用了shape做优化,但称呼不一样

  • 学术论文称它们为 Hidden Classes(容易与 JavaScript 中的类概念混淆)
  • V8 将它们称为 Maps(容易与 JavaScript 中的 Map 概念混淆)
  • Chakra 将它们称为 Types(容易与 JavaScript 中的动态类型和关键字 typeof 混淆)
  • JavaScriptCore 称它们为 Structures
  • SpiderMonkey 称他们为 Shapes
JSC的实现

在JSC内部,JSC将对象的属性和对象的元素存储在同一个内存区域,并且在对象本身中存储指向该区域的指针。指针指向这个内存区域的中间,以这个指针分隔,左边是它的属性(低地址),右边是它的元素 还有一个小标题位于指向包含元素向量长度的地址之前,形如:(也称Butterfly)

--------------------------------------------------------
.. | propY | propX | length | elem0 | elem1 | elem2 | ..
--------------------------------------------------------
                            ^
                            |
            +---------------+
            |
  +-------------+
  | Some Object |
  +-------------+

但是在JSC中,对象的存储不仅仅是Butterfly,还有内联存储。

JSObject的内联:(默认6个索引值)

一个小demo

obj = {'a': 0x1337, 'b': false, 'c': 13.37, 'd': [1,2,3,4]};
obj1 = {'a':0x1111,'b':true,'c':1.123,'d':[1,2],'e':1,'f':2,'g':3};
obj2 = {'a':0x1111,'b':true,'c':1.123,'d':[1,2],'e':1,'f':2,'g':3 , '1':'123'};
print(describe(obj));
print(describe(obj1));
print(describe(obj2));

输出结果:

1557926168168

可以发现对象的属性6个就会使用butterfly来存储多出的属性

调试,观察内存情况

obj对象的内存情况:

1557926605893

其中第一个4字节是JSCell;第二个是Butterfly指针,它是空的,因为所有的属性都存储在内联中。接下来是四个属性的内联JSValue槽:一个integer,false,一个double和一个JSObject指针。JSCell后四位是这个对象的StructureID 。

obj2对象的内存情况:

1557927046793

因为它属性多于6个,所以它分配了一个butterfly来存储多出的属性。

不同C++类构建JSC对象的关系图

1557927954080

JavaScript函数

  • arguments

    arguments对象是所有(非箭头)函数中都可用的局部变量。可以使用arguments对象在函数中引用函数的参数。此对象包含传递给函数的每个参数,第一个参数在索引0处。

    假设一个函数传递了三个参数

      arguments[0]
      arguments[1]
      arguments[2]
      //可以通过arguments来引用函数的参数
    
  • .call 和 .apply函数

    通过给定的”this”对象和参数来调用它们

    call()接收一个参数列表,apply()接收一个参数数组

后续补充

Reference


JS-Engine      js_exploit

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!