《Chrome V8 源码》45. JavaScript API 源码分析(1)

浏览器安全 3年前 (2022) admin
762 0 0
《Chrome V8 源码》45. JavaScript API 源码分析(1)


《Chrome V8 源码》45. JavaScript API 源码分析(1)
 介绍
《Chrome V8 源码》45. JavaScript API 源码分析(1)


substring、getDate、catch 等是常用的 JavaScript API。接下来的几篇文章将从整体上对 JavaScript API 的设计思想、源码和关键函数进行讲解,并能通过例子来分析 JavaScript 在 V8 中的初始化、运行方式,以及它与解释器、编译器、字节码之间的关系。

 

《Chrome V8 源码》45. JavaScript API 源码分析(1)
JavaScript API 的初始化
《Chrome V8 源码》45. JavaScript API 源码分析(1)


在 V8 中,JavaScript API(以下简称:API)的初始化由 IniitializeGlobal() 方法负责,该方法在创建 snapshot 时被调用以完成所有 API的初始化,通过调试 mksnapshot 解决方案(VS 2019)可以看到该函数的运行过程,源码如下:

1.  void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,2.                              Handle<JSFunction> empty_function) {3.  Handle<JSFunction> array_prototype_to_string_fun;4.    // 省略...............5.   SimpleInstallFunction(isolate_, array_function, "isArray",6.                         Builtin::kArrayIsArray, 1, true);7.   SimpleInstallFunction(isolate_, array_function, "from", Builtin::kArrayFrom,8.                         1, false);9.   SimpleInstallFunction(isolate_, array_function, "of", Builtin::kArrayOf, 0,10.                          false);11.    JSObject::AddProperty(isolate_, proto, factory->constructor_string(),12.                          array_function, DONT_ENUM);13.    SimpleInstallFunction(isolate_, proto, "concat",14.                          Builtin::kArrayPrototypeConcat, 1, false);15.    SimpleInstallFunction(isolate_, proto, "copyWithin",16.                          Builtin::kArrayPrototypeCopyWithin, 2, false);17.    SimpleInstallFunction(isolate_, proto, "reverse",18.                          Builtin::kArrayPrototypeReverse, 0, false);19.    SimpleInstallFunction(isolate_, proto, "shift",20.                          Builtin::kArrayPrototypeShift, 0, false);21.    SimpleInstallFunction(isolate_, proto, "unshift",22.                          Builtin::kArrayPrototypeUnshift, 1, false);23.    SimpleInstallFunction(isolate_, proto, "slice",24.                          Builtin::kArrayPrototypeSlice, 2, false);25.    // 省略...............26.  }}

通过上述代码可以看到 SimpleInstallFunction() 每执行一次安装一个 API 到 isolate_ 中。以第 13 行为例,参数 “concat” 是字符串,参数 Builtin::kArrayPrototypeConcat 是枚举值,SimpleInstallFunction() 为二者建立了对应关系,当我们在 JavaScript 源码中使用 array.concat 方法时,就是使用对应的 Builtin 方法。下面讲解 SimpleInstallFunction 如何为”concat” 和 Builtin::kArrayPrototypeConcat 建立对应关系,源码如下:

1.  V8_NOINLINE Handle<JSFunction> SimpleInstallFunction(2.      Isolate* isolate, Handle<JSObject> base, const char* name, Builtin call,3.      int len, bool adapt, PropertyAttributes attrs = DONT_ENUM) {4.    Handle<String> internalized_name =5.        isolate->factory()->InternalizeUtf8String(name);6.    Handle<JSFunction> fun =7.        SimpleCreateFunction(isolate, internalized_name, call, len, adapt);8.    JSObject::AddProperty(isolate, base, internalized_name, fun, attrs);9.    return fun;10.  }

上述代码中,第 4 行创建 V8 内部字符串 internalized_name,它的值是 “concat”;
第 6 行创建 JSFcunction 方法 fun,把 internalized_name 填充到 fun 内部的 SharedFunction 中,该 JSFunction 的功能是 Builtin::kArrayPrototypeConcat;
第 8 行把 fun 填充进 JSOBject 的属性中。
下面讲解 InternalizeUtf8String() 方法,源码如下:

1.  Handle<String> Factory::InternalizeUtf8String(2.      const base::Vector<const char>& string) {3.    base::Vector<const uint8_t> utf8_data =4.        base::Vector<const uint8_t>::cast(string);5.    Utf8Decoder decoder(utf8_data);6.    if (decoder.is_ascii()) return InternalizeString(utf8_data);7.    if (decoder.is_one_byte()) {8.      std::unique_ptr<uint8_t[]> buffer(new uint8_t[decoder.utf16_length()]);9.      decoder.Decode(buffer.get(), utf8_data);10.      return InternalizeString(11.          base::Vector<const uint8_t>(buffer.get(), decoder.utf16_length()));12.    }13.    std::unique_ptr<uint16_t[]> buffer(new uint16_t[decoder.utf16_length()]);14.    decoder.Decode(buffer.get(), utf8_data);15.    return InternalizeString(16.        base::Vector<const base::uc16>(buffer.get(), decoder.utf16_length()));17.  }

上述代码创建 V8 内部字符串,该字符串的类型是InternalzieString,它与 ConsString、OneByteString 等类型的区别是:ConsString 等是 JavaScript 字符串在 V8 中的不同实现,InternalzieString 被用于表达 V8 的基础组件,正如我们现在所说的 “concat”,它是内部字符串,它用于表达一个 SharedFuncion。
上述代码判断字符串 “concat” 的类型是 ASCII、one_byte 或是 two_byte,创建相应的内部符串,并使用 StringTable 缓存以备后面复用。
下面讲解 SimpleCreateFunction() 方法,源码如下:

1.  V8_NOINLINE Handle<JSFunction> SimpleCreateFunction(Isolate* isolate,2.                                                      Handle<String> name,3.                                                      Builtin call, int len,4.                                                      bool adapt) {5.    name = String::Flatten(isolate, name, AllocationType::kOld);6.    Handle<JSFunction> fun =7.        CreateFunctionForBuiltinWithoutPrototype(isolate, name, call);8.    JSObject::MakePrototypesFast(fun, kStartAtReceiver, isolate);9.    fun->shared().set_native(true);10.    if (adapt) {11.      fun->shared().set_internal_formal_parameter_count(JSParameterCount(len));12.    } else {13.      fun->shared().DontAdaptArguments();14.    }15.    fun->shared().set_length(len);16.    return fun;17.  }

上述代码中,参数 name、len、adapt 在 InitializeGlobal() 中规定好了。

第 5 行使用 Flatten 创建简单字符串,本文中的 “concat” 已经是简单字符串;
第 6 行创建 JSFunction,此时的 JSFuncion 还没有被安装到 JSObject 上;
第 10~15 行创建 Builtin call 的参数,这些参数设置在 SharedFunction 中。
CreateFunctionForBuiltinWithoutPrototype() 方法使用 NewSharedFunctionInfo() 创建 SharedFunction,NewSharedFunctionInfo()源码如下:

1.  Handle<SharedFunctionInfo> FactoryBase<Impl>::NewSharedFunctionInfo(2.      MaybeHandle<String> maybe_name, MaybeHandle<HeapObject> maybe_function_data,3.      Builtin builtin, FunctionKind kind) {4.    Handle<SharedFunctionInfo> shared = NewSharedFunctionInfo();5.    DisallowGarbageCollection no_gc;6.    SharedFunctionInfo raw = *shared;7.    Handle<String> shared_name;8.    bool has_shared_name = maybe_name.ToHandle(&shared_name);9.    if (has_shared_name) {10.      DCHECK(shared_name->IsFlat());11.      raw.set_name_or_scope_info(*shared_name, kReleaseStore);12.    } else {13.      DCHECK_EQ(raw.name_or_scope_info(kAcquireLoad),14.                SharedFunctionInfo::kNoSharedNameSentinel);15.    }16.    Handle<HeapObject> function_data;17.    if (maybe_function_data.ToHandle(&function_data)) {18.      DCHECK(!Builtins::IsBuiltinId(builtin));19.      DCHECK_IMPLIES(function_data->IsCode(),20.                     !Code::cast(*function_data).is_builtin());21.      raw.set_function_data(*function_data, kReleaseStore);22.    } else if (Builtins::IsBuiltinId(builtin)) {23.      raw.set_builtin_id(builtin);24.    } else {25.      DCHECK(raw.HasBuiltinId());26.      DCHECK_EQ(Builtin::kIllegal, raw.builtin_id());27.    }28.    raw.CalculateConstructAsBuiltin();29.    raw.set_kind(kind);30.    return shared;31.  }

上述代码中,第 2 行参数 maybe_name 是 ‘concat’,参数 builtin 是 Builtin::kArrayPrototypeConcat;

第 4 行创建 SharedFunctionInfo shared;
第 7-15 行把 ‘concat’ 填充进 shared;
第 17-29 行验证 Builtin::kArrayPrototypeConcat 的值是否正确,并将其设置到 shared 中。
下面讲解 MakePrototypesFast() 方法,源码如下:

1.  void JSObject::MakePrototypesFast(Handle<Object> receiver,2.                                    WhereToStart where_to_start,3.                                    Isolate* isolate) {4.    if (!receiver->IsJSReceiver()) return;5.    for (PrototypeIterator iter(isolate, Handle<JSReceiver>::cast(receiver),6.                                where_to_start);7.         !iter.IsAtEnd(); iter.Advance()) {8.      Handle<Object> current = PrototypeIterator::GetCurrent(iter);9.      if (!current->IsJSObject()) return;10.      Handle<JSObject> current_obj = Handle<JSObject>::cast(current);11.      Map current_map = current_obj->map();12.      if (current_map.is_prototype_map()) {13.        // If the map is already marked as should be fast, we're done. Its14.        // prototypes will have been marked already as well.15.        if (current_map.should_be_fast_prototype_map()) return;16.        Handle<Map> map(current_map, isolate);17.        Map::SetShouldBeFastPrototypeMap(map, true, isolate);18.        JSObject::OptimizeAsPrototype(current_obj);19.      }20.    }21.  }

上述代码通过循环迭代的方式查找相应的 prototype 并设置好 map。图 1 给出了此时的调用堆栈。

《Chrome V8 源码》45. JavaScript API 源码分析(1)
《Chrome V8 源码》45. JavaScript API 源码分析(1)
JavaScript API 的使用方法
《Chrome V8 源码》45. JavaScript API 源码分析(1)


上面从 V8 源码的角度讲解了从 Builtin::kArrayPrototypeConcat 到 JSFuncion 的创建。下面从 JavaScript 源码的角度讲解 Builtin::kArrayPrototypeConcat 的使用方法。测试代码如下:

1.  1.  var a=[1,2,3];2.  2.  var b=[4,5,6];3.  var c= a.concat(b);4.  console.log(c);5.  //分隔线..........................6.  Bytecode Age: 07.  //省略...........................8.   00000159B6561FCA @   28 : c1                Star29.   00000159B6561FCB @   29 : 2d f8 05 08       LdaNamedProperty r2, [5], [8]10.   00000159B6561FCF @   33 : c2                Star111.   00000159B6561FD0 @   34 : 21 04 0a          LdaGlobal [4], [10]12.   00000159B6561FD3 @   37 : c0                Star313.   00000159B6561FD4 @   38 : 5d f9 f8 f7 0c    CallProperty1 r1, r2, r3, [12]14.   00000159B6561FD9 @   43 : 23 06 0e          StaGlobal [6], [14]15.   00000159B6561FDC @   46 : 21 07 10          LdaGlobal [7], [16]16.   00000159B6561FDF @   49 : c1                Star217.   00000159B6561FE0 @   50 : 2d f8 08 12       LdaNamedProperty r2, [8], [18]18.   00000159B6561FE4 @   54 : c2                Star119.   00000159B6561FE5 @   55 : 21 06 14          LdaGlobal [6], [20]20.   00000159B6561FE8 @   58 : c0                Star321.   00000159B6561FE9 @   59 : 5d f9 f8 f7 16    CallProperty1 r1, r2, r3, [22]22.   00000159B6561FEE @   64 : c3                Star023.   00000159B6561FEF @   65 : a8                Return

上述代码中,第 9 行代码加载数组的属性 concat;在本例中,字节码 LdaNamedProperty 的作用是通过字符串 ‘concat’ 加载对应的 JSFunction 方法。

第 13 行代码调用该函数 JSFunction。
字节码由 JavaScript 源码编译并生成,’concat’ 在字节码中保存为常量,该常量是 JavaScript 源码到 JSFuncion 的唯一联系。
后续的几篇文章将会讲解 JavaScript API 的调用过程。


《Chrome V8 源码》45. JavaScript API 源码分析(1)
技术总结
《Chrome V8 源码》45. JavaScript API 源码分析(1)


(1) JavaScript API 以 Builtins 形式存在 V8中;
(2) 在 V8 中使用 SharedFuncion 保存 API,并保存在 JSObject 属性中;
(3) JavaScript 源码中使用的 API 在字节码中被保存为常量字符串,在使用之前利用 LdaNamedProperty 加载相应的 JSFunction。

好了,今天到这里,下次见。
个人能力有限,有不足与纰漏,欢迎批评指正

《Chrome V8 源码》45. JavaScript API 源码分析(1)

- 结尾 -
精彩推荐
【技术分享】2021NUAACTF PWN&RE WP详解
【技术分享】 一种输入回显限制的CLI编写思路
【技术分享】 BPF之路三如何运行BPF程序
《Chrome V8 源码》45. JavaScript API 源码分析(1)
戳“阅读原文”查看更多内容

原文始发于微信公众号(安全客):《Chrome V8 源码》45. JavaScript API 源码分析(1)

版权声明:admin 发表于 2022年1月24日 上午10:19。
转载请注明:《Chrome V8 源码》45. JavaScript API 源码分析(1) | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...