连载《Chrome V8 原理讲解》第十篇 V8 Execution源码分析

阅读量214503

发布时间 : 2021-10-13 14:30:23

 

1 摘要

Execution是V8执行Javascript字节码的运行单元,它负责启动Ignition执行字节码。前几篇文章对Javascript的执行过程和理论知识做过介绍。本文重在实践,注重梳理Execution工作流程,从SharedFunction谈起,跟着Javascript测试用例的执行过程走,代码执行到哪,就讲到哪,详细阐述运行单元Execution的具体工作内容、动作步骤和关键节点,给读者展现一个“活动”的V8。

本文剩余内容按字节码的执行顺序讲解该过程中涉及到的V8源码,故只设置一个章节(章节2)。

关键字: Execution,SharedFunction,Context,BindToCurrentContext

 

2 Execution执行单元

经过GetSharedFunctionInfoForScript()之后,得到了MayHandle<SharedFunctionInfo>类型的maybe_result数据,见下段代码:

0.  MaybeHandle<SharedFunctionInfo> Compiler::GetSharedFunctionInfoForScript(
1.      Isolate* isolate, Handle<String> source,
2.      const Compiler::ScriptDetails& script_details,
3.      ScriptOriginOptions origin_options, v8::Extension* extension,
4.      ScriptData* cached_data, ScriptCompiler::CompileOptions compile_options,
5.      ScriptCompiler::NoCacheReason no_cache_reason, NativesFlag natives) {
6.  //...................删除部分代码,留下最核心功能
7.    // Do a lookup in the compilation cache but not for extensions.
8.    MaybeHandle<SharedFunctionInfo> maybe_result;
9.    IsCompiledScope is_compiled_scope;
10.    if (extension == nullptr) {
11.      bool can_consume_code_cache =
12.          compile_options == ScriptCompiler::kConsumeCodeCache;
13.      if (can_consume_code_cache) {
14.        compile_timer.set_consuming_code_cache();
15.      }
16.      // First check per-isolate compilation cache.
17.      maybe_result = compilation_cache->LookupScript(
18.          source, script_details.name_obj, script_details.line_offset,
19.          script_details.column_offset, origin_options, isolate->native_context(),
20.          language_mode);
21.      if (!maybe_result.is_null()) {
22.        compile_timer.set_hit_isolate_cache();
23.      } 
24.    }
25.  //...................删除部分代码,留下最核心功能
26.    if (maybe_result.is_null()) {
27.      ParseInfo parse_info(isolate);
28.      // No cache entry found compile the script.
29.      NewScript(isolate, &parse_info, source, script_details, origin_options,
30.                natives);
31.      // Compile the function and add it to the isolate cache.
32.      if (origin_options.IsModule()) parse_info.set_module();
33.      parse_info.set_extension(extension);
34.      parse_info.set_eager(compile_options == ScriptCompiler::kEagerCompile);
35.      parse_info.set_language_mode(
36.          stricter_language_mode(parse_info.language_mode(), language_mode));
37.      maybe_result = CompileToplevel(&parse_info, isolate, &is_compiled_scope);
38.      Handle<SharedFunctionInfo> result;
39.      if (extension == nullptr && maybe_result.ToHandle(&result)) {
40.        DCHECK(is_compiled_scope.is_compiled());
41.        compilation_cache->PutScript(source, isolate->native_context(),
42.                                     language_mode, result);
43.      } else if (maybe_result.is_null() && natives != EXTENSION_CODE) {
44.        isolate->ReportPendingMessages();
45.      }
46.    }
47.    return maybe_result;
48.  }

获取SharedFunction时先查找compilation cache,命中则直接返回结果,样例JS源码是第一次运行,第26行的maybe_resultnull,执行37行的CompileToplevel(),得到编译后的结果maybe_result。进入Execution执行单元之前,还需要绑定当面上下文,代码如下:

0.  Local<Script> UnboundScript::BindToCurrentContext() {
1.    auto function_info =
2.        i::Handle<i::SharedFunctionInfo>::cast(Utils::OpenHandle(this));
3.    i::Isolate* isolate = function_info->GetIsolate();
4.    i::Handle<i::JSFunction> function =
5.        isolate->factory()->NewFunctionFromSharedFunctionInfo(
6.            function_info, isolate->native_context());
7.    return ToApiHandle<Script>(function);
8.  }

完成绑定后,进入ExecuteString(),这是执行单元的入口函数,代码如下:

