🔗 V8 Pointer Tagging Exercise

🔗 Description

In v8, JSValues are "Tagged Values" (aka "Tagged Pointers") which can either be a pointer or an integer. If the Least Significant Bit is set, the value (minus that bit) is a pointer. Otherwise the value is a shifted integer.

  • For 32 bit JSValues, integers are encoded as: tagged_jsval = some_int << 1

  • For 64 bit JSValues, integers are encoded as: tagged_jsval = some_int << 32

  • For both 32 bit and 64 bit JSValues, pointers are encoded as: tagged_jsval = some_ptr | 1

🔗 Steps

First let's practice decoding some Tagged JSValues:

  • What is the decoded value of this 64 bit JSValue: 0x0414243400000000
    Reveal Answer The integer 0x4142434
  • What is the decoded value of this 32 bit JSValue: 0x41424340
    Reveal Answer The integer 0x20a121a0 (which is 0x41424340 >> 1)
  • What is the decoded value of this 64 bit JSValue: 0x00001b4a72c8b4d9
    Reveal Answer The pointer 0x1b4a72c8b4d8

For JavaScript objects, the properties and other pointers are usually stored as tagged JSValues. If we construct an object with several properties of different types, we should be able to see how they are encoded in memory.

Warningwarning

If you haven't already, read through the VM overview to understand the VM / exercise setup.

Run the V8 REPL under GDB:

exercise run v8 --gdb
  • Create an object with several properties of different types. Make sure to have at least one small-ish integer (< 31 bits aka SMI) as well as a reference type. For example foo = {a:0x414243, b:{}, c: 1.1, d:null}.
Warningwarning

Integers > 30 bits will be converted to a 64 bit double and the JSValue will be pointer to a heap allocation

  • Print the address of the object by calling %DebugPrint(o)

  • Break in gdb (with ctrl-c) and print memory at the object's address (e.g. x/16gx <object-addr>).

  • Locate the integer property (it should be shifted up as a 64-bit tagged value). Does it match the "tagged SMI" value you expected?

  • Examine how other properties are stored as "tagged pointers". Take note of how:

    • Each pointer has 1 added to it
    • The floating point number (c: 1.1) is actually a pointer to a heap allocation
      • Bonus: Dump memory at that pointer and you'll find the 64-bit float!
    • Other pointers in the object are also tagged pointers (for example the first QWORD in the dump)