Pointer Compression in V8

 


1. 관련글

 

 


2. V8 힙

 

자바 스크립트로 생성한 객체, 배열, 함수는 V8 힙에 위치한다.

조금 더 덧붙이면, 자바 스크립트에서는 배열, 함수도 객체이다.

V8 힙은 GDB 등의 디버거에서 [heap]으로 표시되는 메모리 영역과는 다른 영역이다.

위에서 V8 힙은 상위 32비트가 0x00001f08로 표시되는 모든 메모리 영역으로, 보통 프로그램의 최하위 메모리 주소에 매핑된다.

V8에서 메모리는 모두 격리(isolate)된다고 한다. 위에서 보다시피 최하위 메모리 주소에 매핑되며, 바이너리의 텍스트 세그먼트가 바로 다음에 매핑된다.

V8 힙의 주소는 실행될 때마다 변경되지만, 상위 32비트(0x00001f08)는 실행 중에 변경되지 않는다.

 


3. 포인터 압축

 

따라서, V8 힙 포인터를 저장할 때 하위 32비트만 저장하기로 하였다.

이를 포인터 압축이라고 하며, 포인터 압축을 통해 힙 메모리 사용량을 40%까지 절약하였다고 한다.

V8 힙 주소의 상위 32비트를 격리 루트(isolate root)라고 부른다.

격리 루트는 특정 레지스터(R13)에 저장하며, 이 레지스터를 루트 레지스터라고 부른다.

포인터 압축의 단점은 V8 힙의 사이즈가 최대 4GB(32비트만 주소로 사용)로 제한된다는 것이다.

이런 단점 때문에, node.js는 포인터 압축을 사용하지 않는다.

포인터 압축과 관련된 코드는 아래의 위치에 구현되어 있다.

  • v8/src/common/prt-compr.h
  • v8/src/common/ptr-compr-inl.h

 


4. 포인터 압축과 V8 익스플로잇

 

자바 스크립트를 통해 격리 루트를 알아내는 건 어렵다. 하지만, 격리 루트를 굳이 알아야할 필요는 없다.

addrof 또는 fakeobj가 가능하다면, 가짜 JSArray를 만든 후 엘리먼트 포인터를 수정하여 임의의 주소에 데이터를 쓰고, 읽을 수 있다.

JSArray의 엘리먼트 포인터에는 32비트로 압축된 포인터가 저장된다.

따라서, V8 힙 내부의 주소에만 데이터를 쓰고, 읽을 수 있다.

이를 극복하기 위한 기본적인 방법은 V8 힙에 ArrayBuffer를 할당하는 것이다.

ArrayBuffer의 엘리먼트 포인터는 PartitionAlloc를 사용해서 할당되기 때문이다.

PartitionAlloc은 V8 힙이 아닌 메모리 영역에 할당한다.

즉, ArrayBuffer의 엘리먼트 포인터에 압축되지 않은 64비트 포인터를 저장하여, V8 힙 이외의 메모리 영역에 접근할 수 있다.