chrom-V8环境搭建
chrom-V8环境搭建
环境编译
chrome 里面的 JavaScript 解释器称为v8。
我们下载的源码称为V8,而V8经过编译之后得到的可执行文件为 d8。根据编译时选择的不同,编译出来的 d8 分为 debug版本 和 release版本,一般把这两个版本都编译出来。
下载源码
由于需要去谷歌的网站上下载源码,所以需要保证虚拟机能够走代理。我这里直接设置允许局域网连接,将虚拟机设置为NAT,那么就可以上梯子。
随后,依次安装
depot_tools
这个工具是用来得到v8源码的:
1 | git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git |
ninja
这个工具是用来编译v8的:
1 | git clone https://github.com/ninja-build/ninja.git |
然后就是重新加载环境变量:
1 | source ~/.bashrc |
最后就是去下载V8 源代码:
设置环境变量后拉取 v8 的代码 但考虑到中英文问题和一些网络代理问题,这里不安装字体依赖,有需要的师傅可以试着去掉该参数
1 | fetch v8 |
写一个脚本去跑编译,方便以后直接换版本编译:
1 | !/bin/bash |
执行
1 | time ./build.sh 9.6.180.6 |
编译效率一般取决于自己的设备性能
加载补丁
上面的方法,只适用于编译最新的 V8代码。但是对于我们常见的调试漏洞或者解题,往往需要编译特定版本的 V8 或者加载相应的补丁。
需要用如下方法。
一般题目都会给出有漏洞的版本的commitid
,所以编译之前先把源码的版本reset
到和题目一致的版本,在把题目给出的diff
文件应用到源码中:
1 | git reset --hard 6dc88c191f5ecc5389dc26efa3ca0907faef3598 |
调试
将 “v8/tools/gdbinit” 保存到自己惯用的目录下,这里称之为 “path”,然后将路径写到 .gdbinit 下:
1 | cp v8/tools/gdbinit /path/gdbinit_v8 |
做完以后,就能够在源代码中插入如下代码进行调试了:
可以直接在 js
代码中使用%DebugPrint();
以及%SystemBreak();
下断点。%SystemBreak()
其作用是在调试的时候会断在这条语句这里,%DebugPrint()
则是用来打印对象的相关信息,在debug
版本下会输出很详细的信息。
1 | %DebugPrint(x); 打印变量 x 的相关信息 |
但这两条代码并非原有的语法,在执行时需添加参数 “–allow-natives-syntax”, 否则会提示 “SyntaxError: Unexpected token ‘%’”
调试样本
就用一个简单的 demo 测试一下调试能够正常进行:
1 | //demo.js |
我们暂时不用在意这段代码在做什么,这无关紧要,我们现在只想知道调试环境是否能够正常工作而已,所以读者只需要知道有这么个变量名为 f 的变量即可
在 v8/out/x64_$name.release 目录下可以找到二进制程序 d8,它才是解析执行 js 代码的引擎,通过 gdb 去调试该程序,并将 demo.js 作为参数传给它
1 | $ gdb d8 |
可以看到 gdb 正常发生了中断,但由于我们调试的并非 js 脚本,所以自然不可能顺着脚本中断,而是在 d8 的某行机器码处中断了,此时它会打印出数组 f 的数据:
调试命令
job命令
用于可视化显示JavaScript
对象的内存结构。
gdb
下使用:job 对象地址
telescope命令
功能:查看一下内存数据
使用:telescope 查看地址 (长度)
基础知识
指针标记
V8 使用指针标记机制来区分 指针、双精度数 和 Smis(代表) immediate small integer
1 | Double: Shown as the 64-bit binary representation without any changes |
因此,V8 中 如果一个值表示的是指针,那么会将该值的最低bit 设置为1,所以其实真实的值需要减去 1.
Job 直接给对象地址就行,telescope 的时候,需要给真实值,需要 -1.
V8 对象结构
V8 中的对象有如下属性:
1 | map: 定义了如何访问对象 |
分析:
对象里存储的数据是在elemnts
指向的内存区域的,而且是在对象的上面。也即,在内存申请上,V8先申请了一块内存存储元素内容,然后申请了一块内存存储这个数组的对象结构,对象中的elements
指向了存储元素内容的内存地址。
map属性详解
对象的map
(数组是对象)是一种数据结构,其中包含以下信息:
1 | 对象的动态类型,即 String,Uint8Array,HeapNumber 等 |
属性名称通常存储在Map
中,而属性值则存储在对象本身中几个可能区域之一中。然后,map
将提供属性值在相应区域中的确切位置。
本质上,映射定义了应如何访问对象:
对于对象数组:存储的是每个对象的地址
对于浮点数组:以浮点数形式存储数值
所以,如果将对象数组的map
换成浮点数组 -> 就变成了浮点数组,会以 浮点数的形式存储对象的地址;如果将对 浮点组的 map 换成对象数组 -> 就变成了对象数组,打印浮点数存储的地址。
对象和对象数组
也就是说,对象数组里面,存储的是别的对象的地址。