0.  bool Shell::ExecuteString(Isolate* isolate, Local<String> source,
1.                            Local<Value> name, PrintResult print_result,
2.                            ReportExceptions report_exceptions,
3.                            ProcessMessageQueue process_message_queue) {
4.    bool success = true;
5.    {
6.      PerIsolateData* data = PerIsolateData::Get(isolate);
7.      Local<Context> realm =
8.          Local<Context>::New(isolate, data->realms_[data->realm_current_]);
9.      Context::Scope context_scope(realm);
10.      MaybeLocal<Script> maybe_script;
11.      Local<Context> context(isolate->GetCurrentContext());
12.      ScriptOrigin origin(name);
13.      if (options.compile_options == ScriptCompiler::kConsumeCodeCache) {
14.  //...................删除部分代码,留下最核心功能
15.      } else if (options.stress_background_compile) {
16.  //...................删除部分代码,留下最核心功能
17.      } else {
18.        ScriptCompiler::Source script_source(source, origin);
19.        maybe_script = ScriptCompiler::Compile(context, &script_source,
20.                                               options.compile_options);
21.      }
22.      maybe_result = script->Run(realm);
23.  //...................删除部分代码,留下最核心功能
24.      if (options.code_cache_options ==
25.          ShellOptions::CodeCacheOptions::kProduceCacheAfterExecute) {
26.        // Serialize and store it in memory for the next execution.
27.        ScriptCompiler::CachedData* cached_data =
28.            ScriptCompiler::CreateCodeCache(script->GetUnboundScript());
29.        StoreInCodeCache(isolate, source, cached_data);
30.        delete cached_data;
31.      }
32.      if (process_message_queue && !EmptyMessageQueues(isolate)) success = false;
33.      data->realm_current_ = data->realm_switch_;
34.    }
35.    DCHECK(!try_catch.HasCaught());
36.    return success;
37.  }

上述代码中,maybe_script变量在上述代码中被多次重复使用,在debug时可以观察到它代表的语义多次发生变化,我们只需关注this指针的结果即可。19行执行完后得到maybe_script结果,进入22行script->Run(realm),代码如下:

0.  MaybeLocal<Value> Script::Run(Local<Context> context) {
1.    auto isolate = reinterpret_cast<i::Isolate*>(context->GetIsolate());
2.    TRACE_EVENT_CALL_STATS_SCOPED(isolate, "v8", "V8.Execute");
3.    ENTER_V8(isolate, context, Script, Run, MaybeLocal<Value>(),
4.             InternalEscapableScope);
5.    i::HistogramTimerScope execute_timer(isolate->counters()->execute(), true);
6.    i::AggregatingHistogramTimerScope timer(isolate->counters()->compile_lazy());
7.    i::TimerEventScope<i::TimerEventExecute> timer_scope(isolate);
8.    auto fun = i::Handle<i::JSFunction>::cast(Utils::OpenHandle(this));
9.    i::Handle<i::Object> receiver = isolate->global_proxy();
10.    Local<Value> result;
11.    has_pending_exception = !ToLocal<Value>(
12.        i::Execution::Call(isolate, fun, receiver, 0, nullptr), &result);
13.    RETURN_ON_FAILED_EXECUTION(Value);
14.    RETURN_ESCAPED(result);
15.  }

上述代码中,第8行auto fun = i::Handle<i::JSFunction>::cast(Utils::OpenHandle(this));this结果转为JSFunction,得到console.log(JsPrint(6));的fun,fun是JSFuncion类型,fun中包括了字节码序列,进入12行的call()方法。在call()方法中,执行SetUpForCall()完成参数的设置,最后进入Invoke(),代码如下:

