-
V8源码分析之win7源码编译(第三篇)
2019-12-05 12:09:57前面已经把V8源码download下来和build的过程都讲过了。这里不做过多赘述,只说说在win7下搭建环境与的的一些问题。 0x01 系统环境 名称 版本 位数 备注 操作系统 Windows 7 Professional 6.1.7601, Service...0x00 前言
前面已经把V8源码download下来和build的过程都讲过了。这里不做过多赘述,只说说在win7下搭建环境与的的一些问题。
0x01 系统环境
名称 版本 位数 备注 操作系统 Windows 7 Professional 6.1.7601, Service Pack 1 64 bit \ Python 2.7.13 64 bit \ 没有全局代理的情况下
0x02 设置代理
打开cmd.exe 配置git代理
配置poweshell脚本cipd.ps1的代理,这里在WebClient对象中加入了代理服务器地址
http://127.0.0.2:8080
try { echo "Downloading CIPD client for $Platform from $URL..." $FiddlerWP = New-Object System.Net.WebProxy "http://127.0.0.2:8080" $wc = (New-Object System.Net.WebClient) $wc.Headers.Add("User-Agent", $UserAgent) $wc.Proxy = $FiddlerWP try { $wc.DownloadFile($URL, $TmpPath) } catch { throw "Failed to download the file, check your network connection" }
0x03 禁止depot_tools更新
先谢郭嘉,git可以设置代理,然而,depot_tools和cipd这两个需要翻墙更新,搞了半天没有跟到发起连接的代码位置。这里先说明什么是cipd,再给出禁用更新的方法。
CIPD(Chrome Infrastructure Package Deployment)
CIPD 是包部署工具。由包注册和一个可以创建,更新,下载和安装的命令行客户端两部分组成。了解inux的童鞋是不是有点感觉了。对,就是一个apt-get一样的包管理工具,只不过是跟pip,npm,brew等负责chrome相关的包管理。【传送门】
cipd的包结构
CIPD package具有一个
package name
(例如“ infra / tools / foo”)和一个内容寻址实例的列表(例如“ bec8e88201949be06b06174178c2f62b81e4008e”),其中软件包名称中的斜杠形成了软件包的层次结构,而一个实例是一个ZIP文件。depot_tools
depot_tools 是用于Chromium开发的需要的一系列工具集合。这个定义相当于屁都没说。【传送门】
Chromium使用一个脚本集合,即dept_tools, 用于管理Chromium源码仓库与Chromium开发过程之间的交互。主要包括如下工具集:
- gclient:管理svn和git checkout的工具。类似于repo_tool.
…
其他子项就不一一介绍了,有个印象就成了,打算深入了解depot_tools的童鞋,可以使用此【传送门】。
gclient
gclient是一个python脚本,用于管理模块化依赖项的工作区,每个依赖项都是用独立的不同的的svn或git 拉取(checkout)。
gclient这个要单独拉出来说说,大概意思是,项目比较大,用了很多第三方库,这些库有的是svn有的是git,不好傻瓜式管理。就拍脑袋搞了一个封装,管你用的什么,gclient可以完成仓库管理的各种功能,快活的很。
特性:
- 根据操作系统OS指定依赖
- 根据不同仓库的父子依赖关系指定依赖
- 变量可用于表示抽象概念
- 提供一个checkout运行后即可调用的Hook
- .gclient和DEPS这些python脚本,可以方便地增加和修改额外配置数据
.gclient 文件
一个master 文件。实际上是一个python脚本。它指定以下变量:
- solutions: 指定需要拉取项目的字段数组。
- hooks:checkout的钩子
- target_os: OS以依赖列表
- cache_dir: 主要针对bots,多个工作集使用单个git缓存. 参见 gclient.py --cache-dir
每个项目在solutions数组中定义,同时包含可选的DEPS文件。.gclient文件通过
gclient config <url>
生成,或者手写。每个solustions条目是包含一些变量的字段:- name:checkout的路径
- url:用于fetch/clone的远程仓库
- custom_deps: 可选,定制的依赖
- custom_vars: 可选,定制的变量
- safesync_url:可选
禁用depot_tools的方法
1. 修改gclient.bat文件
在
setlocal
下面增加一行set DEPOT_TOOLS_UPDATE=0
,如下@echo off :: Copyright (c) 2012 The Chromium Authors. All rights reserved. :: Use of this source code is governed by a BSD-style license that can be :: found in the LICENSE file. setlocal echo "--------->turn off the update<------" set DEPOT_TOOLS_UPDATE=0 :: Shall skip automatic update? IF "%DEPOT_TOOLS_UPDATE%" == "0" GOTO :CALL_GCLIENT :: Synchronize the root d irectory before deferring control back to gclient.py. call "%~dp0update_depot_tools.bat" %* :CALL_GCLIENT :: Ensure that "depot_tools" is somewhere in PATH so this tool can be used :: standalone, but allow other PATH manipulations to take priority. set PATH=%PATH%;%~dp0 :: Defer control. IF "%GCLIENT_PY3%" == "1" ( :: TODO(1003139): Use vpython3 once vpython3 works on Windows. python3 "%~dp0gclient.py" %* ) ELSE ( python "%~dp0gclient.py" %* )
这样gclient就不会更新报错了
D:\worksp\git>depot_tools\gclient sync --force "--------->turn off the update<------" [P11288 09:27:15.448 client.go:309 W] RPC failed transiently. Will retry in 1s {"error":"failed to send request: Post https://chrome-infra-packages.appspot.com/prpc/cipd.Repository/ResolveVersion: proxyconnect tcp: tls: first record does ot look like a TLS handshake", "host":"chrome-infra-packages.appspot.com", "method":"ResolveVersion", "service":"cipd.Re ository", "sleepTime":"1s"} [P11288 09:27:15.453 client.go:309 W] RPC failed transiently. Will retry in 1s {"error":"failed to send request: Post https://chrome-infra-packages.appspot.com/prpc/cipd.Repository/ResolveVersion: proxyconnect tcp: tls: first record does ot look like a TLS handshake", "host":"chrome-infra-packages.appspot.com", "method":"ResolveVersion", "service":"cipd.Re ository", "sleepTime":"1s"} [P11288 09:27:15.458 client.go:309 W] RPC failed transiently. Will retry in 1s {"error":"failed to send request: Post https://chrome-infra-packages.appspot.com/prpc/cipd.Repository/ResolveVersion: proxyconnect tcp: tls: first record does ot look like a TLS handshake", "host":"chrome-infra-packages.appspot.com", "method":"ResolveVersion", "service":"cipd.Re ository", "sleepTime":"1s"} 终止批处理操作吗(Y/N)? y D:\worksp\git>depot_tools\gclient sync --force "--------->turn off the update<------"
做事还得做绝,发现fetch还会更新,继续修改fetch.bat, 直接注释掉更新
@echo off :: Copyright (c) 2013 The Chromium Authors. All rights reserved. :: Use of this source code is governed by a BSD-style license that can be :: found in the LICENSE file. setlocal :: Synchronize the root directory before deferring control back to gclient.py. :: call "%~dp0\update_depot_tools.bat" :: Ensure that "depot_tools" is somewhere in PATH so this tool can be used :: standalone, but allow other PATH manipulations to take priority. set PATH=%PATH%;%~dp0 :: Defer control. IF "%GCLIENT_PY3%" == "1" ( vpython3 "%~dp0\fetch.py" %* ) ELSE ( vpython "%~dp0\fetch.py" %* )
2. 使用环境变量
据说在环境变量中设置DEPOT_TOOLS_UPDATE为0 即可。第一种方法亲测有效,这个仅供参考。
0x04 fetch v8
执行如下命令
depot_tools\fetch v8
提示没有权限
Running: 'C:\Users\xxxx\Scripts\python.exe' 'D:\worksp\git\depot_tools\gclient.py' sync - -with_branch_heads Running: git submodule foreach 'git config -f $toplevel/.git/config submodule.$name.ignore all' Traceback (most recent call last): File "D:\worksp\git\depot_tools\\fetch.py", line 318, in <module> sys.exit(main()) File "D:\worksp\git\depot_tools\\fetch.py", line 313, in main return run(options, spec, root) File "D:\worksp\git\depot_tools\\fetch.py", line 307, in run return checkout.init() File "D:\worksp\git\depot_tools\\fetch.py", line 158, in init cwd=wd) File "D:\worksp\git\depot_tools\\fetch.py", line 111, in run_git return git_common.run(*cmd, **kwargs) File "D:\worksp\git\depot_tools\git_common.py", line 700, in run return run_with_stderr(*cmd, **kwargs)[0] File "D:\worksp\git\depot_tools\git_common.py", line 766, in run_with_stderr proc = subprocess2.Popen(cmd, **kwargs) File "D:\worksp\git\depot_tools\subprocess2.py", line 159, in __init__ % (str(e), kwargs.get('cwd'), args[0])) OSError: Execution failed with error: [Error 267] . Check that D:\worksp\git\v8 or E:\Program Files\Git\cmd\git.exe exist and have execution permission.
另一个坑
fetch.py脚本不知道为何在win32环境下会使用
git.bat
命令而不用git
,导致刚开始就报错>depot_tools\fetch v8 --spec 'solutions = [ { "url": "https://chromium.googlesource.com/v8/v8.git", "managed": False, "name": "v8", "deps_file": "DEPS", "custom_deps": {}, }, ] ' v8 (ERRO R) ---------------------------------------- [0:00:00] Started. ---------------------------------------- Traceback (most recent call last): File "D:\worksp\git\depot_tools\metrics.py", line 267, in print_notice_and_exit yield File "D:\worksp\git\depot_tools\gclient.py", line 3183, in <module> sys.exit(main(sys.argv[1:])) File "D:\worksp\git\depot_tools\gclient.py", line 3168, in main return dispatcher.execute(OptionParser(), argv) File "D:\worksp\git\depot_tools\subcommand.py", line 252, in execute return command(parser, args[1:]) File "D:\worksp\git\depot_tools\gclient.py", line 2725, in CMDsync ret = client.RunOnDeps('update', args) File "D:\worksp\git\depot_tools\gclient.py", line 1763, in RunOnDeps patch_refs=patch_refs, target_branches=target_branches) File "D:\worksp\git\depot_tools\gclient_utils.py", line 914, in flush reraise(e[0], e[1], e[2]) File "D:\worksp\git\depot_tools\gclient_utils.py", line 991, in run self.item.run(*self.args, **self.kwargs) File "D:\worksp\git\depot_tools\gclient.py", line 933, in run file_list) File "D:\worksp\git\depot_tools\gclient_scm.py", line 132, in RunCommand return getattr(self, command)(options, args, file_list) File "D:\worksp\git\depot_tools\gclient_scm.py", line 516, in update mirror = self._GetMirror(url, options, revision_ref) File "D:\worksp\git\depot_tools\gclient_scm.py", line 957, in _GetMirror if not self.cache_dir: File "D:\worksp\git\depot_tools\gclient_scm.py", line 218, in cache_dir return git_cache.Mirror.GetCachePath() File "D:\worksp\git\depot_tools\git_cache.py", line 305, in GetCachePath ['cache.cachepath']).strip() File "E:\Program Files\Python\lib\subprocess.py", line 567, in check_output process = Popen(stdout=PIPE, *popenargs, **kwargs) File "E:\Program Files\Python\lib\subprocess.py", line 711, in __init__ errread, errwrite) File "E:\Program Files\Python\lib\subprocess.py", line 959, in _execute_child startupinfo) WindowsError: [Error 2]
直接修改git_cache.py文件,使
git_exe
变量赋值为'git'
,代码如下:class Mirror(object): git_exe = 'git' if sys.platform.startswith('win') else 'git' gsutil_exe = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'gsutil.py') cachepath_lock = threading.Lock()
继续fetch,可以看到已经能正常下载了
1>git config 1>________ running 'git -c core.deltaBaseCacheLimit=2g clone --no-checkout --progress https://chromium.googlesource.com/v 8/v8.git D:\worksp\git\_gclient_v8_prggkr' in 'D:\worksp\git' 1>Cloning into 'D:\worksp\git\_gclient_v8_prggkr'... 1>remote: Sending approximately 581.51 MiB ... 1>remote: Counting objects: 7675, done 1>remote: Finding sources: 100% (15/15) 1>Receiving objects: 13% (96685/723669), 41.43 MiB | 268.00 KiB/s
0x05问题
1. gclient工具目前只支持python2
如果使用python3的话会报错,换成python2的环境即可。
Warning: gclient doesn't yet support Python 3 and may not work correctly.
0x06 参考文献
http://www.cclk.cc/2017/08/03/webrtc/webrtc学习1-全平台编译/
https://blog.csdn.net/yyinhai/article/details/52955608
https://blog.csdn.net/yyinhai/article/details/54016892
https://skylerlee.github.io/codelet/2017/03/08/build-v8/ - gclient:管理svn和git checkout的工具。类似于repo_tool.
-
V8源码分析之d8源码注解(第七篇)
2020-01-22 16:54:50js代码解析的过程为编译成字节码后再加载字节码执行, ScriptCompiler::Compile()的过程是分为词法分析与语法分析,将js代码解析成AST树后就可以很顺利的转换成字节码。 本节先跳过复杂的编译过程看下执行逻辑。 0x...0x00 前言
js代码解析的过程为编译成字节码后再加载字节码执行,
ScriptCompiler::Compile()
的过程是分为词法分析与语法分析,将js代码解析成AST树后就可以很顺利的转换成字节码。本节先跳过复杂的编译过程看下执行逻辑。
0x01 调用栈
Thread 1 "d8" hit Breakpoint 1, v8::Shell::ExecuteString (isolate=0x59000000000, source=..., name=..., print_result=v8::Shell::kNoPrintResult, report_exceptions=v8::Shell::kReportExceptions, process_message_queue=v8::Shell::kProcessMessageQueue) at ../../src/d8/d8.cc:527 527 maybe_result = script->Run(realm); (gdb) s v8::Local<v8::Script>::operator-> (this=0x7fffffffd0a0) at ../../include/v8.h:213 213 V8_INLINE T* operator->() const { return val_; } (gdb) s v8::Script::Run (this=0x5555556c1b98, context=...) at ../../src/api/api.cc:2143 2143 auto isolate = reinterpret_cast<i::Isolate*>(context->GetIsolate()); (gdb) bt #0 v8::Script::Run (this=0x5555556c1b98, context=...) at ../../src/api/api.cc:2143 #1 0x00005555555efd77 in v8::Shell::ExecuteString (isolate=0x59000000000, source=..., name=..., print_result=v8::Shell::kNoPrintResult, report_exceptions=v8::Shell::kReportExceptions, process_message_queue=v8::Shell::kProcessMessageQueue) at ../../src/d8/d8.cc:527 #2 0x00005555555fd57e in v8::SourceGroup::Execute (this=0x5555556542a8, isolate=0x59000000000) at ../../src/d8/d8.cc:2620 #3 0x000055555560008b in v8::Shell::RunMain (isolate=0x59000000000, argc=2, argv=0x7fffffffdf38, last_run=true) at ../../src/d8/d8.cc:3100 #4 0x00005555556013a6 in v8::Shell::Main (argc=2, argv=0x7fffffffdf38) at ../../src/d8/d8.cc:3741 #5 0x00005555556016e2 in main (argc=2, argv=0x7fffffffdf38) at ../../src/d8/d8.cc:3777
0x02 Script::Run函数
MaybeLocal<Value> Script::Run(Local<Context> context) { auto isolate = reinterpret_cast<i::Isolate*>(context->GetIsolate()); //如果开启--trace-events-enabled选项的话,则初始化TraceEvent相关的对象,进行堆栈,性能相关 //指标的跟踪,当然这里没有开启,可以忽略 TRACE_EVENT_CALL_STATS_SCOPED(isolate, "v8", "V8.Execute"); //日志记录V8开始执行,声明布尔型变量has_pending_exception用于保存执行返回结果 ENTER_V8(isolate, context, Script, Run, MaybeLocal<Value>(), InternalEscapableScope); //初始化直方图计时器 i::HistogramTimerScope execute_timer(isolate->counters()->execute(), true); //初始化聚合直方图计时器 i::AggregatingHistogramTimerScope timer(isolate->counters()->compile_lazy()); //初始化计时器事件 i::TimerEventScope<i::TimerEventExecute> timer_scope(isolate); //初始化一个js函数句柄fun auto fun = i::Handle<i::JSFunction>::cast(Utils::OpenHandle(this)); //初始化receiver i::Handle<i::Object> receiver = isolate->global_proxy(); //初始化一个js变量用于保存js的执行结果 Local<Value> result; //执行js代码 has_pending_exception = !ToLocal<Value>( i::Execution::Call(isolate, fun, receiver, 0, nullptr), &result); RETURN_ON_FAILED_EXECUTION(Value); RETURN_ESCAPED(result); }
TRACE_EVENT_CALL_STATS_SCOPED宏
事件跟踪(trace event)是v8引擎的一个重要调试辅助的功能,事件有不同的分组(category),例如堆栈调用,函数执行时间等等。
浏览器可以图形化这些事件日志,方便分析性能瓶颈。借用V8官网的一张图,同学们可以有个初步印象
回到源码上来,看下这个宏展开#define TRACE_EVENT_CALL_STATS_SCOPED(isolate, category_group, name) \ INTERNAL_TRACE_EVENT_CALL_STATS_SCOPED(isolate, category_group, name)
继续展开
#define INTERNAL_TRACE_EVENT_CALL_STATS_SCOPED(isolate, category_group, name) \ INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group); \ v8::internal::tracing::CallStatsScopedTracer INTERNAL_TRACE_EVENT_UID( \ tracer); \ if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ INTERNAL_TRACE_EVENT_UID(tracer) \ .Initialize(isolate, INTERNAL_TRACE_EVENT_UID(category_group_enabled), \ name); \ }
第一句
INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group);
看下宏定义
#define INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO(category_group) \ static TRACE_EVENT_API_ATOMIC_WORD INTERNAL_TRACE_EVENT_UID(atomic) = 0; \ const uint8_t* INTERNAL_TRACE_EVENT_UID(category_group_enabled); \ INTERNAL_TRACE_EVENT_GET_CATEGORY_INFO_CUSTOM_VARIABLES( \ category_group, INTERNAL_TRACE_EVENT_UID(atomic), \ INTERNAL_TRACE_EVENT_UID(category_group_enabled));
宏定义太多了,就不一一介绍了,这段翻译成人话如下:
//初始化一个事件临时变量 static v8::base::AtomicWord trace_event_unique_atomic2144 = 0; //声明事件指针,为啥没有赋值? const uint8_t* trace_event_unique_category_group_enabled2144; //这里赋了初值,通过原子操作,原子操作的主要主用是避免多线程中读写错乱的问题 trace_event_unique_category_group_enabled2144 = reinterpret_cast<const uint8_t*>( v8::base::Relaxed_Load(&(trace_event_unique_atomic2144))); //判断事件指针 != NULL则进到block中执行 if (!trace_event_unique_category_group_enabled2144) { //获取TraceEventHelper的函数句柄,给事件指针 trace_event_unique_category_group_enabled2144 = v8::internal::tracing::TraceEventHelper::GetTracingController( )->GetCategoryGroupEnabled(trace_event_unique_category_group_enabled2144); v8::base::Relaxed_Store( &(trace_event_unique_atomic2144), (reinterpret_cast<v8::base::AtomicWord>( trace_event_unique_category_group_enabled2144))); }
第二句
v8::internal::tracing::CallStatsScopedTracer INTERNAL_TRACE_EVENT_UID( \ tracer); \
展开后
v8::internal::tracing::CallStatsScopedTracer trace_event_unique_tracer2144;
声明了一个CallStatsScopedTracer类型的scope状态跟踪器。
第三句
if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE()) { \ INTERNAL_TRACE_EVENT_UID(tracer) \ .Initialize(isolate, INTERNAL_TRACE_EVENT_UID(category_group_enabled), \ name); \ }
展开后
if( v8::base::Relaxed_Load(reinterpret_cast<const v8::base::Atomic8*>( trace_event_unique_category_group_enabled2144))& (1|4) ){ trace_event_unique_tracer2144.Initialize(isolate, trace_event_unique_category_group_enabled2144, name) }
分解如下:
INTERNAL_TRACE_EVENT_CALL_STATS_SCOPED
中有一个if判断语句if (INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE())
比较关键。
看下if判断的内容#define INTERNAL_TRACE_EVENT_CATEGORY_GROUP_ENABLED_FOR_RECORDING_MODE() \ TRACE_EVENT_API_LOAD_CATEGORY_GROUP_ENABLED() & \ (kEnabledForRecording_CategoryGroupEnabledFlags | \ kEnabledForEventCallback_CategoryGroupEnabledFlags)
其中:
kEnabledForRecording_CategoryGroupEnabledFlags = 1
kEnabledForEventCallback_CategoryGroupEnabledFlags = 4
继续展开前面的宏TRACE_EVENT_API_LOAD_CATEGORY_GROUP_ENABLED()
#define TRACE_EVENT_API_LOAD_CATEGORY_GROUP_ENABLED() \ v8::base::Relaxed_Load(reinterpret_cast<const v8::base::Atomic8*>( \ INTERNAL_TRACE_EVENT_UID(category_group_enabled)))
INTERNAL_TRACE_EVENT_UID
宏的用处是:创建临时变量降低指令开销。这个变量名是由name_prefix和入口代码行号拼接组成的唯一名称,可以有效的避免冲突。P.S. 说是降低指令开销,但完全想不出来降低什么了?你人工命名也不会多一条指令,最大的好处就是不用费脑想变量名了而已。
看看这组宏:#define INTERNAL_TRACE_EVENT_UID3(a, b) trace_event_unique_##a##b #define INTERNAL_TRACE_EVENT_UID2(a, b) INTERNAL_TRACE_EVENT_UID3(a, b) #define INTERNAL_TRACE_EVENT_UID(name_prefix) \ INTERNAL_TRACE_EVENT_UID2(name_prefix, __LINE__)
拼接后的函数调用栈如下:
(gdb) bt #0 v8::base::Relaxed_Load (ptr=0x7ffff7fb3b08 <v8::Script::Run(v8::Local<v8::Context>)::trace_event_unique_atomic2144>) at ../../src/base/atomicops_internals_portable.h:199 #1 0x00007ffff66aafab in v8::Script::Run (this=0x5555556c1b98, context=...) at ../../src/api/api.cc:2144 #2 0x00005555555efd77 in v8::Shell::ExecuteString (isolate=0x167c00000000, source=..., name=..., print_result=v8::Shell::kNoPrintResult, report_exceptions=v8::Shell::kReportExceptions, process_message_queue=v8::Shell::kProcessMessageQueue) at ../../src/d8/d8.cc:527 #3 0x00005555555fd57e in v8::SourceGroup::Execute (this=0x5555556542a8, isolate=0x167c00000000) at ../../src/d8/d8.cc:2620 #4 0x000055555560008b in v8::Shell::RunMain (isolate=0x167c00000000, argc=2, argv=0x7fffffffdf38, last_run=true) at ../../src/d8/d8.cc:3100 #5 0x00005555556013a6 in v8::Shell::Main (argc=2, argv=0x7fffffffdf38) at ../../src/d8/d8.cc:3741 #6 0x00005555556016e2 in main (argc=2, argv=0x7fffffffdf38) at ../../src/d8/d8.cc:3777
trace_event_unique_atomic2144
确实是由固定字符串trace_event_unique_
+name_prefix字符串atomic
和frame 1中的行号2144
组成。TRACE EVENT宏小结
主要用于事件追踪的注册,增加了多线程安全的原子操作保护。不是LZ关心的主要问题,不关心的同学也可以忽略掉。
0x03 小结
- 执行js前增加trace event和计时器帮助性能优化
- 用了很多宏
-
V8源码分析之d8源码注解(第六篇)
2020-01-06 19:44:44d8自己封装了一个js代码执行器,上一篇我们的代码执行到options.isolate_sources[0].Execute(isolate,本文将做进一步分析。 0x01 调用栈 #0 v8::SourceGroup::Execute (this=0x5555556542a8, isolate=0x1e9d...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可以参考这个函数。
-
js引擎v8源码分析之Handle(基于v8 0.1.5)
2020-02-15 02:07:20Handle是使用v8的时候很重要的一个概念和类。他本质是堆对象的封装。我们通过Handle管理真正的对象,而不是直接操作对象。Handle在v8中有两个实现。一个是对外使用的一个是内部使用的。我们先看一下内部使用的。 1 ...Handle是使用v8的时候很重要的一个概念和类。他本质是堆对象的封装。我们通过Handle管理真正的对象,而不是直接操作对象。Handle在v8中有两个实现。一个是对外使用的一个是内部使用的。我们先看一下内部使用的。
1 内部handle
template<class T> class Handle { public: INLINE(Handle(T** location)) { location_ = location; } INLINE(explicit Handle(T* obj)); INLINE(Handle()) : location_(NULL) {} template <class S> Handle(Handle<S> handle) { location_ = reinterpret_cast<T**>(handle.location()); } INLINE(T* operator ->() const) { return operator*(); } bool is_identical_to(const Handle<T> other) const { return operator*() == *other; } INLINE(T* operator*() const); T** location() const { return location_; } template <class S> static Handle<T> cast(Handle<S> that) { T::cast(*that); return Handle<T>(reinterpret_cast<T**>(that.location())); } static Handle<T> null() { return Handle<T>(); } bool is_null() {return location_ == NULL; } inline Handle<T> EscapeFrom(HandleScope* scope); private: T** location_; };
下面是实现。
template<class T> Handle<T>::Handle(T* obj) { location_ = reinterpret_cast<T**>(HandleScope::CreateHandle(obj)); } template <class T> inline T* Handle<T>::operator*() const { return *location_; }
Handle类的定义没有太多的逻辑,就是对用户定义的对象指针进行封装。有一个重要的地址是构造函数。我们看到当我们定义一个Handle的时候,他会调HandleScope::CreateHandle生成一个Handle对象。在HandleScope那篇文章已经分析过了。handle对象的location对象指针一个内存,该内存保存了obj的地址。
2 外部handle
// T表示handle管理的对象的类型 template <class T> class Handle { public: Handle(); explicit Handle(T* val) : val_(val) { } // *that得到指向handle管理的对象的指针,转成T类型,赋值给val_ template <class S> inline Handle(Handle<S> that) : val_(reinterpret_cast<T*>(*that)) { TYPE_CHECK(T, S); } bool IsEmpty() { return val_ == 0; } T* operator->(); T* operator*(); void Clear() { this->val_ = 0; } /* 比较handle指向的对象的地址是否相等 this是指向当前对象的指针,*this是当前对象,**this是返回val_的值,看重载运算符*的实现 *that是val_的值 */ template <class S> bool operator==(Handle<S> that) { void** a = reinterpret_cast<void**>(**this); void** b = reinterpret_cast<void**>(*that); // a等于0,则返回b是否等于0,是的话说明a==b,即true if (a == 0) return b == 0; // a不等于0,如果b==0,则返回false if (b == 0) return false; // 比较ab,即取val_里的内容比较 return *a == *b; } template <class S> bool operator!=(Handle<S> that) { return !operator==(that); } template <class S> static inline Handle<T> Cast(Handle<S> that) { // 返回一个空的handle,即val_是null if (that.IsEmpty()) return Handle<T>(); // *that得到指向handle管理的对象的指针,转成T类型的对象,转成底层对象是类型T的handle return Handle<T>(T::Cast(*that)); } private: T* val_; };
下面是实现。只有两个运算符的重载。
template <class T> T* Handle<T>::operator->() { return val_; } template <class T> T* Handle<T>::operator*() { return val_; }
我们看到Handle的实现没有太内容,就是在对象和用户之前加了一层。下面看看他的两个子类Local和Persistent。
3 Local
1 Local类是基于栈分配的一种Handle,他在一个函数开始的时候,声明一个HandleScope,HandleScope下面所有的Handle都在最近的的HandleScope中分配,函数执行完后,会一起被释放。
template <class T> class Local : public Handle<T> { public: Local(); // 调用Local函数的时候S被替换成that对应的类型,结果是Handle底层的val_指向一个T类型的对象 template <class S> inline Local(Local<S> that) // *that即取得他底层对象的地址 : Handle<T>(reinterpret_cast<T*>(*that)) { TYPE_CHECK(T, S); } template <class S> inline Local(S* that) : Handle<T>(that) { } template <class S> static inline Local<T> Cast(Local<S> that) { if (that.IsEmpty()) return Local<T>(); return Local<T>(T::Cast(*that)); } static Local<T> New(Handle<T> that); };
Local没有做什么事情,是对基类Handle的简单继承。下面是实现。
template <class T> Handle<T>::Handle() : val_(0) { } template <class T> Local<T>::Local() : Handle<T>() { } template <class T> Local<T> Local<T>::New(Handle<T> that) { if (that.IsEmpty()) return Local<T>(); void** p = reinterpret_cast<void**>(*that); return Local<T>(reinterpret_cast<T*>(HandleScope::CreateHandle(*p))); }
我们看看如果使用一个句柄。
HandleScope scope; Local<String> source = String::New('hello');
我们看一下String::New的实现。
// i::Handle表示内部使用的handle Local<String> v8::String::New(const char* data, int length) { if (length == -1) length = strlen(data); // 申请一个对象,由handle管理 i::Handle<i::String> result = i::Factory::NewStringFromUtf8(i::Vector<const char>(data, length)); return Utils::ToLocal(result); } Local<v8::String> Utils::ToLocal(v8::internal::Handle<v8::internal::String> obj) { return Local<String>(reinterpret_cast<String*>(obj.location())); }
我们在看下HandleScope中的那个图。再来例假ToLocal函数的逻辑。
我们对着图来理解ToLocal,我们知道obj.location()返回的是指针,指向保存了对象地址的内存地址。然后转成String*,即拿到对象的地址。构造一个Local对象返回。即Local内部管理用户定义的对象(String::New函数执行完后,他里面定义的result,即handle被析构)。如下图。
当HandleScope析构的时候,他会释放用户定义的对象的内存,然后Local对象本身是在栈上分配的,也会被析构。这就是v8用本地handle(临时handle)管理堆对象的大致原理。一般来说handle在函数结束后就会被释放,如果想在函数执行完还使得句柄可用,可用使用逃逸(escape)。原理是销毁当前的HandleScope,然后在前一个HandleScope对象里分配一个handle。下面继续看看持久句柄。
4 Persistentemplate <class T> class Persistent : public Handle<T> { public: Persistent(); template <class S> inline Persistent(Persistent<S> that): Handle<T>(reinterpret_cast<T*>(*that)) { TYPE_CHECK(T, S); } template <class S> inline Persistent(S* that) : Handle<T>(that) { } template <class S> explicit inline Persistent(Handle<S> that) : Handle<T>(*that) { } template <class S> static inline Persistent<T> Cast(Persistent<S> that) { if (that.IsEmpty()) return Persistent<T>(); return Persistent<T>(T::Cast(*that)); } static Persistent<T> New(Handle<T> that); void Dispose(); void MakeWeak(void* parameters, WeakReferenceCallback callback); void ClearWeak(); bool IsNearDeath(); bool IsWeak(); private: friend class ImplementationUtilities; friend class ObjectTemplate; };
相对于基类Handle,Persistent多了几个功能,我们看一下使用用例。
Persistent<Context> context = Context::New();
我们看一下Context::New()的定义。
Persistent<Context> v8::Context::New(v8::ExtensionConfiguration* extensions, v8::Handle<ObjectTemplate> global_template, v8::Handle<Value> global_object) { i::Handle<i::Context> env = i::Bootstrapper::CreateEnvironment( Utils::OpenHandle(*global_object), global_template, extensions ); return Persistent<Context>(Utils::ToLocal(env)); }
我们看一下CreateEnvironment的实现。
Handle<Context> Bootstrapper::CreateEnvironment(...参数) { Genesis genesis(global_object, global_template, extensions); return genesis.result(); } Genesis::Genesis(...参数) { CreateRoots(global_template, global_object); result_ = global_context_; } void Genesis::CreateRoots(...参数) { // 创建一个全局上下文对象,分配一个Context对象 global_context_ = Handle<Context>::cast( GlobalHandles::Create(*Factory::NewGlobalContext())); } Handle<Context> result() { return result_; }
通过上面的代码我们知道Persistent指向的是一个GlobalHandles::Create返回的地址。所以我们主要分析GlobalHandles这个类的实现。这个类的代码比较多,我们只分析相关的(Node类维护一个对象的信息,地址,状态)。后面会单独分析。
// 一个handle对应一个Node Handle<Object> GlobalHandles::Create(Object* value) { Counters::global_handles.Increment(); Node* result; /* 有一个free_list,保存着DESTROYED状态但还没有被释放的Node, first_free指向第一个节点,为NULL说明没有待回收的节点,即没有可重用的节点 */ if (first_free() == NULL) { // Allocate a new node. // 没有可重用的节点则分配一个新的 result = new Node(value); // 头插法,设置新增的node的下一个节点是当前头结点 result->set_next(head()); // 头指针指向新增的node set_head(result); } else { // Take the first node in the free list. // 获取一个可以重用的节点 result = first_free(); // 获取重用节点在free_list中的第一个节点,first_free指向新的可重用节点 set_first_free(result->next_free()); // 重新初始化该节点 result->Initialize(value); } // 返回Node对象的首地址 return result->handle(); }
从上面的代码中我们大概知道,有一个链表,每个node节点保存了一个持久对象的信息。持久句柄指向的对象都是在这个链表里管理的。持久句柄执行node的地址,最后要调Dispose释放。
template <class T> void Persistent<T>::Dispose() { if (this->IsEmpty()) return; V8::DisposeGlobal(reinterpret_cast<void**>(**this)); } void V8::DisposeGlobal(void** obj) { LOG_API("DisposeGlobal"); if (has_shut_down) return; i::GlobalHandles::Destroy(reinterpret_cast<i::Object**>(obj)); } // 销毁一个节点 void GlobalHandles::Destroy(Object** location) { Counters::global_handles.Decrement(); if (location == NULL) return; Node* node = Node::FromLocation(location); node->Destroy(); // Link the destroyed. // 设置待销毁节点在free_list链表里的下一个节点是当前的头结点 node->set_next_free(first_free()); // 头指针指向待销毁的节点, set_first_free(node); }
大致是根据对象的地址转成node节点,销毁该节点。从链表中删除。
总结,这就是v8中关于handle的一些知识。
-
V8源码分析之Ubuntu16下获取源码(第一篇)
2019-11-28 11:04:34本文获取源码的前提是科学上网,请自行解决。 0x01 使用Git V8的Git仓库为 https://chromium.googlesource.com/v8/v8.git,同时在GitHub上还有个官方镜像: https://github.com/v8/v8. 不管是为了遵循主流思想在git..... -
V8源码分析之Ubuntu16下编译源码(第二篇)
2019-11-28 13:04:57V8引擎使用Ninja进行构建源码,GN是用来辅助生成Ninja配置文件的工具。 从源码构建V8需要三个步骤: 生成构建所需文件( generating build files) 编译(compiling) 运行测试用例(running tests) 官方提供两种... -
V8源码分析之d8源码注解(第五篇)
2019-12-20 18:46:370x00 前言 没了你,我颓废了自己。心里那些苦,都只哽在喉咙里,一想起来就泪如雨下。 ----王国维 ...0x00007ffff4a8ea44 in v8::base::LocalKeyToPthreadKey (local_key=32767) at ../../src/base... -
js引擎v8源码分析之GlobalHandles(基于v8 0.1.5)
2020-02-16 01:29:58GlobalHandles是实现v8中持久句柄功能的类。GlobalHandles主要是维护一个链表,每个节点维护堆对象的状态。我们先看看节点的定义。 class GlobalHandles::Node : public Malloced { public: void Initialize... -
js引擎v8源码分析之HeapNumber(基于v8 0.1.5)
2020-02-15 14:17:56v8里有smi保存整形,但是他只有31位,超过31位的就需要用HeapNumber。 // 存储了数字的堆对象 class HeapNumber: public HeapObject { public: inline double value(); inline void set_value(double value); ... -
js引擎v8源码分析之Object(基于v8 0.1.5)
2020-02-14 22:44:321 v8的对象是4字节对齐的,用地址的低两位出来标记对象的类型。 2 堆对象(HeapObject)是Object的子类。Object里面的很多方法都是用于堆对象。堆对象有自己的一套对象类型判断方式。每个堆对象有一个map属性,他...