JSC Butterfly Exercise
Description
In JavaScriptCore, for most objects, the properties and elements are stored as two arrays of JSValues. However, unlike V8, these two arrays are combined into a single data structure called a Butterfly.
One final twist of the data structure is that the pointer points into the middle of the allocation instead of the beginning. On the right side of the pointer (higher memory addresses) is the elements array. On the left side (lower memory addresses) is first the elements array length struct (public and vector length), followed by the property array (which grows backwards).
[ <------------- Properties ] [ Elements Length ] [ Element Array -----------> ]
/|\
|
m_butterfly
Steps
Run JSC under GDB:
exercise run jsc --gdb
Create an object with many (more than 8) properties and elements. Do this by adding new properties to an existing empty object. For example:
a = {}; a.a = 1; a.b = 2; a.c = 3; a.d = 4; a.e = 5; a.f = 6; a.g = 7; a.h = 8; a[0] = 41; a[1] = 42; a[2] = 43;
Note
In JSC, JSObjects are allocated with a set amount of "in-object" (aka "inline") properties. These slots are in the object memory itself rather than in the butterfly. By default a new empty object may have up to 6 in-object property slots. Only once those have been exhausted will the object start to store properties into the butterfly.
Finding the Butterfly with GDB
- Use
print(describe(a))
to find the object's address. Dump the object's memory and find the butterfly pointer. The butterfly will be the second qword of the object. If there is no butterfly this slot will be 0.
Reveal Answer
JSObject memory:
pwndbg> x/32xg 0x7fa22cfb8040
0x7fa22cfb8040: 0x0100170400007ca2 0x00007f986c4f8428
0x7fa22cfb8050: 0xffff000000000001 0xffff000000000002
0x7fa22cfb8060: 0xffff000000000003 0xffff000000000004
0x7fa22cfb8070: 0xffff000000000005 0xffff000000000006
For this object, 0x7f986c4f8428
is the butterfly pointer. You can also see the first 6 properties we added ended up as in-object properties. The rest of the properties are in the butterfly.
You can also get the butterfly's address directly from describe
output:
JavaScript>>> print(describe(a))
Object: 0x7ff92acb80c0 with butterfly 0x7fe02a4f8428 (Structure 0x7ff92acb7db0:[Object, {a:0, b:1, c:2, d:3, e:4, f:5, g:100, h:101}, NonArrayWithInt32, Proto:0x7ff92acbc000, Leaf]), StructureID: 6502
It outputs:
- Object address
- Butterfly address
- Structure address
- Property indexes (values starting at 100 indicate out-of-line slots in the butterfly as opposed to in-object slots)
- Indexing Type (this tells us the encoding type of the elements array)
- Prototype object address
- structure ID
Dumping the Butterfly Using GDB
Now that you have the butterfly pointer, dump memory around it to find the elements and properties:
- Dump memory at the butterfly (elements vector)
- Dump memory at
butterfly - 8
(public/vector lengths) - Dump even further back to find properties (if they exist), e.g. at
butterfly - 0x28
Reveal Answer
Butterfly memory:
pwndbg> x/32xg 0x00007f986c4f8428 - 0x28
0x7f986c4f8400: 0x0000000000000000 0x0000000000000000
0x7f986c4f8410: 0xffff000000000008 0xffff000000000007
0x7f986c4f8420: 0x0000000300000003 0xffff000000000029
0x7f986c4f8430: 0xffff00000000002a 0xffff00000000002b
We subtracted 40 to see some of the data stored before the butterfly's midway-pointer.
- The element array starts at
0x7f986c4f8428
. It contains all the elements we added to the object. - We can see the element array public and vector lengths (both 3) at
0x7f986c4f8420
(butterfly - 8) - The property array starts at
0x7f986c4f8418
(butterfly - 16) and grows backwards. Only the last two properties made it into the butterfly as the rest are in-object properties.
Dumping the Butterfly with $vm.dumpCell()
We can also use a special global variable $vm
to dump more JSCell information: (requires --useDollarVM=1
)
exercise run jsc -- --useDollarVM=1
$vm
has several debug functions attached to it. For this exercise we will use dumpCell
to dump the object and butterfly: $vm.dumpCell(a)
.
Reveal Answer
>>> $vm.dumpCell(a)
<0x7fea111b8040, Object>
[0] 0x7fea111b8040 : 0x01001704000069c3 header
structureID 27075 0x69c3 structure 0x7fea111b6840
indexingTypeAndMisc 4 0x4 NonArrayWithInt32
type 23 0x17
flags 0 0x0
cellState 1
[1] 0x7fea111b8048 : 0x00007fd0183f8428 butterfly
base 0x7fd0183f8400
hasIndexingHeader YES hasAnyArrayStorage NO
publicLength 3 vectorLength 3
preCapacity 0 propertyCapacity 4
<--- propertyCapacity
[0] 0x7fd0183f8400 : 0x0000000000000000
[1] 0x7fd0183f8408 : 0x0000000000000000
[2] 0x7fd0183f8410 : 0xffff000000000008
[3] 0x7fd0183f8418 : 0xffff000000000007
<--- indexingHeader
[4] 0x7fd0183f8420 : 0x0000000300000003
<--- butterfly
<--- indexedProperties
[5] 0x7fd0183f8428 : 0xffff000000000029
[6] 0x7fd0183f8430 : 0xffff00000000002a
[7] 0x7fd0183f8438 : 0xffff00000000002b
[2] 0x7fea111b8050 : 0xffff000000000001
[3] 0x7fea111b8058 : 0xffff000000000002
[4] 0x7fea111b8060 : 0xffff000000000003
[5] 0x7fea111b8068 : 0xffff000000000004
[6] 0x7fea111b8070 : 0xffff000000000005
[7] 0x7fea111b8078 : 0xffff000000000006
Notice how the properties are filled in backwards (starting at the end of the last allocated property slot). This is why the first butterfly property is at 0x7fd0183f8418
instead of 0x7fd0183f8400
. If more properties are added than there is capacity, the butterfly will be reallocated with more space at the beginning.