0x00 前言
d8自己封装了一个js代码执行器,上一篇我们的代码执行到options.isolate_sources[0].Execute(isolate
,本文将做进一步分析。
0x01 调用栈
#0 v8::SourceGroup::Execute (this=0x5555556542a8, isolate=0x1e9d00000000) at ../../src/d8/d8.cc:2567
#1 0x000055555560008b in v8::Shell::RunMain (isolate=0x1e9d00000000, argc=2, argv=0x7fffffffdf38, last_run=true) at ../../src/d8/d8.cc:3100
#2 0x00005555556013a6 in v8::Shell::Main (argc=2, argv=0x7fffffffdf38) at ../../src/d8/d8.cc:3741
#3 0x00005555556016e2 in main (argc=2, argv=0x7fffffffdf38) at ../../src/d8/d8.cc:3777
0x02 SourceGroup::Execute函数
bool SourceGroup::Execute(Isolate* isolate) {
bool success = true;
for (int i = begin_offset_; i < end_offset_; ++i) {
const char* arg = argv_[i];
//解析-e参数,d8需要执行一段js代码字符串的话,走这个条件分支
if (strcmp(arg, "-e") == 0 && i + 1 < end_offset_) {
// Execute argument given to -e option directly.
//创建作用域
HandleScope handle_scope(isolate);
//创建匿名文件名
Local<String> file_name =
String::NewFromUtf8(isolate, "unnamed", NewStringType::kNormal)
.ToLocalChecked();
//读取js代码
Local<String> source =
String::NewFromUtf8(isolate, argv_[i + 1], NewStringType::kNormal)
.ToLocalChecked();
Shell::set_script_executed();
//执行js代码字符串
if (!Shell::ExecuteString(isolate, source, file_name,
Shell::kNoPrintResult, Shell::kReportExceptions,
Shell::kNoProcessMessageQueue)) {
success = false;
break;
}
++i;
continue;
//判断是否为js的module文件,规则后缀名必须为.mjs与--module参数连用
} else if (ends_with(arg, ".mjs")) {
Shell::set_script_executed();
if (!Shell::ExecuteModule(isolate, arg)) {
success = false;
break;
}
continue;
// 判断是否为module执行模式
} else if (strcmp(arg, "--module") == 0 && i + 1 < end_offset_) {
// Treat the next file as a module.
arg = argv_[++i];
Shell::set_script_executed();
if (!Shell::ExecuteModule(isolate, arg)) {
success = false;
break;
}
continue;
} else if (arg[0] == '-') {
// Ignore other options. They have been parsed already.
continue;
}
//LZ这里的执行命令为d8 test.js,所以前面的逻辑都会跳过,真正的入口位置在这里
// Use all other arguments as names of files to load and run.
//定义作用域
HandleScope handle_scope(isolate);
//创建文件名字符串
Local<String> file_name =
String::NewFromUtf8(isolate, arg, NewStringType::kNormal)
.ToLocalChecked();
//从文件中读取文件内容
Local<String> source = ReadFile(isolate, arg);
if (source.IsEmpty()) {
printf("Error reading '%s'\n", arg);
base::OS::ExitProcess(1);
}
//设置执行状态为true,该静态函数在d8.h中定义
Shell::set_script_executed();
//执行js代码
if (!Shell::ExecuteString(isolate, source, file_name, Shell::kNoPrintResult,
Shell::kReportExceptions,
Shell::kProcessMessageQueue)) {
success = false;
break;
}
}
return success;
}
0x03 Shell::ExecuteString函数
// Executes a string within the current v8 context.
bool Shell::ExecuteString(Isolate* isolate, Local<String> source,
Local<Value> name, PrintResult print_result,
ReportExceptions report_exceptions,
ProcessMessageQueue process_message_queue) {
//i::FLAG_parse_only 为false
if (i::FLAG_parse_only) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
i::VMState<PARSER> state(i_isolate);
i::Handle<i::String> str = Utils::OpenHandle(*(source));
// Set up ParseInfo.
i::ParseInfo parse_info(i_isolate);
parse_info.set_toplevel();
parse_info.set_allow_lazy_parsing();
parse_info.set_language_mode(
i::construct_language_mode(i::FLAG_use_strict));
parse_info.set_script(
parse_info.CreateScript(i_isolate, str, options.compile_options));
if (!i::parsing::ParseProgram(&parse_info, i_isolate)) {
fprintf(stderr, "Failed parsing\n");
return false;
}
return true;
}
HandleScope handle_scope(isolate);
TryCatch try_catch(isolate);
try_catch.SetVerbose(true);
MaybeLocal<Value> maybe_result;
bool success = true;
{
//获取自定义数据,get后为null。
PerIsolateData* data = PerIsolateData::Get(isolate);
//创建realm变量
Local<Context> realm =
Local<Context>::New(isolate, data->realms_[data->realm_current_]);
Context::Scope context_scope(realm);
MaybeLocal<Script> maybe_script;
//创建当前上下文context
Local<Context> context(isolate->GetCurrentContext());
//创建ScriptOrigin对象origin
ScriptOrigin origin(name);
//v8有code caching功能,输入的js代码第一次被编译后,会生成一份cache,再次运行这份js代码时,会优先加载cache,减少重复编译带来的开销。
//编译选项如果为ScriptCompiler::kConsumeCodeCache,则寻找cache并加载
if (options.compile_options == ScriptCompiler::kConsumeCodeCache) {
//根据js代码字符串搜索cache
ScriptCompiler::CachedData* cached_code =
LookupCodeCache(isolate, source);
//如果cache不为空
if (cached_code != nullptr) {
ScriptCompiler::Source script_source(source, origin, cached_code);
maybe_script = ScriptCompiler::Compile(context, &script_source,
options.compile_options);
CHECK(!cached_code->rejected);
} else {
ScriptCompiler::Source script_source(source, origin);
maybe_script = ScriptCompiler::Compile(
context, &script_source, ScriptCompiler::kNoCompileOptions);
}
// options.stress_background_compile为true,则后台编译
} else if (options.stress_background_compile) {
// 启动一个后台线程用于编译js脚本,后台编译就是script streaming 的优化方式
// 同code cache的地位一样重要。浏览器加载多个js脚本时,边下载边编译的过程称为script streaming
// Start a background thread compiling the script.
BackgroundCompileThread background_compile_thread(isolate, source);
//检查线程是否已经就绪
CHECK(background_compile_thread.Start());
// In parallel, compile on the main thread to flush out any data races.
{
TryCatch ignore_try_catch(isolate);
ScriptCompiler::Source script_source(source, origin);
USE(ScriptCompiler::Compile(context, &script_source,
ScriptCompiler::kNoCompileOptions));
}
// Join with background thread and finalize compilation.
background_compile_thread.Join();
maybe_script = v8::ScriptCompiler::Compile(
context, background_compile_thread.streamed_source(), source, origin);
} else {
//没有任何优化的编译方式
ScriptCompiler::Source script_source(source, origin);
maybe_script = ScriptCompiler::Compile(context, &script_source,
options.compile_options);
}
//定义script变量
Local<Script> script;
if (!maybe_script.ToLocal(&script)) {
//打印编译过程中的所有报错
// Print errors that happened during compilation.
if (report_exceptions) ReportException(isolate, &try_catch);
return false;
}
//如果kProduceCache选项开启,则将cache落地。
if (options.code_cache_options ==
ShellOptions::CodeCacheOptions::kProduceCache) {
// Serialize and store it in memory for the next execution.
ScriptCompiler::CachedData* cached_data =
ScriptCompiler::CreateCodeCache(script->GetUnboundScript());
StoreInCodeCache(isolate, source, cached_data);
delete cached_data;
}
//【重点】运行js脚本
maybe_result = script->Run(realm);
//处理选项,如果是kProduceCacheAfterExecute,则运行后在落地cache
if (options.code_cache_options ==
ShellOptions::CodeCacheOptions::kProduceCacheAfterExecute) {
// Serialize and store it in memory for the next execution.
ScriptCompiler::CachedData* cached_data =
ScriptCompiler::CreateCodeCache(script->GetUnboundScript());
StoreInCodeCache(isolate, source, cached_data);
delete cached_data;
}
if (process_message_queue && !EmptyMessageQueues(isolate)) success = false;
data->realm_current_ = data->realm_switch_;
}
Local<Value> result;
//读取结果数据
if (!maybe_result.ToLocal(&result)) {
DCHECK(try_catch.HasCaught());
// Print errors that happened during execution.
if (report_exceptions) ReportException(isolate, &try_catch);
return false;
}
DCHECK(!try_catch.HasCaught());
if (print_result) {
//如果配置了test_shell选项,则把结果重定向到标准输出
if (options.test_shell) {
//如果结果格式未定义,需要Stringify和格式化
if (!result->IsUndefined()) {
// If all went well and the result wasn't undefined then print
// the returned value.
v8::String::Utf8Value str(isolate, result);
fwrite(*str, sizeof(**str), str.length(), stdout);
printf("\n");
}
} else {
v8::String::Utf8Value str(isolate, Stringify(isolate, result));
fwrite(*str, sizeof(**str), str.length(), stdout);
printf("\n");
}
}
return success;
}
0x04 小结
Shell::ExecuteString函数说明了v8运行的大致流程,如果写demo可以参考这个函数。