Chakra除錯筆記 TypedArray

Ox9A82發表於2017-08-08

一.TypedArray型別

TypedArray是漏洞中常見到的結構,手冊用法有四

1.new TypedArray(length);
//byteLength=length * sizeof(TypeName);

length
當傳入length引數時,一個內部陣列緩衝區被建立,該快取區的大小是傳入的length乘以陣列中每個元素的位元組數,每個元素的值都為0.(譯者注:每個元素的位元組數是由具體的建構函式決定的,比如Int16Array的每個元素的位元組數為2,Int32Array的每個元素的位元組數為4)

2.new TypedArray(typedArray); 

typedArray
當傳入一個包含任意型別元素的任意型別化陣列物件(typedArray) (比如 Int32Array)作為引數時,typeArray被複制到一個新的型別陣列。typeArray中的每個值會在複製到新的陣列之前根據構造器進行轉化.新的生成的型別化陣列物件將會有跟傳入的陣列相同的length(譯者注:比如原來的typeArray.length==2,那麼新生成的陣列的length也是2,只是陣列中的每一項進行了轉化)

3.new TypedArray(object); 

object
當傳入一個 object 作為引數時,如同透過 TypedArray.from() 方法一樣建立一個新的型別陣列。

4.new TypedArray(buffer [, byteOffset [, length]]);
//最常見用法,byteOffset、length是位元組數

buffer[, byteOffset, length]
當傳入arrayBuffer和可選引數byteOffset,可選引數length時,一個新的型別化陣列檢視將會被建立,該型別化陣列檢視用於呈現傳入的ArrayBuffer例項。byteOffset和length指定型別化陣列檢視暴露的記憶體範圍,如果兩者都未傳入,那麼整個buffer都會被呈現,如果僅僅忽略length,那麼buffer中偏移(byteOffset)後剩下的buffer將會被呈現.

//MDN規定的型別
Int8Array(); 
Uint8Array(); 
Uint8ClampedArray();
Int16Array(); 
Uint16Array();
Int32Array(); 
Uint32Array(); 
Float32Array(); 
Float64Array();

//但是Chakra在實現上定義如下更多的型別

OBJECT_TYPE(UninitializedObject ) //未初始化時就是這種

// Typed arrays that are optimized by the JIT
OBJECT_TYPE(Int8Array           )
OBJECT_TYPE(Uint8Array          )
OBJECT_TYPE(Uint8ClampedArray   )
OBJECT_TYPE(Int16Array          )
OBJECT_TYPE(Uint16Array         )
OBJECT_TYPE(Int32Array          )
OBJECT_TYPE(Uint32Array         )
OBJECT_TYPE(Float32Array        )
OBJECT_TYPE(Float64Array        )

// Virtual Arrays
//Chakra中一種TypedArray對應兩種OBJECT_TYPE
OBJECT_TYPE(Int8VirtualArray)
OBJECT_TYPE(Uint8VirtualArray)
OBJECT_TYPE(Uint8ClampedVirtualArray)
OBJECT_TYPE(Int16VirtualArray)
OBJECT_TYPE(Uint16VirtualArray)
OBJECT_TYPE(Int32VirtualArray)
OBJECT_TYPE(Uint32VirtualArray)
OBJECT_TYPE(Float32VirtualArray)
OBJECT_TYPE(Float64VirtualArray)

//Mixed Arrays
OBJECT_TYPE(Int8MixedArray)
OBJECT_TYPE(Uint8MixedArray)
OBJECT_TYPE(Uint8ClampedMixedArray)
OBJECT_TYPE(Int16MixedArray)
OBJECT_TYPE(Uint16MixedArray)
OBJECT_TYPE(Int32MixedArray)
OBJECT_TYPE(Uint32MixedArray)
OBJECT_TYPE(Float32MixedArray)
OBJECT_TYPE(Float64MixedArray)

// Typed arrays that are not optimized by the JIT
OBJECT_TYPE(Int64Array)
OBJECT_TYPE(Uint64Array)
OBJECT_TYPE(BoolArray)
OBJECT_TYPE(CharArray)

