V8 学习笔记 0x01
调试
编译好之后就可以调 exp 了。调试还是用 gdb,只是启动时要把 d8 的参数带上:
gdb --args /path/to/d8 --allow-natives-syntax exp.js
问题是不知道在哪下断点。其实在 JS 层就能断:在 exp.js 里写 %SystemBreak();,跑到那儿就会停下来。
例如,
console.log("hello, key");
%SystemBreak();
console.log("bye, key");
在 gdb 使用 run 命令后就会停在中间,只看到打印 hello, key 而没有 bye, key。
要看变量地址用 %DebugPrint(obj);,它会把对象的内存布局整个打出来。
实验
拿一个数组做样例:
// k.js
arr = [1, 2, 3];
%DebugPrint(arr);
%SystemBreak();
gdb --args /path/to/d8 --allow-natives-syntax k.js
在 gdb 里 run 起来,%DebugPrint(arr) 会把 arr 的布局打出来:
DebugPrint: 0x7b8fb55ab1: [JSArray]
- map: 0x119adca9d981 <Map[32](PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x119adca9d9c9 <JSArray[0]>
- elements: 0x119adcabdb49 <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
- length: 3
- properties: 0x36dda0040e19 <FixedArray[0]>
- All own properties (excluding elements): {
0x36dda0041921: [String] in ReadOnlySpace: #length: 0x061b689879c1 <AccessorInfo name= 0x36dda0041921 <String[6]: #length>, data= 0x36dda0040011 <undefined>> (const accessor descriptor, attrs: [W__])
}
- elements: 0x119adcabdb49 <FixedArray[3]> {
0: 1
1: 2
2: 3
}
0x119adca9d981: [Map] in OldSpace
- map: 0x119adca91559 <MetaMap (0x119adca915e9 <NativeContext[307]>)>
- type: JS_ARRAY_TYPE
- instance size: 32
- inobject properties: 0
- unused property fields: 0
- elements kind: PACKED_SMI_ELEMENTS
- enum length: invalid
- back pointer: 0x36dda0040011 <undefined>
- prototype_validity_cell: 0x36dda0041399 <Cell value= [cleared]>
- instance descriptors #1: 0x119adca9e5f1 <DescriptorArray[1]>
- transitions #1: 0x119adca9e629 <TransitionArray[5]>
Transitions #1:
0x36dda00419f1 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_SMI_ELEMENTS) -> 0x119adca9e661 <Map[32](HOLEY_SMI_ELEMENTS)>
- prototype: 0x119adca9d9c9 <JSArray[0]>
- constructor: 0x119adca9d821 <JSFunction Array (sfi = 0x61b68988c99)>
- dependent code: 0x36dda0040e39 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
打印里 0x7b8fb55ab1 这样一串是带了标记位的地址。用 job 看它指向的 Map 结构,用 tel 看这块内存的字节排布:
pwndbg> job 0x7b8fb55ab1
0x7b8fb55ab1: [JSArray]
- map: 0x119adca9d981 <Map[32](PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x119adca9d9c9 <JSArray[0]>
- elements: 0x119adcabdb49 <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
- length: 3
- properties: 0x36dda0040e19 <FixedArray[0]>
- All own properties (excluding elements): {
0x36dda0041921: [String] in ReadOnlySpace: #length: 0x061b689879c1 <AccessorInfo name= 0x36dda0041921 <String[6]: #length>, data= 0x36dda0040011 <undefined>> (const accessor descriptor, attrs: [W__])
}
- elements: 0x119adcabdb49 <FixedArray[3]> {
0: 1
1: 2
2: 3
}
pwndbg> tel 0x7b8fb55ab0
00:0000│ 0x7b8fb55ab0 —▸ 0x119adca9d981 ◂— 0x40000119adca915
01:0008│ 0x7b8fb55ab8 —▸ 0x36dda0040e19 ◂— 0x36dda0040a
02:0010│ 0x7b8fb55ac0 —▸ 0x119adcabdb49 ◂— 0x36dda0040c
03:0018│ 0x7b8fb55ac8 ◂— 0x300000000
04:0020│ 0x7b8fb55ad0 ◂— 0
... ↓ 3 skipped
job 的输出和上面一致。而 0x7b8fb55ab1 是加了标记位的值,真实地址是 0x7b8fb55ab0:地址最低位是标记位,为 1 表示是指针,为 0 表示是 Smi(小整数)。
由此可以推出 JSArray 有四个字段:map、properties、elements、length。 抽象出来,大概是这样:
struct array {
map* map;
properties* properties;
elements* element;
long long length;
};
length 以 Smi 存放,上面打印的原始值是 0x300000000,右移 32 位才是真实的 3。
先看普通数组:elements 指针和数组对象本身的地址差得很远。但浮点数组和对象数组不一样——它们的 elements 就紧跟在数组对象后面。把实验代码稍作修改就能验证。
arr = [1, 2, 3];
f = [3.0,7.2];
ff = [f, f];
%DebugPrint(arr);
%DebugPrint(f);
%DebugPrint(ff);
%SystemBreak();
通过调试,得到下面的内容
DebugPrint: 0x3e9c5b995ae9: [JSArray]
- map: 0x12f3c6e9d981 <Map[32](PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x12f3c6e9d9c9 <JSArray[0]>
- elements: 0x12f3c6ebdb61 <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
- length: 3
- properties: 0x082a1ad80e19 <FixedArray[0]>
- All own properties (excluding elements): {
0x82a1ad81921: [String] in ReadOnlySpace: #length: 0x2b68715479c1 <AccessorInfo name= 0x082a1ad81921 <String[6]: #length>, data= 0x082a1ad80011 <undefined>> (const accessor descriptor, attrs: [W__])
}
- elements: 0x12f3c6ebdb61 <FixedArray[3]> {
0: 1
1: 2
2: 3
}
0x12f3c6e9d981: [Map] in OldSpace
- map: 0x12f3c6e91559 <MetaMap (0x12f3c6e915e9 <NativeContext[307]>)>
- type: JS_ARRAY_TYPE
- instance size: 32
- inobject properties: 0
- unused property fields: 0
- elements kind: PACKED_SMI_ELEMENTS
- enum length: invalid
- back pointer: 0x082a1ad80011 <undefined>
- prototype_validity_cell: 0x082a1ad81399 <Cell value= [cleared]>
- instance descriptors #1: 0x12f3c6e9e5f1 <DescriptorArray[1]>
- transitions #1: 0x12f3c6e9e629 <TransitionArray[5]>
Transitions #1:
0x082a1ad819f1 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_SMI_ELEMENTS) -> 0x12f3c6e9e661 <Map[32](HOLEY_SMI_ELEMENTS)>
- prototype: 0x12f3c6e9d9c9 <JSArray[0]>
- constructor: 0x12f3c6e9d821 <JSFunction Array (sfi = 0x2b6871548c99)>
- dependent code: 0x082a1ad80e39 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
DebugPrint: 0x3e9c5b995b29: [JSArray]
- map: 0x12f3c6e9e6e1 <Map[32](PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x12f3c6e9d9c9 <JSArray[0]>
- elements: 0x3e9c5b995b09 <FixedDoubleArray[2]> [PACKED_DOUBLE_ELEMENTS]
- length: 2
- properties: 0x082a1ad80e19 <FixedArray[0]>
- All own properties (excluding elements): {
0x82a1ad81921: [String] in ReadOnlySpace: #length: 0x2b68715479c1 <AccessorInfo name= 0x082a1ad81921 <String[6]: #length>, data= 0x082a1ad80011 <undefined>> (const accessor descriptor, attrs: [W__])
}
- elements: 0x3e9c5b995b09 <FixedDoubleArray[2]> {
0: 3 (0x4008000000000000)
1: 7.2 (0x401ccccccccccccd)
}
0x12f3c6e9e6e1: [Map] in OldSpace
- map: 0x12f3c6e91559 <MetaMap (0x12f3c6e915e9 <NativeContext[307]>)>
- type: JS_ARRAY_TYPE
- instance size: 32
- inobject properties: 0
- unused property fields: 0
- elements kind: PACKED_DOUBLE_ELEMENTS
- enum length: invalid
- back pointer: 0x12f3c6e9e661 <Map[32](HOLEY_SMI_ELEMENTS)>
- prototype_validity_cell: 0x082a1ad81399 <Cell value= [cleared]>
- instance descriptors #1: 0x12f3c6e9e5f1 <DescriptorArray[1]>
- transitions #1: 0x12f3c6e9e729 <TransitionArray[5]>
Transitions #1:
0x082a1ad819f1 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x12f3c6e9e761 <Map[32](HOLEY_DOUBLE_ELEMENTS)>
- prototype: 0x12f3c6e9d9c9 <JSArray[0]>
- constructor: 0x12f3c6e9d821 <JSFunction Array (sfi = 0x2b6871548c99)>
- dependent code: 0x082a1ad80e39 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
DebugPrint: 0x3e9c5b995b69: [JSArray]
- map: 0x12f3c6e9e7e1 <Map[32](PACKED_ELEMENTS)> [FastProperties]
- prototype: 0x12f3c6e9d9c9 <JSArray[0]>
- elements: 0x3e9c5b995b49 <FixedArray[2]> [PACKED_ELEMENTS]
- length: 2
- properties: 0x082a1ad80e19 <FixedArray[0]>
- All own properties (excluding elements): {
0x82a1ad81921: [String] in ReadOnlySpace: #length: 0x2b68715479c1 <AccessorInfo name= 0x082a1ad81921 <String[6]: #length>, data= 0x082a1ad80011 <undefined>> (const accessor descriptor, attrs: [W__])
}
- elements: 0x3e9c5b995b49 <FixedArray[2]> {
0-1: 0x3e9c5b995b29 <JSArray[2]>
}
0x12f3c6e9e7e1: [Map] in OldSpace
- map: 0x12f3c6e91559 <MetaMap (0x12f3c6e915e9 <NativeContext[307]>)>
- type: JS_ARRAY_TYPE
- instance size: 32
- inobject properties: 0
- unused property fields: 0
- elements kind: PACKED_ELEMENTS
- enum length: invalid
- back pointer: 0x12f3c6e9e761 <Map[32](HOLEY_DOUBLE_ELEMENTS)>
- prototype_validity_cell: 0x082a1ad81399 <Cell value= [cleared]>
- instance descriptors #1: 0x12f3c6e9e5f1 <DescriptorArray[1]>
- transitions #1: 0x12f3c6e9e829 <TransitionArray[5]>
Transitions #1:
0x082a1ad819f1 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_ELEMENTS) -> 0x12f3c6e9e861 <Map[32](HOLEY_ELEMENTS)>
- prototype: 0x12f3c6e9d9c9 <JSArray[0]>
- constructor: 0x12f3c6e9d821 <JSFunction Array (sfi = 0x2b6871548c99)>
- dependent code: 0x082a1ad80e39 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
输出有点长,分步看:先是 arr。
pwndbg> job 0x3e9c5b995ae9
0x3e9c5b995ae9: [JSArray]
- map: 0x12f3c6e9d981 <Map[32](PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x12f3c6e9d9c9 <JSArray[0]>
- elements: 0x12f3c6ebdb61 <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
- length: 3
- properties: 0x082a1ad80e19 <FixedArray[0]>
- All own properties (excluding elements): {
0x82a1ad81921: [String] in ReadOnlySpace: #length: 0x2b68715479c1 <AccessorInfo name= 0x082a1ad81921 <String[6]: #length>, data= 0x082a1ad80011 <undefined>> (const accessor descriptor, attrs: [W__])
}
- elements: 0x12f3c6ebdb61 <FixedArray[3]> {
0: 1
1: 2
2: 3
}
elements 和 arr 的地址确实差很远,上一次不是偶然。接着看 f:
pwndbg> job 0x3e9c5b995b29
0x3e9c5b995b29: [JSArray]
- map: 0x12f3c6e9e6e1 <Map[32](PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x12f3c6e9d9c9 <JSArray[0]>
- elements: 0x3e9c5b995b09 <FixedDoubleArray[2]> [PACKED_DOUBLE_ELEMENTS]
- length: 2
- properties: 0x082a1ad80e19 <FixedArray[0]>
- All own properties (excluding elements): {
0x82a1ad81921: [String] in ReadOnlySpace: #length: 0x2b68715479c1 <AccessorInfo name= 0x082a1ad81921 <String[6]: #length>, data= 0x082a1ad80011 <undefined>> (const accessor descriptor, attrs: [W__])
}
- elements: 0x3e9c5b995b09 <FixedDoubleArray[2]> {
0: 3 (0x4008000000000000)
1: 7.2 (0x401ccccccccccccd)
}
elements 和 f 的地址只差 0x20,正好是”相邻”:两个 element 占 0x10,map 和 length 两个字段又占 0x10,加起来刚好 0x20。
最后是 ff:
pwndbg> job 0x3e9c5b995b69
0x3e9c5b995b69: [JSArray]
- map: 0x12f3c6e9e7e1 <Map[32](PACKED_ELEMENTS)> [FastProperties]
- prototype: 0x12f3c6e9d9c9 <JSArray[0]>
- elements: 0x3e9c5b995b49 <FixedArray[2]> [PACKED_ELEMENTS]
- length: 2
- properties: 0x082a1ad80e19 <FixedArray[0]>
- All own properties (excluding elements): {
0x82a1ad81921: [String] in ReadOnlySpace: #length: 0x2b68715479c1 <AccessorInfo name= 0x082a1ad81921 <String[6]: #length>, data= 0x082a1ad80011 <undefined>> (const accessor descriptor, attrs: [W__])
}
- elements: 0x3e9c5b995b49 <FixedArray[2]> {
0-1: 0x3e9c5b995b29 <JSArray[2]>
}
和 f 一样,elements 与数组对象相邻。
到这里结论就明确了:浮点数组和对象数组的 elements 紧跟在数组对象后面。只要能把 length 改大,越界读写落到的就是相邻内存——下一篇从这一步往下走。