0.  V8_WARN_UNUSED_RESULT MaybeHandle<Object> Invoke(Isolate* isolate,
1.                                                   const InvokeParams& params) {
2.  //...................删除部分代码,留下最核心功能
3.    Object value;
4.    Handle<Code> code =
5.        JSEntry(isolate, params.execution_target, params.is_construct);
6.    {
7.      SaveContext save(isolate);
8.      SealHandleScope shs(isolate);
9.      if (FLAG_clear_exceptions_on_js_entry) isolate->clear_pending_exception();
10.      if (params.execution_target == Execution::Target::kCallable) {
11.        // clang-format off
12.        // {new_target}, {target}, {receiver}, return value: tagged pointers
13.        // {argv}: pointer to array of tagged pointers
14.        using JSEntryFunction = GeneratedCode<Address(
15.            Address root_register_value, Address new_target, Address target,
16.            Address receiver, intptr_t argc, Address** argv)>;
17.        // clang-format on
18.        JSEntryFunction stub_entry =
19.            JSEntryFunction::FromAddress(isolate, code->InstructionStart());
20.        Address orig_func = params.new_target->ptr();
21.        Address func = params.target->ptr();
22.        Address recv = params.receiver->ptr();
23.        Address** argv = reinterpret_cast<Address**>(params.argv);
24.        RuntimeCallTimerScope timer(isolate, RuntimeCallCounterId::kJS_Execution);
25.        value = Object(stub_entry.Call(isolate->isolate_data()->isolate_root(),
26.                                       orig_func, func, recv, params.argc, argv));
27.      } else {
28.  //...................删除部分代码,留下最核心功能     
29.      }
30.    }

Invoke()中,第18行获得入口Builtin功能,该Builtin的编号是40,生成函数是Generate_JSEntry,它的作用是为字节码执行做准备工作。第25行进入执行函数Call(),该函数中调用了函数指针fn_ptr_,该指针是第40号Builtin,测试样例的字节码序列是该函数的参数,目前是c++级别的调试跟踪,图1给出了调用堆栈,从fn_ptr_之后将进入汇编码。

这之后进入汇编执行,先执行下面的Generate_JSEntryVariant完成执行前的准备工作,然后执行样例代码。

0.  void Generate_JSEntryVariant(MacroAssembler* masm, StackFrame::Type type,
1.                               Builtins::Name entry_trampoline) {
2.    Label invoke, handler_entry, exit;
3.    Label not_outermost_js, not_outermost_js_2;
4.    {  // NOLINT. Scope block confuses linter.
5.      NoRootArrayScope uninitialized_root_register(masm);
6.      // Set up frame.
7.      __ pushq(rbp);
8.      __ movq(rbp, rsp);
9.      // Push the stack frame type.
10.      __ Push(Immediate(StackFrame::TypeToMarker(type)));
11.      // Reserve a slot for the context. It is filled after the root register has
12.      // been set up.
13.      __ AllocateStackSpace(kSystemPointerSize);
14.      // Save callee-saved registers (X64/X32/Win64 calling conventions).
15.      __ pushq(r12);
16.      __ pushq(r13);
17.      __ pushq(r14);
18.      __ pushq(r15);
19.  #ifdef _WIN64
20.      __ pushq(rdi);  // Only callee save in Win64 ABI, argument in AMD64 ABI.
21.      __ pushq(rsi);  // Only callee save in Win64 ABI, argument in AMD64 ABI.
22.  #endif
23.      __ pushq(rbx);
24.  //.......................代码太长,省略很多.............................
25.    __ ret(0);
26.  }
27.  }  // namespace
28.  //==================分隔线===============================
29.  void Builtins::Generate_JSEntry(MacroAssembler* masm) {
30.    Generate_JSEntryVariant(masm, StackFrame::ENTRY,
31.                            Builtins::kJSEntryTrampoline);
32.  }

见代码30行,40号Builtin功能JSEntry的实现由Generate_JSEntryVariant负责,Builtin功能的分析方法参见上一篇文章,图2给出汇编代码执行的开始片段,请读者自行跟踪。

图2中上面有c++源码,下面就是汇编码了。测试样例console.log(JsPrint(6));,在V8中console是一个全局功能,它的实现如下代码:

0.  #define RUNTIME_FUNCTION_RETURNS_TYPE(Type, InternalType, Convert, Name)      \
1.    static V8_INLINE InternalType __RT_impl_##Name(Arguments args,              \
2.                                                   Isolate* isolate);           \
3.                                                                                \
4.    V8_NOINLINE static Type Stats_##Name(int args_length, Address* args_object, \
5.                                         Isolate* isolate) {                    \
6.      RuntimeCallTimerScope timer(isolate, RuntimeCallCounterId::k##Name);      \
7.      TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.runtime"),                     \
8.                   "V8.Runtime_" #Name);                                        \
9.      Arguments args(args_length, args_object);                                 \
10.      return Convert(__RT_impl_##Name(args, isolate));                          \
11.    }                                                                           \
12.                                                                                \
13.    Type Name(int args_length, Address* args_object, Isolate* isolate) {        \
14.      DCHECK(isolate->context().is_null() || isolate->context().IsContext());   \
15.      CLOBBER_DOUBLE_REGISTERS();                                               \
16.      if (V8_UNLIKELY(TracingFlags::is_runtime_stats_enabled())) {              \
17.        return Stats_##Name(args_length, args_object, isolate);                 \
18.      }                                                                         \
19.      Arguments args(args_length, args_object);                                 \
20.      return Convert(__RT_impl_##Name(args, isolate));                          \
21.    }                                                                           \
22.                                                                                \
23.    static InternalType __RT_impl_##Name(Arguments args, Isolate* isolate)
24.  #define CONVERT_OBJECT(x) (x).ptr()
25.  #define CONVERT_OBJECTPAIR(x) (x)
26.  #define RUNTIME_FUNCTION(Name) \
27.    RUNTIME_FUNCTION_RETURNS_TYPE(Address, Object, CONVERT_OBJECT, Name)
28.  //==================分隔线===============================
29.  //==================分隔线===============================
30.  Object DeclareGlobals(Isolate* isolate, Handle<FixedArray> declarations,
31.                        int flags, Handle<JSFunction> closure) {
32.    HandleScope scope(isolate);
33.    Handle<JSGlobalObject> global(isolate->global_object());
34.    Handle<Context> context(isolate->context(), isolate);
35.    Handle<FeedbackVector> feedback_vector = Handle<FeedbackVector>::null();
36.    Handle<ClosureFeedbackCellArray> closure_feedback_cell_array =
37.        Handle<ClosureFeedbackCellArray>::null();
38.    if (closure->has_feedback_vector()) {
39.      feedback_vector =
40.          Handle<FeedbackVector>(closure->feedback_vector(), isolate);
41.      closure_feedback_cell_array = Handle<ClosureFeedbackCellArray>(
42.          feedback_vector->closure_feedback_cell_array(), isolate);
43.    } else {
44.      closure_feedback_cell_array = Handle<ClosureFeedbackCellArray>(
45.          closure->closure_feedback_cell_array(), isolate);
46.    }
47.    // Traverse the name/value pairs and set the properties.
48.    int length = declarations->length();
49.    FOR_WITH_HANDLE_SCOPE(isolate, int, i = 0, i, i < length, i += 4, {
50.      Handle<String> name(String::cast(declarations->get(i)), isolate);
51.      FeedbackSlot slot(Smi::ToInt(declarations->get(i + 1)));
52.      Handle<Object> possibly_feedback_cell_slot(declarations->get(i + 2),
53.                                                 isolate);
54.      Handle<Object> initial_value(declarations->get(i + 3), isolate);
55.      bool is_var = initial_value->IsUndefined(isolate);
56.      bool is_function = initial_value->IsSharedFunctionInfo();
57.      DCHECK_NE(is_var, is_function);
58.      Handle<Object> value;
59.      if (is_function) {
60.        DCHECK(possibly_feedback_cell_slot->IsSmi());
61.        Handle<FeedbackCell> feedback_cell =
62.            closure_feedback_cell_array->GetFeedbackCell(
63.                Smi::ToInt(*possibly_feedback_cell_slot));
64.        // Copy the function and update its context. Use it as value.
65.        Handle<SharedFunctionInfo> shared =
66.            Handle<SharedFunctionInfo>::cast(initial_value);
67.        Handle<JSFunction> function =
68.            isolate->factory()->NewFunctionFromSharedFunctionInfo(
69.                shared, context, feedback_cell, AllocationType::kOld);
70.        value = function;
71.      } else {
72.        value = isolate->factory()->undefined_value();
73.      }
74.      // Compute the property attributes. According to ECMA-262,
75.      // the property must be non-configurable except in eval.
76.      bool is_eval = DeclareGlobalsEvalFlag::decode(flags);
77.      int attr = NONE;
78.      if (!is_eval) attr |= DONT_DELETE;
79.      // ES#sec-globaldeclarationinstantiation 5.d:
80.      // If hasRestrictedGlobal is true, throw a SyntaxError exception.
81.      Object result = DeclareGlobal(isolate, global, name, value,
82.                                    static_cast<PropertyAttributes>(attr), is_var,
83.                                    is_function, RedeclarationType::kSyntaxError,
84.                                    feedback_vector, slot);
85.      if (isolate->has_pending_exception()) return result;
86.    });
87.    return ReadOnlyRoots(isolate).undefined_value();
88.  }
89.  //==================分隔线===============================
90.  //==================分隔线===============================
91.  RUNTIME_FUNCTION(Runtime_DeclareGlobals) {
92.    HandleScope scope(isolate);
93.    DCHECK_EQ(3, args.length());
94.    CONVERT_ARG_HANDLE_CHECKED(FixedArray, declarations, 0);
95.    CONVERT_SMI_ARG_CHECKED(flags, 1);
96.    CONVERT_ARG_HANDLE_CHECKED(JSFunction, closure, 2);
97.    return DeclareGlobals(isolate, declarations, flags, closure);
98.  }

RUNTIME_FUNCTION(Runtime_DeclareGlobals)是由宏模板定义的全局功能,该函数先检查参数的正确性,然后进入DeclareGlobals()完成具体功能。在我们的测试样例中,DeclareGlobals()是获得console的全局对象。得到该对象后返回到汇编码状态接着执行,获得该对象的log属性,也就是console.log,检测到它的参数JsPrint(6)是函数,然后编译此函数,得到一份字节码序列,开启一个新的执行单元,请读者自行跟踪。
好了,今天到这里,下次见。
恳请读者批评指正、提出宝贵意见
微信:qq9123013 备注:v8交流 邮箱:v8blink@outlook.com

本文由灰豆原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/254805

安全KER - 有思想的安全新媒体

分享到:微信
+15赞
收藏
灰豆
分享到:微信

发表评论

Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全KER All Rights Reserved 京ICP备08010314号-66