// SIMD_JS
// SIMD並不是TypedArray,但是與TypedArray在一起處理
// Only Simd128 sub-types. Currently no need to track top Simd128 type
OBJECT_TYPE(Simd128Float32x4    )
OBJECT_TYPE(Simd128Int32x4      )
OBJECT_TYPE(Simd128Int16x8      )
OBJECT_TYPE(Simd128Int8x16      )
OBJECT_TYPE(Simd128Uint32x4     )
OBJECT_TYPE(Simd128Uint16x8     )
OBJECT_TYPE(Simd128Uint8x16     )
OBJECT_TYPE(Simd128Bool32x4     )
OBJECT_TYPE(Simd128Bool16x8     )
OBJECT_TYPE(Simd128Bool8x16     )
OBJECT_TYPE(Simd128Float64x2    ) // !! This is a marker for last SIMD type. Insert new SIMD types above.

二.正文

var tst = new Uint32Array(0x10000);

1.TypedArray<>::NewInstance

率先執行到

template <typename TypeName, bool clamped, bool virtualAllocated>
Var TypedArray<TypeName, clamped, virtualAllocated>::NewInstance(RecyclableObject* function, CallInfo callInfo, ...)

TypedArray類是一個template <typename TypeName, bool clamped, bool virtualAllocated>模版類
如此設計是為了TypeName可以指定不同的種類如Uint32Int16

TypedArray<>::NewInstance是一個public static方法,提供外部呼叫建立TypedArray

public:
 static Var NewInstance(RecyclableObject* function, CallInfo callInfo, ...);

TypedArray::NewInstance上來首先獲取ScriptContextThreadContext

Var TypedArray<TypeName, clamped, virtualAllocated>::NewInstance(RecyclableObject* function, CallInfo callInfo, ...)
{
    function->GetScriptContext()->GetThreadContext()->ProbeStack(Js::Constants::MinStackDefault, function->GetScriptContext());
    ARGUMENTS(args, callInfo);
    ScriptContext* scriptContext = function->GetScriptContext();

ThreadContext
ThreadContext在CreateRuntimeCore裡,我們可以看到,在建立JsrtRuntime之前我們需要建立一個ThreadContext,而主要的初始化都是在ThreadContext上進行的。在ThreadContext裡面,我們還可以看到比JsrtRuntime多得多的成員變數,並且有很多我們都非常感興趣,比如:

  • 和記憶體管理相關的Recycler,各種Page Allocator * 和控制流相關的異常資訊 * 各種統計資訊
    我們馬上會提到的JsrtContext的主要實現——ScriptContext的列表
    ……等等

可以看出,上面我們提到的Runtime提供的主要功能基本都在ThreadContext裡面,可以說它是JsrtRuntime的主要實現。而透過程式碼我們可以看得到,JsrtRuntime和ThreadContext是一對一的,所以在讀ChakraCore的程式碼時,我們基本可以把他們認為是一個東西。

ScriptContext
雖然在ScriptContext並沒有直接被JsrtContext所持有,而是放在了JavascriptLibrary之中,但是我們還是先來看看這個類,因為這個類其實更加重要也更加的靠上層。
在JsrtContext的建構函式里面,我們可以看到第一步就是建立ScriptContext,而在銷燬JsrtContext時,其主要做的事情也是由ScriptContext來完成的,可見ScriptContext其實就是JsrtContext的真實實現。(其實看名字我們也看的出來……)
還記得JsrtContext提供的功能麼?在ScriptContext中,我們都可以在其成員變數中找到蹤跡:

  • globalObject:這個就是瀏覽器裡JavaScript中的window變數。
    url:當前ScriptContext的建立者的URL。
    sourceList:用於儲存每個ScriptContext中載入的程式碼。

之後傳遞TypedArray<>::Create函式指標進入TypedArrayBase::CreateNewInstance

Var object = TypedArrayBase::CreateNewInstance(args, scriptContext, sizeof(TypeName), TypedArray<TypeName, clamped, virtualAllocated>::Create);

2.TypedArrayBase::CreateNewInstance

static Var CreateNewInstance(Arguments& args, ScriptContext* scriptContext, uint32 elementSize, PFNCreateTypedArray pfnCreateTypedArray );

第一次進入TypedArrayBase::CreateNewInstance時,arrayBuffer為空。因此會執行
scriptContext->GetLibrary()->CreateArrayBuffer(byteLength)

if (arrayBuffer != nullptr)
{
          
}
else
{
    // Null arrayBuffer - could be new constructor or copy constructor.
    byteLength = elementCount * elementSize;
    arrayBuffer = scriptContext->GetLibrary()->CreateArrayBuffer(byteLength);
}

其中前兩次的函式呼叫
scriptContext ScriptContext
GetLibrary() JavascriptLibrary
最後的CreateArrayBuffer函式,是從JavascriptLibrary中呼叫的
ArrayBuffer* JavascriptLibrary::CreateArrayBuffer(uint32 length)
這個函式是
JavascriptArrayBuffer::Create
的簡單封裝。

3.JavascriptArrayBuffer::Create

JavascriptArrayBuffer* JavascriptArrayBuffer::Create(uint32 length, DynamicType * type)
{
    Recycler* recycler = type->GetScriptContext()->GetRecycler();
    JavascriptArrayBuffer* result = RecyclerNewFinalized(recycler, JavascriptArrayBuffer, length, type);
    Assert(result);
    recycler->AddExternalMemoryUsage(length);
    return result;
    }
    

函式在透過ScriptContext獲取到Memory:Recycler之後呼叫了RecyclerNewFinalized
RecyclerNewFinalized函式內部則呼叫了經過過載的new運算子,如下

template <typename TAllocator>
_Ret_notnull_
NO_EXPORT(void *) __cdecl
operator new(DECLSPEC_GUARD_OVERFLOW size_t byteSize, TAllocator * alloc, char * (TAllocator::*AllocFunc)(size_t))
{
    AssertCanHandleOutOfMemory();
    Assert(byteSize != 0);
    void * buffer = (alloc->*AllocFunc)(byteSize);
    Assume(buffer != nullptr);
    return buffer;
}

這裡分配了72個位元組,使用的是custom heap的記憶體管理,分配出來的是JavascriptArrayBuffer物件。new在分配了記憶體之後開始呼叫JavascriptArrayBuffer的建構函式。
經過一系列建構函式的繼承關係後,最後會呼叫到ArrayBuffer::ArrayBuffer()
這個函式傳遞了lengthallocator兩引數,最後呼叫buffer = (BYTE*)allocator(length)

template <class Allocator>
    ArrayBuffer::ArrayBuffer(uint32 length, DynamicType * type, Allocator allocator) :
        ArrayBufferBase(type)
    {
        buffer = nullptr;
        bufferLength = 0;
        if (length > MaxArrayBufferLength)
        {
            JavascriptError::ThrowTypeError(GetScriptContext(), JSERR_FunctionArgument_Invalid);
        }
        else if (length > 0)
        {
            Recycler* recycler = GetType()->GetLibrary()->GetRecycler();
            if (recycler->ReportExternalMemoryAllocation(length))
            {
                buffer = (BYTE*)allocator(length);
                if (buffer == nullptr)
                {
                    recycler->ReportExternalMemoryFree(length);
                }
            }

            if (buffer == nullptr)
            {
                recycler->CollectNow<CollectOnTypedArrayAllocation>();

                if (recycler->ReportExternalMemoryAllocation(length))
                {
                    buffer = (BYTE*)allocator(length);
                    if (buffer == nullptr)
                    {
                        recycler->ReportExternalMemoryFailure(length);
                    }
                }
            }

            if (buffer != nullptr)
            {
                bufferLength = length;
                ZeroMemory(buffer, bufferLength);
            }
            else
            {
                JavascriptError::ThrowOutOfMemoryError(GetScriptContext());
            }
        }
    }

注意這裡的建構函式是這樣進行傳參的

JavascriptArrayBuffer::JavascriptArrayBuffer(uint32 length, DynamicType * type) :
        ArrayBuffer(length, type, IsValidVirtualBufferLength(length) ? AsmJsVirtualAllocator : malloc)
    {
    }

AsmJsVirtualAllocator是一個宏

#define AsmJsVirtualAllocator ((AllocWrapperType)Js::ArrayBuffer::AllocWrapper<MAX_ASMJS_ARRAYBUFFER_LENGTH>)

跟進buffer = (BYTE*)allocator(length)之後會進入AllocWrapper這個函式

template<size_t MaxVirtualSize = MAX_ASMJS_ARRAYBUFFER_LENGTH>
static void* __cdecl AllocWrapper(DECLSPEC_GUARD_OVERFLOW size_t length)
{
    LPVOID address = VirtualAlloc(nullptr, MaxVirtualSize, MEM_RESERVE, PAGE_NOACCESS);
    //throw out of memory
    if (!address)
    {
        return nullptr;
    }

    if (length == 0)
    {
        return address;
    }

    LPVOID arrayAddress = VirtualAlloc(address, length, MEM_COMMIT, PAGE_READWRITE);
    if (!arrayAddress)
    {
        VirtualFree(address, 0, MEM_RELEASE);
        return nullptr;
    }
    return arrayAddress;
}
    
#define MAX_ASMJS_ARRAYBUFFER_LENGTH 0x100000000 // 4GB

注意這個函式兩次呼叫了VirtualAlloc,第一次是RESERVE,第二次是COMMIT。分配的思路就是無論申請多大記憶體,只要滿足VirtualArray的範圍那麼就RESERVE 4GB的地址空間,之後再有需要多少直接COMMIT就可以了。
之後再跳回到ArrayBuffer::ArrayBuffer中,執行ZeroMemory清空分配出來的記憶體。
這裡實現的是VirtualBuffer的分配

if (buffer != nullptr)
{
    bufferLength = length;
    ZeroMemory(buffer, bufferLength);
}

4.TypedArray<>::Create

在經過上面的一系列分配之後,執行流程返回到TypedArrayBase::CreateNewInstance函式中去。
之後在TypedArrayBase::CreateNewInstance函式中執行了如下流程

byteLength = elementCount * elementSize;    

if (mappedLength == -1)
{
    mappedLength = (byteLength - offset)/elementSize;
}

// Create and set the array based on the source.
TypedArrayBase* newArray  = static_cast<TypedArrayBase*>(pfnCreateTypedArray(arrayBuffer, offset, mappedLength, scriptContext->GetLibrary()));

mappedLength也就是等於byteLength,之後在呼叫pfnCreateTypedArray函式時傳遞了之前建立的arrayBuffer

arrayBuffer = scriptContext->GetLibrary()->CreateArrayBuffer(byteLength);

分配出來的arrayBuffer是 ArrayBufferBase*,在後面可以看到ArrayBufferBase物件是建立TypedArray的基礎

pfnCreateTypedArray其實是

template <typename TypeName, bool clamped, bool virtualAllocated>
Var TypedArray<TypeName, clamped, virtualAllocated>::Create(ArrayBufferBase* arrayBuffer, uint32 byteOffSet, uint32 mappedLength, JavascriptLibrary* javascriptLibrary)

首先計算mappedByteLength=元素個數*單個元素尺寸,然後計算totalLength=byteOffSet+mappedByteLength

if (UInt32Math::Mul(mappedLength, sizeof(TypeName), &mappedByteLength) ||
            UInt32Math::Add(byteOffSet, mappedByteLength, &totalLength) ||
            (totalLength > arrayBuffer->GetByteLength()))

之後依然是呼叫RecyclerNew來分配記憶體,這個函式依然會呼叫過載後的new運算子分配64個位元組,分配出的記憶體作為TypedArray view物件

 DynamicType *type = javascriptLibrary->GetTypedArrayType<TypeName, clamped>(0);
 return RecyclerNew(javascriptLibrary->GetRecycler(), TypedArray, arrayBuffer, byteOffSet, mappedLength, type)

5.TypedArray<>::TypedArray

在new分配了TypedArray物件的記憶體後,就呼叫它的建構函式

template <typename TypeName, bool clamped, bool virtualAllocated>
TypedArray<TypeName, clamped, virtualAllocated>::TypedArray(ArrayBufferBase* arrayBuffer, uint32 byteOffset, uint32 mappedLength, DynamicType* type) :TypedArrayBase(arrayBuffer, byteOffset, mappedLength, sizeof(TypeName), type)

依據不同的Typed型別來設定屬性

switch (type->GetTypeId())
{
    case TypeIds_Int8Array:
        VirtualTableInfo<Int8VirtualArray>::SetVirtualTable(this);
        break;
    case TypeIds_Uint8Array:
        VirtualTableInfo<Uint8VirtualArray>::SetVirtualTable(this);
        break;
    case TypeIds_Uint8ClampedArray:
        VirtualTableInfo<Uint8ClampedVirtualArray>::SetVirtualTable(this);
        break;
    case TypeIds_Int16Array:
        VirtualTableInfo<Int16VirtualArray>::SetVirtualTable(this);
        break;
    case TypeIds_Uint16Array:
        VirtualTableInfo<Uint16VirtualArray>::SetVirtualTable(this);
        break;
    case TypeIds_Int32Array:
        VirtualTableInfo<Int32VirtualArray>::SetVirtualTable(this);
        break;
    case TypeIds_Uint32Array:
        VirtualTableInfo<Uint32VirtualArray>::SetVirtualTable(this);
        break;
    case TypeIds_Float32Array:
        VirtualTableInfo<Float32VirtualArray>::SetVirtualTable(this);
        break;
    case TypeIds_Float64Array:
        VirtualTableInfo<Float64VirtualArray>::SetVirtualTable(this);
        break;
    default:
        break;
}

最後返回的是TypedArray*的指標,至此TypedArray建立成功

物件繼承關係

JavascriptArrayBuffer:
ArrayBuffer:
ArrayBufferBase:
DynamicObject:
RecyclableObject:
FinalizableObject

呼叫總覽
1.建立JavascriptArrayBuffer物件
2.建立Virtual Buffer
3.建立TypedArray物件

Virtual buffer建立流程

kernel32.dll!VirtualAlloc
ChakraCore.dll!Js::ArrayBufferBase::AllocWrapper<4294967296>()
ChakraCore.dll!Js::ArrayBuffer::ArrayBuffer<void * __ptr64 (__cdecl*)(unsigned __int64)>()
ChakraCore.dll!Js::JavascriptArrayBuffer::JavascriptArrayBuffer()
ChakraCore.dll!Js::JavascriptArrayBuffer::Create()
ChakraCore.dll!Js::JavascriptLibrary::CreateArrayBuffer()
ChakraCore.dll!Js::TypedArrayBase::CreateNewInstance()
ChakraCore.dll!Js::TypedArray<unsigned int,0,0>::NewInstance()
ChakraCore.dll!amd64_CallFunction()

TypedArray建立流程

ChakraCore.dll!Memory::Recycler::AllocWithAttributesInlined<0,0>()
ChakraCore.dll!Memory::Recycler::AllocInlined(unsigned __int64 size) 
ChakraCore.dll!operator new<Memory::Recycler>()
ChakraCore.dll!Js::TypedArray<unsigned int,0,0>::Create()
ChakraCore.dll!Js::TypedArrayBase::CreateNewInstance() 
ChakraCore.dll!Js::TypedArray<unsigned int,0,0>::NewInstance()
ChakraCore.dll!amd64_CallFunction() 

JavascriptArrayBuffer建立流程

ChakraCore.dll!Js::JavascriptArrayBuffer::Create() 
ChakraCore.dll!Js::JavascriptLibrary::CreateArrayBuffer()
ChakraCore.dll!Js::TypedArrayBase::CreateNewInstance() 
ChakraCore.dll!Js::TypedArray<unsigned int,0,0>::NewInstance()
ChakraCore.dll!amd64_CallFunction()

相關文章